mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-26 17:44:24 -04:00
Compare commits
334 Commits
v2.0.0.285
...
v2.0.0.315
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5278a0243 | ||
|
|
7c246abc88 | ||
|
|
475f4244c4 | ||
|
|
f57dea7f1f | ||
|
|
f1a5261e0a | ||
|
|
fe5cb9503c | ||
|
|
944a775625 | ||
|
|
c8c17bce7e | ||
|
|
366e3ed0be | ||
|
|
cef6eb7509 | ||
|
|
f88e2e2b79 | ||
|
|
57bcc9f4c1 | ||
|
|
69dd1c6ec4 | ||
|
|
761a106fa9 | ||
|
|
5cd2d71e6f | ||
|
|
96578ca59b | ||
|
|
213f905767 | ||
|
|
9d980a8ac7 | ||
|
|
c4e1a732dd | ||
|
|
9f73b2b7f0 | ||
|
|
149c149094 | ||
|
|
ee224cb422 | ||
|
|
335be1c85d | ||
|
|
a79fc94a54 | ||
|
|
c3acfe34fe | ||
|
|
6e7a2af86b | ||
|
|
cce280d260 | ||
|
|
852f97012f | ||
|
|
f221b00795 | ||
|
|
429298c68c | ||
|
|
af060d73cc | ||
|
|
e98a174884 | ||
|
|
92a23d0f8b | ||
|
|
d5ba11bd51 | ||
|
|
a8aac36379 | ||
|
|
23c6da4746 | ||
|
|
cf9391a7a3 | ||
|
|
d0bf539a73 | ||
|
|
95bd82778f | ||
|
|
b73413f189 | ||
|
|
df4604057e | ||
|
|
1e2ba691ed | ||
|
|
6abda8adef | ||
|
|
84128482f4 | ||
|
|
a184021621 | ||
|
|
c4f8e44f55 | ||
|
|
b359e1c175 | ||
|
|
6dd22e7dcb | ||
|
|
29ca1bc9da | ||
|
|
bc03ad2a18 | ||
|
|
14f49489a7 | ||
|
|
4356da039f | ||
|
|
587aff602a | ||
|
|
1275d8098d | ||
|
|
0c6ca6971d | ||
|
|
f5fde97f68 | ||
|
|
50dc4c4f3d | ||
|
|
c08d8252ff | ||
|
|
2a83088045 | ||
|
|
4ca8178ca8 | ||
|
|
bb48491eb2 | ||
|
|
0534fb4330 | ||
|
|
8b7eedf6f9 | ||
|
|
6ab629ea98 | ||
|
|
97cbdfdc5c | ||
|
|
25c77711cd | ||
|
|
2e6cf2b7f6 | ||
|
|
6592310f2b | ||
|
|
918fcac2aa | ||
|
|
4b7ee3cb9e | ||
|
|
27246de623 | ||
|
|
b1a0e759ef | ||
|
|
f90fdef50d | ||
|
|
702b4429ac | ||
|
|
4eff8d88d1 | ||
|
|
235a986679 | ||
|
|
c3e0dbc173 | ||
|
|
e296d94417 | ||
|
|
0e865fff8c | ||
|
|
9a629c2fc6 | ||
|
|
ecd941a6e5 | ||
|
|
2feb583e45 | ||
|
|
23daae05cc | ||
|
|
b4e8a39c2c | ||
|
|
2f7e3c1c3c | ||
|
|
8a0e873eb5 | ||
|
|
10214bff42 | ||
|
|
1fc99fd24e | ||
|
|
e40508e5e9 | ||
|
|
65f1dbde00 | ||
|
|
4c9f13cb26 | ||
|
|
f831dbd789 | ||
|
|
f18ad21b48 | ||
|
|
61ae7dc189 | ||
|
|
e304a615d0 | ||
|
|
c12f16b6d3 | ||
|
|
c43296ffe9 | ||
|
|
ab6233dd3f | ||
|
|
2a4fd2bbde | ||
|
|
c3d15015fe | ||
|
|
f30e7bc701 | ||
|
|
ee87537848 | ||
|
|
d4532c3856 | ||
|
|
20e40f73b3 | ||
|
|
923488bc02 | ||
|
|
b62d36bdbe | ||
|
|
62f4fc5e58 | ||
|
|
cfefed34fc | ||
|
|
c58d607349 | ||
|
|
f13a4b5aa5 | ||
|
|
9cf575c096 | ||
|
|
2c52ac1a7b | ||
|
|
7e0c833ad0 | ||
|
|
7f38617d76 | ||
|
|
8aa6969aee | ||
|
|
0adea0ded6 | ||
|
|
ccfd66260d | ||
|
|
a6d2283be8 | ||
|
|
b92cc6fb15 | ||
|
|
f2ec02876b | ||
|
|
7378a98e07 | ||
|
|
42a3ff2625 | ||
|
|
4448e87e28 | ||
|
|
90b047f0d4 | ||
|
|
adfaa00ce1 | ||
|
|
755a42ea45 | ||
|
|
210524b51a | ||
|
|
a1a91878ad | ||
|
|
216286db5e | ||
|
|
061c40c8f4 | ||
|
|
15eeb19cd5 | ||
|
|
93c6047cd5 | ||
|
|
db4746bef7 | ||
|
|
971e159fa4 | ||
|
|
a4deea2333 | ||
|
|
6114952012 | ||
|
|
fcc1439754 | ||
|
|
06a2cb0de4 | ||
|
|
9dd66879a2 | ||
|
|
5d03c94b26 | ||
|
|
679455713e | ||
|
|
9aeda7aaba | ||
|
|
ca8e16a5be | ||
|
|
52ec1cf1b1 | ||
|
|
1efb7446c9 | ||
|
|
c5f2c2823e | ||
|
|
beb4aee4c9 | ||
|
|
638e3ca898 | ||
|
|
446d470f53 | ||
|
|
99d9303394 | ||
|
|
8dccf2efe8 | ||
|
|
c7470a426a | ||
|
|
37e4a06b5d | ||
|
|
6e5e781245 | ||
|
|
61c263856b | ||
|
|
593c4b8182 | ||
|
|
a3873634b0 | ||
|
|
36ac4f0a8d | ||
|
|
96469be7f0 | ||
|
|
d1ce1bf218 | ||
|
|
74ad841be4 | ||
|
|
174d1fb0cc | ||
|
|
dc2c1f7928 | ||
|
|
2b6c915e32 | ||
|
|
2a12aca66a | ||
|
|
f5f050f80b | ||
|
|
d8852d840b | ||
|
|
a45f822fdb | ||
|
|
bf5b645416 | ||
|
|
aa7aa3ce61 | ||
|
|
69edfba237 | ||
|
|
6393f66448 | ||
|
|
f39e99bd3c | ||
|
|
112cde1cee | ||
|
|
ebbaa403f6 | ||
|
|
dcc988da06 | ||
|
|
dd6dc38672 | ||
|
|
1d70c97983 | ||
|
|
799f6034c7 | ||
|
|
dc80377a4c | ||
|
|
ca9c39984e | ||
|
|
f021f9b146 | ||
|
|
4036654f3f | ||
|
|
8b1e0f68dd | ||
|
|
ee02e6a31c | ||
|
|
6803e46782 | ||
|
|
a8e805fd5d | ||
|
|
d8662321be | ||
|
|
46da14f3bf | ||
|
|
8438ee0a52 | ||
|
|
6ba78f6aed | ||
|
|
7b0bc4334d | ||
|
|
2e81e278e1 | ||
|
|
8d16b8b9d6 | ||
|
|
a7a6a08807 | ||
|
|
f4573545cd | ||
|
|
a0ac27f9fe | ||
|
|
3b737d4b66 | ||
|
|
72993f3c3c | ||
|
|
03335b2a71 | ||
|
|
aa52a83395 | ||
|
|
14a99a28cc | ||
|
|
be338a651e | ||
|
|
402d6b5411 | ||
|
|
e143b18df3 | ||
|
|
4d837a46af | ||
|
|
ca05c15340 | ||
|
|
2da23ae230 | ||
|
|
a3d649452f | ||
|
|
42f9992af0 | ||
|
|
6095855102 | ||
|
|
30849e6356 | ||
|
|
e466c77487 | ||
|
|
53029fe928 | ||
|
|
8ab0b26773 | ||
|
|
47a0f55255 | ||
|
|
132c876ca9 | ||
|
|
015deacd7b | ||
|
|
c4ce64d98d | ||
|
|
4108bbac7a | ||
|
|
e58576bcfa | ||
|
|
dea58ed663 | ||
|
|
1837ba94cc | ||
|
|
33ed76556f | ||
|
|
59f487392e | ||
|
|
1f8f52ac9b | ||
|
|
bf65807ef3 | ||
|
|
4e6466e10c | ||
|
|
32fc68b9df | ||
|
|
d6079a701c | ||
|
|
019525dd9d | ||
|
|
b69ea349ce | ||
|
|
1bf433872a | ||
|
|
860f55996c | ||
|
|
29d9e3dadf | ||
|
|
70bfad4e6a | ||
|
|
44928c8f64 | ||
|
|
a5fd28326e | ||
|
|
fb7988edb8 | ||
|
|
7b7f199587 | ||
|
|
7b5c0a952b | ||
|
|
83b8ab8fe9 | ||
|
|
f5118fc430 | ||
|
|
b556eda4a0 | ||
|
|
b308f06af3 | ||
|
|
6959f6e13a | ||
|
|
b7e609a7d5 | ||
|
|
3ed8f0ea84 | ||
|
|
1b3993bf6a | ||
|
|
d1df5ed7cd | ||
|
|
539de9e124 | ||
|
|
37959fd753 | ||
|
|
32dd545ef9 | ||
|
|
408a4e0a81 | ||
|
|
cc21d83e69 | ||
|
|
29fe7b2acd | ||
|
|
00dddfeaf3 | ||
|
|
58f0e713fa | ||
|
|
e00ec8b01b | ||
|
|
77edf53c6a | ||
|
|
8bf1d512c2 | ||
|
|
db4eadac40 | ||
|
|
573c2b8f60 | ||
|
|
d3c1deb203 | ||
|
|
b5cfa72c31 | ||
|
|
394b93628c | ||
|
|
6807f92f55 | ||
|
|
ec88c2c2ca | ||
|
|
5750f012cb | ||
|
|
b4d3a41213 | ||
|
|
20782bbbc1 | ||
|
|
5b54b02d7e | ||
|
|
811ce8fa22 | ||
|
|
3fab46a740 | ||
|
|
40987cc335 | ||
|
|
3ddc01e3f4 | ||
|
|
6867319c8d | ||
|
|
8d03850de7 | ||
|
|
8f8fe99a16 | ||
|
|
0829bb6e41 | ||
|
|
fcb4f8fd58 | ||
|
|
e0dd72328c | ||
|
|
672e1bd9ed | ||
|
|
e4a93ded28 | ||
|
|
77fdd724f4 | ||
|
|
955029ec43 | ||
|
|
52a71a4e96 | ||
|
|
f7bdf635b3 | ||
|
|
071839fa86 | ||
|
|
2f06cc6ffa | ||
|
|
7ce9f416d1 | ||
|
|
8833f1ad31 | ||
|
|
a058333f65 | ||
|
|
6e179839d9 | ||
|
|
3a938e18fa | ||
|
|
11803afc39 | ||
|
|
104d35299b | ||
|
|
127e38feb7 | ||
|
|
0934900cab | ||
|
|
15b0bc0333 | ||
|
|
ec413c13bd | ||
|
|
f5ddb36ebd | ||
|
|
6c22a5ffdb | ||
|
|
9ac3a1e094 | ||
|
|
d5cc261985 | ||
|
|
fd02f0ae55 | ||
|
|
2a4d7b82ea | ||
|
|
c9d21c7863 | ||
|
|
d4a4a4095e | ||
|
|
9ca97d0135 | ||
|
|
c1467d0ecd | ||
|
|
327802fae4 | ||
|
|
145fa76614 | ||
|
|
341daf69d1 | ||
|
|
33bb64984a | ||
|
|
de49a5b8f6 | ||
|
|
199c6c84b2 | ||
|
|
095e92c7dd | ||
|
|
a7001ab322 | ||
|
|
bc037e7319 | ||
|
|
b3f11564a7 | ||
|
|
754c1ea331 | ||
|
|
aa9df49ea2 | ||
|
|
85f6d90f40 | ||
|
|
ed4bf349db | ||
|
|
e6566b26d8 | ||
|
|
66a9cddf01 | ||
|
|
428a1439e5 | ||
|
|
344f3d66ef | ||
|
|
27d3ecf6b2 | ||
|
|
05ee57a972 | ||
|
|
7b7f7ac56b | ||
|
|
53f4966e97 | ||
|
|
3c756348eb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -122,3 +122,4 @@ setup/Output/
|
||||
#VS outout folders
|
||||
bin
|
||||
obj
|
||||
output/*
|
||||
|
||||
@@ -33,8 +33,13 @@ Setup guides, FAQ, the more information we have on the wiki the better.
|
||||
- Use 4 spaces instead of tabs, this is the default for VS 2012 and WebStorm (to my knowledge)
|
||||
|
||||
### Pull Requesting ###
|
||||
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
|
||||
- Each PR comes from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork
|
||||
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
|
||||
- new-feature (Good)
|
||||
- fix-bug (Good)
|
||||
- patch (Bad)
|
||||
- develop (Bad)
|
||||
|
||||
If you have any questions about any of this, please let us know.
|
||||
|
||||
240
Logo/Sonarr.svg
Normal file
240
Logo/Sonarr.svg
Normal file
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="218px"
|
||||
height="218px" viewBox="0 0 218 218" enable-background="new 0 0 218 218" xml:space="preserve">
|
||||
<symbol id="hex_grid" viewBox="-114.25 -98.617 228.55 197.233">
|
||||
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#989898" stroke-width="0.5" stroke-linecap="square" stroke-miterlimit="1" d="
|
||||
M72.15,90.3l4.7-2.7l4.65,2.7v5.4l-4.65,2.7l-4.7-2.7V90.3z M62.85,95.7l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7V95.7l4.65,2.7
|
||||
l4.65-2.7 M62.85,90.3l4.65-2.7l4.65,2.7 M62.85,79.55v-5.4l4.65-2.7l4.65,2.7v5.4L67.5,82.2L62.85,79.55L58.2,82.2l-4.65-2.65
|
||||
M72.15,74.15l4.7-2.7l4.65,2.7v5.4l-4.65,2.65l-4.7-2.65 M76.85,87.6v-5.4 M67.5,87.6v-5.4 M81.5,95.7l4.65,2.7l4.65-2.7l4.65,2.7
|
||||
l4.65-2.7l4.65,2.7l4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4
|
||||
l4.65-2.7v-5.4L109.4,31v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7
|
||||
V-31l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4
|
||||
l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7
|
||||
l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7L7-98.4l-4.65,2.7l-4.65-2.7
|
||||
l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7
|
||||
l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7v5.4l-4.65,2.7
|
||||
v5.4l4.65,2.65v5.4l-4.65,2.7v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65
|
||||
v5.4l-4.65,2.7v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7V31l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7
|
||||
v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7v5.4l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.7,2.7
|
||||
l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.7,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7
|
||||
l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7L7,95.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.7,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7
|
||||
l4.65-2.7 M44.25,95.7v-5.4l4.65-2.7l4.65,2.7 M44.25,79.55v-5.4l4.65-2.7l4.65,2.7v5.4L48.9,82.2L44.25,79.55L39.6,82.2
|
||||
l-4.65-2.65 M58.2,87.6v-5.4 M48.9,87.6v-5.4 M53.55,63.35v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L53.55,63.35l-4.65,2.7l-4.65-2.7
|
||||
v-5.4l4.65-2.7l4.65,2.7 M62.85,74.15l-4.65-2.7v-5.4 M53.55,74.15l4.65-2.7 M48.9,71.45v-5.4 M48.9,55.25v-5.4l4.65-2.65
|
||||
l4.65,2.65v5.4 M67.5,71.45v-5.4l4.65-2.7l4.7,2.7v5.4 M67.5,66.05l-4.65-2.7 M58.2,49.85l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
|
||||
M72.15,63.35v-5.4l4.7-2.7l4.65,2.7v5.4l-4.65,2.7 M76.85,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M67.5,55.25l4.65,2.7
|
||||
M34.95,95.7v-5.4l4.65-2.7l4.65,2.7 M16.3,95.7v-5.4l4.65-2.7l4.65,2.7v5.4 M25.6,90.3l4.7-2.7l4.65,2.7 M25.6,79.55v-5.4l4.7-2.7
|
||||
l4.65,2.7v5.4L30.3,82.2L25.6,79.55l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M20.95,87.6v-5.4 M39.6,87.6v-5.4 M30.3,87.6
|
||||
v-5.4 M7,95.7v-5.4l4.65-2.7l4.65,2.7 M-2.3,95.7v-5.4l4.65-2.7L7,90.3 M2.35,82.2l-4.65-2.65v-5.4l4.65-2.7L7,74.15v5.4L2.35,82.2
|
||||
z M16.3,79.55l-4.65,2.65L7,79.55 M2.35,87.6v-5.4 M11.65,87.6v-5.4 M16.3,74.15l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M2.35,71.45
|
||||
v-5.4L7,63.35l4.65,2.7 M2.35,49.85L7,47.2l4.65,2.65v5.4L7,57.95l-4.65-2.7V49.85L-2.3,47.2v-5.4l4.65-2.7L7,41.8v5.4
|
||||
M11.65,55.25l4.65,2.7v5.4 M7,74.15l4.65-2.7 M7,57.95v5.4 M30.3,71.45v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M16.3,57.95
|
||||
l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M30.3,66.05l-4.7-2.7 M30.3,49.85l4.65-2.65l4.65,2.65v5.4l-4.65,2.7l-4.65-2.7V49.85l-4.7-2.65
|
||||
v-5.4l-4.65-2.7v-5.4L25.6,31l4.7,2.7v5.4l-4.7,2.7 M11.65,49.85l4.65-2.65l4.65,2.65v5.4 M25.6,57.95l4.7-2.7 M34.95,57.95v5.4
|
||||
M34.95,47.2v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M25.6,31v-5.4l4.7-2.7l4.65,2.7V31l-4.65,2.7 M34.95,25.6l4.65-2.7l4.65,2.7
|
||||
V31l-4.65,2.7L34.95,31 M30.3,39.1l4.65,2.7 M39.6,39.1v-5.4 M20.95,39.1l-4.65,2.7l-4.65-2.7v-5.4L16.3,31l4.65,2.7 M16.3,41.8
|
||||
v5.4 M2.35,33.7L-2.3,31v-5.4l4.65-2.7L7,25.6V31L2.35,33.7z M7,25.6l4.65-2.7l4.65,2.7V31 M2.35,39.1v-5.4 M11.65,33.7L7,31
|
||||
M7,41.8l4.65-2.7 M11.65,17.5L7,14.85v-5.4l4.65-2.7l4.65,2.7v5.4L11.65,17.5z M11.65,22.9v-5.4 M2.35,22.9v-5.4L7,14.85 M7,9.45
|
||||
l-4.65-2.7v-5.4L7-1.35l4.65,2.7v5.4 M34.95,14.85v-5.4l4.65-2.7l4.65,2.7v5.4L39.6,17.5L34.95,14.85z M39.6,22.9v-5.4 M25.6,25.6
|
||||
l-4.65-2.7v-5.4l4.65-2.65l4.7,2.65v5.4 M16.3,9.45l4.65-2.7l4.65,2.7v5.4 M34.95,9.45l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4
|
||||
M11.65,1.35l4.65-2.7l4.65,2.7v5.4 M25.6,9.45l4.7-2.7 M30.3,17.5l4.65-2.65 M16.3,25.6l4.65-2.7 M20.95,17.5l-4.65-2.65
|
||||
M76.85,49.85l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7v5.4 M62.85,47.2v-5.4l4.65-2.7l4.65,2.7 M62.85,25.6l4.65-2.7l4.65,2.7V31
|
||||
l-4.65,2.7L62.85,31V25.6l-4.65-2.7v-5.4 M72.15,25.6l4.7-2.7l4.65,2.7V31l-4.65,2.7l-4.7-2.7 M76.85,39.1v-5.4 M67.5,39.1v-5.4
|
||||
M53.55,47.2v-5.4l4.65-2.7l4.65,2.7 M44.25,41.8l4.65-2.7l4.65,2.7 M44.25,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7L44.25,31
|
||||
M62.85,31l-4.65,2.7L53.55,31 M58.2,39.1v-5.4 M48.9,39.1v-5.4 M53.55,14.85v-5.4l4.65-2.7l4.65,2.7v5.4L58.2,17.5L53.55,14.85
|
||||
L48.9,17.5l-4.65-2.65 M48.9,22.9v-5.4 M53.55,9.45l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M44.25,9.45l4.65-2.7 M76.85,22.9v-5.4
|
||||
l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M76.85,17.5l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7v5.4 M67.5,22.9v-5.4l4.65-2.65 M67.5,17.5
|
||||
l-4.65-2.65 M72.15,9.45l-4.65-2.7v-5.4l4.65-2.7l4.7,2.7v5.4 M62.85,9.45l4.65-2.7 M53.55,25.6l4.65-2.7 M44.25,63.35l-4.65,2.7
|
||||
M39.6,55.25l4.65,2.7 M39.6,71.45l4.65,2.7 M67.5,49.85l4.65-2.65 M48.9,49.85l-4.65-2.65 M25.6,47.2l-4.65,2.65 M104.75,87.6
|
||||
l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65V87.6l4.65,2.7 M109.4,79.55l-4.65,2.65 M100.1,95.7v-5.4 M81.5,90.3l4.65-2.7
|
||||
l4.65,2.7v5.4 M81.5,74.15l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65 M90.8,79.55l4.65,2.65 M86.15,87.6v-5.4 M90.8,90.3
|
||||
l4.65-2.7 M95.45,71.45v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L95.45,71.45l-4.65,2.7 M86.15,55.25l4.65,2.7v5.4l-4.65,2.7
|
||||
l-4.65-2.7 M86.15,71.45v-5.4 M95.45,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7L95.45,55.25l-4.65,2.7 M100.1,63.35v-5.4
|
||||
M90.8,63.35l4.65,2.7 M109.4,63.35l-4.65,2.7 M104.75,71.45l4.65,2.7 M104.75,55.25l4.65,2.7 M100.1,79.55v-5.4 M100.1,47.2v-5.4
|
||||
l4.65-2.7l4.65,2.7 M100.1,41.8l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M109.4,31l-4.65,2.7 M81.5,41.8l4.65-2.7l4.65,2.7v5.4
|
||||
l-4.65,2.65 M86.15,22.9l4.65,2.7V31l-4.65,2.7L81.5,31 M100.1,31v-5.4l4.65-2.7l4.65,2.7 M90.8,31l4.65,2.7 M86.15,39.1v-5.4
|
||||
M90.8,41.8l4.65-2.7 M86.15,17.5l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M90.8,14.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65
|
||||
M81.5,9.45l4.65-2.7l4.65,2.7 M86.15,6.75v-5.4l4.65-2.7l4.65,2.7v5.4 M100.1,14.85l4.65,2.65v5.4 M109.4,14.85l-4.65,2.65
|
||||
M100.1,9.45l4.65-2.7l4.65,2.7 M95.45,1.35l4.65-2.7l4.65,2.7v5.4 M95.45,22.9l4.65,2.7 M109.4,47.2l-4.65,2.65 M90.8,47.2
|
||||
l4.65,2.65 M104.75-9.45l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65V-9.45l4.65,2.7 M109.4-17.5l-4.65,2.65 M100.1-1.35v-5.4
|
||||
M90.8-1.35v-5.4l4.65-2.7 M81.5-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4L81.5-6.75v5.4l-4.65,2.7 M90.8-6.75l-4.65-2.7
|
||||
M95.45-14.85L90.8-17.5v-5.4l4.65-2.7l4.65,2.7v5.4 M76.85-14.85l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7v5.4 M86.15-14.85l4.65-2.65
|
||||
M95.45-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L95.45-31z M95.45-25.6V-31 M90.8-22.9l-4.65-2.7V-31l4.65-2.7 M86.15-31
|
||||
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M86.15-41.8v-5.4l4.65-2.65l4.65,2.65v5.4 M100.1-33.7l4.65,2.7v5.4l-4.65,2.7 M109.4-33.7
|
||||
l-4.65,2.7 M95.45-47.2l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M104.75-41.8l4.65,2.7 M81.5-22.9l4.65-2.7 M104.75-25.6l4.65,2.7
|
||||
M100.1-49.85v-5.4l4.65-2.7l4.65,2.7 M109.4-66.05l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M104.75-63.35v5.4 M100.1-55.25
|
||||
l-4.65-2.7v-5.4l4.65-2.7 M86.15-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M86.15-74.15l4.65,2.7v5.4l-4.65,2.7l-4.65-2.7v-5.4
|
||||
L86.15-74.15v-5.4l4.65-2.65l4.65,2.65 M90.8-71.45l4.65-2.7l4.65,2.7 M95.45-63.35l-4.65-2.7 M90.8-55.25l4.65-2.7 M86.15-57.95
|
||||
v-5.4 M95.45-74.15v-5.4l4.65-2.65l4.65,2.65v5.4 M81.5-71.45l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65 M81.5-82.2v-5.4l4.65-2.7
|
||||
l4.65,2.7v5.4 M86.15-90.3v-5.4 M104.75-95.7v5.4l-4.65,2.7l-4.65-2.7v-5.4 M100.1-82.2v-5.4 M95.45-90.3l-4.65,2.7 M109.4-82.2
|
||||
l-4.65,2.65 M104.75-90.3l4.65,2.7 M109.4-49.85l-4.65,2.65 M72.15-1.35v-5.4l4.7-2.7 M67.5,1.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7
|
||||
M72.15-17.5l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M67.5-14.85v5.4 M53.55-1.35v-5.4l4.65-2.7l4.65,2.7 M48.9,1.35
|
||||
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M44.25-22.9l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65V-22.9l-4.65-2.7V-31 M53.55-17.5
|
||||
l4.65,2.65v5.4 M48.9-9.45v-5.4 M53.55-33.7v-5.4l4.65-2.7l4.65,2.7v5.4L58.2-31L53.55-33.7z M62.85-22.9l-4.65-2.7V-31 M48.9-25.6
|
||||
V-31l4.65-2.7 M48.9-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M48.9-41.8v-5.4l4.65-2.65l4.65,2.65v5.4 M76.85-25.6V-31l4.65-2.7
|
||||
M76.85-31l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7 M62.85-33.7L67.5-31v5.4 M72.15-39.1l-4.65-2.7v-5.4l4.65-2.65l4.7,2.65v5.4
|
||||
M62.85-39.1l4.65-2.7 M72.15-33.7L67.5-31 M53.55-22.9l4.65-2.7 M58.2-14.85l4.65-2.65 M30.3,1.35l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7
|
||||
v5.4 M30.3-9.45v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M16.3-1.35v-5.4l4.65-2.7l4.65,2.7 M30.3-14.85l-4.7-2.65v-5.4l4.7-2.7
|
||||
l4.65,2.7v5.4 M25.6-17.5l-4.65,2.65L16.3-17.5v-5.4l4.65-2.7l4.65,2.7 M20.95-14.85v5.4 M16.3-6.75l-4.65-2.7v-5.4l4.65-2.65
|
||||
M2.35,1.35l-4.65-2.7v-5.4l4.65-2.7L7-6.75v5.4 M7-17.5l-4.65,2.65L-2.3-17.5v-5.4l4.65-2.7L7-22.9V-17.5l4.65,2.65 M11.65-9.45
|
||||
L7-6.75 M2.35-9.45v-5.4 M11.65-31L7-33.7v-5.4l4.65-2.7l4.65,2.7v5.4L11.65-31z M16.3-22.9l-4.65-2.7V-31 M2.35-25.6V-31L7-33.7
|
||||
M7-39.1l-4.65-2.7v-5.4L7-49.85l4.65,2.65v5.4 M44.25-33.7L39.6-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M20.95-25.6V-31l4.65-2.7
|
||||
l4.7,2.7v5.4 M20.95-47.2l4.65-2.65l4.7,2.65v5.4l-4.7,2.7l-4.65-2.7V-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M30.3-47.2
|
||||
l4.65-2.65l4.65,2.65v5.4 M30.3-41.8l4.65,2.7 M25.6-33.7v-5.4 M34.95-33.7L30.3-31 M34.95-22.9l4.65-2.7 M20.95-31l-4.65-2.7
|
||||
M16.3-39.1l4.65-2.7 M7-22.9l4.65-2.7 M34.95-55.25l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L34.95-55.25v5.4 M25.6-49.85v-5.4
|
||||
l4.7-2.7 M30.3-63.35l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7v5.4 M25.6-66.05l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M30.3-74.15
|
||||
v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M20.95-63.35v5.4 M7-49.85v-5.4l4.65-2.7l4.65,2.7 M2.35-47.2l-4.65-2.65v-5.4l4.65-2.7
|
||||
L7-55.25 M2.35-63.35l-4.65-2.7v-5.4l4.65-2.7L7-71.45v5.4L2.35-63.35z M16.3-71.45l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4
|
||||
M11.65-57.95v-5.4l4.65-2.7 M2.35-57.95v-5.4 M7-66.05l4.65,2.7 M11.65-74.15L7-71.45 M2.35-74.15v-5.4L7-82.2l4.65,2.65
|
||||
M11.65-95.7v5.4L7-87.6l-4.65-2.7v-5.4 M20.95-95.7v5.4l-4.65,2.7l-4.65-2.7 M7-87.6v5.4 M16.3-87.6v5.4 M20.95-90.3l4.65,2.7v5.4
|
||||
l-4.65,2.65 M30.3-79.55l-4.7-2.65 M30.3-95.7v5.4l-4.7,2.7 M48.9-95.7v5.4l-4.65,2.7l-4.65-2.7v-5.4 M34.95-82.2v-5.4l4.65-2.7
|
||||
M30.3-90.3l4.65,2.7 M72.15-49.85v-5.4l4.7-2.7l4.65,2.7 M67.5-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M67.5-74.15l4.65,2.7v5.4
|
||||
l-4.65,2.7l-4.65-2.7v-5.4L67.5-74.15v-5.4l4.65-2.65l4.7,2.65 M81.5-66.05l-4.65,2.7l-4.7-2.7 M72.15-71.45l4.7-2.7 M76.85-63.35
|
||||
v5.4 M67.5-57.95v-5.4 M53.55-49.85v-5.4l4.65-2.7l4.65,2.7 M48.9-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M44.25-66.05v-5.4
|
||||
l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L44.25-66.05l-4.65,2.7 M62.85-66.05l-4.65,2.7l-4.65-2.7 M53.55-71.45l4.65-2.7l4.65,2.7
|
||||
M58.2-57.95v-5.4 M48.9-57.95v-5.4 M48.9-74.15v-5.4l4.65-2.65l4.65,2.65v5.4 M48.9-79.55l-4.65-2.65v-5.4 M58.2-95.7v5.4
|
||||
l-4.65,2.7l-4.65-2.7 M53.55-87.6v5.4 M58.2-79.55l4.65-2.65l4.65,2.65 M62.85-82.2v-5.4l4.65-2.7l4.65,2.7v5.4 M67.5-90.3v-5.4
|
||||
M76.85-95.7v5.4l-4.7,2.7 M58.2-90.3l4.65,2.7 M44.25-17.5l-4.65,2.65 M39.6-9.45l4.65,2.7 M39.6-74.15l4.65,2.7 M39.6-57.95
|
||||
l4.65,2.7 M44.25-49.85L39.6-47.2 M62.85-49.85L58.2-47.2 M16.3-49.85l-4.65,2.65 M44.25-82.2l-4.65,2.65 M76.85-90.3l4.65,2.7
|
||||
M81.5-49.85l-4.65,2.65 M86.15,1.35l-4.65-2.7 M44.25-1.35l-4.65,2.7 M62.85-1.35l-4.65,2.7 M109.4-1.35l-4.65,2.7 M25.6-1.35
|
||||
l-4.65,2.7 M-95.4,95.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-86.1,90.3l4.7-2.7l4.65,2.7v5.4 M-104.7,95.7v-5.4l4.65-2.7l4.65,2.7
|
||||
M-100.05,82.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4L-100.05,82.2z M-95.4,74.15l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65
|
||||
M-86.1,74.15l4.7-2.7l4.65,2.7v5.4l-4.65,2.65l-4.7-2.65 M-100.05,87.6v-5.4 M-90.75,87.6v-5.4 M-81.4,87.6v-5.4 M-109.35,87.6
|
||||
l4.65,2.7 M-104.7,79.55l-4.65,2.65 M-109.35,55.25l4.65,2.7v5.4l-4.65,2.7 M-109.35,71.45l4.65,2.7 M-90.75,66.05l-4.65-2.7v-5.4
|
||||
l4.65-2.7l4.65,2.7v5.4L-90.75,66.05v5.4 M-86.1,57.95l4.7-2.7l4.65,2.7v5.4l-4.65,2.7l-4.7-2.7 M-81.4,71.45v-5.4 M-100.05,71.45
|
||||
v-5.4l4.65-2.7 M-95.4,57.95l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-81.4,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
|
||||
M-100.05,66.05l-4.65-2.7 M-104.7,57.95l4.65-2.7 M-86.1,41.8l-4.65-2.7v-5.4l4.65-2.7l4.7,2.7v5.4L-86.1,41.8z M-81.4,49.85
|
||||
l-4.7-2.65v-5.4 M-100.05,49.85l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-95.4,31l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7V31
|
||||
l4.65,2.7 M-86.1,31v-5.4l4.7-2.7l4.65,2.7V31l-4.65,2.7 M-100.05,39.1v-5.4 M-90.75,39.1l-4.65,2.7 M-109.35,39.1l4.65,2.7
|
||||
M-104.7,31l-4.65,2.7 M-109.35,6.75l4.65,2.7v5.4l-4.65,2.65 M-86.1,14.85v-5.4l4.7-2.7l4.65,2.7v5.4l-4.65,2.65L-86.1,14.85z
|
||||
M-81.4,22.9v-5.4 M-86.1,25.6l-4.65-2.7v-5.4l4.65-2.65 M-100.05,22.9v-5.4l4.65-2.65l4.65,2.65 M-100.05,6.75v-5.4l4.65-2.7
|
||||
l4.65,2.7v5.4l-4.65,2.7L-100.05,6.75z M-81.4,6.75v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-90.75,6.75l4.65,2.7 M-95.4,14.85v-5.4
|
||||
M-95.4,25.6l4.65-2.7 M-100.05,17.5l-4.65-2.65 M-104.7,9.45l4.65-2.7 M-109.35,22.9l4.65,2.7 M-86.1,47.2l-4.65,2.65
|
||||
M-104.7,47.2l-4.65,2.65 M-11.6,95.7v-5.4l4.65-2.7l4.65,2.7 M-20.9,95.7v-5.4l4.65-2.7l4.65,2.7 M-16.25,82.2l-4.65-2.65v-5.4
|
||||
l4.65-2.7l4.65,2.7v5.4L-16.25,82.2v5.4 M-2.3,79.55l-4.65,2.65l-4.65-2.65 M-6.95,82.2v5.4 M-30.2,95.7v-5.4l4.65-2.7l4.65,2.7
|
||||
M-39.55,95.7v-5.4l4.7-2.7l4.65,2.7 M-39.55,79.55v-5.4l4.7-2.7l4.65,2.7v5.4l-4.65,2.65L-39.55,79.55l-4.65,2.65l-4.65-2.65
|
||||
M-20.9,79.55l-4.65,2.65l-4.65-2.65 M-34.85,87.6v-5.4 M-25.55,87.6v-5.4 M-20.9,74.15l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4
|
||||
M-30.2,74.15l4.65-2.7 M-25.55,66.05l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-39.55,74.15l-4.65-2.7v-5.4l4.65-2.7l4.7,2.7v5.4
|
||||
M-30.2,57.95l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-39.55,63.35v-5.4l4.7-2.7 M-30.2,63.35l-4.65,2.7 M-11.6,74.15l4.65-2.7
|
||||
l4.65,2.7 M-6.95,66.05l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95,66.05v5.4 M-16.25,66.05l4.65-2.7 M-11.6,57.95l-4.65-2.7v-5.4
|
||||
l4.65-2.65l4.65,2.65v5.4 M-16.25,55.25l-4.65,2.7 M-48.85,95.7v-5.4l4.65-2.7l4.65,2.7 M-58.15,95.7v-5.4l4.65-2.7l4.65,2.7
|
||||
M-58.15,79.55v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65L-58.15,79.55l-4.65,2.65l-4.65-2.65 M-44.2,87.6v-5.4 M-53.5,87.6v-5.4
|
||||
M-67.45,95.7v-5.4l4.65-2.7l4.65,2.7 M-76.75,90.3l4.65-2.7l4.65,2.7 M-76.75,74.15l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65
|
||||
M-62.8,87.6v-5.4 M-72.1,87.6v-5.4 M-67.45,74.15l4.65-2.7l4.65,2.7 M-62.8,71.45v-5.4l4.65-2.7l4.65,2.7v5.4 M-62.8,66.05
|
||||
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-76.75,63.35l4.65,2.7v5.4 M-62.8,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
|
||||
M-67.45,63.35l-4.65,2.7 M-72.1,55.25l4.65,2.7 M-48.85,74.15l4.65-2.7 M-44.2,66.05l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7
|
||||
M-53.5,49.85l4.65-2.65l4.65,2.65v5.4 M-53.5,66.05l4.65-2.7 M-53.5,55.25l4.65,2.7 M-48.85,47.2v-5.4l4.65-2.7l4.65,2.7v5.4
|
||||
l-4.65,2.65 M-58.15,47.2v-5.4l4.65-2.7l4.65,2.7 M-58.15,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7l-4.65-2.7V25.6l-4.65-2.7v-5.4
|
||||
M-48.85,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7l-4.65-2.7 M-44.2,39.1v-5.4 M-53.5,39.1v-5.4 M-62.8,49.85l-4.65-2.65v-5.4l4.65-2.7
|
||||
l4.65,2.7 M-76.75,47.2v-5.4l4.65-2.7l4.65,2.7 M-76.75,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7l-4.65-2.7 M-58.15,31l-4.65,2.7
|
||||
l-4.65-2.7 M-72.1,39.1v-5.4 M-62.8,39.1v-5.4 M-67.45,14.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65L-67.45,14.85l-4.65,2.65
|
||||
l-4.65-2.65 M-72.1,22.9v-5.4 M-62.8,6.75v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-72.1,6.75l4.65,2.7 M-44.2,22.9v-5.4l4.65-2.65
|
||||
l4.7,2.65v5.4l-4.7,2.7 M-44.2,17.5l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-48.85,14.85l-4.65,2.65l-4.65-2.65 M-53.5,22.9v-5.4
|
||||
M-44.2,6.75v-5.4l4.65-2.7l4.7,2.7v5.4l-4.7,2.7 M-53.5,6.75l4.65,2.7 M-67.45,25.6l4.65-2.7 M-11.6,47.2v-5.4l4.65-2.7l4.65,2.7
|
||||
M-11.6,41.8l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-16.25,33.7L-20.9,31v-5.4l4.65-2.7l4.65,2.7V31 M-2.3,31l-4.65,2.7
|
||||
M-30.2,47.2v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-39.55,41.8l4.7-2.7l4.65,2.7 M-34.85,22.9l4.65,2.7V31l-4.65,2.7l-4.7-2.7
|
||||
M-20.9,31l-4.65,2.7L-30.2,31 M-34.85,39.1v-5.4 M-25.55,39.1v-5.4 M-20.9,25.6l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4
|
||||
M-25.55,17.5l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-25.55,6.75v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-30.2,14.85l-4.65,2.65
|
||||
M-34.85,6.75l4.65,2.7 M-6.95,17.5l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95,17.5v5.4l-4.65,2.7 M-11.6,14.85l-4.65,2.65
|
||||
M-16.25,6.75l4.65,2.7 M-16.25,1.35l4.65-2.7l4.65,2.7v5.4 M-6.95,22.9l4.65,2.7 M-30.2,25.6l4.65-2.7 M-16.25,39.1l-4.65,2.7
|
||||
M-67.45,47.2l-4.65,2.65 M-2.3,47.2l-4.65,2.65 M-34.85,49.85l-4.7-2.65 M-16.25,49.85l-4.65-2.65 M-81.4,39.1l4.65,2.7
|
||||
M-11.6-1.35v-5.4l4.65-2.7l4.65,2.7 M-11.6-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-16.25-14.85l-4.65-2.65v-5.4l4.65-2.7
|
||||
l4.65,2.7v5.4 M-2.3-17.5l-4.65,2.65 M-20.9-1.35v-5.4l4.65-2.7 M-20.9-6.75l-4.65-2.7v-5.4l4.65-2.65 M-25.55-9.45l-4.65,2.7
|
||||
l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65 M-30.2-6.75v5.4l-4.65,2.7 M-39.55-1.35v-5.4l4.7-2.7 M-34.85-14.85l-4.7-2.65v-5.4l4.7-2.7
|
||||
l4.65,2.7v5.4 M-30.2-33.7v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L-30.2-33.7l-4.65,2.7l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7 M-20.9-22.9
|
||||
l-4.65-2.7V-31 M-34.85-25.6V-31 M-39.55-39.1l-4.65-2.7v-5.4l4.65-2.65l4.7,2.65v5.4 M-25.55-41.8v-5.4l4.65-2.65l4.65,2.65v5.4
|
||||
l-4.65,2.7 M-6.95-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95-31v5.4l-4.65,2.7 M-16.25-25.6V-31l4.65-2.7 M-16.25-41.8
|
||||
l4.65,2.7 M-16.25-47.2l4.65-2.65l4.65,2.65v5.4 M-30.2-22.9l4.65-2.7 M-16.25-31l-4.65-2.7 M-6.95-25.6l4.65,2.7 M-44.2,1.35
|
||||
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-58.15-1.35v-5.4l4.65-2.7l4.65,2.7 M-58.15-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4
|
||||
M-62.8-14.85l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-39.55-17.5l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M-44.2-9.45v-5.4
|
||||
M-53.5-14.85l4.65-2.65 M-62.8,1.35l-4.65-2.7v-5.4l4.65-2.7 M-76.75-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4L-76.75-6.75
|
||||
v5.4 M-76.75-17.5v-5.4l4.65-2.7l4.65,2.7 M-67.45-17.5l-4.65,2.65 M-72.1-9.45l4.65,2.7 M-67.45-33.7v-5.4l4.65-2.7l4.65,2.7v5.4
|
||||
L-62.8-31L-67.45-33.7z M-62.8-25.6V-31 M-72.1-25.6V-31l4.65-2.7 M-72.1-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-76.75-39.1
|
||||
l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-62.8-41.8v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M-44.2-25.6V-31l4.65-2.7 M-44.2-31
|
||||
l-4.65-2.7v-5.4l4.65-2.7 M-48.85-33.7L-53.5-31l-4.65-2.7 M-48.85-22.9l-4.65-2.7V-31 M-53.5-41.8l4.65,2.7 M-58.15-22.9l4.65-2.7
|
||||
M-58.15-49.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-48.85-55.25l4.65-2.7l4.65,2.7v5.4 M-58.15-71.45l4.65-2.7l4.65,2.7v5.4
|
||||
l-4.65,2.7l-4.65-2.7V-71.45z M-48.85-71.45l4.65-2.7l4.65,2.7v5.4l-4.65,2.7l-4.65-2.7 M-44.2-57.95v-5.4 M-53.5-57.95v-5.4
|
||||
M-76.75-49.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-67.45-55.25l4.65-2.7l4.65,2.7 M-76.75-55.25l-4.65-2.7v-5.4l4.65-2.7
|
||||
l4.65,2.7v5.4 M-81.4-63.35l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7v5.4 M-81.4-74.15v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
|
||||
M-58.15-66.05l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-62.8-57.95v-5.4 M-72.1-74.15l4.65,2.7 M-67.45-66.05l-4.65,2.7
|
||||
M-67.45-82.2v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65L-67.45-82.2l-4.65,2.65 M-76.75-82.2v-5.4l4.65-2.7l4.65,2.7 M-72.1-90.3
|
||||
v-5.4 M-62.8-90.3v-5.4 M-53.5-74.15v-5.4l4.65-2.65l4.65,2.65v5.4 M-58.15-82.2l4.65,2.65 M-58.15-87.6l4.65-2.7l4.65,2.7v5.4
|
||||
M-53.5-90.3v-5.4 M-44.2-95.7v5.4l-4.65,2.7 M-62.8-74.15v-5.4 M-11.6-49.85v-5.4l4.65-2.7l4.65,2.7 M-20.9-49.85v-5.4l4.65-2.7
|
||||
l4.65,2.7 M-16.25-63.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L-16.25-63.35v5.4 M-2.3-66.05l-4.65,2.7l-4.65-2.7 M-11.6-71.45
|
||||
l4.65-2.7l4.65,2.7 M-6.95-63.35v5.4 M-25.55-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M-39.55-55.25l4.7-2.7l4.65,2.7
|
||||
M-39.55-71.45l4.7-2.7l4.65,2.7v5.4l-4.65,2.7l-4.7-2.7 M-20.9-71.45l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-20.9-66.05
|
||||
l-4.65,2.7l-4.65-2.7 M-25.55-74.15l-4.65,2.7 M-34.85-63.35v5.4 M-25.55-57.95v-5.4 M-34.85-74.15v-5.4l4.65-2.65l4.65,2.65
|
||||
M-44.2-79.55l4.65-2.65l4.7,2.65 M-39.55-82.2v-5.4l4.7-2.7l4.65,2.7v5.4 M-34.85-90.3v-5.4 M-16.25-95.7v5.4l-4.65,2.7l-4.65-2.7
|
||||
v-5.4 M-25.55-90.3l-4.65,2.7 M-20.9-82.2v-5.4 M-6.95-79.55l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95-79.55v5.4 M-16.25-79.55
|
||||
l4.65-2.65 M-11.6-87.6l-4.65-2.7 M-6.95-95.7v5.4 M-44.2-90.3l4.65,2.7 M-62.8-47.2l-4.65-2.65 M-44.2-47.2l-4.65-2.65
|
||||
M-30.2-49.85l-4.65,2.65 M-2.3-49.85l-4.65,2.65 M-95.4-1.35v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-86.1-6.75l4.7-2.7
|
||||
M-100.05,1.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-95.4-17.5l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7V-17.5z M-95.4-22.9
|
||||
l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65 M-81.4-14.85l-4.7-2.65 M-100.05-9.45v-5.4 M-90.75-14.85v5.4 M-109.35-9.45
|
||||
l4.65,2.7 M-104.7-17.5l-4.65,2.65 M-109.35-41.8l4.65,2.7v5.4l-4.65,2.7 M-90.75-25.6V-31l4.65-2.7l4.7,2.7v5.4l-4.7,2.7
|
||||
M-100.05-25.6V-31l4.65-2.7l4.65,2.7 M-100.05-47.2l4.65-2.65l4.65,2.65v5.4l-4.65,2.7l-4.65-2.7V-47.2l-4.65-2.65 M-90.75-41.8
|
||||
l4.65,2.7v5.4 M-86.1-39.1l4.7-2.7 M-95.4-33.7v-5.4 M-109.35-25.6l4.65,2.7 M-100.05-31l-4.65-2.7 M-104.7-39.1l4.65-2.7
|
||||
M-95.4-49.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-86.1-55.25l4.7-2.7 M-95.4-55.25l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4
|
||||
M-100.05-63.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-95.4-71.45l4.65-2.7l4.65,2.7 M-86.1-66.05l-4.65,2.7 M-109.35-57.95
|
||||
l4.65,2.7v5.4l-4.65,2.65 M-109.35-74.15l4.65,2.7 M-104.7-66.05l-4.65,2.7 M-109.35-90.3l4.65,2.7v5.4l-4.65,2.65 M-81.4-79.55
|
||||
l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7 M-86.1-82.2l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M-100.05-74.15v-5.4l4.65-2.65
|
||||
M-95.4-87.6l-4.65-2.7v-5.4 M-81.4-95.7v5.4 M-90.75-95.7v5.4 M-90.75-79.55v5.4 M-104.7-55.25l4.65-2.7 M-100.05-79.55
|
||||
l-4.65-2.65 M-104.7-87.6l4.65-2.7 M-81.4-47.2l-4.7-2.65 M-76.75-33.7L-81.4-31 M-81.4-25.6l4.65,2.7 M-67.45-1.35l-4.65,2.7
|
||||
M-104.7-1.35l-4.65,2.7 M-81.4,1.35l-4.7-2.7 M-25.55,1.35l-4.65-2.7 M-53.5,1.35l4.65-2.7 M-2.3-1.35l-4.65,2.7 M-2.3,57.95
|
||||
l4.65-2.7 M2.35,66.05l-4.65-2.7 M-2.3-39.1l4.65-2.7 M2.35-31l-4.65-2.7 M2.35-79.55L-2.3-82.2 M-2.3-87.6l4.65-2.7 M-2.3,9.45
|
||||
l4.65-2.7 M2.35,17.5l-4.65-2.65"/>
|
||||
</symbol>
|
||||
<g id="Layer_1">
|
||||
</g>
|
||||
<g id="Layer_6">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#EFEEEE" d="M217.5,108.95c0,29.833-10.533,55.399-31.6,76.7
|
||||
c-0.7,0.833-1.484,1.6-2.351,2.3c-3.466,3.399-7.134,6.483-11,9.25c-18.267,13.467-39.366,20.2-63.3,20.2
|
||||
c-23.967,0-45.033-6.733-63.2-20.2c-4.8-3.4-9.3-7.25-13.5-11.55c-16.367-16.267-26.417-35.167-30.15-56.7
|
||||
c-0.733-4.2-1.217-8.467-1.45-12.8c-0.1-2.4-0.15-4.801-0.15-7.2c0-2.534,0.05-4.95,0.15-7.25c0-0.233,0.066-0.467,0.2-0.7
|
||||
c1.567-26.6,12.033-49.583,31.4-68.95C53.85,11.017,79.417,0.5,109.25,0.5c29.934,0,55.483,10.517,76.65,31.55
|
||||
C206.967,53.483,217.5,79.117,217.5,108.95z"/>
|
||||
</g>
|
||||
<g id="Layer_5">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#010101" d="M195.45,43l-22.4,22.4c-8.833,13-13.25,27.867-13.25,44.6
|
||||
c0,17.934,5.067,33.833,15.2,47.7l19,18.95c-2.5,3.066-5.2,6.066-8.1,9c-0.7,0.833-1.484,1.6-2.351,2.3
|
||||
c-2.533,2.5-5.167,4.816-7.899,6.95L158.1,177.35c-13.934-10.733-30.133-16.1-48.6-16.1c-17.933,0-33.833,5.1-47.7,15.3
|
||||
L43.25,195.15c-3.767-2.867-7.333-6.034-10.7-9.5c-2.8-2.801-5.417-5.667-7.85-8.601l19.15-19.2
|
||||
c10.066-13.966,15.1-29.916,15.1-47.85c0-17.5-4.867-33.017-14.6-46.55l-21.05-21c2.833-3.6,5.917-7.067,9.25-10.4
|
||||
c2.934-2.867,5.934-5.55,9-8.05L61.9,44.35C75.7,54.583,91.567,59.7,109.5,59.7c18.467,0,34.666-5.367,48.6-16.1L177.4,24.35
|
||||
c2.899,2.367,5.732,4.933,8.5,7.7C189.367,35.583,192.55,39.233,195.45,43z"/>
|
||||
</g>
|
||||
<g id="Layer_4">
|
||||
<defs>
|
||||
<path id="SVGID_1_" d="M159.8,110c0-16.733,4.417-31.6,13.25-44.6l22.4-22.4c-2.9-3.767-6.083-7.417-9.55-10.95
|
||||
c-2.768-2.767-5.601-5.333-8.5-7.7L158.1,43.6c-13.934,10.733-30.133,16.1-48.6,16.1c-17.933,0-33.8-5.117-47.6-15.35L41.55,24
|
||||
c-3.066,2.5-6.066,5.183-9,8.05c-3.333,3.333-6.417,6.8-9.25,10.4l21.05,21c9.733,13.533,14.6,29.05,14.6,46.55
|
||||
c0,17.934-5.034,33.884-15.1,47.85l-19.15,19.2c2.433,2.934,5.05,5.8,7.85,8.601c3.367,3.466,6.934,6.633,10.7,9.5L61.8,176.55
|
||||
c13.867-10.2,29.767-15.3,47.7-15.3c18.467,0,34.666,5.366,48.6,16.1L175.65,194.9c2.732-2.134,5.366-4.45,7.899-6.95
|
||||
c0.866-0.7,1.65-1.467,2.351-2.3c2.899-2.934,5.6-5.934,8.1-9l-19-18.95C164.867,143.833,159.8,127.934,159.8,110z"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_1_" overflow="visible"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#SVGID_2_)">
|
||||
|
||||
<use xlink:href="#hex_grid" width="228.55" height="197.233" x="-114.25" y="-98.617" transform="matrix(1.1415 0 0 -1.1415 105.5 107.75)" overflow="visible"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#35C6F4" d="M79.1,110.95c-0.033-0.667-0.05-1.333-0.05-2
|
||||
c0-0.7,0.017-1.366,0.05-2c0-0.067,0.017-0.134,0.05-0.2c0.434-7.367,3.333-13.733,8.7-19.1c5.9-5.833,12.983-8.75,21.25-8.75
|
||||
c8.301,0,15.384,2.917,21.25,8.75c5.834,5.934,8.75,13.033,8.75,21.3c0,8.267-2.916,15.35-8.75,21.25
|
||||
c-0.199,0.233-0.416,0.45-0.649,0.649c-0.967,0.934-1.983,1.784-3.05,2.551c-5.066,3.733-10.917,5.6-17.551,5.6
|
||||
c-6.633,0-12.466-1.866-17.5-5.6c-1.333-0.934-2.583-2-3.75-3.2c-4.533-4.5-7.317-9.733-8.35-15.7
|
||||
C79.3,113.334,79.167,112.15,79.1,110.95z M126.1,127.25l3.601,3.6L126.1,127.25z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#35C6F4" stroke-width="2" stroke-miterlimit="1" d="
|
||||
M158.6,60.25l-15,14.65 M31.7,33.1l40.75,40.65 M126.1,127.25l3.601,3.6 M157.05,158l27.65,28.6 M153.05,153.95l-10.75-11.2
|
||||
M186.6,33l-28,27.25 M33.15,186.25l27.35-27.4"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#35C6F4" stroke-width="7" stroke-miterlimit="1" d="
|
||||
M158.6,60.25l-16.949,17.2 M59.4,61.35L76.6,78.5 M60.5,158.85l16.75-17.399 M153.05,153.95l4,4.05 M139.45,140.4l13.6,13.55"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 26 KiB |
13
build.ps1
13
build.ps1
@@ -114,8 +114,8 @@ Function PackageMono()
|
||||
get-childitem $outputFolderMono -File -Filter sqlite3.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
get-childitem $outputFolderMono -File -Filter MediaInfo.* -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
|
||||
Write-Host "Adding MediaInfoDotNet.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\MediaInfoDotNet.dll.config" $outputFolderMono
|
||||
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" $outputFolderMono
|
||||
|
||||
Write-Host Renaming NzbDrone.Console.exe to NzbDrone.exe
|
||||
Get-ChildItem $outputFolderMono -File -Filter "NzbDrone.exe*" -Recurse | foreach ($_) {remove-item $_.fullname}
|
||||
@@ -212,8 +212,8 @@ Function PackageTests()
|
||||
|
||||
CleanFolder $testPackageFolder $true
|
||||
|
||||
Write-Host "Adding MediaInfoDotNet.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\MediaInfoDotNet.dll.config" -Destination $testPackageFolder -Force
|
||||
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
|
||||
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" -Destination $testPackageFolder -Force
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Creating Test Package']"
|
||||
}
|
||||
@@ -221,15 +221,12 @@ Function PackageTests()
|
||||
Function RunGulp()
|
||||
{
|
||||
Write-Host "##teamcity[progressStart 'Running Gulp']"
|
||||
$gulpPath = '.\node_modules\gulp\bin\gulp'
|
||||
Invoke-Expression 'npm install'
|
||||
CheckExitCode
|
||||
|
||||
Invoke-Expression ('node ' + $gulpPath + ' build') -ErrorAction Continue -Verbose
|
||||
Invoke-Expression 'gulp build' -ErrorAction Continue -Verbose
|
||||
CheckExitCode
|
||||
|
||||
Remove-Item $outputFolder\UI\build.txt -ErrorAction Continue
|
||||
|
||||
Write-Host "##teamcity[progressFinish 'Running Gulp']"
|
||||
}
|
||||
|
||||
|
||||
50
commonjsCleanup.linq
Normal file
50
commonjsCleanup.linq
Normal file
@@ -0,0 +1,50 @@
|
||||
<Query Kind="Program" />
|
||||
|
||||
void Main()
|
||||
{
|
||||
var files = Directory.GetFiles("c:\\git\\sonarr\\src\\UI","*.js", SearchOption.AllDirectories);
|
||||
|
||||
var moduleRegex = new Regex(@"module.exports\s*=\s*\(function\s*\(\)\s*{\n\s*return\s*(\w|\W)*\)\.call\(this\);$");
|
||||
var functionHead = new Regex(@"\s*\(function\s*\(\)\s*{\n\s*return\s*");
|
||||
var functionTail = new Regex(@"\}\).call\(this\);$");
|
||||
var multiVar = new Regex(@"^(?<d>var\s*\w*\s*=\s*require\(.*\)),");
|
||||
var seperateDeclearatuin = new Regex(@"^((\w|\$|_)*\s=\srequire\(.*\))(,|;)", RegexOptions.Multiline);
|
||||
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
var text = File.ReadAllText(filePath);
|
||||
|
||||
var newContent = text.Replace("// Generated by uRequire v0.7.0-beta.14 template: 'nodejs'","");
|
||||
|
||||
newContent = newContent.Replace("var __isAMD = !!(typeof define === 'function' && define.amd),","");
|
||||
newContent = newContent.Replace("__isNode = (typeof exports === 'object'),","");
|
||||
newContent = newContent.Replace("__isWeb = !__isNode;","");
|
||||
newContent = newContent.Replace("\"use strict\";","'use strict';");
|
||||
|
||||
newContent = newContent.Trim();
|
||||
|
||||
|
||||
|
||||
if(moduleRegex.IsMatch(newContent))
|
||||
{
|
||||
filePath.Dump();
|
||||
|
||||
newContent = functionHead.Replace(newContent," ");
|
||||
newContent = functionTail.Replace(newContent,"");
|
||||
}
|
||||
|
||||
if(multiVar.IsMatch(newContent))
|
||||
{
|
||||
newContent = multiVar.Replace(newContent,"$1;"); //first one
|
||||
|
||||
}
|
||||
|
||||
newContent = seperateDeclearatuin.Replace(newContent,"var $1;"); //ones after
|
||||
newContent.Replace("var $ = require('jquery'), var","var $ = require('jquery');");
|
||||
|
||||
File.WriteAllText(filePath,newContent.Trim());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Define other methods and classes here
|
||||
@@ -2,12 +2,11 @@ var gulp = require('gulp');
|
||||
var runSequence = require('run-sequence');
|
||||
|
||||
require('./clean');
|
||||
require('./requirejs');
|
||||
require('./less');
|
||||
require('./handlebars');
|
||||
require('./copy');
|
||||
|
||||
gulp.task('build', function () {
|
||||
return runSequence('clean',
|
||||
['requireJs', 'less', 'handlebars', 'copyIndex', 'copyContent']);
|
||||
});
|
||||
['webpack', 'less', 'handlebars', 'copyHtml', 'copyContent', 'copyJs']);
|
||||
});
|
||||
|
||||
14
gulp/copy.js
14
gulp/copy.js
@@ -5,19 +5,23 @@ var cache = require('gulp-cached');
|
||||
var paths = require('./paths.js');
|
||||
|
||||
gulp.task('copyJs', function () {
|
||||
return gulp.src(paths.src.scripts)
|
||||
return gulp.src(
|
||||
[
|
||||
paths.src.root + "polyfills.js",
|
||||
paths.src.root + "JsLibraries/handlebars.runtime.js",
|
||||
])
|
||||
.pipe(cache('copyJs'))
|
||||
.pipe(print())
|
||||
.pipe(gulp.dest(paths.dest.root));
|
||||
});
|
||||
|
||||
gulp.task('copyIndex', function () {
|
||||
return gulp.src(paths.src.index)
|
||||
.pipe(cache('copyIndex'))
|
||||
gulp.task('copyHtml', function () {
|
||||
return gulp.src(paths.src.html)
|
||||
.pipe(cache('copyHtml'))
|
||||
.pipe(gulp.dest(paths.dest.root));
|
||||
});
|
||||
|
||||
gulp.task('copyContent', function () {
|
||||
return gulp.src([paths.src.content + '**/*.*', '!**/*.less'])
|
||||
.pipe(gulp.dest(paths.dest.content));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
require('./watch.js');
|
||||
require('./build.js');
|
||||
require('./clean.js');
|
||||
require('./requirejs.js');
|
||||
require('./jshint.js');
|
||||
require('./handlebars.js');
|
||||
require('./copy.js');
|
||||
require('./less.js');
|
||||
require('./stripBom.js');
|
||||
require('./imageMin.js');
|
||||
require('./webpack.js');
|
||||
|
||||
|
||||
|
||||
@@ -2,18 +2,17 @@ var gulp = require('gulp');
|
||||
var handlebars = require('gulp-handlebars');
|
||||
var declare = require('gulp-declare');
|
||||
var concat = require('gulp-concat');
|
||||
var wrapAmd = require('gulp-wrap-amd');
|
||||
var wrap = require("gulp-wrap");
|
||||
var path = require('path');
|
||||
var streamqueue = require('streamqueue');
|
||||
var stripbom = require('gulp-stripbom');
|
||||
|
||||
var paths = require('./paths.js');
|
||||
var bom = require('./pipelines/gulp-bom.js');
|
||||
|
||||
gulp.task('handlebars', function () {
|
||||
|
||||
var coreStream = gulp.src([paths.src.templates, '!*/**/*Partial.*'])
|
||||
.pipe(bom())
|
||||
.pipe(stripbom({ showLog: false }))
|
||||
.pipe(handlebars())
|
||||
.pipe(declare({
|
||||
namespace: 'T',
|
||||
@@ -30,7 +29,7 @@ gulp.task('handlebars', function () {
|
||||
}));
|
||||
|
||||
var partialStream = gulp.src([paths.src.partials])
|
||||
.pipe(bom())
|
||||
.pipe(stripbom({ showLog: false }))
|
||||
.pipe(handlebars())
|
||||
.pipe(wrap('Handlebars.template(<%= contents %>)'))
|
||||
.pipe(wrap('Handlebars.registerPartial(<%= processPartialName(file.relative) %>, <%= contents %>)', {}, {
|
||||
@@ -48,10 +47,6 @@ gulp.task('handlebars', function () {
|
||||
partialStream,
|
||||
coreStream
|
||||
).pipe(concat('templates.js'))
|
||||
.pipe(wrapAmd({
|
||||
deps: ['handlebars'],
|
||||
params: ['Handlebars'],
|
||||
exports: 'this["T"]'
|
||||
}))
|
||||
|
||||
.pipe(gulp.dest(paths.dest.root));
|
||||
});
|
||||
|
||||
@@ -8,19 +8,6 @@ var paths = require('./paths.js');
|
||||
gulp.task('jshint', function () {
|
||||
return gulp.src([paths.src.scripts, paths.src.exclude.libs])
|
||||
.pipe(cache('jshint'))
|
||||
.pipe(jshint({
|
||||
'-W030': false,
|
||||
'-W064': false,
|
||||
'-W097': false, //Use the function form of “use strict”
|
||||
'-W100': false, //Silently deleted characters (in locales)
|
||||
'undef': true,
|
||||
'globals': {
|
||||
'require': true,
|
||||
'define': true,
|
||||
'window': true,
|
||||
'document': true,
|
||||
'console': true
|
||||
}
|
||||
}))
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter(stylish));
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ gulp.task('less', function () {
|
||||
paths.src.root + 'AddSeries/addSeries.less',
|
||||
paths.src.root + 'Calendar/calendar.less',
|
||||
paths.src.root + 'Cells/cells.less',
|
||||
paths.src.root + 'ManualImport/manualimport.less',
|
||||
paths.src.root + 'Settings/settings.less',
|
||||
paths.src.root + 'System/Logs/logs.less',
|
||||
paths.src.root + 'System/Update/update.less',
|
||||
|
||||
@@ -2,7 +2,7 @@ module.exports = {
|
||||
src: {
|
||||
root: './src/UI/',
|
||||
templates: './src/UI/**/*.hbs',
|
||||
index: './src/UI/index.html',
|
||||
html: './src/UI/*.html',
|
||||
partials: './src/UI/**/*Partial.hbs',
|
||||
scripts: './src/UI/**/*.js',
|
||||
less: ['./src/UI/**/*.less'],
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
var replace = require('gulp-replace');
|
||||
module.exports = function() {
|
||||
return replace(/^\uFEFF/, '');
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
var requirejs = require('requirejs');
|
||||
var paths = require('./paths');
|
||||
|
||||
require('./handlebars.js');
|
||||
require('./jshint.js');
|
||||
|
||||
|
||||
gulp.task('requireJs', ['jshint'], function (cb) {
|
||||
|
||||
var config = {
|
||||
mainConfigFile: 'src/UI/app.js',
|
||||
fileExclusionRegExp: /^.*\.(?!js$)[^.]+$/,
|
||||
preserveLicenseComments: false,
|
||||
dir: paths.dest.root,
|
||||
optimize: 'none',
|
||||
removeCombined: true,
|
||||
inlineText: false,
|
||||
keepBuildDir: true,
|
||||
modules: [
|
||||
{
|
||||
name: 'app',
|
||||
exclude: ['templates.js']
|
||||
}
|
||||
]};
|
||||
|
||||
requirejs.optimize(config, function (buildResponse) {
|
||||
console.log(buildResponse);
|
||||
cb();
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,22 +1,14 @@
|
||||
var gulp = require('gulp');
|
||||
var paths = require('./paths.js');
|
||||
var bom = require('./pipelines/gulp-bom.js');
|
||||
var gulpPrint = require('gulp-print');
|
||||
|
||||
var stripbom = require('gulp-stripbom');
|
||||
|
||||
var stripBom = function (dest) {
|
||||
gulp.src([paths.src.scripts, paths.src.exclude.libs])
|
||||
.pipe(bom())
|
||||
.pipe(gulpPrint(function (filepath) {
|
||||
return "booming: " + filepath;
|
||||
}))
|
||||
.pipe(stripbom({ showLog: false }))
|
||||
.pipe(gulp.dest(dest));
|
||||
|
||||
gulp.src(paths.src.templates)
|
||||
.pipe(bom())
|
||||
.pipe(gulpPrint(function (filepath) {
|
||||
return "booming: " + filepath;
|
||||
}))
|
||||
.pipe(stripbom({ showLog: false }))
|
||||
.pipe(gulp.dest(dest));
|
||||
};
|
||||
|
||||
|
||||
@@ -8,23 +8,26 @@ require('./jshint.js');
|
||||
require('./handlebars.js');
|
||||
require('./less.js');
|
||||
require('./copy.js');
|
||||
require('./webpack.js');
|
||||
|
||||
|
||||
gulp.task('watch', ['jshint', 'handlebars', 'less', 'copyJs','copyIndex', 'copyContent'], function () {
|
||||
gulp.watch([paths.src.scripts, paths.src.exclude.libs], ['jshint', 'copyJs']);
|
||||
gulp.task('watch', ['jshint', 'handlebars', 'less','copyHtml', 'copyContent','copyJs'], function () {
|
||||
gulp.start('webpackWatch');
|
||||
gulp.watch([paths.src.scripts, paths.src.exclude.libs], ['jshint','copyJs']);
|
||||
gulp.watch(paths.src.templates, ['handlebars']);
|
||||
gulp.watch([paths.src.less, paths.src.exclude.libs], ['less']);
|
||||
gulp.watch([paths.src.index], ['copyIndex']);
|
||||
gulp.watch([paths.src.html], ['copyHtml']);
|
||||
gulp.watch([paths.src.content + '**/*.*', '!**/*.less'], ['copyContent']);
|
||||
});
|
||||
|
||||
gulp.task('liveReload', ['jshint', 'handlebars', 'less', 'copyJs'], function () {
|
||||
gulp.task('liveReload', ['jshint', 'handlebars', 'less', 'webPack'], function () {
|
||||
var server = livereload();
|
||||
gulp.watch([
|
||||
'app/**/*.js',
|
||||
'app/**/*.css',
|
||||
'app/index.html'
|
||||
'app/index.html',
|
||||
'app/login.html'
|
||||
]).on('change', function (file) {
|
||||
server.changed(file.path);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
20
gulp/webpack.js
Normal file
20
gulp/webpack.js
Normal file
@@ -0,0 +1,20 @@
|
||||
var gulp = require('gulp');
|
||||
|
||||
var gulpWebpack = require('gulp-webpack');
|
||||
var webpack = require('webpack');
|
||||
var webpackConfig = require('../webpack.config');
|
||||
|
||||
webpackConfig.devtool = "#source-map";
|
||||
|
||||
gulp.task('webpack', function() {
|
||||
return gulp.src('main.js')
|
||||
.pipe(gulpWebpack(webpackConfig, webpack))
|
||||
.pipe(gulp.dest(''));
|
||||
});
|
||||
|
||||
gulp.task('webpackWatch', function() {
|
||||
webpackConfig.watch = true;
|
||||
return gulp.src('main.js')
|
||||
.pipe(gulpWebpack(webpackConfig, webpack))
|
||||
.pipe(gulp.dest(''));
|
||||
});
|
||||
@@ -1 +1 @@
|
||||
require('./gulp/gulpfile.js');
|
||||
require('./gulp/gulpFile.js');
|
||||
|
||||
@@ -2,6 +2,6 @@ EXCLUDE="-exclude:Windows -include:IntegrationTest"
|
||||
TESTDIR="."
|
||||
NUNIT="$TESTDIR/NUnit.Runners.2.6.1/tools/nunit-console-x86.exe"
|
||||
|
||||
mono --debug $NUNIT $EXCLUDE -xml:NzbDrone.Api.Result.xml $TESTDIR/NzbDrone.Api.Test.dll
|
||||
mono --debug $NUNIT $EXCLUDE -xml:NzbDrone.Core.Result.xml $TESTDIR/NzbDrone.Core.Test.dll
|
||||
mono --debug $NUNIT $EXCLUDE -xml:NzbDrone.Integration.Result.xml $TESTDIR/NzbDrone.Integration.Test.dll
|
||||
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Api.Result.xml $TESTDIR/NzbDrone.Api.Test.dll
|
||||
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Core.Result.xml $TESTDIR/NzbDrone.Core.Test.dll
|
||||
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Integration.Result.xml $TESTDIR/NzbDrone.Integration.Test.dll
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
DIR=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
#change these values to match your app
|
||||
EXE_PATH="$DIR/nzbdrone.exe"
|
||||
EXE_PATH="$DIR/NzbDrone.exe"
|
||||
APPNAME="Sonarr"
|
||||
|
||||
#set up environment
|
||||
|
||||
31
package.json
31
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Sonarr",
|
||||
"version": "0.0.0",
|
||||
"version": "2.0.0",
|
||||
"description": "Sonarr",
|
||||
"main": "index.js",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"preinstall": ""
|
||||
},
|
||||
@@ -15,22 +15,25 @@
|
||||
"gitHead": "9ff7aa1bf7fe38c4c5bdb92f56c8ad556916ed67",
|
||||
"readmeFilename": "readme.md",
|
||||
"dependencies": {
|
||||
"fs-extra": "0.12.0",
|
||||
"del": "1.1.1",
|
||||
"gulp": "3.8.10",
|
||||
"gulp-cached": "1.0.1",
|
||||
"del": "0.1.3",
|
||||
"gulp-concat": "2.4.2",
|
||||
"gulp-cached": "1.0.2",
|
||||
"gulp-concat": "2.4.3",
|
||||
"gulp-declare": "0.3.0",
|
||||
"gulp-handlebars": "2.2.0",
|
||||
"gulp-jshint": "1.9.0",
|
||||
"gulp-less": "1.3.6",
|
||||
"gulp-handlebars": "3.0.1",
|
||||
"gulp-jshint": "1.9.2",
|
||||
"gulp-less": "2.0.1",
|
||||
"gulp-print": "1.1.0",
|
||||
"gulp-replace": "0.5.0",
|
||||
"gulp-wrap": "0.5.0",
|
||||
"gulp-wrap-amd": "0.3.1",
|
||||
"gulp-replace": "0.5.2",
|
||||
"gulp-stripbom": "1.0.4",
|
||||
"gulp-run": "1.6.6",
|
||||
"gulp-webpack": "1.2.0",
|
||||
"gulp-wrap": "0.10.1",
|
||||
"handlebars": "2.0.0",
|
||||
"jshint-loader": "0.8.1",
|
||||
"jshint-stylish": "1.0.0",
|
||||
"requirejs": "2.1.15",
|
||||
"run-sequence": "1.0.2",
|
||||
"streamqueue": "0.1.1"
|
||||
"streamqueue": "0.1.1",
|
||||
"webpack": "1.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
86
schemas/torznab.xsd
Normal file
86
schemas/torznab.xsd
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://torznab.com/schemas/2015/feed"
|
||||
xmlns:torznab="http://torznab.com/schemas/2015/feed">
|
||||
<xs:simpleType name="attrNames">
|
||||
<xs:restriction base="xs:string">
|
||||
<!-- https://github.com/nZEDb/nZEDb/blob/master/docs/newznab_api_specification.txt -->
|
||||
<!-- http://newznab.readthedocs.org/en/latest/misc/api/ -->
|
||||
<!-- Original newznab attributes -->
|
||||
<!-- All -->
|
||||
<xs:enumeration value="size" />
|
||||
<xs:enumeration value="category" />
|
||||
<xs:enumeration value="guid" />
|
||||
<xs:enumeration value="poster" />
|
||||
<xs:enumeration value="team" />
|
||||
<xs:enumeration value="grabs" />
|
||||
<xs:enumeration value="comments" />
|
||||
<xs:enumeration value="year" />
|
||||
<!-- TV -->
|
||||
<xs:enumeration value="season" />
|
||||
<xs:enumeration value="episode" />
|
||||
<xs:enumeration value="rageid" />
|
||||
<xs:enumeration value="tvtitle" />
|
||||
<xs:enumeration value="tvairdate" />
|
||||
<!-- TV, Movies, Audio -->
|
||||
<xs:enumeration value="video" />
|
||||
<xs:enumeration value="audio" />
|
||||
<xs:enumeration value="resolution" />
|
||||
<xs:enumeration value="framerate" />
|
||||
<xs:enumeration value="language" />
|
||||
<xs:enumeration value="subs" />
|
||||
<!-- Movies -->
|
||||
<xs:enumeration value="imdb" />
|
||||
<xs:enumeration value="imdbscore" />
|
||||
<xs:enumeration value="imdbtitle" />
|
||||
<xs:enumeration value="imdbtagline" />
|
||||
<xs:enumeration value="imdbscore" />
|
||||
<xs:enumeration value="imdbtitle" />
|
||||
<xs:enumeration value="imdbtagline" />
|
||||
<xs:enumeration value="imdbplot" />
|
||||
<xs:enumeration value="imdbyear" />
|
||||
<xs:enumeration value="imdbdirector" />
|
||||
<xs:enumeration value="imdbactors" />
|
||||
<!-- TV, Movies -->
|
||||
<xs:enumeration value="genre" />
|
||||
<!-- Music -->
|
||||
<xs:enumeration value="artist" />
|
||||
<xs:enumeration value="album" />
|
||||
<xs:enumeration value="publisher" />
|
||||
<xs:enumeration value="tracks" />
|
||||
<!-- Mixed -->
|
||||
<xs:enumeration value="coverurl" />
|
||||
<xs:enumeration value="backdropcoverurl" />
|
||||
<xs:enumeration value="review" />
|
||||
<!-- Book -->
|
||||
<xs:enumeration value="booktitle" />
|
||||
<xs:enumeration value="publishdate" />
|
||||
<xs:enumeration value="author" />
|
||||
<xs:enumeration value="pages" />
|
||||
|
||||
<!-- Generic extensions -->
|
||||
<xs:enumeration value="type" /> <!-- series|movie|music|book if unknown just omit -->
|
||||
<xs:enumeration value="tvdbid" />
|
||||
<xs:enumeration value="bannerurl" />
|
||||
|
||||
<!-- Nzb extensions -->
|
||||
<xs:enumeration value="nzbhash" /> <!-- TBD, hash of sorted article headers of relevant content (relevant excludes stuff like par,nfo,nzb etc) -->
|
||||
|
||||
<!-- Torrent extensions -->
|
||||
<xs:enumeration value="infohash" />
|
||||
<xs:enumeration value="magneturl" />
|
||||
<xs:enumeration value="seeders" />
|
||||
<xs:enumeration value="leechers" />
|
||||
<xs:enumeration value="peers" /> <!-- seeders + leechers -->
|
||||
<xs:enumeration value="seedtype" /> <!-- TBD, which criteria must be met. was going for 'ratio,seedtime,both' but afaik it's always 'either' -->
|
||||
<xs:enumeration value="minimumratio" />
|
||||
<xs:enumeration value="minimumseedtime" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
<xs:element name="attr">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="name" type="torznab:attrNames" />
|
||||
<xs:attribute name="value" type="xs:string" />
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
4
src/LogentriesNLog/packages.config
Normal file
4
src/LogentriesNLog/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="2.1.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -144,7 +144,7 @@
|
||||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<dllmap os="osx" dll="MediaInfo.dll" target="libmediainfo.0.dylib"/>
|
||||
<dllmap os="linux" dll="MediaInfo.dll" target="libmediainfo.so.0" />
|
||||
<dllmap os="freebsd" dll="MediaInfo.dll" target="libmediainfo.so.0" />
|
||||
<dllmap os="solaris" dll="MediaInfo.dll" target="libmediainfo.so.0.0.0" />
|
||||
</configuration>
|
||||
@@ -122,7 +122,7 @@ namespace Microsoft.AspNet.SignalR
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException("user");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!user.Identity.IsAuthenticated)
|
||||
|
||||
@@ -20,8 +20,12 @@ namespace Microsoft.AspNet.SignalR.Hosting
|
||||
throw new ArgumentNullException("instanceName");
|
||||
}
|
||||
|
||||
// Initialize the performance counters
|
||||
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);
|
||||
// Performance counters are broken on mono so just skip this step
|
||||
if (!MonoUtility.IsRunningMono)
|
||||
{
|
||||
// Initialize the performance counters
|
||||
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);
|
||||
}
|
||||
|
||||
// Dispose the dependency resolver on host shut down (cleanly)
|
||||
resolver.InitializeResolverDispose(hostShutdownToken);
|
||||
@@ -41,12 +45,11 @@ namespace Microsoft.AspNet.SignalR.Hosting
|
||||
// TODO: Guard against multiple calls to this
|
||||
|
||||
// When the host triggers the shutdown token, dispose the resolver
|
||||
hostShutdownToken.Register(state =>
|
||||
hostShutdownToken.SafeRegister(state =>
|
||||
{
|
||||
((IDependencyResolver)state).Dispose();
|
||||
},
|
||||
resolver,
|
||||
useSynchronizationContext: false);
|
||||
resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace Microsoft.AspNet.SignalR.Hosting
|
||||
/// </summary>
|
||||
CancellationToken CancellationToken { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code of the response.
|
||||
/// </summary>
|
||||
int StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content type of the response.
|
||||
/// </summary>
|
||||
|
||||
@@ -16,9 +16,9 @@ namespace Microsoft.AspNet.SignalR.Hosting
|
||||
Action<string> OnMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the websocket gracefully closes
|
||||
/// Invoked when the websocket closes
|
||||
/// </summary>
|
||||
Action<bool> OnClose { get; set; }
|
||||
Action OnClose { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when there is an error
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Microsoft.AspNet.SignalR.Hosting
|
||||
/// Accepts an websocket request using the specified user function.
|
||||
/// </summary>
|
||||
/// <param name="callback">The callback that fires when the websocket is ready.</param>
|
||||
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback);
|
||||
/// <param name="initTask">The task that completes when the websocket transport is ready.</param>
|
||||
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback, Task initTask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ namespace Microsoft.AspNet.SignalR.Hubs
|
||||
private Task ExecuteHubEvent(IRequest request, string connectionId, Func<IHub, Task> action)
|
||||
{
|
||||
var hubs = GetHubs(request, connectionId).ToList();
|
||||
var operations = hubs.Select(instance => action(instance).Catch().OrEmpty()).ToArray();
|
||||
var operations = hubs.Select(instance => action(instance).OrEmpty().Catch()).ToArray();
|
||||
|
||||
if (operations.Length == 0)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Microsoft.AspNet.SignalR.Hosting;
|
||||
|
||||
namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
{
|
||||
/// <summary>
|
||||
/// A buffering text writer that supports writing binary directly as well
|
||||
/// </summary>
|
||||
internal unsafe class BinaryTextWriter : BufferTextWriter, IBinaryWriter
|
||||
{
|
||||
public BinaryTextWriter(IResponse response) :
|
||||
base((data, state) => ((IResponse)state).Write(data), response, reuseBuffers: true, bufferSize: 128)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public BinaryTextWriter(IWebSocket socket) :
|
||||
base((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public BinaryTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize) :
|
||||
base(write, state, reuseBuffers, bufferSize)
|
||||
{
|
||||
}
|
||||
|
||||
public void Write(ArraySegment<byte> data)
|
||||
{
|
||||
Writer.Write(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
/// we don't need to write to a long lived buffer. This saves massive amounts of memory
|
||||
/// as the number of connections grows.
|
||||
/// </summary>
|
||||
internal unsafe class BufferTextWriter : TextWriter, IBinaryWriter
|
||||
internal abstract unsafe class BufferTextWriter : TextWriter
|
||||
{
|
||||
private readonly Encoding _encoding;
|
||||
|
||||
@@ -31,13 +31,13 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
}
|
||||
|
||||
public BufferTextWriter(IWebSocket socket) :
|
||||
this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 128)
|
||||
this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024 * 4)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.TextWriter.#ctor", Justification = "It won't be used")]
|
||||
public BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
|
||||
protected BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
|
||||
{
|
||||
_write = write;
|
||||
_writeState = state;
|
||||
@@ -46,7 +46,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
_bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
private ChunkedWriter Writer
|
||||
protected internal ChunkedWriter Writer
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -79,17 +79,12 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
Writer.Write(value);
|
||||
}
|
||||
|
||||
public void Write(ArraySegment<byte> data)
|
||||
{
|
||||
Writer.Write(data);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
Writer.Flush();
|
||||
}
|
||||
|
||||
private class ChunkedWriter
|
||||
internal class ChunkedWriter
|
||||
{
|
||||
private int _charPos;
|
||||
private int _charLen;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
@@ -48,8 +51,14 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
// This normally waits until the callback is finished invoked but we don't care
|
||||
if (_callbackWrapper.TrySetInvoked())
|
||||
{
|
||||
// Bug #1549, .NET 4.0 has a bug where this throws if the CTS
|
||||
_registration.Dispose();
|
||||
try
|
||||
{
|
||||
_registration.Dispose();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// Bug #1549, .NET 4.0 has a bug where this throws if the CTS is disposed.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
{
|
||||
using (var stream = new MemoryStream(128))
|
||||
{
|
||||
var bufferWriter = new BufferTextWriter((buffer, state) =>
|
||||
var bufferWriter = new BinaryTextWriter((buffer, state) =>
|
||||
{
|
||||
((MemoryStream)state).Write(buffer.Array, buffer.Offset, buffer.Count);
|
||||
},
|
||||
@@ -236,8 +236,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
var platform = (int)Environment.OSVersion.Platform;
|
||||
if (platform == 4 || platform == 6 || platform == 128)
|
||||
if (MonoUtility.IsRunningMono)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
{
|
||||
internal static class MonoUtility
|
||||
{
|
||||
private static readonly Lazy<bool> _isRunningMono = new Lazy<bool>(() => CheckRunningOnMono());
|
||||
|
||||
internal static bool IsRunningMono
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isRunningMono.Value;
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This should never fail")]
|
||||
private static bool CheckRunningOnMono()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Type.GetType("Mono.Runtime") != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
_maxSize = maxSize;
|
||||
}
|
||||
|
||||
#if !CLIENT_NET45
|
||||
#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
|
||||
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code.")]
|
||||
public IPerformanceCounter QueueSizeCounter { get; set; }
|
||||
#endif
|
||||
@@ -62,19 +62,16 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
|
||||
if (_maxSize != null)
|
||||
{
|
||||
if (Interlocked.Read(ref _size) == _maxSize)
|
||||
// Increment the size if the queue
|
||||
if (Interlocked.Increment(ref _size) > _maxSize)
|
||||
{
|
||||
// REVIEW: Do we need to make the contract more clear between the
|
||||
// queue full case and the queue drained case? Should we throw an exeception instead?
|
||||
|
||||
Interlocked.Decrement(ref _size);
|
||||
|
||||
// We failed to enqueue because the size limit was reached
|
||||
return null;
|
||||
}
|
||||
|
||||
// Increment the size if the queue
|
||||
Interlocked.Increment(ref _size);
|
||||
|
||||
#if !CLIENT_NET45
|
||||
#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
|
||||
var counter = QueueSizeCounter;
|
||||
if (counter != null)
|
||||
{
|
||||
@@ -93,7 +90,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
|
||||
// Decrement the number of items left in the queue
|
||||
Interlocked.Decrement(ref queue._size);
|
||||
|
||||
#if !CLIENT_NET45
|
||||
#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
|
||||
var counter = QueueSizeCounter;
|
||||
if (counter != null)
|
||||
{
|
||||
|
||||
@@ -33,8 +33,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
_escapedKey = minifiedKey;
|
||||
}
|
||||
|
||||
public static void WriteCursors(TextWriter textWriter, IList<Cursor> cursors)
|
||||
public static void WriteCursors(TextWriter textWriter, IList<Cursor> cursors, string prefix)
|
||||
{
|
||||
textWriter.Write(prefix);
|
||||
|
||||
for (int i = 0; i < cursors.Count; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
@@ -48,7 +50,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteUlongAsHexToBuffer(ulong value, TextWriter textWriter)
|
||||
internal static void WriteUlongAsHexToBuffer(ulong value, TextWriter textWriter)
|
||||
{
|
||||
// This tracks the length of the output and serves as the index for the next character to be written into the pBuffer.
|
||||
// The length could reach up to 16 characters, so at least that much space should remain in the pBuffer.
|
||||
@@ -114,17 +116,17 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static List<Cursor> GetCursors(string cursor)
|
||||
public static List<Cursor> GetCursors(string cursor, string prefix)
|
||||
{
|
||||
return GetCursors(cursor, s => s);
|
||||
return GetCursors(cursor, prefix, s => s);
|
||||
}
|
||||
|
||||
public static List<Cursor> GetCursors(string cursor, Func<string, string> keyMaximizer)
|
||||
public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, string> keyMaximizer)
|
||||
{
|
||||
return GetCursors(cursor, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer);
|
||||
return GetCursors(cursor, prefix, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer);
|
||||
}
|
||||
|
||||
public static List<Cursor> GetCursors(string cursor, Func<string, object, string> keyMaximizer, object state)
|
||||
public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, object, string> keyMaximizer, object state)
|
||||
{
|
||||
// Technically GetCursors should never be called with a null value, so this is extra cautious
|
||||
if (String.IsNullOrEmpty(cursor))
|
||||
@@ -132,6 +134,14 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
throw new FormatException(Resources.Error_InvalidCursorFormat);
|
||||
}
|
||||
|
||||
// If the cursor does not begin with the prefix stream, it isn't necessarily a formatting problem.
|
||||
// The cursor with a different prefix might have had different, but also valid, formatting.
|
||||
// Null should be returned so new cursors will be generated
|
||||
if (!cursor.StartsWith(prefix, StringComparison.Ordinal))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var signals = new HashSet<string>();
|
||||
var cursors = new List<Cursor>();
|
||||
string currentKey = null;
|
||||
@@ -143,8 +153,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
var sbEscaped = new StringBuilder();
|
||||
Cursor parsedCursor;
|
||||
|
||||
foreach (var ch in cursor)
|
||||
for (int i = prefix.Length; i < cursor.Length; i++)
|
||||
{
|
||||
var ch = cursor[i];
|
||||
|
||||
// escape can only be true if we are consuming the key
|
||||
if (escape)
|
||||
{
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR.Infrastructure;
|
||||
|
||||
@@ -11,6 +13,8 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
{
|
||||
internal class DefaultSubscription : Subscription
|
||||
{
|
||||
internal static string _defaultCursorPrefix = GetCursorPrefix();
|
||||
|
||||
private List<Cursor> _cursors;
|
||||
private List<Topic> _cursorTopics;
|
||||
|
||||
@@ -36,7 +40,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
else
|
||||
{
|
||||
// Ensure delegate continues to use the C# Compiler static delegate caching optimization.
|
||||
_cursors = Cursor.GetCursors(cursor, (k, s) => UnminifyCursor(k, s), stringMinifier) ?? GetCursorsFromEventKeys(EventKeys, topics);
|
||||
_cursors = Cursor.GetCursors(cursor, _defaultCursorPrefix, (k, s) => UnminifyCursor(k, s), stringMinifier) ?? GetCursorsFromEventKeys(EventKeys, topics);
|
||||
}
|
||||
|
||||
_cursorTopics = new List<Topic>();
|
||||
@@ -126,7 +130,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
{
|
||||
lock (_cursors)
|
||||
{
|
||||
Cursor.WriteCursors(textWriter, _cursors);
|
||||
Cursor.WriteCursors(textWriter, _cursors, _defaultCursorPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,6 +200,22 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
return list;
|
||||
}
|
||||
|
||||
private static string GetCursorPrefix()
|
||||
{
|
||||
using (var rng = new RNGCryptoServiceProvider())
|
||||
{
|
||||
var data = new byte[4];
|
||||
rng.GetBytes(data);
|
||||
|
||||
using (var writer = new StringWriter(CultureInfo.InvariantCulture))
|
||||
{
|
||||
var randomValue = (ulong)BitConverter.ToUInt32(data, 0);
|
||||
Cursor.WriteUlongAsHexToBuffer(randomValue, writer);
|
||||
return "d-" + writer.ToString() + "-";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong GetMessageId(TopicLookup topics, string key)
|
||||
{
|
||||
Topic topic;
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!subscription.UnsetQueued() || workTask.IsFaulted)
|
||||
if (!subscription.UnsetQueued() || workTask.IsFaulted || workTask.IsCanceled)
|
||||
{
|
||||
// If we don't have more work to do just make the subscription null
|
||||
subscription = null;
|
||||
@@ -271,7 +271,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
Trace.TraceEvent(TraceEventType.Error, 0, "Work failed for " + subscription.Identity + ": " + task.Exception.GetBaseException());
|
||||
}
|
||||
|
||||
if (moreWork && !task.IsFaulted)
|
||||
if (moreWork && !task.IsFaulted && !task.IsCanceled)
|
||||
{
|
||||
PumpImpl(taskCompletionSource, subscription);
|
||||
}
|
||||
@@ -295,10 +295,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
|
||||
Trace.TraceEvent(TraceEventType.Verbose, 0, "Dispoing the broker");
|
||||
|
||||
//Check if OS is not Windows and exit
|
||||
var platform = (int)Environment.OSVersion.Platform;
|
||||
|
||||
if ((platform == 4) || (platform == 6) || (platform == 128))
|
||||
if (MonoUtility.IsRunningMono)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
private static readonly List<ArraySegment<Message>> _emptyList = new List<ArraySegment<Message>>();
|
||||
public readonly static MessageResult TerminalMessage = new MessageResult(terminal: true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="T:IList{Message}"/> associated with the result.
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an optimization to avoid allocations.")]
|
||||
public IList<ArraySegment<Message>> Messages { get; private set; }
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
{
|
||||
public class ScaleoutSubscription : Subscription
|
||||
{
|
||||
private const string _scaleoutCursorPrefix = "s-";
|
||||
|
||||
private readonly IList<ScaleoutMappingStore> _streams;
|
||||
private readonly List<Cursor> _cursors;
|
||||
|
||||
@@ -40,10 +42,15 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
}
|
||||
else
|
||||
{
|
||||
cursors = Cursor.GetCursors(cursor);
|
||||
cursors = Cursor.GetCursors(cursor, _scaleoutCursorPrefix);
|
||||
|
||||
// If the cursor had a default prefix, "d-", cursors might be null
|
||||
if (cursors == null)
|
||||
{
|
||||
cursors = new List<Cursor>();
|
||||
}
|
||||
// If the streams don't match the cursors then throw it out
|
||||
if (cursors.Count != _streams.Count)
|
||||
else if (cursors.Count != _streams.Count)
|
||||
{
|
||||
cursors.Clear();
|
||||
}
|
||||
@@ -63,7 +70,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
|
||||
public override void WriteCursor(TextWriter textWriter)
|
||||
{
|
||||
Cursor.WriteCursors(textWriter, _cursors);
|
||||
Cursor.WriteCursors(textWriter, _cursors, _scaleoutCursorPrefix);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "The list needs to be populated")]
|
||||
|
||||
@@ -120,13 +120,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
|
||||
WorkImpl(tcs);
|
||||
|
||||
// Fast Path
|
||||
if (tcs.Task.IsCompleted)
|
||||
{
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
return FinishAsync(tcs);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public bool SetQueued()
|
||||
@@ -140,19 +134,6 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
return Interlocked.CompareExchange(ref _state, State.Idle, State.Working) != State.Working;
|
||||
}
|
||||
|
||||
private static Task FinishAsync(TaskCompletionSource<object> tcs)
|
||||
{
|
||||
return tcs.Task.ContinueWith(task =>
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
{
|
||||
return TaskAsyncHelper.FromError(task.Exception);
|
||||
}
|
||||
|
||||
return TaskAsyncHelper.Empty;
|
||||
}).FastUnwrap();
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "We have a sync and async code path.")]
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to avoid user code taking the process down.")]
|
||||
private void WorkImpl(TaskCompletionSource<object> taskCompletionSource)
|
||||
@@ -200,7 +181,14 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletionSource.TrySetUnwrappedException(ex);
|
||||
if (ex.InnerException is TaskCanceledException)
|
||||
{
|
||||
taskCompletionSource.TrySetCanceled();
|
||||
}
|
||||
else
|
||||
{
|
||||
taskCompletionSource.TrySetUnwrappedException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -233,6 +221,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
|
||||
{
|
||||
taskCompletionSource.TrySetUnwrappedException(task.Exception);
|
||||
}
|
||||
else if (task.IsCanceled)
|
||||
{
|
||||
taskCompletionSource.TrySetCanceled();
|
||||
}
|
||||
else if (task.Result)
|
||||
{
|
||||
WorkImpl(taskCompletionSource);
|
||||
|
||||
@@ -71,9 +71,11 @@
|
||||
<Compile Include="Infrastructure\AckHandler.cs" />
|
||||
<Compile Include="Configuration\DefaultConfigurationManager.cs" />
|
||||
<Compile Include="Infrastructure\ArraySegmentTextReader.cs" />
|
||||
<Compile Include="Infrastructure\BinaryTextWriter.cs" />
|
||||
<Compile Include="Infrastructure\ConnectionManager.cs" />
|
||||
<Compile Include="ConnectionMessage.cs" />
|
||||
<Compile Include="Infrastructure\DefaultProtectedData.cs" />
|
||||
<Compile Include="Infrastructure\MonoUtility.cs" />
|
||||
<Compile Include="Infrastructure\DiffPair.cs" />
|
||||
<Compile Include="Infrastructure\DiffSet.cs" />
|
||||
<Compile Include="GlobalHost.cs" />
|
||||
@@ -280,4 +282,4 @@
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR.Configuration;
|
||||
using Microsoft.AspNet.SignalR.Hosting;
|
||||
@@ -165,7 +166,7 @@ namespace Microsoft.AspNet.SignalR
|
||||
|
||||
if (Transport == null)
|
||||
{
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport));
|
||||
return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport));
|
||||
}
|
||||
|
||||
string connectionToken = context.Request.QueryString["connectionToken"];
|
||||
@@ -173,10 +174,17 @@ namespace Microsoft.AspNet.SignalR
|
||||
// If there's no connection id then this is a bad request
|
||||
if (String.IsNullOrEmpty(connectionToken))
|
||||
{
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken));
|
||||
return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken));
|
||||
}
|
||||
|
||||
string connectionId = GetConnectionId(context, connectionToken);
|
||||
string connectionId;
|
||||
string message;
|
||||
int statusCode;
|
||||
|
||||
if (!TryGetConnectionId(context, connectionToken, out connectionId, out message, out statusCode))
|
||||
{
|
||||
return FailResponse(context.Response, message, statusCode);
|
||||
}
|
||||
|
||||
// Set the transport's connection id to the unprotected one
|
||||
Transport.ConnectionId = connectionId;
|
||||
@@ -227,10 +235,21 @@ namespace Microsoft.AspNet.SignalR
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to catch any exception when unprotecting data.")]
|
||||
internal string GetConnectionId(HostContext context, string connectionToken)
|
||||
internal bool TryGetConnectionId(HostContext context,
|
||||
string connectionToken,
|
||||
out string connectionId,
|
||||
out string message,
|
||||
out int statusCode)
|
||||
{
|
||||
string unprotectedConnectionToken = null;
|
||||
|
||||
// connectionId is only valid when this method returns true
|
||||
connectionId = null;
|
||||
|
||||
// message and statusCode are only valid when this method returns false
|
||||
message = null;
|
||||
statusCode = 400;
|
||||
|
||||
try
|
||||
{
|
||||
unprotectedConnectionToken = ProtectedData.Unprotect(connectionToken, Purposes.ConnectionToken);
|
||||
@@ -242,21 +261,24 @@ namespace Microsoft.AspNet.SignalR
|
||||
|
||||
if (String.IsNullOrEmpty(unprotectedConnectionToken))
|
||||
{
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat));
|
||||
message = String.Format(CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat);
|
||||
return false;
|
||||
}
|
||||
|
||||
var tokens = unprotectedConnectionToken.Split(SplitChars, 2);
|
||||
|
||||
string connectionId = tokens[0];
|
||||
connectionId = tokens[0];
|
||||
string tokenUserName = tokens.Length > 1 ? tokens[1] : String.Empty;
|
||||
string userName = GetUserIdentity(context);
|
||||
|
||||
if (!String.Equals(tokenUserName, userName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity));
|
||||
message = String.Format(CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity);
|
||||
statusCode = 403;
|
||||
return false;
|
||||
}
|
||||
|
||||
return connectionId;
|
||||
return true;
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to prevent any failures in unprotecting")]
|
||||
@@ -477,6 +499,12 @@ namespace Microsoft.AspNet.SignalR
|
||||
return context.Response.End(data);
|
||||
}
|
||||
|
||||
private static Task FailResponse(IResponse response, string message, int statusCode = 400)
|
||||
{
|
||||
response.StatusCode = statusCode;
|
||||
return response.End(message);
|
||||
}
|
||||
|
||||
private static bool IsNegotiationRequest(IRequest request)
|
||||
{
|
||||
return request.Url.LocalPath.EndsWith("/negotiate", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* ASP.NET SignalR JavaScript Library v1.1.3
|
||||
* ASP.NET SignalR JavaScript Library v1.2.2
|
||||
* http://signalr.net/
|
||||
*
|
||||
* Copyright Microsoft Open Technologies, Inc. All rights reserved.
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
|
||||
/// <reference path="jquery.signalR.js" />
|
||||
(function ($, window) {
|
||||
(function ($, window, undefined) {
|
||||
/// <param name="$" type="jQuery" />
|
||||
"use strict";
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR.Infrastructure;
|
||||
@@ -159,7 +163,7 @@ namespace Microsoft.AspNet.SignalR
|
||||
{
|
||||
// observe Exception
|
||||
#if !WINDOWS_PHONE && !SILVERLIGHT && !NETFX_CORE
|
||||
Trace.TraceError("SignalR exception thrown by Task: {0}", exception);
|
||||
Trace.TraceWarning("SignalR exception thrown by Task: {0}", exception);
|
||||
#endif
|
||||
handler(exception, state);
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flowed to the caller.")]
|
||||
private Task ProcessReceiveRequest(ITransportConnection connection)
|
||||
protected Task ProcessReceiveRequest(ITransportConnection connection)
|
||||
{
|
||||
Func<Task> initialize = null;
|
||||
|
||||
@@ -273,7 +273,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
{
|
||||
var context = (MessageContext)state;
|
||||
|
||||
response.TimedOut = context.Transport.IsTimedOut;
|
||||
response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested;
|
||||
|
||||
// If we're telling the client to disconnect then clean up the instantiated connection.
|
||||
if (response.Disconnect)
|
||||
@@ -282,7 +282,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
return context.Transport.Send(response).Then(c => OnDisconnectMessage(c), context)
|
||||
.Then(() => TaskAsyncHelper.False);
|
||||
}
|
||||
else if (response.TimedOut || response.Aborted)
|
||||
else if (context.Transport.IsTimedOut || response.Aborted)
|
||||
{
|
||||
context.Registration.Dispose();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR.Hosting;
|
||||
using Microsoft.AspNet.SignalR.Infrastructure;
|
||||
@@ -252,7 +253,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
{
|
||||
var context = (MessageContext)state;
|
||||
|
||||
response.TimedOut = context.Transport.IsTimedOut;
|
||||
response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested;
|
||||
|
||||
Task task = TaskAsyncHelper.Empty;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
private readonly Action<TextWriter> _writeCursor;
|
||||
|
||||
public PersistentResponse()
|
||||
: this(message => true, writer => { })
|
||||
: this(message => false, writer => { })
|
||||
{
|
||||
|
||||
}
|
||||
@@ -61,9 +61,10 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
public bool Aborted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if the connection timed out.
|
||||
/// True if the client should try reconnecting.
|
||||
/// </summary>
|
||||
public bool TimedOut { get; set; }
|
||||
// This is set when the host is shutting down.
|
||||
public bool Reconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Signed token representing the list of groups. Updates on change.
|
||||
@@ -106,7 +107,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
jsonWriter.WriteValue(1);
|
||||
}
|
||||
|
||||
if (TimedOut)
|
||||
if (Reconnect)
|
||||
{
|
||||
jsonWriter.WritePropertyName("T");
|
||||
jsonWriter.WriteValue(1);
|
||||
|
||||
@@ -130,6 +130,14 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
}
|
||||
}
|
||||
|
||||
protected CancellationToken HostShutdownToken
|
||||
{
|
||||
get
|
||||
{
|
||||
return _hostShutdownToken;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTimedOut
|
||||
{
|
||||
get
|
||||
@@ -186,7 +194,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
|
||||
protected virtual TextWriter CreateResponseWriter()
|
||||
{
|
||||
return new BufferTextWriter(Context.Response);
|
||||
return new BinaryTextWriter(Context.Response);
|
||||
}
|
||||
|
||||
protected void IncrementErrors()
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
private bool _isAlive = true;
|
||||
|
||||
private readonly Action<string> _message;
|
||||
private readonly Action<bool> _closed;
|
||||
private readonly Action _closed;
|
||||
private readonly Action<Exception> _error;
|
||||
|
||||
public WebSocketTransport(HostContext context,
|
||||
@@ -74,28 +74,39 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
|
||||
public override Task ProcessRequest(ITransportConnection connection)
|
||||
{
|
||||
var webSocketRequest = _context.Request as IWebSocketRequest;
|
||||
|
||||
// Throw if the server implementation doesn't support websockets
|
||||
if (webSocketRequest == null)
|
||||
if (IsAbortRequest)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported);
|
||||
return connection.Abort(ConnectionId);
|
||||
}
|
||||
|
||||
return webSocketRequest.AcceptWebSocketRequest(socket =>
|
||||
else
|
||||
{
|
||||
_socket = socket;
|
||||
socket.OnClose = _closed;
|
||||
socket.OnMessage = _message;
|
||||
socket.OnError = _error;
|
||||
var webSocketRequest = _context.Request as IWebSocketRequest;
|
||||
|
||||
return ProcessRequestCore(connection);
|
||||
});
|
||||
// Throw if the server implementation doesn't support websockets
|
||||
if (webSocketRequest == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported);
|
||||
}
|
||||
|
||||
Connection = connection;
|
||||
InitializePersistentState();
|
||||
|
||||
return webSocketRequest.AcceptWebSocketRequest(socket =>
|
||||
{
|
||||
_socket = socket;
|
||||
socket.OnClose = _closed;
|
||||
socket.OnMessage = _message;
|
||||
socket.OnError = _error;
|
||||
|
||||
return ProcessReceiveRequest(connection);
|
||||
},
|
||||
InitializeTcs.Task);
|
||||
}
|
||||
}
|
||||
|
||||
protected override TextWriter CreateResponseWriter()
|
||||
{
|
||||
return new BufferTextWriter(_socket);
|
||||
return new BinaryTextWriter(_socket);
|
||||
}
|
||||
|
||||
public override Task Send(object value)
|
||||
@@ -113,6 +124,11 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
return Send((object)response);
|
||||
}
|
||||
|
||||
protected internal override Task InitializeResponse(ITransportConnection connection)
|
||||
{
|
||||
return _socket.Send("{}");
|
||||
}
|
||||
|
||||
private static Task PerformSend(object state)
|
||||
{
|
||||
var context = (WebSocketTransportContext)state;
|
||||
@@ -131,18 +147,11 @@ namespace Microsoft.AspNet.SignalR.Transports
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClosed(bool clean)
|
||||
private void OnClosed()
|
||||
{
|
||||
Trace.TraceInformation("CloseSocket({0}, {1})", clean, ConnectionId);
|
||||
|
||||
// If we performed a clean disconnect then we go through the normal disconnect routine. However,
|
||||
// If we performed an unclean disconnect we want to mark the connection as "not alive" and let the
|
||||
// HeartBeat clean it up. This is to maintain consistency across the transports.
|
||||
if (clean)
|
||||
{
|
||||
Abort();
|
||||
}
|
||||
Trace.TraceInformation("CloseSocket({0})", ConnectionId);
|
||||
|
||||
// Require a request to /abort to stop tracking the connection. #2195
|
||||
_isAlive = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR.Hosting;
|
||||
using Microsoft.AspNet.SignalR.Owin.Infrastructure;
|
||||
@@ -65,9 +66,19 @@ namespace Microsoft.AspNet.SignalR.Owin
|
||||
|
||||
if (!_connection.Authorize(serverRequest))
|
||||
{
|
||||
// If we failed to authorize the request then return a 403 since the request
|
||||
// can't do anything
|
||||
return EndResponse(environment, 403, "Forbidden");
|
||||
IPrincipal user = hostContext.Request.User;
|
||||
if (user != null && user.Identity.IsAuthenticated)
|
||||
{
|
||||
// If we failed to authorize the request then return a 403 since the request
|
||||
// can't do anything
|
||||
return EndResponse(environment, 403, "Forbidden");
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we failed to authorize the request and the user is not authenticated
|
||||
// then return a 401
|
||||
return EndResponse(environment, 401, "Unauthorized");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.SignalR.Owin.Infrastructure;
|
||||
using Microsoft.AspNet.SignalR.Hosting;
|
||||
|
||||
namespace Microsoft.AspNet.SignalR.Owin
|
||||
{
|
||||
@@ -138,15 +139,17 @@ namespace Microsoft.AspNet.SignalR.Owin
|
||||
}
|
||||
|
||||
#if NET45
|
||||
public Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback)
|
||||
public Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback, Task initTask)
|
||||
{
|
||||
var accept = _environment.Get<Action<IDictionary<string, object>, WebSocketFunc>>(OwinConstants.WebSocketAccept);
|
||||
if (accept == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.Error_NotWebSocketRequest);
|
||||
var response = new ServerResponse(_environment);
|
||||
response.StatusCode = 400;
|
||||
return response.End(Resources.Error_NotWebSocketRequest);
|
||||
}
|
||||
|
||||
var handler = new OwinWebSocketHandler(callback);
|
||||
var handler = new OwinWebSocketHandler(callback, initTask);
|
||||
accept(null, handler.ProcessRequestAsync);
|
||||
return TaskAsyncHelper.Empty;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,18 @@ namespace Microsoft.AspNet.SignalR.Owin
|
||||
get { return _callCancelled; }
|
||||
}
|
||||
|
||||
public int StatusCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return _environment.Get<int>(OwinConstants.ResponseStatusCode);
|
||||
}
|
||||
set
|
||||
{
|
||||
_environment[OwinConstants.ResponseStatusCode] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string ContentType
|
||||
{
|
||||
get { return ResponseHeaders.GetHeader("Content-Type"); }
|
||||
|
||||
@@ -116,14 +116,12 @@ namespace NzbDrone.Api.Test.MappingTests
|
||||
profileResource.InjectTo<Profile>();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_map_tracked_command()
|
||||
{
|
||||
var profileResource = new ApplicationUpdateCommand();
|
||||
profileResource.InjectTo<CommandResource>();
|
||||
var commandResource = new CommandModel { Body = new ApplicationUpdateCommand() };
|
||||
commandResource.InjectTo<CommandResource>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Omu.ValueInjecter">
|
||||
<HintPath>..\packages\valueinjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
|
||||
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -104,7 +104,7 @@
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -112,4 +112,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
|
||||
<package id="NUnit" version="2.6.3" targetFramework="net40" />
|
||||
<package id="valueinjecter" version="2.3.3" targetFramework="net40" />
|
||||
</packages>
|
||||
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
|
||||
</packages>
|
||||
|
||||
62
src/NzbDrone.Api/Authentication/1tews5g3.gd1~
Normal file
62
src/NzbDrone.Api/Authentication/1tews5g3.gd1~
Normal file
@@ -0,0 +1,62 @@
|
||||
using Nancy;
|
||||
using Nancy.Authentication.Basic;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Cryptography;
|
||||
using NzbDrone.Api.Extensions.Pipelines;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Authentication
|
||||
{
|
||||
public class EnableAuthInNancy : IRegisterNancyPipeline
|
||||
{
|
||||
private readonly IAuthenticationService _authenticationService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public EnableAuthInNancy(IAuthenticationService authenticationService,
|
||||
IConfigService configService,
|
||||
IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_authenticationService = authenticationService;
|
||||
_configService = configService;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public void Register(IPipelines pipelines)
|
||||
{
|
||||
RegisterFormsAuth(pipelines);
|
||||
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
|
||||
pipelines.BeforeRequest.AddItemToEndOfPipeline(RequiresAuthentication);
|
||||
}
|
||||
|
||||
private Response RequiresAuthentication(NancyContext context)
|
||||
{
|
||||
Response response = null;
|
||||
|
||||
if (!_authenticationService.IsAuthenticated(context))
|
||||
{
|
||||
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void RegisterFormsAuth(IPipelines pipelines)
|
||||
{
|
||||
var cryptographyConfiguration = new CryptographyConfiguration(
|
||||
new RijndaelEncryptionProvider(new PassphraseKeyGenerator(_configService.RijndaelPassphrase,
|
||||
new byte[] {1, 2, 3, 4, 5, 6, 7, 8})),
|
||||
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase,
|
||||
new byte[] {1, 2, 3, 4, 5, 6, 7, 8}))
|
||||
);
|
||||
|
||||
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
|
||||
{
|
||||
RedirectUrl = "~/login",
|
||||
UserMapper = _authenticationService,
|
||||
CryptographyConfiguration = cryptographyConfiguration
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
48
src/NzbDrone.Api/Authentication/AuthenticationModule.cs
Normal file
48
src/NzbDrone.Api/Authentication/AuthenticationModule.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Nancy;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Extensions;
|
||||
using Nancy.ModelBinding;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Authentication
|
||||
{
|
||||
public class AuthenticationModule : NancyModule
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public AuthenticationModule(IUserService userService, IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_userService = userService;
|
||||
_configFileProvider = configFileProvider;
|
||||
Post["/login"] = x => Login(this.Bind<LoginResource>());
|
||||
Get["/logout"] = x => Logout();
|
||||
}
|
||||
|
||||
private Response Login(LoginResource resource)
|
||||
{
|
||||
var user = _userService.FindUser(resource.Username, resource.Password);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return Context.GetRedirect("~/login?returnUrl=" + (string)Request.Query.returnUrl);
|
||||
}
|
||||
|
||||
DateTime? expiry = null;
|
||||
|
||||
if (resource.RememberMe)
|
||||
{
|
||||
expiry = DateTime.UtcNow.AddDays(7);
|
||||
}
|
||||
|
||||
return this.LoginAndRedirect(user.Identifier, expiry);
|
||||
}
|
||||
|
||||
private Response Logout()
|
||||
{
|
||||
return this.LogoutAndRedirect(_configFileProvider.UrlBase + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,16 @@
|
||||
using System.Linq;
|
||||
using Nancy;
|
||||
using Nancy.Authentication.Basic;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Security;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Authentication
|
||||
{
|
||||
public interface IAuthenticationService : IUserValidator
|
||||
public interface IAuthenticationService : IUserValidator, IUserMapper
|
||||
{
|
||||
bool IsAuthenticated(NancyContext context);
|
||||
}
|
||||
@@ -17,37 +19,52 @@ namespace NzbDrone.Api.Authentication
|
||||
public class AuthenticationService : IAuthenticationService
|
||||
{
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IUserService _userService;
|
||||
private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" };
|
||||
|
||||
private static String API_KEY;
|
||||
private static AuthenticationType AUTH_METHOD;
|
||||
|
||||
public AuthenticationService(IConfigFileProvider configFileProvider)
|
||||
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
|
||||
{
|
||||
_configFileProvider = configFileProvider;
|
||||
_userService = userService;
|
||||
API_KEY = configFileProvider.ApiKey;
|
||||
AUTH_METHOD = configFileProvider.AuthenticationMethod;
|
||||
}
|
||||
|
||||
public IUserIdentity Validate(string username, string password)
|
||||
{
|
||||
if (!Enabled)
|
||||
if (AUTH_METHOD == AuthenticationType.None)
|
||||
{
|
||||
return AnonymousUser;
|
||||
}
|
||||
|
||||
if (_configFileProvider.Username.Equals(username) &&
|
||||
_configFileProvider.Password.Equals(password))
|
||||
var user = _userService.FindUser(username, password);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return new NzbDroneUser { UserName = username };
|
||||
return new NzbDroneUser { UserName = user.Username };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool Enabled
|
||||
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
|
||||
{
|
||||
get
|
||||
if (AUTH_METHOD == AuthenticationType.None)
|
||||
{
|
||||
return _configFileProvider.AuthenticationEnabled;
|
||||
return AnonymousUser;
|
||||
}
|
||||
|
||||
var user = _userService.FindUser(identifier);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return new NzbDroneUser { UserName = user.Username };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsAuthenticated(NancyContext context)
|
||||
@@ -59,13 +76,13 @@ namespace NzbDrone.Api.Authentication
|
||||
return ValidApiKey(apiKey);
|
||||
}
|
||||
|
||||
if (AUTH_METHOD == AuthenticationType.None)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.Request.IsFeedRequest())
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ValidUser(context) || ValidApiKey(apiKey))
|
||||
{
|
||||
return true;
|
||||
@@ -74,7 +91,12 @@ namespace NzbDrone.Api.Authentication
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Enabled)
|
||||
if (context.Request.IsLoginRequest())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.Request.IsContentRequest())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,46 @@
|
||||
using Nancy;
|
||||
using System;
|
||||
using System.Text;
|
||||
using Nancy;
|
||||
using Nancy.Authentication.Basic;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Cryptography;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Api.Extensions.Pipelines;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Authentication
|
||||
{
|
||||
public class EnableAuthInNancy : IRegisterNancyPipeline
|
||||
{
|
||||
private readonly IAuthenticationService _authenticationService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public EnableAuthInNancy(IAuthenticationService authenticationService)
|
||||
public EnableAuthInNancy(IAuthenticationService authenticationService,
|
||||
IConfigService configService,
|
||||
IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_authenticationService = authenticationService;
|
||||
_configService = configService;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public void Register(IPipelines pipelines)
|
||||
{
|
||||
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
|
||||
if (_configFileProvider.AuthenticationMethod == AuthenticationType.Forms)
|
||||
{
|
||||
RegisterFormsAuth(pipelines);
|
||||
}
|
||||
|
||||
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
|
||||
{
|
||||
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
|
||||
}
|
||||
|
||||
pipelines.BeforeRequest.AddItemToEndOfPipeline(RequiresAuthentication);
|
||||
pipelines.AfterRequest.AddItemToEndOfPipeline(RemoveLoginHooksForApiCalls);
|
||||
}
|
||||
|
||||
private Response RequiresAuthentication(NancyContext context)
|
||||
@@ -31,5 +54,33 @@ namespace NzbDrone.Api.Authentication
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void RegisterFormsAuth(IPipelines pipelines)
|
||||
{
|
||||
var cryptographyConfiguration = new CryptographyConfiguration(
|
||||
new RijndaelEncryptionProvider(new PassphraseKeyGenerator(_configService.RijndaelPassphrase, Encoding.ASCII.GetBytes(_configService.RijndaelSalt))),
|
||||
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt)))
|
||||
);
|
||||
|
||||
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
|
||||
{
|
||||
RedirectUrl = _configFileProvider.UrlBase + "/login",
|
||||
UserMapper = _authenticationService,
|
||||
CryptographyConfiguration = cryptographyConfiguration
|
||||
});
|
||||
}
|
||||
|
||||
private void RemoveLoginHooksForApiCalls(NancyContext context)
|
||||
{
|
||||
if (context.Request.IsApiRequest())
|
||||
{
|
||||
if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&
|
||||
context.Response.Headers["Location"].StartsWith("/login", StringComparison.InvariantCultureIgnoreCase)) ||
|
||||
context.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
context.Response = new { Error = "Unauthorized" }.AsResponse(HttpStatusCode.Unauthorized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
9
src/NzbDrone.Api/Authentication/LoginResource.cs
Normal file
9
src/NzbDrone.Api/Authentication/LoginResource.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Api.Authentication
|
||||
{
|
||||
public class LoginResource
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Api.Blacklist
|
||||
{
|
||||
@@ -13,6 +14,9 @@ namespace NzbDrone.Api.Blacklist
|
||||
public string SourceTitle { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public DownloadProtocol Protocol { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
public SeriesResource Series { get; set; }
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace NzbDrone.Api.Calendar
|
||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||
|
||||
var episodes = _episodeService.EpisodesBetweenDates(start, end);
|
||||
var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
|
||||
var icalCalendar = new iCalendar();
|
||||
|
||||
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
|
||||
|
||||
@@ -23,14 +23,17 @@ namespace NzbDrone.Api.Calendar
|
||||
{
|
||||
var start = DateTime.Today;
|
||||
var end = DateTime.Today.AddDays(2);
|
||||
var includeUnmonitored = false;
|
||||
|
||||
var queryStart = Request.Query.Start;
|
||||
var queryEnd = Request.Query.End;
|
||||
var queryIncludeUnmonitored = Request.Query.Unmonitored;
|
||||
|
||||
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
|
||||
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
|
||||
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
|
||||
|
||||
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end));
|
||||
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end, includeUnmonitored));
|
||||
|
||||
return resources.OrderBy(e => e.AirDateUtc).ToList();
|
||||
}
|
||||
|
||||
@@ -53,11 +53,9 @@ namespace NzbDrone.Api.ClientSchema
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
return result.OrderBy(r => r.Order).ToList();
|
||||
}
|
||||
|
||||
|
||||
public static object ReadFormSchema(List<Field> fields, Type targetType, object defaults = null)
|
||||
{
|
||||
Ensure.That(targetType, () => targetType).IsNotNull();
|
||||
|
||||
@@ -4,10 +4,9 @@ using System.Linq;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Api.Mapping;
|
||||
using NzbDrone.Api.Validation;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ProgressMessaging;
|
||||
using NzbDrone.SignalR;
|
||||
@@ -15,56 +14,53 @@ using NzbDrone.SignalR;
|
||||
|
||||
namespace NzbDrone.Api.Commands
|
||||
{
|
||||
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, Command>, IHandle<CommandUpdatedEvent>
|
||||
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
|
||||
{
|
||||
private readonly ICommandExecutor _commandExecutor;
|
||||
private readonly IContainer _container;
|
||||
private readonly ITrackCommands _trackCommands;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly IServiceFactory _serviceFactory;
|
||||
|
||||
public CommandModule(ICommandExecutor commandExecutor,
|
||||
public CommandModule(IManageCommandQueue commandQueueManager,
|
||||
IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IContainer container,
|
||||
ITrackCommands trackCommands)
|
||||
IServiceFactory serviceFactory)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_commandExecutor = commandExecutor;
|
||||
_container = container;
|
||||
_trackCommands = trackCommands;
|
||||
_commandQueueManager = commandQueueManager;
|
||||
_serviceFactory = serviceFactory;
|
||||
|
||||
GetResourceById = GetCommand;
|
||||
CreateResource = StartCommand;
|
||||
GetResourceAll = GetAllCommands;
|
||||
GetResourceAll = GetStartedCommands;
|
||||
|
||||
PostValidator.RuleFor(c => c.Name).NotBlank();
|
||||
}
|
||||
|
||||
private CommandResource GetCommand(int id)
|
||||
{
|
||||
return _trackCommands.GetById(id).InjectTo<CommandResource>();
|
||||
return _commandQueueManager.Get(id).InjectTo<CommandResource>();
|
||||
}
|
||||
|
||||
private int StartCommand(CommandResource commandResource)
|
||||
{
|
||||
var commandType =
|
||||
_container.GetImplementations(typeof(Command))
|
||||
.Single(c => c.Name.Replace("Command", "")
|
||||
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||
_serviceFactory.GetImplementations(typeof (Command))
|
||||
.Single(c => c.Name.Replace("Command", "")
|
||||
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
dynamic command = Request.Body.FromJson(commandType);
|
||||
command.Manual = true;
|
||||
command.Trigger = CommandTrigger.Manual;
|
||||
|
||||
var trackedCommand = (Command)_commandExecutor.PublishCommandAsync(command);
|
||||
var trackedCommand = _commandQueueManager.Push(command, CommandPriority.Normal, CommandTrigger.Manual);
|
||||
return trackedCommand.Id;
|
||||
}
|
||||
|
||||
private List<CommandResource> GetAllCommands()
|
||||
private List<CommandResource> GetStartedCommands()
|
||||
{
|
||||
return ToListResource(_trackCommands.RunningCommands);
|
||||
return ToListResource(_commandQueueManager.GetStarted());
|
||||
}
|
||||
|
||||
public void Handle(CommandUpdatedEvent message)
|
||||
{
|
||||
if (message.Command.SendUpdatesToClient)
|
||||
if (message.Command.Body.SendUpdatesToClient)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, message.Command.Id);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Api.Commands
|
||||
{
|
||||
@@ -8,11 +9,75 @@ namespace NzbDrone.Api.Commands
|
||||
{
|
||||
public String Name { get; set; }
|
||||
public String Message { get; set; }
|
||||
public DateTime StartedOn { get; set; }
|
||||
public DateTime StateChangeTime { get; set; }
|
||||
public Boolean SendUpdatesToClient { get; set; }
|
||||
public CommandStatus State { get; set; }
|
||||
public Command Body { get; set; }
|
||||
public CommandPriority Priority { get; set; }
|
||||
public CommandStatus Status { get; set; }
|
||||
public DateTime Queued { get; set; }
|
||||
public DateTime? Started { get; set; }
|
||||
public DateTime? Ended { get; set; }
|
||||
public TimeSpan? Duration { get; set; }
|
||||
public string Exception { get; set; }
|
||||
public CommandTrigger Trigger { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string CompletionMessage { get; set; }
|
||||
|
||||
//Legacy
|
||||
public CommandStatus State
|
||||
{
|
||||
get
|
||||
{
|
||||
return Status;
|
||||
}
|
||||
|
||||
set { }
|
||||
}
|
||||
|
||||
public Boolean Manual
|
||||
{
|
||||
get
|
||||
{
|
||||
return Trigger == CommandTrigger.Manual;
|
||||
}
|
||||
|
||||
set { }
|
||||
}
|
||||
|
||||
public DateTime StartedOn
|
||||
{
|
||||
get
|
||||
{
|
||||
return Queued;
|
||||
}
|
||||
|
||||
set { }
|
||||
}
|
||||
|
||||
public DateTime? StateChangeTime
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
if (Started.HasValue) return Started.Value;
|
||||
|
||||
return Ended;
|
||||
}
|
||||
|
||||
set { }
|
||||
}
|
||||
|
||||
public Boolean SendUpdatesToClient
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Body != null) return Body.SendUpdatesToClient;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
set { }
|
||||
}
|
||||
|
||||
public DateTime? LastExecutionTime { get; set; }
|
||||
public Boolean Manual { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,19 @@ namespace NzbDrone.Api.Config
|
||||
{
|
||||
public class DownloadClientConfigModule : NzbDroneConfigModule<DownloadClientConfigResource>
|
||||
{
|
||||
public DownloadClientConfigModule(IConfigService configService, RootFolderValidator rootFolderValidator, PathExistsValidator pathExistsValidator)
|
||||
public DownloadClientConfigModule(IConfigService configService,
|
||||
RootFolderValidator rootFolderValidator,
|
||||
PathExistsValidator pathExistsValidator,
|
||||
MappedNetworkDriveValidator mappedNetworkDriveValidator)
|
||||
: base(configService)
|
||||
{
|
||||
SharedValidator.RuleFor(c => c.DownloadedEpisodesFolder)
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(rootFolderValidator)
|
||||
.SetValidator(mappedNetworkDriveValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
using System.Reflection;
|
||||
using FluentValidation;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Update;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -13,11 +15,13 @@ namespace NzbDrone.Api.Config
|
||||
public class HostConfigModule : NzbDroneRestModule<HostConfigResource>
|
||||
{
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public HostConfigModule(IConfigFileProvider configFileProvider)
|
||||
public HostConfigModule(IConfigFileProvider configFileProvider, IUserService userService)
|
||||
: base("/config/host")
|
||||
{
|
||||
_configFileProvider = configFileProvider;
|
||||
_userService = userService;
|
||||
|
||||
GetResourceSingle = GetHostConfig;
|
||||
GetResourceById = GetHostConfig;
|
||||
@@ -26,8 +30,8 @@ namespace NzbDrone.Api.Config
|
||||
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
|
||||
SharedValidator.RuleFor(c => c.Port).ValidPort();
|
||||
|
||||
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled);
|
||||
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled);
|
||||
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
|
||||
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
|
||||
|
||||
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
|
||||
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
|
||||
@@ -46,6 +50,14 @@ namespace NzbDrone.Api.Config
|
||||
resource.InjectFrom(_configFileProvider);
|
||||
resource.Id = 1;
|
||||
|
||||
var user = _userService.FindUser();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
resource.Username = user.Username;
|
||||
resource.Password = user.Password;
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -61,6 +73,11 @@ namespace NzbDrone.Api.Config
|
||||
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
|
||||
|
||||
_configFileProvider.SaveConfigDictionary(dictionary);
|
||||
|
||||
if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_userService.Upsert(resource.Username, resource.Password);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Authentication;
|
||||
using NzbDrone.Core.Update;
|
||||
|
||||
namespace NzbDrone.Api.Config
|
||||
@@ -11,7 +12,7 @@ namespace NzbDrone.Api.Config
|
||||
public Int32 SslPort { get; set; }
|
||||
public Boolean EnableSsl { get; set; }
|
||||
public Boolean LaunchBrowser { get; set; }
|
||||
public bool AuthenticationEnabled { get; set; }
|
||||
public AuthenticationType AuthenticationMethod { get; set; }
|
||||
public Boolean AnalyticsEnabled { get; set; }
|
||||
public String Username { get; set; }
|
||||
public String Password { get; set; }
|
||||
|
||||
@@ -14,5 +14,7 @@ namespace NzbDrone.Api.Config
|
||||
public String LongDateFormat { get; set; }
|
||||
public String TimeFormat { get; set; }
|
||||
public Boolean ShowRelativeDates { get; set; }
|
||||
|
||||
public Boolean EnableColorImpairedMode { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.DownloadClient
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Validate(DownloadClientDefinition definition)
|
||||
protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
|
||||
{
|
||||
if (!definition.Enable) return;
|
||||
base.Validate(definition);
|
||||
base.Validate(definition, includeWarnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ namespace NzbDrone.Api.Episodes
|
||||
public Nullable<Int32> SceneAbsoluteEpisodeNumber { get; set; }
|
||||
public Nullable<Int32> SceneEpisodeNumber { get; set; }
|
||||
public Nullable<Int32> SceneSeasonNumber { get; set; }
|
||||
public Int32 TvDbEpisodeId { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public DateTime? GrabDate { get; set; }
|
||||
public String SeriesTitle { get; set; }
|
||||
|
||||
@@ -26,5 +26,15 @@ namespace NzbDrone.Api.Extensions
|
||||
request.UserHostAddress.Equals("127.0.0.1") ||
|
||||
request.UserHostAddress.Equals("::1"));
|
||||
}
|
||||
|
||||
public static bool IsLoginRequest(this Request request)
|
||||
{
|
||||
return request.Path.Equals("/login", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool IsContentRequest(this Request request)
|
||||
{
|
||||
return request.Path.StartsWith("/Content/", StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,31 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Nancy;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Api.FileSystem
|
||||
{
|
||||
public class FileSystemModule : NzbDroneApiModule
|
||||
{
|
||||
private readonly IFileSystemLookupService _fileSystemLookupService;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
|
||||
public FileSystemModule(IFileSystemLookupService fileSystemLookupService)
|
||||
public FileSystemModule(IFileSystemLookupService fileSystemLookupService,
|
||||
IDiskProvider diskProvider,
|
||||
IDiskScanService diskScanService)
|
||||
: base("/filesystem")
|
||||
{
|
||||
_fileSystemLookupService = fileSystemLookupService;
|
||||
_diskProvider = diskProvider;
|
||||
_diskScanService = diskScanService;
|
||||
Get["/"] = x => GetContents();
|
||||
Get["/type"] = x => GetEntityType();
|
||||
Get["/mediafiles"] = x => GetMediaFiles();
|
||||
}
|
||||
|
||||
private Response GetContents()
|
||||
@@ -29,5 +41,36 @@ namespace NzbDrone.Api.FileSystem
|
||||
|
||||
return _fileSystemLookupService.LookupContents((string)pathQuery.Value, includeFiles).AsResponse();
|
||||
}
|
||||
|
||||
private Response GetEntityType()
|
||||
{
|
||||
var pathQuery = Request.Query.path;
|
||||
var path = (string)pathQuery.Value;
|
||||
|
||||
if (_diskProvider.FileExists(path))
|
||||
{
|
||||
return new { type = "file" }.AsResponse();
|
||||
}
|
||||
|
||||
//Return folder even if it doesn't exist on disk to avoid leaking anything from the UI about the underlying system
|
||||
return new { type = "folder" }.AsResponse();
|
||||
}
|
||||
|
||||
private Response GetMediaFiles()
|
||||
{
|
||||
var pathQuery = Request.Query.path;
|
||||
var path = (string)pathQuery.Value;
|
||||
|
||||
if (!_diskProvider.FolderExists(path))
|
||||
{
|
||||
return new string[0].AsResponse();
|
||||
}
|
||||
|
||||
return _diskScanService.GetVideoFiles(path).Select(f => new {
|
||||
Path = f,
|
||||
RelativePath = path.GetRelativePath(f),
|
||||
Name = Path.GetFileName(f)
|
||||
}).AsResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
var pathToFile = mapper.Map(resourceUrl);
|
||||
var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64();
|
||||
|
||||
return resourceUrl + "?h=" + hash;
|
||||
return resourceUrl + "?h=" + hash.Trim('=');
|
||||
}
|
||||
|
||||
private static bool ShouldBreakCache(string path)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
@@ -17,7 +18,14 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
var path = Path.Combine("Content", "Images", "favicon.ico");
|
||||
var fileName = "favicon.ico";
|
||||
|
||||
if (BuildInfo.IsDebug)
|
||||
{
|
||||
fileName = "favicon-debug.ico";
|
||||
}
|
||||
|
||||
var path = Path.Combine("Content", "Images", fileName);
|
||||
|
||||
return Path.Combine(_appFolderInfo.StartUpFolder, "UI", path);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
private readonly IAnalyticsService _analyticsService;
|
||||
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
||||
private readonly string _indexPath;
|
||||
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src)=\").*?(css|js|png|ico|ics)(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static String API_KEY;
|
||||
private static String URL_BASE;
|
||||
@@ -49,7 +49,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
return !resourceUrl.Contains(".");
|
||||
return !resourceUrl.Contains(".") && !resourceUrl.StartsWith("/login");
|
||||
}
|
||||
|
||||
public override Response GetResponse(string resourceUrl)
|
||||
|
||||
88
src/NzbDrone.Api/Frontend/Mappers/LoginHtmlMapper.cs
Normal file
88
src/NzbDrone.Api/Frontend/Mappers/LoginHtmlMapper.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Nancy;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Frontend.Mappers
|
||||
{
|
||||
public class LoginHtmlMapper : StaticResourceMapperBase
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
||||
private readonly string _indexPath;
|
||||
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static String URL_BASE;
|
||||
private string _generatedContent;
|
||||
|
||||
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||
IDiskProvider diskProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
Func<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||
Logger logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_cacheBreakProviderFactory = cacheBreakProviderFactory;
|
||||
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "login.html");
|
||||
|
||||
URL_BASE = configFileProvider.UrlBase;
|
||||
}
|
||||
|
||||
public override string Map(string resourceUrl)
|
||||
{
|
||||
return _indexPath;
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
{
|
||||
return resourceUrl.StartsWith("/login");
|
||||
}
|
||||
|
||||
public override Response GetResponse(string resourceUrl)
|
||||
{
|
||||
var response = base.GetResponse(resourceUrl);
|
||||
response.Headers["X-UA-Compatible"] = "IE=edge";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
protected override Stream GetContentStream(string filePath)
|
||||
{
|
||||
var text = GetLoginText();
|
||||
|
||||
var stream = new MemoryStream();
|
||||
var writer = new StreamWriter(stream);
|
||||
writer.Write(text);
|
||||
writer.Flush();
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
|
||||
private string GetLoginText()
|
||||
{
|
||||
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
|
||||
{
|
||||
return _generatedContent;
|
||||
}
|
||||
|
||||
var text = _diskProvider.ReadAllText(_indexPath);
|
||||
|
||||
var cacheBreakProvider = _cacheBreakProviderFactory();
|
||||
|
||||
text = ReplaceRegex.Replace(text, match =>
|
||||
{
|
||||
var url = cacheBreakProvider.AddCacheBreakerToPath(match.Value);
|
||||
return URL_BASE + url;
|
||||
});
|
||||
|
||||
_generatedContent = text;
|
||||
|
||||
return _generatedContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,13 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
private static readonly Regex RegexResizedImage = new Regex(@"-\d+\.jpg($|\?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
|
||||
public MediaCoverMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
|
||||
: base(diskProvider, logger)
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_diskProvider = diskProvider;
|
||||
}
|
||||
|
||||
public override string Map(string resourceUrl)
|
||||
@@ -25,25 +27,18 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||
path = path.Trim(Path.DirectorySeparatorChar);
|
||||
|
||||
return Path.Combine(_appFolderInfo.GetAppDataPath(), path);
|
||||
}
|
||||
var resourcePath = Path.Combine(_appFolderInfo.GetAppDataPath(), path);
|
||||
|
||||
public override Response GetResponse(string resourceUrl)
|
||||
{
|
||||
var result = base.GetResponse(resourceUrl);
|
||||
|
||||
// Return the full sized image if someone requests a non-existing resized one.
|
||||
// TODO: This code can be removed later once everyone had the update for a while.
|
||||
if (result is NotFoundResponse)
|
||||
if (!_diskProvider.FileExists(resourcePath) || _diskProvider.GetFileSize(resourcePath) == 0)
|
||||
{
|
||||
var baseResourceUrl = RegexResizedImage.Replace(resourceUrl, ".jpg$1");
|
||||
if (baseResourceUrl != resourceUrl)
|
||||
var baseResourcePath = RegexResizedImage.Replace(resourcePath, ".jpg$1");
|
||||
if (baseResourcePath != resourcePath)
|
||||
{
|
||||
result = base.GetResponse(baseResourceUrl);
|
||||
return baseResourcePath;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return resourcePath;
|
||||
}
|
||||
|
||||
public override bool CanHandle(string resourceUrl)
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
{
|
||||
return resourceUrl.StartsWith("/Content") ||
|
||||
resourceUrl.EndsWith(".js") ||
|
||||
resourceUrl.EndsWith(".map") ||
|
||||
resourceUrl.EndsWith(".css") ||
|
||||
(resourceUrl.EndsWith(".ico") && !resourceUrl.Equals("/favicon.ico")) ||
|
||||
resourceUrl.EndsWith(".swf");
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace NzbDrone.Api.History
|
||||
public Boolean QualityCutoffNotMet { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public string NzbInfoUrl { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.Indexers
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Validate(IndexerDefinition definition)
|
||||
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
|
||||
{
|
||||
if (!definition.Enable) return;
|
||||
base.Validate(definition);
|
||||
base.Validate(definition, includeWarnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,10 +43,12 @@ namespace NzbDrone.Api.Indexers
|
||||
_prioritizeDownloadDecision = prioritizeDownloadDecision;
|
||||
_downloadService = downloadService;
|
||||
_logger = logger;
|
||||
|
||||
GetResourceAll = GetReleases;
|
||||
Post["/"] = x => DownloadRelease(this.Bind<ReleaseResource>());
|
||||
|
||||
PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
|
||||
PostValidator.RuleFor(s => s.Guid).NotEmpty();
|
||||
|
||||
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
|
||||
}
|
||||
|
||||
40
src/NzbDrone.Api/ManualImport/ManualImportModule.cs
Normal file
40
src/NzbDrone.Api/ManualImport/ManualImportModule.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Api.ManualImport
|
||||
{
|
||||
public class ManualImportModule : NzbDroneRestModule<ManualImportResource>
|
||||
{
|
||||
private readonly IManualImportService _manualImportService;
|
||||
|
||||
public ManualImportModule(IManualImportService manualImportService)
|
||||
: base("/manualimport")
|
||||
{
|
||||
_manualImportService = manualImportService;
|
||||
|
||||
GetResourceAll = GetMediaFiles;
|
||||
}
|
||||
|
||||
private List<ManualImportResource> GetMediaFiles()
|
||||
{
|
||||
var folderQuery = Request.Query.folder;
|
||||
var folder = (string)folderQuery.Value;
|
||||
|
||||
var downloadIdQuery = Request.Query.downloadId;
|
||||
var downloadId = (string)downloadIdQuery.Value;
|
||||
|
||||
return ToListResource(_manualImportService.GetMediaFiles(folder, downloadId)).Select(AddQualityWeight).ToList();
|
||||
}
|
||||
|
||||
private ManualImportResource AddQualityWeight(ManualImportResource item)
|
||||
{
|
||||
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
|
||||
item.QualityWeight += item.Quality.Revision.Real * 10;
|
||||
item.QualityWeight += item.Quality.Revision.Version;
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/NzbDrone.Api/ManualImport/ManualImportResource.cs
Normal file
32
src/NzbDrone.Api/ManualImport/ManualImportResource.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Api.ManualImport
|
||||
{
|
||||
public class ManualImportResource : RestResource
|
||||
{
|
||||
public string Path { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Name { get; set; }
|
||||
public long Size { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
public int? SeasonNumber { get; set; }
|
||||
public List<EpisodeResource> Episodes { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int QualityWeight { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
public IEnumerable<Rejection> Rejections { get; set; }
|
||||
|
||||
public int Id
|
||||
{
|
||||
get
|
||||
{
|
||||
return Path.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Api.MediaCovers
|
||||
{
|
||||
var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", seriesId.ToString(), filename);
|
||||
|
||||
if (!_diskProvider.FileExists(filePath))
|
||||
if (!_diskProvider.FileExists(filePath) || _diskProvider.GetFileSize(filePath) == 0)
|
||||
{
|
||||
// Return the full sized image if someone requests a non-existing resized one.
|
||||
// TODO: This code can be removed later once everyone had the update for a while.
|
||||
|
||||
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.Metadata
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Validate(MetadataDefinition definition)
|
||||
protected override void Validate(MetadataDefinition definition, bool includeWarnings)
|
||||
{
|
||||
if (!definition.Enable) return;
|
||||
base.Validate(definition);
|
||||
base.Validate(definition, includeWarnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.Notifications
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Validate(NotificationDefinition definition)
|
||||
protected override void Validate(NotificationDefinition definition, bool includeWarnings)
|
||||
{
|
||||
if (!definition.OnGrab && !definition.OnDownload) return;
|
||||
base.Validate(definition);
|
||||
base.Validate(definition, includeWarnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ namespace NzbDrone.Api.Notifications
|
||||
{
|
||||
public class NotificationResource : ProviderResource
|
||||
{
|
||||
public String Link { get; set; }
|
||||
public Boolean OnGrab { get; set; }
|
||||
public Boolean OnDownload { get; set; }
|
||||
public Boolean OnUpgrade { get; set; }
|
||||
public String TestCommand { get; set; }
|
||||
public HashSet<Int32> Tags { get; set; }
|
||||
public string Link { get; set; }
|
||||
public bool OnGrab { get; set; }
|
||||
public bool OnDownload { get; set; }
|
||||
public bool OnUpgrade { get; set; }
|
||||
public string TestCommand { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,9 @@
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Nancy.Authentication.Basic.0.23.2\lib\net40\Nancy.Authentication.Basic.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy.Authentication.Forms">
|
||||
<HintPath>..\packages\Nancy.Authentication.Forms.0.23.2\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
@@ -80,6 +83,8 @@
|
||||
</Compile>
|
||||
<Compile Include="Authentication\AuthenticationService.cs" />
|
||||
<Compile Include="Authentication\EnableAuthInNancy.cs" />
|
||||
<Compile Include="Authentication\AuthenticationModule.cs" />
|
||||
<Compile Include="Authentication\LoginResource.cs" />
|
||||
<Compile Include="Authentication\NzbDroneUser.cs" />
|
||||
<Compile Include="Blacklist\BlacklistModule.cs" />
|
||||
<Compile Include="Blacklist\BlacklistResource.cs" />
|
||||
@@ -94,6 +99,11 @@
|
||||
<Compile Include="Commands\CommandResource.cs" />
|
||||
<Compile Include="Extensions\AccessControlHeaders.cs" />
|
||||
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
|
||||
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
|
||||
<Compile Include="Parse\ParseModule.cs" />
|
||||
<Compile Include="Parse\ParseResource.cs" />
|
||||
<Compile Include="ManualImport\ManualImportModule.cs" />
|
||||
<Compile Include="ManualImport\ManualImportResource.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
|
||||
@@ -254,7 +264,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
|
||||
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
@@ -262,4 +272,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
49
src/NzbDrone.Api/Parse/ParseModule.cs
Normal file
49
src/NzbDrone.Api/Parse/ParseModule.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Api.Parse
|
||||
{
|
||||
public class ParseModule : NzbDroneRestModule<ParseResource>
|
||||
{
|
||||
private readonly IParsingService _parsingService;
|
||||
|
||||
public ParseModule(IParsingService parsingService)
|
||||
{
|
||||
_parsingService = parsingService;
|
||||
|
||||
GetResourceSingle = Parse;
|
||||
}
|
||||
|
||||
private ParseResource Parse()
|
||||
{
|
||||
var title = Request.Query.Title.Value;
|
||||
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
||||
|
||||
if (parsedEpisodeInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo);
|
||||
|
||||
if (remoteEpisode == null)
|
||||
{
|
||||
remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo
|
||||
};
|
||||
|
||||
return new ParseResource
|
||||
{
|
||||
Title = title,
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo
|
||||
};
|
||||
}
|
||||
|
||||
var resource = ToResource(remoteEpisode);
|
||||
resource.Title = title;
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/NzbDrone.Api/Parse/ParseResource.cs
Normal file
17
src/NzbDrone.Api/Parse/ParseResource.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Api.Parse
|
||||
{
|
||||
public class ParseResource : RestResource
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
public List<EpisodeResource> Episodes { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Nancy;
|
||||
using NzbDrone.Api.ClientSchema;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Api.Mapping;
|
||||
using NzbDrone.Common.Reflection;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
using Omu.ValueInjecter;
|
||||
|
||||
namespace NzbDrone.Api
|
||||
@@ -53,9 +55,9 @@ namespace NzbDrone.Api
|
||||
|
||||
private List<TProviderResource> GetAll()
|
||||
{
|
||||
var providerDefinitions = _providerFactory.All();
|
||||
var providerDefinitions = _providerFactory.All().OrderBy(p => p.ImplementationName);
|
||||
|
||||
var result = new List<TProviderResource>(providerDefinitions.Count);
|
||||
var result = new List<TProviderResource>(providerDefinitions.Count());
|
||||
|
||||
foreach (var definition in providerDefinitions)
|
||||
{
|
||||
@@ -67,16 +69,16 @@ namespace NzbDrone.Api
|
||||
result.Add(providerResource);
|
||||
}
|
||||
|
||||
return result;
|
||||
return result.OrderBy(p => p.Name).ToList();
|
||||
}
|
||||
|
||||
private int CreateProvider(TProviderResource providerResource)
|
||||
{
|
||||
var providerDefinition = GetDefinition(providerResource);
|
||||
var providerDefinition = GetDefinition(providerResource, false);
|
||||
|
||||
if (providerDefinition.Enable)
|
||||
{
|
||||
Test(providerDefinition);
|
||||
Test(providerDefinition, false);
|
||||
}
|
||||
|
||||
providerDefinition = _providerFactory.Create(providerDefinition);
|
||||
@@ -86,12 +88,17 @@ namespace NzbDrone.Api
|
||||
|
||||
private void UpdateProvider(TProviderResource providerResource)
|
||||
{
|
||||
var providerDefinition = GetDefinition(providerResource);
|
||||
var providerDefinition = GetDefinition(providerResource, false);
|
||||
|
||||
if (providerDefinition.Enable)
|
||||
{
|
||||
Test(providerDefinition, false);
|
||||
}
|
||||
|
||||
_providerFactory.Update(providerDefinition);
|
||||
}
|
||||
|
||||
private TProviderDefinition GetDefinition(TProviderResource providerResource)
|
||||
private TProviderDefinition GetDefinition(TProviderResource providerResource, bool includeWarnings = false)
|
||||
{
|
||||
var definition = new TProviderDefinition();
|
||||
|
||||
@@ -105,7 +112,7 @@ namespace NzbDrone.Api
|
||||
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
|
||||
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(providerResource.Fields, configContract, preset);
|
||||
|
||||
Validate(definition);
|
||||
Validate(definition, includeWarnings);
|
||||
|
||||
return definition;
|
||||
}
|
||||
@@ -117,7 +124,7 @@ namespace NzbDrone.Api
|
||||
|
||||
private Response GetTemplates()
|
||||
{
|
||||
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().ToList();
|
||||
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().OrderBy(p => p.ImplementationName).ToList();
|
||||
|
||||
var result = new List<TProviderResource>(defaultDefinitions.Count());
|
||||
|
||||
@@ -149,31 +156,40 @@ namespace NzbDrone.Api
|
||||
|
||||
private Response Test(TProviderResource providerResource)
|
||||
{
|
||||
var providerDefinition = GetDefinition(providerResource);
|
||||
var providerDefinition = GetDefinition(providerResource, true);
|
||||
|
||||
Test(providerDefinition);
|
||||
Test(providerDefinition, true);
|
||||
|
||||
return "{}";
|
||||
}
|
||||
|
||||
private void Test(TProviderDefinition providerDefinition)
|
||||
protected virtual void Validate(TProviderDefinition definition, bool includeWarnings)
|
||||
{
|
||||
var result = _providerFactory.Test(providerDefinition);
|
||||
var validationResult = definition.Settings.Validate();
|
||||
|
||||
VerifyValidationResult(validationResult, includeWarnings);
|
||||
}
|
||||
|
||||
protected virtual void Test(TProviderDefinition definition, bool includeWarnings)
|
||||
{
|
||||
var validationResult = _providerFactory.Test(definition);
|
||||
|
||||
VerifyValidationResult(validationResult, includeWarnings);
|
||||
}
|
||||
|
||||
protected void VerifyValidationResult(ValidationResult validationResult, bool includeWarnings)
|
||||
{
|
||||
var result = new NzbDroneValidationResult(validationResult.Errors);
|
||||
|
||||
if (includeWarnings && (!result.IsValid || result.HasWarnings))
|
||||
{
|
||||
throw new ValidationException(result.Failures);
|
||||
}
|
||||
|
||||
if (!result.IsValid)
|
||||
{
|
||||
throw new ValidationException(result.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Validate(TProviderDefinition definition)
|
||||
{
|
||||
var validationResult = definition.Settings.Validate();
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Api.ClientSchema;
|
||||
using NzbDrone.Api.REST;
|
||||
|
||||
@@ -7,11 +6,12 @@ namespace NzbDrone.Api
|
||||
{
|
||||
public class ProviderResource : RestResource
|
||||
{
|
||||
public String Name { get; set; }
|
||||
public string Name { get; set; }
|
||||
public List<Field> Fields { get; set; }
|
||||
public String Implementation { get; set; }
|
||||
public String ConfigContract { get; set; }
|
||||
public String InfoLink { get; set; }
|
||||
public string ImplementationName { get; set; }
|
||||
public string Implementation { get; set; }
|
||||
public string ConfigContract { get; set; }
|
||||
public string InfoLink { get; set; }
|
||||
|
||||
public List<ProviderResource> Presets { get; set; }
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace NzbDrone.Api.RemotePathMappings
|
||||
{
|
||||
private readonly IRemotePathMappingService _remotePathMappingService;
|
||||
|
||||
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService, PathExistsValidator pathExistsValidator)
|
||||
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService,
|
||||
PathExistsValidator pathExistsValidator,
|
||||
MappedNetworkDriveValidator mappedNetworkDriveValidator)
|
||||
{
|
||||
_remotePathMappingService = remotePathMappingService;
|
||||
|
||||
@@ -31,6 +33,7 @@ namespace NzbDrone.Api.RemotePathMappings
|
||||
SharedValidator.RuleFor(c => c.LocalPath)
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(mappedNetworkDriveValidator)
|
||||
.SetValidator(pathExistsValidator);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace NzbDrone.Api.RootFolders
|
||||
IBroadcastSignalRMessage signalRBroadcaster,
|
||||
RootFolderValidator rootFolderValidator,
|
||||
PathExistsValidator pathExistsValidator,
|
||||
DroneFactoryValidator droneFactoryValidator)
|
||||
DroneFactoryValidator droneFactoryValidator,
|
||||
MappedNetworkDriveValidator mappedNetworkDriveValidator)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_rootFolderService = rootFolderService;
|
||||
@@ -29,8 +30,9 @@ namespace NzbDrone.Api.RootFolders
|
||||
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||
.IsValidPath()
|
||||
.SetValidator(rootFolderValidator)
|
||||
.SetValidator(pathExistsValidator)
|
||||
.SetValidator(droneFactoryValidator);
|
||||
.SetValidator(droneFactoryValidator)
|
||||
.SetValidator(mappedNetworkDriveValidator)
|
||||
.SetValidator(pathExistsValidator);
|
||||
}
|
||||
|
||||
private RootFolderResource GetRootFolder(int id)
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace NzbDrone.Api.Series
|
||||
private readonly ISearchForNewSeries _searchProxy;
|
||||
|
||||
public SeriesLookupModule(ISearchForNewSeries searchProxy)
|
||||
: base("/Series/lookup")
|
||||
: base("/series/lookup")
|
||||
{
|
||||
_searchProxy = searchProxy;
|
||||
Get["/"] = x => Search();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user