Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9d9a6c441 | ||
|
|
8838ee481e | ||
|
|
0fd4c9c998 | ||
|
|
b857108db4 | ||
|
|
16ec910ba5 | ||
|
|
5e98d96f65 | ||
|
|
7e2b9a30a2 | ||
|
|
4d683922c1 | ||
|
|
9d594988d9 | ||
|
|
c441e888c2 | ||
|
|
298daf9895 | ||
|
|
d04dff9130 | ||
|
|
5eef5a594c | ||
|
|
cce98709a7 | ||
|
|
3fef151fca | ||
|
|
a6fe517f87 | ||
|
|
964c5008ca | ||
|
|
f250ae58a4 | ||
|
|
7af194822c | ||
|
|
c4b1f3f4fe | ||
|
|
f4b13533ce | ||
|
|
638baf564d | ||
|
|
21b3d99249 | ||
|
|
d8dda5b24f | ||
|
|
25fb4361dc | ||
|
|
8ec1808313 | ||
|
|
43efca9d04 | ||
|
|
34b9480a1b | ||
|
|
51dfeb8190 | ||
|
|
3a03d940a1 | ||
|
|
69268b7d21 | ||
|
|
e69cf3942e | ||
|
|
2e6c8e6537 | ||
|
|
ca084f1216 | ||
|
|
a1bc3617a4 | ||
|
|
a1e2042315 | ||
|
|
3bb173c979 | ||
|
|
70135a5bf4 | ||
|
|
efa9ac77b8 | ||
|
|
e8b330f3ba | ||
|
|
56adf4a1ee | ||
|
|
9f92b3b774 | ||
|
|
9d774b4486 | ||
|
|
163e1e3f2e | ||
|
|
adaaf51e9a | ||
|
|
2ba5978f84 | ||
|
|
46240fb7d1 | ||
|
|
a4fa8ca79d | ||
|
|
701f709c23 | ||
|
|
3d22a66701 | ||
|
|
a401ff4fb4 | ||
|
|
72419227e9 | ||
|
|
22bff59789 | ||
|
|
b3746dc58e | ||
|
|
b950bcbd0b | ||
|
|
aa6206a235 | ||
|
|
8cf23b79bf | ||
|
|
bb884eeab7 | ||
|
|
72d2201dd6 | ||
|
|
1b65df6c45 | ||
|
|
a7d1f128ad | ||
|
|
17ef29887e | ||
|
|
06822e2514 | ||
|
|
fc1c26f58f | ||
|
|
ef3cd00ef1 | ||
|
|
08f483d77d | ||
|
|
405ebe5ee2 | ||
|
|
59b4cefd53 | ||
|
|
d037147c23 | ||
|
|
34097e2d81 | ||
|
|
e0f51f756c | ||
|
|
8c9b834bc4 | ||
|
|
9178740c26 | ||
|
|
922cca9652 | ||
|
|
e2f0354deb | ||
|
|
6891252cf6 | ||
|
|
c9debdcce5 | ||
|
|
6911dfde02 | ||
|
|
e7ddb2d7b2 | ||
|
|
51fde7a52b | ||
|
|
b36178d6d4 | ||
|
|
c19dffcb46 | ||
|
|
e04245cff5 | ||
|
|
d5b08a8f51 | ||
|
|
49179170fa | ||
|
|
e224ad1a6a | ||
|
|
ba552bd1f8 | ||
|
|
ad1de823bd | ||
|
|
f2f8ad68a3 | ||
|
|
23e3dd91b3 | ||
|
|
b4df21776e | ||
|
|
741538fa3c | ||
|
|
270d32b45e | ||
|
|
cedf54865d | ||
|
|
c0199cf356 | ||
|
|
c95bb9d29c | ||
|
|
29ae400b80 | ||
|
|
0cd4d540aa | ||
|
|
6ce710dbb1 | ||
|
|
90744c3b80 | ||
|
|
d777cf74a5 | ||
|
|
9b1d6eb6e0 | ||
|
|
676fa7199b | ||
|
|
fd30b3501e | ||
|
|
2a601d2731 | ||
|
|
296c9ca767 | ||
|
|
245f1fe366 | ||
|
|
ce3b8b7894 | ||
|
|
0c61eb11f5 | ||
|
|
9ec4866cea | ||
|
|
d48dbc6bbb | ||
|
|
5cb9d78e35 | ||
|
|
78c866ce59 | ||
|
|
ab6fcc92e5 | ||
|
|
ba640e2659 | ||
|
|
9849a8334f | ||
|
|
ad645d960d | ||
|
|
98604a2ab8 | ||
|
|
bd8da45c1a | ||
|
|
b46787ae93 | ||
|
|
48b2b9235d | ||
|
|
efd2e44ded | ||
|
|
93e4e47cd6 | ||
|
|
9ec65c5f9a | ||
|
|
1597d028f0 | ||
|
|
2178397c2f | ||
|
|
568f8327ef | ||
|
|
7e2b7b7376 | ||
|
|
253e578a98 | ||
|
|
d590d037ad | ||
|
|
64298127c5 | ||
|
|
9122136594 | ||
|
|
1ee76f7488 | ||
|
|
91bfdcd4bd | ||
|
|
2b02ae4196 | ||
|
|
326f866d7e | ||
|
|
86b1d6f443 | ||
|
|
62e35e9a42 | ||
|
|
dc02fb4a06 | ||
|
|
c639d774cf | ||
|
|
58bc146d11 | ||
|
|
429e2fcc40 | ||
|
|
7d8140316c | ||
|
|
3dfb8e0b0b | ||
|
|
9aeea8bcad | ||
|
|
850ce9ac3c | ||
|
|
61b901a481 | ||
|
|
08a2c2d148 | ||
|
|
d87bb1f11c | ||
|
|
bf4d642125 | ||
|
|
7163886d31 | ||
|
|
a3930e1c05 | ||
|
|
32a037b1c6 | ||
|
|
c170b2a06d | ||
|
|
0807c32a1c | ||
|
|
c447212562 | ||
|
|
54bbde5c1d | ||
|
|
7009984357 | ||
|
|
7229b73862 | ||
|
|
66b9da36dd | ||
|
|
9d9c5f53e7 | ||
|
|
9a7300cda1 | ||
|
|
e618103f38 | ||
|
|
114b6b8a41 | ||
|
|
5064beffe9 | ||
|
|
19c4e7d7d3 | ||
|
|
8b799c7904 | ||
|
|
a6c5894272 | ||
|
|
0fdc62c914 | ||
|
|
88af84579c | ||
|
|
917dc7695f | ||
|
|
65e3e4faa2 | ||
|
|
9e7ec3a65c | ||
|
|
7ac7b127c6 | ||
|
|
3945753c5c | ||
|
|
599585d57a | ||
|
|
10cf36401f | ||
|
|
fe2834f4a6 | ||
|
|
674617c0f8 | ||
|
|
f92f8928b0 | ||
|
|
45868bc523 | ||
|
|
9b2594355f | ||
|
|
e30cb26200 | ||
|
|
63d3ab1cc1 | ||
|
|
188d907e10 | ||
|
|
e972a86233 | ||
|
|
52a76e87ad | ||
|
|
8ab19f8121 | ||
|
|
6f3c8c68d4 | ||
|
|
98385ff4c8 | ||
|
|
62a6be26af | ||
|
|
5c1169ba9d | ||
|
|
0b47a5ca3a | ||
|
|
9da75a777b | ||
|
|
2fc2190b59 | ||
|
|
e108935430 | ||
|
|
bbde74db8d | ||
|
|
9fce4edf00 | ||
|
|
ea3c6c3278 | ||
|
|
6a0bd397cd | ||
|
|
7bbb137fd9 | ||
|
|
e67d32445f | ||
|
|
83280f3f99 | ||
|
|
9c81fbaa2b | ||
|
|
1d7415b91d | ||
|
|
bc5a042dcc | ||
|
|
e5af0bdd52 | ||
|
|
dc2939a4f9 | ||
|
|
f52ec56a0f | ||
|
|
75b38df3d6 | ||
|
|
0f5aeea345 | ||
|
|
d80fc36b29 | ||
|
|
4ca42c9c11 | ||
|
|
6c760902d6 | ||
|
|
2a0fcc7566 | ||
|
|
b763ccdd08 | ||
|
|
e72a0d1141 | ||
|
|
295e5a1ccf | ||
|
|
497fafb555 | ||
|
|
585f556006 | ||
|
|
7b115126c9 | ||
|
|
1a9165a084 | ||
|
|
4a74d54cdc | ||
|
|
a0755d4b1c | ||
|
|
084181a9a5 | ||
|
|
5c381b9410 | ||
|
|
0be9500518 | ||
|
|
9c2da72dcc | ||
|
|
3f6145cd07 | ||
|
|
e155f19d0d | ||
|
|
0c7ea8a5a0 | ||
|
|
1f3474a154 | ||
|
|
c0c1f9cc61 | ||
|
|
e4382c23a3 | ||
|
|
29b5305ab4 | ||
|
|
758154e87e | ||
|
|
ea8b1815f6 | ||
|
|
ebc0b7205f | ||
|
|
06ee770184 | ||
|
|
900325186b | ||
|
|
3b9816f3b3 | ||
|
|
3e5779e5ce | ||
|
|
66d0cb4a1b | ||
|
|
54c85c7951 | ||
|
|
941da4be1c | ||
|
|
69c94baf18 | ||
|
|
90016f6eda | ||
|
|
06b647e30d | ||
|
|
84ebcb84f9 | ||
|
|
b11610face | ||
|
|
6e90419014 | ||
|
|
0457c3c30e | ||
|
|
945cfbf21b | ||
|
|
5769cb0dee | ||
|
|
efd043e309 | ||
|
|
fd19563160 | ||
|
|
1a2ed24566 | ||
|
|
d2fecc47ec | ||
|
|
f9b0a5efb9 | ||
|
|
e304e34ac7 | ||
|
|
ff3826c0c8 | ||
|
|
c96b043a19 | ||
|
|
a1d0f14eb8 | ||
|
|
0234f4dab9 | ||
|
|
e00411f7ad | ||
|
|
3b2350cd8f | ||
|
|
d490091a9a | ||
|
|
5be36b517f | ||
|
|
dab323e8ff | ||
|
|
7e02f86af8 | ||
|
|
26ed43fca8 | ||
|
|
dab4e3d5f8 | ||
|
|
07e8b6b095 | ||
|
|
52523a5d3c | ||
|
|
ded4b7ef6f | ||
|
|
aa9d669a2b | ||
|
|
5378884bf8 | ||
|
|
631d947a9a | ||
|
|
7280807588 | ||
|
|
ca010ac522 | ||
|
|
e2e1ef9bc6 | ||
|
|
a96b793c98 | ||
|
|
f7ba0ff634 | ||
|
|
a0339d9814 | ||
|
|
eb7a1d7117 | ||
|
|
65fa0e21fa | ||
|
|
60ea86d499 | ||
|
|
0411bb6b44 | ||
|
|
d0198b15ee | ||
|
|
c4e8678d8f | ||
|
|
d6cd9cd7e4 | ||
|
|
6c4da76056 | ||
|
|
bf918e6e56 | ||
|
|
0616aae684 | ||
|
|
2d137ddcd6 | ||
|
|
94a2be275b | ||
|
|
aba4bae39e | ||
|
|
541fe5a610 | ||
|
|
f7df469cc3 | ||
|
|
648fe27604 | ||
|
|
a6532d149c | ||
|
|
966c097f76 | ||
|
|
d3d5b9dda2 | ||
|
|
a693777433 | ||
|
|
b406c3cb68 | ||
|
|
432354dcd1 | ||
|
|
496e9a0fd5 | ||
|
|
d3727d1fef | ||
|
|
a37ce75038 | ||
|
|
69d1807e9a | ||
|
|
0c32fbe706 | ||
|
|
a1ed6901d4 | ||
|
|
1f4ca0ac48 | ||
|
|
ff16c84b8b | ||
|
|
d1d45c21d4 | ||
|
|
9c14a2699e | ||
|
|
66becf2642 | ||
|
|
7240db2567 | ||
|
|
6118fea823 | ||
|
|
49e0571cb9 | ||
|
|
de78d1e737 | ||
|
|
93d4df29a6 | ||
|
|
ae7dc456ca | ||
|
|
ea1e01d417 | ||
|
|
00c545b37e | ||
|
|
f51962f645 | ||
|
|
8e5b96059c | ||
|
|
aab4461ab6 | ||
|
|
001404d5c4 | ||
|
|
9416d185e7 | ||
|
|
a43df2b275 | ||
|
|
856bed9337 | ||
|
|
3a00c7a79a | ||
|
|
fa202e3eb3 | ||
|
|
d6e3ebf41a | ||
|
|
81f9614595 | ||
|
|
a464856216 | ||
|
|
150a49fb51 | ||
|
|
8cdc48f33f | ||
|
|
0325660abe | ||
|
|
b52acfdcd0 | ||
|
|
d34455af3f | ||
|
|
06f2f1c661 | ||
|
|
7c87c534f9 | ||
|
|
22151ff4b0 | ||
|
|
a9a2fb78ed | ||
|
|
21e1c100e7 | ||
|
|
8d267febcb | ||
|
|
cd3cc7c811 | ||
|
|
686ea47449 | ||
|
|
41cb84957e | ||
|
|
1de8ace080 | ||
|
|
7d3401ff9f | ||
|
|
a7d517fb09 | ||
|
|
3dfedc305a | ||
|
|
210c23dd5d | ||
|
|
9544b0e616 | ||
|
|
55796324b6 | ||
|
|
a717594950 | ||
|
|
084bf59b6a | ||
|
|
51c32e09bd | ||
|
|
cbb15a13dd | ||
|
|
f757ce0bde | ||
|
|
667f768749 | ||
|
|
ea6e3335a3 | ||
|
|
a59ed331a8 | ||
|
|
d3c48970ae | ||
|
|
65bd0ad229 | ||
|
|
1580f5c22e | ||
|
|
da05a44187 | ||
|
|
0f619a2d43 | ||
|
|
769982cc43 | ||
|
|
30ba21c616 | ||
|
|
91b596fb0f | ||
|
|
d79dc8f1ab | ||
|
|
ed63a08333 | ||
|
|
e136a6a4e4 | ||
|
|
b02889a451 | ||
|
|
1683e4da76 | ||
|
|
d7bc0625e3 | ||
|
|
20863084da | ||
|
|
75dc68f176 | ||
|
|
6ae655ad7a | ||
|
|
eb3a0ac217 | ||
|
|
42207516b9 | ||
|
|
f90a87f943 | ||
|
|
a5480dd124 | ||
|
|
1ac7ba0892 | ||
|
|
e409859c35 | ||
|
|
2add9fa264 | ||
|
|
f3d192c548 | ||
|
|
e4032106d6 | ||
|
|
300b47d6f8 | ||
|
|
89aff6e199 | ||
|
|
bfbfb4262b | ||
|
|
79c293f32b | ||
|
|
a364c435e4 | ||
|
|
799aa5c26a | ||
|
|
d7655ec95a | ||
|
|
7761015b15 | ||
|
|
6279a95b6c | ||
|
|
1cff59fdaf | ||
|
|
864ea22a70 | ||
|
|
acad25c534 | ||
|
|
269f346e1c | ||
|
|
f03a2518ba | ||
|
|
cc28e7b4c3 | ||
|
|
41119b1d77 | ||
|
|
5da4cb7d12 | ||
|
|
933dbd205c | ||
|
|
431e24ff37 | ||
|
|
d12b6cabc9 | ||
|
|
8ccb7f2f16 | ||
|
|
dd9c21fe3f | ||
|
|
a48babfa09 | ||
|
|
e13cc15edf | ||
|
|
073f1dad3f | ||
|
|
e721f3d302 | ||
|
|
b18487f512 | ||
|
|
c1b0dc4225 | ||
|
|
31ed56bcc9 | ||
|
|
0c714a78de | ||
|
|
708f5fcd70 | ||
|
|
feb86435d8 | ||
|
|
11b5c9ced1 | ||
|
|
ad1c4851b1 | ||
|
|
5a1a6d1b57 | ||
|
|
341e2c5edb | ||
|
|
4f5a2cd3d0 | ||
|
|
2ba476c6ef | ||
|
|
9872e9a1a3 | ||
|
|
c26e867859 | ||
|
|
4607c2f28b | ||
|
|
17f1bc5344 | ||
|
|
7bb7cf9551 | ||
|
|
b51a2eb4e3 | ||
|
|
1c3132b3aa | ||
|
|
88a2d364f6 | ||
|
|
3dc515507c | ||
|
|
a1fe8f2519 | ||
|
|
f0b4f2f3c8 | ||
|
|
d2e2008f1b | ||
|
|
2f10b9bbe8 | ||
|
|
d0ae36ac56 | ||
|
|
729a105377 | ||
|
|
0bdc707fdf | ||
|
|
ca8ab32eaf | ||
|
|
6e2338184e | ||
|
|
dbe644aee2 | ||
|
|
e293302bfe | ||
|
|
7c068ca4c5 |
@@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
||||||
@@ -28,8 +28,12 @@ ehthumbs.db
|
|||||||
Icon?
|
Icon?
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
#ignore thumbnails created by windows
|
#Ignore files generated by PyCharm
|
||||||
Thumbs.db
|
.idea/*
|
||||||
|
|
||||||
|
#Ignore files generated by vi
|
||||||
|
*.swp
|
||||||
|
|
||||||
#Ignore files build by Visual Studio
|
#Ignore files build by Visual Studio
|
||||||
*.obj
|
*.obj
|
||||||
*.exe
|
*.exe
|
||||||
|
|||||||
@@ -65,3 +65,7 @@ getAlbumInfo&id=$albumid (See above, returns Summary and Content)
|
|||||||
|
|
||||||
getArtistThumb&id=$artistid (Returns either a relative path to the cached thumbnail artist image, or an http:// address if the cache dir can't be written to)
|
getArtistThumb&id=$artistid (Returns either a relative path to the cached thumbnail artist image, or an http:// address if the cache dir can't be written to)
|
||||||
getAlbumThumb&id=$albumid (see above)
|
getAlbumThumb&id=$albumid (see above)
|
||||||
|
|
||||||
|
choose_specific_download&id=$albumid (Gives you a list of results from searcher.searchforalbum(). Basically runs a normal search, but rather than sorting them and downloading the best result, it dumps the data, which you can then pass on to download_specific_release(). Returns a list of dictionaries with params: title, size, url, provider & kind - all of these values must be passed back to download_specific_release)
|
||||||
|
|
||||||
|
download_specific_release&id=albumid&title=$title&size=$size&url=$url&provider=$provider&kind=$kind (Allows you to manually pass a choose_specific_download release back to searcher.send_to_downloader())
|
||||||
@@ -14,26 +14,30 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os, sys, locale
|
import os, sys
|
||||||
import time
|
|
||||||
import signal
|
|
||||||
|
|
||||||
from lib.configobj import ConfigObj
|
# Ensure lib added to path, before any other imports
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||||
import headphones
|
|
||||||
|
|
||||||
from headphones import webstart, logger
|
from headphones import webstart, logger
|
||||||
|
|
||||||
try:
|
from configobj import ConfigObj
|
||||||
import argparse
|
|
||||||
except ImportError:
|
|
||||||
import lib.argparse as argparse
|
|
||||||
|
|
||||||
|
import locale
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import argparse
|
||||||
|
import headphones
|
||||||
|
|
||||||
|
# Register signals, such as CTRL + C
|
||||||
signal.signal(signal.SIGINT, headphones.sig_handler)
|
signal.signal(signal.SIGINT, headphones.sig_handler)
|
||||||
signal.signal(signal.SIGTERM, headphones.sig_handler)
|
signal.signal(signal.SIGTERM, headphones.sig_handler)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
"""
|
||||||
|
Headphones application entry point. Parses arguments, setups encoding and
|
||||||
|
initializes the application.
|
||||||
|
"""
|
||||||
|
|
||||||
# Fixed paths to Headphones
|
# Fixed paths to Headphones
|
||||||
if hasattr(sys, 'frozen'):
|
if hasattr(sys, 'frozen'):
|
||||||
@@ -59,7 +63,7 @@ def main():
|
|||||||
headphones.SYS_ENCODING = 'UTF-8'
|
headphones.SYS_ENCODING = 'UTF-8'
|
||||||
|
|
||||||
# Set up and gather command line arguments
|
# Set up and gather command line arguments
|
||||||
parser = argparse.ArgumentParser(description='Music add-on for SABnzbd+')
|
parser = argparse.ArgumentParser(description='Music add-on for SABnzbd+, Transmission and more.')
|
||||||
|
|
||||||
parser.add_argument('-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
parser.add_argument('-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
||||||
parser.add_argument('-q', '--quiet', action='store_true', help='Turn off console logging')
|
parser.add_argument('-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||||
@@ -73,16 +77,16 @@ def main():
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
headphones.VERBOSE = 2
|
headphones.VERBOSE = True
|
||||||
elif args.quiet:
|
if args.quiet:
|
||||||
headphones.VERBOSE = 0
|
headphones.QUIET = True
|
||||||
|
|
||||||
if args.daemon:
|
if args.daemon:
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
print "Daemonize not supported under Windows, starting normally"
|
sys.stderr.write("Daemonizing not supported under Windows, starting normally\n")
|
||||||
else:
|
else:
|
||||||
headphones.DAEMON=True
|
headphones.DAEMON = True
|
||||||
headphones.VERBOSE = False
|
headphones.QUIET = True
|
||||||
|
|
||||||
if args.pidfile:
|
if args.pidfile:
|
||||||
headphones.PIDFILE = str(args.pidfile)
|
headphones.PIDFILE = str(args.pidfile)
|
||||||
@@ -94,13 +98,16 @@ def main():
|
|||||||
# The pidfile is only useful in daemon mode, make sure we can write the file properly
|
# The pidfile is only useful in daemon mode, make sure we can write the file properly
|
||||||
if headphones.DAEMON:
|
if headphones.DAEMON:
|
||||||
headphones.CREATEPID = True
|
headphones.CREATEPID = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file(headphones.PIDFILE, 'w').write("pid\n")
|
with open(headphones.PIDFILE, 'w') as fp:
|
||||||
except IOError, e:
|
fp.write("pid\n")
|
||||||
raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno))
|
except IOError as e:
|
||||||
|
raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno)
|
||||||
else:
|
else:
|
||||||
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
||||||
|
|
||||||
|
# Determine which data directory and config file to use
|
||||||
if args.datadir:
|
if args.datadir:
|
||||||
headphones.DATA_DIR = args.datadir
|
headphones.DATA_DIR = args.datadir
|
||||||
else:
|
else:
|
||||||
@@ -124,47 +131,45 @@ def main():
|
|||||||
|
|
||||||
# Put the database in the DATA_DIR
|
# Put the database in the DATA_DIR
|
||||||
headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db')
|
headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db')
|
||||||
|
|
||||||
headphones.CFG = ConfigObj(headphones.CONFIG_FILE, encoding='utf-8')
|
headphones.CFG = ConfigObj(headphones.CONFIG_FILE, encoding='utf-8')
|
||||||
|
|
||||||
# Read config & start logging
|
# Read config and start logging
|
||||||
headphones.initialize()
|
headphones.initialize()
|
||||||
|
|
||||||
if headphones.DAEMON:
|
if headphones.DAEMON:
|
||||||
if sys.platform == "win32":
|
headphones.daemonize()
|
||||||
print "Daemonize not supported under Windows, starting normally"
|
|
||||||
else:
|
|
||||||
headphones.daemonize()
|
|
||||||
|
|
||||||
#configure the connection to the musicbrainz database
|
# Configure the connection to the musicbrainz database
|
||||||
headphones.mb.startmb()
|
headphones.mb.startmb()
|
||||||
|
|
||||||
# Force the http port if neccessary
|
# Force the http port if neccessary
|
||||||
if args.port:
|
if args.port:
|
||||||
http_port = args.port
|
http_port = args.port
|
||||||
logger.info('Using forced port: %i' % http_port)
|
logger.info('Using forced web server port: %i', http_port)
|
||||||
else:
|
else:
|
||||||
http_port = int(headphones.HTTP_PORT)
|
http_port = int(headphones.HTTP_PORT)
|
||||||
|
|
||||||
# Try to start the server.
|
# Try to start the server. Will exit here is address is already in use.
|
||||||
webstart.initialize({
|
webstart.initialize({
|
||||||
'http_port': http_port,
|
'http_port': http_port,
|
||||||
'http_host': headphones.HTTP_HOST,
|
'http_host': headphones.HTTP_HOST,
|
||||||
'http_root': headphones.HTTP_ROOT,
|
'http_root': headphones.HTTP_ROOT,
|
||||||
'http_proxy': headphones.HTTP_PROXY,
|
'http_proxy': headphones.HTTP_PROXY,
|
||||||
'enable_https': headphones.ENABLE_HTTPS,
|
'enable_https': headphones.ENABLE_HTTPS,
|
||||||
'https_cert': headphones.HTTPS_CERT,
|
'https_cert': headphones.HTTPS_CERT,
|
||||||
'https_key': headphones.HTTPS_KEY,
|
'https_key': headphones.HTTPS_KEY,
|
||||||
'http_username': headphones.HTTP_USERNAME,
|
'http_username': headphones.HTTP_USERNAME,
|
||||||
'http_password': headphones.HTTP_PASSWORD,
|
'http_password': headphones.HTTP_PASSWORD,
|
||||||
})
|
})
|
||||||
|
|
||||||
if headphones.LAUNCH_BROWSER and not args.nolaunch:
|
if headphones.LAUNCH_BROWSER and not args.nolaunch:
|
||||||
headphones.launch_browser(headphones.HTTP_HOST, http_port, headphones.HTTP_ROOT)
|
headphones.launch_browser(headphones.HTTP_HOST, http_port,
|
||||||
|
headphones.HTTP_ROOT)
|
||||||
|
|
||||||
# Start the background threads
|
# Start the background threads
|
||||||
headphones.start()
|
headphones.start()
|
||||||
|
|
||||||
|
# Wait endlessy for a signal to happen
|
||||||
while True:
|
while True:
|
||||||
if not headphones.SIGNAL:
|
if not headphones.SIGNAL:
|
||||||
try:
|
try:
|
||||||
@@ -172,7 +177,8 @@ def main():
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
headphones.SIGNAL = 'shutdown'
|
headphones.SIGNAL = 'shutdown'
|
||||||
else:
|
else:
|
||||||
logger.info('Received signal: ' + headphones.SIGNAL)
|
logger.info('Received signal: %s', headphones.SIGNAL)
|
||||||
|
|
||||||
if headphones.SIGNAL == 'shutdown':
|
if headphones.SIGNAL == 'shutdown':
|
||||||
headphones.shutdown()
|
headphones.shutdown()
|
||||||
elif headphones.SIGNAL == 'restart':
|
elif headphones.SIGNAL == 'restart':
|
||||||
@@ -182,7 +188,6 @@ def main():
|
|||||||
|
|
||||||
headphones.SIGNAL = None
|
headphones.SIGNAL = None
|
||||||
|
|
||||||
return
|
# Call main()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -1,13 +1,35 @@
|
|||||||
#Headphones
|
#Headphones
|
||||||
|
|
||||||
|
###Support & Discuss
|
||||||
|
|
||||||
|
You are free to join the HP support community on IRC where you can ask questions, hang around and discuss anything related to HP.
|
||||||
|
|
||||||
|
1. Use any IRC client and connect to the Freenode server.
|
||||||
|
2. Join #headphones
|
||||||
|
|
||||||
###Installation and Notes
|
###Installation and Notes
|
||||||
|
|
||||||
This is a relatively early release of a third-party add-on for SABnzbd. It's been around for about a year, and while
|
[Read our Wiki](../../wiki) on how to install and use HeadPhones properly.
|
||||||
quite functional, there are still a lot of kinks to work out!
|
|
||||||
|
|
||||||
To run it, just double-click the Headphones.py file (in Windows - you may need to right click and click 'Open With' -> Python) or launch a terminal, cd into the Headphones directory and run 'python headphones.py'.
|
[**Troubleshooting** page](../../wiki/TroubleShooting) in the wiki can help you with comon problems.
|
||||||
|
|
||||||
|
**Issues** can be reported on the GitHub issue tracker considering these rules:
|
||||||
|
|
||||||
|
1. Analyze your log, you just might find the solution yourself!
|
||||||
|
2. You read the wiki and searched existing issues, but this is not solving your problem.
|
||||||
|
3. Post the issue with a clear title, description and the HP log and use [proper markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your text (code/log in code blocks).
|
||||||
|
4. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||||
|
|
||||||
|
**Feature requests** can be reported on the GitHub issue tracker too:
|
||||||
|
|
||||||
|
1. Search for similar existing 'issues', feature requests can be recognized by the label 'Request'.
|
||||||
|
2. If a similar Request exists, post a comment (+1, or add a new idea to the existing request), otherwise you can create a new one.
|
||||||
|
|
||||||
|
If you **comply with these rules** you can [post your request/issue](http://github.com/rembo10/headphones/issues).
|
||||||
|
|
||||||
|
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
||||||
|
If you change something in the code always make a PR to the developer branch instead of the master branch.
|
||||||
|
|
||||||
For additional startup options, type 'python Headphones.py -h' or 'python Headphones.py --help'
|
|
||||||
|
|
||||||
###Screenshots
|
###Screenshots
|
||||||
|
|
||||||
@@ -39,6 +61,5 @@ Album Page with track overview:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
If you run into any issues, use the GitHub issue tracker at: http://github.com/rembo10/headphones/issues.
|
|
||||||
|
|
||||||
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
##############################
|
|
||||||
### TODO LIST (18/08/12) ###
|
|
||||||
##############################
|
|
||||||
|
|
||||||
1. Add ability to fetch a specific extra, rather than pull down the whole category
|
|
||||||
- This requires extra calls to MB right now, so modify 1st mb artist call to pull down the
|
|
||||||
whole album list, then only send the desried ones to importer.py
|
|
||||||
2. Update API with newstyle getExtras function (lacking entirely right now)
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
.dataTables_wrapper {
|
|
||||||
border-radius: 20px;
|
|
||||||
-webkit-border-radius: 20px;
|
|
||||||
-moz-border-radius: 20px;
|
|
||||||
width: 88%;
|
|
||||||
margin: 20px auto 0 auto;
|
|
||||||
padding: 25px;
|
|
||||||
background-color: white;
|
|
||||||
position: relative;
|
|
||||||
min-height: 155px;
|
|
||||||
clear: both;
|
|
||||||
_height: 302px;
|
|
||||||
zoom: 1; /* Feeling sorry for IE */
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_processing {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 20px;
|
|
||||||
height: 30px;
|
|
||||||
margin-left: -125px;
|
|
||||||
margin-top: -15px;
|
|
||||||
padding: 14px 0 2px 0;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_length {
|
|
||||||
width: 40%;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_filter {
|
|
||||||
width: 50%;
|
|
||||||
float: right;
|
|
||||||
text-align: right;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_info {
|
|
||||||
width: 50%;
|
|
||||||
float: left;
|
|
||||||
background-color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_paginate {
|
|
||||||
width: 44px;
|
|
||||||
* width: 50px;
|
|
||||||
float: right;
|
|
||||||
text-align: right;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pagination nested */
|
|
||||||
.paginate_disabled_previous, .paginate_enabled_previous, .paginate_disabled_next, .paginate_enabled_next {
|
|
||||||
height: 19px;
|
|
||||||
width: 19px;
|
|
||||||
margin-left: 3px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paginate_disabled_previous {
|
|
||||||
background-image: url('../images/back_disabled.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.paginate_enabled_previous {
|
|
||||||
background-image: url('../images/back_enabled.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.paginate_disabled_next {
|
|
||||||
background-image: url('../images/forward_disabled.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.paginate_enabled_next {
|
|
||||||
background-image: url('../images/forward_enabled.jpg');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* DataTables display
|
|
||||||
*/
|
|
||||||
table.display {
|
|
||||||
margin: 20px auto;
|
|
||||||
clear: both;
|
|
||||||
|
|
||||||
/* Note Firefox 3.5 and before have a bug with border-collapse
|
|
||||||
* ( https://bugzilla.mozilla.org/show%5Fbug.cgi?id=155955 )
|
|
||||||
* border-spacing: 0; is one possible option. Conditional-css.com is
|
|
||||||
* useful for this kind of thing
|
|
||||||
*
|
|
||||||
* Further note IE 6/7 has problems when calculating widths with border width.
|
|
||||||
* It subtracts one px relative to the other browsers from the first column, and
|
|
||||||
* adds one to the end...
|
|
||||||
*
|
|
||||||
* If you want that effect I'd suggest setting a border-top/left on th/td's and
|
|
||||||
* then filling in the gaps with other borders.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display thead th {
|
|
||||||
padding: 3px 18px 3px 10px;
|
|
||||||
background-color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
* cursor: hand;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tfoot th {
|
|
||||||
padding: 3px 18px 3px 10px;
|
|
||||||
border-top: 1px solid black;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.heading2 td {
|
|
||||||
border-bottom: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display td {
|
|
||||||
padding: 3px 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display td.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* DataTables sorting
|
|
||||||
*/
|
|
||||||
|
|
||||||
.sorting_asc {
|
|
||||||
background: url('../images/sort_asc.png') no-repeat center right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting_desc {
|
|
||||||
background: url('../images/sort_desc.png') no-repeat center right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting {
|
|
||||||
background: url('../images/sort_both.png') no-repeat center right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting_asc_disabled {
|
|
||||||
background: url('../images/sort_asc_disabled.png') no-repeat center right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sorting_desc_disabled {
|
|
||||||
background: url('../images/sort_desc_disabled.png') no-repeat center right;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* DataTables row classes
|
|
||||||
*/
|
|
||||||
table.display tr.odd.gradeA {
|
|
||||||
background-color: #ddffdd;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.gradeA {
|
|
||||||
background-color: #ddffdd;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.odd.gradeC {
|
|
||||||
background-color: #ddddff;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.gradeC {
|
|
||||||
background-color: #ddddff;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.odd.gradeX {
|
|
||||||
background-color: #ffdddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.gradeX {
|
|
||||||
background-color: #ffdddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.odd.gradeU {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.gradeU {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
table.display tr.odd.gradeZ {
|
|
||||||
background-color: #FCFAFA;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.gradeZ {
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
* Misc
|
|
||||||
*/
|
|
||||||
.dataTables_scroll {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_scrollBody {
|
|
||||||
*margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top, .bottom {
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #F5F5F5;
|
|
||||||
border: 1px solid #CCCCCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top .dataTables_info {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dataTables_empty {
|
|
||||||
font-size: 24px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-color: white;
|
|
||||||
height: 50px;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
tfoot input {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
width: 100%;
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
tfoot input.search_init {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.group {
|
|
||||||
background-color: #d1cfd0;
|
|
||||||
border-bottom: 2px solid #A19B9E;
|
|
||||||
border-top: 2px solid #A19B9E;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.details {
|
|
||||||
background-color: #d1cfd0;
|
|
||||||
border: 2px solid #A19B9E;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paging_full_numbers {
|
|
||||||
width: 400px;
|
|
||||||
height: 22px;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paging_full_numbers span.paginate_button,
|
|
||||||
.paging_full_numbers span.paginate_active {
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
-webkit-border-radius: 5px;
|
|
||||||
-moz-border-radius: 5px;
|
|
||||||
padding: 2px 5px;
|
|
||||||
margin: 0 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
*cursor: hand;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paging_full_numbers span.paginate_button {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paging_full_numbers span.paginate_button:hover {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paging_full_numbers span.paginate_active {
|
|
||||||
background-color: #99B3FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.row_selected td {
|
|
||||||
background-color: #B0BED9;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.odd.row_selected td {
|
|
||||||
background-color: #9FAFD1;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.box {
|
|
||||||
height: 100px;
|
|
||||||
padding: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
border: 1px solid #8080FF;
|
|
||||||
background-color: #E5E5FF;
|
|
||||||
}
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
/* HTML5 ✰ Boilerplate */
|
|
||||||
|
|
||||||
html, body, div, span, object, iframe,
|
|
||||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
|
||||||
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
|
|
||||||
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
|
|
||||||
fieldset, form, label, legend,
|
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
|
||||||
article, aside, canvas, details, figcaption, figure,
|
|
||||||
footer, header, hgroup, menu, nav, section, summary,
|
|
||||||
time, mark, audio, video {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
font-size: 100%;
|
|
||||||
font: inherit;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
article, aside, details, figcaption, figure,
|
|
||||||
footer, header, hgroup, menu, nav, section {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote, q { quotes: none; }
|
|
||||||
blockquote:before, blockquote:after,
|
|
||||||
q:before, q:after { content: ''; content: none; }
|
|
||||||
ins { background-color: #ff9; color: #000; text-decoration: none; }
|
|
||||||
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
|
|
||||||
del { text-decoration: line-through; }
|
|
||||||
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
|
|
||||||
table { border-collapse: collapse; border-spacing: 0; }
|
|
||||||
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
|
|
||||||
input, select { vertical-align: middle; }
|
|
||||||
|
|
||||||
body { font:13px/1.231 sans-serif; *font-size:small; }
|
|
||||||
select, input, textarea, button { font:99% sans-serif; }
|
|
||||||
pre, code, kbd, samp { font-family: monospace, sans-serif; }
|
|
||||||
|
|
||||||
html { overflow-y: scroll; }
|
|
||||||
a:hover, a:active { outline: none; }
|
|
||||||
ul, ol { margin-left: 2em; }
|
|
||||||
ol { list-style-type: decimal; }
|
|
||||||
nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
|
|
||||||
small { font-size: 85%; }
|
|
||||||
strong, th { font-weight: bold; }
|
|
||||||
td { vertical-align: top; }
|
|
||||||
|
|
||||||
sub, sup { font-size: 75%; line-height: 0; position: relative; }
|
|
||||||
sup { top: -0.5em; }
|
|
||||||
sub { bottom: -0.25em; }
|
|
||||||
|
|
||||||
pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; }
|
|
||||||
textarea { overflow: auto; }
|
|
||||||
.ie6 legend, .ie7 legend { margin-left: -7px; }
|
|
||||||
input[type="radio"] { vertical-align: text-bottom; }
|
|
||||||
input[type="checkbox"] { vertical-align: bottom; }
|
|
||||||
.ie7 input[type="checkbox"] { vertical-align: baseline; }
|
|
||||||
.ie6 input { vertical-align: text-bottom; }
|
|
||||||
label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
|
|
||||||
button, input, select, textarea { margin: 0; }
|
|
||||||
input:valid, textarea:valid { }
|
|
||||||
input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; }
|
|
||||||
.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
|
|
||||||
|
|
||||||
::-moz-selection{ background: grey; color:#fff; text-shadow: none; }
|
|
||||||
::selection { background: grey; color:#fff; text-shadow: none; }
|
|
||||||
|
|
||||||
button { width: auto; overflow: visible; }
|
|
||||||
.ie7 img { -ms-interpolation-mode: bicubic; }
|
|
||||||
|
|
||||||
body, select, input, textarea { color: #444; }
|
|
||||||
h1, h2, h3, h4, h5, h6 { font-weight: bold; }
|
|
||||||
|
|
||||||
/*
|
|
||||||
// ========================================== \\
|
|
||||||
|| ||
|
|
||||||
|| Custom Styles ||
|
|
||||||
|| ||
|
|
||||||
\\ ========================================== //
|
|
||||||
*/
|
|
||||||
|
|
||||||
a:link {
|
|
||||||
color: #5E2612;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:visited {
|
|
||||||
color: #5E2612;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
a:hover { /*this effect is not shown in NN4.xx*/
|
|
||||||
color: #999999;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a:active {/*colour in NN4.xx is red*/
|
|
||||||
color: #5E2612;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
a.blue {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
container { }
|
|
||||||
|
|
||||||
body { background-color: #EBF4FB; min-width: 935px; }
|
|
||||||
|
|
||||||
header { min-height: 68px; width: 100%; min-width: 935px; padding-left: 0px; padding-right: 10px; background-color: #CDC9C9; position: fixed; z-index: 998; }
|
|
||||||
|
|
||||||
h1 { font-size: 24px; }
|
|
||||||
h2 { font-size: 20px; }
|
|
||||||
h3 { font-size: 16px; }
|
|
||||||
h4 { font-size: 14px; }
|
|
||||||
|
|
||||||
p.indented { padding-top: 20px; margin-left: 20px; font-size: 14px; }
|
|
||||||
p.center { text-align: center; font-size: 18px; }
|
|
||||||
.smalltext2 { font-size: 12px; margin-left: 45px; }
|
|
||||||
|
|
||||||
div#updatebar { text-align: center; min-width: 970px; width: 100%; background-color: light-blue; float: left; }
|
|
||||||
div#logo { float: left; padding-left: 10px; }
|
|
||||||
|
|
||||||
ul#nav { margin: 25px 0 0 0; float: left; list-style-type: none; }
|
|
||||||
ul#nav li { margin: 40px 0px auto 10px; display: inline; }
|
|
||||||
ul#nav li a { padding: 5px; font-size: 16px; font-weight: bold; color: #330000; text-decoration: none; }
|
|
||||||
ul#nav li a:hover { background-color: #a3e532; }
|
|
||||||
|
|
||||||
div#subhead_container { height: 30px; width:100%; min-width: 1000px; background-color:#330000; float: left; list-style-type: none; z-index: 998; overflow: hidden; }
|
|
||||||
ul#subhead_menu { margin-top: 5px; }
|
|
||||||
ul#subhead_menu li { width: 100%; height: 100%; display: inline; }
|
|
||||||
ul#subhead_menu li a { padding: 5px 15px 10px 15px; vertical-align: middle; color: white; font-size: 16px; text-decoration: none; }
|
|
||||||
ul#subhead_menu li a:hover { width: 100%; height: 100%; background-color: #grey; }
|
|
||||||
|
|
||||||
div#searchbar { margin: 24px 30px auto auto; float: right; }
|
|
||||||
|
|
||||||
div#main { margin: 0; padding: 80px 0 0 0; }
|
|
||||||
|
|
||||||
.table_wrapper { border-radius: 20px; -webkit-border-radius: 20px; -moz-border-radius: 20px; width: 88%; margin: 20px auto 0 auto; padding: 25px; background-color: white; position: relative; min-height: 200px; clear: both; _height: 302px; zoom: 1; }
|
|
||||||
.manage_wrapper { width: 88%; margin: 20px auto 0 auto; padding: 25px; min-height: 150px; clear: both; _height: 302px; zoom: 1; }
|
|
||||||
.table_wrapper_left { padding: 25px; background-color: #ffffff; float: left; width: 40%; min-height: 100px; margin-top: 25px; margin-left: 30px; margin-right: auto; -moz-border-radius: 20px; border-radius: 20px; }
|
|
||||||
.table_wrapper_right{ padding: 25px; background-color: #ffffff; width: 40%; min-height: 100px; margin-top: 25px; margin-left: auto; margin-right: 30px; -moz-border-radius: 20px; border-radius: 20px; }
|
|
||||||
|
|
||||||
.configtable { font-size: 14px; line-height:18px; }
|
|
||||||
.configtable td { width: 350px; padding: 10px; }
|
|
||||||
.configtable td#middle { vertical-align: middle; }
|
|
||||||
|
|
||||||
table#artist_table { background-color: white; width: 100%; padding: 20px; }
|
|
||||||
|
|
||||||
table#artist_table th#select { text-align: left; }
|
|
||||||
table#artist_table th#name { text-align: left; min-width: 200px; }
|
|
||||||
table#artist_table th#status { text-align: left; min-width: 50px; }
|
|
||||||
table#artist_table th#album { text-align: left; min-width: 300px; }
|
|
||||||
table#artist_table th#have { text-align: center; }
|
|
||||||
table#artist_table td#name { vertical-align: middle; text-align: left; min-width:200px; }
|
|
||||||
table#artist_table td#status { vertical-align: middle; text-align: left; min-width: 50px; }
|
|
||||||
table#artist_table td#album { vertical-align: middle; text-align: left; min-width: 300px; }
|
|
||||||
table#artist_table td#have { vertical-align: middle; }
|
|
||||||
|
|
||||||
div#paddingheader { padding-top: 48px; font-size: 24px; font-weight: bold; text-align: center; }
|
|
||||||
div#nopaddingheader { font-size: 24px; font-weight: bold; text-align: center; }
|
|
||||||
table#album_table { background-color: white; }
|
|
||||||
|
|
||||||
table#album_table th#select { vertical-align: middle; text-align: left; min-width: 10px; }
|
|
||||||
table#album_table th#albumart { text-align: left; min-width: 50px; }
|
|
||||||
table#album_table th#albumname { text-align: center; min-width: 150px; }
|
|
||||||
table#album_table th#reldate { width: 175px; text-align: center; min-width: 70px; }
|
|
||||||
table#album_table th#status { width: 175px; text-align: center; min-width: 80px; }
|
|
||||||
table#album_table th#type { width: 175px; text-align: center; min-width: 100px; }
|
|
||||||
table#album_table th#bitrate { text-align: center; min-width: 60px; }
|
|
||||||
table#album_table th#albumformat { text-align: center; min-width: 60px; }
|
|
||||||
table#album_table th#wantlossless { width: 80px; text-align: center; min-width: 80px; }
|
|
||||||
table#album_table td#select { vertical-align: middle; text-align: left; }
|
|
||||||
table#album_table td#albumart { vertical-align: middle; text-align: left; }
|
|
||||||
table#album_table td#albumname { vertical-align: middle; text-align: center; }
|
|
||||||
table#album_table td#reldate { vertical-align: middle; text-align: center; }
|
|
||||||
table#album_table td#status { vertical-align: middle; text-align: center; font-size: 13px; }
|
|
||||||
table#album_table td#status a#wantlossless { white-space: nowrap; }
|
|
||||||
table#album_table td#type { vertical-align: middle; text-align: center; }
|
|
||||||
table#album_table td#have { vertical-align: middle; }
|
|
||||||
table#album_table td#bitrate { vertical-align: middle; text-align: center; font-size: 13px; }
|
|
||||||
table#album_table td#albumformat { vertical-align: middle; text-align: center; font-size: 13px; }
|
|
||||||
table#album_table td#wantlossless { vertical-align: middle; text-align: center; font-size: 13px; }
|
|
||||||
|
|
||||||
img.albumArt { float: left; padding-right: 5px; }
|
|
||||||
div#albumheader { padding-top: 48px; height: 200px; }
|
|
||||||
div#track_wrapper { margin-left: -50px; padding-top: 20px; font-size: 16px; width: 100%; }
|
|
||||||
|
|
||||||
table#track_table th#number { text-align: right; min-width: 10px; }
|
|
||||||
table#track_table th#name { text-align: center; min-width: 350px; }
|
|
||||||
table#track_table th#duration { width: 175px; text-align: center; min-width: 100px; }
|
|
||||||
table#track_table th#location { text-align: center; width: 250px; }
|
|
||||||
table#track_table th#bitrate { text-align: center; min-width: 75px; }
|
|
||||||
table#track_table th#format { text-align: center; min-width: 75px; }
|
|
||||||
|
|
||||||
table#track_table td#number { vertical-align: middle; text-align: right; }
|
|
||||||
table#track_table td#name { vertical-align: middle; text-align: center; font-size: 15px; }
|
|
||||||
table#track_table td#duration { vertical-align: middle; text-align: center; }
|
|
||||||
table#track_table td#location { vertical-align: middle; text-align: center; font-size: 11px; }
|
|
||||||
table#track_table td#bitrate { vertical-align: middle; text-align: center; font-size: 12px; }
|
|
||||||
table#track_table td#format { vertical-align: middle; text-align: center; font-size: 12px; }
|
|
||||||
|
|
||||||
table#history_table { background-color: white; width: 100%; font-size: 13px; }
|
|
||||||
|
|
||||||
table#history_table td#dateadded { vertical-align: middle; text-align: center; min-width: 150px; font-size: 14px; }
|
|
||||||
table#history_table td#filename { vertical-align: middle; text-align: center; min-width: 100px; font-size: 15px; }
|
|
||||||
table#history_table td#size { vertical-align: middle; text-align: center; min-width: 75px; font-size: 14px; }
|
|
||||||
table#history_table td#status { vertical-align: middle; text-align: center; font-size: 14px; }
|
|
||||||
table#history_table td#action { vertical-align: middle; text-align: center; font-size: 14px; }
|
|
||||||
|
|
||||||
table#log_table { background-color: white; }
|
|
||||||
|
|
||||||
table#log_table th#timestamp { text-align: left; min-width: 150px; }
|
|
||||||
table#log_table th#level { text-align: left; min-width: 60px; }
|
|
||||||
table#log_table th#message { text-align: left; min-width: 500px; }
|
|
||||||
|
|
||||||
table#upcoming_table th#albumart { text-align: center; min-width: 50px; }
|
|
||||||
table#upcoming_table th#albumname { text-align: center; min-width: 200px; }
|
|
||||||
table#upcoming_table th#artistname { text-align: center; min-width: 150px; }
|
|
||||||
table#upcoming_table th#reldate { text-align: center; min-width: 100px; }
|
|
||||||
table#upcoming_table th#type { text-align: center; min-width: 75px; }
|
|
||||||
|
|
||||||
table#upcoming_table td#select { vertical-align: middle; text-align: center; }
|
|
||||||
table#upcoming_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; }
|
|
||||||
table#upcoming_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; }
|
|
||||||
table#upcoming_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; }
|
|
||||||
table#upcoming_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; }
|
|
||||||
table#upcoming_table td#type { vertical-align: middle; text-align: center; min-width: 75px; }
|
|
||||||
table#upcoming_table td#status { vertical-align: middle; text-align: center; }
|
|
||||||
|
|
||||||
table#wanted_table th#albumart { text-align: center; min-width: 50px; }
|
|
||||||
table#wanted_table th#albumname { text-align: center; min-width: 200px; }
|
|
||||||
table#wanted_table th#artistname { text-align: center; min-width: 150px; }
|
|
||||||
table#wanted_table th#reldate { text-align: center; min-width: 100px; }
|
|
||||||
table#wanted_table th#type { text-align: center; min-width: 75px; }
|
|
||||||
|
|
||||||
table#wanted_table td#select { vertical-align: middle; text-align: center; }
|
|
||||||
table#wanted_table td#albumart { vertical-align: middle; text-align: center; min-width: 50px; }
|
|
||||||
table#wanted_table td#albumname { vertical-align: middle; text-align: center; min-width: 200px; }
|
|
||||||
table#wanted_table td#artistname { vertical-align: middle; text-align: center; min-width: 150px; }
|
|
||||||
table#wanted_table td#reldate { vertical-align: middle; text-align: center; min-width: 100px; }
|
|
||||||
table#wanted_table td#type { vertical-align: middle; text-align: center; min-width: 75px; }
|
|
||||||
table#wanted_table td#status { vertical-align: middle; text-align: center; }
|
|
||||||
|
|
||||||
table#searchresults_table th#albumname { text-align: left; min-width: 225px; }
|
|
||||||
table#searchresults_table th#artistname { text-align: center; min-width: 325px; }
|
|
||||||
table#searchresults_table th#score { text-align: center; min-width: 75px; }
|
|
||||||
|
|
||||||
table#searchresults_table td#albumname { vertical-align: middle; text-align: left; min-width: 200px; }
|
|
||||||
table#searchresults_table td#artistname { vertical-align: middle; text-align: left; min-width: 300px; }
|
|
||||||
table#searchresults_table td#score { vertical-align: middle; text-align: center; min-width: 75px; }
|
|
||||||
|
|
||||||
div.progress-container { border: 1px solid #ccc; width: 100px; height: 14px; margin: 2px 5px 2px 0; padding: 1px; float: left; background: white; }
|
|
||||||
div.progress-container > div { background-color: #a3e532; height: 14px; }
|
|
||||||
.havetracks { font-size: 13px; margin-left: 36px; padding-bottom: 3px; vertical-align: middle; }
|
|
||||||
|
|
||||||
footer { margin: 20px auto 20px auto; }
|
|
||||||
div#version { text-align: center; font-weight: bold; }
|
|
||||||
div#donate { text-align: center; margin: 20px auto 20px auto; }
|
|
||||||
|
|
||||||
div#shutdown{ text-align: center; vertical-align: middle; }
|
|
||||||
|
|
||||||
.cloudtag { padding-top: 30px; font-size:16px; }
|
|
||||||
#cloud a.tag1 { font-size: 0.7em; font-weight: 100; }
|
|
||||||
#cloud a.tag2 { font-size: 0.8em; font-weight: 200; }
|
|
||||||
#cloud a.tag3 { font-size: 0.9em; font-weight: 300; }
|
|
||||||
#cloud a.tag4 { font-size: 1.0em; font-weight: 400; }
|
|
||||||
#cloud a.tag5 { font-size: 1.2em; font-weight: 500; }
|
|
||||||
#cloud a.tag6 { font-size: 1.4em; font-weight: 600; }
|
|
||||||
#cloud a.tag7 { font-size: 1.6em; font-weight: 700; }
|
|
||||||
#cloud a.tag8 { font-size: 1.8em; font-weight: 800; }
|
|
||||||
#cloud a.tag9 { font-size: 2.2em; font-weight: 900; }
|
|
||||||
#cloud a.tag10 { font-size: 2.5em; font-weight: 900; }
|
|
||||||
|
|
||||||
#cloud { padding: 2px; line-height: 1.5em; text-align: center; }
|
|
||||||
#cloud a { padding: 0px; }
|
|
||||||
#cloud { margin: 0; }
|
|
||||||
#cloud li { display: inline; }
|
|
||||||
|
|
||||||
.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
|
|
||||||
.hidden { display: none; visibility: hidden; }
|
|
||||||
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
|
|
||||||
.visuallyhidden.focusable:active,
|
|
||||||
.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
|
|
||||||
.invisible { visibility: hidden; }
|
|
||||||
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
|
|
||||||
.clearfix:after { clear: both; }
|
|
||||||
.clearfix { zoom: 1; }
|
|
||||||
|
|
||||||
|
|
||||||
@media all and (orientation:portrait) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (orientation:landscape) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-device-width: 480px) {
|
|
||||||
|
|
||||||
html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
* { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important;
|
|
||||||
-ms-filter: none !important; }
|
|
||||||
a, a:visited { color: #444 !important; text-decoration: underline; }
|
|
||||||
a[href]:after { content: " (" attr(href) ")"; }
|
|
||||||
abbr[title]:after { content: " (" attr(title) ")"; }
|
|
||||||
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
|
|
||||||
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
|
|
||||||
thead { display: table-header-group; }
|
|
||||||
tr, img { page-break-inside: avoid; }
|
|
||||||
@page { margin: 0.5cm; }
|
|
||||||
p, h2, h3 { orphans: 3; widows: 3; }
|
|
||||||
h2, h3{ page-break-after: avoid; }
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 212 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 206 B |
|
After Width: | Height: | Size: 220 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -5,10 +5,15 @@
|
|||||||
%>
|
%>
|
||||||
|
|
||||||
<%def name="headerIncludes()">
|
<%def name="headerIncludes()">
|
||||||
|
|
||||||
<div id="subhead_container">
|
<div id="subhead_container">
|
||||||
|
<div id="back_to_previous_link">
|
||||||
|
<a href="artistPage?ArtistID=${album['ArtistID']}" class="back">« Back to ${album['ArtistName']}</a>
|
||||||
|
</div>
|
||||||
<div id="subhead_menu">
|
<div id="subhead_menu">
|
||||||
|
|
||||||
<a id="menu_link_delete" href="deleteAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Album</a>
|
<a id="menu_link_delete" href="deleteAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Album</a>
|
||||||
%if album['Status'] == 'Skipped':
|
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
|
||||||
<a id="menu_link_wanted" href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=False', $(this),true)" data-success="'${album['AlbumTitle']}' added to queue"><i class="fa fa-heart"></i> Mark Album as Wanted</a>
|
<a id="menu_link_wanted" href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=False', $(this),true)" data-success="'${album['AlbumTitle']}' added to queue"><i class="fa fa-heart"></i> Mark Album as Wanted</a>
|
||||||
%elif album['Status'] == 'Wanted':
|
%elif album['Status'] == 'Wanted':
|
||||||
<a id="menu_link_check" href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=True', $(this));" data-success="Forced checking successful"><i class="fa fa-search"></i> Force Check</a>
|
<a id="menu_link_check" href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=True', $(this));" data-success="Forced checking successful"><i class="fa fa-search"></i> Force Check</a>
|
||||||
@@ -59,9 +64,23 @@
|
|||||||
<input type="button" value="Save changes" onclick="doAjaxCall('editSearchTerm',$(this),'tabs',true);return false;" data-success="Search term updated"/>
|
<input type="button" value="Save changes" onclick="doAjaxCall('editSearchTerm',$(this),'tabs',true);return false;" data-success="Search term updated"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<a class="menu_link_edit" id="choose_specific_download" href="#" onclick="getAvailableDownloads()"><i class="fa fa-search"></i> Choose Specific Download</a>
|
||||||
|
<div id="choose_specific_download_dialog" title="Choose a specific download for this album" style="display:none" class="configtable">
|
||||||
|
<table class="display" id="downloads_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th id="title">Title</th>
|
||||||
|
<th id="size">Size</th>
|
||||||
|
<th id="provider">Provider</th>
|
||||||
|
<th id="kind">Kind</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="downloads_table_body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="artistPage?ArtistID=${album['ArtistID']}" class="back">« Back to ${album['ArtistName']}</a>
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="body()">
|
<%def name="body()">
|
||||||
@@ -71,8 +90,14 @@
|
|||||||
<img height="200" alt="" class="albumArt" src="artwork/album/${album['AlbumID']}">
|
<img height="200" alt="" class="albumArt" src="artwork/album/${album['AlbumID']}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1><a href="http://musicbrainz.org/release-group/${album['AlbumID']}">${album['AlbumTitle']}</a></h1>
|
<h1 id="albumname">
|
||||||
<h2><a href="http://musicbrainz.org/artist/${album['ArtistID']}">${album['ArtistName']}</a></h2>
|
<a href="http://musicbrainz.org/release-group/${album['AlbumID']}" id="albumnamelink">${album['AlbumTitle']}</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2 id="artistname">
|
||||||
|
<a href="http://musicbrainz.org/artist/${album['ArtistID']}" id="artistnamelink">${album['ArtistName']}</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<%
|
<%
|
||||||
totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
|
totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
|
||||||
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
|
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
|
||||||
@@ -89,7 +114,6 @@
|
|||||||
<li>Duration: <span>${albumduration}</span></li>
|
<li>Duration: <span>${albumduration}</span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="track_wrapper">
|
<div id="track_wrapper">
|
||||||
<table class="display" id="track_table">
|
<table class="display" id="track_table">
|
||||||
@@ -200,9 +224,115 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getAvailableDownloads() {
|
||||||
|
ShowSpinner();
|
||||||
|
$.getJSON("choose_specific_download?AlbumID=${album['AlbumID']}", function(data) {
|
||||||
|
loader.remove();
|
||||||
|
feedback.fadeOut();
|
||||||
|
search_results = data
|
||||||
|
for( var i = 0, len = data.length; i < len; i++ ) {
|
||||||
|
$('#downloads_table_body').append('<tr><td id="title"><a href="#" onclick="downloadSpecificRelease('+i+')">'+data[i].title+'</a></td><td id="size"><span title='+data[i].size+'></span>'+(data[i].size / (1024*1024)).toFixed(2)+' MB</td><td id="provider">'+data[i].provider+'</td><td id="kind">'+data[i].kind+'</td></tr>');
|
||||||
|
}
|
||||||
|
$('#downloads_table').dataTable({
|
||||||
|
"aoColumns": [
|
||||||
|
null,
|
||||||
|
{ "sType": "title-numeric"},
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"aaSorting": [],
|
||||||
|
"bFilter": false,
|
||||||
|
"bInfo": false,
|
||||||
|
"bPaginate": false,
|
||||||
|
});
|
||||||
|
$("#choose_specific_download_dialog").dialog({ width: "1000px" });
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadSpecificRelease(i){
|
||||||
|
|
||||||
|
title = search_results[i].title
|
||||||
|
size = search_results[i].size
|
||||||
|
url = search_results[i].url
|
||||||
|
provider = search_results[i].provider
|
||||||
|
kind = search_results[i].kind
|
||||||
|
|
||||||
|
ShowSpinner();
|
||||||
|
$.getJSON("download_specific_release?AlbumID=${album['AlbumID']}&title="+title+"&size="+size+"&url="+url+"&provider="+provider+"&kind=" + kind, function(data) {
|
||||||
|
loader.remove();
|
||||||
|
feedback.fadeOut();
|
||||||
|
refreshSubmenu();
|
||||||
|
$("#choose_specific_download_dialog").dialog("close");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowSpinner() {
|
||||||
|
feedback = $("#ajaxMsg");
|
||||||
|
update = $("#updatebar");
|
||||||
|
if ( update.is(":visible") ) {
|
||||||
|
var height = update.height() + 35;
|
||||||
|
feedback.css("bottom",height + "px");
|
||||||
|
} else {
|
||||||
|
feedback.removeAttr("style");
|
||||||
|
}
|
||||||
|
loader = $("<i class='fa fa-refresh fa-spin'></i>");
|
||||||
|
feedback.prepend(loader);
|
||||||
|
feedback.fadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
var loadingMessage = false;
|
||||||
|
var spinner_active = false;
|
||||||
|
var loadingtext_active = false;
|
||||||
|
var refreshInterval;
|
||||||
|
var wasLoading = false;
|
||||||
|
var x = 0;
|
||||||
|
|
||||||
|
function checkAlbumStatus() {
|
||||||
|
$.getJSON("getAlbumjson?AlbumID=${album['AlbumID']}", function(data) {
|
||||||
|
if (data['Status'] == "Loading"){
|
||||||
|
wasLoading = true;
|
||||||
|
$('#albumnamelink').text(data["AlbumTitle"]);
|
||||||
|
$('#artistnamelink').text(data["ArtistName"]);
|
||||||
|
if (loadingMessage == false){
|
||||||
|
$("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
|
||||||
|
showArtistMsg("Getting album information");
|
||||||
|
loadingMessage = true;
|
||||||
|
}
|
||||||
|
if (spinner_active == false){
|
||||||
|
$('#albumname').prepend('<i class="fa fa-refresh fa-spin" id="albumnamespinner"></i>')
|
||||||
|
spinner_active = true;
|
||||||
|
}
|
||||||
|
if (loadingtext_active == false){
|
||||||
|
$('#albumname').append('<h3 id="loadingtext"><i>(Album information is currently being loaded)</i></h3>')
|
||||||
|
loadingtext_active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if (++x === 10) {
|
||||||
|
clearInterval(refreshInterval);
|
||||||
|
}
|
||||||
|
var sts = $("#artistname").text();
|
||||||
|
if (wasLoading == true || sts == "Loading"){
|
||||||
|
location.reload();
|
||||||
|
$('#albumnamespinner').remove()
|
||||||
|
$('#loadingtext').remove()
|
||||||
|
$('#ajaxMsg2').remove()
|
||||||
|
spinner_active = false
|
||||||
|
loadingtext_active = false
|
||||||
|
loadingMessage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
getAlbumInfo();
|
getAlbumInfo();
|
||||||
initThisPage();
|
initThisPage();
|
||||||
|
checkAlbumStatus();
|
||||||
|
refreshInterval = setInterval(function(){
|
||||||
|
checkAlbumStatus();
|
||||||
|
}, 3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<%def name="headerIncludes()">
|
<%def name="headerIncludes()">
|
||||||
<div id="subhead_container">
|
<div id="subhead_container">
|
||||||
<div id="subhead_menu">
|
<div id="subhead_menu">
|
||||||
<a id="menu_link_refresh" onclick="doAjaxCall('refreshArtist?ArtistID=${artist['ArtistID']}', $(this)),'table'" href="#" data-success="'${artist['ArtistName']}' is being refreshed"><i class="fa fa-refresh"></i> Refresh Artist</a>
|
<a id="menu_link_refresh" onclick="doSimpleAjaxCall('refreshArtist?ArtistID=${artist['ArtistID']}')" href="#"><i class="fa fa-refresh"></i> Refresh Artist</a>
|
||||||
<a id="menu_link_delete" href="deleteArtist?ArtistID=${artist['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Artist</a>
|
<a id="menu_link_delete" href="deleteArtist?ArtistID=${artist['ArtistID']}"><i class="fa fa-trash-o"></i> Delete Artist</a>
|
||||||
%if artist['Status'] == 'Paused':
|
%if artist['Status'] == 'Paused':
|
||||||
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeArtist?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="${artist['ArtistName']} resumed"><i class="fa fa-play"></i> Resume Artist</a>
|
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeArtist?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="${artist['ArtistName']} resumed"><i class="fa fa-play"></i> Resume Artist</a>
|
||||||
@@ -16,22 +16,22 @@
|
|||||||
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseArtist?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="${artist['ArtistName']} paused"><i class="fa fa-pause"></i> Pause Artist</a>
|
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseArtist?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="${artist['ArtistName']} paused"><i class="fa fa-pause"></i> Pause Artist</a>
|
||||||
%endif
|
%endif
|
||||||
%if artist['IncludeExtras']:
|
%if artist['IncludeExtras']:
|
||||||
<a id="menu_link_removeextra" href="#" onclick="doAjaxCall('removeExtras?ArtistID=${artist['ArtistID']}',$(this),true)" data-success="Extras removed for ${artist['ArtistName']}"><i class="fa fa-minus"></i> Remove Extras</a>
|
<a id="menu_link_removeextra" href="#" onclick="doAjaxCall('removeExtras?ArtistID=${artist['ArtistID']}&ArtistName=${artist['ArtistName']}',$(this),'submenu&table')" data-success="Extras removed for ${artist['ArtistName']}"><i class="fa fa-minus"></i> Remove Extras</a>
|
||||||
<a class="menu_link_edit" id="menu_link_modifyextra" href="#"><i class="fa fa-pencil"></i> Modify Extras</a>
|
<a class="menu_link_edit" id="menu_link_modifyextra" href="#"><i class="fa fa-pencil"></i> Modify Extras</a>
|
||||||
%else:
|
%else:
|
||||||
<a id="menu_link_getextra" href="#"><i class="fa fa-plus"></i> Get Extras</a>
|
<a id="menu_link_getextra" href="#"><i class="fa fa-plus"></i> Get Extras</a>
|
||||||
%endif
|
%endif
|
||||||
<div id="dialog" title="Choose Which Extras to Fetch" style="display:none" class="configtable">
|
<div id="dialog" title="Choose Which Extras to Fetch" style="display:none" class="configtable">
|
||||||
<form action="getExtras" method="get" class="form">
|
<form action="getExtras" method="get" class="form">
|
||||||
<input type="hidden" name="ArtistID" value="${artist['ArtistID']}">
|
<input type="hidden" name="ArtistID" value="${artist['ArtistID']}">
|
||||||
<input type="hidden" name="newstyle" value="true">
|
<input type="hidden" name="newstyle" value="true">
|
||||||
%for extra in extras:
|
%for extra in extras:
|
||||||
<input type="checkbox" id="${extra}" name="${extra}" value="1" ${extras[extra]} />${string.capwords(extra)}<br>
|
<input type="checkbox" id="${extra}" name="${extra}" value="1" ${extras[extra]} />${string.capwords(extra)}<br>
|
||||||
%endfor
|
%endfor
|
||||||
<br>
|
<br>
|
||||||
<input id="submit" type="submit" value="Fetch Extras">
|
<input id="submit" type="submit" value="Fetch Extras">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="home" class="back">« Back to overview</a>
|
<a href="home" class="back">« Back to overview</a>
|
||||||
@@ -42,14 +42,8 @@
|
|||||||
<div id="artistImg">
|
<div id="artistImg">
|
||||||
<img id="artistImage" class="albumArt" alt="" src="artwork/artist/${artist['ArtistID']}"/>
|
<img id="artistImage" class="albumArt" alt="" src="artwork/artist/${artist['ArtistID']}"/>
|
||||||
</div>
|
</div>
|
||||||
<h1>
|
<h1 id="artistname">
|
||||||
%if artist['Status'] == 'Loading':
|
<a href="http://musicbrainz.org/artist/${artist['ArtistID']}" id="artistnamelink">${artist['ArtistName']}</a>
|
||||||
<i class="fa fa-refresh fa-spin"></i>
|
|
||||||
%endif
|
|
||||||
<a href="http://musicbrainz.org/artist/${artist['ArtistID']}">${artist['ArtistName']}</a>
|
|
||||||
%if artist['Status'] == 'Loading':
|
|
||||||
<h3><i>(Album information for this artist is currently being loaded)</i></h3>
|
|
||||||
%endif
|
|
||||||
</h1>
|
</h1>
|
||||||
<div id="artistBio"></div>
|
<div id="artistBio"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,6 +56,7 @@
|
|||||||
<option value="Wanted">Wanted</option>
|
<option value="Wanted">Wanted</option>
|
||||||
<option value="WantedNew">Wanted (new only)</option>
|
<option value="WantedNew">Wanted (new only)</option>
|
||||||
<option value="Skipped">Skipped</option>
|
<option value="Skipped">Skipped</option>
|
||||||
|
<option value="Ignored">Ignored</option>
|
||||||
<option value="Downloaded">Downloaded</option>
|
<option value="Downloaded">Downloaded</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" value="Go">
|
<input type="hidden" value="Go">
|
||||||
@@ -89,6 +84,8 @@
|
|||||||
grade = 'X'
|
grade = 'X'
|
||||||
elif album['Status'] == 'Snatched':
|
elif album['Status'] == 'Snatched':
|
||||||
grade = 'C'
|
grade = 'C'
|
||||||
|
elif album['Status'] == 'Ignored':
|
||||||
|
grade = 'I'
|
||||||
else:
|
else:
|
||||||
grade = 'A'
|
grade = 'A'
|
||||||
|
|
||||||
@@ -123,15 +120,15 @@
|
|||||||
%>
|
%>
|
||||||
<tr class="grade${grade}">
|
<tr class="grade${grade}">
|
||||||
<td id="select"><input type="checkbox" name="${album['AlbumID']}" class="checkbox" /></td>
|
<td id="select"><input type="checkbox" name="${album['AlbumID']}" class="checkbox" /></td>
|
||||||
<td id="albumart"><img class="albumArt" id="${album['AlbumID']}" src="artwork/thumbs/album/${album['AlbumID']}" height="64" width="64"></td>
|
<td id="albumart"><img class="albumArtnostretch" id="${album['AlbumID']}" src="artwork/thumbs/album/${album['AlbumID']}" height="64" width="64"></td>
|
||||||
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
||||||
<td id="reldate">${album['ReleaseDate']}</td>
|
<td id="reldate">${album['ReleaseDate']}</td>
|
||||||
<td id="type">${album['Type']}</td>
|
<td id="type">${album['Type']}</td>
|
||||||
<td id="status">${album['Status']}
|
<td id="status">${album['Status']}
|
||||||
%if album['Status'] == 'Skipped':
|
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
|
||||||
[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}',$(this),'table')" data-success="'${album['AlbumTitle']}' added to Wanted list">want</a>]
|
[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}',$(this),'table')" data-success="'${album['AlbumTitle']}' added to Wanted list">want</a>]
|
||||||
%elif (album['Status'] == 'Wanted' or album['Status'] == 'Wanted Lossless'):
|
%elif (album['Status'] == 'Wanted' or album['Status'] == 'Wanted Lossless'):
|
||||||
[<a href="#" onclick="doAjaxCall('unqueueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}',$(this),'table')" data-success="'${album['AlbumTitle']}' skipped">skip</a>]
|
[<a href="#" onclick="doAjaxCall('unqueueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}',$(this),'table')" data-success="'${album['AlbumTitle']}' skipped">skip</a>] [<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}', $(this),'table')" data-success="Trying to download'${album['AlbumTitle']}'" title="Search if available for download">search</a>]
|
||||||
%else:
|
%else:
|
||||||
[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}', $(this),'table')" data-success="Retrying the same version of '${album['AlbumTitle']}'" title="Retry the same download again">retry</a>][<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=True', $(this),'table')" title="Try a new download, skipping all previously tried nzbs" data-success="Downloading new version for '${album['AlbumTitle']}'" data-success="Looking for a new version of '${album['AlbumTitle']}'">new</a>]
|
[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}', $(this),'table')" data-success="Retrying the same version of '${album['AlbumTitle']}'" title="Retry the same download again">retry</a>][<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${album['AlbumID']}&ArtistID=${album['ArtistID']}&new=True', $(this),'table')" title="Try a new download, skipping all previously tried nzbs" data-success="Downloading new version for '${album['AlbumTitle']}'" data-success="Looking for a new version of '${album['AlbumTitle']}'">new</a>]
|
||||||
%endif
|
%endif
|
||||||
@@ -153,9 +150,6 @@
|
|||||||
|
|
||||||
<%def name="headIncludes()">
|
<%def name="headIncludes()">
|
||||||
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
|
<link rel="stylesheet" href="interfaces/default/css/data_table.css">
|
||||||
%if artist['Status'] == 'Loading':
|
|
||||||
<meta http-equiv="refresh" content="5">
|
|
||||||
%endif
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
<%def name="javascriptIncludes()">
|
||||||
@@ -168,7 +162,6 @@
|
|||||||
getInfo(elem,id,'artist');
|
getInfo(elem,id,'artist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<%
|
<%
|
||||||
if headphones.SONGKICK_FILTER_ENABLED:
|
if headphones.SONGKICK_FILTER_ENABLED:
|
||||||
songkick_filter_enabled = "true"
|
songkick_filter_enabled = "true"
|
||||||
@@ -193,15 +186,15 @@
|
|||||||
|
|
||||||
template = '<li><a target="_blank" href="URI"><span class="sk-name">NAME</span><span class="sk-location">LOC</span></a></li>';
|
template = '<li><a target="_blank" href="URI"><span class="sk-name">NAME</span><span class="sk-location">LOC</span></a></li>';
|
||||||
|
|
||||||
$.getJSON("http://api.songkick.com/api/3.0/artists/mbid:${artist['ArtistID']}/calendar.json?apikey=${headphones.SONGKICK_APIKEY}&jsoncallback=?",
|
$.getJSON("https://api.songkick.com/api/3.0/artists/mbid:${artist['ArtistID']}/calendar.json?apikey=${headphones.SONGKICK_APIKEY}&jsoncallback=?",
|
||||||
function(data){
|
function(data){
|
||||||
if (data['resultsPage'].totalEntries >= 1) {
|
if (data['resultsPage'].totalEntries >= 1) {
|
||||||
|
|
||||||
if( ${songkick_filter_enabled} ) {
|
if( ${songkick_filter_enabled} ) {
|
||||||
data.resultsPage.results.event = $.grep(data.resultsPage.results.event, function(element,index){
|
data.resultsPage.results.event = $.grep(data.resultsPage.results.event, function(element,index){
|
||||||
return element.venue.metroArea.id == ${songkick_location};
|
return element.venue.metroArea.id == ${songkick_location};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var tourDate;
|
var tourDate;
|
||||||
|
|
||||||
@@ -237,18 +230,49 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var loadingMessage = false;
|
||||||
|
var spinner_active = false;
|
||||||
|
var loadingtext_active = false;
|
||||||
|
|
||||||
|
function checkArtistStatus() {
|
||||||
|
$.getJSON("getArtistjson?ArtistID=${artist['ArtistID']}", function(data) {
|
||||||
|
if (data['Status'] == "Loading"){
|
||||||
|
refreshTable();
|
||||||
|
$('#artistnamelink').text(data["ArtistName"]);
|
||||||
|
if (loadingMessage == false){
|
||||||
|
$("#ajaxMsg").after( "<div id='ajaxMsg2' class='ajaxMsg'></div>" );
|
||||||
|
showArtistMsg("Getting artist information");
|
||||||
|
loadingMessage = true;
|
||||||
|
}
|
||||||
|
if (spinner_active == false){
|
||||||
|
$('#artistname').prepend('<i class="fa fa-refresh fa-spin" id="artistnamespinner"></i>')
|
||||||
|
spinner_active = true;
|
||||||
|
}
|
||||||
|
if (loadingtext_active == false){
|
||||||
|
$('#artistname').append('<h3 id="loadingtext"><i>(Album information for this artist is currently being loaded)</i></h3>')
|
||||||
|
loadingtext_active = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$('#artistnamespinner').remove()
|
||||||
|
$('#loadingtext').remove()
|
||||||
|
$('#ajaxMsg2').remove()
|
||||||
|
spinner_active = false
|
||||||
|
loadingtext_active = false
|
||||||
|
loadingMessage = false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initThisPage() {
|
function initThisPage() {
|
||||||
$('#menu_link_getextra').click(function() {
|
$('#menu_link_getextra').click(function(event) {
|
||||||
$('#dialog').dialog();
|
$('#dialog').dialog();
|
||||||
return false;
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
$('#menu_link_modifyextra').click(function() {
|
$('#menu_link_modifyextra').click(function() {
|
||||||
$('#dialog').dialog();
|
$('#dialog').dialog();
|
||||||
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
%if artist['Status'] == 'Loading':
|
|
||||||
showMsg("Getting artist information",true);
|
|
||||||
%endif
|
|
||||||
$('#album_table').dataTable({
|
$('#album_table').dataTable({
|
||||||
"bDestroy": true,
|
"bDestroy": true,
|
||||||
"aoColumns": [
|
"aoColumns": [
|
||||||
@@ -265,6 +289,7 @@
|
|||||||
"aoColumnDefs": [
|
"aoColumnDefs": [
|
||||||
{ 'bSortable': false, 'aTargets': [ 0,1 ] }
|
{ 'bSortable': false, 'aTargets': [ 0,1 ] }
|
||||||
],
|
],
|
||||||
|
"bStateSave": true,
|
||||||
"oLanguage": {
|
"oLanguage": {
|
||||||
"sLengthMenu":"Show _MENU_ albums per page",
|
"sLengthMenu":"Show _MENU_ albums per page",
|
||||||
"sEmptyTable": "No album information available",
|
"sEmptyTable": "No album information available",
|
||||||
@@ -279,7 +304,7 @@
|
|||||||
resetFilters("albums");
|
resetFilters("albums");
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
initFancybox();
|
initFancybox();
|
||||||
},1500)
|
},1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@@ -287,8 +312,12 @@
|
|||||||
initThisPage();
|
initThisPage();
|
||||||
getArtistBio();
|
getArtistBio();
|
||||||
if( ${songkick_enabled} ){
|
if( ${songkick_enabled} ){
|
||||||
getArtistsCalendar();
|
getArtistsCalendar();
|
||||||
}
|
}
|
||||||
|
checkArtistStatus();
|
||||||
|
setInterval(function(){
|
||||||
|
checkArtistStatus();
|
||||||
|
}, 1500);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
from headphones import version
|
from headphones import version
|
||||||
%>
|
%>
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
|
|
||||||
<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
|
<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
|
||||||
<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
|
<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
|
||||||
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
|
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
|
||||||
@@ -20,26 +19,28 @@
|
|||||||
|
|
||||||
<link rel="shortcut icon" href="images/favicon.ico">
|
<link rel="shortcut icon" href="images/favicon.ico">
|
||||||
<link rel="apple-touch-icon" href="images/headphoneslogo.png">
|
<link rel="apple-touch-icon" href="images/headphoneslogo.png">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/jquery-ui.min.css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="interfaces/default/css/style.css">
|
<link rel="stylesheet" href="interfaces/default/css/style.css">
|
||||||
<link rel="stylesheet" href="interfaces/default/css/jquery-ui.css">
|
|
||||||
<link rel="stylesheet" href="interfaces/default/css/font-awesome.min.css">
|
<link rel="stylesheet" href="interfaces/default/css/font-awesome.min.css">
|
||||||
${next.headIncludes()}
|
${next.headIncludes()}
|
||||||
|
|
||||||
<script src="js/libs/modernizr-1.7.min.js"></script>
|
<script src="js/libs/modernizr-2.8.3.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="ajaxMsg"></div>
|
<div id="ajaxMsg" class="ajaxMsg"></div>
|
||||||
% if not headphones.CURRENT_VERSION:
|
% if not headphones.CURRENT_VERSION:
|
||||||
<div id="updatebar">
|
<div id="updatebar">
|
||||||
You're running an unknown version of Headphones. <a href="update">Update</a> or
|
You're running an unknown version of Headphones. <a href="update">Update</a> or
|
||||||
<a href="#" onclick="$('#updatebar').slideUp('slow');">Close</a>
|
<a href="#" onclick="$('#updatebar').slideUp('slow');">Close</a>
|
||||||
</div>
|
</div>
|
||||||
% elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.INSTALL_TYPE != 'win':
|
% elif headphones.CURRENT_VERSION != headphones.LATEST_VERSION and headphones.COMMITS_BEHIND > 0 and headphones.INSTALL_TYPE != 'win':
|
||||||
<div id="updatebar">
|
<div id="updatebar">
|
||||||
A <a href="https://github.com/${headphones.GIT_USER}/headphones/compare/${headphones.CURRENT_VERSION}...${headphones.LATEST_VERSION}"> newer version</a> is available. You're ${headphones.COMMITS_BEHIND} commits behind. <a href="update">Update</a> or <a href="#" onclick="$('#updatebar').slideUp('slow');">Close</a>
|
A <a href="https://github.com/${headphones.GIT_USER}/headphones/compare/${headphones.CURRENT_VERSION}...${headphones.LATEST_VERSION}"> newer version</a> is available. You're ${headphones.COMMITS_BEHIND} commits behind. <a href="update">Update</a> or <a href="#" onclick="$('#updatebar').slideUp('slow');">Close</a>
|
||||||
</div>
|
</div>
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
@@ -55,16 +56,16 @@
|
|||||||
<li><a href="config" class="config"><i class="fa fa-gear fa-lg"></i></a></li>
|
<li><a href="config" class="config"><i class="fa fa-gear fa-lg"></i></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div id="searchbar">
|
<div id="searchbar">
|
||||||
<form action="search" method="get">
|
<form action="search" method="get">
|
||||||
<input type="text" value="" placeholder="Search" onfocus="if(this.value==this.defaultValue) this.value='';" name="name" />
|
<input type="text" value="" placeholder="Search" onfocus="if(this.value==this.defaultValue) this.value='';" name="name" />
|
||||||
<i class='fa fa-search mini-icon'></i>
|
<i class='fa fa-search mini-icon'></i>
|
||||||
<select name="type">
|
<select name="type" id="search_type">
|
||||||
<option value="artist">Artist</option>
|
<option value="artist">Artist</option>
|
||||||
<option value="album">Album</option>
|
<option value="album">Album</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="submit" value="Add"/>
|
<input type="submit" value="Search"/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -77,6 +78,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
|
<div id="info">
|
||||||
|
<small>
|
||||||
|
<a href="https://github.com/rembo10/headphones"><i class="fa fa-headphones"></i> Website</a> |
|
||||||
|
<a href="https://github.com/rembo10/headphones/wiki/TroubleShooting"><i class="fa fa-ambulance"></i> Help</a>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
<div id="actions">
|
<div id="actions">
|
||||||
<small>
|
<small>
|
||||||
<a href="shutdown"><i class="fa fa-power-off"></i> Shutdown</a> |
|
<a href="shutdown"><i class="fa fa-power-off"></i> Shutdown</a> |
|
||||||
@@ -89,23 +96,44 @@
|
|||||||
%if version.HEADPHONES_VERSION != 'master':
|
%if version.HEADPHONES_VERSION != 'master':
|
||||||
(${version.HEADPHONES_VERSION})
|
(${version.HEADPHONES_VERSION})
|
||||||
%endif
|
%endif
|
||||||
|
%if headphones.GIT_BRANCH != 'master':
|
||||||
|
(${headphones.GIT_BRANCH})
|
||||||
|
%endif
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<a href="#main" id="toTop"><i class="fa fa-angle-double-up"></i> <span>Back to top</span></a>
|
<a href="#main" id="toTop"><i class="fa fa-angle-double-up"></i> <span>Back to top</span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/libs/jquery-1.7.2.min.js"></script>
|
<script src="js/libs/jquery-1.11.1.min.js"></script>
|
||||||
<script src="js/libs/jquery-ui.min.js"></script>
|
<script src="js/libs/jquery-ui.min.js"></script>
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
|
||||||
${next.javascriptIncludes()}
|
${next.javascriptIncludes()}
|
||||||
|
|
||||||
<script src="js/plugins.js"></script>
|
|
||||||
<script src="interfaces/default/js/script.js"></script>
|
<script src="interfaces/default/js/script.js"></script>
|
||||||
<!--[if lt IE 7 ]>
|
|
||||||
<script src="js/libs/dd_belatedpng.js"></script>
|
|
||||||
<script> DD_belatedPNG.fix('img, .png_bg');</script>
|
|
||||||
<![endif]-->
|
|
||||||
<!-- This template is made by Elmar Kouwenhoven -->
|
<!-- This template is made by Elmar Kouwenhoven -->
|
||||||
|
|
||||||
|
<!-- Persist search type using local storage -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('form:first *:input[type!=hidden]:first').focus();
|
||||||
|
try{
|
||||||
|
var type = window.localStorage.getItem('search_type');
|
||||||
|
$("#search_type").val(type);
|
||||||
|
} catch(e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('select[id=search_type]').change(function() {
|
||||||
|
var type = $(this).val()
|
||||||
|
try{
|
||||||
|
window.localStorage.setItem('search_type', type);
|
||||||
|
} catch(e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,14 @@ table.display tr.even.gradeA {
|
|||||||
background-color: #ddffdd;
|
background-color: #ddffdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.display tr.odd.gradeI {
|
||||||
|
background-color: #bebebe;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.display tr.even.gradeI {
|
||||||
|
background-color: #bebebe;
|
||||||
|
}
|
||||||
|
|
||||||
table.display tr.odd.gradeC {
|
table.display tr.odd.gradeC {
|
||||||
background-color: #ebf5ff;
|
background-color: #ebf5ff;
|
||||||
}
|
}
|
||||||
@@ -254,6 +262,13 @@ table.display tr.even.gradeU {
|
|||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.display tr.odd.gradeW {
|
||||||
|
background-color: #ffffaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.display tr.even.gradeW {
|
||||||
|
background-color: #ffffaa;
|
||||||
|
}
|
||||||
|
|
||||||
table.display tr.odd.gradeZ {
|
table.display tr.odd.gradeZ {
|
||||||
background-color: #FAFAFA;
|
background-color: #FAFAFA;
|
||||||
@@ -269,6 +284,7 @@ table.display tr.gradeL #status {
|
|||||||
}
|
}
|
||||||
table.display tr.gradeA td,
|
table.display tr.gradeA td,
|
||||||
table.display tr.gradeC td,
|
table.display tr.gradeC td,
|
||||||
|
table.display tr.gradeI td,
|
||||||
table.display tr.gradeX td,
|
table.display tr.gradeX td,
|
||||||
table.display tr.gradeU td,
|
table.display tr.gradeU td,
|
||||||
table.display tr.gradeZ td {border-bottom: 1px solid #FFF;}
|
table.display tr.gradeZ td {border-bottom: 1px solid #FFF;}
|
||||||
|
|||||||
@@ -1,547 +0,0 @@
|
|||||||
/*!
|
|
||||||
* jQuery UI CSS Framework 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Theming/API
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Layout helpers
|
|
||||||
----------------------------------*/
|
|
||||||
.ui-helper-hidden { display: none; }
|
|
||||||
.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
|
|
||||||
.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
|
|
||||||
.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
|
|
||||||
.ui-helper-clearfix:after { clear: both; }
|
|
||||||
.ui-helper-clearfix { zoom: 1; }
|
|
||||||
.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
|
|
||||||
|
|
||||||
|
|
||||||
/* Interaction Cues
|
|
||||||
----------------------------------*/
|
|
||||||
.ui-state-disabled { cursor: default !important; }
|
|
||||||
|
|
||||||
|
|
||||||
/* Icons
|
|
||||||
----------------------------------*/
|
|
||||||
|
|
||||||
/* states and images */
|
|
||||||
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
|
|
||||||
|
|
||||||
|
|
||||||
/* Misc visuals
|
|
||||||
----------------------------------*/
|
|
||||||
|
|
||||||
/* Overlays */
|
|
||||||
.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
|
||||||
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* jQuery UI CSS Framework 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Theming/API
|
|
||||||
*
|
|
||||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS,%20Helvetica,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=6px&bgColorHeader=dddddd&bgTextureHeader=02_glass.png&bgImgOpacityHeader=35&borderColorHeader=bbbbbb&fcHeader=444444&iconColorHeader=999999&bgColorContent=c9c9c9&bgTextureContent=05_inset_soft.png&bgImgOpacityContent=50&borderColorContent=aaaaaa&fcContent=333333&iconColorContent=999999&bgColorDefault=eeeeee&bgTextureDefault=02_glass.png&bgImgOpacityDefault=60&borderColorDefault=cccccc&fcDefault=3383bb&iconColorDefault=70b2e1&bgColorHover=f8f8f8&bgTextureHover=02_glass.png&bgImgOpacityHover=100&borderColorHover=bbbbbb&fcHover=599fcf&iconColorHover=3383bb&bgColorActive=999999&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=75&borderColorActive=999999&fcActive=ffffff&iconColorActive=454545&bgColorHighlight=eeeeee&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=ffffff&fcHighlight=444444&iconColorHighlight=3383bb&bgColorError=c0402a&bgTextureError=01_flat.png&bgImgOpacityError=55&borderColorError=c0402a&fcError=ffffff&iconColorError=fbc856&bgColorOverlay=eeeeee&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=80&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=60&thicknessShadow=4px&offsetTopShadow=-4px&offsetLeftShadow=-4px&cornerRadiusShadow=0pxdow=0px
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/* Component containers
|
|
||||||
----------------------------------*/
|
|
||||||
.ui-widget { font-family: Trebuchet MS, Helvetica, Arial, sans-serif; font-size: 1.1em; }
|
|
||||||
.ui-widget .ui-widget { font-size: 1em; }
|
|
||||||
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Helvetica, Arial, sans-serif; font-size: 1em; }
|
|
||||||
.ui-widget-content { border: 1px solid #aaaaaa; background: #f2f2f2; color: #333333; }
|
|
||||||
.ui-widget-content a { /*color: #333333;*/ }
|
|
||||||
.ui-widget-header { border: 1px solid #bbbbbb; background: #dddddd url(../images/ui-bg_glass_35_dddddd_1x400.png) 50% 50% repeat-x; color: #444444; font-weight: bold; }
|
|
||||||
.ui-widget-header a { color: #444444; }
|
|
||||||
|
|
||||||
/* Interaction states
|
|
||||||
----------------------------------*/
|
|
||||||
.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #cccccc; background: #eeeeee url(../images/ui-bg_glass_60_eeeeee_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #3383bb; }
|
|
||||||
.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #3383bb; text-decoration: none; }
|
|
||||||
.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #bbbbbb; background: #f8f8f8 url(../images/ui-bg_glass_100_f8f8f8_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #599fcf; }
|
|
||||||
.ui-state-hover a, .ui-state-hover a:hover { color: #599fcf; text-decoration: none; }
|
|
||||||
.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #999999; background: #ccc; font-weight: bold; color: #ffffff; }
|
|
||||||
.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ffffff; text-decoration: none; }
|
|
||||||
.ui-widget :active { outline: none; }
|
|
||||||
|
|
||||||
/* Interaction Cues
|
|
||||||
----------------------------------*/
|
|
||||||
.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #ffffff; background: #eeeeee url(../images/ui-bg_flat_55_eeeeee_40x100.png) 50% 50% repeat-x; color: #444444; }
|
|
||||||
.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #444444; }
|
|
||||||
.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #c0402a; background: #c0402a url(../images/ui-bg_flat_55_c0402a_40x100.png) 50% 50% repeat-x; color: #ffffff; }
|
|
||||||
.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
|
|
||||||
.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
|
|
||||||
.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
|
|
||||||
.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
|
|
||||||
.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
|
|
||||||
|
|
||||||
/* Icons
|
|
||||||
----------------------------------*/
|
|
||||||
|
|
||||||
/* states and images */
|
|
||||||
.ui-icon { width: 16px; height: 16px; background-image: url(../images/icon_sprite_black.png); }
|
|
||||||
.ui-widget-content .ui-icon {background-image: url(../images/icon_sprite_black.png); }
|
|
||||||
.ui-widget-header .ui-icon {background-image: url(../images/icon_sprite_black.png); }
|
|
||||||
.ui-button.ui-state-hover .ui-icon { background-image: url(../images/icon_sprite_white.png); }
|
|
||||||
.ui-state-default .ui-icon { background-image: url(../images/ui-icons_70b2e1_256x240.png); }
|
|
||||||
.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(../images/ui-icons_3383bb_256x240.png); }
|
|
||||||
.ui-state-active .ui-icon {background-image: url(../images/ui-icons_454545_256x240.png); }
|
|
||||||
.ui-state-highlight .ui-icon {background-image: url(../images/ui-icons_3383bb_256x240.png); }
|
|
||||||
.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(../images/ui-icons_fbc856_256x240.png); }
|
|
||||||
|
|
||||||
/* positioning */
|
|
||||||
.ui-icon-carat-1-n { background-position: 0 0; }
|
|
||||||
.ui-icon-carat-1-ne { background-position: -16px 0; }
|
|
||||||
.ui-icon-carat-1-e { background-position: -32px 0; }
|
|
||||||
.ui-icon-carat-1-se { background-position: -48px 0; }
|
|
||||||
.ui-icon-carat-1-s { background-position: -64px 0; }
|
|
||||||
.ui-icon-carat-1-sw { background-position: -80px 0; }
|
|
||||||
.ui-icon-carat-1-w { background-position: -96px 0; }
|
|
||||||
.ui-icon-carat-1-nw { background-position: -112px 0; }
|
|
||||||
.ui-icon-carat-2-n-s { background-position: -128px 0; }
|
|
||||||
.ui-icon-carat-2-e-w { background-position: -144px 0; }
|
|
||||||
.ui-icon-triangle-1-n { background-position: 0 -16px; }
|
|
||||||
.ui-icon-triangle-1-ne { background-position: -16px -16px; }
|
|
||||||
.ui-icon-triangle-1-e { background-position: -32px -16px; }
|
|
||||||
.ui-icon-triangle-1-se { background-position: -48px -16px; }
|
|
||||||
.ui-icon-triangle-1-s { background-position: -64px -16px; }
|
|
||||||
.ui-icon-triangle-1-sw { background-position: -80px -16px; }
|
|
||||||
.ui-icon-triangle-1-w { background-position: -96px -16px; }
|
|
||||||
.ui-icon-triangle-1-nw { background-position: -112px -16px; }
|
|
||||||
.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
|
|
||||||
.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
|
|
||||||
.ui-icon-arrow-1-n { background-position: 0 -32px; }
|
|
||||||
.ui-icon-arrow-1-ne { background-position: -16px -32px; }
|
|
||||||
.ui-icon-arrow-1-e { background-position: -32px -32px; }
|
|
||||||
.ui-icon-arrow-1-se { background-position: -48px -32px; }
|
|
||||||
.ui-icon-arrow-1-s { background-position: -64px -32px; }
|
|
||||||
.ui-icon-arrow-1-sw { background-position: -80px -32px; }
|
|
||||||
.ui-icon-arrow-1-w { background-position: -96px -32px; }
|
|
||||||
.ui-icon-arrow-1-nw { background-position: -112px -32px; }
|
|
||||||
.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
|
|
||||||
.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
|
|
||||||
.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
|
|
||||||
.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
|
|
||||||
.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
|
|
||||||
.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
|
|
||||||
.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
|
|
||||||
.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
|
|
||||||
.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
|
|
||||||
.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
|
|
||||||
.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
|
|
||||||
.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
|
|
||||||
.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
|
|
||||||
.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
|
|
||||||
.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
|
|
||||||
.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
|
|
||||||
.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
|
|
||||||
.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
|
|
||||||
.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
|
|
||||||
.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
|
|
||||||
.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
|
|
||||||
.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
|
|
||||||
.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
|
|
||||||
.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
|
|
||||||
.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
|
|
||||||
.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
|
|
||||||
.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
|
|
||||||
.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
|
|
||||||
.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
|
|
||||||
.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
|
|
||||||
.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
|
|
||||||
.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
|
|
||||||
.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
|
|
||||||
.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
|
|
||||||
.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
|
|
||||||
.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
|
|
||||||
.ui-icon-arrow-4 { background-position: 0 -80px; }
|
|
||||||
.ui-icon-arrow-4-diag { background-position: -16px -80px; }
|
|
||||||
.ui-icon-extlink { background-position: -32px -80px; }
|
|
||||||
.ui-icon-newwin { background-position: -48px -80px; }
|
|
||||||
.ui-icon-refresh { background-position: -64px -80px; }
|
|
||||||
.ui-icon-shuffle { background-position: -80px -80px; }
|
|
||||||
.ui-icon-transfer-e-w { background-position: -96px -80px; }
|
|
||||||
.ui-icon-transferthick-e-w { background-position: -112px -80px; }
|
|
||||||
.ui-icon-folder-collapsed { background-position: 0 -96px; }
|
|
||||||
.ui-icon-folder-open { background-position: -16px -96px; }
|
|
||||||
.ui-icon-document { background-position: -32px -96px; }
|
|
||||||
.ui-icon-document-b { background-position: -48px -96px; }
|
|
||||||
.ui-icon-note { background-position: -64px -96px; }
|
|
||||||
.ui-icon-mail-closed { background-position: -80px -96px; }
|
|
||||||
.ui-icon-mail-open { background-position: -96px -96px; }
|
|
||||||
.ui-icon-suitcase { background-position: -112px -96px; }
|
|
||||||
.ui-icon-comment { background-position: -128px -96px; }
|
|
||||||
.ui-icon-person { background-position: -144px -96px; }
|
|
||||||
.ui-icon-print { background-position: -160px -96px; }
|
|
||||||
.ui-icon-trash { background-position: -176px -96px; }
|
|
||||||
.ui-icon-locked { background-position: -192px -96px; }
|
|
||||||
.ui-icon-unlocked { background-position: -208px -96px; }
|
|
||||||
.ui-icon-bookmark { background-position: -224px -96px; }
|
|
||||||
.ui-icon-tag { background-position: -240px -96px; }
|
|
||||||
.ui-icon-home { background-position: 0 -112px; }
|
|
||||||
.ui-icon-flag { background-position: -16px -112px; }
|
|
||||||
.ui-icon-calendar { background-position: -32px -112px; }
|
|
||||||
.ui-icon-cart { background-position: -48px -112px; }
|
|
||||||
.ui-icon-pencil { background-position: -64px -112px; }
|
|
||||||
.ui-icon-clock { background-position: -80px -112px; }
|
|
||||||
.ui-icon-disk { background-position: -96px -112px; }
|
|
||||||
.ui-icon-calculator { background-position: -112px -112px; }
|
|
||||||
.ui-icon-zoomin { background-position: -128px -112px; }
|
|
||||||
.ui-icon-zoomout { background-position: -144px -112px; }
|
|
||||||
.ui-icon-search { background-position: -160px -112px; }
|
|
||||||
.ui-icon-wrench { background-position: -176px -112px; }
|
|
||||||
.ui-icon-gear { background-position: -192px -112px; }
|
|
||||||
.ui-icon-heart { background-position: -208px -112px; }
|
|
||||||
.ui-icon-star { background-position: -224px -112px; }
|
|
||||||
.ui-icon-link { background-position: -240px -112px; }
|
|
||||||
.ui-icon-cancel { background-position: 0 -128px; }
|
|
||||||
.ui-icon-plus { background-position: -16px -128px; }
|
|
||||||
.ui-icon-plusthick { background-position: -32px -128px; }
|
|
||||||
.ui-icon-minus { background-position: -48px -128px; }
|
|
||||||
.ui-icon-minusthick { background-position: -64px -128px; }
|
|
||||||
.ui-icon-close { background-position: -80px -128px; }
|
|
||||||
.ui-icon-closethick { background-position: -96px -128px; }
|
|
||||||
.ui-icon-key { background-position: -112px -128px; }
|
|
||||||
.ui-icon-lightbulb { background-position: -128px -128px; }
|
|
||||||
.ui-icon-scissors { background-position: -144px -128px; }
|
|
||||||
.ui-icon-clipboard { background-position: -160px -128px; }
|
|
||||||
.ui-icon-copy { background-position: -176px -128px; }
|
|
||||||
.ui-icon-contact { background-position: -192px -128px; }
|
|
||||||
.ui-icon-image { background-position: -208px -128px; }
|
|
||||||
.ui-icon-video { background-position: -224px -128px; }
|
|
||||||
.ui-icon-script { background-position: -240px -128px; }
|
|
||||||
.ui-icon-alert { background-position: 0 -144px; }
|
|
||||||
.ui-icon-info { background-position: -16px -144px; }
|
|
||||||
.ui-icon-notice { background-position: -32px -144px; }
|
|
||||||
.ui-icon-help { background-position: -48px -144px; }
|
|
||||||
.ui-icon-check { background-position: -64px -144px; }
|
|
||||||
.ui-icon-bullet { background-position: -80px -144px; }
|
|
||||||
.ui-icon-radio-off { background-position: -96px -144px; }
|
|
||||||
.ui-icon-radio-on { background-position: -112px -144px; }
|
|
||||||
.ui-icon-pin-w { background-position: -128px -144px; }
|
|
||||||
.ui-icon-pin-s { background-position: -144px -144px; }
|
|
||||||
.ui-icon-play { background-position: 0 -160px; }
|
|
||||||
.ui-icon-pause { background-position: -16px -160px; }
|
|
||||||
.ui-icon-seek-next { background-position: -32px -160px; }
|
|
||||||
.ui-icon-seek-prev { background-position: -48px -160px; }
|
|
||||||
.ui-icon-seek-end { background-position: -64px -160px; }
|
|
||||||
.ui-icon-seek-start { background-position: -80px -160px; }
|
|
||||||
/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
|
|
||||||
.ui-icon-seek-first { background-position: -80px -160px; }
|
|
||||||
.ui-icon-stop { background-position: -96px -160px; }
|
|
||||||
.ui-icon-eject { background-position: -112px -160px; }
|
|
||||||
.ui-icon-volume-off { background-position: -128px -160px; }
|
|
||||||
.ui-icon-volume-on { background-position: -144px -160px; }
|
|
||||||
.ui-icon-power { background-position: 0 -176px; }
|
|
||||||
.ui-icon-signal-diag { background-position: -16px -176px; }
|
|
||||||
.ui-icon-signal { background-position: -32px -176px; }
|
|
||||||
.ui-icon-battery-0 { background-position: -48px -176px; }
|
|
||||||
.ui-icon-battery-1 { background-position: -64px -176px; }
|
|
||||||
.ui-icon-battery-2 { background-position: -80px -176px; }
|
|
||||||
.ui-icon-battery-3 { background-position: -96px -176px; }
|
|
||||||
.ui-icon-circle-plus { background-position: 0 -192px; }
|
|
||||||
.ui-icon-circle-minus { background-position: -16px -192px; }
|
|
||||||
.ui-icon-circle-close { background-position: -32px -192px; }
|
|
||||||
.ui-icon-circle-triangle-e { background-position: -48px -192px; }
|
|
||||||
.ui-icon-circle-triangle-s { background-position: -64px -192px; }
|
|
||||||
.ui-icon-circle-triangle-w { background-position: -80px -192px; }
|
|
||||||
.ui-icon-circle-triangle-n { background-position: -96px -192px; }
|
|
||||||
.ui-icon-circle-arrow-e { background-position: -112px -192px; }
|
|
||||||
.ui-icon-circle-arrow-s { background-position: -128px -192px; }
|
|
||||||
.ui-icon-circle-arrow-w { background-position: -144px -192px; }
|
|
||||||
.ui-icon-circle-arrow-n { background-position: -160px -192px; }
|
|
||||||
.ui-icon-circle-zoomin { background-position: -176px -192px; }
|
|
||||||
.ui-icon-circle-zoomout { background-position: -192px -192px; }
|
|
||||||
.ui-icon-circle-check { background-position: -208px -192px; }
|
|
||||||
.ui-icon-circlesmall-plus { background-position: 0 -208px; }
|
|
||||||
.ui-icon-circlesmall-minus { background-position: -16px -208px; }
|
|
||||||
.ui-icon-circlesmall-close { background-position: -32px -208px; }
|
|
||||||
.ui-icon-squaresmall-plus { background-position: -48px -208px; }
|
|
||||||
.ui-icon-squaresmall-minus { background-position: -64px -208px; }
|
|
||||||
.ui-icon-squaresmall-close { background-position: -80px -208px; }
|
|
||||||
.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
|
|
||||||
.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
|
|
||||||
.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
|
|
||||||
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
|
|
||||||
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
|
|
||||||
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
|
|
||||||
|
|
||||||
|
|
||||||
/* Misc visuals
|
|
||||||
----------------------------------*/
|
|
||||||
|
|
||||||
/* Corner radius */
|
|
||||||
.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 6px; -webkit-border-top-left-radius: 6px; -khtml-border-top-left-radius: 6px; border-top-left-radius: 6px; }
|
|
||||||
.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 6px; -webkit-border-top-right-radius: 6px; -khtml-border-top-right-radius: 6px; border-top-right-radius: 6px; }
|
|
||||||
.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 6px; -webkit-border-bottom-left-radius: 6px; -khtml-border-bottom-left-radius: 6px; border-bottom-left-radius: 6px; }
|
|
||||||
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 6px; -webkit-border-bottom-right-radius: 6px; -khtml-border-bottom-right-radius: 6px; border-bottom-right-radius: 6px; }
|
|
||||||
|
|
||||||
/* Overlays */
|
|
||||||
.ui-widget-overlay { background: #eeeeee url(../images/ui-bg_flat_0_eeeeee_40x100.png) 50% 50% repeat-x; opacity: .80;filter:Alpha(Opacity=80); }
|
|
||||||
.ui-widget-shadow { margin: -4px 0 0 -4px; padding: 4px; background: #aaaaaa url(../images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .60;filter:Alpha(Opacity=60); -moz-border-radius: 0pxdow=0px; -khtml-border-radius: 0pxdow=0px; -webkit-border-radius: 0pxdow=0px; border-radius: 0pxdow=0px; }/*!
|
|
||||||
* jQuery UI Resizable 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Resizable#theming
|
|
||||||
*/
|
|
||||||
.ui-resizable { position: relative;}
|
|
||||||
.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block; }
|
|
||||||
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
|
|
||||||
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
|
|
||||||
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
|
|
||||||
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
|
|
||||||
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
|
|
||||||
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
|
|
||||||
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
|
|
||||||
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
|
|
||||||
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*!
|
|
||||||
* jQuery UI Selectable 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Selectable#theming
|
|
||||||
*/
|
|
||||||
.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
|
|
||||||
/*!
|
|
||||||
* jQuery UI Accordion 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Accordion#theming
|
|
||||||
*/
|
|
||||||
/* IE/Win - Fix animation bug - #4615 */
|
|
||||||
.ui-accordion { width: 100%; }
|
|
||||||
.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
|
|
||||||
.ui-accordion .ui-accordion-li-fix { display: inline; }
|
|
||||||
.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
|
|
||||||
.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
|
|
||||||
.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
|
|
||||||
.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
|
|
||||||
.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
|
|
||||||
.ui-accordion .ui-accordion-content-active { display: block; }
|
|
||||||
/*!
|
|
||||||
* jQuery UI Autocomplete 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Autocomplete#theming
|
|
||||||
*/
|
|
||||||
.ui-autocomplete { position: absolute; cursor: default; }
|
|
||||||
|
|
||||||
/* workarounds */
|
|
||||||
* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* jQuery UI Menu @VERSION
|
|
||||||
*
|
|
||||||
* Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Menu#theming
|
|
||||||
*/
|
|
||||||
.ui-menu {
|
|
||||||
list-style:none;
|
|
||||||
padding: 2px;
|
|
||||||
margin: 0;
|
|
||||||
display:block;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.ui-menu .ui-menu {
|
|
||||||
margin-top: -3px;
|
|
||||||
}
|
|
||||||
.ui-menu .ui-menu-item {
|
|
||||||
margin:0;
|
|
||||||
padding: 0;
|
|
||||||
zoom: 1;
|
|
||||||
float: left;
|
|
||||||
clear: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.ui-menu .ui-menu-item a {
|
|
||||||
text-decoration:none;
|
|
||||||
display:block;
|
|
||||||
padding:.2em .4em;
|
|
||||||
line-height:1.5;
|
|
||||||
zoom:1;
|
|
||||||
}
|
|
||||||
.ui-menu .ui-menu-item a.ui-state-hover,
|
|
||||||
.ui-menu .ui-menu-item a.ui-state-active {
|
|
||||||
font-weight: normal;
|
|
||||||
margin: -1px;
|
|
||||||
}
|
|
||||||
/*!
|
|
||||||
* jQuery UI Button 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Button#theming
|
|
||||||
*/
|
|
||||||
.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
|
|
||||||
.ui-button:hover {
|
|
||||||
color:#3383BB;
|
|
||||||
}
|
|
||||||
.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
|
|
||||||
button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
|
|
||||||
.ui-button-icons-only { width: 3.4em; }
|
|
||||||
button.ui-button-icons-only { width: 3.7em; }
|
|
||||||
|
|
||||||
/*button text element */
|
|
||||||
.ui-button .ui-button-text { display: block; line-height: 1.4; }
|
|
||||||
.ui-button-text-only .ui-button-text { padding: .4em 1em; }
|
|
||||||
.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
|
|
||||||
.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: 0.2em 0.5em 0.2em 1.8em }
|
|
||||||
.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: 0.2em 1.8em 0.2em 0.5em }
|
|
||||||
.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
|
|
||||||
/* no icon support for input elements, provide padding by default */
|
|
||||||
input.ui-button { padding: .4em 1em; }
|
|
||||||
|
|
||||||
/*button icon element(s) */
|
|
||||||
.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
|
|
||||||
.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
|
|
||||||
.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .3em; }
|
|
||||||
.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
|
|
||||||
.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
|
|
||||||
|
|
||||||
/*button sets*/
|
|
||||||
.ui-buttonset { margin-right: 7px; }
|
|
||||||
.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
|
|
||||||
|
|
||||||
/* workarounds */
|
|
||||||
button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
|
|
||||||
/*!
|
|
||||||
* jQuery UI Dialog 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Dialog#theming
|
|
||||||
*/
|
|
||||||
.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
|
|
||||||
.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
|
|
||||||
.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
|
|
||||||
.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
|
|
||||||
.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
|
|
||||||
.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
|
|
||||||
.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
|
|
||||||
.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
|
|
||||||
.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
|
|
||||||
.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
|
|
||||||
.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
|
|
||||||
.ui-draggable .ui-dialog-titlebar { cursor: move; }
|
|
||||||
/*!
|
|
||||||
* jQuery UI Tabs 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Tabs#theming
|
|
||||||
*/
|
|
||||||
.ui-tabs { position: relative; padding: 0em; zoom: 1; background: transparent; border: none;} /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
|
|
||||||
.ui-tabs .ui-tabs-nav { margin: 0; padding: 0; background: none; border:none;}
|
|
||||||
.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap;background: #FFF; }
|
|
||||||
.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; font-weight: normal; }
|
|
||||||
.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; background-color: #f7f7f7; border-color: #CCC; }
|
|
||||||
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a { color: #454545; }
|
|
||||||
.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
|
|
||||||
.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
|
|
||||||
.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: #f7f7f7; border: 1px solid #CCC; }
|
|
||||||
.ui-tabs .ui-tabs-hide { display: none !important; }
|
|
||||||
/*!
|
|
||||||
* jQuery UI Datepicker 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Datepicker#theming
|
|
||||||
*/
|
|
||||||
.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
|
|
||||||
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
|
|
||||||
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
|
|
||||||
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
|
|
||||||
.ui-datepicker .ui-datepicker-prev { left:2px; }
|
|
||||||
.ui-datepicker .ui-datepicker-next { right:2px; }
|
|
||||||
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
|
|
||||||
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
|
|
||||||
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
|
|
||||||
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
|
|
||||||
.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
|
|
||||||
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
|
|
||||||
.ui-datepicker select.ui-datepicker-month,
|
|
||||||
.ui-datepicker select.ui-datepicker-year { width: 49%;}
|
|
||||||
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
|
|
||||||
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
|
|
||||||
.ui-datepicker td { border: 0; padding: 1px; }
|
|
||||||
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
|
|
||||||
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
|
|
||||||
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
|
|
||||||
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
|
|
||||||
|
|
||||||
/* with multiple calendars */
|
|
||||||
.ui-datepicker.ui-datepicker-multi { width:auto; }
|
|
||||||
.ui-datepicker-multi .ui-datepicker-group { float:left; }
|
|
||||||
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
|
|
||||||
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
|
|
||||||
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
|
|
||||||
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
|
|
||||||
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
|
|
||||||
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
|
|
||||||
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
|
|
||||||
.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
|
|
||||||
|
|
||||||
/* RTL support */
|
|
||||||
.ui-datepicker-rtl { direction: rtl; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
|
|
||||||
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
|
|
||||||
|
|
||||||
/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
|
|
||||||
.ui-datepicker-cover {
|
|
||||||
display: none; /*sorry for IE5*/
|
|
||||||
display/**/: block; /*sorry for IE5*/
|
|
||||||
position: absolute; /*must have*/
|
|
||||||
z-index: -1; /*must have*/
|
|
||||||
filter: mask(); /*must have*/
|
|
||||||
top: -4px; /*must have*/
|
|
||||||
left: -4px; /*must have*/
|
|
||||||
width: 200px; /*must have*/
|
|
||||||
height: 200px; /*must have*/
|
|
||||||
}/*!
|
|
||||||
* jQuery UI Progressbar 1.8.19
|
|
||||||
*
|
|
||||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
|
||||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
|
||||||
* http://jquery.org/license
|
|
||||||
*
|
|
||||||
* http://docs.jquery.com/UI/Progressbar#theming
|
|
||||||
*/
|
|
||||||
.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
|
|
||||||
.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
|
|
||||||
@@ -163,6 +163,14 @@ img.albumArt {
|
|||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
img.albumArt-nostretch {
|
||||||
|
float: left;
|
||||||
|
min-height: 64px;
|
||||||
|
min-width: 64px;
|
||||||
|
max-width: 250px;
|
||||||
|
max-height: 300px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@@ -393,6 +401,9 @@ form .checkbox small {
|
|||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
form .indent input {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
@@ -492,7 +503,7 @@ header .wrapper {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 960px;
|
width: 990px;
|
||||||
}
|
}
|
||||||
header #logo {
|
header #logo {
|
||||||
float: left;
|
float: left;
|
||||||
@@ -534,7 +545,7 @@ footer {
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
#ajaxMsg {
|
.ajaxMsg {
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important;
|
background-image: -moz-linear-gradient(#ffffff, #eeeeee) !important;
|
||||||
background-image: linear-gradient(#ffffff, #eeeeee) !important;
|
background-image: linear-gradient(#ffffff, #eeeeee) !important;
|
||||||
@@ -565,16 +576,16 @@ footer {
|
|||||||
-o-opacity: 0.8 !important;
|
-o-opacity: 0.8 !important;
|
||||||
opacity: 0.8 !important;
|
opacity: 0.8 !important;
|
||||||
}
|
}
|
||||||
#ajaxMsg .msg {
|
.ajaxMsg .msg {
|
||||||
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
font-family: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
#ajaxMsg .loader {
|
.ajaxMsg .loader {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
#ajaxMsg.success {
|
.ajaxMsg .success {
|
||||||
background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important;
|
background-image: -moz-linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||||
background-image: linear-gradient(#d3ffd7, #c2edc6) !important;
|
background-image: linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||||
background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important;
|
background-image: -webkit-linear-gradient(#d3ffd7, #c2edc6) !important;
|
||||||
@@ -584,7 +595,7 @@ footer {
|
|||||||
padding: 15px 10px;
|
padding: 15px 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#ajaxMsg.error {
|
.ajaxMsg .error {
|
||||||
background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important;
|
background-image: -moz-linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||||
background-image: linear-gradient(#ffd3d3, #edc4c4) !important;
|
background-image: linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||||
background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important;
|
background-image: -webkit-linear-gradient(#ffd3d3, #edc4c4) !important;
|
||||||
@@ -594,7 +605,7 @@ footer {
|
|||||||
padding: 15px 10px;
|
padding: 15px 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
#ajaxMsg .ui-icon {
|
.ajaxMsg .ui-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
@@ -688,7 +699,7 @@ footer {
|
|||||||
}
|
}
|
||||||
#subhead #subhead_container #subhead_menu {
|
#subhead #subhead_container #subhead_menu {
|
||||||
float: right;
|
float: right;
|
||||||
margin-top: 5px;
|
margin-top: 0px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
@@ -713,6 +724,11 @@ footer {
|
|||||||
color: #FFF;
|
color: #FFF;
|
||||||
border-color: #518CC6 #518CC6 #2A65A0;
|
border-color: #518CC6 #518CC6 #2A65A0;
|
||||||
}
|
}
|
||||||
|
#subhead #subhead_container #back_to_previous_link {
|
||||||
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
div#searchbar {
|
div#searchbar {
|
||||||
border-left: 1px solid #FAFAFA;
|
border-left: 1px solid #FAFAFA;
|
||||||
-moz-box-shadow: -1px 0 0 #e0e0e0;
|
-moz-box-shadow: -1px 0 0 #e0e0e0;
|
||||||
@@ -742,7 +758,7 @@ div#searchbar input[type=text] {
|
|||||||
line-height: normal;
|
line-height: normal;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
padding: 4px 5px 4px 25px;
|
padding: 4px 5px 4px 25px;
|
||||||
width: 150px;
|
width: 185px;
|
||||||
}
|
}
|
||||||
div#searchbar .mini-icon {
|
div#searchbar .mini-icon {
|
||||||
color: #999;
|
color: #999;
|
||||||
@@ -769,7 +785,7 @@ div#searchbar .mini-icon {
|
|||||||
_height: 302px;
|
_height: 302px;
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
clear: both;
|
clear: both;
|
||||||
margin: 30px auto 0;
|
margin: 60px auto 0;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -1051,6 +1067,22 @@ div#artistheader h2 a {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
#downloads_table th#title {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 500px;
|
||||||
|
}
|
||||||
|
#downloads_table th#size {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
#downloads_table th#provider {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
#downloads_table th#kind {
|
||||||
|
text-align: center;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
#history_table {
|
#history_table {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -1094,13 +1126,34 @@ div#artistheader h2 a {
|
|||||||
padding: 2px 10px;
|
padding: 2px 10px;
|
||||||
}
|
}
|
||||||
#searchresults_table th#albumname {
|
#searchresults_table th#albumname {
|
||||||
min-width: 225px;
|
min-width: 250px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
#searchresults_table th#artistname {
|
#searchresults_table th#artistname {
|
||||||
min-width: 325px;
|
min-width: 325px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
#searchresults_table th#artistnamesmall {
|
||||||
|
min-width: 125px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#searchresults_table th#reldate {
|
||||||
|
min-width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#searchresults_table th#format {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#searchresults_table th#tracks {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
#searchresults_table #artistImg {
|
#searchresults_table #artistImg {
|
||||||
background: url("../images/loader_black.gif") no-repeat scroll center center #ffffff;
|
background: url("../images/loader_black.gif") no-repeat scroll center center #ffffff;
|
||||||
border: 3px solid #FFFFFF;
|
border: 3px solid #FFFFFF;
|
||||||
@@ -1112,15 +1165,40 @@ div#artistheader h2 a {
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
}
|
}
|
||||||
#searchresults_table td#albumname {
|
#searchresults_table td#albumname {
|
||||||
min-width: 200px;
|
min-width: 250px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
#searchresults_table td#artistname {
|
#searchresults_table td#artistname {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
#searchresults_table td#artistnamesmall {
|
||||||
|
min-width: 100px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#searchresults_table td#reldate {
|
||||||
|
min-width: 80px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#searchresults_table td#format {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
#searchresults_table td#tracks {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
#searchresults_table td#add {
|
#searchresults_table td#add {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
@@ -1183,6 +1261,13 @@ div#artistheader h2 a {
|
|||||||
float: left;
|
float: left;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
#info {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: -5px;
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
#actions {
|
#actions {
|
||||||
float: right;
|
float: right;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -1212,6 +1297,7 @@ div#artistheader h2 a {
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 7px;
|
top: 7px;
|
||||||
}
|
}
|
||||||
|
#trashcan {margin-top:15px;}
|
||||||
.cloudtag {
|
.cloudtag {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
@@ -1321,6 +1407,15 @@ div#artistheader h2 a {
|
|||||||
.clearfix:after {
|
.clearfix:after {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
.override-float {
|
||||||
|
float: none !important;
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
clear: none !important;
|
||||||
|
display: inline !important;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
#album_table th#albumname,
|
#album_table th#albumname,
|
||||||
#upcoming_table th#artistname,
|
#upcoming_table th#artistname,
|
||||||
#wanted_table th#artistname {
|
#wanted_table th#artistname {
|
||||||
@@ -1374,6 +1469,11 @@ div#artistheader h3 span {
|
|||||||
min-width: 75px;
|
min-width: 75px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
#searchresults_table th#scoresmall {
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
#track_table td#bitrate,
|
#track_table td#bitrate,
|
||||||
#track_table td#format {
|
#track_table td#format {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
@@ -938,6 +938,13 @@ div#artistheader {
|
|||||||
float: left;
|
float: left;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
#info {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: -5px;
|
||||||
|
color: @border-color;
|
||||||
|
}
|
||||||
#actions {
|
#actions {
|
||||||
float: right;
|
float: right;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<%inherit file="base.html"/>
|
<%inherit file="base.html"/>
|
||||||
<%!
|
<%!
|
||||||
from headphones import helpers
|
from headphones import helpers
|
||||||
|
import cgi
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%def name="headerIncludes()">
|
<%def name="headerIncludes()">
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
<th id="size">Size</th>
|
<th id="size">Size</th>
|
||||||
<th id="status">Status</th>
|
<th id="status">Status</th>
|
||||||
<th id="action"></th>
|
<th id="action"></th>
|
||||||
|
<th id="delete"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -50,10 +52,11 @@
|
|||||||
%>
|
%>
|
||||||
<tr class="grade${grade}">
|
<tr class="grade${grade}">
|
||||||
<td id="dateadded">${item['DateAdded']}</td>
|
<td id="dateadded">${item['DateAdded']}</td>
|
||||||
<td id="filename">${item['Title']} [<a href="${item['URL']}">${fileid}</a>]<a href="albumPage?AlbumID=${item['AlbumID']}">[album page]</a></td>
|
<td id="filename">${cgi.escape(item['Title'], quote=True)} [<a href="${item['URL']}">${fileid}</a>]<a href="albumPage?AlbumID=${item['AlbumID']}">[album page]</a></td>
|
||||||
<td id="size">${helpers.bytes_to_mb(item['Size'])}</td>
|
<td id="size">${helpers.bytes_to_mb(item['Size'])}</td>
|
||||||
<td id="status">${item['Status']}</td>
|
<td id="status">${item['Status']}</td>
|
||||||
<td id="action">[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${item['AlbumID']}&redirect=history', $(this),'table')" data-success="Retrying download of '${item['Title']}'">retry</a>][<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${item['AlbumID']}&new=True&redirect=history',$(this),'table')" data-success="Looking for a new version of '${item['Title']}'">new</a>]</td>
|
<td id="action">[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${item['AlbumID']}&redirect=history', $(this),'table')" data-success="Retrying download of '${cgi.escape(item['Title'], quote=True)}'">retry</a>][<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${item['AlbumID']}&new=True&redirect=history',$(this),'table')" data-success="Looking for a new version of '${cgi.escape(item['Title'], quote=True)}'">new</a>]</td>
|
||||||
|
<td id="delete"><a href="#" onclick="doAjaxCall('clearhistory?date_added=${item['DateAdded']}&title=${cgi.escape(item['Title'], quote=True)}',$(this),'table')" data-success="${cgi.escape(item['Title'], quote=True)} cleared from history"><img src="interfaces/default/images/trashcan.png" height="18" width="18" id="trashcan" title="Clear this item from the history"></a>
|
||||||
</tr>
|
</tr>
|
||||||
%endfor
|
%endfor
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 182 B |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 129 B |
|
Before Width: | Height: | Size: 109 B |
|
Before Width: | Height: | Size: 110 B |
|
Before Width: | Height: | Size: 114 B |
|
Before Width: | Height: | Size: 96 B |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -24,129 +24,128 @@
|
|||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
<%def name="javascriptIncludes()">
|
||||||
|
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function initThisPage() {
|
function initThisPage() {
|
||||||
$('#artist_table').dataTable({
|
$('#artist_table').dataTable({
|
||||||
"bDestroy": true,
|
"bDestroy": true,
|
||||||
"aoColumnDefs": [
|
"aoColumnDefs": [
|
||||||
{
|
{
|
||||||
"bSortable": false,
|
"bSortable": false,
|
||||||
"aTargets": [ 0 ],
|
"aTargets": [0],
|
||||||
"mData":"ArtistID",
|
"mData":"ArtistID",
|
||||||
"mRender": function ( data, type, full ) {
|
"mRender": function ( data, type, full ) {
|
||||||
return '<div id="artistImg"><img class="albumArt" alt="" id="'+ data + '" src="artwork/thumbs/artist/' + data + '"/></div>';
|
return '<div id="artistImg"><img class="albumArt" alt="" id="'+ data + '" data-src="artwork/thumbs/artist/' + data + '"/></div>';
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"aTargets":[1],
|
|
||||||
"mDataProp":"ArtistSortName",
|
|
||||||
"mRender":function (data,type,full) {
|
|
||||||
|
|
||||||
return '<span title="' + full['ArtistID'] + '"></span><a href="artistPage?ArtistID=' + full['ArtistID'] + '">' + full['ArtistName'] + '</a>'
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{"aTargets":[2],"mDataProp":"Status"},
|
|
||||||
{
|
|
||||||
"aTargets":[3],
|
|
||||||
"mDataProp":"LatestAlbum",
|
|
||||||
"mRender":function(data,type,full){
|
|
||||||
artist = full;
|
|
||||||
if (artist['ReleaseDate'] && artist['LatestAlbum'])
|
|
||||||
{
|
|
||||||
releasedate = artist['ReleaseDate'];
|
|
||||||
albumdisplay = '<i>' + artist['LatestAlbum'] + '</i> (' + artist['ReleaseDate'] + ')';
|
|
||||||
}
|
|
||||||
else if(artist['LatestAlbum'])
|
|
||||||
{
|
|
||||||
releasedate = '';
|
|
||||||
albumdisplay = '<i>' + artist['LatestAlbum'] + '</i>';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
releasedate = '';
|
|
||||||
albumdisplay = '<i>None</i>';
|
|
||||||
}
|
|
||||||
if (artist['ReleaseInFuture'] === 'True')
|
|
||||||
{
|
|
||||||
grade = 'gradeA';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
grade = 'gradeZ';
|
|
||||||
}
|
|
||||||
artist['Grade'] = grade;
|
|
||||||
return '<span title="' + releasedate + '"></span><a href="albumPage?AlbumID=' + full['AlbumID'] + '">' + albumdisplay + '</a>'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"aTargets":[4],
|
|
||||||
"mDataProp":"HaveTracks",
|
|
||||||
"mRender":function(data,type,full){
|
|
||||||
if(full['TotalTracks'] > 0)
|
|
||||||
{
|
|
||||||
percent = (full['HaveTracks']*100.0)/full['TotalTracks']
|
|
||||||
if(percent > 100){
|
|
||||||
percent = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
full['TotalTracks'] = '?';
|
|
||||||
percent = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<span title="' + percent + '"></span><div class="progress-container"><div style="width:' + percent + '%"><div class="havetracks">' + full['HaveTracks'] + '/' + full['TotalTracks'] + '</div></div></div>';
|
|
||||||
|
|
||||||
}},
|
|
||||||
|
|
||||||
],
|
|
||||||
|
|
||||||
"oLanguage": {
|
|
||||||
"sSearch": "",
|
|
||||||
"sLengthMenu":"Show _MENU_ artists per page",
|
|
||||||
"sInfo":"Showing _START_ to _END_ of _TOTAL_ artists",
|
|
||||||
"sInfoEmpty":"Showing 0 to 0 of 0 artists",
|
|
||||||
"sInfoFiltered":"(filtered from _MAX_ total artists)",
|
|
||||||
"sEmptyTable": " ",
|
|
||||||
},
|
},
|
||||||
"bStateSave": true,
|
{
|
||||||
"iDisplayLength": 50,
|
"aTargets":[1],
|
||||||
"sPaginationType": "full_numbers",
|
"mDataProp":"ArtistSortName",
|
||||||
"bProcessing": true,
|
"mRender":function (data,type,full) {
|
||||||
"bServerSide": true,
|
return '<span title="' + full['ArtistID'] + '"></span><a href="artistPage?ArtistID=' + full['ArtistID'] + '">' + full['ArtistName'] + '</a>'
|
||||||
"sAjaxSource": 'getArtists.json',
|
}
|
||||||
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ){
|
},
|
||||||
$('td', nRow).closest('tr').addClass(aData['Grade'])
|
{
|
||||||
nRow.children[0].id = 'albumart';
|
"aTargets":[2],"mDataProp":"Status"
|
||||||
nRow.children[1].id = 'name';
|
},
|
||||||
nRow.children[2].id = 'status'
|
{
|
||||||
nRow.children[3].id = 'album'
|
"aTargets":[3],
|
||||||
nRow.children[4].id = 'have'
|
"mDataProp":"LatestAlbum",
|
||||||
return nRow;
|
"mRender":function(data,type,full){
|
||||||
},
|
artist = full;
|
||||||
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
if (artist['ReleaseDate'] && artist['LatestAlbum'])
|
||||||
/* Add some extra data to the sender */
|
{
|
||||||
$.getJSON( sSource, aoData, function (json) { fnCallback(json) } )
|
releasedate = artist['ReleaseDate'];
|
||||||
|
albumdisplay = '<i>' + artist['LatestAlbum'] + '</i> (' + artist['ReleaseDate'] + ')';
|
||||||
|
}
|
||||||
|
else if(artist['LatestAlbum'])
|
||||||
|
{
|
||||||
|
releasedate = '';
|
||||||
|
albumdisplay = '<i>' + artist['LatestAlbum'] + '</i>';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
releasedate = '';
|
||||||
|
albumdisplay = '<i>None</i>';
|
||||||
|
}
|
||||||
|
if (artist['ReleaseInFuture'] === 'True')
|
||||||
|
{
|
||||||
|
grade = 'gradeA';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
grade = 'gradeZ';
|
||||||
|
}
|
||||||
|
artist['Grade'] = grade;
|
||||||
|
return '<span title="' + releasedate + '"></span><a href="albumPage?AlbumID=' + full['AlbumID'] + '">' + albumdisplay + '</a>'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"aTargets":[4],
|
||||||
|
"mDataProp":"HaveTracks",
|
||||||
|
"mRender":function(data,type,full){
|
||||||
|
if(full['TotalTracks'] > 0)
|
||||||
|
{
|
||||||
|
percent = (full['HaveTracks']*100.0)/full['TotalTracks']
|
||||||
|
if(percent > 100){
|
||||||
|
percent = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
full['TotalTracks'] = '?';
|
||||||
|
percent = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<span title="' + percent + '"></span><div class="progress-container"><div style="width:' + percent + '%"><div class="havetracks">' + full['HaveTracks'] + '/' + full['TotalTracks'] + '</div></div></div>';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"oLanguage": {
|
||||||
|
"sSearch": "",
|
||||||
|
"sLengthMenu":"Show _MENU_ artists per page",
|
||||||
|
"sInfo":"Showing _START_ to _END_ of _TOTAL_ artists",
|
||||||
|
"sInfoEmpty":"Showing 0 to 0 of 0 artists",
|
||||||
|
"sInfoFiltered":"(filtered from _MAX_ total artists)",
|
||||||
|
"sEmptyTable": " ",
|
||||||
},
|
},
|
||||||
"fnInitComplete": function(oSettings, json)
|
"bStateSave": true,
|
||||||
{
|
"iDisplayLength": 50,
|
||||||
}});
|
"sPaginationType": "full_numbers",
|
||||||
|
"bProcessing": true,
|
||||||
|
"bServerSide": true,
|
||||||
|
"sAjaxSource": 'getArtists.json',
|
||||||
|
"fnRowCallback": function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
|
||||||
|
$('td', nRow).closest('tr').addClass(aData['Grade'])
|
||||||
|
nRow.children[0].id = 'albumart';
|
||||||
|
nRow.children[1].id = 'name';
|
||||||
|
nRow.children[2].id = 'status'
|
||||||
|
nRow.children[3].id = 'album'
|
||||||
|
nRow.children[4].id = 'have'
|
||||||
|
return nRow;
|
||||||
|
},
|
||||||
|
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
||||||
|
/* Add some extra data to the sender */
|
||||||
|
$.getJSON( sSource, aoData, function (json) { fnCallback(json) } )
|
||||||
|
},
|
||||||
|
"fnInitComplete": function(oSettings, json)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#artist_table').on("draw.dt", function () {
|
||||||
|
$("img").unveil();
|
||||||
|
});
|
||||||
|
|
||||||
|
resetFilters("artist or album");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
resetFilters("artist or album");
|
initThisPage();
|
||||||
}
|
});
|
||||||
|
$(window).load(function(){
|
||||||
$(document).ready(function() {
|
initFancybox();
|
||||||
initThisPage();
|
refreshLoadArtist();
|
||||||
});
|
});
|
||||||
$(window).load(function(){
|
|
||||||
initFancybox();
|
|
||||||
refreshLoadArtist();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|||||||
@@ -1,648 +0,0 @@
|
|||||||
/*
|
|
||||||
* File: jquery.dataTables.min.js
|
|
||||||
* Version: 1.8.1
|
|
||||||
* Author: Allan Jardine (www.sprymedia.co.uk)
|
|
||||||
* Info: www.datatables.net
|
|
||||||
*
|
|
||||||
* Copyright 2008-2011 Allan Jardine, all rights reserved.
|
|
||||||
*
|
|
||||||
* This source file is free software, under either the GPL v2 license or a
|
|
||||||
* BSD style license, as supplied with this software.
|
|
||||||
*
|
|
||||||
* This source file is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
|
|
||||||
*/
|
|
||||||
(function (i, wa, p) {
|
|
||||||
i.fn.dataTableSettings = [];
|
|
||||||
var D = i.fn.dataTableSettings; i.fn.dataTableExt = {};
|
|
||||||
var o = i.fn.dataTableExt;
|
|
||||||
o.sVersion = "1.8.1";
|
|
||||||
o.sErrMode = "alert";
|
|
||||||
o.iApiIndex = 0; o.oApi = {};
|
|
||||||
o.afnFiltering = [];
|
|
||||||
o.aoFeatures = [];
|
|
||||||
o.ofnSearch = {};
|
|
||||||
o.afnSortData = [];
|
|
||||||
o.oStdClasses = {
|
|
||||||
sPagePrevEnabled: "paginate_enabled_previous",
|
|
||||||
sPagePrevDisabled: "paginate_disabled_previous",
|
|
||||||
sPageNextEnabled: "paginate_enabled_next",
|
|
||||||
sPageNextDisabled: "paginate_disabled_next",
|
|
||||||
sPageJUINext: "", sPageJUIPrev: "",
|
|
||||||
sPageButton: "paginate_button",
|
|
||||||
sPageButtonActive: "paginate_active",
|
|
||||||
sPageButtonStaticDisabled: "paginate_button paginate_button_disabled",
|
|
||||||
sPageFirst: "first",
|
|
||||||
sPagePrevious: "previous",
|
|
||||||
sPageNext: "next",
|
|
||||||
sPageLast: "last",
|
|
||||||
sStripOdd: "odd",
|
|
||||||
sStripEven: "even",
|
|
||||||
sRowEmpty: "dataTables_empty",
|
|
||||||
sWrapper: "dataTables_wrapper",
|
|
||||||
sFilter: "dataTables_filter",
|
|
||||||
sInfo: "dataTables_info",
|
|
||||||
sPaging: "dataTables_paginate paging_",
|
|
||||||
sLength: "dataTables_length",
|
|
||||||
sProcessing: "dataTables_processing",
|
|
||||||
sSortAsc: "sorting_asc",
|
|
||||||
sSortDesc: "sorting_desc",
|
|
||||||
sSortable: "sorting",
|
|
||||||
sSortableAsc: "sorting_asc_disabled",
|
|
||||||
sSortableDesc: "sorting_desc_disabled",
|
|
||||||
sSortableNone: "sorting_disabled",
|
|
||||||
sSortColumn: "sorting_",
|
|
||||||
sSortJUIAsc: "",
|
|
||||||
sSortJUIDesc: "",
|
|
||||||
sSortJUI: "",
|
|
||||||
sSortJUIAscAllowed: "",
|
|
||||||
sSortJUIDescAllowed: "", sSortJUIWrapper: "", sSortIcon: "", sScrollWrapper: "dataTables_scroll",
|
|
||||||
sScrollHead: "dataTables_scrollHead",
|
|
||||||
sScrollHeadInner: "dataTables_scrollHeadInner",
|
|
||||||
sScrollBody: "dataTables_scrollBody",
|
|
||||||
sScrollFoot: "dataTables_scrollFoot",
|
|
||||||
sScrollFootInner: "dataTables_scrollFootInner",
|
|
||||||
sFooterTH: ""
|
|
||||||
}; o.oJUIClasses = { sPagePrevEnabled: "fg-button ui-button ui-state-default ui-corner-left",
|
|
||||||
sPagePrevDisabled: "fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
|
|
||||||
sPageNextEnabled: "fg-button ui-button ui-state-default ui-corner-right",
|
|
||||||
sPageNextDisabled: "fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",
|
|
||||||
sPageJUINext: "ui-icon ui-icon-circle-arrow-e",
|
|
||||||
sPageJUIPrev: "ui-icon ui-icon-circle-arrow-w",
|
|
||||||
sPageButton: "fg-button ui-button ui-state-default",
|
|
||||||
sPageButtonActive: "fg-button ui-button ui-state-default ui-state-disabled",
|
|
||||||
sPageButtonStaticDisabled: "fg-button ui-button ui-state-default ui-state-disabled",
|
|
||||||
sPageFirst: "first ui-corner-tl ui-corner-bl",
|
|
||||||
sPagePrevious: "previous",
|
|
||||||
sPageNext: "next",
|
|
||||||
sPageLast: "last ui-corner-tr ui-corner-br",
|
|
||||||
sStripOdd: "odd",
|
|
||||||
sStripEven: "even",
|
|
||||||
sRowEmpty: "dataTables_empty",
|
|
||||||
sWrapper: "dataTables_wrapper",
|
|
||||||
sFilter: "dataTables_filter",
|
|
||||||
sInfo: "dataTables_info",
|
|
||||||
sPaging: "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",
|
|
||||||
sLength: "dataTables_length",
|
|
||||||
sProcessing: "dataTables_processing",
|
|
||||||
sSortAsc: "ui-state-default",
|
|
||||||
sSortDesc: "ui-state-default",
|
|
||||||
sSortable: "ui-state-default",
|
|
||||||
sSortableAsc: "ui-state-default",
|
|
||||||
sSortableDesc: "ui-state-default",
|
|
||||||
sSortableNone: "ui-state-default",
|
|
||||||
sSortColumn: "sorting_",
|
|
||||||
sSortJUIAsc: "css_right ui-icon ui-icon-triangle-1-n",
|
|
||||||
sSortJUIDesc: "css_right ui-icon ui-icon-triangle-1-s",
|
|
||||||
sSortJUI: "css_right ui-icon ui-icon-carat-2-n-s",
|
|
||||||
sSortJUIAscAllowed: "css_right ui-icon ui-icon-carat-1-n",
|
|
||||||
sSortJUIDescAllowed: "css_right ui-icon ui-icon-carat-1-s",
|
|
||||||
sSortJUIWrapper: "DataTables_sort_wrapper",
|
|
||||||
sSortIcon: "DataTables_sort_icon",
|
|
||||||
sScrollWrapper: "dataTables_scroll",
|
|
||||||
sScrollHead: "dataTables_scrollHead ui-state-default",
|
|
||||||
sScrollHeadInner: "dataTables_scrollHeadInner",
|
|
||||||
sScrollBody: "dataTables_scrollBody",
|
|
||||||
sScrollFoot: "dataTables_scrollFoot ui-state-default",
|
|
||||||
sScrollFootInner: "dataTables_scrollFootInner",
|
|
||||||
sFooterTH: "ui-state-default"
|
|
||||||
}; o.oPagination = { two_button: { fnInit: function (g, l, r) {
|
|
||||||
var s, w, y; if (g.bJUI) {
|
|
||||||
s = p.createElement("a"); w = p.createElement("a"); y = p.createElement("span"); y.className = g.oClasses.sPageJUINext; w.appendChild(y); y = p.createElement("span"); y.className = g.oClasses.sPageJUIPrev;
|
|
||||||
s.appendChild(y)
|
|
||||||
}
|
|
||||||
else { s = p.createElement("div"); w = p.createElement("div") } s.className = g.oClasses.sPagePrevDisabled; w.className = g.oClasses.sPageNextDisabled; s.title = g.oLanguage.oPaginate.sPrevious; w.title = g.oLanguage.oPaginate.sNext; l.appendChild(s); l.appendChild(w); i(s).bind("click.DT", function () { g.oApi._fnPageChange(g, "previous") && r(g) }); i(w).bind("click.DT", function () { g.oApi._fnPageChange(g, "next") && r(g) }); i(s).bind("selectstart.DT", function () { return false }); i(w).bind("selectstart.DT", function () { return false });
|
|
||||||
|
|
||||||
if (g.sTableId !== "" && typeof g.aanFeatures.p == "undefined") {
|
|
||||||
l.setAttribute("id", g.sTableId + "_paginate");
|
|
||||||
s.setAttribute("id", g.sTableId + "_previous");
|
|
||||||
w.setAttribute("id", g.sTableId + "_next")
|
|
||||||
}
|
|
||||||
}, fnUpdate: function (g) {
|
|
||||||
if (g.aanFeatures.p) for (var l = g.aanFeatures.p, r = 0, s = l.length; r < s; r++)
|
|
||||||
if (l[r].childNodes.length !== 0) {
|
|
||||||
l[r].childNodes[0].className = g._iDisplayStart === 0 ? g.oClasses.sPagePrevDisabled : g.oClasses.sPagePrevEnabled; l[r].childNodes[1].className = g.fnDisplayEnd() == g.fnRecordsDisplay() ? g.oClasses.sPageNextDisabled :
|
|
||||||
g.oClasses.sPageNextEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, iFullNumbersShowPages: 5, full_numbers: { fnInit: function (g, l, r) {
|
|
||||||
var s = p.createElement("span"), w = p.createElement("span"), y = p.createElement("span"), G = p.createElement("span"), x = p.createElement("span"); s.innerHTML = g.oLanguage.oPaginate.sFirst; w.innerHTML = g.oLanguage.oPaginate.sPrevious; G.innerHTML = g.oLanguage.oPaginate.sNext; x.innerHTML = g.oLanguage.oPaginate.sLast; var v = g.oClasses; s.className = v.sPageButton + " " + v.sPageFirst; w.className = v.sPageButton + " " + v.sPagePrevious; G.className =
|
|
||||||
v.sPageButton + " " + v.sPageNext; x.className = v.sPageButton + " " + v.sPageLast; l.appendChild(s); l.appendChild(w); l.appendChild(y); l.appendChild(G); l.appendChild(x); i(s).bind("click.DT", function () { g.oApi._fnPageChange(g, "first") && r(g) }); i(w).bind("click.DT", function () { g.oApi._fnPageChange(g, "previous") && r(g) }); i(G).bind("click.DT", function () { g.oApi._fnPageChange(g, "next") && r(g) }); i(x).bind("click.DT", function () { g.oApi._fnPageChange(g, "last") && r(g) }); i("span", l).bind("mousedown.DT", function () { return false }).bind("selectstart.DT",
|
|
||||||
|
|
||||||
function () { return false }); if (g.sTableId !== "" && typeof g.aanFeatures.p == "undefined") { l.setAttribute("id", g.sTableId + "_paginate"); s.setAttribute("id", g.sTableId + "_first"); w.setAttribute("id", g.sTableId + "_previous"); G.setAttribute("id", g.sTableId + "_next"); x.setAttribute("id", g.sTableId + "_last") }
|
|
||||||
}, fnUpdate: function (g, l) {
|
|
||||||
if (g.aanFeatures.p) {
|
|
||||||
var r = o.oPagination.iFullNumbersShowPages, s = Math.floor(r / 2), w = Math.ceil(g.fnRecordsDisplay() / g._iDisplayLength), y = Math.ceil(g._iDisplayStart / g._iDisplayLength) + 1, G =
|
|
||||||
"", x, v = g.oClasses; if (w < r) { s = 1; x = w } else if (y <= s) { s = 1; x = r } else if (y >= w - s) { s = w - r + 1; x = w } else { s = y - Math.ceil(r / 2) + 1; x = s + r - 1 } for (r = s; r <= x; r++) G += y != r ? '<span class="' + v.sPageButton + '">' + r + "</span>" : '<span class="' + v.sPageButtonActive + '">' + r + "</span>"; x = g.aanFeatures.p; var z, Y = function (L) { g._iDisplayStart = (this.innerHTML * 1 - 1) * g._iDisplayLength; l(g); L.preventDefault() }, V = function () { return false }; r = 0; for (s = x.length; r < s; r++) if (x[r].childNodes.length !== 0) {
|
|
||||||
z = i("span:eq(2)", x[r]); z.html(G); i("span", z).bind("click.DT",
|
|
||||||
Y).bind("mousedown.DT", V).bind("selectstart.DT", V); z = x[r].getElementsByTagName("span"); z = [z[0], z[1], z[z.length - 2], z[z.length - 1]]; i(z).removeClass(v.sPageButton + " " + v.sPageButtonActive + " " + v.sPageButtonStaticDisabled); if (y == 1) { z[0].className += " " + v.sPageButtonStaticDisabled; z[1].className += " " + v.sPageButtonStaticDisabled } else { z[0].className += " " + v.sPageButton; z[1].className += " " + v.sPageButton } if (w === 0 || y == w || g._iDisplayLength == -1) {
|
|
||||||
z[2].className += " " + v.sPageButtonStaticDisabled; z[3].className += " " +
|
|
||||||
v.sPageButtonStaticDisabled
|
|
||||||
} else { z[2].className += " " + v.sPageButton; z[3].className += " " + v.sPageButton }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}; o.oSort = { "string-asc": function (g, l) { if (typeof g != "string") g = ""; if (typeof l != "string") l = ""; g = g.toLowerCase(); l = l.toLowerCase(); return g < l ? -1 : g > l ? 1 : 0 }, "string-desc": function (g, l) { if (typeof g != "string") g = ""; if (typeof l != "string") l = ""; g = g.toLowerCase(); l = l.toLowerCase(); return g < l ? 1 : g > l ? -1 : 0 }, "html-asc": function (g, l) {
|
|
||||||
g = g.replace(/<.*?>/g, "").toLowerCase(); l = l.replace(/<.*?>/g, "").toLowerCase(); return g <
|
|
||||||
l ? -1 : g > l ? 1 : 0
|
|
||||||
}, "html-desc": function (g, l) { g = g.replace(/<.*?>/g, "").toLowerCase(); l = l.replace(/<.*?>/g, "").toLowerCase(); return g < l ? 1 : g > l ? -1 : 0 }, "date-asc": function (g, l) { g = Date.parse(g); l = Date.parse(l); if (isNaN(g) || g === "") g = Date.parse("01/01/1970 00:00:00"); if (isNaN(l) || l === "") l = Date.parse("01/01/1970 00:00:00"); return g - l }, "date-desc": function (g, l) {
|
|
||||||
g = Date.parse(g); l = Date.parse(l); if (isNaN(g) || g === "") g = Date.parse("01/01/1970 00:00:00"); if (isNaN(l) || l === "") l = Date.parse("01/01/1970 00:00:00"); return l -
|
|
||||||
g
|
|
||||||
}, "numeric-asc": function (g, l) { return (g == "-" || g === "" ? 0 : g * 1) - (l == "-" || l === "" ? 0 : l * 1) }, "numeric-desc": function (g, l) { return (l == "-" || l === "" ? 0 : l * 1) - (g == "-" || g === "" ? 0 : g * 1) }
|
|
||||||
}; o.aTypes = [function (g) { if (typeof g == "number") return "numeric"; else if (typeof g != "string") return null; var l, r = false; l = g.charAt(0); if ("0123456789-".indexOf(l) == -1) return null; for (var s = 1; s < g.length; s++) { l = g.charAt(s); if ("0123456789.".indexOf(l) == -1) return null; if (l == ".") { if (r) return null; r = true } } return "numeric" }, function (g) {
|
|
||||||
var l = Date.parse(g);
|
|
||||||
|
|
||||||
if (l !== null && !isNaN(l) || typeof g == "string" && g.length === 0) return "date"; return null
|
|
||||||
}, function (g) { if (typeof g == "string" && g.indexOf("<") != -1 && g.indexOf(">") != -1) return "html"; return null } ]; o.fnVersionCheck = function (g) { var l = function (x, v) { for (; x.length < v; ) x += "0"; return x }, r = o.sVersion.split("."); g = g.split("."); for (var s = "", w = "", y = 0, G = g.length; y < G; y++) { s += l(r[y], 3); w += l(g[y], 3) } return parseInt(s, 10) >= parseInt(w, 10) }; o._oExternConfig = { iNextUnique: 0 }; i.fn.dataTable = function (g) {
|
|
||||||
function l() {
|
|
||||||
this.fnRecordsTotal =
|
|
||||||
function () { return this.oFeatures.bServerSide ? parseInt(this._iRecordsTotal, 10) : this.aiDisplayMaster.length }; this.fnRecordsDisplay = function () { return this.oFeatures.bServerSide ? parseInt(this._iRecordsDisplay, 10) : this.aiDisplay.length }; this.fnDisplayEnd = function () { return this.oFeatures.bServerSide ? this.oFeatures.bPaginate === false || this._iDisplayLength == -1 ? this._iDisplayStart + this.aiDisplay.length : Math.min(this._iDisplayStart + this._iDisplayLength, this._iRecordsDisplay) : this._iDisplayEnd }; this.sInstance =
|
|
||||||
this.oInstance = null; this.oFeatures = { bPaginate: true, bLengthChange: true, bFilter: true, bSort: true, bInfo: true, bAutoWidth: true, bProcessing: false, bSortClasses: true, bStateSave: false, bServerSide: false, bDeferRender: false }; this.oScroll = { sX: "", sXInner: "", sY: "", bCollapse: false, bInfinite: false, iLoadGap: 100, iBarWidth: 0, bAutoCss: true }; this.aanFeatures = [];
|
|
||||||
this.oLanguage = { sProcessing: "Processing...", sLengthMenu: "Show _MENU_ artists per page", sZeroRecords: "No matching records found", sEmptyTable: "",
|
|
||||||
sLoadingRecords: "Loading...", sInfo: "Showing _START_ to _END_ of _TOTAL_ artists", sInfoEmpty: "Showing 0 to 0 of 0 artists", sInfoFiltered: "(filtered from _MAX_ total artists)", sInfoPostFix: "", sSearch: "Filter:", sUrl: "", oPaginate: { sFirst: "First", sPrevious: "Previous", sNext: "Next", sLast: "Last" }, fnInfoCallback: null
|
|
||||||
}; this.aoData = []; this.aiDisplay = []; this.aiDisplayMaster = []; this.aoColumns = []; this.aoHeader = []; this.aoFooter = []; this.iNextId = 0; this.asDataSearch = []; this.oPreviousSearch = { sSearch: "", bRegex: false,
|
|
||||||
bSmart: true
|
|
||||||
}; this.aoPreSearchCols = []; this.aaSorting = [[0, "asc", 0]]; this.aaSortingFixed = null; this.asStripClasses = []; this.asDestoryStrips = []; this.sDestroyWidth = 0; this.fnFooterCallback = this.fnHeaderCallback = this.fnRowCallback = null; this.aoDrawCallback = []; this.fnInitComplete = this.fnPreDrawCallback = null; this.sTableId = ""; this.nTableWrapper = this.nTBody = this.nTFoot = this.nTHead = this.nTable = null; this.bInitialised = this.bDeferLoading = false; this.aoOpenRows = []; this.sDom = "lfrtip"; this.sPaginationType = "two_button";
|
|
||||||
this.iCookieDuration = 7200; this.sCookiePrefix = "SpryMedia_DataTables_"; this.fnCookieCallback = null; this.aoStateSave = []; this.aoStateLoad = []; this.sAjaxSource = this.oLoadedState = null; this.sAjaxDataProp = "aaData"; this.bAjaxDataGet = true; this.jqXHR = null; this.fnServerData = function (a, b, c, d) { d.jqXHR = i.ajax({ url: a, data: b, success: c, dataType: "json", cache: false, error: function (f, e) { e == "parsererror" && alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.") } }) };
|
|
||||||
this.fnFormatNumber = function (a) { if (a < 1E3) return a; else { var b = a + ""; a = b.split(""); var c = ""; b = b.length; for (var d = 0; d < b; d++) { if (d % 3 === 0 && d !== 0) c = "," + c; c = a[b - d - 1] + c } } return c }; this.aLengthMenu = [10, 25, 50, 100]; this.bDrawing = this.iDraw = 0; this.iDrawError = -1; this._iDisplayLength = 10; this._iDisplayStart = 0; this._iDisplayEnd = 10; this._iRecordsDisplay = this._iRecordsTotal = 0; this.bJUI = false; this.oClasses = o.oStdClasses; this.bSortCellsTop = this.bSorted = this.bFiltered = false; this.oInit = null
|
|
||||||
} function r(a) {
|
|
||||||
return function () {
|
|
||||||
var b =
|
|
||||||
[A(this[o.iApiIndex])].concat(Array.prototype.slice.call(arguments)); return o.oApi[a].apply(this, b)
|
|
||||||
}
|
|
||||||
} function s(a) {
|
|
||||||
var b, c, d = a.iInitDisplayStart; if (a.bInitialised === false) setTimeout(function () { s(a) }, 200); else {
|
|
||||||
xa(a); V(a); L(a, a.aoHeader); a.nTFoot && L(a, a.aoFooter);
|
|
||||||
K(a, true); a.oFeatures.bAutoWidth && ea(a);
|
|
||||||
b = 0; for (c = a.aoColumns.length; b < c; b++)
|
|
||||||
if (a.aoColumns[b].sWidth !== null) a.aoColumns[b].nTh.style.width = u(a.aoColumns[b].sWidth);
|
|
||||||
if (a.oFeatures.bSort) R(a);
|
|
||||||
else if (a.oFeatures.bFilter) M(a, a.oPreviousSearch);
|
|
||||||
else { a.aiDisplay = a.aiDisplayMaster.slice(); E(a); C(a) } if (a.sAjaxSource !== null && !a.oFeatures.bServerSide) a.fnServerData.call(a.oInstance, a.sAjaxSource, [], function (f) { var e = f; if (a.sAjaxDataProp !== "") e = Z(a.sAjaxDataProp)(f); for (b = 0; b < e.length; b++) v(a, e[b]); a.iInitDisplayStart = d; if (a.oFeatures.bSort) R(a); else { a.aiDisplay = a.aiDisplayMaster.slice(); E(a); C(a) } K(a, false); w(a, f) }, a); else if (!a.oFeatures.bServerSide) { K(a, false); w(a) }
|
|
||||||
}
|
|
||||||
} function w(a, b) {
|
|
||||||
a._bInitComplete = true; if (typeof a.fnInitComplete == "function") typeof b !=
|
|
||||||
"undefined" ? a.fnInitComplete.call(a.oInstance, a, b) : a.fnInitComplete.call(a.oInstance, a)
|
|
||||||
} function y(a, b, c) {
|
|
||||||
n(a.oLanguage, b, "sProcessing"); n(a.oLanguage, b, "sLengthMenu"); n(a.oLanguage, b, "sEmptyTable"); n(a.oLanguage, b, "sLoadingRecords"); n(a.oLanguage, b, "sZeroRecords"); n(a.oLanguage, b, "sInfo"); n(a.oLanguage, b, "sInfoEmpty"); n(a.oLanguage, b, "sInfoFiltered"); n(a.oLanguage, b, "sInfoPostFix"); n(a.oLanguage, b, "sSearch"); if (typeof b.oPaginate != "undefined") {
|
|
||||||
n(a.oLanguage.oPaginate, b.oPaginate, "sFirst"); n(a.oLanguage.oPaginate,
|
|
||||||
b.oPaginate, "sPrevious"); n(a.oLanguage.oPaginate, b.oPaginate, "sNext"); n(a.oLanguage.oPaginate, b.oPaginate, "sLast")
|
|
||||||
} typeof b.sEmptyTable == "undefined" && typeof b.sZeroRecords != "undefined" && n(a.oLanguage, b, "sZeroRecords", "sEmptyTable"); typeof b.sLoadingRecords == "undefined" && typeof b.sZeroRecords != "undefined" && n(a.oLanguage, b, "sZeroRecords", "sLoadingRecords"); c && s(a)
|
|
||||||
} function G(a, b) {
|
|
||||||
var c = a.aoColumns.length; b = { sType: null, _bAutoType: true, bVisible: true, bSearchable: true, bSortable: true, asSorting: ["asc", "desc"],
|
|
||||||
sSortingClass: a.oClasses.sSortable, sSortingClassJUI: a.oClasses.sSortJUI, sTitle: b ? b.innerHTML : "", sName: "", sWidth: null, sWidthOrig: null, sClass: null, fnRender: null, bUseRendered: true, iDataSort: c, mDataProp: c, fnGetData: null, fnSetData: null, sSortDataType: "std", sDefaultContent: null, sContentPadding: "", nTh: b ? b : p.createElement("th"), nTf: null
|
|
||||||
}; a.aoColumns.push(b); if (typeof a.aoPreSearchCols[c] == "undefined" || a.aoPreSearchCols[c] === null) a.aoPreSearchCols[c] = { sSearch: "", bRegex: false, bSmart: true }; else {
|
|
||||||
if (typeof a.aoPreSearchCols[c].bRegex ==
|
|
||||||
"undefined") a.aoPreSearchCols[c].bRegex = true; if (typeof a.aoPreSearchCols[c].bSmart == "undefined") a.aoPreSearchCols[c].bSmart = true
|
|
||||||
} x(a, c, null)
|
|
||||||
} function x(a, b, c) {
|
|
||||||
b = a.aoColumns[b]; if (typeof c != "undefined" && c !== null) {
|
|
||||||
if (typeof c.sType != "undefined") { b.sType = c.sType; b._bAutoType = false } n(b, c, "bVisible"); n(b, c, "bSearchable"); n(b, c, "bSortable"); n(b, c, "sTitle"); n(b, c, "sName"); n(b, c, "sWidth"); n(b, c, "sWidth", "sWidthOrig"); n(b, c, "sClass"); n(b, c, "fnRender"); n(b, c, "bUseRendered"); n(b, c, "iDataSort"); n(b, c, "mDataProp");
|
|
||||||
n(b, c, "asSorting"); n(b, c, "sSortDataType"); n(b, c, "sDefaultContent"); n(b, c, "sContentPadding")
|
|
||||||
} b.fnGetData = Z(b.mDataProp); b.fnSetData = ya(b.mDataProp); if (!a.oFeatures.bSort) b.bSortable = false; if (!b.bSortable || i.inArray("asc", b.asSorting) == -1 && i.inArray("desc", b.asSorting) == -1) { b.sSortingClass = a.oClasses.sSortableNone; b.sSortingClassJUI = "" } else if (b.bSortable || i.inArray("asc", b.asSorting) == -1 && i.inArray("desc", b.asSorting) == -1) { b.sSortingClass = a.oClasses.sSortable; b.sSortingClassJUI = a.oClasses.sSortJUI } else if (i.inArray("asc",
|
|
||||||
b.asSorting) != -1 && i.inArray("desc", b.asSorting) == -1) { b.sSortingClass = a.oClasses.sSortableAsc; b.sSortingClassJUI = a.oClasses.sSortJUIAscAllowed } else if (i.inArray("asc", b.asSorting) == -1 && i.inArray("desc", b.asSorting) != -1) { b.sSortingClass = a.oClasses.sSortableDesc; b.sSortingClassJUI = a.oClasses.sSortJUIDescAllowed }
|
|
||||||
} function v(a, b) {
|
|
||||||
var c; c = typeof b.length == "number" ? b.slice() : i.extend(true, {}, b); b = a.aoData.length; var d = { nTr: null, _iId: a.iNextId++, _aData: c, _anHidden: [], _sRowStripe: "" }; a.aoData.push(d); for (var f,
|
|
||||||
e = 0, h = a.aoColumns.length; e < h; e++) { c = a.aoColumns[e]; typeof c.fnRender == "function" && c.bUseRendered && c.mDataProp !== null && N(a, b, e, c.fnRender({ iDataRow: b, iDataColumn: e, aData: d._aData, oSettings: a })); if (c._bAutoType && c.sType != "string") { f = H(a, b, e, "type"); if (f !== null && f !== "") { f = fa(f); if (c.sType === null) c.sType = f; else if (c.sType != f) c.sType = "string" } } } a.aiDisplayMaster.push(b); a.oFeatures.bDeferRender || z(a, b); return b
|
|
||||||
} function z(a, b) {
|
|
||||||
var c = a.aoData[b], d; if (c.nTr === null) {
|
|
||||||
c.nTr = p.createElement("tr"); typeof c._aData.DT_RowId !=
|
|
||||||
"undefined" && c.nTr.setAttribute("id", c._aData.DT_RowId); typeof c._aData.DT_RowClass != "undefined" && i(c.nTr).addClass(c._aData.DT_RowClass); for (var f = 0, e = a.aoColumns.length; f < e; f++) {
|
|
||||||
var h = a.aoColumns[f]; d = p.createElement("td"); d.innerHTML = typeof h.fnRender == "function" && (!h.bUseRendered || h.mDataProp === null) ? h.fnRender({ iDataRow: b, iDataColumn: f, aData: c._aData, oSettings: a }) : H(a, b, f, "display"); if (h.sClass !== null) d.className = h.sClass; if (h.bVisible) { c.nTr.appendChild(d); c._anHidden[f] = null } else c._anHidden[f] =
|
|
||||||
d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function Y(a) {
|
|
||||||
var b, c, d, f, e, h, j, k, m; if (a.bDeferLoading || a.sAjaxSource === null) { j = a.nTBody.childNodes; b = 0; for (c = j.length; b < c; b++) if (j[b].nodeName.toUpperCase() == "TR") { k = a.aoData.length; a.aoData.push({ nTr: j[b], _iId: a.iNextId++, _aData: [], _anHidden: [], _sRowStripe: "" }); a.aiDisplayMaster.push(k); h = j[b].childNodes; d = e = 0; for (f = h.length; d < f; d++) { m = h[d].nodeName.toUpperCase(); if (m == "TD" || m == "TH") { N(a, k, e, i.trim(h[d].innerHTML)); e++ } } } } j = $(a); h = []; b = 0; for (c = j.length; b < c; b++) {
|
|
||||||
d = 0; for (f = j[b].childNodes.length; d <
|
|
||||||
f; d++) { e = j[b].childNodes[d]; m = e.nodeName.toUpperCase(); if (m == "TD" || m == "TH") h.push(e) }
|
|
||||||
} h.length != j.length * a.aoColumns.length && J(a, 1, "Unexpected number of TD elements. Expected " + j.length * a.aoColumns.length + " and got " + h.length + ". DataTables does not support rowspan / colspan in the table body, and there must be one cell for each row/column combination."); d = 0; for (f = a.aoColumns.length; d < f; d++) {
|
|
||||||
if (a.aoColumns[d].sTitle === null) a.aoColumns[d].sTitle = a.aoColumns[d].nTh.innerHTML; j = a.aoColumns[d]._bAutoType;
|
|
||||||
m = typeof a.aoColumns[d].fnRender == "function"; e = a.aoColumns[d].sClass !== null; k = a.aoColumns[d].bVisible; var t, q; if (j || m || e || !k) {
|
|
||||||
b = 0; for (c = a.aoData.length; b < c; b++) {
|
|
||||||
t = h[b * f + d]; if (j && a.aoColumns[d].sType != "string") { q = H(a, b, d, "type"); if (q !== "") { q = fa(q); if (a.aoColumns[d].sType === null) a.aoColumns[d].sType = q; else if (a.aoColumns[d].sType != q) a.aoColumns[d].sType = "string" } } if (m) {
|
|
||||||
q = a.aoColumns[d].fnRender({ iDataRow: b, iDataColumn: d, aData: a.aoData[b]._aData, oSettings: a }); t.innerHTML = q; a.aoColumns[d].bUseRendered &&
|
|
||||||
N(a, b, d, q)
|
|
||||||
} if (e) t.className += " " + a.aoColumns[d].sClass; if (k) a.aoData[b]._anHidden[d] = null; else { a.aoData[b]._anHidden[d] = t; t.parentNode.removeChild(t) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function V(a) {
|
|
||||||
var b, c, d; a.nTHead.getElementsByTagName("tr"); if (a.nTHead.getElementsByTagName("th").length !== 0) { b = 0; for (d = a.aoColumns.length; b < d; b++) { c = a.aoColumns[b].nTh; a.aoColumns[b].sClass !== null && i(c).addClass(a.aoColumns[b].sClass); if (a.aoColumns[b].sTitle != c.innerHTML) c.innerHTML = a.aoColumns[b].sTitle } } else {
|
|
||||||
var f = p.createElement("tr"); b = 0;
|
|
||||||
for (d = a.aoColumns.length; b < d; b++) { c = a.aoColumns[b].nTh; c.innerHTML = a.aoColumns[b].sTitle; a.aoColumns[b].sClass !== null && i(c).addClass(a.aoColumns[b].sClass); f.appendChild(c) } i(a.nTHead).html("")[0].appendChild(f); W(a.aoHeader, a.nTHead)
|
|
||||||
} if (a.bJUI) { b = 0; for (d = a.aoColumns.length; b < d; b++) { c = a.aoColumns[b].nTh; f = p.createElement("div"); f.className = a.oClasses.sSortJUIWrapper; i(c).contents().appendTo(f); var e = p.createElement("span"); e.className = a.oClasses.sSortIcon; f.appendChild(e); c.appendChild(f) } } d = function () {
|
|
||||||
this.onselectstart =
|
|
||||||
function () { return false }; return false
|
|
||||||
}; if (a.oFeatures.bSort) for (b = 0; b < a.aoColumns.length; b++) if (a.aoColumns[b].bSortable !== false) { ga(a, a.aoColumns[b].nTh, b); i(a.aoColumns[b].nTh).bind("mousedown.DT", d) } else i(a.aoColumns[b].nTh).addClass(a.oClasses.sSortableNone); a.oClasses.sFooterTH !== "" && i(">tr>th", a.nTFoot).addClass(a.oClasses.sFooterTH); if (a.nTFoot !== null) { c = S(a, null, a.aoFooter); b = 0; for (d = a.aoColumns.length; b < d; b++) if (typeof c[b] != "undefined") a.aoColumns[b].nTf = c[b] }
|
|
||||||
} function L(a, b, c) {
|
|
||||||
var d, f,
|
|
||||||
e, h = [], j = [], k = a.aoColumns.length; if (typeof c == "undefined") c = false; d = 0; for (f = b.length; d < f; d++) { h[d] = b[d].slice(); h[d].nTr = b[d].nTr; for (e = k - 1; e >= 0; e--) !a.aoColumns[e].bVisible && !c && h[d].splice(e, 1); j.push([]) } d = 0; for (f = h.length; d < f; d++) {
|
|
||||||
if (h[d].nTr) { a = 0; for (e = h[d].nTr.childNodes.length; a < e; a++) h[d].nTr.removeChild(h[d].nTr.childNodes[0]) } e = 0; for (b = h[d].length; e < b; e++) {
|
|
||||||
k = c = 1; if (typeof j[d][e] == "undefined") {
|
|
||||||
h[d].nTr.appendChild(h[d][e].cell); for (j[d][e] = 1; typeof h[d + c] != "undefined" && h[d][e].cell == h[d +
|
|
||||||
c][e].cell; ) { j[d + c][e] = 1; c++ } for (; typeof h[d][e + k] != "undefined" && h[d][e].cell == h[d][e + k].cell; ) { for (a = 0; a < c; a++) j[d + a][e + k] = 1; k++ } h[d][e].cell.setAttribute("rowspan", c); h[d][e].cell.setAttribute("colspan", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function C(a) {
|
|
||||||
var b, c, d = [], f = 0, e = false; b = a.asStripClasses.length; c = a.aoOpenRows.length; if (!(a.fnPreDrawCallback !== null && a.fnPreDrawCallback.call(a.oInstance, a) === false)) {
|
|
||||||
a.bDrawing = true; if (typeof a.iInitDisplayStart != "undefined" && a.iInitDisplayStart != -1) {
|
|
||||||
a._iDisplayStart = a.oFeatures.bServerSide ?
|
|
||||||
a.iInitDisplayStart : a.iInitDisplayStart >= a.fnRecordsDisplay() ? 0 : a.iInitDisplayStart; a.iInitDisplayStart = -1; E(a)
|
|
||||||
} if (a.bDeferLoading) { a.bDeferLoading = false; a.iDraw++ } else if (a.oFeatures.bServerSide) { if (!a.bDestroying && !za(a)) return } else a.iDraw++; if (a.aiDisplay.length !== 0) {
|
|
||||||
var h = a._iDisplayStart, j = a._iDisplayEnd; if (a.oFeatures.bServerSide) { h = 0; j = a.aoData.length } for (h = h; h < j; h++) {
|
|
||||||
var k = a.aoData[a.aiDisplay[h]]; k.nTr === null && z(a, a.aiDisplay[h]); var m = k.nTr; if (b !== 0) {
|
|
||||||
var t = a.asStripClasses[f % b]; if (k._sRowStripe !=
|
|
||||||
t) { i(m).removeClass(k._sRowStripe).addClass(t); k._sRowStripe = t }
|
|
||||||
} if (typeof a.fnRowCallback == "function") { m = a.fnRowCallback.call(a.oInstance, m, a.aoData[a.aiDisplay[h]]._aData, f, h); if (!m && !e) { J(a, 0, "A node was not returned by fnRowCallback"); e = true } } d.push(m); f++; if (c !== 0) for (k = 0; k < c; k++) m == a.aoOpenRows[k].nParent && d.push(a.aoOpenRows[k].nTr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d[0] = p.createElement("tr"); if (typeof a.asStripClasses[0] != "undefined") d[0].className = a.asStripClasses[0]; e = a.oLanguage.sZeroRecords.replace("_MAX_", a.fnFormatNumber(a.fnRecordsTotal()));
|
|
||||||
if (a.iDraw == 1 && a.sAjaxSource !== null && !a.oFeatures.bServerSide) e = a.oLanguage.sLoadingRecords; else if (typeof a.oLanguage.sEmptyTable != "undefined" && a.fnRecordsTotal() === 0) e = a.oLanguage.sEmptyTable; b = p.createElement("td"); b.setAttribute("valign", "top"); b.colSpan = X(a); b.className = a.oClasses.sRowEmpty; b.innerHTML = e; d[f].appendChild(b)
|
|
||||||
} typeof a.fnHeaderCallback == "function" && a.fnHeaderCallback.call(a.oInstance, i(">tr", a.nTHead)[0], aa(a), a._iDisplayStart, a.fnDisplayEnd(), a.aiDisplay); typeof a.fnFooterCallback ==
|
|
||||||
"function" && a.fnFooterCallback.call(a.oInstance, i(">tr", a.nTFoot)[0], aa(a), a._iDisplayStart, a.fnDisplayEnd(), a.aiDisplay); f = p.createDocumentFragment(); b = p.createDocumentFragment(); if (a.nTBody) { e = a.nTBody.parentNode; b.appendChild(a.nTBody); if (!a.oScroll.bInfinite || !a._bInitComplete || a.bSorted || a.bFiltered) { c = a.nTBody.childNodes; for (b = c.length - 1; b >= 0; b--) c[b].parentNode.removeChild(c[b]) } b = 0; for (c = d.length; b < c; b++) f.appendChild(d[b]); a.nTBody.appendChild(f); e !== null && e.appendChild(a.nTBody) } for (b = a.aoDrawCallback.length -
|
|
||||||
1; b >= 0; b--) a.aoDrawCallback[b].fn.call(a.oInstance, a); a.bSorted = false; a.bFiltered = false; a.bDrawing = false; if (a.oFeatures.bServerSide) { K(a, false); typeof a._bInitComplete == "undefined" && w(a) }
|
|
||||||
}
|
|
||||||
} function ba(a) { if (a.oFeatures.bSort) R(a, a.oPreviousSearch); else if (a.oFeatures.bFilter) M(a, a.oPreviousSearch); else { E(a); C(a) } } function za(a) {
|
|
||||||
if (a.bAjaxDataGet) {
|
|
||||||
K(a, true); var b = a.aoColumns.length, c = [], d, f; a.iDraw++; c.push({ name: "sEcho", value: a.iDraw }); c.push({ name: "iColumns", value: b }); c.push({ name: "sColumns", value: ha(a) });
|
|
||||||
c.push({ name: "iDisplayStart", value: a._iDisplayStart }); c.push({ name: "iDisplayLength", value: a.oFeatures.bPaginate !== false ? a._iDisplayLength : -1 }); for (f = 0; f < b; f++) { d = a.aoColumns[f].mDataProp; c.push({ name: "mDataProp_" + f, value: typeof d == "function" ? "function" : d }) } if (a.oFeatures.bFilter !== false) {
|
|
||||||
c.push({ name: "sSearch", value: a.oPreviousSearch.sSearch }); c.push({ name: "bRegex", value: a.oPreviousSearch.bRegex }); for (f = 0; f < b; f++) {
|
|
||||||
c.push({ name: "sSearch_" + f, value: a.aoPreSearchCols[f].sSearch }); c.push({ name: "bRegex_" +
|
|
||||||
f, value: a.aoPreSearchCols[f].bRegex
|
|
||||||
}); c.push({ name: "bSearchable_" + f, value: a.aoColumns[f].bSearchable })
|
|
||||||
}
|
|
||||||
} if (a.oFeatures.bSort !== false) {
|
|
||||||
d = a.aaSortingFixed !== null ? a.aaSortingFixed.length : 0; var e = a.aaSorting.length; c.push({ name: "iSortingCols", value: d + e }); for (f = 0; f < d; f++) { c.push({ name: "iSortCol_" + f, value: a.aaSortingFixed[f][0] }); c.push({ name: "sSortDir_" + f, value: a.aaSortingFixed[f][1] }) } for (f = 0; f < e; f++) { c.push({ name: "iSortCol_" + (f + d), value: a.aaSorting[f][0] }); c.push({ name: "sSortDir_" + (f + d), value: a.aaSorting[f][1] }) } for (f =
|
|
||||||
0; f < b; f++) c.push({ name: "bSortable_" + f, value: a.aoColumns[f].bSortable })
|
|
||||||
} a.fnServerData.call(a.oInstance, a.sAjaxSource, c, function (h) { Aa(a, h) }, a); return false
|
|
||||||
} else return true
|
|
||||||
} function Aa(a, b) {
|
|
||||||
if (typeof b.sEcho != "undefined") if (b.sEcho * 1 < a.iDraw) return; else a.iDraw = b.sEcho * 1; if (!a.oScroll.bInfinite || a.oScroll.bInfinite && (a.bSorted || a.bFiltered)) ia(a); a._iRecordsTotal = b.iTotalRecords; a._iRecordsDisplay = b.iTotalDisplayRecords; var c = ha(a); if (c = typeof b.sColumns != "undefined" && c !== "" && b.sColumns != c) var d =
|
|
||||||
Ba(a, b.sColumns); b = Z(a.sAjaxDataProp)(b); for (var f = 0, e = b.length; f < e; f++) if (c) { for (var h = [], j = 0, k = a.aoColumns.length; j < k; j++) h.push(b[f][d[j]]); v(a, h) } else v(a, b[f]); a.aiDisplay = a.aiDisplayMaster.slice(); a.bAjaxDataGet = false; C(a); a.bAjaxDataGet = true; K(a, false)
|
|
||||||
} function xa(a) {
|
|
||||||
var b = p.createElement("div"); a.nTable.parentNode.insertBefore(b, a.nTable); a.nTableWrapper = p.createElement("div"); a.nTableWrapper.className = a.oClasses.sWrapper; a.sTableId !== "" && a.nTableWrapper.setAttribute("id", a.sTableId + "_wrapper");
|
|
||||||
a.nTableReinsertBefore = a.nTable.nextSibling; for (var c = a.nTableWrapper, d = a.sDom.split(""), f, e, h, j, k, m, t, q = 0; q < d.length; q++) {
|
|
||||||
e = 0; h = d[q]; if (h == "<") {
|
|
||||||
j = p.createElement("div"); k = d[q + 1]; if (k == "'" || k == '"') {
|
|
||||||
m = ""; for (t = 2; d[q + t] != k; ) { m += d[q + t]; t++ } if (m == "H") m = "fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix"; else if (m == "F") m = "fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"; if (m.indexOf(".") != -1) {
|
|
||||||
k = m.split("."); j.setAttribute("id", k[0].substr(1,
|
|
||||||
k[0].length - 1)); j.className = k[1]
|
|
||||||
} else if (m.charAt(0) == "#") j.setAttribute("id", m.substr(1, m.length - 1)); else j.className = m; q += t
|
|
||||||
} c.appendChild(j); c = j
|
|
||||||
} else if (h == ">") c = c.parentNode; else if (h == "l" && a.oFeatures.bPaginate && a.oFeatures.bLengthChange) { f = Ca(a); e = 1 } else if (h == "f" && a.oFeatures.bFilter) { f = Da(a); e = 1 } else if (h == "r" && a.oFeatures.bProcessing) { f = Ea(a); e = 1 } else if (h == "t") { f = Fa(a); e = 1 } else if (h == "i" && a.oFeatures.bInfo) { f = Ga(a); e = 1 } else if (h == "p" && a.oFeatures.bPaginate) { f = Ha(a); e = 1 } else if (o.aoFeatures.length !==
|
|
||||||
0) { j = o.aoFeatures; t = 0; for (k = j.length; t < k; t++) if (h == j[t].cFeature) { if (f = j[t].fnInit(a)) e = 1; break } } if (e == 1 && f !== null) { if (typeof a.aanFeatures[h] != "object") a.aanFeatures[h] = []; a.aanFeatures[h].push(f); c.appendChild(f) }
|
|
||||||
} b.parentNode.replaceChild(a.nTableWrapper, b)
|
|
||||||
} function Fa(a) {
|
|
||||||
if (a.oScroll.sX === "" && a.oScroll.sY === "") return a.nTable; var b = p.createElement("div"), c = p.createElement("div"), d = p.createElement("div"), f = p.createElement("div"), e = p.createElement("div"), h = p.createElement("div"), j = a.nTable.cloneNode(false),
|
|
||||||
k = a.nTable.cloneNode(false), m = a.nTable.getElementsByTagName("thead")[0], t = a.nTable.getElementsByTagName("tfoot").length === 0 ? null : a.nTable.getElementsByTagName("tfoot")[0], q = typeof g.bJQueryUI != "undefined" && g.bJQueryUI ? o.oJUIClasses : o.oStdClasses; c.appendChild(d); e.appendChild(h); f.appendChild(a.nTable); b.appendChild(c); b.appendChild(f); d.appendChild(j); j.appendChild(m); if (t !== null) { b.appendChild(e); h.appendChild(k); k.appendChild(t) } b.className = q.sScrollWrapper; c.className = q.sScrollHead; d.className =
|
|
||||||
q.sScrollHeadInner; f.className = q.sScrollBody; e.className = q.sScrollFoot; h.className = q.sScrollFootInner; if (a.oScroll.bAutoCss) { c.style.overflow = "hidden"; c.style.position = "relative"; e.style.overflow = "hidden"; f.style.overflow = "auto" } c.style.border = "0"; c.style.width = "100%"; e.style.border = "0"; d.style.width = "150%"; j.removeAttribute("id"); j.style.marginLeft = "0"; a.nTable.style.marginLeft = "0"; if (t !== null) { k.removeAttribute("id"); k.style.marginLeft = "0" } d = i(">caption", a.nTable); h = 0; for (k = d.length; h < k; h++) j.appendChild(d[h]);
|
|
||||||
if (a.oScroll.sX !== "") { c.style.width = u(a.oScroll.sX); f.style.width = u(a.oScroll.sX); if (t !== null) e.style.width = u(a.oScroll.sX); i(f).scroll(function () { c.scrollLeft = this.scrollLeft; if (t !== null) e.scrollLeft = this.scrollLeft }) } if (a.oScroll.sY !== "") f.style.height = u(a.oScroll.sY); a.aoDrawCallback.push({ fn: Ia, sName: "scrolling" }); a.oScroll.bInfinite && i(f).scroll(function () {
|
|
||||||
if (!a.bDrawing) if (i(this).scrollTop() + i(this).height() > i(a.nTable).height() - a.oScroll.iLoadGap) if (a.fnDisplayEnd() < a.fnRecordsDisplay()) {
|
|
||||||
ja(a,
|
|
||||||
"next"); E(a); C(a)
|
|
||||||
}
|
|
||||||
}); a.nScrollHead = c; a.nScrollFoot = e; return b
|
|
||||||
} function Ia(a) {
|
|
||||||
var b = a.nScrollHead.getElementsByTagName("div")[0], c = b.getElementsByTagName("table")[0], d = a.nTable.parentNode, f, e, h, j, k, m, t, q, I = []; h = a.nTable.getElementsByTagName("thead"); h.length > 0 && a.nTable.removeChild(h[0]); if (a.nTFoot !== null) { k = a.nTable.getElementsByTagName("tfoot"); k.length > 0 && a.nTable.removeChild(k[0]) } h = a.nTHead.cloneNode(true); a.nTable.insertBefore(h, a.nTable.childNodes[0]); if (a.nTFoot !== null) {
|
|
||||||
k = a.nTFoot.cloneNode(true);
|
|
||||||
a.nTable.insertBefore(k, a.nTable.childNodes[1])
|
|
||||||
} if (a.oScroll.sX === "") { d.style.width = "100%"; b.parentNode.style.width = "100%" } var O = S(a, h); f = 0; for (e = O.length; f < e; f++) { t = Ja(a, f); O[f].style.width = a.aoColumns[t].sWidth } a.nTFoot !== null && P(function (B) { B.style.width = "" }, k.getElementsByTagName("tr")); f = i(a.nTable).outerWidth(); if (a.oScroll.sX === "") { a.nTable.style.width = "100%"; if (i.browser.msie && i.browser.version <= 7) a.nTable.style.width = u(i(a.nTable).outerWidth() - a.oScroll.iBarWidth) } else if (a.oScroll.sXInner !==
|
|
||||||
"") a.nTable.style.width = u(a.oScroll.sXInner); else if (f == i(d).width() && i(d).height() < i(a.nTable).height()) { a.nTable.style.width = u(f - a.oScroll.iBarWidth); if (i(a.nTable).outerWidth() > f - a.oScroll.iBarWidth) a.nTable.style.width = u(f) } else a.nTable.style.width = u(f); f = i(a.nTable).outerWidth(); if (a.oScroll.sX === "") { d.style.width = u(f + a.oScroll.iBarWidth); b.parentNode.style.width = u(f + a.oScroll.iBarWidth) } e = a.nTHead.getElementsByTagName("tr"); h = h.getElementsByTagName("tr"); P(function (B, F) {
|
|
||||||
m = B.style; m.paddingTop =
|
|
||||||
"0"; m.paddingBottom = "0"; m.borderTopWidth = "0"; m.borderBottomWidth = "0"; m.height = 0; q = i(B).width(); F.style.width = u(q); I.push(q)
|
|
||||||
}, h, e); i(h).height(0); if (a.nTFoot !== null) { j = k.getElementsByTagName("tr"); k = a.nTFoot.getElementsByTagName("tr"); P(function (B, F) { m = B.style; m.paddingTop = "0"; m.paddingBottom = "0"; m.borderTopWidth = "0"; m.borderBottomWidth = "0"; m.height = 0; q = i(B).width(); F.style.width = u(q); I.push(q) }, j, k); i(j).height(0) } P(function (B) { B.innerHTML = ""; B.style.width = u(I.shift()) }, h); a.nTFoot !== null && P(function (B) {
|
|
||||||
B.innerHTML =
|
|
||||||
""; B.style.width = u(I.shift())
|
|
||||||
}, j); if (i(a.nTable).outerWidth() < f) if (a.oScroll.sX === "") J(a, 1, "The table cannot fit into the current element which will cause column misalignment. It is suggested that you enable x-scrolling or increase the width the table has in which to be drawn"); else a.oScroll.sXInner !== "" && J(a, 1, "The table cannot fit into the current element which will cause column misalignment. It is suggested that you increase the sScrollXInner property to allow it to draw in a larger area, or simply remove that parameter to allow automatic calculation");
|
|
||||||
if (a.oScroll.sY === "") if (i.browser.msie && i.browser.version <= 7) d.style.height = u(a.nTable.offsetHeight + a.oScroll.iBarWidth); if (a.oScroll.sY !== "" && a.oScroll.bCollapse) { d.style.height = u(a.oScroll.sY); j = a.oScroll.sX !== "" && a.nTable.offsetWidth > d.offsetWidth ? a.oScroll.iBarWidth : 0; if (a.nTable.offsetHeight < d.offsetHeight) d.style.height = u(i(a.nTable).height() + j) } j = i(a.nTable).outerWidth(); c.style.width = u(j); b.style.width = u(j + a.oScroll.iBarWidth); if (a.nTFoot !== null) {
|
|
||||||
b = a.nScrollFoot.getElementsByTagName("div")[0];
|
|
||||||
c = b.getElementsByTagName("table")[0]; b.style.width = u(a.nTable.offsetWidth + a.oScroll.iBarWidth); c.style.width = u(a.nTable.offsetWidth)
|
|
||||||
} if (a.bSorted || a.bFiltered) d.scrollTop = 0
|
|
||||||
}
|
|
||||||
// sFilter
|
|
||||||
function ca(a) { if (a.oFeatures.bAutoWidth === false) return false; ea(a); for (var b = 0, c = a.aoColumns.length; b < c; b++) a.aoColumns[b].nTh.style.width = a.aoColumns[b].sWidth } function Da(a) {
|
|
||||||
var b = a.oLanguage.sSearch;
|
|
||||||
b = b.indexOf("_INPUT_") !== -1 ? b.replace("_INPUT_", '<input type="text" />') : b === "" ? '<input type="text" />' : b + ' <input type="text" />';
|
|
||||||
var c = p.createElement("div");
|
|
||||||
c.className = a.oClasses.sFilter;
|
|
||||||
c.innerHTML = "<div>" + b + "</div>";
|
|
||||||
a.sTableId !== "" && typeof a.aanFeatures.f == "undefined" && c.setAttribute("id", a.sTableId + "_filter");
|
|
||||||
b = i("input", c);
|
|
||||||
b.val(a.oPreviousSearch.sSearch.replace('"', """));
|
|
||||||
b.bind("keyup.DT", function () {
|
|
||||||
for (var d = a.aanFeatures.f, f = 0, e = d.length;
|
|
||||||
f < e; f++)
|
|
||||||
d[f] != this.parentNode && i("input", d[f]).val(this.value);
|
|
||||||
this.value != a.oPreviousSearch.sSearch && M(a, {
|
|
||||||
sSearch: this.value, bRegex: a.oPreviousSearch.bRegex, bSmart: a.oPreviousSearch.bSmart
|
|
||||||
})
|
|
||||||
});
|
|
||||||
b.bind("keypress.DT", function (d) {
|
|
||||||
if (d.keyCode == 13) return false
|
|
||||||
});
|
|
||||||
return c
|
|
||||||
|
|
||||||
} function M(a, b, c) { Ka(a, b.sSearch, c, b.bRegex, b.bSmart); for (b = 0; b < a.aoPreSearchCols.length; b++) La(a, a.aoPreSearchCols[b].sSearch, b, a.aoPreSearchCols[b].bRegex, a.aoPreSearchCols[b].bSmart); o.afnFiltering.length !== 0 && Ma(a); a.bFiltered = true; a._iDisplayStart = 0; E(a); C(a); ka(a, 0) } function Ma(a) {
|
|
||||||
for (var b = o.afnFiltering, c = 0, d = b.length; c < d; c++) for (var f = 0, e = 0, h = a.aiDisplay.length; e < h; e++) {
|
|
||||||
var j = a.aiDisplay[e - f];
|
|
||||||
if (!b[c](a, da(a, j, "filter"),
|
|
||||||
j)) {
|
|
||||||
a.aiDisplay.splice(e - f, 1); f++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function La(a, b, c, d, f) { if (b !== "") { var e = 0; b = la(b, d, f); for (d = a.aiDisplay.length - 1; d >= 0; d--) { f = ma(H(a, a.aiDisplay[d], c, "filter"), a.aoColumns[c].sType); if (!b.test(f)) { a.aiDisplay.splice(d, 1); e++ } } } } function Ka(a, b, c, d, f) {
|
|
||||||
var e = la(b, d, f); if (typeof c == "undefined" || c === null) c = 0; if (o.afnFiltering.length !== 0) c = 1; if (b.length <= 0) { a.aiDisplay.splice(0, a.aiDisplay.length); a.aiDisplay = a.aiDisplayMaster.slice() } else if (a.aiDisplay.length == a.aiDisplayMaster.length || a.oPreviousSearch.sSearch.length >
|
|
||||||
b.length || c == 1 || b.indexOf(a.oPreviousSearch.sSearch) !== 0) { a.aiDisplay.splice(0, a.aiDisplay.length); ka(a, 1); for (c = 0; c < a.aiDisplayMaster.length; c++) e.test(a.asDataSearch[c]) && a.aiDisplay.push(a.aiDisplayMaster[c]) } else { var h = 0; for (c = 0; c < a.asDataSearch.length; c++) if (!e.test(a.asDataSearch[c])) { a.aiDisplay.splice(c - h, 1); h++ } } a.oPreviousSearch.sSearch = b; a.oPreviousSearch.bRegex = d; a.oPreviousSearch.bSmart = f
|
|
||||||
} function ka(a, b) {
|
|
||||||
a.asDataSearch.splice(0, a.asDataSearch.length); b = typeof b != "undefined" && b == 1 ? a.aiDisplayMaster :
|
|
||||||
a.aiDisplay; for (var c = 0, d = b.length; c < d; c++) a.asDataSearch[c] = na(a, da(a, b[c], "filter"))
|
|
||||||
} function na(a, b) { var c = ""; if (typeof a.__nTmpFilter == "undefined") a.__nTmpFilter = p.createElement("div"); for (var d = a.__nTmpFilter, f = 0, e = a.aoColumns.length; f < e; f++) if (a.aoColumns[f].bSearchable) c += ma(b[f], a.aoColumns[f].sType) + " "; if (c.indexOf("&") !== -1) { d.innerHTML = c; c = d.textContent ? d.textContent : d.innerText; c = c.replace(/\n/g, " ").replace(/\r/g, "") } return c } function la(a, b, c) {
|
|
||||||
if (c) {
|
|
||||||
a = b ? a.split(" ") : oa(a).split(" ");
|
|
||||||
a = "^(?=.*?" + a.join(")(?=.*?") + ").*$"; return new RegExp(a, "i")
|
|
||||||
} else { a = b ? a : oa(a); return new RegExp(a, "i") }
|
|
||||||
} function ma(a, b) { if (typeof o.ofnSearch[b] == "function") return o.ofnSearch[b](a); else if (b == "html") return a.replace(/\n/g, " ").replace(/<.*?>/g, ""); else if (typeof a == "string") return a.replace(/\n/g, " "); else if (a === null) return ""; return a } function R(a, b) {
|
|
||||||
var c, d, f, e, h = [], j = [], k = o.oSort; d = a.aoData; var m = a.aoColumns; if (!a.oFeatures.bServerSide && (a.aaSorting.length !== 0 || a.aaSortingFixed !== null)) {
|
|
||||||
h = a.aaSortingFixed !==
|
|
||||||
null ? a.aaSortingFixed.concat(a.aaSorting) : a.aaSorting.slice(); for (c = 0; c < h.length; c++) { var t = h[c][0]; f = pa(a, t); e = a.aoColumns[t].sSortDataType; if (typeof o.afnSortData[e] != "undefined") { var q = o.afnSortData[e](a, t, f); f = 0; for (e = d.length; f < e; f++) N(a, f, t, q[f]) } } c = 0; for (d = a.aiDisplayMaster.length; c < d; c++) j[a.aiDisplayMaster[c]] = c; var I = h.length; a.aiDisplayMaster.sort(function (O, B) {
|
|
||||||
var F, qa; for (c = 0; c < I; c++) {
|
|
||||||
F = m[h[c][0]].iDataSort; qa = m[F].sType; F = k[(qa ? qa : "string") + "-" + h[c][1]](H(a, O, F, "sort"), H(a, B, F, "sort"));
|
|
||||||
if (F !== 0) return F
|
|
||||||
} return k["numeric-asc"](j[O], j[B])
|
|
||||||
})
|
|
||||||
} if ((typeof b == "undefined" || b) && !a.oFeatures.bDeferRender) T(a); a.bSorted = true; if (a.oFeatures.bFilter) M(a, a.oPreviousSearch, 1); else { a.aiDisplay = a.aiDisplayMaster.slice(); a._iDisplayStart = 0; E(a); C(a) }
|
|
||||||
} function ga(a, b, c, d) {
|
|
||||||
i(b).bind("click.DT", function (f) {
|
|
||||||
if (a.aoColumns[c].bSortable !== false) {
|
|
||||||
var e = function () {
|
|
||||||
var h, j; if (f.shiftKey) {
|
|
||||||
for (var k = false, m = 0; m < a.aaSorting.length; m++) if (a.aaSorting[m][0] == c) {
|
|
||||||
k = true; h = a.aaSorting[m][0]; j = a.aaSorting[m][2] +
|
|
||||||
1; if (typeof a.aoColumns[h].asSorting[j] == "undefined") a.aaSorting.splice(m, 1); else { a.aaSorting[m][1] = a.aoColumns[h].asSorting[j]; a.aaSorting[m][2] = j } break
|
|
||||||
} k === false && a.aaSorting.push([c, a.aoColumns[c].asSorting[0], 0])
|
|
||||||
} else if (a.aaSorting.length == 1 && a.aaSorting[0][0] == c) { h = a.aaSorting[0][0]; j = a.aaSorting[0][2] + 1; if (typeof a.aoColumns[h].asSorting[j] == "undefined") j = 0; a.aaSorting[0][1] = a.aoColumns[h].asSorting[j]; a.aaSorting[0][2] = j } else {
|
|
||||||
a.aaSorting.splice(0, a.aaSorting.length); a.aaSorting.push([c, a.aoColumns[c].asSorting[0],
|
|
||||||
0])
|
|
||||||
} R(a)
|
|
||||||
}; if (a.oFeatures.bProcessing) { K(a, true); setTimeout(function () { e(); a.oFeatures.bServerSide || K(a, false) }, 0) } else e(); typeof d == "function" && d(a)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} function T(a) {
|
|
||||||
var b, c, d, f, e, h = a.aoColumns.length, j = a.oClasses; for (b = 0; b < h; b++) a.aoColumns[b].bSortable && i(a.aoColumns[b].nTh).removeClass(j.sSortAsc + " " + j.sSortDesc + " " + a.aoColumns[b].sSortingClass); f = a.aaSortingFixed !== null ? a.aaSortingFixed.concat(a.aaSorting) : a.aaSorting.slice(); for (b = 0; b < a.aoColumns.length; b++) if (a.aoColumns[b].bSortable) {
|
|
||||||
e = a.aoColumns[b].sSortingClass;
|
|
||||||
d = -1; for (c = 0; c < f.length; c++) if (f[c][0] == b) { e = f[c][1] == "asc" ? j.sSortAsc : j.sSortDesc; d = c; break } i(a.aoColumns[b].nTh).addClass(e); if (a.bJUI) { c = i("span", a.aoColumns[b].nTh); c.removeClass(j.sSortJUIAsc + " " + j.sSortJUIDesc + " " + j.sSortJUI + " " + j.sSortJUIAscAllowed + " " + j.sSortJUIDescAllowed); c.addClass(d == -1 ? a.aoColumns[b].sSortingClassJUI : f[d][1] == "asc" ? j.sSortJUIAsc : j.sSortJUIDesc) }
|
|
||||||
} else i(a.aoColumns[b].nTh).addClass(a.aoColumns[b].sSortingClass); e = j.sSortColumn; if (a.oFeatures.bSort && a.oFeatures.bSortClasses) {
|
|
||||||
d =
|
|
||||||
Q(a); if (a.oFeatures.bDeferRender) i(d).removeClass(e + "1 " + e + "2 " + e + "3"); else if (d.length >= h) for (b = 0; b < h; b++) if (d[b].className.indexOf(e + "1") != -1) { c = 0; for (a = d.length / h; c < a; c++) d[h * c + b].className = i.trim(d[h * c + b].className.replace(e + "1", "")) } else if (d[b].className.indexOf(e + "2") != -1) { c = 0; for (a = d.length / h; c < a; c++) d[h * c + b].className = i.trim(d[h * c + b].className.replace(e + "2", "")) } else if (d[b].className.indexOf(e + "3") != -1) {
|
|
||||||
c = 0; for (a = d.length / h; c < a; c++) d[h * c + b].className = i.trim(d[h * c + b].className.replace(" " +
|
|
||||||
e + "3", ""))
|
|
||||||
} j = 1; var k; for (b = 0; b < f.length; b++) { k = parseInt(f[b][0], 10); c = 0; for (a = d.length / h; c < a; c++) d[h * c + k].className += " " + e + j; j < 3 && j++ }
|
|
||||||
}
|
|
||||||
} function Ha(a) { if (a.oScroll.bInfinite) return null; var b = p.createElement("div"); b.className = a.oClasses.sPaging + a.sPaginationType; o.oPagination[a.sPaginationType].fnInit(a, b, function (c) { E(c); C(c) }); typeof a.aanFeatures.p == "undefined" && a.aoDrawCallback.push({ fn: function (c) { o.oPagination[c.sPaginationType].fnUpdate(c, function (d) { E(d); C(d) }) }, sName: "pagination" }); return b }
|
|
||||||
function ja(a, b) {
|
|
||||||
var c = a._iDisplayStart; if (b == "first") a._iDisplayStart = 0; else if (b == "previous") { a._iDisplayStart = a._iDisplayLength >= 0 ? a._iDisplayStart - a._iDisplayLength : 0; if (a._iDisplayStart < 0) a._iDisplayStart = 0 } else if (b == "next") if (a._iDisplayLength >= 0) { if (a._iDisplayStart + a._iDisplayLength < a.fnRecordsDisplay()) a._iDisplayStart += a._iDisplayLength } else a._iDisplayStart = 0; else if (b == "last") if (a._iDisplayLength >= 0) { b = parseInt((a.fnRecordsDisplay() - 1) / a._iDisplayLength, 10) + 1; a._iDisplayStart = (b - 1) * a._iDisplayLength } else a._iDisplayStart =
|
|
||||||
0; else J(a, 0, "Unknown paging action: " + b); return c != a._iDisplayStart
|
|
||||||
} function Ga(a) { var b = p.createElement("div"); b.className = a.oClasses.sInfo; if (typeof a.aanFeatures.i == "undefined") { a.aoDrawCallback.push({ fn: Na, sName: "information" }); a.sTableId !== "" && b.setAttribute("id", a.sTableId + "_info") } return b } function Na(a) {
|
|
||||||
if (!(!a.oFeatures.bInfo || a.aanFeatures.i.length === 0)) {
|
|
||||||
var b = a._iDisplayStart + 1, c = a.fnDisplayEnd(), d = a.fnRecordsTotal(), f = a.fnRecordsDisplay(), e = a.fnFormatNumber(b), h = a.fnFormatNumber(c), j =
|
|
||||||
a.fnFormatNumber(d), k = a.fnFormatNumber(f); if (a.oScroll.bInfinite) e = a.fnFormatNumber(1); e = a.fnRecordsDisplay() === 0 && a.fnRecordsDisplay() == a.fnRecordsTotal() ? a.oLanguage.sInfoEmpty + a.oLanguage.sInfoPostFix : a.fnRecordsDisplay() === 0 ? a.oLanguage.sInfoEmpty + " " + a.oLanguage.sInfoFiltered.replace("_MAX_", j) + a.oLanguage.sInfoPostFix : a.fnRecordsDisplay() == a.fnRecordsTotal() ? a.oLanguage.sInfo.replace("_START_", e).replace("_END_", h).replace("_TOTAL_", k) + a.oLanguage.sInfoPostFix : a.oLanguage.sInfo.replace("_START_",
|
|
||||||
e).replace("_END_", h).replace("_TOTAL_", k) + " " + a.oLanguage.sInfoFiltered.replace("_MAX_", a.fnFormatNumber(a.fnRecordsTotal())) + a.oLanguage.sInfoPostFix; if (a.oLanguage.fnInfoCallback !== null) e = a.oLanguage.fnInfoCallback(a, b, c, d, f, e); a = a.aanFeatures.i; b = 0; for (c = a.length; b < c; b++) i(a[b]).html(e)
|
|
||||||
}
|
|
||||||
} function Ca(a) {
|
|
||||||
if (a.oScroll.bInfinite) return null; var b = '<select size="1" ' + (a.sTableId === "" ? "" : 'name="' + a.sTableId + '_length"') + ">", c, d; if (a.aLengthMenu.length == 2 && typeof a.aLengthMenu[0] == "object" && typeof a.aLengthMenu[1] ==
|
|
||||||
"object") { c = 0; for (d = a.aLengthMenu[0].length; c < d; c++) b += '<option value="' + a.aLengthMenu[0][c] + '">' + a.aLengthMenu[1][c] + "</option>" } else { c = 0; for (d = a.aLengthMenu.length; c < d; c++) b += '<option value="' + a.aLengthMenu[c] + '">' + a.aLengthMenu[c] + "</option>" } b += "</select>"; var f = p.createElement("div"); a.sTableId !== "" && typeof a.aanFeatures.l == "undefined" && f.setAttribute("id", a.sTableId + "_length"); f.className = a.oClasses.sLength; f.innerHTML = "<label>" + a.oLanguage.sLengthMenu.replace("_MENU_", b) + "</label>"; i('select option[value="' +
|
|
||||||
a._iDisplayLength + '"]', f).attr("selected", true); i("select", f).bind("change.DT", function () { var e = i(this).val(), h = a.aanFeatures.l; c = 0; for (d = h.length; c < d; c++) h[c] != this.parentNode && i("select", h[c]).val(e); a._iDisplayLength = parseInt(e, 10); E(a); if (a.fnDisplayEnd() == a.fnRecordsDisplay()) { a._iDisplayStart = a.fnDisplayEnd() - a._iDisplayLength; if (a._iDisplayStart < 0) a._iDisplayStart = 0 } if (a._iDisplayLength == -1) a._iDisplayStart = 0; C(a) }); return f
|
|
||||||
} function Ea(a) {
|
|
||||||
var b = p.createElement("div"); a.sTableId !== "" && typeof a.aanFeatures.r ==
|
|
||||||
"undefined" && b.setAttribute("id", a.sTableId + "_processing"); b.innerHTML = a.oLanguage.sProcessing; b.className = a.oClasses.sProcessing; a.nTable.parentNode.insertBefore(b, a.nTable); return b
|
|
||||||
} function K(a, b) { if (a.oFeatures.bProcessing) { a = a.aanFeatures.r; for (var c = 0, d = a.length; c < d; c++) a[c].style.visibility = b ? "visible" : "hidden" } } function Ja(a, b) { for (var c = -1, d = 0; d < a.aoColumns.length; d++) { a.aoColumns[d].bVisible === true && c++; if (c == b) return d } return null } function pa(a, b) {
|
|
||||||
for (var c = -1, d = 0; d < a.aoColumns.length; d++) {
|
|
||||||
a.aoColumns[d].bVisible ===
|
|
||||||
true && c++; if (d == b) return a.aoColumns[d].bVisible === true ? c : null
|
|
||||||
} return null
|
|
||||||
} function U(a, b) { var c, d; c = a._iDisplayStart; for (d = a._iDisplayEnd; c < d; c++) if (a.aoData[a.aiDisplay[c]].nTr == b) return a.aiDisplay[c]; c = 0; for (d = a.aoData.length; c < d; c++) if (a.aoData[c].nTr == b) return c; return null } function X(a) { for (var b = 0, c = 0; c < a.aoColumns.length; c++) a.aoColumns[c].bVisible === true && b++; return b } function E(a) {
|
|
||||||
a._iDisplayEnd = a.oFeatures.bPaginate === false ? a.aiDisplay.length : a._iDisplayStart + a._iDisplayLength > a.aiDisplay.length ||
|
|
||||||
a._iDisplayLength == -1 ? a.aiDisplay.length : a._iDisplayStart + a._iDisplayLength
|
|
||||||
} function Oa(a, b) { if (!a || a === null || a === "") return 0; if (typeof b == "undefined") b = p.getElementsByTagName("body")[0]; var c = p.createElement("div"); c.style.width = u(a); b.appendChild(c); a = c.offsetWidth; b.removeChild(c); return a } function ea(a) {
|
|
||||||
var b = 0, c, d = 0, f = a.aoColumns.length, e, h = i("th", a.nTHead); for (e = 0; e < f; e++) if (a.aoColumns[e].bVisible) {
|
|
||||||
d++; if (a.aoColumns[e].sWidth !== null) {
|
|
||||||
c = Oa(a.aoColumns[e].sWidthOrig, a.nTable.parentNode); if (c !==
|
|
||||||
null) a.aoColumns[e].sWidth = u(c); b++
|
|
||||||
}
|
|
||||||
} if (f == h.length && b === 0 && d == f && a.oScroll.sX === "" && a.oScroll.sY === "") for (e = 0; e < a.aoColumns.length; e++) { c = i(h[e]).width(); if (c !== null) a.aoColumns[e].sWidth = u(c) } else {
|
|
||||||
b = a.nTable.cloneNode(false); e = a.nTHead.cloneNode(true); d = p.createElement("tbody"); c = p.createElement("tr"); b.removeAttribute("id"); b.appendChild(e); if (a.nTFoot !== null) { b.appendChild(a.nTFoot.cloneNode(true)); P(function (k) { k.style.width = "" }, b.getElementsByTagName("tr")) } b.appendChild(d); d.appendChild(c);
|
|
||||||
d = i("thead th", b); if (d.length === 0) d = i("tbody tr:eq(0)>td", b); h = S(a, e); for (e = d = 0; e < f; e++) { var j = a.aoColumns[e]; if (j.bVisible && j.sWidthOrig !== null && j.sWidthOrig !== "") h[e - d].style.width = u(j.sWidthOrig); else if (j.bVisible) h[e - d].style.width = ""; else d++ } for (e = 0; e < f; e++) if (a.aoColumns[e].bVisible) { d = Pa(a, e); if (d !== null) { d = d.cloneNode(true); if (a.aoColumns[e].sContentPadding !== "") d.innerHTML += a.aoColumns[e].sContentPadding; c.appendChild(d) } } f = a.nTable.parentNode; f.appendChild(b); if (a.oScroll.sX !== "" && a.oScroll.sXInner !==
|
|
||||||
"") b.style.width = u(a.oScroll.sXInner); else if (a.oScroll.sX !== "") { b.style.width = ""; if (i(b).width() < f.offsetWidth) b.style.width = u(f.offsetWidth) } else if (a.oScroll.sY !== "") b.style.width = u(f.offsetWidth); b.style.visibility = "hidden"; Qa(a, b); f = i("tbody tr:eq(0)", b).children(); if (f.length === 0) f = S(a, i("thead", b)[0]); if (a.oScroll.sX !== "") {
|
|
||||||
for (e = d = c = 0; e < a.aoColumns.length; e++) if (a.aoColumns[e].bVisible) {
|
|
||||||
c += a.aoColumns[e].sWidthOrig === null ? i(f[d]).outerWidth() : parseInt(a.aoColumns[e].sWidth.replace("px", ""),
|
|
||||||
10) + (i(f[d]).outerWidth() - i(f[d]).width()); d++
|
|
||||||
} b.style.width = u(c); a.nTable.style.width = u(c)
|
|
||||||
} for (e = d = 0; e < a.aoColumns.length; e++) if (a.aoColumns[e].bVisible) { c = i(f[d]).width(); if (c !== null && c > 0) a.aoColumns[e].sWidth = u(c); d++ } a.nTable.style.width = u(i(b).outerWidth()); b.parentNode.removeChild(b)
|
|
||||||
}
|
|
||||||
} function Qa(a, b) { if (a.oScroll.sX === "" && a.oScroll.sY !== "") { i(b).width(); b.style.width = u(i(b).outerWidth() - a.oScroll.iBarWidth) } else if (a.oScroll.sX !== "") b.style.width = u(i(b).outerWidth()) } function Pa(a, b) {
|
|
||||||
var c =
|
|
||||||
Ra(a, b); if (c < 0) return null; if (a.aoData[c].nTr === null) { var d = p.createElement("td"); d.innerHTML = H(a, c, b, ""); return d } return Q(a, c)[b]
|
|
||||||
} function Ra(a, b) { for (var c = -1, d = -1, f = 0; f < a.aoData.length; f++) { var e = H(a, f, b, "display") + ""; e = e.replace(/<.*?>/g, ""); if (e.length > c) { c = e.length; d = f } } return d } function u(a) { if (a === null) return "0px"; if (typeof a == "number") { if (a < 0) return "0px"; return a + "px" } var b = a.charCodeAt(a.length - 1); if (b < 48 || b > 57) return a; return a + "px" } function Va(a, b) {
|
|
||||||
if (a.length != b.length) return 1; for (var c =
|
|
||||||
0; c < a.length; c++) if (a[c] != b[c]) return 2; return 0
|
|
||||||
} function fa(a) { for (var b = o.aTypes, c = b.length, d = 0; d < c; d++) { var f = b[d](a); if (f !== null) return f } return "string" } function A(a) { for (var b = 0; b < D.length; b++) if (D[b].nTable == a) return D[b]; return null } function aa(a) { for (var b = [], c = a.aoData.length, d = 0; d < c; d++) b.push(a.aoData[d]._aData); return b } function $(a) { for (var b = [], c = 0, d = a.aoData.length; c < d; c++) a.aoData[c].nTr !== null && b.push(a.aoData[c].nTr); return b } function Q(a, b) {
|
|
||||||
var c = [], d, f, e, h, j; f = 0; var k = a.aoData.length;
|
|
||||||
if (typeof b != "undefined") { f = b; k = b + 1 } for (f = f; f < k; f++) { j = a.aoData[f]; if (j.nTr !== null) { b = []; e = 0; for (h = j.nTr.childNodes.length; e < h; e++) { d = j.nTr.childNodes[e].nodeName.toLowerCase(); if (d == "td" || d == "th") b.push(j.nTr.childNodes[e]) } e = d = 0; for (h = a.aoColumns.length; e < h; e++) if (a.aoColumns[e].bVisible) c.push(b[e - d]); else { c.push(j._anHidden[e]); d++ } } } return c
|
|
||||||
} function oa(a) { return a.replace(new RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^)", "g"), "\\$1") } function ra(a, b) {
|
|
||||||
for (var c = -1, d =
|
|
||||||
0, f = a.length; d < f; d++) if (a[d] == b) c = d; else a[d] > b && a[d]--; c != -1 && a.splice(c, 1)
|
|
||||||
} function Ba(a, b) { b = b.split(","); for (var c = [], d = 0, f = a.aoColumns.length; d < f; d++) for (var e = 0; e < f; e++) if (a.aoColumns[d].sName == b[e]) { c.push(e); break } return c } function ha(a) { for (var b = "", c = 0, d = a.aoColumns.length; c < d; c++) b += a.aoColumns[c].sName + ","; if (b.length == d) return ""; return b.slice(0, -1) } function J(a, b, c) {
|
|
||||||
a = a.sTableId === "" ? "DataTables warning: " + c : "DataTables warning (table id = '" + a.sTableId + "'): " + c; if (b === 0) if (o.sErrMode ==
|
|
||||||
"alert") alert(a); else throw a; else typeof console != "undefined" && typeof console.log != "undefined" && console.log(a)
|
|
||||||
} function ia(a) { a.aoData.splice(0, a.aoData.length); a.aiDisplayMaster.splice(0, a.aiDisplayMaster.length); a.aiDisplay.splice(0, a.aiDisplay.length); E(a) } function sa(a) {
|
|
||||||
if (!(!a.oFeatures.bStateSave || typeof a.bDestroying != "undefined")) {
|
|
||||||
var b, c, d, f = "{"; f += '"iCreate":' + (new Date).getTime() + ","; f += '"iStart":' + (a.oScroll.bInfinite ? 0 : a._iDisplayStart) + ","; f += '"iEnd":' + (a.oScroll.bInfinite ? a._iDisplayLength :
|
|
||||||
a._iDisplayEnd) + ","; f += '"iLength":' + a._iDisplayLength + ","; f += '"sFilter":"' + encodeURIComponent(a.oPreviousSearch.sSearch) + '",'; f += '"sFilterEsc":' + !a.oPreviousSearch.bRegex + ","; f += '"aaSorting":[ '; for (b = 0; b < a.aaSorting.length; b++) f += "[" + a.aaSorting[b][0] + ',"' + a.aaSorting[b][1] + '"],'; f = f.substring(0, f.length - 1); f += "],"; f += '"aaSearchCols":[ '; for (b = 0; b < a.aoPreSearchCols.length; b++) f += '["' + encodeURIComponent(a.aoPreSearchCols[b].sSearch) + '",' + !a.aoPreSearchCols[b].bRegex + "],"; f = f.substring(0, f.length -
|
|
||||||
1); f += "],"; f += '"abVisCols":[ '; for (b = 0; b < a.aoColumns.length; b++) f += a.aoColumns[b].bVisible + ","; f = f.substring(0, f.length - 1); f += "]"; b = 0; for (c = a.aoStateSave.length; b < c; b++) { d = a.aoStateSave[b].fn(a, f); if (d !== "") f = d } f += "}"; Sa(a.sCookiePrefix + a.sInstance, f, a.iCookieDuration, a.sCookiePrefix, a.fnCookieCallback)
|
|
||||||
}
|
|
||||||
} function Ta(a, b) {
|
|
||||||
if (a.oFeatures.bStateSave) {
|
|
||||||
var c, d, f; d = ta(a.sCookiePrefix + a.sInstance); if (d !== null && d !== "") {
|
|
||||||
try { c = typeof i.parseJSON == "function" ? i.parseJSON(d.replace(/'/g, '"')) : eval("(" + d + ")") } catch (e) { return } d =
|
|
||||||
0; for (f = a.aoStateLoad.length; d < f; d++) if (!a.aoStateLoad[d].fn(a, c)) return; a.oLoadedState = i.extend(true, {}, c); a._iDisplayStart = c.iStart; a.iInitDisplayStart = c.iStart; a._iDisplayEnd = c.iEnd; a._iDisplayLength = c.iLength; a.oPreviousSearch.sSearch = decodeURIComponent(c.sFilter); a.aaSorting = c.aaSorting.slice(); a.saved_aaSorting = c.aaSorting.slice(); if (typeof c.sFilterEsc != "undefined") a.oPreviousSearch.bRegex = !c.sFilterEsc; if (typeof c.aaSearchCols != "undefined") for (d = 0; d < c.aaSearchCols.length; d++) a.aoPreSearchCols[d] =
|
|
||||||
{ sSearch: decodeURIComponent(c.aaSearchCols[d][0]), bRegex: !c.aaSearchCols[d][1] }; if (typeof c.abVisCols != "undefined") { b.saved_aoColumns = []; for (d = 0; d < c.abVisCols.length; d++) { b.saved_aoColumns[d] = {}; b.saved_aoColumns[d].bVisible = c.abVisCols[d] } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function Sa(a, b, c, d, f) {
|
|
||||||
var e = new Date; e.setTime(e.getTime() + c * 1E3); c = wa.location.pathname.split("/"); a = a + "_" + c.pop().replace(/[\/:]/g, "").toLowerCase(); var h; if (f !== null) {
|
|
||||||
h = typeof i.parseJSON == "function" ? i.parseJSON(b) : eval("(" + b + ")"); b = f(a, h, e.toGMTString(),
|
|
||||||
c.join("/") + "/")
|
|
||||||
} else b = a + "=" + encodeURIComponent(b) + "; expires=" + e.toGMTString() + "; path=" + c.join("/") + "/"; f = ""; e = 9999999999999; if ((ta(a) !== null ? p.cookie.length : b.length + p.cookie.length) + 10 > 4096) {
|
|
||||||
a = p.cookie.split(";"); for (var j = 0, k = a.length; j < k; j++) if (a[j].indexOf(d) != -1) { var m = a[j].split("="); try { h = eval("(" + decodeURIComponent(m[1]) + ")") } catch (t) { continue } if (typeof h.iCreate != "undefined" && h.iCreate < e) { f = m[0]; e = h.iCreate } } if (f !== "") p.cookie = f + "=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=" + c.join("/") +
|
|
||||||
"/"
|
|
||||||
} p.cookie = b
|
|
||||||
} function ta(a) { var b = wa.location.pathname.split("/"); a = a + "_" + b[b.length - 1].replace(/[\/:]/g, "").toLowerCase() + "="; b = p.cookie.split(";"); for (var c = 0; c < b.length; c++) { for (var d = b[c]; d.charAt(0) == " "; ) d = d.substring(1, d.length); if (d.indexOf(a) === 0) return decodeURIComponent(d.substring(a.length, d.length)) } return null } function W(a, b) {
|
|
||||||
b = b.getElementsByTagName("tr"); var c, d, f, e, h, j, k, m, t = function (O, B, F) { for (; typeof O[B][F] != "undefined"; ) F++; return F }; a.splice(0, a.length); d = 0; for (j = b.length; d <
|
|
||||||
j; d++) a.push([]); d = 0; for (j = b.length; d < j; d++) { f = 0; for (k = b[d].childNodes.length; f < k; f++) { c = b[d].childNodes[f]; if (c.nodeName.toUpperCase() == "TD" || c.nodeName.toUpperCase() == "TH") { var q = c.getAttribute("colspan") * 1, I = c.getAttribute("rowspan") * 1; q = !q || q === 0 || q === 1 ? 1 : q; I = !I || I === 0 || I === 1 ? 1 : I; m = t(a, d, 0); for (h = 0; h < q; h++) for (e = 0; e < I; e++) { a[d + e][m + h] = { cell: c, unique: q == 1 ? true : false }; a[d + e].nTr = b[d] } } } }
|
|
||||||
} function S(a, b, c) {
|
|
||||||
var d = []; if (typeof c == "undefined") { c = a.aoHeader; if (typeof b != "undefined") { c = []; W(c, b) } } b = 0;
|
|
||||||
for (var f = c.length; b < f; b++) for (var e = 0, h = c[b].length; e < h; e++) if (c[b][e].unique && (typeof d[e] == "undefined" || !a.bSortCellsTop)) d[e] = c[b][e].cell; return d
|
|
||||||
} function Ua() {
|
|
||||||
var a = p.createElement("p"), b = a.style; b.width = "100%"; b.height = "200px"; var c = p.createElement("div"); b = c.style; b.position = "absolute"; b.top = "0px"; b.left = "0px"; b.visibility = "hidden"; b.width = "200px"; b.height = "150px"; b.overflow = "hidden"; c.appendChild(a); p.body.appendChild(c); b = a.offsetWidth; c.style.overflow = "scroll"; a = a.offsetWidth; if (b == a) a =
|
|
||||||
c.clientWidth; p.body.removeChild(c); return b - a
|
|
||||||
} function P(a, b, c) { for (var d = 0, f = b.length; d < f; d++) for (var e = 0, h = b[d].childNodes.length; e < h; e++) if (b[d].childNodes[e].nodeType == 1) typeof c != "undefined" ? a(b[d].childNodes[e], c[d].childNodes[e]) : a(b[d].childNodes[e]) } function n(a, b, c, d) { if (typeof d == "undefined") d = c; if (typeof b[c] != "undefined") a[d] = b[c] } function da(a, b, c) { for (var d = [], f = 0, e = a.aoColumns.length; f < e; f++) d.push(H(a, b, f, c)); return d } function H(a, b, c, d) {
|
|
||||||
var f = a.aoColumns[c]; if ((c = f.fnGetData(a.aoData[b]._aData)) ===
|
|
||||||
undefined) { if (a.iDrawError != a.iDraw && f.sDefaultContent === null) { J(a, 0, "Requested unknown parameter '" + f.mDataProp + "' from the data source for row " + b); a.iDrawError = a.iDraw } return f.sDefaultContent } if (c === null && f.sDefaultContent !== null) c = f.sDefaultContent; if (d == "display" && c === null) return ""; return c
|
|
||||||
} function N(a, b, c, d) { a.aoColumns[c].fnSetData(a.aoData[b]._aData, d) } function Z(a) {
|
|
||||||
if (a === null) return function () { return null }; else if (typeof a == "function") return function (c) { return a(c) }; else if (typeof a ==
|
|
||||||
"string" && a.indexOf(".") != -1) { var b = a.split("."); return b.length == 2 ? function (c) { return c[b[0]][b[1]] } : b.length == 3 ? function (c) { return c[b[0]][b[1]][b[2]] } : function (c) { for (var d = 0, f = b.length; d < f; d++) c = c[b[d]]; return c } } else return function (c) { return c[a] }
|
|
||||||
} function ya(a) {
|
|
||||||
if (a === null) return function () { }; else if (typeof a == "function") return function (c, d) { return a(c, d) }; else if (typeof a == "string" && a.indexOf(".") != -1) {
|
|
||||||
var b = a.split("."); return b.length == 2 ? function (c, d) { c[b[0]][b[1]] = d } : b.length == 3 ? function (c,
|
|
||||||
d) { c[b[0]][b[1]][b[2]] = d } : function (c, d) { for (var f = 0, e = b.length - 1; f < e; f++) c = c[b[f]]; c[b[b.length - 1]] = d }
|
|
||||||
} else return function (c, d) { c[a] = d }
|
|
||||||
} this.oApi = {}; this.fnDraw = function (a) { var b = A(this[o.iApiIndex]); if (typeof a != "undefined" && a === false) { E(b); C(b) } else ba(b) }; this.fnFilter = function (a, b, c, d, f) {
|
|
||||||
var e = A(this[o.iApiIndex]); if (e.oFeatures.bFilter) {
|
|
||||||
if (typeof c == "undefined") c = false; if (typeof d == "undefined") d = true; if (typeof f == "undefined") f = true; if (typeof b == "undefined" || b === null) {
|
|
||||||
M(e, { sSearch: a, bRegex: c,
|
|
||||||
bSmart: d
|
|
||||||
}, 1); if (f && typeof e.aanFeatures.f != "undefined") { b = e.aanFeatures.f; c = 0; for (d = b.length; c < d; c++) i("input", b[c]).val(a) }
|
|
||||||
} else { e.aoPreSearchCols[b].sSearch = a; e.aoPreSearchCols[b].bRegex = c; e.aoPreSearchCols[b].bSmart = d; M(e, e.oPreviousSearch, 1) }
|
|
||||||
}
|
|
||||||
}; this.fnSettings = function () { return A(this[o.iApiIndex]) }; this.fnVersionCheck = o.fnVersionCheck; this.fnSort = function (a) { var b = A(this[o.iApiIndex]); b.aaSorting = a; R(b) }; this.fnSortListener = function (a, b, c) { ga(A(this[o.iApiIndex]), a, b, c) }; this.fnAddData = function (a,
|
|
||||||
b) { if (a.length === 0) return []; var c = [], d, f = A(this[o.iApiIndex]); if (typeof a[0] == "object") for (var e = 0; e < a.length; e++) { d = v(f, a[e]); if (d == -1) return c; c.push(d) } else { d = v(f, a); if (d == -1) return c; c.push(d) } f.aiDisplay = f.aiDisplayMaster.slice(); if (typeof b == "undefined" || b) ba(f); return c }; this.fnDeleteRow = function (a, b, c) {
|
|
||||||
var d = A(this[o.iApiIndex]); a = typeof a == "object" ? U(d, a) : a; var f = d.aoData.splice(a, 1), e = i.inArray(a, d.aiDisplay); d.asDataSearch.splice(e, 1); ra(d.aiDisplayMaster, a); ra(d.aiDisplay, a); typeof b ==
|
|
||||||
"function" && b.call(this, d, f); if (d._iDisplayStart >= d.aiDisplay.length) { d._iDisplayStart -= d._iDisplayLength; if (d._iDisplayStart < 0) d._iDisplayStart = 0 } if (typeof c == "undefined" || c) { E(d); C(d) } return f
|
|
||||||
}; this.fnClearTable = function (a) { var b = A(this[o.iApiIndex]); ia(b); if (typeof a == "undefined" || a) C(b) }; this.fnOpen = function (a, b, c) {
|
|
||||||
var d = A(this[o.iApiIndex]); this.fnClose(a); var f = p.createElement("tr"), e = p.createElement("td"); f.appendChild(e); e.className = c; e.colSpan = X(d); if (typeof b.jquery != "undefined" || typeof b ==
|
|
||||||
"object") e.appendChild(b); else e.innerHTML = b; b = i("tr", d.nTBody); i.inArray(a, b) != -1 && i(f).insertAfter(a); d.aoOpenRows.push({ nTr: f, nParent: a }); return f
|
|
||||||
}; this.fnClose = function (a) { for (var b = A(this[o.iApiIndex]), c = 0; c < b.aoOpenRows.length; c++) if (b.aoOpenRows[c].nParent == a) { (a = b.aoOpenRows[c].nTr.parentNode) && a.removeChild(b.aoOpenRows[c].nTr); b.aoOpenRows.splice(c, 1); return 0 } return 1 }; this.fnGetData = function (a, b) {
|
|
||||||
var c = A(this[o.iApiIndex]); if (typeof a != "undefined") {
|
|
||||||
a = typeof a == "object" ? U(c, a) : a; if (typeof b !=
|
|
||||||
"undefined") return H(c, a, b, ""); return typeof c.aoData[a] != "undefined" ? c.aoData[a]._aData : null
|
|
||||||
} return aa(c)
|
|
||||||
}; this.fnGetNodes = function (a) { var b = A(this[o.iApiIndex]); if (typeof a != "undefined") return typeof b.aoData[a] != "undefined" ? b.aoData[a].nTr : null; return $(b) }; this.fnGetPosition = function (a) { var b = A(this[o.iApiIndex]), c = a.nodeName.toUpperCase(); if (c == "TR") return U(b, a); else if (c == "TD" || c == "TH") { c = U(b, a.parentNode); for (var d = Q(b, c), f = 0; f < b.aoColumns.length; f++) if (d[f] == a) return [c, pa(b, f), f] } return null };
|
|
||||||
this.fnUpdate = function (a, b, c, d, f) {
|
|
||||||
var e = A(this[o.iApiIndex]); b = typeof b == "object" ? U(e, b) : b; if (i.isArray(a) && typeof a == "object") { e.aoData[b]._aData = a.slice(); for (c = 0; c < e.aoColumns.length; c++) this.fnUpdate(H(e, b, c), b, c, false, false) } else if (typeof a == "object") { e.aoData[b]._aData = i.extend(true, {}, a); for (c = 0; c < e.aoColumns.length; c++) this.fnUpdate(H(e, b, c), b, c, false, false) } else {
|
|
||||||
a = a; N(e, b, c, a); if (e.aoColumns[c].fnRender !== null) {
|
|
||||||
a = e.aoColumns[c].fnRender({ iDataRow: b, iDataColumn: c, aData: e.aoData[b]._aData,
|
|
||||||
oSettings: e
|
|
||||||
}); e.aoColumns[c].bUseRendered && N(e, b, c, a)
|
|
||||||
} if (e.aoData[b].nTr !== null) Q(e, b)[c].innerHTML = a
|
|
||||||
} c = i.inArray(b, e.aiDisplay); e.asDataSearch[c] = na(e, da(e, b, "filter")); if (typeof f == "undefined" || f) ca(e); if (typeof d == "undefined" || d) ba(e); return 0
|
|
||||||
}; this.fnSetColumnVis = function (a, b, c) {
|
|
||||||
var d = A(this[o.iApiIndex]), f, e; e = d.aoColumns.length; var h, j; if (d.aoColumns[a].bVisible != b) {
|
|
||||||
if (b) {
|
|
||||||
for (f = j = 0; f < a; f++) d.aoColumns[f].bVisible && j++; j = j >= X(d); if (!j) for (f = a; f < e; f++) if (d.aoColumns[f].bVisible) { h = f; break } f = 0;
|
|
||||||
for (e = d.aoData.length; f < e; f++) if (d.aoData[f].nTr !== null) j ? d.aoData[f].nTr.appendChild(d.aoData[f]._anHidden[a]) : d.aoData[f].nTr.insertBefore(d.aoData[f]._anHidden[a], Q(d, f)[h])
|
|
||||||
} else { f = 0; for (e = d.aoData.length; f < e; f++) if (d.aoData[f].nTr !== null) { h = Q(d, f)[a]; d.aoData[f]._anHidden[a] = h; h.parentNode.removeChild(h) } } d.aoColumns[a].bVisible = b; L(d, d.aoHeader); d.nTFoot && L(d, d.aoFooter); f = 0; for (e = d.aoOpenRows.length; f < e; f++) d.aoOpenRows[f].nTr.colSpan = X(d); if (typeof c == "undefined" || c) { ca(d); C(d) } sa(d)
|
|
||||||
}
|
|
||||||
}; this.fnPageChange =
|
|
||||||
function (a, b) { var c = A(this[o.iApiIndex]); ja(c, a); E(c); if (typeof b == "undefined" || b) C(c) }; this.fnDestroy = function () {
|
|
||||||
var a = A(this[o.iApiIndex]), b = a.nTableWrapper.parentNode, c = a.nTBody, d, f; a.bDestroying = true; d = 0; for (f = a.aoColumns.length; d < f; d++) a.aoColumns[d].bVisible === false && this.fnSetColumnVis(d, true); i(a.nTableWrapper).find("*").andSelf().unbind(".DT"); i("tbody>tr>td." + a.oClasses.sRowEmpty, a.nTable).parent().remove(); if (a.nTable != a.nTHead.parentNode) { i(">thead", a.nTable).remove(); a.nTable.appendChild(a.nTHead) } if (a.nTFoot &&
|
|
||||||
a.nTable != a.nTFoot.parentNode) { i(">tfoot", a.nTable).remove(); a.nTable.appendChild(a.nTFoot) } a.nTable.parentNode.removeChild(a.nTable); i(a.nTableWrapper).remove(); a.aaSorting = []; a.aaSortingFixed = []; T(a); i($(a)).removeClass(a.asStripClasses.join(" ")); if (a.bJUI) {
|
|
||||||
i("th", a.nTHead).removeClass([o.oStdClasses.sSortable, o.oJUIClasses.sSortableAsc, o.oJUIClasses.sSortableDesc, o.oJUIClasses.sSortableNone].join(" ")); i("th span." + o.oJUIClasses.sSortIcon, a.nTHead).remove(); i("th", a.nTHead).each(function () {
|
|
||||||
var e =
|
|
||||||
i("div." + o.oJUIClasses.sSortJUIWrapper, this), h = e.contents(); i(this).append(h); e.remove()
|
|
||||||
})
|
|
||||||
} else i("th", a.nTHead).removeClass([o.oStdClasses.sSortable, o.oStdClasses.sSortableAsc, o.oStdClasses.sSortableDesc, o.oStdClasses.sSortableNone].join(" ")); a.nTableReinsertBefore ? b.insertBefore(a.nTable, a.nTableReinsertBefore) : b.appendChild(a.nTable); d = 0; for (f = a.aoData.length; d < f; d++) a.aoData[d].nTr !== null && c.appendChild(a.aoData[d].nTr); if (a.oFeatures.bAutoWidth === true) a.nTable.style.width = u(a.sDestroyWidth);
|
|
||||||
i(">tr:even", c).addClass(a.asDestoryStrips[0]); i(">tr:odd", c).addClass(a.asDestoryStrips[1]); d = 0; for (f = D.length; d < f; d++) D[d] == a && D.splice(d, 1); a = null
|
|
||||||
};
|
|
||||||
this.fnAdjustColumnSizing = function (a) { var b = A(this[o.iApiIndex]); ca(b); if (typeof a == "undefined" || a) this.fnDraw(false); else if (b.oScroll.sX !== "" || b.oScroll.sY !== "") this.oApi._fnScrollDraw(b) }; for (var ua in o.oApi) if (ua) this[ua] = r(ua); this.oApi._fnExternApiFunc = r; this.oApi._fnInitalise = s; this.oApi._fnInitComplete = w; this.oApi._fnLanguageProcess = y; this.oApi._fnAddColumn =
|
|
||||||
G;
|
|
||||||
this.oApi._fnColumnOptions = x; this.oApi._fnAddData = v; this.oApi._fnCreateTr = z; this.oApi._fnGatherData = Y; this.oApi._fnBuildHead = V; this.oApi._fnDrawHead = L; this.oApi._fnDraw = C; this.oApi._fnReDraw = ba; this.oApi._fnAjaxUpdate = za; this.oApi._fnAjaxUpdateDraw = Aa; this.oApi._fnAddOptionsHtml = xa; this.oApi._fnFeatureHtmlTable = Fa; this.oApi._fnScrollDraw = Ia; this.oApi._fnAjustColumnSizing = ca; this.oApi._fnFeatureHtmlFilter = Da; this.oApi._fnFilterComplete = M; this.oApi._fnFilterCustom = Ma; this.oApi._fnFilterColumn = La;
|
|
||||||
this.oApi._fnFilter = Ka; this.oApi._fnBuildSearchArray = ka; this.oApi._fnBuildSearchRow = na; this.oApi._fnFilterCreateSearch = la; this.oApi._fnDataToSearch = ma; this.oApi._fnSort = R; this.oApi._fnSortAttachListener = ga; this.oApi._fnSortingClasses = T; this.oApi._fnFeatureHtmlPaginate = Ha; this.oApi._fnPageChange = ja; this.oApi._fnFeatureHtmlInfo = Ga; this.oApi._fnUpdateInfo = Na; this.oApi._fnFeatureHtmlLength = Ca; this.oApi._fnFeatureHtmlProcessing = Ea; this.oApi._fnProcessingDisplay = K; this.oApi._fnVisibleToColumnIndex = Ja; this.oApi._fnColumnIndexToVisible =
|
|
||||||
pa;
|
|
||||||
this.oApi._fnNodeToDataIndex = U;
|
|
||||||
this.oApi._fnVisbleColumns = X; this.oApi._fnCalculateEnd = E; this.oApi._fnConvertToWidth = Oa; this.oApi._fnCalculateColumnWidths = ea; this.oApi._fnScrollingWidthAdjust = Qa; this.oApi._fnGetWidestNode = Pa; this.oApi._fnGetMaxLenString = Ra; this.oApi._fnStringToCss = u; this.oApi._fnArrayCmp = Va; this.oApi._fnDetectType = fa; this.oApi._fnSettingsFromNode = A; this.oApi._fnGetDataMaster = aa; this.oApi._fnGetTrNodes = $; this.oApi._fnGetTdNodes = Q; this.oApi._fnEscapeRegex = oa; this.oApi._fnDeleteIndex =
|
|
||||||
ra;
|
|
||||||
this.oApi._fnReOrderIndex = Ba;
|
|
||||||
this.oApi._fnColumnOrdering = ha;
|
|
||||||
this.oApi._fnLog = J;
|
|
||||||
this.oApi._fnClearTable = ia;
|
|
||||||
this.oApi._fnSaveState = sa;
|
|
||||||
this.oApi._fnLoadState = Ta;
|
|
||||||
this.oApi._fnCreateCookie = Sa;
|
|
||||||
this.oApi._fnReadCookie = ta;
|
|
||||||
this.oApi._fnDetectHeader = W; this.oApi._fnGetUniqueThs = S; this.oApi._fnScrollBarWidth = Ua; this.oApi._fnApplyToChildren = P; this.oApi._fnMap = n; this.oApi._fnGetRowData = da; this.oApi._fnGetCellData = H; this.oApi._fnSetCellData = N; this.oApi._fnGetObjectDataFn = Z; this.oApi._fnSetObjectDataFn = ya; var va =
|
|
||||||
this;
|
|
||||||
return this.each(function () {
|
|
||||||
var a = 0, b, c, d, f; a = 0; for (b = D.length; a < b; a++) {
|
|
||||||
if (D[a].nTable == this) if (typeof g == "undefined" || typeof g.bRetrieve != "undefined" && g.bRetrieve === true) return D[a].oInstance; else if (typeof g.bDestroy != "undefined" && g.bDestroy === true) { D[a].oInstance.fnDestroy(); break } else {
|
|
||||||
J(D[a], 0, "Cannot reinitialise DataTable.\n\nTo retrieve the DataTables object for this table, please pass either no arguments to the dataTable() function, or set bRetrieve to true. Alternatively, to destory the old table and create a new one, set bDestroy to true (note that a lot of changes to the configuration can be made through the API which is usually much faster).");
|
|
||||||
return
|
|
||||||
} if (D[a].sTableId !== "" && D[a].sTableId == this.getAttribute("id")) { D.splice(a, 1); break }
|
|
||||||
} var e = new l; D.push(e); var h = false, j = false; a = this.getAttribute("id"); if (a !== null) { e.sTableId = a; e.sInstance = a } else e.sInstance = o._oExternConfig.iNextUnique++; if (this.nodeName.toLowerCase() != "table") J(e, 0, "Attempted to initialise DataTables on a node which is not a table: " + this.nodeName); else {
|
|
||||||
e.nTable = this; e.oInstance = va.length == 1 ? va : i(this).dataTable(); e.oApi = va.oApi; e.sDestroyWidth = i(this).width(); if (typeof g !=
|
|
||||||
"undefined" && g !== null) {
|
|
||||||
e.oInit = g; n(e.oFeatures, g, "bPaginate");
|
|
||||||
n(e.oFeatures, g, "bLengthChange");
|
|
||||||
n(e.oFeatures, g, "bFilter");
|
|
||||||
n(e.oFeatures, g, "bSort");
|
|
||||||
n(e.oFeatures, g, "bInfo");
|
|
||||||
n(e.oFeatures, g, "bProcessing");
|
|
||||||
n(e.oFeatures, g, "bAutoWidth");
|
|
||||||
n(e.oFeatures, g, "bSortClasses");
|
|
||||||
n(e.oFeatures, g, "bServerSide");
|
|
||||||
n(e.oFeatures, g, "bDeferRender");
|
|
||||||
n(e.oScroll, g, "sScrollX", "sX");
|
|
||||||
n(e.oScroll, g, "sScrollXInner", "sXInner");
|
|
||||||
n(e.oScroll, g, "sScrollY", "sY");
|
|
||||||
n(e.oScroll, g, "bScrollCollapse", "bCollapse");
|
|
||||||
n(e.oScroll, g, "bScrollInfinite", "bInfinite");
|
|
||||||
n(e.oScroll, g, "iScrollLoadGap", "iLoadGap");
|
|
||||||
n(e.oScroll, g, "bScrollAutoCss", "bAutoCss");
|
|
||||||
n(e, g, "asStripClasses");
|
|
||||||
n(e, g, "fnPreDrawCallback");
|
|
||||||
n(e, g, "fnRowCallback"); n(e, g, "fnHeaderCallback"); n(e, g, "fnFooterCallback"); n(e, g, "fnCookieCallback"); n(e, g, "fnInitComplete"); n(e, g, "fnServerData"); n(e, g, "fnFormatNumber"); n(e, g, "aaSorting"); n(e, g, "aaSortingFixed"); n(e, g, "aLengthMenu"); n(e, g, "sPaginationType"); n(e, g, "sAjaxSource"); n(e, g, "sAjaxDataProp"); n(e, g, "iCookieDuration"); n(e, g, "sCookiePrefix");
|
|
||||||
n(e, g, "sDom");
|
|
||||||
n(e, g, "bSortCellsTop");
|
|
||||||
n(e, g, "oSearch", "oPreviousSearch");
|
|
||||||
n(e, g, "aoSearchCols", "aoPreSearchCols");
|
|
||||||
n(e, g, "iDisplayLength", "_iDisplayLength");
|
|
||||||
n(e, g, "bJQueryUI", "bJUI");
|
|
||||||
n(e.oLanguage, g, "fnInfoCallback");
|
|
||||||
typeof g.fnDrawCallback == "function" && e.aoDrawCallback.push({
|
|
||||||
fn: g.fnDrawCallback, sName: "user"
|
|
||||||
});
|
|
||||||
typeof g.fnStateSaveCallback == "function" && e.aoStateSave.push({
|
|
||||||
fn: g.fnStateSaveCallback, sName: "user"
|
|
||||||
});
|
|
||||||
typeof g.fnStateLoadCallback == "function" && e.aoStateLoad.push({
|
|
||||||
fn: g.fnStateLoadCallback, sName: "user"
|
|
||||||
});
|
|
||||||
if (e.oFeatures.bServerSide && e.oFeatures.bSort && e.oFeatures.bSortClasses) e.aoDrawCallback.push({ fn: T, sName: "server_side_sort_classes" }); else e.oFeatures.bDeferRender && e.aoDrawCallback.push({ fn: T, sName: "defer_sort_classes" }); if (typeof g.bJQueryUI != "undefined" && g.bJQueryUI) { e.oClasses = o.oJUIClasses; if (typeof g.sDom == "undefined") e.sDom = '<"H"lfr>t<"F"ip>' } if (e.oScroll.sX !== "" || e.oScroll.sY !== "") e.oScroll.iBarWidth = Ua(); if (typeof g.iDisplayStart != "undefined" && typeof e.iInitDisplayStart == "undefined") {
|
|
||||||
e.iInitDisplayStart = g.iDisplayStart; e._iDisplayStart = g.iDisplayStart
|
|
||||||
} if (typeof g.bStateSave != "undefined") { e.oFeatures.bStateSave = g.bStateSave; Ta(e, g); e.aoDrawCallback.push({ fn: sa, sName: "state_save" }) } if (typeof g.iDeferLoading != "undefined") { e.bDeferLoading = true; e._iRecordsTotal = g.iDeferLoading; e._iRecordsDisplay = g.iDeferLoading } if (typeof g.aaData != "undefined") j = true; if (typeof g != "undefined" && typeof g.aoData != "undefined") g.aoColumns = g.aoData; if (typeof g.oLanguage != "undefined") if (typeof g.oLanguage.sUrl != "undefined" && g.oLanguage.sUrl !==
|
|
||||||
"") { e.oLanguage.sUrl = g.oLanguage.sUrl; i.getJSON(e.oLanguage.sUrl, null, function (t) { y(e, t, true) }); h = true } else y(e, g.oLanguage, false)
|
|
||||||
} else g = {}; if (typeof g.asStripClasses == "undefined") { e.asStripClasses.push(e.oClasses.sStripOdd); e.asStripClasses.push(e.oClasses.sStripEven) } c = false; d = i(">tbody>tr", this); a = 0; for (b = e.asStripClasses.length; a < b; a++) if (d.filter(":lt(2)").hasClass(e.asStripClasses[a])) { c = true; break } if (c) {
|
|
||||||
e.asDestoryStrips = ["", ""]; if (i(d[0]).hasClass(e.oClasses.sStripOdd)) e.asDestoryStrips[0] +=
|
|
||||||
e.oClasses.sStripOdd + " "; if (i(d[0]).hasClass(e.oClasses.sStripEven)) e.asDestoryStrips[0] += e.oClasses.sStripEven; if (i(d[1]).hasClass(e.oClasses.sStripOdd)) e.asDestoryStrips[1] += e.oClasses.sStripOdd + " "; if (i(d[1]).hasClass(e.oClasses.sStripEven)) e.asDestoryStrips[1] += e.oClasses.sStripEven; d.removeClass(e.asStripClasses.join(" "))
|
|
||||||
} c = []; var k; a = this.getElementsByTagName("thead"); if (a.length !== 0) { W(e.aoHeader, a[0]); c = S(e) } if (typeof g.aoColumns == "undefined") { k = []; a = 0; for (b = c.length; a < b; a++) k.push(null) } else k =
|
|
||||||
g.aoColumns; a = 0; for (b = k.length; a < b; a++) { if (typeof g.saved_aoColumns != "undefined" && g.saved_aoColumns.length == b) { if (k[a] === null) k[a] = {}; k[a].bVisible = g.saved_aoColumns[a].bVisible } G(e, c ? c[a] : null) } if (typeof g.aoColumnDefs != "undefined") for (a = g.aoColumnDefs.length - 1; a >= 0; a--) {
|
|
||||||
var m = g.aoColumnDefs[a].aTargets; i.isArray(m) || J(e, 1, "aTargets must be an array of targets, not a " + typeof m); c = 0; for (d = m.length; c < d; c++) if (typeof m[c] == "number" && m[c] >= 0) { for (; e.aoColumns.length <= m[c]; ) G(e); x(e, m[c], g.aoColumnDefs[a]) } else if (typeof m[c] ==
|
|
||||||
"number" && m[c] < 0) x(e, e.aoColumns.length + m[c], g.aoColumnDefs[a]); else if (typeof m[c] == "string") { b = 0; for (f = e.aoColumns.length; b < f; b++) if (m[c] == "_all" || i(e.aoColumns[b].nTh).hasClass(m[c])) x(e, b, g.aoColumnDefs[a]) }
|
|
||||||
} if (typeof k != "undefined") { a = 0; for (b = k.length; a < b; a++) x(e, a, k[a]) } a = 0; for (b = e.aaSorting.length; a < b; a++) {
|
|
||||||
if (e.aaSorting[a][0] >= e.aoColumns.length)
|
|
||||||
e.aaSorting[a][0] = 0; k = e.aoColumns[e.aaSorting[a][0]];
|
|
||||||
if (typeof e.aaSorting[a][2] == "undefined") e.aaSorting[a][2] = 0;
|
|
||||||
if (typeof g.aaSorting == "undefined" && typeof e.saved_aaSorting == "undefined")
|
|
||||||
e.aaSorting[a][1] = k.asSorting[0];
|
|
||||||
c = 0; for (d = k.asSorting.length;
|
|
||||||
c < d; c++) if (e.aaSorting[a][1] == k.asSorting[c]) {
|
|
||||||
e.aaSorting[a][2] = c; break
|
|
||||||
}
|
|
||||||
} T(e); a = i(">thead", this); if (a.length === 0) {
|
|
||||||
a = [p.createElement("thead")]; this.appendChild(a[0])
|
|
||||||
} e.nTHead = a[0]; a = i(">tbody", this);
|
|
||||||
if (a.length === 0) {
|
|
||||||
a = [p.createElement("tbody")];
|
|
||||||
this.appendChild(a[0])
|
|
||||||
} e.nTBody = a[0]; a = i(">tfoot", this);
|
|
||||||
if (a.length > 0) { e.nTFoot = a[0]; W(e.aoFooter, e.nTFoot) }
|
|
||||||
if (j)
|
|
||||||
for (a = 0; a < g.aaData.length; a++) v(e, g.aaData[a]);
|
|
||||||
else Y(e); e.aiDisplay = e.aiDisplayMaster.slice(); e.bInitialised = true; h === false && s(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})(jQuery, window, document);
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
window.log = function(){
|
|
||||||
log.history = log.history || [];
|
|
||||||
log.history.push(arguments);
|
|
||||||
arguments.callee = arguments.callee.caller;
|
|
||||||
if(this.console) console.log( Array.prototype.slice.call(arguments) );
|
|
||||||
};
|
|
||||||
(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();)b[a]=b[a]||c})(window.console=window.console||{});
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-string-asc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
var y = b.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-string-desc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
var y = b.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-numeric-asc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
var y = b.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
x = parseFloat( x );
|
|
||||||
y = parseFloat( y );
|
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
var y = b.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
x = parseFloat( x );
|
|
||||||
y = parseFloat( y );
|
|
||||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
function toggle(source) {
|
|
||||||
checkboxes = document.getElementsByClassName('checkbox');
|
|
||||||
for(var i in checkboxes)
|
|
||||||
checkboxes[i].checked = source.checked;
|
|
||||||
}
|
|
||||||
@@ -67,32 +67,44 @@ function getInfo(elem,id,type) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageLinks(elem,id,type) {
|
function getImageLinks(elem,id,type,unveil) {
|
||||||
|
|
||||||
if ( type == 'artist' ) {
|
if ( type == 'artist' ) {
|
||||||
var infoURL = "getImageLinks?ArtistID=" + id;
|
var infoURL = "getImageLinks?ArtistID=" + id;
|
||||||
} else {
|
} else {
|
||||||
var infoURL = "getImageLinks?AlbumID=" + id;
|
var infoURL = "getImageLinks?AlbumID=" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Data from the cache by ID
|
// Get Data from the cache by ID
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: infoURL,
|
url: infoURL,
|
||||||
cache: true,
|
cache: true,
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data){
|
success: function(data){
|
||||||
if ( data.thumbnail == "" || data.thumbnail == undefined ) {
|
if (!data) {
|
||||||
|
// Invalid response
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.thumbnail) {
|
||||||
var thumbnail = "interfaces/default/images/no-cover-artist.png";
|
var thumbnail = "interfaces/default/images/no-cover-artist.png";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var thumbnail = data.thumbnail;
|
var thumbnail = data.thumbnail;
|
||||||
}
|
}
|
||||||
if ( data.artwork == "" || data.artwork == undefined ) {
|
if (!data.artwork) {
|
||||||
var artwork = "interfaces/default/images/no-cover-artist.png";
|
var artwork = "interfaces/default/images/no-cover-artist.png";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var artwork = data.artwork;
|
var artwork = data.artwork;
|
||||||
}
|
}
|
||||||
$(elem).attr("src", thumbnail);
|
|
||||||
|
if (unveil) {
|
||||||
|
$(elem).attr("data-src", thumbnail);
|
||||||
|
$(elem).unveil();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(elem).attr("src", thumbnail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -228,6 +240,21 @@ function showMsg(msg,loader,timeout,ms) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showArtistMsg(msg) {
|
||||||
|
var feedback = $("#ajaxMsg2");
|
||||||
|
update = $("#updatebar");
|
||||||
|
if ( update.is(":visible") ) {
|
||||||
|
var height = update.height() + 35;
|
||||||
|
feedback.css("bottom",height + "px");
|
||||||
|
} else {
|
||||||
|
feedback.removeAttr("style");
|
||||||
|
}
|
||||||
|
feedback.fadeIn();
|
||||||
|
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
||||||
|
feedback.css("padding","14px 10px")
|
||||||
|
$(feedback).prepend(message);
|
||||||
|
}
|
||||||
|
|
||||||
function doAjaxCall(url,elem,reload,form) {
|
function doAjaxCall(url,elem,reload,form) {
|
||||||
// Set Message
|
// Set Message
|
||||||
feedback = $("#ajaxMsg");
|
feedback = $("#ajaxMsg");
|
||||||
@@ -316,6 +343,10 @@ function doAjaxCall(url,elem,reload,form) {
|
|||||||
}
|
}
|
||||||
if ( reload == "tabs") refreshTab();
|
if ( reload == "tabs") refreshTab();
|
||||||
if ( reload == "page") location.reload();
|
if ( reload == "page") location.reload();
|
||||||
|
if ( reload == "submenu&table") {
|
||||||
|
refreshSubmenu();
|
||||||
|
refreshTable();
|
||||||
|
}
|
||||||
if ( form ) {
|
if ( form ) {
|
||||||
// Change the option to 'choose...'
|
// Change the option to 'choose...'
|
||||||
$(formID + " select").children('option[disabled=disabled]').attr('selected','selected');
|
$(formID + " select").children('option[disabled=disabled]').attr('selected','selected');
|
||||||
@@ -330,18 +361,16 @@ function doAjaxCall(url,elem,reload,form) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doSimpleAjaxCall(url) {
|
||||||
|
$.ajax(url);
|
||||||
|
}
|
||||||
|
|
||||||
function resetFilters(text){
|
function resetFilters(text){
|
||||||
if ( $(".dataTables_filter").length > 0 ) {
|
if ( $(".dataTables_filter").length > 0 ) {
|
||||||
$(".dataTables_filter input").attr("placeholder","filter " + text + "");
|
$(".dataTables_filter input").attr("placeholder","filter " + text + "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function preventDefault(){
|
|
||||||
$("a[href='#']").live('click', function(){
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initFancybox() {
|
function initFancybox() {
|
||||||
if ( $("a[rel=dialog]").length > 0 ) {
|
if ( $("a[rel=dialog]").length > 0 ) {
|
||||||
$.getScript('interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js', function() {
|
$.getScript('interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js', function() {
|
||||||
@@ -351,11 +380,6 @@ function initFancybox() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
|
||||||
initHeader();
|
|
||||||
preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
init();
|
initHeader();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
lossless<%inherit file="base.html"/>
|
<%inherit file="base.html"/>
|
||||||
<%!
|
<%!
|
||||||
from headphones import helpers
|
from headphones import helpers
|
||||||
%>
|
%>
|
||||||
|
|
||||||
|
<%def name="headerIncludes()">
|
||||||
|
<div id="subhead_container">
|
||||||
|
<div id="subhead_menu">
|
||||||
|
<a class="menu_link_edit" href="clearLogs"><i class="fa fa-trash-o"></i> Clear log</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="body()">
|
<%def name="body()">
|
||||||
<div class="title">
|
<div id="paddingheader">
|
||||||
<h1 class="clearfix"><i class="fa fa-flag"></i> Logs</h1>
|
<h1 class="clearfix"><i class="fa fa-flag"></i> Logs</h1>
|
||||||
</div>
|
</div>
|
||||||
<table class="display" id="log_table">
|
<table class="display" id="log_table">
|
||||||
@@ -29,7 +37,6 @@ lossless<%inherit file="base.html"/>
|
|||||||
<option value="300">5 Minutes</option>
|
<option value="300">5 Minutes</option>
|
||||||
<option value="600">10 Minutes</option>
|
<option value="600">10 Minutes</option>
|
||||||
</select></div>
|
</select></div>
|
||||||
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="headIncludes()">
|
<%def name="headIncludes()">
|
||||||
@@ -40,6 +47,8 @@ lossless<%inherit file="base.html"/>
|
|||||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
|
initActions();
|
||||||
|
|
||||||
$('#log_table').dataTable( {
|
$('#log_table').dataTable( {
|
||||||
"bProcessing": true,
|
"bProcessing": true,
|
||||||
"bServerSide": true,
|
"bServerSide": true,
|
||||||
@@ -49,23 +58,26 @@ $(document).ready(function() {
|
|||||||
"iDisplayLength": 25,
|
"iDisplayLength": 25,
|
||||||
"bStateSave": true,
|
"bStateSave": true,
|
||||||
"oLanguage": {
|
"oLanguage": {
|
||||||
"sSearch":"",
|
"sSearch":"Filter:",
|
||||||
"sLengthMenu":"Show _MENU_ lines per page",
|
"sLengthMenu":"Show _MENU_ lines per page",
|
||||||
"sEmptyTable": "No log information available",
|
"sEmptyTable": "No log information available",
|
||||||
"sInfo":"Showing _START_ to _END_ of _TOTAL_ lines",
|
"sInfo":"Showing _START_ to _END_ of _TOTAL_ lines",
|
||||||
"sInfoEmpty":"Showing 0 to 0 of 0 lines",
|
"sInfoEmpty":"Showing 0 to 0 of 0 lines",
|
||||||
"sInfoFiltered":"(filtered from _MAX_ total lines)"},
|
"sInfoFiltered":"(filtered from _MAX_ total lines)"},
|
||||||
"fnRowCallback": function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
|
"fnRowCallback": function (nRow, aData, iDisplayIndex, iDisplayIndexFull) {
|
||||||
if (aData[1] === "WARNING" || aData[1] === "ERROR")
|
if (aData[1] === "ERROR")
|
||||||
{
|
{
|
||||||
$('td', nRow).closest('tr').addClass("gradeX");
|
$('td', nRow).closest('tr').addClass("gradeX");
|
||||||
}
|
}
|
||||||
|
else if (aData[1] === "WARNING")
|
||||||
|
{
|
||||||
|
$('td', nRow).closest('tr').addClass("gradeW");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$('td', nRow).closest('tr').addClass("gradeZ");
|
$('td', nRow).closest('tr').addClass("gradeZ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return nRow;
|
return nRow;
|
||||||
},
|
},
|
||||||
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<a href="manageAlbums?Status=Snatched"><i class="fa fa-cloud-download fa-fw"></i> </span>Manage Snatched Albums</a><br>
|
<a href="manageAlbums?Status=Snatched"><i class="fa fa-cloud-download fa-fw"></i> </span>Manage Snatched Albums</a><br>
|
||||||
<a href="manageAlbums?Status=Upcoming"><i class="fa fa-calendar fa-fw"></i> Manage Upcoming Albums</a><br>
|
<a href="manageAlbums?Status=Upcoming"><i class="fa fa-calendar fa-fw"></i> Manage Upcoming Albums</a><br>
|
||||||
<a href="manageAlbums?Status=Wanted"><i class="fa fa-heart fa-fw"></i> </span>Manage Wanted Albums</a><br>
|
<a href="manageAlbums?Status=Wanted"><i class="fa fa-heart fa-fw"></i> </span>Manage Wanted Albums</a><br>
|
||||||
|
<a href="manageAlbums?Status=Ignored"><i class="fa fa-meh-o fa-fw"></i> </span>Manage Ignored Albums</a><br>
|
||||||
<br><br>
|
<br><br>
|
||||||
<a href="manageAlbums">Manage All Albums</a>
|
<a href="manageAlbums">Manage All Albums</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +123,6 @@
|
|||||||
<div class="links">
|
<div class="links">
|
||||||
<a href="#" onclick="doAjaxCall('forceSearch',$(this))" data-success="Checking for wanted albums successful" data-error="Error checking wanted albums"><i class="fa fa-search fa-fw"></i> Force Check for Wanted Albums</a>
|
<a href="#" onclick="doAjaxCall('forceSearch',$(this))" data-success="Checking for wanted albums successful" data-error="Error checking wanted albums"><i class="fa fa-search fa-fw"></i> Force Check for Wanted Albums</a>
|
||||||
<a href="#" onclick="doAjaxCall('forceUpdate',$(this))" data-success="Update active artists successful" data-error="Error forcing update artists"><i class="fa fa-heart fa-fw"></i> Force Update Active Artists [Fast]</a>
|
<a href="#" onclick="doAjaxCall('forceUpdate',$(this))" data-success="Update active artists successful" data-error="Error forcing update artists"><i class="fa fa-heart fa-fw"></i> Force Update Active Artists [Fast]</a>
|
||||||
<a href="#" onclick="doAjaxCall('forcePostProcess',$(this))" data-success="Post-Processor is being loaded" data-error="Error during Post-Processing"><i class="fa fa-wrench fa-fw"></i> Force Post-Process Albums in Download Folder</a>
|
|
||||||
<a href="#" onclick="doAjaxCall('checkGithub',$(this))" data-success="Checking for update successful" data-error="Error checking for update"><i class="fa fa-refresh fa-fw"></i> Check for Headphones Updates</a>
|
<a href="#" onclick="doAjaxCall('checkGithub',$(this))" data-success="Checking for update successful" data-error="Error checking for update"><i class="fa fa-refresh fa-fw"></i> Check for Headphones Updates</a>
|
||||||
<a href="#" id="delete_empty_artists"><i class="fa fa-trash-o fa-fw"></i> Delete empty Artists</a>
|
<a href="#" id="delete_empty_artists"><i class="fa fa-trash-o fa-fw"></i> Delete empty Artists</a>
|
||||||
<div id="emptyartistdialog" title="Confirm Artist Deletion" style="display:none" class="configtable">
|
<div id="emptyartistdialog" title="Confirm Artist Deletion" style="display:none" class="configtable">
|
||||||
@@ -137,8 +137,27 @@
|
|||||||
No empty artists found.
|
No empty artists found.
|
||||||
%endif
|
%endif
|
||||||
</div>
|
</div>
|
||||||
|
<a href="#" onclick="doAjaxCall('forcePostProcess',$(this))" data-success="Post-Processor is being loaded" data-error="Error during Post-Processing"><i class="fa fa-wrench fa-fw"></i> Force Post-Process Albums in Download Folder</a>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<form action="forcePostProcess" method="GET">
|
||||||
|
<fieldset>
|
||||||
|
<div class="row">
|
||||||
|
<label>Force Post-Process Albums in Alternate Folder</label>
|
||||||
|
<input type="text" value="" name="dir" id="dir" size="50" /><input type="submit" />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<form action="forcePostProcess" method="GET">
|
||||||
|
<fieldset>
|
||||||
|
<div class="row">
|
||||||
|
<label>Post-Process Single Folder</label>
|
||||||
|
<input type="text" value="" name="album_dir" id="album_dir" size="50" /><input type="submit" />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<option value="WantedNew">Wanted (new only)</option>
|
<option value="WantedNew">Wanted (new only)</option>
|
||||||
<option value="WantedLossless">Wanted (lossless)</option>
|
<option value="WantedLossless">Wanted (lossless)</option>
|
||||||
<option value="Skipped">Skipped</option>
|
<option value="Skipped">Skipped</option>
|
||||||
|
<option value="Ignored">Ignored</option>
|
||||||
<option value="Downloaded">Downloaded</option>
|
<option value="Downloaded">Downloaded</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" value="Go">
|
<input type="hidden" value="Go">
|
||||||
@@ -50,6 +51,8 @@
|
|||||||
grade = 'Z'
|
grade = 'Z'
|
||||||
elif album['Status'] == 'Wanted':
|
elif album['Status'] == 'Wanted':
|
||||||
grade = 'X'
|
grade = 'X'
|
||||||
|
elif album['Status'] == 'Ignored':
|
||||||
|
grade = 'I'
|
||||||
elif album['Status'] == 'Snatched':
|
elif album['Status'] == 'Snatched':
|
||||||
grade = 'C'
|
grade = 'C'
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -11,36 +11,49 @@
|
|||||||
<th id="albumart"></th>
|
<th id="albumart"></th>
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<th id="albumname">Album Name</th>
|
<th id="albumname">Album Name</th>
|
||||||
|
<th id="artistnamesmall">Artist Name</th>
|
||||||
|
<th id="format">Format</th>
|
||||||
|
<th id="tracks">Tracks</th>
|
||||||
|
<th id="reldate">Date</th>
|
||||||
|
<th id="scoresmall">Score</th>
|
||||||
|
%else:
|
||||||
|
<th id="artistname">Artist Name</th>
|
||||||
|
<th id="score">Score</th>
|
||||||
%endif
|
%endif
|
||||||
<th id="artistname">Artist Name</th>
|
<th id="mb"></th>
|
||||||
<th id="score">Score</th>
|
|
||||||
<th id="add"></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
%if searchresults:
|
%if searchresults:
|
||||||
%for result in searchresults:
|
%for result in searchresults:
|
||||||
<%
|
<%
|
||||||
if result['score'] == 100:
|
if result['score'] == 100:
|
||||||
grade = 'A'
|
grade = 'A'
|
||||||
else:
|
else:
|
||||||
grade = 'Z'
|
grade = 'Z'
|
||||||
%>
|
|
||||||
|
if type == 'album':
|
||||||
|
albuminfo = 'Type: ' + result['rgtype'] + ', Country: ' + result['country']
|
||||||
|
caa_group_url = "http://coverartarchive.org/release-group/%s/front-250.jpg" %result['rgid']
|
||||||
|
%>
|
||||||
<tr class="grade${grade}">
|
<tr class="grade${grade}">
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<td id="albumart"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50"></div></td>
|
<td id="albumart" style=" text-align: center; vertical-align: middle;"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50" onerror="this.onerror=null;this.src='${caa_group_url}'"></div></td>
|
||||||
%else:
|
%else:
|
||||||
<td id="albumart"><div id="artistImg"><img title="${result['id']}" class="albumArt" height="50" width="50"></div></td>
|
<td id="albumart"><div id="artistImg"><img title="${result['id']}" class="albumArt" height="50" width="50"></div></td>
|
||||||
%endif
|
%endif
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
|
<td id="albumname"><a href="addReleaseById?rid=${result['albumid']}&rgid=${result['rgid']}" title="${albuminfo}">${result['title']}</a></td>
|
||||||
%endif
|
<td id="artistnamesmall"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||||
<td id="artistname"><a href="${result['url']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
<td id="format">${result['formats']}</td>
|
||||||
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
|
<td id="tracks">${result['tracks']}</td>
|
||||||
%if type == 'album':
|
<td id="reldate">${result['date']}</td>
|
||||||
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
|
<td id="score"><a href="${result['albumurl']} "title="View on MusicBrainz"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></a></td>
|
||||||
%else:
|
<td id="musicbrainz" style=" text-align: center; line-height: 0; vertical-align: middle;"><a href="${result['albumurl']}"><img src="interfaces/default/images/MusicBrainz_Album_Icon.png" title="View on MusicBrainz" height="20" width="20"></a></td>
|
||||||
<td id="add"><a href="addArtist?artistid=${result['id']}"><i class="fa fa-plus"></i> Add this artist</a></td>
|
%else:
|
||||||
|
<td id="artistname"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||||
|
<td id="score"><a href="${result['url']} "title="View on MusicBrainz"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></a></td>
|
||||||
|
<td id="musicbrainz" style=" text-align: center; line-height: 0; vertical-align: middle;"><a href="${result['url']}"><img src="interfaces/default/images/MusicBrainz_Artist_Icon.png" title="View on MusicBrainz" height="20" width="20"></a></td>
|
||||||
%endif
|
%endif
|
||||||
</tr>
|
</tr>
|
||||||
%endfor
|
%endfor
|
||||||
@@ -55,27 +68,28 @@
|
|||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
<%def name="javascriptIncludes()">
|
||||||
|
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script type="text/javascript">
|
||||||
function getArt() {
|
|
||||||
$("table#searchresults_table tr td#albumart img").each(function(){
|
function getArt() {
|
||||||
var id = $(this).attr('title');
|
$("table#searchresults_table tr td#albumart img").each(function(){
|
||||||
var image = $(this);
|
var id = $(this).attr('title');
|
||||||
if ( !image.hasClass('done') ) {
|
var image = $(this);
|
||||||
image.addClass('done');
|
|
||||||
getImageLinks(image,id,"${type}");
|
if (!image.hasClass('done')) {
|
||||||
}
|
image.addClass('done');
|
||||||
});
|
getImageLinks(image, id, "${type}", true);
|
||||||
}
|
}
|
||||||
function initThisPage() {
|
});
|
||||||
getArt();
|
}
|
||||||
$('#searchresults_table').dataTable(
|
function initThisPage() {
|
||||||
{
|
getArt();
|
||||||
|
$('#searchresults_table').dataTable({
|
||||||
"bDestroy": true,
|
"bDestroy": true,
|
||||||
"aoColumnDefs": [
|
"aoColumnDefs": [
|
||||||
{ 'bSortable': false, 'aTargets': [ 0,3 ] }
|
{ 'bSortable': false, 'aTargets': [ 0 ] }
|
||||||
],
|
],
|
||||||
"oLanguage": {
|
"oLanguage": {
|
||||||
"sLengthMenu":"Show _MENU_ results per page",
|
"sLengthMenu":"Show _MENU_ results per page",
|
||||||
@@ -88,13 +102,14 @@
|
|||||||
"sPaginationType": "full_numbers",
|
"sPaginationType": "full_numbers",
|
||||||
"aaSorting": []
|
"aaSorting": []
|
||||||
});
|
});
|
||||||
|
$('#searchresults_table').on("draw.dt", function () {
|
||||||
|
$("img").unveil();
|
||||||
|
});
|
||||||
resetFilters("album");
|
resetFilters("album");
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initThisPage();
|
initFancybox();
|
||||||
});
|
initThisPage();
|
||||||
$(window).load(function(){
|
});
|
||||||
initFancybox();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<%def name="body()">
|
<%def name="body()">
|
||||||
|
<div id="paddingheader">
|
||||||
<div class="title">
|
|
||||||
<h1 class="clearfix"><i class="fa fa-heart"></i> Wanted Albums</h1>
|
<h1 class="clearfix"><i class="fa fa-heart"></i> Wanted Albums</h1>
|
||||||
</div>
|
</div>
|
||||||
<form action="markAlbums" method="get" id="markAlbums">
|
<form action="markAlbums" method="get" id="markAlbums">
|
||||||
@@ -20,6 +19,7 @@
|
|||||||
<select name="action" onChange="doAjaxCall('markAlbums',$(this),'table',true);" data-error="You didn't select any albums">
|
<select name="action" onChange="doAjaxCall('markAlbums',$(this),'table',true);" data-error="You didn't select any albums">
|
||||||
<option disabled="disabled" selected="selected">Choose...</option>
|
<option disabled="disabled" selected="selected">Choose...</option>
|
||||||
<option value="Skipped">Skipped</option>
|
<option value="Skipped">Skipped</option>
|
||||||
|
<option value="Ignored">Ignored</option>
|
||||||
<option value="Downloaded">Downloaded</option>
|
<option value="Downloaded">Downloaded</option>
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" value="Go">
|
<input type="hidden" value="Go">
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
%for album in wanted:
|
%for album in wanted:
|
||||||
<tr class="gradeZ">
|
<tr class="gradeZ">
|
||||||
<td id="select"><input type="checkbox" name="${album['AlbumID']}" class="checkbox" /></th>
|
<td id="select"><input type="checkbox" name="${album['AlbumID']}" class="checkbox" /></th>
|
||||||
<td id="albumart"><img title="${album['AlbumID']}" height="64" width="64" src="artwork/thumbs/album/${album['AlbumID']}"></td>
|
<td id="albumart"><img title="${album['AlbumID']}" height="64" width="64" src="interfaces/default/images/no-cover-art.png" data-src="artwork/thumbs/album/${album['AlbumID']}"></td>
|
||||||
<td id="artistname"><a href="artistPage?ArtistID=${album['ArtistID']}">${album['ArtistName']}</a></td>
|
<td id="artistname"><a href="artistPage?ArtistID=${album['ArtistID']}">${album['ArtistName']}</a></td>
|
||||||
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
||||||
<td id="reldate">${album['ReleaseDate']}</td>
|
<td id="reldate">${album['ReleaseDate']}</td>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
%for album in upcoming:
|
%for album in upcoming:
|
||||||
<tr class="gradeZ">
|
<tr class="gradeZ">
|
||||||
<td id="albumart"><img title="${album['AlbumID']}" height="64" width="64" src="artwork/thumbs/album/${album['AlbumID']}"></td>
|
<td id="albumart"><img title="${album['AlbumID']}" height="64" width="64" data-src="artwork/thumbs/album/${album['AlbumID']}"></td>
|
||||||
<td id="artistname"><a href="artistPage?ArtistID=${album['ArtistID']}">${album['ArtistName']}</a></td>
|
<td id="artistname"><a href="artistPage?ArtistID=${album['ArtistID']}">${album['ArtistName']}</a></td>
|
||||||
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
<td id="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
||||||
<td id="reldate">${album['ReleaseDate']}</td>
|
<td id="reldate">${album['ReleaseDate']}</td>
|
||||||
@@ -88,24 +88,25 @@
|
|||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
<%def name="javascriptIncludes()">
|
||||||
|
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function initThisPage() {
|
function initThisPage() {
|
||||||
|
$("img").unveil();
|
||||||
$('#wanted_table').dataTable({
|
$('#wanted_table').dataTable({
|
||||||
"oLanguage": {
|
"oLanguage": {
|
||||||
"sEmptyTable": " "
|
"sEmptyTable": " "
|
||||||
},
|
},
|
||||||
"bDestroy":true,
|
"bDestroy":true,
|
||||||
"bFilter": false,
|
"bFilter": false,
|
||||||
"bInfo": false,
|
"bInfo": false,
|
||||||
"bPaginate": false
|
"bPaginate": false
|
||||||
|
});
|
||||||
|
resetFilters("artists");
|
||||||
|
initActions();
|
||||||
|
}
|
||||||
|
$(document).ready(function() {
|
||||||
|
initThisPage();
|
||||||
});
|
});
|
||||||
resetFilters("artists");
|
|
||||||
initActions();
|
|
||||||
}
|
|
||||||
$(document).ready(function() {
|
|
||||||
initThisPage();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
window.log = function(){
|
||||||
|
log.history = log.history || [];
|
||||||
|
log.history.push(arguments);
|
||||||
|
arguments.callee = arguments.callee.caller;
|
||||||
|
|
||||||
|
if (this.console) {
|
||||||
|
console.log(Array.prototype.slice.call(arguments));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function toggle(source) {
|
||||||
|
checkboxes = document.getElementsByClassName('checkbox');
|
||||||
|
|
||||||
|
for (var i in checkboxes) {
|
||||||
|
checkboxes[i].checked = source.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
!function(t){t.fn.unveil=function(i,e){function n(){var i=a.filter(function(){var i=t(this);if(!i.is(":hidden")){var e=o.scrollTop(),n=e+o.height(),r=i.offset().top,s=r+i.height();return s>=e-u&&n+u>=r}});r=i.trigger("unveil"),a=a.not(r)}var r,o=t(window),u=i||0,s=window.devicePixelRatio>1,l=s?"data-src-retina":"data-src",a=this;return this.one("unveil",function(){var t=this.getAttribute(l);t=t||this.getAttribute("data-src"),t&&(this.setAttribute("src",t),"function"==typeof e&&e.call(this))}),o.on("scroll.unveil resize.unveil lookup.unveil",n),n(),this}}(window.jQuery||window.Zepto);
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
window.log = function(){
|
|
||||||
log.history = log.history || [];
|
|
||||||
log.history.push(arguments);
|
|
||||||
arguments.callee = arguments.callee.caller;
|
|
||||||
if(this.console) console.log( Array.prototype.slice.call(arguments) );
|
|
||||||
};
|
|
||||||
(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();)b[a]=b[a]||c})(window.console=window.console||{});
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-string-asc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
var y = b.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-string-desc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
var y = b.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-numeric-asc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
var y = b.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
x = parseFloat( x );
|
|
||||||
y = parseFloat( y );
|
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
var y = b.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
x = parseFloat( x );
|
|
||||||
y = parseFloat( y );
|
|
||||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
function toggle(source) {
|
|
||||||
checkboxes = document.getElementsByClassName('checkbox');
|
|
||||||
for(var i in checkboxes)
|
|
||||||
checkboxes[i].checked = source.checked;
|
|
||||||
}
|
|
||||||
@@ -15,20 +15,18 @@
|
|||||||
|
|
||||||
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
|
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
|
||||||
|
|
||||||
from __future__ import with_statement
|
import os
|
||||||
|
import sys
|
||||||
import os, sys, subprocess
|
import subprocess
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
from lib.apscheduler.scheduler import Scheduler
|
|
||||||
from lib.configobj import ConfigObj
|
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
|
from apscheduler.scheduler import Scheduler
|
||||||
|
from configobj import ConfigObj
|
||||||
|
|
||||||
from headphones import versioncheck, logger, version
|
from headphones import versioncheck, logger, version
|
||||||
from headphones.common import *
|
from headphones.common import *
|
||||||
|
|
||||||
@@ -41,7 +39,8 @@ SIGNAL = None
|
|||||||
SYS_PLATFORM = None
|
SYS_PLATFORM = None
|
||||||
SYS_ENCODING = None
|
SYS_ENCODING = None
|
||||||
|
|
||||||
VERBOSE = 1
|
QUIET = False
|
||||||
|
VERBOSE = False
|
||||||
DAEMON = False
|
DAEMON = False
|
||||||
CREATEPID = False
|
CREATEPID = False
|
||||||
PIDFILE= None
|
PIDFILE= None
|
||||||
@@ -82,7 +81,8 @@ API_KEY = None
|
|||||||
|
|
||||||
GIT_PATH = None
|
GIT_PATH = None
|
||||||
GIT_USER = None
|
GIT_USER = None
|
||||||
GIT_BRANCH =None
|
GIT_BRANCH = None
|
||||||
|
DO_NOT_OVERRIDE_GIT_BRANCH = False
|
||||||
INSTALL_TYPE = None
|
INSTALL_TYPE = None
|
||||||
CURRENT_VERSION = None
|
CURRENT_VERSION = None
|
||||||
LATEST_VERSION = None
|
LATEST_VERSION = None
|
||||||
@@ -105,15 +105,20 @@ PREFERRED_BITRATE_HIGH_BUFFER = None
|
|||||||
PREFERRED_BITRATE_LOW_BUFFER = None
|
PREFERRED_BITRATE_LOW_BUFFER = None
|
||||||
PREFERRED_BITRATE_ALLOW_LOSSLESS = False
|
PREFERRED_BITRATE_ALLOW_LOSSLESS = False
|
||||||
DETECT_BITRATE = False
|
DETECT_BITRATE = False
|
||||||
|
LOSSLESS_BITRATE_FROM = None
|
||||||
|
LOSSLESS_BITRATE_TO = None
|
||||||
ADD_ARTISTS = False
|
ADD_ARTISTS = False
|
||||||
CORRECT_METADATA = False
|
CORRECT_METADATA = False
|
||||||
|
FREEZE_DB = False
|
||||||
MOVE_FILES = False
|
MOVE_FILES = False
|
||||||
RENAME_FILES = False
|
RENAME_FILES = False
|
||||||
CLEANUP_FILES = False
|
CLEANUP_FILES = False
|
||||||
|
KEEP_NFO = False
|
||||||
ADD_ALBUM_ART = False
|
ADD_ALBUM_ART = False
|
||||||
ALBUM_ART_FORMAT = None
|
ALBUM_ART_FORMAT = None
|
||||||
EMBED_ALBUM_ART = False
|
EMBED_ALBUM_ART = False
|
||||||
EMBED_LYRICS = False
|
EMBED_LYRICS = False
|
||||||
|
REPLACE_EXISTING_FOLDERS = False
|
||||||
NZB_DOWNLOADER = None # 0: sabnzbd, 1: nzbget, 2: blackhole
|
NZB_DOWNLOADER = None # 0: sabnzbd, 1: nzbget, 2: blackhole
|
||||||
TORRENT_DOWNLOADER = None # 0: blackhole, 1: transmission, 2: utorrent
|
TORRENT_DOWNLOADER = None # 0: blackhole, 1: transmission, 2: utorrent
|
||||||
DOWNLOAD_DIR = None
|
DOWNLOAD_DIR = None
|
||||||
@@ -125,6 +130,8 @@ EXTRAS = None
|
|||||||
AUTOWANT_UPCOMING = False
|
AUTOWANT_UPCOMING = False
|
||||||
AUTOWANT_ALL = False
|
AUTOWANT_ALL = False
|
||||||
KEEP_TORRENT_FILES = False
|
KEEP_TORRENT_FILES = False
|
||||||
|
PREFER_TORRENTS = None # 0: nzbs, 1: torrents, 2: no preference
|
||||||
|
OPEN_MAGNET_LINKS = False
|
||||||
|
|
||||||
SEARCH_INTERVAL = 360
|
SEARCH_INTERVAL = 360
|
||||||
LIBRARYSCAN = False
|
LIBRARYSCAN = False
|
||||||
@@ -132,6 +139,7 @@ LIBRARYSCAN_INTERVAL = 300
|
|||||||
DOWNLOAD_SCAN_INTERVAL = 5
|
DOWNLOAD_SCAN_INTERVAL = 5
|
||||||
UPDATE_DB_INTERVAL = 24
|
UPDATE_DB_INTERVAL = 24
|
||||||
MB_IGNORE_AGE = 365
|
MB_IGNORE_AGE = 365
|
||||||
|
TORRENT_REMOVAL_INTERVAL = 720
|
||||||
|
|
||||||
SAB_HOST = None
|
SAB_HOST = None
|
||||||
SAB_USERNAME = None
|
SAB_USERNAME = None
|
||||||
@@ -143,6 +151,7 @@ NZBGET_USERNAME = None
|
|||||||
NZBGET_PASSWORD = None
|
NZBGET_PASSWORD = None
|
||||||
NZBGET_CATEGORY = None
|
NZBGET_CATEGORY = None
|
||||||
NZBGET_HOST = None
|
NZBGET_HOST = None
|
||||||
|
NZBGET_PRIORITY = 0
|
||||||
|
|
||||||
HEADPHONES_INDEXER = False
|
HEADPHONES_INDEXER = False
|
||||||
|
|
||||||
@@ -153,6 +162,7 @@ TRANSMISSION_PASSWORD = None
|
|||||||
UTORRENT_HOST = None
|
UTORRENT_HOST = None
|
||||||
UTORRENT_USERNAME = None
|
UTORRENT_USERNAME = None
|
||||||
UTORRENT_PASSWORD = None
|
UTORRENT_PASSWORD = None
|
||||||
|
UTORRENT_LABEL = None
|
||||||
|
|
||||||
NEWZNAB = False
|
NEWZNAB = False
|
||||||
NEWZNAB_HOST = None
|
NEWZNAB_HOST = None
|
||||||
@@ -164,10 +174,6 @@ NZBSORG = False
|
|||||||
NZBSORG_UID = None
|
NZBSORG_UID = None
|
||||||
NZBSORG_HASH = None
|
NZBSORG_HASH = None
|
||||||
|
|
||||||
NZBSRUS = False
|
|
||||||
NZBSRUS_UID = None
|
|
||||||
NZBSRUS_APIKEY = None
|
|
||||||
|
|
||||||
OMGWTFNZBS = False
|
OMGWTFNZBS = False
|
||||||
OMGWTFNZBS_UID = None
|
OMGWTFNZBS_UID = None
|
||||||
OMGWTFNZBS_APIKEY = None
|
OMGWTFNZBS_APIKEY = None
|
||||||
@@ -178,7 +184,7 @@ REQUIRED_WORDS = None
|
|||||||
|
|
||||||
LASTFM_USERNAME = None
|
LASTFM_USERNAME = None
|
||||||
|
|
||||||
LOSSY_MEDIA_FORMATS = ["mp3", "aac", "ogg", "ape", "m4a"]
|
LOSSY_MEDIA_FORMATS = ["mp3", "aac", "ogg", "ape", "m4a", "asf", "wma"]
|
||||||
LOSSLESS_MEDIA_FORMATS = ["flac"]
|
LOSSLESS_MEDIA_FORMATS = ["flac"]
|
||||||
MEDIA_FORMATS = LOSSY_MEDIA_FORMATS + LOSSLESS_MEDIA_FORMATS
|
MEDIA_FORMATS = LOSSY_MEDIA_FORMATS + LOSSLESS_MEDIA_FORMATS
|
||||||
|
|
||||||
@@ -186,20 +192,26 @@ ALBUM_COMPLETION_PCT = None # This is used in importer.py to determine how co
|
|||||||
|
|
||||||
TORRENTBLACKHOLE_DIR = None
|
TORRENTBLACKHOLE_DIR = None
|
||||||
NUMBEROFSEEDERS = 10
|
NUMBEROFSEEDERS = 10
|
||||||
ISOHUNT = None
|
|
||||||
KAT = None
|
KAT = None
|
||||||
|
KAT_PROXY_URL = None
|
||||||
|
KAT_RATIO = None
|
||||||
MININOVA = None
|
MININOVA = None
|
||||||
|
MININOVA_RATIO = None
|
||||||
PIRATEBAY = None
|
PIRATEBAY = None
|
||||||
PIRATEBAY_PROXY_URL = None
|
PIRATEBAY_PROXY_URL = None
|
||||||
|
PIRATEBAY_RATIO = None
|
||||||
WAFFLES = None
|
WAFFLES = None
|
||||||
WAFFLES_UID = None
|
WAFFLES_UID = None
|
||||||
WAFFLES_PASSKEY = None
|
WAFFLES_PASSKEY = None
|
||||||
|
WAFFLES_RATIO = None
|
||||||
RUTRACKER = None
|
RUTRACKER = None
|
||||||
RUTRACKER_USER = None
|
RUTRACKER_USER = None
|
||||||
RUTRACKER_PASSWORD = None
|
RUTRACKER_PASSWORD = None
|
||||||
|
RUTRACKER_RATIO = None
|
||||||
WHATCD = None
|
WHATCD = None
|
||||||
WHATCD_USERNAME = None
|
WHATCD_USERNAME = None
|
||||||
WHATCD_PASSWORD = None
|
WHATCD_PASSWORD = None
|
||||||
|
WHATCD_RATIO = None
|
||||||
DOWNLOAD_TORRENT_DIR = None
|
DOWNLOAD_TORRENT_DIR = None
|
||||||
|
|
||||||
INTERFACE = None
|
INTERFACE = None
|
||||||
@@ -218,7 +230,13 @@ ENCODEROUTPUTFORMAT = None
|
|||||||
ENCODERQUALITY = None
|
ENCODERQUALITY = None
|
||||||
ENCODERVBRCBR = None
|
ENCODERVBRCBR = None
|
||||||
ENCODERLOSSLESS = False
|
ENCODERLOSSLESS = False
|
||||||
|
ENCODER_MULTICORE = False
|
||||||
|
ENCODER_MULTICORE_COUNT = 0
|
||||||
DELETE_LOSSLESS_FILES = False
|
DELETE_LOSSLESS_FILES = False
|
||||||
|
GROWL_ENABLED = True
|
||||||
|
GROWL_HOST = None
|
||||||
|
GROWL_PASSWORD = None
|
||||||
|
GROWL_ONSNATCH = True
|
||||||
PROWL_ENABLED = True
|
PROWL_ENABLED = True
|
||||||
PROWL_PRIORITY = 1
|
PROWL_PRIORITY = 1
|
||||||
PROWL_KEYS = None
|
PROWL_KEYS = None
|
||||||
@@ -229,6 +247,8 @@ XBMC_USERNAME = None
|
|||||||
XBMC_PASSWORD = None
|
XBMC_PASSWORD = None
|
||||||
XBMC_UPDATE = False
|
XBMC_UPDATE = False
|
||||||
XBMC_NOTIFY = False
|
XBMC_NOTIFY = False
|
||||||
|
LMS_ENABLED = False
|
||||||
|
LMS_HOST = None
|
||||||
PLEX_ENABLED = False
|
PLEX_ENABLED = False
|
||||||
PLEX_SERVER_HOST = None
|
PLEX_SERVER_HOST = None
|
||||||
PLEX_CLIENT_HOST = None
|
PLEX_CLIENT_HOST = None
|
||||||
@@ -238,7 +258,7 @@ PLEX_UPDATE = False
|
|||||||
PLEX_NOTIFY = False
|
PLEX_NOTIFY = False
|
||||||
NMA_ENABLED = False
|
NMA_ENABLED = False
|
||||||
NMA_APIKEY = None
|
NMA_APIKEY = None
|
||||||
NMA_PRIORITY = None
|
NMA_PRIORITY = 0
|
||||||
NMA_ONSNATCH = None
|
NMA_ONSNATCH = None
|
||||||
PUSHALOT_ENABLED = False
|
PUSHALOT_ENABLED = False
|
||||||
PUSHALOT_APIKEY = None
|
PUSHALOT_APIKEY = None
|
||||||
@@ -248,6 +268,7 @@ PUSHOVER_ENABLED = True
|
|||||||
PUSHOVER_PRIORITY = 1
|
PUSHOVER_PRIORITY = 1
|
||||||
PUSHOVER_KEYS = None
|
PUSHOVER_KEYS = None
|
||||||
PUSHOVER_ONSNATCH = True
|
PUSHOVER_ONSNATCH = True
|
||||||
|
PUSHOVER_APITOKEN = None
|
||||||
PUSHBULLET_ENABLED = True
|
PUSHBULLET_ENABLED = True
|
||||||
PUSHBULLET_APIKEY = None
|
PUSHBULLET_APIKEY = None
|
||||||
PUSHBULLET_DEVICEID = None
|
PUSHBULLET_DEVICEID = None
|
||||||
@@ -257,6 +278,16 @@ TWITTER_ONSNATCH = False
|
|||||||
TWITTER_USERNAME = None
|
TWITTER_USERNAME = None
|
||||||
TWITTER_PASSWORD = None
|
TWITTER_PASSWORD = None
|
||||||
TWITTER_PREFIX = None
|
TWITTER_PREFIX = None
|
||||||
|
OSX_NOTIFY_ENABLED = False
|
||||||
|
OSX_NOTIFY_ONSNATCH = False
|
||||||
|
OSX_NOTIFY_APP = None
|
||||||
|
BOXCAR_ENABLED = False
|
||||||
|
BOXCAR_ONSNATCH = False
|
||||||
|
BOXCAR_TOKEN = None
|
||||||
|
SUBSONIC_ENABLED = False
|
||||||
|
SUBSONIC_HOST = None
|
||||||
|
SUBSONIC_USERNAME = None
|
||||||
|
SUBSONIC_PASSWORD = None
|
||||||
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||||
MIRROR = None
|
MIRROR = None
|
||||||
CUSTOMHOST = None
|
CUSTOMHOST = None
|
||||||
@@ -268,12 +299,15 @@ SONGKICK_ENABLED = False
|
|||||||
SONGKICK_APIKEY = None
|
SONGKICK_APIKEY = None
|
||||||
SONGKICK_LOCATION = None
|
SONGKICK_LOCATION = None
|
||||||
SONGKICK_FILTER_ENABLED = False
|
SONGKICK_FILTER_ENABLED = False
|
||||||
|
MPC_ENABLED = False
|
||||||
|
|
||||||
CACHE_SIZEMB = 32
|
CACHE_SIZEMB = 32
|
||||||
JOURNAL_MODE = None
|
JOURNAL_MODE = None
|
||||||
|
|
||||||
UMASK = None
|
UMASK = None
|
||||||
|
|
||||||
|
VERIFY_SSL_CERT = True
|
||||||
|
|
||||||
def CheckSection(sec):
|
def CheckSection(sec):
|
||||||
""" Check if INI section exists, if not create it """
|
""" Check if INI section exists, if not create it """
|
||||||
try:
|
try:
|
||||||
@@ -296,7 +330,7 @@ def check_setting_int(config, cfg_name, item_name, def_val):
|
|||||||
except:
|
except:
|
||||||
config[cfg_name] = {}
|
config[cfg_name] = {}
|
||||||
config[cfg_name][item_name] = my_val
|
config[cfg_name][item_name] = my_val
|
||||||
logger.debug(item_name + " -> " + str(my_val))
|
logger.debug("%s -> %s", item_name, my_val)
|
||||||
return my_val
|
return my_val
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
@@ -313,69 +347,50 @@ def check_setting_str(config, cfg_name, item_name, def_val, log=True):
|
|||||||
config[cfg_name] = {}
|
config[cfg_name] = {}
|
||||||
config[cfg_name][item_name] = my_val
|
config[cfg_name][item_name] = my_val
|
||||||
|
|
||||||
if log:
|
logger.debug("%s -> %s", item_name, my_val if log else "******")
|
||||||
logger.debug(item_name + " -> " + my_val)
|
|
||||||
else:
|
|
||||||
logger.debug(item_name + " -> ******")
|
|
||||||
return my_val
|
return my_val
|
||||||
|
|
||||||
def initialize():
|
def initialize():
|
||||||
|
|
||||||
with INIT_LOCK:
|
with INIT_LOCK:
|
||||||
|
|
||||||
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
|
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, QUIET, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
|
||||||
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, \
|
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
|
||||||
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
|
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, MUSIC_DIR, DESTINATION_DIR, \
|
||||||
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, MOVE_FILES, \
|
LOSSLESS_DESTINATION_DIR, PREFERRED_QUALITY, PREFERRED_BITRATE, DETECT_BITRATE, ADD_ARTISTS, CORRECT_METADATA, FREEZE_DB, MOVE_FILES, \
|
||||||
RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, FILE_UNDERSCORES, CLEANUP_FILES, INCLUDE_EXTRAS, EXTRAS, AUTOWANT_UPCOMING, AUTOWANT_ALL, KEEP_TORRENT_FILES, \
|
RENAME_FILES, FOLDER_FORMAT, FILE_FORMAT, FILE_UNDERSCORES, CLEANUP_FILES, KEEP_NFO, INCLUDE_EXTRAS, EXTRAS, AUTOWANT_UPCOMING, AUTOWANT_ALL, KEEP_TORRENT_FILES, PREFER_TORRENTS, OPEN_MAGNET_LINKS, \
|
||||||
ADD_ALBUM_ART, ALBUM_ART_FORMAT, EMBED_ALBUM_ART, EMBED_LYRICS, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, SEARCH_INTERVAL, \
|
ADD_ALBUM_ART, ALBUM_ART_FORMAT, EMBED_ALBUM_ART, EMBED_LYRICS, REPLACE_EXISTING_FOLDERS, DOWNLOAD_DIR, BLACKHOLE, BLACKHOLE_DIR, USENET_RETENTION, SEARCH_INTERVAL, \
|
||||||
TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, ISOHUNT, KAT, PIRATEBAY, PIRATEBAY_PROXY_URL, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \
|
TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, KAT, KAT_PROXY_URL, KAT_RATIO, PIRATEBAY, PIRATEBAY_PROXY_URL, PIRATEBAY_RATIO, MININOVA, MININOVA_RATIO, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, WAFFLES_RATIO, \
|
||||||
RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \
|
RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, RUTRACKER_RATIO, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, WHATCD_RATIO, DOWNLOAD_TORRENT_DIR, \
|
||||||
LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, UPDATE_DB_INTERVAL, MB_IGNORE_AGE, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \
|
LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, UPDATE_DB_INTERVAL, MB_IGNORE_AGE, TORRENT_REMOVAL_INTERVAL, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \
|
||||||
NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \
|
NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \
|
||||||
UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \
|
UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, UTORRENT_LABEL, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \
|
||||||
NZBSORG, NZBSORG_UID, NZBSORG_HASH, NZBSRUS, NZBSRUS_UID, NZBSRUS_APIKEY, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \
|
NZBSORG, NZBSORG_UID, NZBSORG_HASH, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \
|
||||||
NZB_DOWNLOADER, TORRENT_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, LASTFM_USERNAME, \
|
NZB_DOWNLOADER, TORRENT_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, LASTFM_USERNAME, \
|
||||||
INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \
|
INTERFACE, FOLDER_PERMISSIONS, FILE_PERMISSIONS, ENCODERFOLDER, ENCODER_PATH, ENCODER, XLDPROFILE, BITRATE, SAMPLINGFREQUENCY, \
|
||||||
MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, DELETE_LOSSLESS_FILES, \
|
MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \
|
||||||
PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, MIRRORLIST, \
|
GROWL_ENABLED, GROWL_HOST, GROWL_PASSWORD, GROWL_ONSNATCH, PROWL_ENABLED, PROWL_PRIORITY, PROWL_KEYS, PROWL_ONSNATCH, PUSHOVER_ENABLED, PUSHOVER_PRIORITY, PUSHOVER_KEYS, PUSHOVER_ONSNATCH, PUSHOVER_APITOKEN, MIRRORLIST, \
|
||||||
TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \
|
TWITTER_ENABLED, TWITTER_ONSNATCH, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, OSX_NOTIFY_ENABLED, OSX_NOTIFY_ONSNATCH, OSX_NOTIFY_APP, BOXCAR_ENABLED, BOXCAR_ONSNATCH, BOXCAR_TOKEN, \
|
||||||
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
|
PUSHBULLET_ENABLED, PUSHBULLET_APIKEY, PUSHBULLET_DEVICEID, PUSHBULLET_ONSNATCH, \
|
||||||
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
MIRROR, CUSTOMHOST, CUSTOMPORT, CUSTOMSLEEP, HPUSER, HPPASS, XBMC_ENABLED, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, XBMC_UPDATE, \
|
||||||
XBMC_NOTIFY, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
XBMC_NOTIFY, LMS_ENABLED, LMS_HOST, NMA_ENABLED, NMA_APIKEY, NMA_PRIORITY, NMA_ONSNATCH, SYNOINDEX_ENABLED, ALBUM_COMPLETION_PCT, PREFERRED_BITRATE_HIGH_BUFFER, \
|
||||||
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
PREFERRED_BITRATE_LOW_BUFFER, PREFERRED_BITRATE_ALLOW_LOSSLESS, LOSSLESS_BITRATE_FROM, LOSSLESS_BITRATE_TO, CACHE_SIZEMB, JOURNAL_MODE, UMASK, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, \
|
||||||
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
|
PLEX_ENABLED, PLEX_SERVER_HOST, PLEX_CLIENT_HOST, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE, PLEX_NOTIFY, PUSHALOT_ENABLED, PUSHALOT_APIKEY, \
|
||||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED
|
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, SUBSONIC_ENABLED, SUBSONIC_HOST, SUBSONIC_USERNAME, SUBSONIC_PASSWORD, VERIFY_SSL_CERT
|
||||||
|
|
||||||
|
|
||||||
if __INITIALIZED__:
|
if __INITIALIZED__:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Make sure all the config sections exist
|
# Make sure all the config sections exist
|
||||||
CheckSection('General')
|
for section in ('General', 'SABnzbd', 'NZBget', 'Transmission',
|
||||||
CheckSection('SABnzbd')
|
'uTorrent', 'Headphones', 'Newznab', 'NZBsorg',
|
||||||
CheckSection('NZBget')
|
'omgwtfnzbs', 'Piratebay', 'Kat', 'Mininova', 'Waffles',
|
||||||
CheckSection('Transmission')
|
'Rutracker', 'What.cd', 'Growl', 'Prowl', 'Pushover',
|
||||||
CheckSection('uTorrent')
|
'PushBullet', 'XBMC', 'LMS', 'Plex', 'NMA', 'Pushalot',
|
||||||
CheckSection('Headphones')
|
'Synoindex', 'Twitter', 'OSX_Notify', 'Boxcar',
|
||||||
CheckSection('Newznab')
|
'Songkick', 'Advanced'):
|
||||||
CheckSection('NZBsorg')
|
CheckSection(section)
|
||||||
CheckSection('NZBsRus')
|
|
||||||
CheckSection('omgwtfnzbs')
|
|
||||||
CheckSection('Waffles')
|
|
||||||
CheckSection('Rutracker')
|
|
||||||
CheckSection('What.cd')
|
|
||||||
CheckSection('Prowl')
|
|
||||||
CheckSection('Pushover')
|
|
||||||
CheckSection('PushBullet')
|
|
||||||
CheckSection('XBMC')
|
|
||||||
CheckSection('Plex')
|
|
||||||
CheckSection('NMA')
|
|
||||||
CheckSection('Pushalot')
|
|
||||||
CheckSection('Synoindex')
|
|
||||||
CheckSection('Twitter')
|
|
||||||
CheckSection('Songkick')
|
|
||||||
CheckSection('Advanced')
|
|
||||||
|
|
||||||
# Set global variables based on config file or use defaults
|
# Set global variables based on config file or use defaults
|
||||||
CONFIG_VERSION = check_setting_str(CFG, 'General', 'config_version', '0')
|
CONFIG_VERSION = check_setting_str(CFG, 'General', 'config_version', '0')
|
||||||
@@ -402,6 +417,7 @@ def initialize():
|
|||||||
GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '')
|
GIT_PATH = check_setting_str(CFG, 'General', 'git_path', '')
|
||||||
GIT_USER = check_setting_str(CFG, 'General', 'git_user', 'rembo10')
|
GIT_USER = check_setting_str(CFG, 'General', 'git_user', 'rembo10')
|
||||||
GIT_BRANCH = check_setting_str(CFG, 'General', 'git_branch', 'master')
|
GIT_BRANCH = check_setting_str(CFG, 'General', 'git_branch', 'master')
|
||||||
|
DO_NOT_OVERRIDE_GIT_BRANCH = check_setting_int(CFG, 'General', 'do_not_override_git_branch', 0)
|
||||||
LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', '')
|
LOG_DIR = check_setting_str(CFG, 'General', 'log_dir', '')
|
||||||
CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', '')
|
CACHE_DIR = check_setting_str(CFG, 'General', 'cache_dir', '')
|
||||||
|
|
||||||
@@ -418,18 +434,23 @@ def initialize():
|
|||||||
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
|
PREFERRED_BITRATE_LOW_BUFFER = check_setting_int(CFG, 'General', 'preferred_bitrate_low_buffer', '')
|
||||||
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
|
PREFERRED_BITRATE_ALLOW_LOSSLESS = bool(check_setting_int(CFG, 'General', 'preferred_bitrate_allow_lossless', 0))
|
||||||
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
|
DETECT_BITRATE = bool(check_setting_int(CFG, 'General', 'detect_bitrate', 0))
|
||||||
|
LOSSLESS_BITRATE_FROM = check_setting_int(CFG, 'General', 'lossless_bitrate_from', '')
|
||||||
|
LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '')
|
||||||
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
|
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
|
||||||
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
|
CORRECT_METADATA = bool(check_setting_int(CFG, 'General', 'correct_metadata', 0))
|
||||||
|
FREEZE_DB = bool(check_setting_int(CFG, 'General', 'freeze_db', 0))
|
||||||
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
|
MOVE_FILES = bool(check_setting_int(CFG, 'General', 'move_files', 0))
|
||||||
RENAME_FILES = bool(check_setting_int(CFG, 'General', 'rename_files', 0))
|
RENAME_FILES = bool(check_setting_int(CFG, 'General', 'rename_files', 0))
|
||||||
FOLDER_FORMAT = check_setting_str(CFG, 'General', 'folder_format', 'Artist/Album [Year]')
|
FOLDER_FORMAT = check_setting_str(CFG, 'General', 'folder_format', 'Artist/Album [Year]')
|
||||||
FILE_FORMAT = check_setting_str(CFG, 'General', 'file_format', 'Track Artist - Album [Year] - Title')
|
FILE_FORMAT = check_setting_str(CFG, 'General', 'file_format', 'Track Artist - Album [Year] - Title')
|
||||||
FILE_UNDERSCORES = bool(check_setting_int(CFG, 'General', 'file_underscores', 0))
|
FILE_UNDERSCORES = bool(check_setting_int(CFG, 'General', 'file_underscores', 0))
|
||||||
CLEANUP_FILES = bool(check_setting_int(CFG, 'General', 'cleanup_files', 0))
|
CLEANUP_FILES = bool(check_setting_int(CFG, 'General', 'cleanup_files', 0))
|
||||||
|
KEEP_NFO = bool(check_setting_int(CFG, 'General', 'keep_nfo', 0))
|
||||||
ADD_ALBUM_ART = bool(check_setting_int(CFG, 'General', 'add_album_art', 0))
|
ADD_ALBUM_ART = bool(check_setting_int(CFG, 'General', 'add_album_art', 0))
|
||||||
ALBUM_ART_FORMAT = check_setting_str(CFG, 'General', 'album_art_format', 'folder')
|
ALBUM_ART_FORMAT = check_setting_str(CFG, 'General', 'album_art_format', 'folder')
|
||||||
EMBED_ALBUM_ART = bool(check_setting_int(CFG, 'General', 'embed_album_art', 0))
|
EMBED_ALBUM_ART = bool(check_setting_int(CFG, 'General', 'embed_album_art', 0))
|
||||||
EMBED_LYRICS = bool(check_setting_int(CFG, 'General', 'embed_lyrics', 0))
|
EMBED_LYRICS = bool(check_setting_int(CFG, 'General', 'embed_lyrics', 0))
|
||||||
|
REPLACE_EXISTING_FOLDERS = bool(check_setting_int(CFG, 'General', 'replace_existing_folders', 0))
|
||||||
NZB_DOWNLOADER = check_setting_int(CFG, 'General', 'nzb_downloader', 0)
|
NZB_DOWNLOADER = check_setting_int(CFG, 'General', 'nzb_downloader', 0)
|
||||||
TORRENT_DOWNLOADER = check_setting_int(CFG, 'General', 'torrent_downloader', 0)
|
TORRENT_DOWNLOADER = check_setting_int(CFG, 'General', 'torrent_downloader', 0)
|
||||||
DOWNLOAD_DIR = check_setting_str(CFG, 'General', 'download_dir', '')
|
DOWNLOAD_DIR = check_setting_str(CFG, 'General', 'download_dir', '')
|
||||||
@@ -441,6 +462,8 @@ def initialize():
|
|||||||
AUTOWANT_UPCOMING = bool(check_setting_int(CFG, 'General', 'autowant_upcoming', 1))
|
AUTOWANT_UPCOMING = bool(check_setting_int(CFG, 'General', 'autowant_upcoming', 1))
|
||||||
AUTOWANT_ALL = bool(check_setting_int(CFG, 'General', 'autowant_all', 0))
|
AUTOWANT_ALL = bool(check_setting_int(CFG, 'General', 'autowant_all', 0))
|
||||||
KEEP_TORRENT_FILES = bool(check_setting_int(CFG, 'General', 'keep_torrent_files', 0))
|
KEEP_TORRENT_FILES = bool(check_setting_int(CFG, 'General', 'keep_torrent_files', 0))
|
||||||
|
PREFER_TORRENTS = check_setting_int(CFG, 'General', 'prefer_torrents', 0)
|
||||||
|
OPEN_MAGNET_LINKS = bool(check_setting_int(CFG, 'General', 'open_magnet_links', 0))
|
||||||
|
|
||||||
SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'search_interval', 1440)
|
SEARCH_INTERVAL = check_setting_int(CFG, 'General', 'search_interval', 1440)
|
||||||
LIBRARYSCAN = bool(check_setting_int(CFG, 'General', 'libraryscan', 1))
|
LIBRARYSCAN = bool(check_setting_int(CFG, 'General', 'libraryscan', 1))
|
||||||
@@ -448,27 +471,37 @@ def initialize():
|
|||||||
DOWNLOAD_SCAN_INTERVAL = check_setting_int(CFG, 'General', 'download_scan_interval', 5)
|
DOWNLOAD_SCAN_INTERVAL = check_setting_int(CFG, 'General', 'download_scan_interval', 5)
|
||||||
UPDATE_DB_INTERVAL = check_setting_int(CFG, 'General', 'update_db_interval', 24)
|
UPDATE_DB_INTERVAL = check_setting_int(CFG, 'General', 'update_db_interval', 24)
|
||||||
MB_IGNORE_AGE = check_setting_int(CFG, 'General', 'mb_ignore_age', 365)
|
MB_IGNORE_AGE = check_setting_int(CFG, 'General', 'mb_ignore_age', 365)
|
||||||
|
TORRENT_REMOVAL_INTERVAL = check_setting_int(CFG, 'General', 'torrent_removal_interval', 720)
|
||||||
|
|
||||||
TORRENTBLACKHOLE_DIR = check_setting_str(CFG, 'General', 'torrentblackhole_dir', '')
|
TORRENTBLACKHOLE_DIR = check_setting_str(CFG, 'General', 'torrentblackhole_dir', '')
|
||||||
NUMBEROFSEEDERS = check_setting_str(CFG, 'General', 'numberofseeders', '10')
|
NUMBEROFSEEDERS = check_setting_str(CFG, 'General', 'numberofseeders', '10')
|
||||||
ISOHUNT = bool(check_setting_int(CFG, 'General', 'isohunt', 0))
|
|
||||||
KAT = bool(check_setting_int(CFG, 'General', 'kat', 0))
|
|
||||||
PIRATEBAY = bool(check_setting_int(CFG, 'General', 'piratebay', 0))
|
|
||||||
PIRATEBAY_PROXY_URL = check_setting_str(CFG, 'General', 'piratebay_proxy_url', '')
|
|
||||||
MININOVA = bool(check_setting_int(CFG, 'General', 'mininova', 0))
|
|
||||||
DOWNLOAD_TORRENT_DIR = check_setting_str(CFG, 'General', 'download_torrent_dir', '')
|
DOWNLOAD_TORRENT_DIR = check_setting_str(CFG, 'General', 'download_torrent_dir', '')
|
||||||
|
|
||||||
|
KAT = bool(check_setting_int(CFG, 'Kat', 'kat', 0))
|
||||||
|
KAT_PROXY_URL = check_setting_str(CFG, 'Kat', 'kat_proxy_url', '')
|
||||||
|
KAT_RATIO = check_setting_str(CFG, 'Kat', 'kat_ratio', '')
|
||||||
|
|
||||||
|
PIRATEBAY = bool(check_setting_int(CFG, 'Piratebay', 'piratebay', 0))
|
||||||
|
PIRATEBAY_PROXY_URL = check_setting_str(CFG, 'Piratebay', 'piratebay_proxy_url', '')
|
||||||
|
PIRATEBAY_RATIO = check_setting_str(CFG, 'Piratebay', 'piratebay_ratio', '')
|
||||||
|
|
||||||
|
MININOVA = bool(check_setting_int(CFG, 'Mininova', 'mininova', 0))
|
||||||
|
MININOVA_RATIO = check_setting_str(CFG, 'Mininova', 'mininova_ratio', '')
|
||||||
|
|
||||||
WAFFLES = bool(check_setting_int(CFG, 'Waffles', 'waffles', 0))
|
WAFFLES = bool(check_setting_int(CFG, 'Waffles', 'waffles', 0))
|
||||||
WAFFLES_UID = check_setting_str(CFG, 'Waffles', 'waffles_uid', '')
|
WAFFLES_UID = check_setting_str(CFG, 'Waffles', 'waffles_uid', '')
|
||||||
WAFFLES_PASSKEY = check_setting_str(CFG, 'Waffles', 'waffles_passkey', '')
|
WAFFLES_PASSKEY = check_setting_str(CFG, 'Waffles', 'waffles_passkey', '')
|
||||||
|
WAFFLES_RATIO = check_setting_str(CFG, 'Waffles', 'waffles_ratio', '')
|
||||||
|
|
||||||
RUTRACKER = bool(check_setting_int(CFG, 'Rutracker', 'rutracker', 0))
|
RUTRACKER = bool(check_setting_int(CFG, 'Rutracker', 'rutracker', 0))
|
||||||
RUTRACKER_USER = check_setting_str(CFG, 'Rutracker', 'rutracker_user', '')
|
RUTRACKER_USER = check_setting_str(CFG, 'Rutracker', 'rutracker_user', '')
|
||||||
RUTRACKER_PASSWORD = check_setting_str(CFG, 'Rutracker', 'rutracker_password', '')
|
RUTRACKER_PASSWORD = check_setting_str(CFG, 'Rutracker', 'rutracker_password', '')
|
||||||
|
RUTRACKER_RATIO = check_setting_str(CFG, 'Rutracker', 'rutracker_ratio', '')
|
||||||
|
|
||||||
WHATCD = bool(check_setting_int(CFG, 'What.cd', 'whatcd', 0))
|
WHATCD = bool(check_setting_int(CFG, 'What.cd', 'whatcd', 0))
|
||||||
WHATCD_USERNAME = check_setting_str(CFG, 'What.cd', 'whatcd_username', '')
|
WHATCD_USERNAME = check_setting_str(CFG, 'What.cd', 'whatcd_username', '')
|
||||||
WHATCD_PASSWORD = check_setting_str(CFG, 'What.cd', 'whatcd_password', '')
|
WHATCD_PASSWORD = check_setting_str(CFG, 'What.cd', 'whatcd_password', '')
|
||||||
|
WHATCD_RATIO = check_setting_str(CFG, 'What.cd', 'whatcd_ratio', '')
|
||||||
|
|
||||||
SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '')
|
SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '')
|
||||||
SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '')
|
SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '')
|
||||||
@@ -480,6 +513,7 @@ def initialize():
|
|||||||
NZBGET_PASSWORD = check_setting_str(CFG, 'NZBget', 'nzbget_password', '')
|
NZBGET_PASSWORD = check_setting_str(CFG, 'NZBget', 'nzbget_password', '')
|
||||||
NZBGET_CATEGORY = check_setting_str(CFG, 'NZBget', 'nzbget_category', '')
|
NZBGET_CATEGORY = check_setting_str(CFG, 'NZBget', 'nzbget_category', '')
|
||||||
NZBGET_HOST = check_setting_str(CFG, 'NZBget', 'nzbget_host', '')
|
NZBGET_HOST = check_setting_str(CFG, 'NZBget', 'nzbget_host', '')
|
||||||
|
NZBGET_PRIORITY = check_setting_int(CFG, 'NZBget', 'nzbget_priority', 0)
|
||||||
|
|
||||||
HEADPHONES_INDEXER = bool(check_setting_int(CFG, 'Headphones', 'headphones_indexer', 0))
|
HEADPHONES_INDEXER = bool(check_setting_int(CFG, 'Headphones', 'headphones_indexer', 0))
|
||||||
|
|
||||||
@@ -490,6 +524,7 @@ def initialize():
|
|||||||
UTORRENT_HOST = check_setting_str(CFG, 'uTorrent', 'utorrent_host', '')
|
UTORRENT_HOST = check_setting_str(CFG, 'uTorrent', 'utorrent_host', '')
|
||||||
UTORRENT_USERNAME = check_setting_str(CFG, 'uTorrent', 'utorrent_username', '')
|
UTORRENT_USERNAME = check_setting_str(CFG, 'uTorrent', 'utorrent_username', '')
|
||||||
UTORRENT_PASSWORD = check_setting_str(CFG, 'uTorrent', 'utorrent_password', '')
|
UTORRENT_PASSWORD = check_setting_str(CFG, 'uTorrent', 'utorrent_password', '')
|
||||||
|
UTORRENT_LABEL = check_setting_str(CFG, 'uTorrent', 'utorrent_label', '')
|
||||||
|
|
||||||
NEWZNAB = bool(check_setting_int(CFG, 'Newznab', 'newznab', 0))
|
NEWZNAB = bool(check_setting_int(CFG, 'Newznab', 'newznab', 0))
|
||||||
NEWZNAB_HOST = check_setting_str(CFG, 'Newznab', 'newznab_host', '')
|
NEWZNAB_HOST = check_setting_str(CFG, 'Newznab', 'newznab_host', '')
|
||||||
@@ -504,10 +539,6 @@ def initialize():
|
|||||||
NZBSORG_UID = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_uid', '')
|
NZBSORG_UID = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_uid', '')
|
||||||
NZBSORG_HASH = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_hash', '')
|
NZBSORG_HASH = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_hash', '')
|
||||||
|
|
||||||
NZBSRUS = bool(check_setting_int(CFG, 'NZBsRus', 'nzbsrus', 0))
|
|
||||||
NZBSRUS_UID = check_setting_str(CFG, 'NZBsRus', 'nzbsrus_uid', '')
|
|
||||||
NZBSRUS_APIKEY = check_setting_str(CFG, 'NZBsRus', 'nzbsrus_apikey', '')
|
|
||||||
|
|
||||||
OMGWTFNZBS = bool(check_setting_int(CFG, 'omgwtfnzbs', 'omgwtfnzbs', 0))
|
OMGWTFNZBS = bool(check_setting_int(CFG, 'omgwtfnzbs', 'omgwtfnzbs', 0))
|
||||||
OMGWTFNZBS_UID = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_uid', '')
|
OMGWTFNZBS_UID = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_uid', '')
|
||||||
OMGWTFNZBS_APIKEY = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_apikey', '')
|
OMGWTFNZBS_APIKEY = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_apikey', '')
|
||||||
@@ -534,8 +565,15 @@ def initialize():
|
|||||||
ENCODERQUALITY = check_setting_int(CFG, 'General', 'encoderquality', 2)
|
ENCODERQUALITY = check_setting_int(CFG, 'General', 'encoderquality', 2)
|
||||||
ENCODERVBRCBR = check_setting_str(CFG, 'General', 'encodervbrcbr', 'cbr')
|
ENCODERVBRCBR = check_setting_str(CFG, 'General', 'encodervbrcbr', 'cbr')
|
||||||
ENCODERLOSSLESS = bool(check_setting_int(CFG, 'General', 'encoderlossless', 1))
|
ENCODERLOSSLESS = bool(check_setting_int(CFG, 'General', 'encoderlossless', 1))
|
||||||
|
ENCODER_MULTICORE = bool(check_setting_int(CFG, 'General', 'encoder_multicore', 0))
|
||||||
|
ENCODER_MULTICORE_COUNT = max(0, check_setting_int(CFG, 'General', 'encoder_multicore_count', 0))
|
||||||
DELETE_LOSSLESS_FILES = bool(check_setting_int(CFG, 'General', 'delete_lossless_files', 1))
|
DELETE_LOSSLESS_FILES = bool(check_setting_int(CFG, 'General', 'delete_lossless_files', 1))
|
||||||
|
|
||||||
|
GROWL_ENABLED = bool(check_setting_int(CFG, 'Growl', 'growl_enabled', 0))
|
||||||
|
GROWL_HOST = check_setting_str(CFG, 'Growl', 'growl_host', '')
|
||||||
|
GROWL_PASSWORD = check_setting_str(CFG, 'Growl', 'growl_password', '')
|
||||||
|
GROWL_ONSNATCH = bool(check_setting_int(CFG, 'Growl', 'growl_onsnatch', 0))
|
||||||
|
|
||||||
PROWL_ENABLED = bool(check_setting_int(CFG, 'Prowl', 'prowl_enabled', 0))
|
PROWL_ENABLED = bool(check_setting_int(CFG, 'Prowl', 'prowl_enabled', 0))
|
||||||
PROWL_KEYS = check_setting_str(CFG, 'Prowl', 'prowl_keys', '')
|
PROWL_KEYS = check_setting_str(CFG, 'Prowl', 'prowl_keys', '')
|
||||||
PROWL_ONSNATCH = bool(check_setting_int(CFG, 'Prowl', 'prowl_onsnatch', 0))
|
PROWL_ONSNATCH = bool(check_setting_int(CFG, 'Prowl', 'prowl_onsnatch', 0))
|
||||||
@@ -548,6 +586,9 @@ def initialize():
|
|||||||
XBMC_UPDATE = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update', 0))
|
XBMC_UPDATE = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update', 0))
|
||||||
XBMC_NOTIFY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify', 0))
|
XBMC_NOTIFY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify', 0))
|
||||||
|
|
||||||
|
LMS_ENABLED = bool(check_setting_int(CFG, 'LMS', 'lms_enabled', 0))
|
||||||
|
LMS_HOST = check_setting_str(CFG, 'LMS', 'lms_host', '')
|
||||||
|
|
||||||
PLEX_ENABLED = bool(check_setting_int(CFG, 'Plex', 'plex_enabled', 0))
|
PLEX_ENABLED = bool(check_setting_int(CFG, 'Plex', 'plex_enabled', 0))
|
||||||
PLEX_SERVER_HOST = check_setting_str(CFG, 'Plex', 'plex_server_host', '')
|
PLEX_SERVER_HOST = check_setting_str(CFG, 'Plex', 'plex_server_host', '')
|
||||||
PLEX_CLIENT_HOST = check_setting_str(CFG, 'Plex', 'plex_client_host', '')
|
PLEX_CLIENT_HOST = check_setting_str(CFG, 'Plex', 'plex_client_host', '')
|
||||||
@@ -571,6 +612,7 @@ def initialize():
|
|||||||
PUSHOVER_KEYS = check_setting_str(CFG, 'Pushover', 'pushover_keys', '')
|
PUSHOVER_KEYS = check_setting_str(CFG, 'Pushover', 'pushover_keys', '')
|
||||||
PUSHOVER_ONSNATCH = bool(check_setting_int(CFG, 'Pushover', 'pushover_onsnatch', 0))
|
PUSHOVER_ONSNATCH = bool(check_setting_int(CFG, 'Pushover', 'pushover_onsnatch', 0))
|
||||||
PUSHOVER_PRIORITY = check_setting_int(CFG, 'Pushover', 'pushover_priority', 0)
|
PUSHOVER_PRIORITY = check_setting_int(CFG, 'Pushover', 'pushover_priority', 0)
|
||||||
|
PUSHOVER_APITOKEN = check_setting_str(CFG, 'Pushover', 'pushover_apitoken', '')
|
||||||
|
|
||||||
PUSHBULLET_ENABLED = bool(check_setting_int(CFG, 'PushBullet', 'pushbullet_enabled', 0))
|
PUSHBULLET_ENABLED = bool(check_setting_int(CFG, 'PushBullet', 'pushbullet_enabled', 0))
|
||||||
PUSHBULLET_APIKEY = check_setting_str(CFG, 'PushBullet', 'pushbullet_apikey', '')
|
PUSHBULLET_APIKEY = check_setting_str(CFG, 'PushBullet', 'pushbullet_apikey', '')
|
||||||
@@ -583,6 +625,19 @@ def initialize():
|
|||||||
TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
|
TWITTER_PASSWORD = check_setting_str(CFG, 'Twitter', 'twitter_password', '')
|
||||||
TWITTER_PREFIX = check_setting_str(CFG, 'Twitter', 'twitter_prefix', 'Headphones')
|
TWITTER_PREFIX = check_setting_str(CFG, 'Twitter', 'twitter_prefix', 'Headphones')
|
||||||
|
|
||||||
|
OSX_NOTIFY_ENABLED = bool(check_setting_int(CFG, 'OSX_Notify', 'osx_notify_enabled', 0))
|
||||||
|
OSX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'OSX_Notify', 'osx_notify_onsnatch', 0))
|
||||||
|
OSX_NOTIFY_APP = check_setting_str(CFG, 'OSX_Notify', 'osx_notify_app', '/Applications/Headphones')
|
||||||
|
|
||||||
|
BOXCAR_ENABLED = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_enabled', 0))
|
||||||
|
BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
|
||||||
|
BOXCAR_TOKEN = check_setting_str(CFG, 'Boxcar', 'boxcar_token', '')
|
||||||
|
|
||||||
|
SUBSONIC_ENABLED = bool(check_setting_int(CFG, 'Subsonic', 'subsonic_enabled', 0))
|
||||||
|
SUBSONIC_HOST = check_setting_str(CFG, 'Subsonic', 'subsonic_host', '')
|
||||||
|
SUBSONIC_USERNAME = check_setting_str(CFG, 'Subsonic', 'subsonic_username', '')
|
||||||
|
SUBSONIC_PASSWORD = check_setting_str(CFG, 'Subsonic', 'subsonic_password', '')
|
||||||
|
|
||||||
SONGKICK_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
|
SONGKICK_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
|
||||||
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
||||||
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
||||||
@@ -600,6 +655,8 @@ def initialize():
|
|||||||
|
|
||||||
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
|
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
|
||||||
|
|
||||||
|
VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', 1))
|
||||||
|
|
||||||
# update folder formats in the config & bump up config version
|
# update folder formats in the config & bump up config version
|
||||||
if CONFIG_VERSION == '0':
|
if CONFIG_VERSION == '0':
|
||||||
from headphones.helpers import replace_all
|
from headphones.helpers import replace_all
|
||||||
@@ -649,10 +706,10 @@ def initialize():
|
|||||||
CONFIG_VERSION = '3'
|
CONFIG_VERSION = '3'
|
||||||
|
|
||||||
if CONFIG_VERSION == '3':
|
if CONFIG_VERSION == '3':
|
||||||
#Update the BLACKHOLE option to the NZB_DOWNLOADER format
|
#Update the BLACKHOLE option to the NZB_DOWNLOADER format
|
||||||
if BLACKHOLE:
|
if BLACKHOLE:
|
||||||
NZB_DOWNLOADER = 2
|
NZB_DOWNLOADER = 2
|
||||||
CONFIG_VERSION = '4'
|
CONFIG_VERSION = '4'
|
||||||
|
|
||||||
# Enable Headphones Indexer if they have a VIP account
|
# Enable Headphones Indexer if they have a VIP account
|
||||||
if CONFIG_VERSION == '4':
|
if CONFIG_VERSION == '4':
|
||||||
@@ -668,10 +725,10 @@ def initialize():
|
|||||||
os.makedirs(LOG_DIR)
|
os.makedirs(LOG_DIR)
|
||||||
except OSError:
|
except OSError:
|
||||||
if VERBOSE:
|
if VERBOSE:
|
||||||
print 'Unable to create the log directory. Logging to screen only.'
|
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
|
||||||
|
|
||||||
# Start the logger, silence console logging if we need to
|
# Start the logger, disable console if needed
|
||||||
logger.headphones_log.initLogger(verbose=VERBOSE)
|
logger.initLogger(console=not QUIET, verbose=VERBOSE)
|
||||||
|
|
||||||
if not CACHE_DIR:
|
if not CACHE_DIR:
|
||||||
# Put the cache dir in the data dir for now
|
# Put the cache dir in the data dir for now
|
||||||
@@ -680,7 +737,7 @@ def initialize():
|
|||||||
try:
|
try:
|
||||||
os.makedirs(CACHE_DIR)
|
os.makedirs(CACHE_DIR)
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.error('Could not create cache dir. Check permissions of datadir: ' + DATA_DIR)
|
logger.error('Could not create cache dir. Check permissions of datadir: %s', DATA_DIR)
|
||||||
|
|
||||||
# Sanity check for search interval. Set it to at least 6 hours
|
# Sanity check for search interval. Set it to at least 6 hours
|
||||||
if SEARCH_INTERVAL < 360:
|
if SEARCH_INTERVAL < 360:
|
||||||
@@ -692,17 +749,18 @@ def initialize():
|
|||||||
try:
|
try:
|
||||||
dbcheck()
|
dbcheck()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error("Can't connect to the database: %s" % e)
|
logger.error("Can't connect to the database: %s", e)
|
||||||
|
|
||||||
# Get the currently installed version - returns None, 'win32' or the git hash
|
# Get the currently installed version - returns None, 'win32' or the git hash
|
||||||
# Also sets INSTALL_TYPE variable to 'win', 'git' or 'source'
|
# Also sets INSTALL_TYPE variable to 'win', 'git' or 'source'
|
||||||
CURRENT_VERSION = versioncheck.getVersion()
|
CURRENT_VERSION, GIT_BRANCH = versioncheck.getVersion()
|
||||||
|
|
||||||
# Check for new versions
|
# Check for new versions
|
||||||
if CHECK_GITHUB_ON_STARTUP:
|
if CHECK_GITHUB_ON_STARTUP:
|
||||||
try:
|
try:
|
||||||
LATEST_VERSION = versioncheck.checkGithub()
|
LATEST_VERSION = versioncheck.checkGithub()
|
||||||
except:
|
except:
|
||||||
|
logger.exception("Unhandled exception")
|
||||||
LATEST_VERSION = CURRENT_VERSION
|
LATEST_VERSION = CURRENT_VERSION
|
||||||
else:
|
else:
|
||||||
LATEST_VERSION = CURRENT_VERSION
|
LATEST_VERSION = CURRENT_VERSION
|
||||||
@@ -729,7 +787,7 @@ def daemonize():
|
|||||||
if pid != 0:
|
if pid != 0:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
raise RuntimeError("1st fork failed: %s [%d]" % (e.strerror, e.errno))
|
raise RuntimeError("1st fork failed: %s [%d]", e.strerror, e.errno)
|
||||||
|
|
||||||
os.setsid()
|
os.setsid()
|
||||||
|
|
||||||
@@ -743,7 +801,7 @@ def daemonize():
|
|||||||
if pid != 0:
|
if pid != 0:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except OSError, e:
|
except OSError, e:
|
||||||
raise RuntimeError("2nd fork failed: %s [%d]" % (e.strerror, e.errno))
|
raise RuntimeError("2nd fork failed: %s [%d]", e.strerror, e.errno)
|
||||||
|
|
||||||
dev_null = file('/dev/null', 'r')
|
dev_null = file('/dev/null', 'r')
|
||||||
os.dup2(dev_null.fileno(), sys.stdin.fileno())
|
os.dup2(dev_null.fileno(), sys.stdin.fileno())
|
||||||
@@ -756,12 +814,13 @@ def daemonize():
|
|||||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||||
|
|
||||||
pid = str(os.getpid())
|
pid = os.getpid()
|
||||||
logger.info('Daemonized to PID: %s' % pid)
|
logger.info('Daemonized to PID: %d', pid)
|
||||||
|
|
||||||
if CREATEPID:
|
if CREATEPID:
|
||||||
logger.info("Writing PID " + pid + " to " + str(PIDFILE))
|
logger.info("Writing PID %d to %s", pid, PIDFILE)
|
||||||
file(PIDFILE, 'w').write("%s\n" % pid)
|
with file(PIDFILE, 'w') as fp:
|
||||||
|
fp.write("%s\n" % pid)
|
||||||
|
|
||||||
def launch_browser(host, port, root):
|
def launch_browser(host, port, root):
|
||||||
|
|
||||||
@@ -775,10 +834,14 @@ def launch_browser(host, port, root):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root))
|
webbrowser.open('%s://%s:%i%s' % (protocol, host, port, root))
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.error('Could not launch browser: %s' % e)
|
logger.error('Could not launch browser: %s', e)
|
||||||
|
|
||||||
def config_write():
|
def config_write():
|
||||||
|
"""
|
||||||
|
Write configuration to file. If an IOError occures during a write, it will
|
||||||
|
be caught.
|
||||||
|
"""
|
||||||
|
|
||||||
new_config = ConfigObj(encoding="UTF-8")
|
new_config = ConfigObj(encoding="UTF-8")
|
||||||
new_config.filename = CONFIG_FILE
|
new_config.filename = CONFIG_FILE
|
||||||
@@ -802,6 +865,7 @@ def config_write():
|
|||||||
new_config['General']['git_path'] = GIT_PATH
|
new_config['General']['git_path'] = GIT_PATH
|
||||||
new_config['General']['git_user'] = GIT_USER
|
new_config['General']['git_user'] = GIT_USER
|
||||||
new_config['General']['git_branch'] = GIT_BRANCH
|
new_config['General']['git_branch'] = GIT_BRANCH
|
||||||
|
new_config['General']['do_not_override_git_branch'] = int(DO_NOT_OVERRIDE_GIT_BRANCH)
|
||||||
|
|
||||||
new_config['General']['check_github'] = int(CHECK_GITHUB)
|
new_config['General']['check_github'] = int(CHECK_GITHUB)
|
||||||
new_config['General']['check_github_on_startup'] = int(CHECK_GITHUB_ON_STARTUP)
|
new_config['General']['check_github_on_startup'] = int(CHECK_GITHUB_ON_STARTUP)
|
||||||
@@ -816,18 +880,23 @@ def config_write():
|
|||||||
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
|
new_config['General']['preferred_bitrate_low_buffer'] = PREFERRED_BITRATE_LOW_BUFFER
|
||||||
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
|
new_config['General']['preferred_bitrate_allow_lossless'] = int(PREFERRED_BITRATE_ALLOW_LOSSLESS)
|
||||||
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
|
new_config['General']['detect_bitrate'] = int(DETECT_BITRATE)
|
||||||
|
new_config['General']['lossless_bitrate_from'] = LOSSLESS_BITRATE_FROM
|
||||||
|
new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO
|
||||||
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
|
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
|
||||||
new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
|
new_config['General']['correct_metadata'] = int(CORRECT_METADATA)
|
||||||
|
new_config['General']['freeze_db'] = int(FREEZE_DB)
|
||||||
new_config['General']['move_files'] = int(MOVE_FILES)
|
new_config['General']['move_files'] = int(MOVE_FILES)
|
||||||
new_config['General']['rename_files'] = int(RENAME_FILES)
|
new_config['General']['rename_files'] = int(RENAME_FILES)
|
||||||
new_config['General']['folder_format'] = FOLDER_FORMAT
|
new_config['General']['folder_format'] = FOLDER_FORMAT
|
||||||
new_config['General']['file_format'] = FILE_FORMAT
|
new_config['General']['file_format'] = FILE_FORMAT
|
||||||
new_config['General']['file_underscores'] = int(FILE_UNDERSCORES)
|
new_config['General']['file_underscores'] = int(FILE_UNDERSCORES)
|
||||||
new_config['General']['cleanup_files'] = int(CLEANUP_FILES)
|
new_config['General']['cleanup_files'] = int(CLEANUP_FILES)
|
||||||
|
new_config['General']['keep_nfo'] = int(KEEP_NFO)
|
||||||
new_config['General']['add_album_art'] = int(ADD_ALBUM_ART)
|
new_config['General']['add_album_art'] = int(ADD_ALBUM_ART)
|
||||||
new_config['General']['album_art_format'] = ALBUM_ART_FORMAT
|
new_config['General']['album_art_format'] = ALBUM_ART_FORMAT
|
||||||
new_config['General']['embed_album_art'] = int(EMBED_ALBUM_ART)
|
new_config['General']['embed_album_art'] = int(EMBED_ALBUM_ART)
|
||||||
new_config['General']['embed_lyrics'] = int(EMBED_LYRICS)
|
new_config['General']['embed_lyrics'] = int(EMBED_LYRICS)
|
||||||
|
new_config['General']['replace_existing_folders'] = int(REPLACE_EXISTING_FOLDERS)
|
||||||
new_config['General']['nzb_downloader'] = NZB_DOWNLOADER
|
new_config['General']['nzb_downloader'] = NZB_DOWNLOADER
|
||||||
new_config['General']['torrent_downloader'] = TORRENT_DOWNLOADER
|
new_config['General']['torrent_downloader'] = TORRENT_DOWNLOADER
|
||||||
new_config['General']['download_dir'] = DOWNLOAD_DIR
|
new_config['General']['download_dir'] = DOWNLOAD_DIR
|
||||||
@@ -838,30 +907,44 @@ def config_write():
|
|||||||
new_config['General']['autowant_upcoming'] = int(AUTOWANT_UPCOMING)
|
new_config['General']['autowant_upcoming'] = int(AUTOWANT_UPCOMING)
|
||||||
new_config['General']['autowant_all'] = int(AUTOWANT_ALL)
|
new_config['General']['autowant_all'] = int(AUTOWANT_ALL)
|
||||||
new_config['General']['keep_torrent_files'] = int(KEEP_TORRENT_FILES)
|
new_config['General']['keep_torrent_files'] = int(KEEP_TORRENT_FILES)
|
||||||
|
new_config['General']['prefer_torrents'] = PREFER_TORRENTS
|
||||||
|
new_config['General']['open_magnet_links'] = OPEN_MAGNET_LINKS
|
||||||
|
|
||||||
new_config['General']['numberofseeders'] = NUMBEROFSEEDERS
|
new_config['General']['numberofseeders'] = NUMBEROFSEEDERS
|
||||||
new_config['General']['torrentblackhole_dir'] = TORRENTBLACKHOLE_DIR
|
new_config['General']['torrentblackhole_dir'] = TORRENTBLACKHOLE_DIR
|
||||||
new_config['General']['isohunt'] = int(ISOHUNT)
|
|
||||||
new_config['General']['kat'] = int(KAT)
|
|
||||||
new_config['General']['mininova'] = int(MININOVA)
|
|
||||||
new_config['General']['piratebay'] = int(PIRATEBAY)
|
|
||||||
new_config['General']['piratebay_proxy_url'] = PIRATEBAY_PROXY_URL
|
|
||||||
new_config['General']['download_torrent_dir'] = DOWNLOAD_TORRENT_DIR
|
new_config['General']['download_torrent_dir'] = DOWNLOAD_TORRENT_DIR
|
||||||
|
|
||||||
|
new_config['Kat'] = {}
|
||||||
|
new_config['Kat']['kat'] = int(KAT)
|
||||||
|
new_config['Kat']['kat_proxy_url'] = KAT_PROXY_URL
|
||||||
|
new_config['Kat']['kat_ratio'] = KAT_RATIO
|
||||||
|
|
||||||
|
new_config['Mininova'] = {}
|
||||||
|
new_config['Mininova']['mininova'] = int(MININOVA)
|
||||||
|
new_config['Mininova']['mininova_ratio'] = MININOVA_RATIO
|
||||||
|
|
||||||
|
new_config['Piratebay'] = {}
|
||||||
|
new_config['Piratebay']['piratebay'] = int(PIRATEBAY)
|
||||||
|
new_config['Piratebay']['piratebay_proxy_url'] = PIRATEBAY_PROXY_URL
|
||||||
|
new_config['Piratebay']['piratebay_ratio'] = PIRATEBAY_RATIO
|
||||||
|
|
||||||
new_config['Waffles'] = {}
|
new_config['Waffles'] = {}
|
||||||
new_config['Waffles']['waffles'] = int(WAFFLES)
|
new_config['Waffles']['waffles'] = int(WAFFLES)
|
||||||
new_config['Waffles']['waffles_uid'] = WAFFLES_UID
|
new_config['Waffles']['waffles_uid'] = WAFFLES_UID
|
||||||
new_config['Waffles']['waffles_passkey'] = WAFFLES_PASSKEY
|
new_config['Waffles']['waffles_passkey'] = WAFFLES_PASSKEY
|
||||||
|
new_config['Waffles']['waffles_ratio'] = WAFFLES_RATIO
|
||||||
|
|
||||||
new_config['Rutracker'] = {}
|
new_config['Rutracker'] = {}
|
||||||
new_config['Rutracker']['rutracker'] = int(RUTRACKER)
|
new_config['Rutracker']['rutracker'] = int(RUTRACKER)
|
||||||
new_config['Rutracker']['rutracker_user'] = RUTRACKER_USER
|
new_config['Rutracker']['rutracker_user'] = RUTRACKER_USER
|
||||||
new_config['Rutracker']['rutracker_password'] = RUTRACKER_PASSWORD
|
new_config['Rutracker']['rutracker_password'] = RUTRACKER_PASSWORD
|
||||||
|
new_config['Rutracker']['rutracker_ratio'] = RUTRACKER_RATIO
|
||||||
|
|
||||||
new_config['What.cd'] = {}
|
new_config['What.cd'] = {}
|
||||||
new_config['What.cd']['whatcd'] = int(WHATCD)
|
new_config['What.cd']['whatcd'] = int(WHATCD)
|
||||||
new_config['What.cd']['whatcd_username'] = WHATCD_USERNAME
|
new_config['What.cd']['whatcd_username'] = WHATCD_USERNAME
|
||||||
new_config['What.cd']['whatcd_password'] = WHATCD_PASSWORD
|
new_config['What.cd']['whatcd_password'] = WHATCD_PASSWORD
|
||||||
|
new_config['What.cd']['whatcd_ratio'] = WHATCD_RATIO
|
||||||
|
|
||||||
new_config['General']['search_interval'] = SEARCH_INTERVAL
|
new_config['General']['search_interval'] = SEARCH_INTERVAL
|
||||||
new_config['General']['libraryscan'] = int(LIBRARYSCAN)
|
new_config['General']['libraryscan'] = int(LIBRARYSCAN)
|
||||||
@@ -869,6 +952,7 @@ def config_write():
|
|||||||
new_config['General']['download_scan_interval'] = DOWNLOAD_SCAN_INTERVAL
|
new_config['General']['download_scan_interval'] = DOWNLOAD_SCAN_INTERVAL
|
||||||
new_config['General']['update_db_interval'] = UPDATE_DB_INTERVAL
|
new_config['General']['update_db_interval'] = UPDATE_DB_INTERVAL
|
||||||
new_config['General']['mb_ignore_age'] = MB_IGNORE_AGE
|
new_config['General']['mb_ignore_age'] = MB_IGNORE_AGE
|
||||||
|
new_config['General']['torrent_removal_interval'] = TORRENT_REMOVAL_INTERVAL
|
||||||
|
|
||||||
new_config['SABnzbd'] = {}
|
new_config['SABnzbd'] = {}
|
||||||
new_config['SABnzbd']['sab_host'] = SAB_HOST
|
new_config['SABnzbd']['sab_host'] = SAB_HOST
|
||||||
@@ -882,6 +966,7 @@ def config_write():
|
|||||||
new_config['NZBget']['nzbget_password'] = NZBGET_PASSWORD
|
new_config['NZBget']['nzbget_password'] = NZBGET_PASSWORD
|
||||||
new_config['NZBget']['nzbget_category'] = NZBGET_CATEGORY
|
new_config['NZBget']['nzbget_category'] = NZBGET_CATEGORY
|
||||||
new_config['NZBget']['nzbget_host'] = NZBGET_HOST
|
new_config['NZBget']['nzbget_host'] = NZBGET_HOST
|
||||||
|
new_config['NZBget']['nzbget_priority'] = NZBGET_PRIORITY
|
||||||
|
|
||||||
new_config['Headphones'] = {}
|
new_config['Headphones'] = {}
|
||||||
new_config['Headphones']['headphones_indexer'] = int(HEADPHONES_INDEXER)
|
new_config['Headphones']['headphones_indexer'] = int(HEADPHONES_INDEXER)
|
||||||
@@ -895,6 +980,7 @@ def config_write():
|
|||||||
new_config['uTorrent']['utorrent_host'] = UTORRENT_HOST
|
new_config['uTorrent']['utorrent_host'] = UTORRENT_HOST
|
||||||
new_config['uTorrent']['utorrent_username'] = UTORRENT_USERNAME
|
new_config['uTorrent']['utorrent_username'] = UTORRENT_USERNAME
|
||||||
new_config['uTorrent']['utorrent_password'] = UTORRENT_PASSWORD
|
new_config['uTorrent']['utorrent_password'] = UTORRENT_PASSWORD
|
||||||
|
new_config['uTorrent']['utorrent_label'] = UTORRENT_LABEL
|
||||||
|
|
||||||
new_config['Newznab'] = {}
|
new_config['Newznab'] = {}
|
||||||
new_config['Newznab']['newznab'] = int(NEWZNAB)
|
new_config['Newznab']['newznab'] = int(NEWZNAB)
|
||||||
@@ -914,11 +1000,6 @@ def config_write():
|
|||||||
new_config['NZBsorg']['nzbsorg_uid'] = NZBSORG_UID
|
new_config['NZBsorg']['nzbsorg_uid'] = NZBSORG_UID
|
||||||
new_config['NZBsorg']['nzbsorg_hash'] = NZBSORG_HASH
|
new_config['NZBsorg']['nzbsorg_hash'] = NZBSORG_HASH
|
||||||
|
|
||||||
new_config['NZBsRus'] = {}
|
|
||||||
new_config['NZBsRus']['nzbsrus'] = int(NZBSRUS)
|
|
||||||
new_config['NZBsRus']['nzbsrus_uid'] = NZBSRUS_UID
|
|
||||||
new_config['NZBsRus']['nzbsrus_apikey'] = NZBSRUS_APIKEY
|
|
||||||
|
|
||||||
new_config['omgwtfnzbs'] = {}
|
new_config['omgwtfnzbs'] = {}
|
||||||
new_config['omgwtfnzbs']['omgwtfnzbs'] = int(OMGWTFNZBS)
|
new_config['omgwtfnzbs']['omgwtfnzbs'] = int(OMGWTFNZBS)
|
||||||
new_config['omgwtfnzbs']['omgwtfnzbs_uid'] = OMGWTFNZBS_UID
|
new_config['omgwtfnzbs']['omgwtfnzbs_uid'] = OMGWTFNZBS_UID
|
||||||
@@ -928,6 +1009,12 @@ def config_write():
|
|||||||
new_config['General']['ignored_words'] = IGNORED_WORDS
|
new_config['General']['ignored_words'] = IGNORED_WORDS
|
||||||
new_config['General']['required_words'] = REQUIRED_WORDS
|
new_config['General']['required_words'] = REQUIRED_WORDS
|
||||||
|
|
||||||
|
new_config['Growl'] = {}
|
||||||
|
new_config['Growl']['growl_enabled'] = int(GROWL_ENABLED)
|
||||||
|
new_config['Growl']['growl_host'] = GROWL_HOST
|
||||||
|
new_config['Growl']['growl_password'] = GROWL_PASSWORD
|
||||||
|
new_config['Growl']['growl_onsnatch'] = int(GROWL_ONSNATCH)
|
||||||
|
|
||||||
new_config['Prowl'] = {}
|
new_config['Prowl'] = {}
|
||||||
new_config['Prowl']['prowl_enabled'] = int(PROWL_ENABLED)
|
new_config['Prowl']['prowl_enabled'] = int(PROWL_ENABLED)
|
||||||
new_config['Prowl']['prowl_keys'] = PROWL_KEYS
|
new_config['Prowl']['prowl_keys'] = PROWL_KEYS
|
||||||
@@ -942,6 +1029,10 @@ def config_write():
|
|||||||
new_config['XBMC']['xbmc_update'] = int(XBMC_UPDATE)
|
new_config['XBMC']['xbmc_update'] = int(XBMC_UPDATE)
|
||||||
new_config['XBMC']['xbmc_notify'] = int(XBMC_NOTIFY)
|
new_config['XBMC']['xbmc_notify'] = int(XBMC_NOTIFY)
|
||||||
|
|
||||||
|
new_config['LMS'] = {}
|
||||||
|
new_config['LMS']['lms_enabled'] = int(LMS_ENABLED)
|
||||||
|
new_config['LMS']['lms_host'] = LMS_HOST
|
||||||
|
|
||||||
new_config['Plex'] = {}
|
new_config['Plex'] = {}
|
||||||
new_config['Plex']['plex_enabled'] = int(PLEX_ENABLED)
|
new_config['Plex']['plex_enabled'] = int(PLEX_ENABLED)
|
||||||
new_config['Plex']['plex_server_host'] = PLEX_SERVER_HOST
|
new_config['Plex']['plex_server_host'] = PLEX_SERVER_HOST
|
||||||
@@ -954,7 +1045,7 @@ def config_write():
|
|||||||
new_config['NMA'] = {}
|
new_config['NMA'] = {}
|
||||||
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
|
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
|
||||||
new_config['NMA']['nma_apikey'] = NMA_APIKEY
|
new_config['NMA']['nma_apikey'] = NMA_APIKEY
|
||||||
new_config['NMA']['nma_priority'] = NMA_PRIORITY
|
new_config['NMA']['nma_priority'] = int(NMA_PRIORITY)
|
||||||
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
|
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
|
||||||
|
|
||||||
new_config['Pushalot'] = {}
|
new_config['Pushalot'] = {}
|
||||||
@@ -967,6 +1058,7 @@ def config_write():
|
|||||||
new_config['Pushover']['pushover_keys'] = PUSHOVER_KEYS
|
new_config['Pushover']['pushover_keys'] = PUSHOVER_KEYS
|
||||||
new_config['Pushover']['pushover_onsnatch'] = int(PUSHOVER_ONSNATCH)
|
new_config['Pushover']['pushover_onsnatch'] = int(PUSHOVER_ONSNATCH)
|
||||||
new_config['Pushover']['pushover_priority'] = int(PUSHOVER_PRIORITY)
|
new_config['Pushover']['pushover_priority'] = int(PUSHOVER_PRIORITY)
|
||||||
|
new_config['Pushover']['pushover_apitoken'] = PUSHOVER_APITOKEN
|
||||||
|
|
||||||
new_config['PushBullet'] = {}
|
new_config['PushBullet'] = {}
|
||||||
new_config['PushBullet']['pushbullet_enabled'] = int(PUSHBULLET_ENABLED)
|
new_config['PushBullet']['pushbullet_enabled'] = int(PUSHBULLET_ENABLED)
|
||||||
@@ -981,6 +1073,22 @@ def config_write():
|
|||||||
new_config['Twitter']['twitter_password'] = TWITTER_PASSWORD
|
new_config['Twitter']['twitter_password'] = TWITTER_PASSWORD
|
||||||
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
|
new_config['Twitter']['twitter_prefix'] = TWITTER_PREFIX
|
||||||
|
|
||||||
|
new_config['OSX_Notify'] = {}
|
||||||
|
new_config['OSX_Notify']['osx_notify_enabled'] = int(OSX_NOTIFY_ENABLED)
|
||||||
|
new_config['OSX_Notify']['osx_notify_onsnatch'] = int(OSX_NOTIFY_ONSNATCH)
|
||||||
|
new_config['OSX_Notify']['osx_notify_app'] = OSX_NOTIFY_APP
|
||||||
|
|
||||||
|
new_config['Boxcar'] = {}
|
||||||
|
new_config['Boxcar']['boxcar_enabled'] = int(BOXCAR_ENABLED)
|
||||||
|
new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
|
||||||
|
new_config['Boxcar']['boxcar_token'] = BOXCAR_TOKEN
|
||||||
|
|
||||||
|
new_config['Subsonic'] = {}
|
||||||
|
new_config['Subsonic']['subsonic_enabled'] = int(SUBSONIC_ENABLED)
|
||||||
|
new_config['Subsonic']['subsonic_host'] = SUBSONIC_HOST
|
||||||
|
new_config['Subsonic']['subsonic_username'] = SUBSONIC_USERNAME
|
||||||
|
new_config['Subsonic']['subsonic_password'] = SUBSONIC_PASSWORD
|
||||||
|
|
||||||
new_config['Songkick'] = {}
|
new_config['Songkick'] = {}
|
||||||
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
||||||
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
||||||
@@ -1006,6 +1114,8 @@ def config_write():
|
|||||||
new_config['General']['encoderquality'] = ENCODERQUALITY
|
new_config['General']['encoderquality'] = ENCODERQUALITY
|
||||||
new_config['General']['encodervbrcbr'] = ENCODERVBRCBR
|
new_config['General']['encodervbrcbr'] = ENCODERVBRCBR
|
||||||
new_config['General']['encoderlossless'] = int(ENCODERLOSSLESS)
|
new_config['General']['encoderlossless'] = int(ENCODERLOSSLESS)
|
||||||
|
new_config['General']['encoder_multicore'] = int(ENCODER_MULTICORE)
|
||||||
|
new_config['General']['encoder_multicore_count'] = int(ENCODER_MULTICORE_COUNT)
|
||||||
new_config['General']['delete_lossless_files'] = int(DELETE_LOSSLESS_FILES)
|
new_config['General']['delete_lossless_files'] = int(DELETE_LOSSLESS_FILES)
|
||||||
|
|
||||||
new_config['General']['mirror'] = MIRROR
|
new_config['General']['mirror'] = MIRROR
|
||||||
@@ -1019,9 +1129,15 @@ def config_write():
|
|||||||
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
|
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
|
||||||
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
|
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
|
||||||
new_config['Advanced']['journal_mode'] = JOURNAL_MODE
|
new_config['Advanced']['journal_mode'] = JOURNAL_MODE
|
||||||
|
new_config['Advanced']['verify_ssl_cert'] = int(VERIFY_SSL_CERT)
|
||||||
|
|
||||||
new_config.write()
|
# Write it to file
|
||||||
|
logger.info("Writing configuration to file")
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_config.write()
|
||||||
|
except IOError as e:
|
||||||
|
logger.error("Error writing configuration file: %s", e)
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
|
|
||||||
@@ -1030,7 +1146,7 @@ def start():
|
|||||||
if __INITIALIZED__:
|
if __INITIALIZED__:
|
||||||
|
|
||||||
# Start our scheduled background tasks
|
# Start our scheduled background tasks
|
||||||
from headphones import updater, searcher, librarysync, postprocessor
|
from headphones import updater, searcher, librarysync, postprocessor, torrentfinished
|
||||||
|
|
||||||
SCHED.add_interval_job(updater.dbUpdate, hours=UPDATE_DB_INTERVAL)
|
SCHED.add_interval_job(updater.dbUpdate, hours=UPDATE_DB_INTERVAL)
|
||||||
SCHED.add_interval_job(searcher.searchforalbum, minutes=SEARCH_INTERVAL)
|
SCHED.add_interval_job(searcher.searchforalbum, minutes=SEARCH_INTERVAL)
|
||||||
@@ -1039,16 +1155,20 @@ def start():
|
|||||||
if CHECK_GITHUB:
|
if CHECK_GITHUB:
|
||||||
SCHED.add_interval_job(versioncheck.checkGithub, minutes=CHECK_GITHUB_INTERVAL)
|
SCHED.add_interval_job(versioncheck.checkGithub, minutes=CHECK_GITHUB_INTERVAL)
|
||||||
|
|
||||||
if DOWNLOAD_SCAN_INTERVAL > 0:
|
if DOWNLOAD_SCAN_INTERVAL > 0:
|
||||||
SCHED.add_interval_job(postprocessor.checkFolder, minutes=DOWNLOAD_SCAN_INTERVAL)
|
SCHED.add_interval_job(postprocessor.checkFolder, minutes=DOWNLOAD_SCAN_INTERVAL)
|
||||||
|
|
||||||
|
# Remove Torrent + data if Post Processed and finished Seeding
|
||||||
|
if TORRENT_REMOVAL_INTERVAL > 0:
|
||||||
|
SCHED.add_interval_job(torrentfinished.checkTorrentFinished, minutes=TORRENT_REMOVAL_INTERVAL)
|
||||||
|
|
||||||
SCHED.start()
|
SCHED.start()
|
||||||
|
|
||||||
started = True
|
started = True
|
||||||
|
|
||||||
def sig_handler(signum=None, frame=None):
|
def sig_handler(signum=None, frame=None):
|
||||||
if type(signum) != type(None):
|
if signum is not None:
|
||||||
logger.info("Signal %i caught, saving and exiting..." % int(signum))
|
logger.info("Signal %i caught, saving and exiting...", signum)
|
||||||
shutdown()
|
shutdown()
|
||||||
|
|
||||||
def dbcheck():
|
def dbcheck():
|
||||||
@@ -1267,10 +1387,10 @@ def shutdown(restart=False, update=False):
|
|||||||
try:
|
try:
|
||||||
versioncheck.update()
|
versioncheck.update()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn('Headphones failed to update: %s. Restarting.' % e)
|
logger.warn('Headphones failed to update: %s. Restarting.', e)
|
||||||
|
|
||||||
if CREATEPID :
|
if CREATEPID :
|
||||||
logger.info ('Removing pidfile %s' % PIDFILE)
|
logger.info ('Removing pidfile %s', PIDFILE)
|
||||||
os.remove(PIDFILE)
|
os.remove(PIDFILE)
|
||||||
|
|
||||||
if restart:
|
if restart:
|
||||||
@@ -1279,7 +1399,7 @@ def shutdown(restart=False, update=False):
|
|||||||
popen_list += ARGS
|
popen_list += ARGS
|
||||||
if '--nolaunch' not in popen_list:
|
if '--nolaunch' not in popen_list:
|
||||||
popen_list += ['--nolaunch']
|
popen_list += ['--nolaunch']
|
||||||
logger.info('Restarting Headphones with ' + str(popen_list))
|
logger.info('Restarting Headphones with %s', popen_list)
|
||||||
subprocess.Popen(popen_list, cwd=os.getcwd())
|
subprocess.Popen(popen_list, cwd=os.getcwd())
|
||||||
|
|
||||||
os._exit(0)
|
os._exit(0)
|
||||||
|
|||||||
@@ -13,39 +13,30 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import urllib2
|
from headphones import request, db
|
||||||
from headphones import db
|
|
||||||
|
|
||||||
def getAlbumArt(albumid):
|
def getAlbumArt(albumid):
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
asin = myDB.action('SELECT AlbumASIN from albums WHERE AlbumID=?', [albumid]).fetchone()[0]
|
asin = myDB.action('SELECT AlbumASIN from albums WHERE AlbumID=?', [albumid]).fetchone()[0]
|
||||||
|
|
||||||
if not asin:
|
if asin:
|
||||||
return None
|
return 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % asin
|
||||||
|
|
||||||
url = 'http://ec1.images-amazon.com/images/P/%s.01.LZZZZZZZ.jpg' % asin
|
|
||||||
|
|
||||||
return url
|
|
||||||
|
|
||||||
def getCachedArt(albumid):
|
def getCachedArt(albumid):
|
||||||
|
|
||||||
from headphones import cache
|
from headphones import cache
|
||||||
|
|
||||||
c = cache.Cache()
|
c = cache.Cache()
|
||||||
|
|
||||||
artwork_path = c.get_artwork_from_cache(AlbumID=albumid)
|
artwork_path = c.get_artwork_from_cache(AlbumID=albumid)
|
||||||
|
|
||||||
if not artwork_path:
|
if not artwork_path:
|
||||||
return None
|
return
|
||||||
|
|
||||||
if artwork_path.startswith('http://'):
|
if artwork_path.startswith('http://'):
|
||||||
try:
|
artwork = request.request_content(artwork_path, timeout=20)
|
||||||
artwork = urllib2.urlopen(artwork_path, timeout=20).read()
|
|
||||||
return artwork
|
if not artwork:
|
||||||
except:
|
logger.warn("Unable to open url: %s", artwork_path)
|
||||||
logger.warn("Unable to open url: " + artwork_path)
|
return
|
||||||
return None
|
|
||||||
else:
|
else:
|
||||||
artwork = open(artwork_path, "r").read()
|
with open(artwork_path, "r") as fp:
|
||||||
return artwork
|
return fp.read()
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ def switch(AlbumID, ReleaseID):
|
|||||||
myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', AlbumID])
|
myDB.action('UPDATE albums SET Status=? WHERE AlbumID=?', ['Downloaded', AlbumID])
|
||||||
|
|
||||||
# Update have track counts on index
|
# Update have track counts on index
|
||||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [newalbumdata['ArtistID']]))
|
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']]))
|
||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [newalbumdata['ArtistID']]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']]))
|
||||||
|
|
||||||
controlValueDict = {"ArtistID": newalbumdata['ArtistID']}
|
controlValueDict = {"ArtistID": newalbumdata['ArtistID']}
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,19 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import headphones
|
|
||||||
|
|
||||||
from headphones import db, mb, importer, searcher, cache, postprocessor, versioncheck, logger
|
from headphones import db, mb, importer, searcher, cache, postprocessor, versioncheck, logger
|
||||||
|
|
||||||
import lib.simplejson as simplejson
|
|
||||||
from xml.dom.minidom import Document
|
from xml.dom.minidom import Document
|
||||||
|
|
||||||
|
import headphones
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
||||||
'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist',
|
'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist',
|
||||||
'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'getVersion', 'checkGithub',
|
'addAlbum', 'queueAlbum', 'unqueueAlbum', 'forceSearch', 'forceProcess', 'getVersion', 'checkGithub',
|
||||||
'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo', 'getArtistThumb', 'getAlbumThumb']
|
'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo',
|
||||||
|
'getArtistThumb', 'getAlbumThumb', 'choose_specific_download', 'download_specific_release']
|
||||||
|
|
||||||
class Api(object):
|
class Api(object):
|
||||||
|
|
||||||
@@ -79,17 +80,17 @@ class Api(object):
|
|||||||
def fetchData(self):
|
def fetchData(self):
|
||||||
|
|
||||||
if self.data == 'OK':
|
if self.data == 'OK':
|
||||||
logger.info('Recieved API command: ' + self.cmd)
|
logger.info('Recieved API command: %s', self.cmd)
|
||||||
methodToCall = getattr(self, "_" + self.cmd)
|
methodToCall = getattr(self, "_" + self.cmd)
|
||||||
result = methodToCall(**self.kwargs)
|
result = methodToCall(**self.kwargs)
|
||||||
if 'callback' not in self.kwargs:
|
if 'callback' not in self.kwargs:
|
||||||
if type(self.data) == type(''):
|
if type(self.data) == type(''):
|
||||||
return self.data
|
return self.data
|
||||||
else:
|
else:
|
||||||
return simplejson.dumps(self.data)
|
return json.dumps(self.data)
|
||||||
else:
|
else:
|
||||||
self.callback = self.kwargs['callback']
|
self.callback = self.kwargs['callback']
|
||||||
self.data = simplejson.dumps(self.data)
|
self.data = json.dumps(self.data)
|
||||||
self.data = self.callback + '(' + self.data + ');'
|
self.data = self.callback + '(' + self.data + ');'
|
||||||
return self.data
|
return self.data
|
||||||
else:
|
else:
|
||||||
@@ -144,7 +145,7 @@ class Api(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _getHistory(self, **kwargs):
|
def _getHistory(self, **kwargs):
|
||||||
self.data = self._dic_from_query('SELECT * from snatched order by DateAdded DESC')
|
self.data = self._dic_from_query('SELECT * from snatched WHERE status NOT LIKE "Seed%" order by DateAdded DESC')
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getUpcoming(self, **kwargs):
|
def _getUpcoming(self, **kwargs):
|
||||||
@@ -306,7 +307,10 @@ class Api(object):
|
|||||||
searcher.searchforalbum()
|
searcher.searchforalbum()
|
||||||
|
|
||||||
def _forceProcess(self, **kwargs):
|
def _forceProcess(self, **kwargs):
|
||||||
postprocessor.forcePostProcess()
|
self.dir = None
|
||||||
|
if 'dir' in kwargs:
|
||||||
|
self.dir = kwargs['dir']
|
||||||
|
postprocessor.forcePostProcess(self.dir)
|
||||||
|
|
||||||
def _getVersion(self, **kwargs):
|
def _getVersion(self, **kwargs):
|
||||||
self.data = {
|
self.data = {
|
||||||
@@ -389,3 +393,65 @@ class Api(object):
|
|||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
self.data = cache.getThumb(AlbumID=self.id)
|
self.data = cache.getThumb(AlbumID=self.id)
|
||||||
|
|
||||||
|
def _choose_specific_download(self, **kwargs):
|
||||||
|
|
||||||
|
if 'id' not in kwargs:
|
||||||
|
self.data = 'Missing parameter: id'
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.id = kwargs['id']
|
||||||
|
|
||||||
|
results = searcher.searchforalbum(self.id, choose_specific_download=True)
|
||||||
|
|
||||||
|
results_as_dicts = []
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
|
||||||
|
result_dict = {
|
||||||
|
'title':result[0],
|
||||||
|
'size':result[1],
|
||||||
|
'url':result[2],
|
||||||
|
'provider':result[3],
|
||||||
|
'kind':result[4]
|
||||||
|
}
|
||||||
|
results_as_dicts.append(result_dict)
|
||||||
|
|
||||||
|
self.data = results_as_dicts
|
||||||
|
|
||||||
|
def _download_specific_release(self, **kwargs):
|
||||||
|
|
||||||
|
expected_kwargs =['id', 'title','size','url','provider','kind']
|
||||||
|
|
||||||
|
for kwarg in expected_kwargs:
|
||||||
|
if kwarg not in kwargs:
|
||||||
|
self.data = 'Missing parameter: ' + kwarg
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
title = kwargs['title']
|
||||||
|
size = kwargs['size']
|
||||||
|
url = kwargs['url']
|
||||||
|
provider = kwargs['provider']
|
||||||
|
kind = kwargs['kind']
|
||||||
|
id = kwargs['id']
|
||||||
|
|
||||||
|
for kwarg in expected_kwargs:
|
||||||
|
del kwargs[kwarg]
|
||||||
|
|
||||||
|
# Handle situations where the torrent url contains arguments that are parsed
|
||||||
|
if kwargs:
|
||||||
|
import urllib, urllib2
|
||||||
|
url = urllib2.quote(url, safe=":?/=&") + '&' + urllib.urlencode(kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = [(title,int(size),url,provider,kind)]
|
||||||
|
except ValueError:
|
||||||
|
result = [(title,float(size),url,provider,kind)]
|
||||||
|
|
||||||
|
logger.info(u"Making sure we can download the chosen result")
|
||||||
|
(data, bestqual) = searcher.preprocess(result)
|
||||||
|
|
||||||
|
if data and bestqual:
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [id]).fetchone()
|
||||||
|
searcher.send_to_downloader(data, bestqual, album)
|
||||||
|
|||||||
@@ -14,14 +14,13 @@
|
|||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import glob, urllib, urllib2
|
import glob
|
||||||
|
import urllib
|
||||||
import lib.simplejson as simplejson
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
from headphones import db, helpers, logger
|
|
||||||
|
|
||||||
lastfm_apikey = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
from headphones import db, helpers, logger, lastfm, request
|
||||||
|
|
||||||
|
LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
"""
|
"""
|
||||||
@@ -43,25 +42,22 @@ class Cache(object):
|
|||||||
|
|
||||||
path_to_art_cache = os.path.join(headphones.CACHE_DIR, 'artwork')
|
path_to_art_cache = os.path.join(headphones.CACHE_DIR, 'artwork')
|
||||||
|
|
||||||
id = None
|
|
||||||
id_type = None # 'artist' or 'album' - set automatically depending on whether ArtistID or AlbumID is passed
|
|
||||||
query_type = None # 'artwork','thumb' or 'info' - set automatically
|
|
||||||
|
|
||||||
artwork_files = []
|
|
||||||
thumb_files = []
|
|
||||||
|
|
||||||
artwork_errors = False
|
|
||||||
artwork_url = None
|
|
||||||
|
|
||||||
thumb_errors = False
|
|
||||||
thumb_url = None
|
|
||||||
|
|
||||||
info_summary = None
|
|
||||||
info_content = None
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.id = None
|
||||||
|
self.id_type = None # 'artist' or 'album' - set automatically depending on whether ArtistID or AlbumID is passed
|
||||||
|
self.query_type = None # 'artwork','thumb' or 'info' - set automatically
|
||||||
|
|
||||||
pass
|
self.artwork_files = []
|
||||||
|
self.thumb_files = []
|
||||||
|
|
||||||
|
self.artwork_errors = False
|
||||||
|
self.artwork_url = None
|
||||||
|
|
||||||
|
self.thumb_errors = False
|
||||||
|
self.thumb_url = None
|
||||||
|
|
||||||
|
self.info_summary = None
|
||||||
|
self.info_content = None
|
||||||
|
|
||||||
def _findfilesstartingwith(self,pattern,folder):
|
def _findfilesstartingwith(self,pattern,folder):
|
||||||
files = []
|
files = []
|
||||||
@@ -127,9 +123,9 @@ class Cache(object):
|
|||||||
return thumb_url
|
return thumb_url
|
||||||
|
|
||||||
def get_artwork_from_cache(self, ArtistID=None, AlbumID=None):
|
def get_artwork_from_cache(self, ArtistID=None, AlbumID=None):
|
||||||
'''
|
"""
|
||||||
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
||||||
'''
|
"""
|
||||||
|
|
||||||
self.query_type = 'artwork'
|
self.query_type = 'artwork'
|
||||||
|
|
||||||
@@ -153,9 +149,9 @@ class Cache(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def get_thumb_from_cache(self, ArtistID=None, AlbumID=None):
|
def get_thumb_from_cache(self, ArtistID=None, AlbumID=None):
|
||||||
'''
|
"""
|
||||||
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
||||||
'''
|
"""
|
||||||
|
|
||||||
self.query_type = 'thumb'
|
self.query_type = 'thumb'
|
||||||
|
|
||||||
@@ -210,80 +206,41 @@ class Cache(object):
|
|||||||
if ArtistID:
|
if ArtistID:
|
||||||
|
|
||||||
self.id_type = 'artist'
|
self.id_type = 'artist'
|
||||||
|
data = lastfm.request_lastfm("artist.getinfo", mbid=ArtistID, api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
params = { "method": "artist.getInfo",
|
if not data:
|
||||||
"api_key": lastfm_apikey,
|
|
||||||
"mbid": ArtistID,
|
|
||||||
"format": "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
url = "http://ws.audioscrobbler.com/2.0/?" + urllib.urlencode(params)
|
|
||||||
logger.debug('Retrieving artist information from: ' + url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = urllib2.urlopen(url, timeout=20).read()
|
|
||||||
except:
|
|
||||||
logger.warn('Could not open url: ' + url)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if result:
|
try:
|
||||||
|
image_url = data['artist']['image'][-1]['#text']
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
logger.debug('No artist image found')
|
||||||
|
image_url = None
|
||||||
|
|
||||||
try:
|
thumb_url = self._get_thumb_url(data)
|
||||||
data = simplejson.JSONDecoder().decode(result)
|
if not thumb_url:
|
||||||
except:
|
logger.debug('No artist thumbnail image found')
|
||||||
logger.warn('Could not parse data from url: ' + url)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
image_url = data['artist']['image'][-1]['#text']
|
|
||||||
except Exception:
|
|
||||||
logger.debug('No artist image found on url: ' + url)
|
|
||||||
image_url = None
|
|
||||||
|
|
||||||
thumb_url = self._get_thumb_url(data)
|
|
||||||
if not thumb_url:
|
|
||||||
logger.debug('No artist thumbnail image found on url: ' + url)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
self.id_type = 'album'
|
self.id_type = 'album'
|
||||||
|
data = lastfm.request_lastfm("album.getinfo", mbid=AlbumID, api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
params = { "method": "album.getInfo",
|
if not data:
|
||||||
"api_key": lastfm_apikey,
|
|
||||||
"mbid": AlbumID,
|
|
||||||
"format": "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
url = "http://ws.audioscrobbler.com/2.0/?" + urllib.urlencode(params)
|
|
||||||
logger.debug('Retrieving album information from: ' + url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = urllib2.urlopen(url, timeout=20).read()
|
|
||||||
except:
|
|
||||||
logger.warn('Could not open url: ' + url)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if result:
|
try:
|
||||||
|
image_url = data['album']['image'][-1]['#text']
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
logger.debug('No album image found on last.fm')
|
||||||
|
image_url = None
|
||||||
|
|
||||||
try:
|
thumb_url = self._get_thumb_url(data)
|
||||||
data = simplejson.JSONDecoder().decode(result)
|
|
||||||
except:
|
|
||||||
logger.warn('Could not parse data from url: ' + url)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
if not thumb_url:
|
||||||
image_url = data['artist']['image'][-1]['#text']
|
logger.debug('No album thumbnail image found on last.fm')
|
||||||
except Exception:
|
|
||||||
logger.debug('No artist image found on url: ' + url)
|
|
||||||
image_url = None
|
|
||||||
|
|
||||||
thumb_url = self._get_thumb_url(data)
|
return {'artwork' : image_url, 'thumbnail' : thumb_url }
|
||||||
|
|
||||||
if not thumb_url:
|
|
||||||
logger.debug('No artist thumbnail image found on url: ' + url)
|
|
||||||
|
|
||||||
image_dict = {'artwork' : image_url, 'thumbnail' : thumb_url }
|
|
||||||
return image_dict
|
|
||||||
|
|
||||||
def _update_cache(self):
|
def _update_cache(self):
|
||||||
'''
|
'''
|
||||||
@@ -294,94 +251,59 @@ class Cache(object):
|
|||||||
# Since lastfm uses release ids rather than release group ids for albums, we have to do a artist + album search for albums
|
# Since lastfm uses release ids rather than release group ids for albums, we have to do a artist + album search for albums
|
||||||
if self.id_type == 'artist':
|
if self.id_type == 'artist':
|
||||||
|
|
||||||
params = { "method": "artist.getInfo",
|
data = lastfm.request_lastfm("artist.getinfo", mbid=self.id, api_key=LASTFM_API_KEY)
|
||||||
"api_key": lastfm_apikey,
|
|
||||||
"mbid": self.id,
|
|
||||||
"format": "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
url = "http://ws.audioscrobbler.com/2.0/?" + urllib.urlencode(params)
|
if not data:
|
||||||
logger.debug('Retrieving artist information from: ' + url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = urllib2.urlopen(url, timeout=20).read()
|
|
||||||
except:
|
|
||||||
logger.warn('Could not open url: ' + url)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if result:
|
try:
|
||||||
|
self.info_summary = data['artist']['bio']['summary']
|
||||||
|
except KeyError:
|
||||||
|
logger.debug('No artist bio summary found')
|
||||||
|
self.info_summary = None
|
||||||
|
try:
|
||||||
|
self.info_content = data['artist']['bio']['content']
|
||||||
|
except KeyError:
|
||||||
|
logger.debug('No artist bio found')
|
||||||
|
self.info_content = None
|
||||||
|
try:
|
||||||
|
image_url = data['artist']['image'][-1]['#text']
|
||||||
|
except KeyError:
|
||||||
|
logger.debug('No artist image found')
|
||||||
|
image_url = None
|
||||||
|
|
||||||
try:
|
thumb_url = self._get_thumb_url(data)
|
||||||
data = simplejson.JSONDecoder().decode(result)
|
if not thumb_url:
|
||||||
except:
|
logger.debug('No artist thumbnail image found')
|
||||||
logger.warn('Could not parse data from url: ' + url)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.info_summary = data['artist']['bio']['summary']
|
|
||||||
except Exception:
|
|
||||||
logger.debug('No artist bio summary found on url: ' + url)
|
|
||||||
self.info_summary = None
|
|
||||||
try:
|
|
||||||
self.info_content = data['artist']['bio']['content']
|
|
||||||
except Exception:
|
|
||||||
logger.debug('No artist bio found on url: ' + url)
|
|
||||||
self.info_content = None
|
|
||||||
try:
|
|
||||||
image_url = data['artist']['image'][-1]['#text']
|
|
||||||
except Exception:
|
|
||||||
logger.debug('No artist image found on url: ' + url)
|
|
||||||
image_url = None
|
|
||||||
|
|
||||||
thumb_url = self._get_thumb_url(data)
|
|
||||||
if not thumb_url:
|
|
||||||
logger.debug('No artist thumbnail image found on url: ' + url)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
dbartist = myDB.action('SELECT ArtistName, AlbumTitle FROM albums WHERE AlbumID=?', [self.id]).fetchone()
|
dbartist = myDB.action('SELECT ArtistName, AlbumTitle FROM albums WHERE AlbumID=?', [self.id]).fetchone()
|
||||||
|
data = lastfm.request_lastfm("album.getinfo", artist=dbartist['ArtistName'], album=dbartist['AlbumTitle'], api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
params = { "method": "album.getInfo",
|
if not data:
|
||||||
"api_key": lastfm_apikey,
|
|
||||||
"artist": dbartist['ArtistName'].encode('utf-8'),
|
|
||||||
"album": dbartist['AlbumTitle'].encode('utf-8'),
|
|
||||||
"format": "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
url = "http://ws.audioscrobbler.com/2.0/?" + urllib.urlencode(params)
|
|
||||||
|
|
||||||
logger.debug('Retrieving artist information from: ' + url)
|
|
||||||
try:
|
|
||||||
result = urllib2.urlopen(url, timeout=20).read()
|
|
||||||
except:
|
|
||||||
logger.warn('Could not open url: ' + url)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if result:
|
try:
|
||||||
try:
|
self.info_summary = data['album']['wiki']['summary']
|
||||||
data = simplejson.JSONDecoder().decode(result)
|
except KeyError:
|
||||||
except:
|
logger.debug('No album summary found')
|
||||||
logger.warn('Could not parse data from url: ' + url)
|
self.info_summary = None
|
||||||
return
|
try:
|
||||||
try:
|
self.info_content = data['album']['wiki']['content']
|
||||||
self.info_summary = data['album']['wiki']['summary']
|
except KeyError:
|
||||||
except Exception:
|
logger.debug('No album infomation found')
|
||||||
logger.debug('No album summary found from: ' + url)
|
self.info_content = None
|
||||||
self.info_summary = None
|
try:
|
||||||
try:
|
image_url = data['album']['image'][-1]['#text']
|
||||||
self.info_content = data['album']['wiki']['content']
|
except KeyError:
|
||||||
except Exception:
|
logger.debug('No album image link found')
|
||||||
logger.debug('No album infomation found from: ' + url)
|
image_url = None
|
||||||
self.info_content = None
|
|
||||||
try:
|
|
||||||
image_url = data['album']['image'][-1]['#text']
|
|
||||||
except Exception:
|
|
||||||
logger.debug('No album image link found on url: ' + url)
|
|
||||||
image_url = None
|
|
||||||
|
|
||||||
thumb_url = self._get_thumb_url(data)
|
thumb_url = self._get_thumb_url(data)
|
||||||
|
|
||||||
if not thumb_url:
|
if not thumb_url:
|
||||||
logger.debug('No album thumbnail image found on url: ' + url)
|
logger.debug('No album thumbnail image found')
|
||||||
|
|
||||||
#Save the content & summary to the database no matter what if we've opened up the url
|
#Save the content & summary to the database no matter what if we've opened up the url
|
||||||
if self.id_type == 'artist':
|
if self.id_type == 'artist':
|
||||||
@@ -411,20 +333,15 @@ class Cache(object):
|
|||||||
|
|
||||||
# Should we grab the artwork here if we're just grabbing thumbs or info?? Probably not since the files can be quite big
|
# Should we grab the artwork here if we're just grabbing thumbs or info?? Probably not since the files can be quite big
|
||||||
if image_url and self.query_type == 'artwork':
|
if image_url and self.query_type == 'artwork':
|
||||||
try:
|
artwork = request.request_content(image_url, timeout=20)
|
||||||
artwork = urllib2.urlopen(image_url, timeout=20).read()
|
|
||||||
except Exception, e:
|
|
||||||
logger.error('Unable to open url "' + image_url + '". Error: ' + str(e))
|
|
||||||
artwork = None
|
|
||||||
|
|
||||||
if artwork:
|
if artwork:
|
||||||
|
|
||||||
# Make sure the artwork dir exists:
|
# Make sure the artwork dir exists:
|
||||||
if not os.path.isdir(self.path_to_art_cache):
|
if not os.path.isdir(self.path_to_art_cache):
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.path_to_art_cache)
|
os.makedirs(self.path_to_art_cache)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error('Unable to create artwork cache dir. Error: ' + str(e))
|
logger.error('Unable to create artwork cache dir. Error: %s', e)
|
||||||
self.artwork_errors = True
|
self.artwork_errors = True
|
||||||
self.artwork_url = image_url
|
self.artwork_url = image_url
|
||||||
|
|
||||||
@@ -433,37 +350,30 @@ class Cache(object):
|
|||||||
try:
|
try:
|
||||||
os.remove(artwork_file)
|
os.remove(artwork_file)
|
||||||
except:
|
except:
|
||||||
logger.error('Error deleting file from the cache: ' + artwork_file)
|
logger.error('Error deleting file from the cache: %s', artwork_file)
|
||||||
|
|
||||||
ext = os.path.splitext(image_url)[1]
|
ext = os.path.splitext(image_url)[1]
|
||||||
|
|
||||||
artwork_path = os.path.join(self.path_to_art_cache, self.id + '.' + helpers.today() + ext)
|
artwork_path = os.path.join(self.path_to_art_cache, self.id + '.' + helpers.today() + ext)
|
||||||
try:
|
try:
|
||||||
f = open(artwork_path, 'wb')
|
with open(artwork_path, 'wb') as f:
|
||||||
f.write(artwork)
|
f.write(artwork)
|
||||||
f.close()
|
except IOError as e:
|
||||||
except Exception, e:
|
logger.error('Unable to write to the cache dir: %s', e)
|
||||||
logger.error('Unable to write to the cache dir: ' + str(e))
|
|
||||||
self.artwork_errors = True
|
self.artwork_errors = True
|
||||||
self.artwork_url = image_url
|
self.artwork_url = image_url
|
||||||
|
|
||||||
# Grab the thumbnail as well if we're getting the full artwork (as long as it's missing/outdated
|
# Grab the thumbnail as well if we're getting the full artwork (as long as it's missing/outdated
|
||||||
if thumb_url and self.query_type in ['thumb','artwork'] and not (self.thumb_files and self._is_current(self.thumb_files[0])):
|
if thumb_url and self.query_type in ['thumb','artwork'] and not (self.thumb_files and self._is_current(self.thumb_files[0])):
|
||||||
|
artwork = request.request_content(thumb_url, timeout=20)
|
||||||
try:
|
|
||||||
artwork = urllib2.urlopen(thumb_url, timeout=20).read()
|
|
||||||
except Exception, e:
|
|
||||||
logger.error('Unable to open url "' + thumb_url + '". Error: ' + str(e))
|
|
||||||
artwork = None
|
|
||||||
|
|
||||||
if artwork:
|
if artwork:
|
||||||
|
|
||||||
# Make sure the artwork dir exists:
|
# Make sure the artwork dir exists:
|
||||||
if not os.path.isdir(self.path_to_art_cache):
|
if not os.path.isdir(self.path_to_art_cache):
|
||||||
try:
|
try:
|
||||||
os.makedirs(self.path_to_art_cache)
|
os.makedirs(self.path_to_art_cache)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.error('Unable to create artwork cache dir. Error: ' + str(e))
|
logger.error('Unable to create artwork cache dir. Error: %s' + e)
|
||||||
self.thumb_errors = True
|
self.thumb_errors = True
|
||||||
self.thumb_url = thumb_url
|
self.thumb_url = thumb_url
|
||||||
|
|
||||||
@@ -471,18 +381,17 @@ class Cache(object):
|
|||||||
for thumb_file in self.thumb_files:
|
for thumb_file in self.thumb_files:
|
||||||
try:
|
try:
|
||||||
os.remove(thumb_file)
|
os.remove(thumb_file)
|
||||||
except:
|
except Exception as e:
|
||||||
logger.error('Error deleting file from the cache: ' + thumb_file)
|
logger.error('Error deleting file from the cache: %s', thumb_file)
|
||||||
|
|
||||||
ext = os.path.splitext(image_url)[1]
|
ext = os.path.splitext(image_url)[1]
|
||||||
|
|
||||||
thumb_path = os.path.join(self.path_to_art_cache, 'T_' + self.id + '.' + helpers.today() + ext)
|
thumb_path = os.path.join(self.path_to_art_cache, 'T_' + self.id + '.' + helpers.today() + ext)
|
||||||
try:
|
try:
|
||||||
f = open(thumb_path, 'wb')
|
with open(thumb_path, 'wb') as f:
|
||||||
f.write(artwork)
|
f.write(artwork)
|
||||||
f.close()
|
except IOError as e:
|
||||||
except Exception, e:
|
logger.error('Unable to write to the cache dir: %s', e)
|
||||||
logger.error('Unable to write to the cache dir: ' + str(e))
|
|
||||||
self.thumb_errors = True
|
self.thumb_errors = True
|
||||||
self.thumb_url = image_url
|
self.thumb_url = image_url
|
||||||
|
|
||||||
|
|||||||
@@ -59,37 +59,32 @@ class DBConnection:
|
|||||||
return
|
return
|
||||||
|
|
||||||
sqlResult = None
|
sqlResult = None
|
||||||
attempt = 0
|
|
||||||
|
|
||||||
while attempt < 5:
|
try:
|
||||||
try:
|
with self.connection as c:
|
||||||
if args == None:
|
if args == None:
|
||||||
#logger.debug(self.filename+": "+query)
|
sqlResult = c.execute(query)
|
||||||
sqlResult = self.connection.execute(query)
|
|
||||||
else:
|
else:
|
||||||
#logger.debug(self.filename+": "+query+" with args "+str(args))
|
sqlResult = c.execute(query, args)
|
||||||
sqlResult = self.connection.execute(query, args)
|
|
||||||
self.connection.commit()
|
except sqlite3.OperationalError, e:
|
||||||
break
|
if "unable to open database file" in e.message or "database is locked" in e.message:
|
||||||
except sqlite3.OperationalError, e:
|
logger.warn('Database Error: %s', e)
|
||||||
if "unable to open database file" in e.message or "database is locked" in e.message:
|
else:
|
||||||
logger.warn('Database Error: %s' % e)
|
logger.error('Database error: %s', e)
|
||||||
attempt += 1
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
logger.error('Database error: %s' % e)
|
|
||||||
raise
|
|
||||||
except sqlite3.DatabaseError, e:
|
|
||||||
logger.error('Fatal Error executing %s :: %s' % (query, e))
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
except sqlite3.DatabaseError, e:
|
||||||
|
logger.error('Fatal Error executing %s :: %s', query, e)
|
||||||
|
raise
|
||||||
|
|
||||||
return sqlResult
|
return sqlResult
|
||||||
|
|
||||||
def select(self, query, args=None):
|
def select(self, query, args=None):
|
||||||
|
|
||||||
sqlResults = self.action(query, args).fetchall()
|
sqlResults = self.action(query, args).fetchall()
|
||||||
|
|
||||||
if sqlResults == None:
|
if sqlResults == None or sqlResults == [None]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return sqlResults
|
return sqlResults
|
||||||
|
|||||||
@@ -13,29 +13,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
def ex(e):
|
|
||||||
"""
|
|
||||||
Returns a string from the exception text if it exists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# sanity check
|
|
||||||
if not e.args or not e.args[0]:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
e_message = e.args[0]
|
|
||||||
|
|
||||||
# if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway
|
|
||||||
if not e_message:
|
|
||||||
try:
|
|
||||||
e_message = str(e.args[0])
|
|
||||||
except:
|
|
||||||
e_message = ""
|
|
||||||
|
|
||||||
return e_message
|
|
||||||
|
|
||||||
|
|
||||||
class HeadphonesException(Exception):
|
class HeadphonesException(Exception):
|
||||||
"Generic Headphones Exception - should never be thrown, only subclassed"
|
"""
|
||||||
|
Generic Headphones Exception - should never be thrown, only subclassed
|
||||||
|
"""
|
||||||
|
|
||||||
class NewzbinAPIThrottled(HeadphonesException):
|
class NewzbinAPIThrottled(HeadphonesException):
|
||||||
"Newzbin has throttled us, deal with it"
|
"""
|
||||||
|
Newzbin has throttled us, deal with it
|
||||||
|
"""
|
||||||
|
|||||||
@@ -13,15 +13,25 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os, time
|
import os
|
||||||
from operator import itemgetter
|
import re
|
||||||
|
import time
|
||||||
|
import shutil
|
||||||
import datetime
|
import datetime
|
||||||
import re, shutil
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
import unicodedata
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from operator import itemgetter
|
||||||
|
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||||
|
|
||||||
|
# Modified from https://github.com/Verrus/beets-plugin-featInTitle
|
||||||
|
RE_FEATURING = re.compile(r"[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.")
|
||||||
|
|
||||||
|
RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I)
|
||||||
|
RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I)
|
||||||
|
|
||||||
def multikeysort(items, columns):
|
def multikeysort(items, columns):
|
||||||
|
|
||||||
comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns]
|
comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns]
|
||||||
|
|
||||||
def comparer(left, right):
|
def comparer(left, right):
|
||||||
@@ -160,15 +170,28 @@ def piratesize(size):
|
|||||||
|
|
||||||
return size
|
return size
|
||||||
|
|
||||||
def replace_all(text, dic):
|
def replace_all(text, dic, normalize=False):
|
||||||
|
|
||||||
if not text:
|
if not text:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
for i, j in dic.iteritems():
|
for i, j in dic.iteritems():
|
||||||
|
if normalize:
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
j = unicodedata.normalize('NFD', j)
|
||||||
|
else:
|
||||||
|
j = unicodedata.normalize('NFC', j)
|
||||||
text = text.replace(i, j)
|
text = text.replace(i, j)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
def replace_illegal_chars(string, type="file"):
|
||||||
|
if type == "file":
|
||||||
|
string = re.sub('[\?"*:|<>/]', '_', string)
|
||||||
|
if type == "folder":
|
||||||
|
string = re.sub('[:\?<>"|]', '_', string)
|
||||||
|
|
||||||
|
return string
|
||||||
|
|
||||||
def cleanName(string):
|
def cleanName(string):
|
||||||
|
|
||||||
pass1 = latinToAscii(string).lower()
|
pass1 = latinToAscii(string).lower()
|
||||||
@@ -187,8 +210,107 @@ def cleanTitle(title):
|
|||||||
|
|
||||||
return title
|
return title
|
||||||
|
|
||||||
|
def split_path(f):
|
||||||
|
"""
|
||||||
|
Split a path into components, starting with the drive letter (if any). Given
|
||||||
|
a path, os.path.join(*split_path(f)) should be path equal to f.
|
||||||
|
"""
|
||||||
|
|
||||||
|
components = []
|
||||||
|
drive, path = os.path.splitdrive(f)
|
||||||
|
|
||||||
|
# Stip the folder from the path, iterate until nothing is left
|
||||||
|
while True:
|
||||||
|
path, folder = os.path.split(path)
|
||||||
|
|
||||||
|
if folder:
|
||||||
|
components.append(folder)
|
||||||
|
else:
|
||||||
|
if path:
|
||||||
|
components.append(path)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# Append the drive (if any)
|
||||||
|
if drive:
|
||||||
|
components.append(drive)
|
||||||
|
|
||||||
|
# Reverse components
|
||||||
|
components.reverse()
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return components
|
||||||
|
|
||||||
|
def expand_subfolders(f):
|
||||||
|
"""
|
||||||
|
Try to expand a given folder and search for subfolders containing media
|
||||||
|
files. This should work for discographies indexed per album in the same
|
||||||
|
root, possibly with folders per CD (if any).
|
||||||
|
|
||||||
|
This algorithm will return nothing if the result is only one folder. In this
|
||||||
|
case, normal post processing will be better.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from headphones import logger
|
||||||
|
|
||||||
|
# Find all folders with media files in them
|
||||||
|
media_folders = []
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(f):
|
||||||
|
for file in files:
|
||||||
|
extension = os.path.splitext(file)[1].lower()[1:]
|
||||||
|
|
||||||
|
if extension in headphones.MEDIA_FORMATS:
|
||||||
|
if root not in media_folders:
|
||||||
|
media_folders.append(root)
|
||||||
|
|
||||||
|
# Stop here if nothing found
|
||||||
|
if len(media_folders) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Split into path components
|
||||||
|
media_folders = [ split_path(media_folder) for media_folder in media_folders ]
|
||||||
|
|
||||||
|
# Correct folder endings such as CD1 etc.
|
||||||
|
for index, media_folder in enumerate(media_folders):
|
||||||
|
if RE_CD.match(media_folder[-1]):
|
||||||
|
media_folders[index] = media_folders[index][:-1]
|
||||||
|
|
||||||
|
# Verify the result by computing path depth relative to root.
|
||||||
|
path_depths = [ len(media_folder) for media_folder in media_folders ]
|
||||||
|
difference = max(path_depths) - min(path_depths)
|
||||||
|
|
||||||
|
if difference > 0:
|
||||||
|
logger.info("Found %d media folders, but depth difference between lowest and deepest media folder is %d (expected zero). If this is a discography or a collection of albums, make sure albums are per folder.", len(media_folders), difference)
|
||||||
|
|
||||||
|
# While already failed, advice the user what he could try. We assume the
|
||||||
|
# directory may contain separate CD's and maybe some extra's. The
|
||||||
|
# structure may look like X albums at same depth, and (one or more)
|
||||||
|
# extra folders with a higher depth.
|
||||||
|
extra_media_folders = [ media_folder[:min(path_depths)] for media_folder in media_folders if len(media_folder) > min(path_depths) ]
|
||||||
|
extra_media_folders = list(set([ os.path.join(*media_folder) for media_folder in extra_media_folders ]))
|
||||||
|
|
||||||
|
logger.info("Please look at the following folder(s), since they cause the depth difference: %s", extra_media_folders)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Convert back to paths and remove duplicates, which may be there after
|
||||||
|
# correcting the paths
|
||||||
|
media_folders = list(set([ os.path.join(*media_folder) for media_folder in media_folders ]))
|
||||||
|
|
||||||
|
# Don't return a result if the number of subfolders is one. In this case,
|
||||||
|
# this algorithm will not improve processing and will likely interfere
|
||||||
|
# with other attempts such as MusicBrainz release group IDs.
|
||||||
|
if len(media_folders) == 1:
|
||||||
|
logger.debug("Did not expand subfolder, as it resulted in one folder.")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.debug("Expanded subfolders in folder: %s", media_folders)
|
||||||
|
return media_folders
|
||||||
|
|
||||||
def extract_data(s):
|
def extract_data(s):
|
||||||
|
|
||||||
|
s = s.replace('_', ' ')
|
||||||
|
|
||||||
#headphones default format
|
#headphones default format
|
||||||
pattern = re.compile(r'(?P<name>.*?)\s\-\s(?P<album>.*?)\s\[(?P<year>.*?)\]', re.VERBOSE)
|
pattern = re.compile(r'(?P<name>.*?)\s\-\s(?P<album>.*?)\s\[(?P<year>.*?)\]', re.VERBOSE)
|
||||||
match = pattern.match(s)
|
match = pattern.match(s)
|
||||||
@@ -207,9 +329,118 @@ def extract_data(s):
|
|||||||
album = match.group("album")
|
album = match.group("album")
|
||||||
year = match.group("year")
|
year = match.group("year")
|
||||||
return (name, album, year)
|
return (name, album, year)
|
||||||
|
|
||||||
|
#Gonna take a guess on this one - might be enough to search on mb
|
||||||
|
pat = re.compile(r"(?P<name>.*?)\s*-\s*(?P<album>[^\[(-]*)")
|
||||||
|
|
||||||
|
match = pat.match(s)
|
||||||
|
if match:
|
||||||
|
name = match.group("name")
|
||||||
|
album = match.group("album")
|
||||||
|
year = None
|
||||||
|
return (name, album, year)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return (None, None, None)
|
return (None, None, None)
|
||||||
|
|
||||||
|
def extract_metadata(f):
|
||||||
|
"""
|
||||||
|
Scan all files in the given directory and decide on an artist, album and
|
||||||
|
year based on the metadata. A decision is based on the number of different
|
||||||
|
artists, albums and years found in the media files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from headphones import logger
|
||||||
|
|
||||||
|
# Walk directory and scan all media files
|
||||||
|
results = []
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(f):
|
||||||
|
for file in files:
|
||||||
|
# Count the number of potential media files
|
||||||
|
extension = os.path.splitext(file)[1].lower()[1:]
|
||||||
|
|
||||||
|
if extension in headphones.MEDIA_FORMATS:
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
# Try to read the file info
|
||||||
|
try:
|
||||||
|
media_file = MediaFile(os.path.join(root, file))
|
||||||
|
except (FileTypeError, UnreadableFileError):
|
||||||
|
# Probably not a media file
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Append metadata to file
|
||||||
|
artist = media_file.albumartist or media_file.artist
|
||||||
|
album = media_file.album
|
||||||
|
year = media_file.year
|
||||||
|
|
||||||
|
if artist and album and year:
|
||||||
|
results.append((artist.lower(), album.lower(), year))
|
||||||
|
|
||||||
|
# Verify results
|
||||||
|
if len(results) == 0:
|
||||||
|
logger.info("No metadata in media files found, ignoring.")
|
||||||
|
return (None, None, None)
|
||||||
|
|
||||||
|
# Require that some percentage of files have tags
|
||||||
|
count_ratio = 0.75
|
||||||
|
|
||||||
|
if count < (count_ratio * len(results)):
|
||||||
|
logger.info("Counted %d media files, but only %d have tags, ignoring.", count, len(results))
|
||||||
|
return (None, None, None)
|
||||||
|
|
||||||
|
# Count distinct values
|
||||||
|
artists = list(set([ x[0] for x in results ]))
|
||||||
|
albums = list(set([ x[1] for x in results ]))
|
||||||
|
years = list(set([ x[2] for x in results ]))
|
||||||
|
|
||||||
|
# Remove things such as CD2 from album names
|
||||||
|
if len(albums) > 1:
|
||||||
|
new_albums = list(albums)
|
||||||
|
|
||||||
|
# Replace occurences of e.g. CD1
|
||||||
|
for index, album in enumerate(new_albums):
|
||||||
|
if RE_CD_ALBUM.search(album):
|
||||||
|
old_album = new_albums[index]
|
||||||
|
new_albums[index] = RE_CD_ALBUM.sub("", album).strip()
|
||||||
|
|
||||||
|
logger.debug("Stripped albumd number identifier: %s -> %s", old_album, new_albums[index])
|
||||||
|
|
||||||
|
# Remove duplicates
|
||||||
|
new_albums = list(set(new_albums))
|
||||||
|
|
||||||
|
# Safety check: if nothing has merged, then ignore the work. This can
|
||||||
|
# happen if only one CD of a multi part CD is processed.
|
||||||
|
if len(new_albums) < len(albums):
|
||||||
|
albums = new_albums
|
||||||
|
|
||||||
|
# All files have the same metadata, so it's trivial
|
||||||
|
if len(artists) == 1 and len(albums) == 1:
|
||||||
|
return (artists[0], albums[0], years[0])
|
||||||
|
|
||||||
|
# (Lots of) different artists. Could be a featuring album, so test for this.
|
||||||
|
if len(artists) > 1 and len(albums) == 1:
|
||||||
|
split_artists = [ RE_FEATURING.split(artist) for artist in artists ]
|
||||||
|
featurings = [ len(split_artist) - 1 for split_artist in split_artists ]
|
||||||
|
logger.info("Album seem to feature %d different artists", sum(featurings))
|
||||||
|
|
||||||
|
if sum(featurings) > 0:
|
||||||
|
# Find the artist of which the least splits have been generated.
|
||||||
|
# Ideally, this should be 0, which should be the album artist
|
||||||
|
# itself.
|
||||||
|
artist = split_artists[featurings.index(min(featurings))][0]
|
||||||
|
|
||||||
|
# Done
|
||||||
|
return (artist, albums[0], years[0])
|
||||||
|
|
||||||
|
# Not sure what to do here.
|
||||||
|
logger.info("Found %d artists, %d albums and %d years in metadata, so ignoring", len(artists), len(albums), len(years))
|
||||||
|
logger.debug("Artists: %s, Albums: %s, Years: %s", artists, albums, years)
|
||||||
|
|
||||||
|
return (None, None, None)
|
||||||
|
|
||||||
def extract_logline(s):
|
def extract_logline(s):
|
||||||
# Default log format
|
# Default log format
|
||||||
pattern = re.compile(r'(?P<timestamp>.*?)\s\-\s(?P<level>.*?)\s*\:\:\s(?P<thread>.*?)\s\:\s(?P<message>.*)', re.VERBOSE)
|
pattern = re.compile(r'(?P<timestamp>.*?)\s\-\s(?P<level>.*?)\s*\:\:\s(?P<thread>.*?)\s\:\s(?P<message>.*)', re.VERBOSE)
|
||||||
@@ -240,7 +471,7 @@ def extract_song_data(s):
|
|||||||
year = match.group("year")
|
year = match.group("year")
|
||||||
return (name, album, year)
|
return (name, album, year)
|
||||||
else:
|
else:
|
||||||
logger.info("Couldn't parse " + s + " into a valid default format")
|
logger.info("Couldn't parse %s into a valid default format", s)
|
||||||
|
|
||||||
#newzbin default format
|
#newzbin default format
|
||||||
pattern = re.compile(r'(?P<name>.*?)\s\-\s(?P<album>.*?)\s\((?P<year>\d+?\))', re.VERBOSE)
|
pattern = re.compile(r'(?P<name>.*?)\s\-\s(?P<album>.*?)\s\((?P<year>\d+?\))', re.VERBOSE)
|
||||||
@@ -251,7 +482,7 @@ def extract_song_data(s):
|
|||||||
year = match.group("year")
|
year = match.group("year")
|
||||||
return (name, album, year)
|
return (name, album, year)
|
||||||
else:
|
else:
|
||||||
logger.info("Couldn't parse " + s + " into a valid Newbin format")
|
logger.info("Couldn't parse %s into a valid Newbin format", s)
|
||||||
return (name, album, year)
|
return (name, album, year)
|
||||||
|
|
||||||
def smartMove(src, dest, delete=True):
|
def smartMove(src, dest, delete=True):
|
||||||
@@ -262,7 +493,7 @@ def smartMove(src, dest, delete=True):
|
|||||||
filename = os.path.basename(src)
|
filename = os.path.basename(src)
|
||||||
|
|
||||||
if os.path.isfile(os.path.join(dest, filename)):
|
if os.path.isfile(os.path.join(dest, filename)):
|
||||||
logger.info('Destination file exists: %s' % os.path.join(dest, filename).decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info('Destination file exists: %s', os.path.join(dest, filename))
|
||||||
title = os.path.splitext(filename)[0]
|
title = os.path.splitext(filename)[0]
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = os.path.splitext(filename)[1]
|
||||||
i = 1
|
i = 1
|
||||||
@@ -271,12 +502,12 @@ def smartMove(src, dest, delete=True):
|
|||||||
if os.path.isfile(os.path.join(dest, newfile)):
|
if os.path.isfile(os.path.join(dest, newfile)):
|
||||||
i += 1
|
i += 1
|
||||||
else:
|
else:
|
||||||
logger.info('Renaming to %s' % newfile)
|
logger.info('Renaming to %s', newfile)
|
||||||
try:
|
try:
|
||||||
os.rename(src, os.path.join(source_dir, newfile))
|
os.rename(src, os.path.join(source_dir, newfile))
|
||||||
filename = newfile
|
filename = newfile
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn('Error renaming %s: %s' % (src.decode(headphones.SYS_ENCODING, 'replace'), str(e).decode(headphones.SYS_ENCODING, 'replace')))
|
logger.warn('Error renaming %s: %s', src.decode(headphones.SYS_ENCODING, 'replace'), e)
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -286,7 +517,7 @@ def smartMove(src, dest, delete=True):
|
|||||||
shutil.copy(os.path.join(source_dir, filename), os.path.join(dest, filename))
|
shutil.copy(os.path.join(source_dir, filename), os.path.join(dest, filename))
|
||||||
return True
|
return True
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn('Error moving file %s: %s' % (filename.decode(headphones.SYS_ENCODING, 'replace'), str(e).decode(headphones.SYS_ENCODING, 'replace')))
|
logger.warn('Error moving file %s: %s', filename.decode(headphones.SYS_ENCODING, 'replace'), e)
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
#Sab renaming functions #
|
#Sab renaming functions #
|
||||||
@@ -340,9 +571,9 @@ def sab_sanitize_foldername(name):
|
|||||||
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def split_string(mystring):
|
def split_string(mystring, splitvar=','):
|
||||||
mylist = []
|
mylist = []
|
||||||
for each_word in mystring.split(','):
|
for each_word in mystring.split(splitvar):
|
||||||
mylist.append(each_word.strip())
|
mylist.append(each_word.strip())
|
||||||
return mylist
|
return mylist
|
||||||
|
|
||||||
@@ -354,10 +585,10 @@ def create_https_certificates(ssl_cert, ssl_key):
|
|||||||
from headphones import logger
|
from headphones import logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from OpenSSL import crypto #@UnresolvedImport
|
from OpenSSL import crypto
|
||||||
from lib.certgen import createKeyPair, createCertRequest, createCertificate, TYPE_RSA, serial #@UnresolvedImport
|
from lib.certgen import createKeyPair, createCertRequest, createCertificate, TYPE_RSA, serial
|
||||||
except:
|
except:
|
||||||
logger.warn(u"pyopenssl module missing, please install for https access")
|
logger.warn("pyOpenSSL module missing, please install to enable HTTPS")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Create the CA Certificate
|
# Create the CA Certificate
|
||||||
@@ -372,10 +603,12 @@ def create_https_certificates(ssl_cert, ssl_key):
|
|||||||
|
|
||||||
# Save the key and certificate to disk
|
# Save the key and certificate to disk
|
||||||
try:
|
try:
|
||||||
open(ssl_key, 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
with open(ssl_key, 'w') as f:
|
||||||
open(ssl_cert, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||||
except:
|
with open(ssl_cert, 'w') as f:
|
||||||
logger.error(u"Error creating SSL key and certificate")
|
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||||
|
except IOError as e:
|
||||||
|
logger.error("Error creating SSL key and certificate: %s", e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -13,24 +13,26 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from lib.pyItunes import *
|
from headphones import logger, helpers, db, mb, lastfm
|
||||||
|
|
||||||
|
from beets.mediafile import MediaFile
|
||||||
|
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
import os
|
|
||||||
from lib.beets.mediafile import MediaFile
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
from headphones import logger, helpers, db, mb, albumart, lastfm
|
|
||||||
|
|
||||||
blacklisted_special_artist_names = ['[anonymous]','[data]','[no artist]','[traditional]','[unknown]','Various Artists']
|
|
||||||
blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088','33cf029c-63b0-41a0-9855-be2a3665fb3b',\
|
|
||||||
'314e1c25-dde7-4e4d-b2f4-0a7b9f7c56dc','eec63d3c-3b81-4ad4-b1e4-7c147d4d2b61',\
|
|
||||||
'9be7f096-97ec-4615-8957-8d40b5dcbc41','125ec42a-7229-4250-afc5-e057484327fe',\
|
|
||||||
'89ad4ac3-39f7-470e-963a-56509c546377']
|
|
||||||
|
|
||||||
|
blacklisted_special_artist_names = ['[anonymous]', '[data]', '[no artist]',
|
||||||
|
'[traditional]','[unknown]','Various Artists']
|
||||||
|
blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088',
|
||||||
|
'33cf029c-63b0-41a0-9855-be2a3665fb3b',
|
||||||
|
'314e1c25-dde7-4e4d-b2f4-0a7b9f7c56dc',
|
||||||
|
'eec63d3c-3b81-4ad4-b1e4-7c147d4d2b61',
|
||||||
|
'9be7f096-97ec-4615-8957-8d40b5dcbc41',
|
||||||
|
'125ec42a-7229-4250-afc5-e057484327fe',
|
||||||
|
'89ad4ac3-39f7-470e-963a-56509c546377']
|
||||||
|
|
||||||
def is_exists(artistid):
|
def is_exists(artistid):
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
# See if the artist is already in the database
|
# See if the artist is already in the database
|
||||||
@@ -56,7 +58,7 @@ def artistlist_to_mbids(artistlist, forced=False):
|
|||||||
if not isinstance(artist, unicode):
|
if not isinstance(artist, unicode):
|
||||||
try:
|
try:
|
||||||
artist = artist.decode('utf-8', 'replace')
|
artist = artist.decode('utf-8', 'replace')
|
||||||
except:
|
except Exception:
|
||||||
logger.warn("Unable to convert artist to unicode so cannot do a database lookup")
|
logger.warn("Unable to convert artist to unicode so cannot do a database lookup")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -68,7 +70,6 @@ def artistlist_to_mbids(artistlist, forced=False):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
artistid = results[0]['id']
|
artistid = results[0]['id']
|
||||||
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
logger.info('MusicBrainz query turned up no matches for: %s' % artist)
|
logger.info('MusicBrainz query turned up no matches for: %s' % artist)
|
||||||
continue
|
continue
|
||||||
@@ -98,9 +99,10 @@ def artistlist_to_mbids(artistlist, forced=False):
|
|||||||
|
|
||||||
# Update the similar artist tag cloud:
|
# Update the similar artist tag cloud:
|
||||||
logger.info('Updating artist information from Last.fm')
|
logger.info('Updating artist information from Last.fm')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lastfm.getSimilar()
|
lastfm.getSimilar()
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.warn('Failed to update arist information from Last.fm: %s' % e)
|
logger.warn('Failed to update arist information from Last.fm: %s' % e)
|
||||||
|
|
||||||
def addArtistIDListToDB(artistidlist):
|
def addArtistIDListToDB(artistidlist):
|
||||||
@@ -129,11 +131,9 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
|
|
||||||
# We need the current minimal info in the database instantly
|
# We need the current minimal info in the database instantly
|
||||||
# so we don't throw a 500 error when we redirect to the artistPage
|
# so we don't throw a 500 error when we redirect to the artistPage
|
||||||
|
|
||||||
controlValueDict = {"ArtistID": artistid}
|
controlValueDict = {"ArtistID": artistid}
|
||||||
|
|
||||||
# Don't replace a known artist name with an "Artist ID" placeholder
|
# Don't replace a known artist name with an "Artist ID" placeholder
|
||||||
|
|
||||||
dbartist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [artistid]).fetchone()
|
dbartist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [artistid]).fetchone()
|
||||||
|
|
||||||
# Only modify the Include Extras stuff if it's a new artist. We need it early so we know what to fetch
|
# Only modify the Include Extras stuff if it's a new artist. We need it early so we know what to fetch
|
||||||
@@ -182,24 +182,27 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
|
|
||||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||||
|
|
||||||
# See if we need to grab extras. Artist specific extras take precedence over global option
|
# See if we need to grab extras. Artist specific extras take precedence
|
||||||
# Global options are set when adding a new artist
|
# over global option. Global options are set when adding a new artist
|
||||||
myDB = db.DBConnection()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db_artist = myDB.action('SELECT IncludeExtras, Extras from artists WHERE ArtistID=?', [artistid]).fetchone()
|
db_artist = myDB.action('SELECT IncludeExtras, Extras from artists WHERE ArtistID=?', [artistid]).fetchone()
|
||||||
includeExtras = db_artist['IncludeExtras']
|
includeExtras = db_artist['IncludeExtras']
|
||||||
except IndexError:
|
except IndexError:
|
||||||
includeExtras = False
|
includeExtras = False
|
||||||
|
|
||||||
#Clean all references to release group in dB that are no longer referenced in musicbrainz
|
# Clean all references to release group in dB that are no longer referenced
|
||||||
|
# from the musicbrainz refresh
|
||||||
group_list = []
|
group_list = []
|
||||||
force_repackage = 0
|
force_repackage = 0
|
||||||
#Don't nuke the database if there's a MusicBrainz error
|
|
||||||
if len(artist['releasegroups']) != 0 and not extrasonly:
|
# Don't nuke the database if there's a MusicBrainz error
|
||||||
|
if len(artist['releasegroups']) != 0:
|
||||||
for groups in artist['releasegroups']:
|
for groups in artist['releasegroups']:
|
||||||
group_list.append(groups['id'])
|
group_list.append(groups['id'])
|
||||||
remove_missing_groups_from_albums = myDB.action("SELECT AlbumID FROM albums WHERE ArtistID=?", [artistid])
|
if not extrasonly:
|
||||||
|
remove_missing_groups_from_albums = myDB.select("SELECT AlbumID FROM albums WHERE ArtistID=?", [artistid])
|
||||||
|
else:
|
||||||
|
remove_missing_groups_from_albums = myDB.select('SELECT AlbumID FROM albums WHERE ArtistID=? AND Status="Skipped" AND Type!="Album"', [artistid])
|
||||||
for items in remove_missing_groups_from_albums:
|
for items in remove_missing_groups_from_albums:
|
||||||
if items['AlbumID'] not in group_list:
|
if items['AlbumID'] not in group_list:
|
||||||
# Remove all from albums/tracks that aren't in release groups
|
# Remove all from albums/tracks that aren't in release groups
|
||||||
@@ -207,16 +210,18 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
myDB.action("DELETE FROM allalbums WHERE AlbumID=?", [items['AlbumID']])
|
myDB.action("DELETE FROM allalbums WHERE AlbumID=?", [items['AlbumID']])
|
||||||
myDB.action("DELETE FROM tracks WHERE AlbumID=?", [items['AlbumID']])
|
myDB.action("DELETE FROM tracks WHERE AlbumID=?", [items['AlbumID']])
|
||||||
myDB.action("DELETE FROM alltracks WHERE AlbumID=?", [items['AlbumID']])
|
myDB.action("DELETE FROM alltracks WHERE AlbumID=?", [items['AlbumID']])
|
||||||
logger.info("[%s] Removing all references to release group %s to reflect MusicBrainz" % (artist['artist_name'], items['AlbumID']))
|
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [items['AlbumID']])
|
||||||
force_repackage = 1
|
logger.info("[%s] Removing all references to release group %s to reflect MusicBrainz refresh" % (artist['artist_name'], items['AlbumID']))
|
||||||
|
if not extrasonly:
|
||||||
|
force_repackage = 1
|
||||||
else:
|
else:
|
||||||
logger.info("[%s] Error pulling data from MusicBrainz: Maintaining dB" % artist['artist_name'])
|
if not extrasonly:
|
||||||
|
logger.info("[%s] There was either an error pulling data from MusicBrainz or there might not be any releases for this category" % artist['artist_name'])
|
||||||
|
|
||||||
# Then search for releases within releasegroups, if releases don't exist, then remove from allalbums/alltracks
|
# Then search for releases within releasegroups, if releases don't exist, then remove from allalbums/alltracks
|
||||||
|
album_searches = []
|
||||||
|
|
||||||
for rg in artist['releasegroups']:
|
for rg in artist['releasegroups']:
|
||||||
|
|
||||||
al_title = rg['title']
|
al_title = rg['title']
|
||||||
today = helpers.today()
|
today = helpers.today()
|
||||||
rgid = rg['id']
|
rgid = rg['id']
|
||||||
@@ -224,62 +229,65 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
#Make a user configurable variable to skip update of albums with release dates older than this date (in days)
|
#Make a user configurable variable to skip update of albums with release dates older than this date (in days)
|
||||||
pause_delta = headphones.MB_IGNORE_AGE
|
pause_delta = headphones.MB_IGNORE_AGE
|
||||||
|
|
||||||
check_release_date = myDB.action("SELECT ReleaseDate, Status from albums WHERE ArtistID=? AND AlbumTitle=?", (artistid, al_title)).fetchone()
|
rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone()
|
||||||
|
|
||||||
#Skip update if Status set
|
|
||||||
if check_release_date and check_release_date[1]:
|
|
||||||
logger.info("[%s] Not updating: %s (Status is %s, skipping)" % (artist['artist_name'], rg['title'], check_release_date[1]))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not forcefull:
|
if not forcefull:
|
||||||
if check_release_date:
|
new_release_group = False
|
||||||
if check_release_date[0] is None:
|
|
||||||
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
try:
|
||||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
check_release_date = rg_exists['ReleaseDate']
|
||||||
else:
|
except TypeError:
|
||||||
if len(check_release_date[0]) == 10:
|
check_release_date = None
|
||||||
release_date = check_release_date[0]
|
new_release_group = True
|
||||||
elif len(check_release_date[0]) == 7:
|
|
||||||
release_date = check_release_date[0]+"-31"
|
|
||||||
elif len(check_release_date[0]) == 4:
|
if new_release_group:
|
||||||
release_date = check_release_date[0]+"-12-31"
|
|
||||||
else:
|
|
||||||
release_date = today
|
|
||||||
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
|
|
||||||
logger.info("[%s] Now updating: %s (Release Date <%s Days) " % (artist['artist_name'], rg['title'], pause_delta))
|
|
||||||
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
|
||||||
else:
|
|
||||||
logger.info("[%s] Skipping: %s (Release Date >%s Days)" % (artist['artist_name'], rg['title'], pause_delta))
|
|
||||||
skip_log = 1
|
|
||||||
new_releases = 0
|
|
||||||
else:
|
|
||||||
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
||||||
new_releases = mb.get_new_releases(rgid,includeExtras)
|
new_releases = mb.get_new_releases(rgid,includeExtras)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if check_release_date is None or check_release_date == u"None":
|
||||||
|
logger.info("[%s] Now updating: %s (No Release Date)" % (artist['artist_name'], rg['title']))
|
||||||
|
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||||
|
else:
|
||||||
|
if len(check_release_date) == 10:
|
||||||
|
release_date = check_release_date
|
||||||
|
elif len(check_release_date) == 7:
|
||||||
|
release_date = check_release_date+"-31"
|
||||||
|
elif len(check_release_date) == 4:
|
||||||
|
release_date = check_release_date+"-12-31"
|
||||||
|
else:
|
||||||
|
release_date = today
|
||||||
|
if helpers.get_age(today) - helpers.get_age(release_date) < pause_delta:
|
||||||
|
logger.info("[%s] Now updating: %s (Release Date <%s Days)", artist['artist_name'], rg['title'], pause_delta)
|
||||||
|
new_releases = mb.get_new_releases(rgid,includeExtras,True)
|
||||||
|
else:
|
||||||
|
logger.info("[%s] Skipping: %s (Release Date >%s Days)", artist['artist_name'], rg['title'], pause_delta)
|
||||||
|
skip_log = 1
|
||||||
|
new_releases = 0
|
||||||
|
|
||||||
if force_repackage == 1:
|
if force_repackage == 1:
|
||||||
new_releases = -1
|
new_releases = -1
|
||||||
logger.info('[%s] Forcing repackage of %s (Release Group Removed)' % (artist['artist_name'], al_title))
|
logger.info('[%s] Forcing repackage of %s (Release Group Removed)', artist['artist_name'], al_title)
|
||||||
else:
|
else:
|
||||||
new_releases = new_releases
|
new_releases = new_releases
|
||||||
else:
|
else:
|
||||||
logger.info("[%s] Now adding/updating: %s (Comprehensive Force)" % (artist['artist_name'], rg['title']))
|
logger.info("[%s] Now adding/updating: %s (Comprehensive Force)", artist['artist_name'], rg['title'])
|
||||||
new_releases = mb.get_new_releases(rgid,includeExtras,forcefull)
|
new_releases = mb.get_new_releases(rgid,includeExtras,forcefull)
|
||||||
|
|
||||||
#What this does is adds new releases per artist to the allalbums + alltracks databases
|
|
||||||
#new_releases = mb.get_new_releases(rgid,includeExtras)
|
|
||||||
#print al_title
|
|
||||||
#print new_releases
|
|
||||||
|
|
||||||
if new_releases != 0:
|
if new_releases != 0:
|
||||||
#Dump existing hybrid release since we're repackaging/replacing it
|
# Dump existing hybrid release since we're repackaging/replacing it
|
||||||
myDB.action("DELETE from albums WHERE ReleaseID=?", [rg['id']])
|
myDB.action("DELETE from albums WHERE ReleaseID=?", [rg['id']])
|
||||||
myDB.action("DELETE from allalbums WHERE ReleaseID=?", [rg['id']])
|
myDB.action("DELETE from allalbums WHERE ReleaseID=?", [rg['id']])
|
||||||
myDB.action("DELETE from tracks WHERE ReleaseID=?", [rg['id']])
|
myDB.action("DELETE from tracks WHERE ReleaseID=?", [rg['id']])
|
||||||
myDB.action("DELETE from alltracks WHERE ReleaseID=?", [rg['id']])
|
myDB.action("DELETE from alltracks WHERE ReleaseID=?", [rg['id']])
|
||||||
|
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [rg['id']])
|
||||||
|
|
||||||
# This will be used later to build a hybrid release
|
# This will be used later to build a hybrid release
|
||||||
fullreleaselist = []
|
fullreleaselist = []
|
||||||
#Search for releases within a release group
|
# Search for releases within a release group
|
||||||
find_hybrid_releases = myDB.action("SELECT * from allalbums WHERE AlbumID=?", [rg['id']])
|
find_hybrid_releases = myDB.action("SELECT * from allalbums WHERE AlbumID=?", [rg['id']])
|
||||||
|
|
||||||
# Build the dictionary for the fullreleaselist
|
# Build the dictionary for the fullreleaselist
|
||||||
for items in find_hybrid_releases:
|
for items in find_hybrid_releases:
|
||||||
if items['ReleaseID'] != rg['id']: #don't include hybrid information, since that's what we're replacing
|
if items['ReleaseID'] != rg['id']: #don't include hybrid information, since that's what we're replacing
|
||||||
@@ -310,14 +318,12 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
newValueDict['Tracks'] = hybrid_track_array
|
newValueDict['Tracks'] = hybrid_track_array
|
||||||
fullreleaselist.append(newValueDict)
|
fullreleaselist.append(newValueDict)
|
||||||
|
|
||||||
#print fullreleaselist
|
|
||||||
|
|
||||||
# Basically just do the same thing again for the hybrid release
|
# Basically just do the same thing again for the hybrid release
|
||||||
# This may end up being called with an empty fullreleaselist
|
# This may end up being called with an empty fullreleaselist
|
||||||
try:
|
try:
|
||||||
hybridrelease = getHybridRelease(fullreleaselist)
|
hybridrelease = getHybridRelease(fullreleaselist)
|
||||||
logger.info('[%s] Packaging %s releases into hybrid title' % (artist['artist_name'], rg['title']))
|
logger.info('[%s] Packaging %s releases into hybrid title' % (artist['artist_name'], rg['title']))
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
errors = True
|
errors = True
|
||||||
logger.warn('[%s] Unable to get hybrid release information for %s: %s' % (artist['artist_name'],rg['title'],e))
|
logger.warn('[%s] Unable to get hybrid release information for %s: %s' % (artist['artist_name'],rg['title'],e))
|
||||||
continue
|
continue
|
||||||
@@ -377,11 +383,12 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
# If there's no release in the main albums tables, add the default (hybrid)
|
# If there's no release in the main albums tables, add the default (hybrid)
|
||||||
# If there is a release, check the ReleaseID against the AlbumID to see if they differ (user updated)
|
# If there is a release, check the ReleaseID against the AlbumID to see if they differ (user updated)
|
||||||
# check if the album already exists
|
# check if the album already exists
|
||||||
rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone()
|
|
||||||
if not rg_exists:
|
if not rg_exists:
|
||||||
releaseid = rg['id']
|
releaseid = rg['id']
|
||||||
else:
|
else:
|
||||||
releaseid = rg_exists['ReleaseID']
|
releaseid = rg_exists['ReleaseID']
|
||||||
|
if not releaseid:
|
||||||
|
releaseid = rg['id']
|
||||||
|
|
||||||
album = myDB.action('SELECT * from allalbums WHERE ReleaseID=?', [releaseid]).fetchone()
|
album = myDB.action('SELECT * from allalbums WHERE ReleaseID=?', [releaseid]).fetchone()
|
||||||
|
|
||||||
@@ -398,11 +405,14 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
"ReleaseFormat": album['ReleaseFormat']
|
"ReleaseFormat": album['ReleaseFormat']
|
||||||
}
|
}
|
||||||
|
|
||||||
if not rg_exists:
|
if rg_exists:
|
||||||
|
newValueDict['DateAdded'] = rg_exists['DateAdded']
|
||||||
|
newValueDict['Status'] = rg_exists['Status']
|
||||||
|
|
||||||
|
else:
|
||||||
today = helpers.today()
|
today = helpers.today()
|
||||||
|
|
||||||
newValueDict['DateAdded']= today
|
newValueDict['DateAdded'] = today
|
||||||
|
|
||||||
if headphones.AUTOWANT_ALL:
|
if headphones.AUTOWANT_ALL:
|
||||||
newValueDict['Status'] = "Wanted"
|
newValueDict['Status'] = "Wanted"
|
||||||
@@ -419,11 +429,17 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
|
|
||||||
tracks = myDB.action('SELECT * from alltracks WHERE ReleaseID=?', [releaseid]).fetchall()
|
tracks = myDB.action('SELECT * from alltracks WHERE ReleaseID=?', [releaseid]).fetchall()
|
||||||
|
|
||||||
# This is used to see how many tracks you have from an album - to mark it as downloaded. Default is 80%, can be set in config as ALBUM_COMPLETION_PCT
|
# This is used to see how many tracks you have from an album - to
|
||||||
|
# mark it as downloaded. Default is 80%, can be set in config as
|
||||||
|
# ALBUM_COMPLETION_PCT
|
||||||
total_track_count = len(tracks)
|
total_track_count = len(tracks)
|
||||||
|
|
||||||
for track in tracks:
|
if total_track_count == 0:
|
||||||
|
logger.warning("Total track count is zero for Release ID " +
|
||||||
|
"'%s', skipping.", releaseid)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for track in tracks:
|
||||||
controlValueDict = {"TrackID": track['TrackID'],
|
controlValueDict = {"TrackID": track['TrackID'],
|
||||||
"AlbumID": rg['id']}
|
"AlbumID": rg['id']}
|
||||||
|
|
||||||
@@ -459,18 +475,44 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
logger.info(u"[%s] Seeing if we need album art for %s" % (artist['artist_name'], rg['title']))
|
logger.info(u"[%s] Seeing if we need album art for %s" % (artist['artist_name'], rg['title']))
|
||||||
cache.getThumb(AlbumID=rg['id'])
|
cache.getThumb(AlbumID=rg['id'])
|
||||||
|
|
||||||
#start a search for the album if it's new, hasn't been marked as downloaded and autowant_all is selected:
|
# Start a search for the album if it's new, hasn't been marked as
|
||||||
|
# downloaded and autowant_all is selected. This search is deferred,
|
||||||
|
# in case the search failes and the rest of the import will halt.
|
||||||
if not rg_exists and not marked_as_downloaded and headphones.AUTOWANT_ALL:
|
if not rg_exists and not marked_as_downloaded and headphones.AUTOWANT_ALL:
|
||||||
from headphones import searcher
|
album_searches.append(rg['id'])
|
||||||
searcher.searchforalbum(albumid=rg['id'])
|
|
||||||
else:
|
else:
|
||||||
if skip_log == 0:
|
if skip_log == 0:
|
||||||
logger.info(u"[%s] No new releases, so no changes made to %s" % (artist['artist_name'], rg['title']))
|
logger.info(u"[%s] No new releases, so no changes made to %s" % (artist['artist_name'], rg['title']))
|
||||||
|
|
||||||
|
time.sleep(3)
|
||||||
|
finalize_update(artistid, artist['artist_name'], errors)
|
||||||
|
|
||||||
|
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
||||||
|
cache.getThumb(ArtistID=artistid)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
logger.info("[%s] Finished updating artist: %s but with errors, so not marking it as updated in the database" % (artist['artist_name'], artist['artist_name']))
|
||||||
|
else:
|
||||||
|
myDB.action('DELETE FROM newartists WHERE ArtistName = ?', [artist['artist_name']])
|
||||||
|
logger.info(u"Updating complete for: %s" % artist['artist_name'])
|
||||||
|
|
||||||
|
# Start searching for newly added albums
|
||||||
|
if album_searches:
|
||||||
|
from headphones import searcher
|
||||||
|
logger.info("Start searching for %d albums.", len(album_searches))
|
||||||
|
|
||||||
|
for album_search in album_searches:
|
||||||
|
searcher.searchforalbum(albumid=album_search)
|
||||||
|
|
||||||
|
def finalize_update(artistid, artistname, errors=False):
|
||||||
|
# Moving this little bit to it's own function so we can update have tracks & latest album when deleting extras
|
||||||
|
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
|
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
|
||||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid]))
|
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [artistid]))
|
||||||
#havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']]))
|
#havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']]))
|
||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artist['artist_name']]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artistname]))
|
||||||
|
|
||||||
controlValueDict = {"ArtistID": artistid}
|
controlValueDict = {"ArtistID": artistid}
|
||||||
|
|
||||||
@@ -491,20 +533,23 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
|
|
||||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||||
|
|
||||||
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
def addReleaseById(rid, rgid=None):
|
||||||
cache.getThumb(ArtistID=artistid)
|
|
||||||
|
|
||||||
if errors:
|
|
||||||
logger.info("[%s] Finished updating artist: %s but with errors, so not marking it as updated in the database" % (artist['artist_name'], artist['artist_name']))
|
|
||||||
else:
|
|
||||||
myDB.action('DELETE FROM newartists WHERE ArtistName = ?', [artist['artist_name']])
|
|
||||||
logger.info(u"Updating complete for: %s" % artist['artist_name'])
|
|
||||||
|
|
||||||
|
|
||||||
def addReleaseById(rid):
|
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
|
# Create minimum info upfront if added from searchresults
|
||||||
|
status = ''
|
||||||
|
if rgid:
|
||||||
|
dbalbum = myDB.select("SELECT * from albums WHERE AlbumID=?", [rgid])
|
||||||
|
if not dbalbum:
|
||||||
|
status = 'Loading'
|
||||||
|
controlValueDict = {"AlbumID": rgid}
|
||||||
|
newValueDict = {"AlbumTitle": rgid,
|
||||||
|
"ArtistName": status,
|
||||||
|
"Status": status}
|
||||||
|
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
rgid = None
|
rgid = None
|
||||||
artistid = None
|
artistid = None
|
||||||
release_dict = None
|
release_dict = None
|
||||||
@@ -518,11 +563,15 @@ def addReleaseById(rid):
|
|||||||
logger.debug("Didn't find releaseID " + rid + " in the cache. Looking up its ReleaseGroupID")
|
logger.debug("Didn't find releaseID " + rid + " in the cache. Looking up its ReleaseGroupID")
|
||||||
try:
|
try:
|
||||||
release_dict = mb.getRelease(rid)
|
release_dict = mb.getRelease(rid)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.info('Unable to get release information for Release: ' + str(rid) + " " + str(e))
|
logger.info('Unable to get release information for Release %s: %s', rid, e)
|
||||||
|
if status == 'Loading':
|
||||||
|
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||||
return
|
return
|
||||||
if not release_dict:
|
if not release_dict:
|
||||||
logger.info('Unable to get release information for Release: ' + str(rid) + " no dict")
|
logger.info('Unable to get release information for Release %s: no dict', rid)
|
||||||
|
if status == 'Loading':
|
||||||
|
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||||
return
|
return
|
||||||
|
|
||||||
rgid = release_dict['rgid']
|
rgid = release_dict['rgid']
|
||||||
@@ -540,7 +589,6 @@ def addReleaseById(rid):
|
|||||||
else:
|
else:
|
||||||
sortname = release_dict['artist_name']
|
sortname = release_dict['artist_name']
|
||||||
|
|
||||||
|
|
||||||
logger.info(u"Now manually adding: " + release_dict['artist_name'] + " - with status Paused")
|
logger.info(u"Now manually adding: " + release_dict['artist_name'] + " - with status Paused")
|
||||||
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
||||||
newValueDict = {"ArtistName": release_dict['artist_name'],
|
newValueDict = {"ArtistName": release_dict['artist_name'],
|
||||||
@@ -556,21 +604,27 @@ def addReleaseById(rid):
|
|||||||
|
|
||||||
elif not artist_exists and not release_dict:
|
elif not artist_exists and not release_dict:
|
||||||
logger.error("Artist does not exist in the database and did not get a valid response from MB. Skipping release.")
|
logger.error("Artist does not exist in the database and did not get a valid response from MB. Skipping release.")
|
||||||
|
if status == 'Loading':
|
||||||
|
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||||
return
|
return
|
||||||
|
|
||||||
if not rg_exists and release_dict: #it should never be the case that we have an rg and not the artist
|
if not rg_exists and release_dict or status == 'Loading' and release_dict: #it should never be the case that we have an rg and not the artist
|
||||||
#but if it is this will fail
|
#but if it is this will fail
|
||||||
logger.info(u"Now adding-by-id album (" + release_dict['title'] + ") from id: " + rgid)
|
logger.info(u"Now adding-by-id album (" + release_dict['title'] + ") from id: " + rgid)
|
||||||
controlValueDict = {"AlbumID": rgid}
|
controlValueDict = {"AlbumID": rgid}
|
||||||
|
if status != 'Loading':
|
||||||
|
status = 'Wanted'
|
||||||
|
|
||||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||||
|
"ReleaseID": rgid,
|
||||||
"ArtistName": release_dict['artist_name'],
|
"ArtistName": release_dict['artist_name'],
|
||||||
"AlbumTitle": release_dict['rg_title'],
|
"AlbumTitle": release_dict['rg_title'],
|
||||||
"AlbumASIN": release_dict['asin'],
|
"AlbumASIN": release_dict['asin'],
|
||||||
"ReleaseDate": release_dict['date'],
|
"ReleaseDate": release_dict['date'],
|
||||||
"DateAdded": helpers.today(),
|
"DateAdded": helpers.today(),
|
||||||
"Status": 'Wanted',
|
"Status": status,
|
||||||
"Type": release_dict['rg_type']
|
"Type": release_dict['rg_type'],
|
||||||
|
"ReleaseID": rid
|
||||||
}
|
}
|
||||||
|
|
||||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||||
@@ -579,7 +633,6 @@ def addReleaseById(rid):
|
|||||||
myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']])
|
myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']])
|
||||||
|
|
||||||
for track in release_dict['tracks']:
|
for track in release_dict['tracks']:
|
||||||
|
|
||||||
cleanname = helpers.cleanName(release_dict['artist_name'] + ' ' + release_dict['rg_title'] + ' ' + track['title'])
|
cleanname = helpers.cleanName(release_dict['artist_name'] + ' ' + release_dict['rg_title'] + ' ' + track['title'])
|
||||||
|
|
||||||
controlValueDict = {"TrackID": track['id'],
|
controlValueDict = {"TrackID": track['id'],
|
||||||
@@ -610,11 +663,19 @@ def addReleaseById(rid):
|
|||||||
|
|
||||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||||
|
|
||||||
#start a search for the album
|
# Reset status
|
||||||
|
if status == 'Loading':
|
||||||
|
controlValueDict = {"AlbumID": rgid}
|
||||||
|
newValueDict = {"Status": "Wanted"}
|
||||||
|
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||||
|
|
||||||
|
# Start a search for the album
|
||||||
import searcher
|
import searcher
|
||||||
searcher.searchforalbum(rgid, False)
|
searcher.searchforalbum(rgid, False)
|
||||||
elif not rg_exists and not release_dict:
|
elif not rg_exists and not release_dict:
|
||||||
logger.error("ReleaseGroup does not exist in the database and did not get a valid response from MB. Skipping release.")
|
logger.error("ReleaseGroup does not exist in the database and did not get a valid response from MB. Skipping release.")
|
||||||
|
if status == 'Loading':
|
||||||
|
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
logger.info('Release ' + str(rid) + " already exists in the database!")
|
logger.info('Release ' + str(rid) + " already exists in the database!")
|
||||||
@@ -650,39 +711,43 @@ def updateFormat():
|
|||||||
|
|
||||||
def getHybridRelease(fullreleaselist):
|
def getHybridRelease(fullreleaselist):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of best group of tracks from the list of releases & earliest release date
|
Returns a dictionary of best group of tracks from the list of releases and
|
||||||
|
earliest release date
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if len(fullreleaselist) == 0:
|
if len(fullreleaselist) == 0:
|
||||||
raise Exception("getHybridRelease was called with an empty fullreleaselist")
|
raise ValueError("Empty fullreleaselist")
|
||||||
|
|
||||||
sortable_release_list = []
|
sortable_release_list = []
|
||||||
|
|
||||||
|
formats = {
|
||||||
|
'2xVinyl': '2',
|
||||||
|
'Vinyl': '2',
|
||||||
|
'CD': '0',
|
||||||
|
'Cassette': '3',
|
||||||
|
'2xCD': '1',
|
||||||
|
'Digital Media': '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
countries = {
|
||||||
|
'US': '0',
|
||||||
|
'GB': '1',
|
||||||
|
'JP': '2',
|
||||||
|
}
|
||||||
|
|
||||||
for release in fullreleaselist:
|
for release in fullreleaselist:
|
||||||
|
# Find values for format and country
|
||||||
formats = {
|
|
||||||
'2xVinyl': '2',
|
|
||||||
'Vinyl': '2',
|
|
||||||
'CD': '0',
|
|
||||||
'Cassette': '3',
|
|
||||||
'2xCD': '1',
|
|
||||||
'Digital Media': '0'
|
|
||||||
}
|
|
||||||
|
|
||||||
countries = {
|
|
||||||
'US': '0',
|
|
||||||
'GB': '1',
|
|
||||||
'JP': '2',
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
format = int(formats[release['Format']])
|
format = int(formats[release['Format']])
|
||||||
except:
|
except (ValueError, KeyError):
|
||||||
format = 3
|
format = 3
|
||||||
|
|
||||||
try:
|
try:
|
||||||
country = int(countries[release['Country']])
|
country = int(countries[release['Country']])
|
||||||
except:
|
except (ValueError, KeyError):
|
||||||
country = 3
|
country = 3
|
||||||
|
|
||||||
|
# Create record
|
||||||
release_dict = {
|
release_dict = {
|
||||||
'hasasin': bool(release['AlbumASIN']),
|
'hasasin': bool(release['AlbumASIN']),
|
||||||
'asin': release['AlbumASIN'],
|
'asin': release['AlbumASIN'],
|
||||||
@@ -692,15 +757,19 @@ def getHybridRelease(fullreleaselist):
|
|||||||
'format': format,
|
'format': format,
|
||||||
'country': country,
|
'country': country,
|
||||||
'tracks': release['Tracks']
|
'tracks': release['Tracks']
|
||||||
}
|
}
|
||||||
|
|
||||||
sortable_release_list.append(release_dict)
|
sortable_release_list.append(release_dict)
|
||||||
|
|
||||||
#necessary to make dates that miss the month and/or day show up after full dates
|
# Necessary to make dates that miss the month and/or day show up after full
|
||||||
|
# dates
|
||||||
def getSortableReleaseDate(releaseDate):
|
def getSortableReleaseDate(releaseDate):
|
||||||
|
# Change this value to change the sorting behaviour of none, returning
|
||||||
|
# 'None' will put it at the top which was normal behaviour for pre-ngs
|
||||||
|
# versions
|
||||||
if releaseDate == None:
|
if releaseDate == None:
|
||||||
return 'None';#change this value to change the sorting behaviour of none, returning 'None' will put it at the top
|
return 'None';
|
||||||
#which was normal behaviour for pre-ngs versions
|
|
||||||
if releaseDate.count('-') == 2:
|
if releaseDate.count('-') == 2:
|
||||||
return releaseDate
|
return releaseDate
|
||||||
elif releaseDate.count('-') == 1:
|
elif releaseDate.count('-') == 1:
|
||||||
|
|||||||
@@ -13,237 +13,144 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import urllib, urllib2
|
|
||||||
from xml.dom import minidom
|
|
||||||
from collections import defaultdict
|
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
from headphones import db, logger
|
|
||||||
|
|
||||||
api_key = '395e6ec6bb557382fc41fde867bce66f'
|
from headphones import db, logger, request
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
TIMEOUT = 60 # seconds
|
||||||
|
ENTRY_POINT = "http://ws.audioscrobbler.com/2.0/"
|
||||||
|
API_KEY = "395e6ec6bb557382fc41fde867bce66f"
|
||||||
|
|
||||||
|
def request_lastfm(method, **kwargs):
|
||||||
|
"""
|
||||||
|
Call a Last.FM API method. Automatically sets the method and API key. Method
|
||||||
|
will return the result if no error occured.
|
||||||
|
|
||||||
|
By default, this method will request the JSON format, since it is more
|
||||||
|
lightweight than XML.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Prepare request
|
||||||
|
kwargs["method"] = method
|
||||||
|
kwargs.setdefault("api_key", API_KEY)
|
||||||
|
kwargs.setdefault("format", "json")
|
||||||
|
|
||||||
|
# Send request
|
||||||
|
logger.debug("Calling Last.FM method: %s", method)
|
||||||
|
logger.debug("Last.FM call parameters: %s", kwargs)
|
||||||
|
|
||||||
|
data = request.request_json(ENTRY_POINT, timeout=TIMEOUT, params=kwargs)
|
||||||
|
|
||||||
|
# Parse response and check for errors.
|
||||||
|
if not data:
|
||||||
|
logger.error("Error calling Last.FM method: %s", method)
|
||||||
|
return
|
||||||
|
|
||||||
|
if "error" in data:
|
||||||
|
logger.error("Last.FM returned an error: %s", data["message"])
|
||||||
|
return
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
def getSimilar():
|
def getSimilar():
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
results = myDB.select('SELECT ArtistID from artists ORDER BY HaveTracks DESC')
|
results = myDB.select("SELECT ArtistID from artists ORDER BY HaveTracks DESC")
|
||||||
|
|
||||||
|
logger.info("Fetching similar artists from Last.FM for tag cloud")
|
||||||
artistlist = []
|
artistlist = []
|
||||||
|
|
||||||
for result in results[:12]:
|
for result in results[:12]:
|
||||||
|
data = request_lastfm("artist.getsimilar", mbid=result["ArtistId"])
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
url = 'http://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&mbid=%s&api_key=%s' % (result['ArtistID'], api_key)
|
if data and "similarartists" in data:
|
||||||
|
artists = data["similarartists"]["artist"]
|
||||||
|
|
||||||
try:
|
for artist in artists:
|
||||||
data = urllib2.urlopen(url, timeout=20).read()
|
try:
|
||||||
except:
|
artist_mbid = artist["mbid"]
|
||||||
time.sleep(1)
|
artist_name = artist["name"]
|
||||||
continue
|
except TypeError:
|
||||||
|
continue
|
||||||
|
|
||||||
if not data or len(data) < 200:
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
d = minidom.parseString(data)
|
|
||||||
except:
|
|
||||||
logger.debug("Could not parse similar artist data from last.fm")
|
|
||||||
|
|
||||||
node = d.documentElement
|
|
||||||
artists = d.getElementsByTagName("artist")
|
|
||||||
|
|
||||||
for artist in artists:
|
|
||||||
namenode = artist.getElementsByTagName("name")[0].childNodes
|
|
||||||
mbidnode = artist.getElementsByTagName("mbid")[0].childNodes
|
|
||||||
|
|
||||||
for node in namenode:
|
|
||||||
artist_name = node.data
|
|
||||||
for node in mbidnode:
|
|
||||||
artist_mbid = node.data
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not any(artist_mbid in x for x in results):
|
if not any(artist_mbid in x for x in results):
|
||||||
artistlist.append((artist_name, artist_mbid))
|
artistlist.append((artist_name, artist_mbid))
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
# Add new artists to tag cloud
|
||||||
|
logger.debug("Fetched %d artists from Last.FM", len(artistlist))
|
||||||
count = defaultdict(int)
|
count = defaultdict(int)
|
||||||
|
|
||||||
for artist, mbid in artistlist:
|
for artist, mbid in artistlist:
|
||||||
count[artist, mbid] += 1
|
count[artist, mbid] += 1
|
||||||
|
|
||||||
items = count.items()
|
items = count.items()
|
||||||
|
|
||||||
top_list = sorted(items, key=lambda x: x[1], reverse=True)[:25]
|
top_list = sorted(items, key=lambda x: x[1], reverse=True)[:25]
|
||||||
|
|
||||||
random.shuffle(top_list)
|
random.shuffle(top_list)
|
||||||
|
|
||||||
myDB.action('''DELETE from lastfmcloud''')
|
myDB.action("DELETE from lastfmcloud")
|
||||||
for tuple in top_list:
|
for item in top_list:
|
||||||
artist_name, artist_mbid = tuple[0]
|
artist_name, artist_mbid = item[0]
|
||||||
count = tuple[1]
|
count = item[1]
|
||||||
myDB.action('INSERT INTO lastfmcloud VALUES( ?, ?, ?)', [artist_name, artist_mbid, count])
|
|
||||||
|
myDB.action("INSERT INTO lastfmcloud VALUES( ?, ?, ?)", [artist_name, artist_mbid, count])
|
||||||
|
|
||||||
|
logger.debug("Inserted %d artists into Last.FM tag cloud", len(top_list))
|
||||||
|
|
||||||
def getArtists():
|
def getArtists():
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
results = myDB.select('SELECT ArtistID from artists')
|
results = myDB.select("SELECT ArtistID from artists")
|
||||||
|
|
||||||
if not headphones.LASTFM_USERNAME:
|
if not headphones.LASTFM_USERNAME:
|
||||||
|
logger.warn("Last.FM username not set, not importing artists.")
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
logger.info("Fetching artists from Last.FM for username: %s", headphones.LASTFM_USERNAME)
|
||||||
username = headphones.LASTFM_USERNAME
|
data = request_lastfm("library.getartists", limit=10000, user=headphones.LASTFM_USERNAME)
|
||||||
|
|
||||||
url = 'http://ws.audioscrobbler.com/2.0/?method=library.getartists&limit=10000&api_key=%s&user=%s' % (api_key, username)
|
if data and "artists" in data:
|
||||||
data = urllib2.urlopen(url, timeout=20).read()
|
artistlist = []
|
||||||
|
artists = data["artists"]["artist"]
|
||||||
|
logger.debug("Fetched %d artists from Last.FM", len(artists))
|
||||||
|
|
||||||
try:
|
for artist in artists:
|
||||||
d = minidom.parseString(data)
|
artist_mbid = artist["mbid"]
|
||||||
except:
|
|
||||||
logger.error("Could not parse artist list from last.fm data")
|
|
||||||
return
|
|
||||||
|
|
||||||
artists = d.getElementsByTagName("artist")
|
|
||||||
|
|
||||||
artistlist = []
|
|
||||||
|
|
||||||
for artist in artists:
|
|
||||||
mbidnode = artist.getElementsByTagName("mbid")[0].childNodes
|
|
||||||
|
|
||||||
for node in mbidnode:
|
|
||||||
artist_mbid = node.data
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not any(artist_mbid in x for x in results):
|
if not any(artist_mbid in x for x in results):
|
||||||
artistlist.append(artist_mbid)
|
artistlist.append(artist_mbid)
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
from headphones import importer
|
from headphones import importer
|
||||||
|
|
||||||
for artistid in artistlist:
|
for artistid in artistlist:
|
||||||
importer.addArtisttoDB(artistid)
|
importer.addArtisttoDB(artistid)
|
||||||
|
|
||||||
|
logger.info("Imported %d new artists from Last.FM", len(artistlist))
|
||||||
|
|
||||||
def getTagTopArtists(tag, limit=50):
|
def getTagTopArtists(tag, limit=50):
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
results = myDB.select('SELECT ArtistID from artists')
|
results = myDB.select("SELECT ArtistID from artists")
|
||||||
|
|
||||||
url = 'http://ws.audioscrobbler.com/2.0/?method=tag.gettopartists&limit=%s&tag=%s&api_key=%s' % (limit, tag, api_key)
|
logger.info("Fetching top artists from Last.FM for tag: %s", tag)
|
||||||
data = urllib2.urlopen(url, timeout=20).read()
|
data = request_lastfm("tag.gettopartists", limit=limit, tag=tag)
|
||||||
|
|
||||||
try:
|
if data and "topartists" in data:
|
||||||
d = minidom.parseString(data)
|
artistlist = []
|
||||||
except:
|
artists = data["topartists"]["artist"]
|
||||||
logger.error("Could not parse artist list from last.fm data")
|
logger.debug("Fetched %d artists from Last.FM", len(artists))
|
||||||
return
|
|
||||||
|
|
||||||
artists = d.getElementsByTagName("artist")
|
for artist in artists:
|
||||||
|
artist_mbid = artist["mbid"]
|
||||||
|
|
||||||
artistlist = []
|
|
||||||
|
|
||||||
for artist in artists:
|
|
||||||
mbidnode = artist.getElementsByTagName("mbid")[0].childNodes
|
|
||||||
|
|
||||||
for node in mbidnode:
|
|
||||||
artist_mbid = node.data
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not any(artist_mbid in x for x in results):
|
if not any(artist_mbid in x for x in results):
|
||||||
artistlist.append(artist_mbid)
|
artistlist.append(artist_mbid)
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
from headphones import importer
|
from headphones import importer
|
||||||
|
|
||||||
for artistid in artistlist:
|
|
||||||
importer.addArtisttoDB(artistid)
|
|
||||||
|
|
||||||
|
|
||||||
def getAlbumDescription(rgid, artist, album):
|
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
|
||||||
result = myDB.select('SELECT Summary from descriptions WHERE ReleaseGroupID=?', [rgid])
|
|
||||||
|
|
||||||
if result:
|
|
||||||
return
|
|
||||||
|
|
||||||
params = { "method": 'album.getInfo',
|
|
||||||
"api_key": api_key,
|
|
||||||
"artist": artist.encode('utf-8'),
|
|
||||||
"album": album.encode('utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
searchURL = 'http://ws.audioscrobbler.com/2.0/?' + urllib.urlencode(params)
|
|
||||||
data = urllib2.urlopen(searchURL, timeout=20).read()
|
|
||||||
|
|
||||||
if data == '<?xml version="1.0" encoding="utf-8"?><lfm status="failed"><error code="6">Album not found</error></lfm>':
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
d = minidom.parseString(data)
|
|
||||||
|
|
||||||
albuminfo = d.getElementsByTagName("album")
|
|
||||||
|
|
||||||
for item in albuminfo:
|
|
||||||
summarynode = item.getElementsByTagName("summary")[0].childNodes
|
|
||||||
contentnode = item.getElementsByTagName("content")[0].childNodes
|
|
||||||
for node in summarynode:
|
|
||||||
summary = node.data
|
|
||||||
for node in contentnode:
|
|
||||||
content = node.data
|
|
||||||
|
|
||||||
controlValueDict = {'ReleaseGroupID': rgid}
|
|
||||||
newValueDict = {'Summary': summary,
|
|
||||||
'Content': content}
|
|
||||||
myDB.upsert("descriptions", newValueDict, controlValueDict)
|
|
||||||
|
|
||||||
except:
|
|
||||||
return
|
|
||||||
|
|
||||||
def getAlbumDescriptionOld(rgid, releaselist):
|
|
||||||
"""
|
|
||||||
This was a dumb way to do it - going to just use artist & album name but keeping this here
|
|
||||||
because I may use it to fetch and cache album art
|
|
||||||
"""
|
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
|
||||||
result = myDB.select('SELECT Summary from descriptions WHERE ReleaseGroupID=?', [rgid])
|
|
||||||
|
|
||||||
if result:
|
|
||||||
return
|
|
||||||
|
|
||||||
for release in releaselist:
|
|
||||||
|
|
||||||
mbid = release['releaseid']
|
|
||||||
url = 'http://ws.audioscrobbler.com/2.0/?method=album.getInfo&mbid=%s&api_key=%s' % (mbid, api_key)
|
|
||||||
data = urllib.urlopen(url).read()
|
|
||||||
|
|
||||||
if data == '<?xml version="1.0" encoding="utf-8"?><lfm status="failed"><error code="6">Album not found</error></lfm>':
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
d = minidom.parseString(data)
|
|
||||||
|
|
||||||
albuminfo = d.getElementsByTagName("album")
|
|
||||||
|
|
||||||
for item in albuminfo:
|
|
||||||
summarynode = item.getElementsByTagName("summary")[0].childNodes
|
|
||||||
contentnode = item.getElementsByTagName("content")[0].childNodes
|
|
||||||
for node in summarynode:
|
|
||||||
summary = node.data
|
|
||||||
for node in contentnode:
|
|
||||||
content = node.data
|
|
||||||
|
|
||||||
controlValueDict = {'ReleaseGroupID': rgid}
|
|
||||||
newValueDict = {'ReleaseID': mbid,
|
|
||||||
'Summary': summary,
|
|
||||||
'Content': content}
|
|
||||||
myDB.upsert("descriptions", newValueDict, controlValueDict)
|
|
||||||
break
|
|
||||||
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
|
for artistid in artistlist:
|
||||||
|
importer.addArtisttoDB(artistid)
|
||||||
|
|
||||||
|
logger.debug("Added %d new artists from Last.FM", len(artistlist))
|
||||||
@@ -15,11 +15,11 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
from lib.beets.mediafile import MediaFile
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
from headphones import db, logger, helpers, importer
|
|
||||||
|
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||||
|
|
||||||
|
from headphones import db, logger, helpers, importer, lastfm
|
||||||
|
|
||||||
# You can scan a single directory and append it to the current library by specifying append=True, ArtistID & ArtistName
|
# You can scan a single directory and append it to the current library by specifying append=True, ArtistID & ArtistName
|
||||||
def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
|
def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
|
||||||
@@ -50,7 +50,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
|
|
||||||
if not append:
|
if not append:
|
||||||
# Clean up bad filepaths
|
# Clean up bad filepaths
|
||||||
tracks = myDB.select('SELECT Location, TrackID from alltracks WHERE Location IS NOT NULL')
|
tracks = myDB.select('SELECT Location from alltracks WHERE Location IS NOT NULL UNION SELECT Location from tracks WHERE Location IS NOT NULL')
|
||||||
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING)
|
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING)
|
||||||
@@ -61,7 +61,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
del_have_tracks = myDB.select('SELECT Location, Matched, ArtistName from have')
|
del_have_tracks = myDB.select('SELECT Location, Matched, ArtistName from have')
|
||||||
|
|
||||||
for track in del_have_tracks:
|
for track in del_have_tracks:
|
||||||
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING)
|
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING, 'replace')
|
||||||
if not os.path.isfile(encoded_track_string):
|
if not os.path.isfile(encoded_track_string):
|
||||||
if track['ArtistName']:
|
if track['ArtistName']:
|
||||||
#Make sure deleted files get accounted for when updating artist track counts
|
#Make sure deleted files get accounted for when updating artist track counts
|
||||||
@@ -84,8 +84,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
for directory in d[:]:
|
for directory in d[:]:
|
||||||
if directory.startswith("."):
|
if directory.startswith("."):
|
||||||
d.remove(directory)
|
d.remove(directory)
|
||||||
for files in f:
|
|
||||||
|
|
||||||
|
for files in f:
|
||||||
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
||||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||||
|
|
||||||
@@ -104,9 +104,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
# Try to read the metadata
|
# Try to read the metadata
|
||||||
try:
|
try:
|
||||||
f = MediaFile(song)
|
f = MediaFile(song)
|
||||||
|
except (FileTypeError, UnreadableFileError):
|
||||||
except:
|
logger.error("Cannot read file media file '%s'. It may be corrupted or not a media file.", unicode_song_path)
|
||||||
logger.error('Cannot read file: ' + unicode_song_path)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Grab the bitrates for the auto detect bit rate option
|
# Grab the bitrates for the auto detect bit rate option
|
||||||
@@ -216,6 +215,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
if song['ArtistName'] and song['AlbumTitle'] and song['TrackTitle']:
|
if song['ArtistName'] and song['AlbumTitle'] and song['TrackTitle']:
|
||||||
|
|
||||||
track = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
track = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
||||||
|
have_updated = False
|
||||||
if track:
|
if track:
|
||||||
controlValueDict = { 'ArtistName' : track['ArtistName'],
|
controlValueDict = { 'ArtistName' : track['ArtistName'],
|
||||||
'AlbumTitle' : track['AlbumTitle'],
|
'AlbumTitle' : track['AlbumTitle'],
|
||||||
@@ -228,6 +228,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
have_updated = True
|
||||||
else:
|
else:
|
||||||
track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
||||||
if track:
|
if track:
|
||||||
@@ -240,11 +241,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
have_updated = True
|
||||||
else:
|
else:
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : "Failed"}
|
newValueDict2 = { 'Matched' : "Failed"}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
have_updated = True
|
||||||
|
|
||||||
alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
||||||
if alltrack:
|
if alltrack:
|
||||||
@@ -272,9 +274,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
else:
|
else:
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
# alltracks may not exist if adding album manually, have should only be set to failed if not already updated in tracks
|
||||||
newValueDict2 = { 'Matched' : "Failed"}
|
if not have_updated:
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
|
newValueDict2 = { 'Matched' : "Failed"}
|
||||||
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : "Failed"}
|
newValueDict2 = { 'Matched' : "Failed"}
|
||||||
@@ -328,10 +333,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [ArtistID])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [ArtistName]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [ArtistID])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [ArtistName]))
|
||||||
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, ArtistID])
|
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, ArtistID])
|
||||||
|
|
||||||
update_album_status()
|
if not append:
|
||||||
|
update_album_status()
|
||||||
|
lastfm.getSimilar()
|
||||||
logger.info('Library scan complete')
|
logger.info('Library scan complete')
|
||||||
|
|
||||||
#ADDED THIS SECTION TO MARK ALBUMS AS DOWNLOADED IF ARTISTS ARE ADDED EN MASSE BEFORE LIBRARY IS SCANNED
|
#ADDED THIS SECTION TO MARK ALBUMS AS DOWNLOADED IF ARTISTS ARE ADDED EN MASSE BEFORE LIBRARY IS SCANNED
|
||||||
def update_album_status(AlbumID=None):
|
def update_album_status(AlbumID=None):
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
logger.info('Counting matched tracks to mark albums as skipped/downloaded')
|
logger.info('Counting matched tracks to mark albums as skipped/downloaded')
|
||||||
@@ -353,13 +360,21 @@ def update_album_status(AlbumID=None):
|
|||||||
album_completion = 0
|
album_completion = 0
|
||||||
logger.info('Album %s does not have any tracks in database' % album['AlbumTitle'])
|
logger.info('Album %s does not have any tracks in database' % album['AlbumTitle'])
|
||||||
|
|
||||||
if album_completion >= headphones.ALBUM_COMPLETION_PCT:
|
if album_completion >= headphones.ALBUM_COMPLETION_PCT and album['Status'] == 'Skipped':
|
||||||
new_album_status = "Downloaded"
|
new_album_status = "Downloaded"
|
||||||
|
|
||||||
|
# I don't think we want to change Downloaded->Skipped.....
|
||||||
|
# I think we can only automatically change Skipped->Downloaded when updating
|
||||||
|
# There was a bug report where this was causing infinite downloads if the album was
|
||||||
|
# recent, but matched to less than 80%. It would go Downloaded->Skipped->Wanted->Downloaded->Skipped->Wanted->etc....
|
||||||
|
#else:
|
||||||
|
# if album['Status'] == "Skipped" or album['Status'] == "Downloaded":
|
||||||
|
# new_album_status = "Skipped"
|
||||||
|
# else:
|
||||||
|
# new_album_status = album['Status']
|
||||||
else:
|
else:
|
||||||
if album['Status'] == "Skipped" or album['Status'] == "Downloaded":
|
new_album_status = album['Status']
|
||||||
new_album_status = "Skipped"
|
|
||||||
else:
|
|
||||||
new_album_status = album['Status']
|
|
||||||
myDB.upsert("albums", {'Status' : new_album_status}, {'AlbumID' : album['AlbumID']})
|
myDB.upsert("albums", {'Status' : new_album_status}, {'AlbumID' : album['AlbumID']})
|
||||||
if new_album_status != album['Status']:
|
if new_album_status != album['Status']:
|
||||||
logger.info('Album %s changed to %s' % (album['AlbumTitle'], new_album_status))
|
logger.info('Album %s changed to %s' % (album['AlbumTitle'], new_album_status))
|
||||||
|
|||||||
@@ -13,84 +13,182 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import os
|
|
||||||
import threading
|
|
||||||
import logging
|
|
||||||
from logging import handlers
|
|
||||||
|
|
||||||
import headphones
|
|
||||||
from headphones import helpers
|
from headphones import helpers
|
||||||
|
|
||||||
MAX_SIZE = 1000000 # 1mb
|
from logutils.queue import QueueHandler, QueueListener
|
||||||
|
from logging import handlers
|
||||||
|
|
||||||
|
import multiprocessing
|
||||||
|
import contextlib
|
||||||
|
import headphones
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# These settings are for file logging only
|
||||||
|
FILENAME = "headphones.log"
|
||||||
|
MAX_SIZE = 1000000 # 1 MB
|
||||||
MAX_FILES = 5
|
MAX_FILES = 5
|
||||||
|
|
||||||
|
# Headphones logger
|
||||||
|
logger = logging.getLogger("headphones")
|
||||||
|
|
||||||
# Simple rotating log handler that uses RotatingFileHandler
|
# Global queue of multiprocessing logging
|
||||||
class RotatingLogger(object):
|
queue = multiprocessing.Queue()
|
||||||
|
|
||||||
def __init__(self, filename, max_size, max_files):
|
class LogListHandler(logging.Handler):
|
||||||
|
"""
|
||||||
|
Log handler for Web UI.
|
||||||
|
"""
|
||||||
|
|
||||||
self.filename = filename
|
def emit(self, record):
|
||||||
self.max_size = max_size
|
message = self.format(record)
|
||||||
self.max_files = max_files
|
message = message.replace("\n", "<br />")
|
||||||
|
|
||||||
|
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
||||||
|
|
||||||
def initLogger(self, verbose=1):
|
@contextlib.contextmanager
|
||||||
|
def listener():
|
||||||
|
"""
|
||||||
|
Wrapper that create a QueueListener, starts it and automatically stops it.
|
||||||
|
To be used in a with statement in the main process, for multiprocessing.
|
||||||
|
"""
|
||||||
|
|
||||||
l = logging.getLogger('headphones')
|
queue_listener = QueueListener(queue, *logger.handlers)
|
||||||
l.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
self.filename = os.path.join(headphones.LOG_DIR, self.filename)
|
try:
|
||||||
|
queue_listener.start()
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
queue_listener.stop()
|
||||||
|
|
||||||
filehandler = handlers.RotatingFileHandler(self.filename, maxBytes=self.max_size, backupCount=self.max_files)
|
def initMultiprocessing():
|
||||||
filehandler.setLevel(logging.DEBUG)
|
"""
|
||||||
|
Remove all handlers and add QueueHandler on top. This should only be called
|
||||||
|
inside a multiprocessing worker process, since it changes the logger
|
||||||
|
completely.
|
||||||
|
"""
|
||||||
|
|
||||||
fileformatter = logging.Formatter('%(asctime)s - %(levelname)-7s :: %(message)s', '%d-%b-%Y %H:%M:%S')
|
for handler in logger.handlers[:]:
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
|
||||||
filehandler.setFormatter(fileformatter)
|
queue_handler = QueueHandler(queue)
|
||||||
l.addHandler(filehandler)
|
queue_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
if verbose:
|
logger.addHandler(queue_handler)
|
||||||
consolehandler = logging.StreamHandler()
|
|
||||||
if verbose == 1:
|
|
||||||
consolehandler.setLevel(logging.INFO)
|
|
||||||
if verbose == 2:
|
|
||||||
consolehandler.setLevel(logging.DEBUG)
|
|
||||||
consoleformatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(message)s', '%d-%b-%Y %H:%M:%S')
|
|
||||||
consolehandler.setFormatter(consoleformatter)
|
|
||||||
l.addHandler(consolehandler)
|
|
||||||
|
|
||||||
def log(self, message, level):
|
# Change current thread name for log record
|
||||||
|
threading.current_thread().name = multiprocessing.current_process().name
|
||||||
|
|
||||||
logger = logging.getLogger('headphones')
|
def initLogger(console=False, verbose=False):
|
||||||
|
"""
|
||||||
|
Setup logging for Headphones. It uses the logger instance with the name
|
||||||
|
'headphones'. Three log handlers are added:
|
||||||
|
|
||||||
threadname = threading.currentThread().getName()
|
* RotatingFileHandler: for the file headphones.log
|
||||||
|
* LogListHandler: for Web UI
|
||||||
|
* StreamHandler: for console (if console)
|
||||||
|
|
||||||
if level != 'DEBUG':
|
Console logging is only enabled if console is set to True.
|
||||||
headphones.LOG_LIST.insert(0, (helpers.now(), message, level, threadname))
|
"""
|
||||||
|
|
||||||
message = threadname + ' : ' + message
|
# Close and remove old handlers. This is required to reinit the loggers
|
||||||
|
# at runtime
|
||||||
|
for handler in logger.handlers[:]:
|
||||||
|
# Just make sure it is cleaned up.
|
||||||
|
if isinstance(handler, handlers.RotatingFileHandler):
|
||||||
|
handler.close()
|
||||||
|
elif isinstance(handler, logging.StreamHandler):
|
||||||
|
handler.flush()
|
||||||
|
|
||||||
if level == 'DEBUG':
|
logger.removeHandler(handler)
|
||||||
logger.debug(message)
|
|
||||||
elif level == 'INFO':
|
|
||||||
logger.info(message)
|
|
||||||
elif level == 'WARNING':
|
|
||||||
logger.warn(message)
|
|
||||||
else:
|
|
||||||
logger.error(message)
|
|
||||||
|
|
||||||
headphones_log = RotatingLogger('headphones.log', MAX_SIZE, MAX_FILES)
|
# Configure the logger to accept all messages
|
||||||
|
logger.propagate = False
|
||||||
|
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
|
|
||||||
def debug(message):
|
# Setup file logger
|
||||||
headphones_log.log(message, level='DEBUG')
|
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
||||||
|
|
||||||
def info(message):
|
file_formatter = logging.Formatter('%(asctime)s - %(levelname)-7s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
||||||
headphones_log.log(message, level='INFO')
|
file_handler = handlers.RotatingFileHandler(filename, maxBytes=MAX_SIZE, backupCount=MAX_FILES)
|
||||||
|
file_handler.setLevel(logging.DEBUG)
|
||||||
|
file_handler.setFormatter(file_formatter)
|
||||||
|
|
||||||
def warn(message):
|
logger.addHandler(file_handler)
|
||||||
headphones_log.log(message, level='WARNING')
|
|
||||||
|
|
||||||
def error(message):
|
# Add list logger
|
||||||
headphones_log.log(message, level='ERROR')
|
loglist_handler = LogListHandler()
|
||||||
|
loglist_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
logger.addHandler(loglist_handler)
|
||||||
|
|
||||||
|
# Setup console logger
|
||||||
|
if console:
|
||||||
|
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(console_formatter)
|
||||||
|
console_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# Install exception hooks
|
||||||
|
initHooks()
|
||||||
|
|
||||||
|
def initHooks(global_exceptions=True, thread_exceptions=True, pass_original=True):
|
||||||
|
"""
|
||||||
|
This method installs exception catching mechanisms. Any exception caught
|
||||||
|
will pass through the exception hook, and will be logged to the logger as
|
||||||
|
an error. Additionally, a traceback is provided.
|
||||||
|
|
||||||
|
This is very useful for crashing threads and any other bugs, that may not
|
||||||
|
be exposed when running as daemon.
|
||||||
|
|
||||||
|
The default exception hook is still considered, if pass_original is True.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def excepthook(*exception_info):
|
||||||
|
# We should always catch this to prevent loops!
|
||||||
|
try:
|
||||||
|
message = "".join(traceback.format_exception(*exception_info))
|
||||||
|
logger.error("Uncaught exception: %s", message)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Original excepthook
|
||||||
|
if pass_original:
|
||||||
|
sys.__excepthook__(*exception_info)
|
||||||
|
|
||||||
|
# Global exception hook
|
||||||
|
if global_exceptions:
|
||||||
|
sys.excepthook = excepthook
|
||||||
|
|
||||||
|
# Thread exception hook
|
||||||
|
if thread_exceptions:
|
||||||
|
old_init = threading.Thread.__init__
|
||||||
|
|
||||||
|
def new_init(self, *args, **kwargs):
|
||||||
|
old_init(self, *args, **kwargs)
|
||||||
|
old_run = self.run
|
||||||
|
|
||||||
|
def new_run(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
old_run(*args, **kwargs)
|
||||||
|
except (KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
excepthook(*sys.exc_info())
|
||||||
|
self.run = new_run
|
||||||
|
|
||||||
|
# Monkey patch the run() by monkey patching the __init__ method
|
||||||
|
threading.Thread.__init__ = new_init
|
||||||
|
|
||||||
|
# Expose logger methods
|
||||||
|
info = logger.info
|
||||||
|
warn = logger.warn
|
||||||
|
error = logger.error
|
||||||
|
debug = logger.debug
|
||||||
|
warning = logger.warning
|
||||||
|
exception = logger.exception
|
||||||
@@ -14,11 +14,9 @@
|
|||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import urllib, urllib2
|
|
||||||
from xml.dom import minidom
|
|
||||||
import htmlentitydefs
|
import htmlentitydefs
|
||||||
|
|
||||||
from headphones import logger
|
from headphones import logger, request
|
||||||
|
|
||||||
def getLyrics(artist, song):
|
def getLyrics(artist, song):
|
||||||
|
|
||||||
@@ -27,21 +25,13 @@ def getLyrics(artist, song):
|
|||||||
"fmt": 'xml'
|
"fmt": 'xml'
|
||||||
}
|
}
|
||||||
|
|
||||||
searchURL = 'http://lyrics.wikia.com/api.php?' + urllib.urlencode(params)
|
url = 'http://lyrics.wikia.com/api.php'
|
||||||
|
data = request.request_minidom(url, params=params)
|
||||||
|
|
||||||
try:
|
if not data:
|
||||||
data = urllib2.urlopen(searchURL, timeout=20).read()
|
|
||||||
except Exception, e:
|
|
||||||
logger.warn('Error opening: %s. Error: %s' % (searchURL, e))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
url = data.getElementsByTagName("url")
|
||||||
parseddata = minidom.parseString(data)
|
|
||||||
except Exception, e:
|
|
||||||
logger.warn('Error parsing data from url: %s. Error: %s' % (searchURL, e))
|
|
||||||
return
|
|
||||||
|
|
||||||
url = parseddata.getElementsByTagName("url")
|
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
lyricsurl = url[0].firstChild.nodeValue
|
lyricsurl = url[0].firstChild.nodeValue
|
||||||
@@ -49,10 +39,10 @@ def getLyrics(artist, song):
|
|||||||
logger.info('No lyrics found for %s - %s' % (artist, song))
|
logger.info('No lyrics found for %s - %s' % (artist, song))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
lyricspage = request.request_content(lyricsurl)
|
||||||
lyricspage = urllib.urlopen(lyricsurl).read()
|
|
||||||
except Exception, e:
|
if not lyricspage:
|
||||||
logger.warn('Error fetching lyrics from: %s. Error: %s' % (lyricsurl, e))
|
logger.warn('Error fetching lyrics from: %s' % lyricsurl)
|
||||||
return
|
return
|
||||||
|
|
||||||
m = re.compile('''<div class='lyricbox'><div class='rtMatcher'>.*?</div>(.*?)<!--''').search(lyricspage)
|
m = re.compile('''<div class='lyricbox'><div class='rtMatcher'>.*?</div>(.*?)<!--''').search(lyricspage)
|
||||||
|
|||||||
@@ -13,21 +13,23 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
|
|
||||||
import headphones
|
|
||||||
from headphones import logger, db, helpers
|
from headphones import logger, db, helpers
|
||||||
from headphones.helpers import multikeysort, replace_all
|
from headphones.helpers import multikeysort, replace_all
|
||||||
|
|
||||||
import lib.musicbrainzngs as musicbrainzngs
|
import time
|
||||||
from lib.musicbrainzngs import WebServiceError
|
import threading
|
||||||
|
import headphones
|
||||||
|
import musicbrainzngs
|
||||||
|
|
||||||
|
try:
|
||||||
|
from collections import OrderedDict
|
||||||
|
except ImportError:
|
||||||
|
# Python 2.6.x fallback, from libs
|
||||||
|
import OrderedDict
|
||||||
|
|
||||||
mb_lock = threading.Lock()
|
mb_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
# Quick fix to add mirror switching on the fly. Need to probably return the mbhost & mbport that's
|
# Quick fix to add mirror switching on the fly. Need to probably return the mbhost & mbport that's
|
||||||
# being used, so we can send those values to the log
|
# being used, so we can send those values to the log
|
||||||
def startmb():
|
def startmb():
|
||||||
@@ -67,7 +69,7 @@ def startmb():
|
|||||||
else:
|
else:
|
||||||
musicbrainzngs.hpauth(mbuser,mbpass)
|
musicbrainzngs.hpauth(mbuser,mbpass)
|
||||||
|
|
||||||
logger.debug('Using the following server values:\nMBHost: %s ; MBPort: %i ; Sleep Interval: %i ' % (mbhost, mbport, sleepytime))
|
logger.debug('Using the following server values: MBHost: %s, MBPort: %i, Sleep Interval: %i', mbhost, mbport, sleepytime)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -77,13 +79,15 @@ def findArtist(name, limit=1):
|
|||||||
artistlist = []
|
artistlist = []
|
||||||
artistResults = None
|
artistResults = None
|
||||||
|
|
||||||
chars = set('!?*')
|
chars = set('!?*-')
|
||||||
if any((c in chars) for c in name):
|
if any((c in chars) for c in name):
|
||||||
name = '"'+name+'"'
|
name = '"'+name+'"'
|
||||||
|
|
||||||
|
criteria = {'artist': name.lower()}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
artistResults = musicbrainzngs.search_artists(query='artist:'+name,limit=limit)['artist-list']
|
artistResults = musicbrainzngs.search_artists(limit=limit, **criteria)['artist-list']
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -119,33 +123,81 @@ def findArtist(name, limit=1):
|
|||||||
})
|
})
|
||||||
return artistlist
|
return artistlist
|
||||||
|
|
||||||
def findRelease(name, limit=1):
|
def findRelease(name, limit=1, artist=None):
|
||||||
|
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
releaselist = []
|
releaselist = []
|
||||||
releaseResults = None
|
releaseResults = None
|
||||||
|
|
||||||
chars = set('!?')
|
# additional artist search
|
||||||
|
if not artist and ':' in name:
|
||||||
|
name, artist = name.rsplit(":",1)
|
||||||
|
|
||||||
|
chars = set('!?*-')
|
||||||
if any((c in chars) for c in name):
|
if any((c in chars) for c in name):
|
||||||
name = '"'+name+'"'
|
name = '"'+name+'"'
|
||||||
|
if artist and any((c in chars) for c in artist):
|
||||||
|
artist = '"'+artist+'"'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit)['release-list']
|
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit,artist=artist)['release-list']
|
||||||
except WebServiceError, e: #need to update exceptions
|
except musicbrainzngs.WebServiceError as e: #need to update exceptions
|
||||||
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
if not releaseResults:
|
if not releaseResults:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for result in releaseResults:
|
for result in releaseResults:
|
||||||
releaselist.append({
|
|
||||||
|
title = result['title']
|
||||||
|
if 'disambiguation' in result:
|
||||||
|
title += ' (' + result['disambiguation'] + ')'
|
||||||
|
|
||||||
|
# Get formats and track counts
|
||||||
|
format_dict = OrderedDict()
|
||||||
|
formats = ''
|
||||||
|
tracks = ''
|
||||||
|
if 'medium-list' in result:
|
||||||
|
for medium in result['medium-list']:
|
||||||
|
if 'format' in medium:
|
||||||
|
format = medium['format']
|
||||||
|
if format not in format_dict:
|
||||||
|
format_dict[format] = 0
|
||||||
|
format_dict[format] += 1
|
||||||
|
if 'track-count' in medium:
|
||||||
|
if tracks:
|
||||||
|
tracks += ' + '
|
||||||
|
tracks += str(medium['track-count'])
|
||||||
|
for format, count in format_dict.items():
|
||||||
|
if formats:
|
||||||
|
formats += ' + '
|
||||||
|
if count > 1:
|
||||||
|
formats += str(count) + 'x'
|
||||||
|
formats += format
|
||||||
|
|
||||||
|
rg_type = ''
|
||||||
|
if 'type' in result['release-group']:
|
||||||
|
rg_type = result['release-group']['type']
|
||||||
|
if rg_type == 'Album' and 'secondary-type-list' in result['release-group']:
|
||||||
|
secondary_type = result['release-group']['secondary-type-list'][0]
|
||||||
|
if secondary_type != rg_type:
|
||||||
|
rg_type = secondary_type
|
||||||
|
|
||||||
|
releaselist.append({
|
||||||
'uniquename': unicode(result['artist-credit'][0]['artist']['name']),
|
'uniquename': unicode(result['artist-credit'][0]['artist']['name']),
|
||||||
'title': unicode(result['title']),
|
'title': unicode(title),
|
||||||
'id': unicode(result['artist-credit'][0]['artist']['id']),
|
'id': unicode(result['artist-credit'][0]['artist']['id']),
|
||||||
'albumid': unicode(result['id']),
|
'albumid': unicode(result['id']),
|
||||||
'url': unicode("http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),#probably needs to be changed
|
'url': unicode("http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),#probably needs to be changed
|
||||||
'albumurl': unicode("http://musicbrainz.org/release/" + result['id']),#probably needs to be changed
|
'albumurl': unicode("http://musicbrainz.org/release/" + result['id']),#probably needs to be changed
|
||||||
'score': int(result['ext:score'])
|
'score': int(result['ext:score']),
|
||||||
|
'date': unicode(result['date']) if 'date' in result else '',
|
||||||
|
'country': unicode(result['country']) if 'country' in result else '',
|
||||||
|
'formats': unicode(formats),
|
||||||
|
'tracks': unicode(tracks),
|
||||||
|
'rgid': unicode(result['release-group']['id']),
|
||||||
|
'rgtype': unicode(rg_type)
|
||||||
})
|
})
|
||||||
return releaselist
|
return releaselist
|
||||||
|
|
||||||
@@ -164,7 +216,7 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
while newRgs == None or len(newRgs) >= limit:
|
while newRgs == None or len(newRgs) >= limit:
|
||||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type="album",offset=len(artist['release-group-list']),limit=limit)['release-group-list']
|
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type="album",offset=len(artist['release-group-list']),limit=limit)['release-group-list']
|
||||||
artist['release-group-list'] += newRgs
|
artist['release-group-list'] += newRgs
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
except Exception,e:
|
except Exception,e:
|
||||||
@@ -200,7 +252,7 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
|
|
||||||
if not extrasonly:
|
if not extrasonly:
|
||||||
for rg in artist['release-group-list']:
|
for rg in artist['release-group-list']:
|
||||||
if rg['type'] != 'Album': #only add releases without a secondary type
|
if "secondary-type-list" in rg.keys(): #only add releases without a secondary type
|
||||||
continue
|
continue
|
||||||
releasegroups.append({
|
releasegroups.append({
|
||||||
'title': unicode(rg['title']),
|
'title': unicode(rg['title']),
|
||||||
@@ -221,35 +273,48 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
|
|
||||||
if includeExtras:
|
if includeExtras:
|
||||||
|
|
||||||
# Need to convert extras string from something like '2,5.6' to ['ep','live','remix']
|
# Need to convert extras string from something like '2,5.6' to ['ep','live','remix'] (append new extras to end)
|
||||||
extras = db_artist['Extras']
|
if db_artist['Extras']:
|
||||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook"]
|
extras = map(int, db_artist['Extras'].split(','))
|
||||||
|
else:
|
||||||
|
extras = []
|
||||||
|
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other", "dj-mix", "mixtape/street", "broadcast", "interview", "demo"]
|
||||||
|
|
||||||
includes = []
|
includes = []
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for extra in extras_list:
|
for extra in extras_list:
|
||||||
if str(i) in extras:
|
if i in extras:
|
||||||
includes.append(extra)
|
includes.append(extra)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
for include in includes:
|
for include in includes:
|
||||||
|
|
||||||
artist = None
|
mb_extras_list = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
artist = musicbrainzngs.get_artist_by_id(artistid,includes=["releases","release-groups"],release_status=['official'],release_type=include)['artist']
|
limit = 200
|
||||||
except WebServiceError, e:
|
newRgs = None
|
||||||
|
while newRgs == None or len(newRgs) >= limit:
|
||||||
|
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type=include,offset=len(mb_extras_list),limit=limit)['release-group-list']
|
||||||
|
mb_extras_list += newRgs
|
||||||
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
if not artist:
|
for rg in mb_extras_list:
|
||||||
continue
|
|
||||||
for rg in artist['release-group-list']:
|
rg_type = rg['type']
|
||||||
|
if rg_type == 'Album' and 'secondary-type-list' in rg:
|
||||||
|
secondary_type = rg['secondary-type-list'][0]
|
||||||
|
if secondary_type != rg_type:
|
||||||
|
rg_type = secondary_type
|
||||||
|
|
||||||
releasegroups.append({
|
releasegroups.append({
|
||||||
'title': unicode(rg['title']),
|
'title': unicode(rg['title']),
|
||||||
'id': unicode(rg['id']),
|
'id': unicode(rg['id']),
|
||||||
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
|
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
|
||||||
'type': unicode(rg['type'])
|
'type': unicode(rg_type)
|
||||||
})
|
})
|
||||||
|
|
||||||
artist_dict['releasegroups'] = releasegroups
|
artist_dict['releasegroups'] = releasegroups
|
||||||
@@ -268,7 +333,7 @@ def getReleaseGroup(rgid):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid,["artists","releases","media","discids",])['release-group']
|
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid,["artists","releases","media","discids",])['release-group']
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -291,7 +356,7 @@ def getRelease(releaseid, include_artist_info=True):
|
|||||||
results = musicbrainzngs.get_release_by_id(releaseid,["artists","release-groups","media","recordings"]).get('release')
|
results = musicbrainzngs.get_release_by_id(releaseid,["artists","release-groups","media","recordings"]).get('release')
|
||||||
else:
|
else:
|
||||||
results = musicbrainzngs.get_release_by_id(releaseid,["media","recordings"]).get('release')
|
results = musicbrainzngs.get_release_by_id(releaseid,["media","recordings"]).get('release')
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed (%s)' % (releaseid, str(e)))
|
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed (%s)' % (releaseid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -320,8 +385,15 @@ def getRelease(releaseid, include_artist_info=True):
|
|||||||
release['rg_title'] = unicode(results['release-group']['title'])
|
release['rg_title'] = unicode(results['release-group']['title'])
|
||||||
try:
|
try:
|
||||||
release['rg_type'] = unicode(results['release-group']['type'])
|
release['rg_type'] = unicode(results['release-group']['type'])
|
||||||
|
|
||||||
|
if release['rg_type'] == 'Album' and 'secondary-type-list' in results['release-group']:
|
||||||
|
secondary_type = unicode(results['release-group']['secondary-type-list'][0])
|
||||||
|
if secondary_type != release['rg_type']:
|
||||||
|
release['rg_type'] = secondary_type
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
release['rg_type'] = u'Unknown'
|
release['rg_type'] = u'Unknown'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.warn("Release " + releaseid + "had no ReleaseGroup associated")
|
logger.warn("Release " + releaseid + "had no ReleaseGroup associated")
|
||||||
|
|
||||||
@@ -346,7 +418,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
newResults = newResults['release-list']
|
newResults = newResults['release-list']
|
||||||
results += newResults
|
results += newResults
|
||||||
|
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
return False
|
return False
|
||||||
@@ -373,7 +445,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
logger.info("Removing all references to release %s to reflect MusicBrainz" % items['ReleaseID'])
|
logger.info("Removing all references to release %s to reflect MusicBrainz" % items['ReleaseID'])
|
||||||
force_repackage1 = 1
|
force_repackage1 = 1
|
||||||
else:
|
else:
|
||||||
logger.info("Error pulling data from MusicBrainz: Maintaining dB")
|
logger.info("There was either an error pulling data from MusicBrainz or there might not be any releases for this category")
|
||||||
|
|
||||||
num_new_releases = 0
|
num_new_releases = 0
|
||||||
|
|
||||||
@@ -401,6 +473,10 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
raise Exception('No release group associated with release id ' + releasedata['id'] + ' album id' + rgid)
|
raise Exception('No release group associated with release id ' + releasedata['id'] + ' album id' + rgid)
|
||||||
release['Type'] = unicode(releasedata['release-group']['type'])
|
release['Type'] = unicode(releasedata['release-group']['type'])
|
||||||
|
|
||||||
|
if release['Type'] == 'Album' and 'secondary-type-list' in releasedata['release-group']:
|
||||||
|
secondary_type = unicode(releasedata['release-group']['secondary-type-list'][0])
|
||||||
|
if secondary_type != release['Type']:
|
||||||
|
release['Type'] = secondary_type
|
||||||
|
|
||||||
#making the assumption that the most important artist will be first in the list
|
#making the assumption that the most important artist will be first in the list
|
||||||
if 'artist-credit' in releasedata:
|
if 'artist-credit' in releasedata:
|
||||||
@@ -535,7 +611,7 @@ def findArtistbyAlbum(name):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
results = musicbrainzngs.search_release_groups(term).get('release-group-list')
|
results = musicbrainzngs.search_release_groups(term).get('release-group-list')
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -564,11 +640,23 @@ def findArtistbyAlbum(name):
|
|||||||
def findAlbumID(artist=None, album=None):
|
def findAlbumID(artist=None, album=None):
|
||||||
|
|
||||||
results = None
|
results = None
|
||||||
|
chars = set('!?*-')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
term = '"'+album+'" AND artist:"'+artist+'"'
|
if album and artist:
|
||||||
results = musicbrainzngs.search_release_groups(term,1).get('release-group-list')
|
if any((c in chars) for c in album):
|
||||||
except WebServiceError, e:
|
album = '"'+album+'"'
|
||||||
|
if any((c in chars) for c in artist):
|
||||||
|
artist = '"'+artist+'"'
|
||||||
|
criteria = {'release': album.lower()}
|
||||||
|
criteria['artist'] = artist.lower()
|
||||||
|
else:
|
||||||
|
if any((c in chars) for c in album):
|
||||||
|
album = '"'+album+'"'
|
||||||
|
criteria = {'release': album.lower()}
|
||||||
|
|
||||||
|
results = musicbrainzngs.search_release_groups(limit=1, **criteria).get('release-group-list')
|
||||||
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to query MusicBrainz for %s - %s failed (%s)' % (artist, album, str(e)))
|
logger.warn('Attempt to query MusicBrainz for %s - %s failed (%s)' % (artist, album, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,11 @@ import os
|
|||||||
import headphones
|
import headphones
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from headphones import logger
|
from headphones import logger
|
||||||
from lib.beets.mediafile import MediaFile
|
from beets.mediafile import MediaFile
|
||||||
|
|
||||||
try:
|
|
||||||
import argparse
|
|
||||||
except ImportError:
|
|
||||||
import lib.argparse as argparse
|
|
||||||
|
|
||||||
# xld
|
# xld
|
||||||
if headphones.ENCODER == 'xld':
|
if headphones.ENCODER == 'xld':
|
||||||
@@ -41,26 +37,29 @@ def encode(albumPath):
|
|||||||
global xldProfile
|
global xldProfile
|
||||||
(xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.XLDPROFILE)
|
(xldProfile, xldFormat, xldBitrate) = getXldProfile.getXldProfile(headphones.XLDPROFILE)
|
||||||
if not xldFormat:
|
if not xldFormat:
|
||||||
logger.error(u'Details for xld profile %s not found, files will not be re-encoded' % (xldProfile))
|
logger.error('Details for xld profile \'%s\' not found, files will not be re-encoded', xldProfile)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
tempDirEncode=os.path.join(albumPath,"temp")
|
tempDirEncode=os.path.join(albumPath,"temp")
|
||||||
musicFiles=[]
|
musicFiles=[]
|
||||||
musicFinalFiles=[]
|
musicFinalFiles=[]
|
||||||
musicTempFiles=[]
|
musicTempFiles=[]
|
||||||
encoder =""
|
encoder = ""
|
||||||
|
|
||||||
|
# Create temporary directory, but remove the old one first.
|
||||||
|
try:
|
||||||
|
if os.path.exists(tempDirEncode):
|
||||||
|
shutil.rmtree(tempDirEncode)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
if not os.path.exists(tempDirEncode):
|
|
||||||
os.mkdir(tempDirEncode)
|
|
||||||
else:
|
|
||||||
shutil.rmtree(tempDirEncode)
|
|
||||||
time.sleep(1)
|
|
||||||
os.mkdir(tempDirEncode)
|
os.mkdir(tempDirEncode)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Unable to create temporary directory")
|
||||||
|
return None
|
||||||
|
|
||||||
for r,d,f in os.walk(albumPath):
|
for r,d,f in os.walk(albumPath):
|
||||||
for music in f:
|
for music in f:
|
||||||
if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
if any(music.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||||
|
|
||||||
if not XLD:
|
if not XLD:
|
||||||
encoderFormat = headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)
|
encoderFormat = headphones.ENCODEROUTPUTFORMAT.encode(headphones.SYS_ENCODING)
|
||||||
else:
|
else:
|
||||||
@@ -75,7 +74,7 @@ def encode(albumPath):
|
|||||||
musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
|
musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
|
||||||
musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
|
musicTempFiles.append(os.path.join(tempDirEncode, musicTemp))
|
||||||
else:
|
else:
|
||||||
logger.debug('%s is already encoded' % (music))
|
logger.debug('%s is already encoded', music)
|
||||||
else:
|
else:
|
||||||
musicFiles.append(os.path.join(r, music))
|
musicFiles.append(os.path.join(r, music))
|
||||||
musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
|
musicTemp = os.path.normpath(os.path.splitext(music)[0] + '.' + encoderFormat)
|
||||||
@@ -97,49 +96,88 @@ def encode(albumPath):
|
|||||||
encoder = "C:/Program Files/ffmpeg/bin/ffmpeg.exe"
|
encoder = "C:/Program Files/ffmpeg/bin/ffmpeg.exe"
|
||||||
else:
|
else:
|
||||||
encoder="ffmpeg"
|
encoder="ffmpeg"
|
||||||
|
elif headphones.ENCODER == 'libav':
|
||||||
|
if headphones.SYS_PLATFORM == "win32":
|
||||||
|
encoder = "C:/Program Files/libav/bin/avconv.exe"
|
||||||
|
else:
|
||||||
|
encoder="avconv"
|
||||||
|
|
||||||
i=0
|
i=0
|
||||||
encoder_failed = False
|
encoder_failed = False
|
||||||
|
jobs = []
|
||||||
|
|
||||||
for music in musicFiles:
|
for music in musicFiles:
|
||||||
infoMusic=MediaFile(music)
|
infoMusic = MediaFile(music)
|
||||||
encode = False
|
encode = False
|
||||||
|
|
||||||
if XLD:
|
if XLD:
|
||||||
if xldBitrate and (infoMusic.bitrate / 1000 <= xldBitrate):
|
if xldBitrate and (infoMusic.bitrate / 1000 <= xldBitrate):
|
||||||
logger.info('%s has bitrate <= %skb, will not be re-encoded' % (music.decode(headphones.SYS_ENCODING, 'replace'), xldBitrate))
|
logger.info('%s has bitrate <= %skb, will not be re-encoded', music.decode(headphones.SYS_ENCODING, 'replace'), xldBitrate)
|
||||||
else:
|
else:
|
||||||
encode = True
|
encode = True
|
||||||
elif headphones.ENCODER == 'lame':
|
elif headphones.ENCODER == 'lame':
|
||||||
if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]):
|
if not any(music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.' + x) for x in ["mp3", "wav"]):
|
||||||
logger.warn(u'Lame cannot encode %s format for %s, use ffmpeg' % (os.path.splitext(music)[1].decode(headphones.SYS_ENCODING, 'replace'),music.decode(headphones.SYS_ENCODING, 'replace')))
|
logger.warn('Lame cannot encode %s format for %s, use ffmpeg', os.path.splitext(music)[1], music)
|
||||||
else:
|
else:
|
||||||
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.mp3') and (int(infoMusic.bitrate / 1000) <= headphones.BITRATE)):
|
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.mp3') and (int(infoMusic.bitrate / 1000) <= headphones.BITRATE)):
|
||||||
logger.info('%s has bitrate <= %skb, will not be re-encoded' % (music.decode(headphones.SYS_ENCODING, 'replace'),headphones.BITRATE))
|
logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.BITRATE)
|
||||||
else:
|
else:
|
||||||
encode = True
|
encode = True
|
||||||
else:
|
else:
|
||||||
if headphones.ENCODEROUTPUTFORMAT=='ogg':
|
if headphones.ENCODEROUTPUTFORMAT=='ogg':
|
||||||
if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.ogg'):
|
if music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.ogg'):
|
||||||
logger.warn('Cannot re-encode .ogg %s' % (music.decode(headphones.SYS_ENCODING, 'replace')))
|
logger.warn('Cannot re-encode .ogg %s', music.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
else:
|
else:
|
||||||
encode = True
|
encode = True
|
||||||
elif (headphones.ENCODEROUTPUTFORMAT=='mp3' or headphones.ENCODEROUTPUTFORMAT=='m4a'):
|
elif (headphones.ENCODEROUTPUTFORMAT=='mp3' or headphones.ENCODEROUTPUTFORMAT=='m4a'):
|
||||||
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.'+headphones.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate / 1000 ) <= headphones.BITRATE)):
|
if (music.decode(headphones.SYS_ENCODING, 'replace').lower().endswith('.'+headphones.ENCODEROUTPUTFORMAT) and (int(infoMusic.bitrate / 1000 ) <= headphones.BITRATE)):
|
||||||
logger.info('%s has bitrate <= %skb, will not be re-encoded' % (music.decode(headphones.SYS_ENCODING, 'replace'),headphones.BITRATE))
|
logger.info('%s has bitrate <= %skb, will not be re-encoded', music, headphones.BITRATE)
|
||||||
else:
|
else:
|
||||||
encode = True
|
encode = True
|
||||||
# encode
|
# encode
|
||||||
if encode:
|
if encode:
|
||||||
if not command(encoder,music,musicTempFiles[i],albumPath):
|
job = (encoder, music, musicTempFiles[i], albumPath)
|
||||||
encoder_failed = True
|
jobs.append(job)
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
musicFiles[i] = None
|
musicFiles[i] = None
|
||||||
musicTempFiles[i] = None
|
musicTempFiles[i] = None
|
||||||
|
|
||||||
i=i+1
|
i=i+1
|
||||||
|
|
||||||
|
# Encode music files
|
||||||
|
if len(jobs) > 0:
|
||||||
|
processes = 1
|
||||||
|
|
||||||
|
# Use multicore if enabled
|
||||||
|
if headphones.ENCODER_MULTICORE:
|
||||||
|
if headphones.ENCODER_MULTICORE_COUNT == 0:
|
||||||
|
processes = multiprocessing.cpu_count()
|
||||||
|
else:
|
||||||
|
processes = headphones.ENCODER_MULTICORE_COUNT
|
||||||
|
|
||||||
|
logger.debug("Multi-core encoding enabled, spawning %d processes",
|
||||||
|
processes)
|
||||||
|
|
||||||
|
# Use multiprocessing only if it's worth the overhead. and if it is
|
||||||
|
# enabled. If not, then use the old fashioned way.
|
||||||
|
if processes > 1:
|
||||||
|
with logger.listener():
|
||||||
|
pool = multiprocessing.Pool(processes=processes)
|
||||||
|
results = pool.map_async(command_map, jobs)
|
||||||
|
|
||||||
|
# No new processes will be created, so close it and wait for all
|
||||||
|
# processes to finish
|
||||||
|
pool.close()
|
||||||
|
pool.join()
|
||||||
|
|
||||||
|
# Retrieve the results
|
||||||
|
results = results.get()
|
||||||
|
else:
|
||||||
|
results = map(command_map, jobs)
|
||||||
|
|
||||||
|
# The results are either True or False, so determine if one is False
|
||||||
|
encoder_failed = not all(results)
|
||||||
|
|
||||||
musicFiles = filter(None, musicFiles)
|
musicFiles = filter(None, musicFiles)
|
||||||
musicTempFiles = filter(None, musicTempFiles)
|
musicTempFiles = filter(None, musicTempFiles)
|
||||||
|
|
||||||
@@ -148,7 +186,7 @@ def encode(albumPath):
|
|||||||
for dest in musicTempFiles:
|
for dest in musicTempFiles:
|
||||||
if not os.path.exists(dest):
|
if not os.path.exists(dest):
|
||||||
encoder_failed = True
|
encoder_failed = True
|
||||||
logger.error('Encoded file %s does not exist in the destination temp directory' % (dest.decode(headphones.SYS_ENCODING, 'replace')))
|
logger.error("Encoded file '%s' does not exist in the destination temp directory", dest)
|
||||||
|
|
||||||
# No errors, move from temp to parent
|
# No errors, move from temp to parent
|
||||||
if not encoder_failed and musicTempFiles:
|
if not encoder_failed and musicTempFiles:
|
||||||
@@ -164,7 +202,7 @@ def encode(albumPath):
|
|||||||
try:
|
try:
|
||||||
shutil.move(dest, albumPath)
|
shutil.move(dest, albumPath)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error('Could not move %s to %s : %s' % (dest.decode(headphones.SYS_ENCODING, 'replace'), albumPath.decode(headphones.SYS_ENCODING, 'replace'), e))
|
logger.error('Could not move %s to %s: %s', dest, albumPath, e)
|
||||||
encoder_failed = True
|
encoder_failed = True
|
||||||
break
|
break
|
||||||
i += 1
|
i += 1
|
||||||
@@ -174,7 +212,7 @@ def encode(albumPath):
|
|||||||
|
|
||||||
# Return with error if any encoding errors
|
# Return with error if any encoding errors
|
||||||
if encoder_failed:
|
if encoder_failed:
|
||||||
logger.error('One or more files failed to encode, check debuglog and ensure you have the latest version of %s installed' % (headphones.ENCODER))
|
logger.error("One or more files failed to encode. Ensure you have the latest version of %s installed.", headphones.ENCODER)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@@ -184,15 +222,37 @@ def encode(albumPath):
|
|||||||
musicFinalFiles.append(os.path.join(r, music))
|
musicFinalFiles.append(os.path.join(r, music))
|
||||||
|
|
||||||
if not musicTempFiles:
|
if not musicTempFiles:
|
||||||
logger.info('Encoding for folder %s is not required' % (albumPath.decode(headphones.SYS_ENCODING, 'replace')))
|
logger.info('Encoding for folder \'%s\' is not required', albumPath)
|
||||||
|
|
||||||
return musicFinalFiles
|
return musicFinalFiles
|
||||||
|
|
||||||
def command(encoder,musicSource,musicDest,albumPath):
|
def command_map(args):
|
||||||
|
"""
|
||||||
|
Wrapper for the '[multiprocessing.]map()' method, to unpack the arguments
|
||||||
|
and wrap exceptions.
|
||||||
|
"""
|
||||||
|
|
||||||
cmd=[]
|
# Initialize multiprocessing logger
|
||||||
startMusicTime=time.time()
|
if multiprocessing.current_process().name != "MainProcess":
|
||||||
|
logger.initMultiprocessing()
|
||||||
|
|
||||||
|
# Start encoding
|
||||||
|
try:
|
||||||
|
return command(*args)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Encoder raised an exception.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def command(encoder, musicSource, musicDest, albumPath):
|
||||||
|
"""
|
||||||
|
Encode a given music file with a certain encoder. Returns True on success,
|
||||||
|
or False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
startMusicTime = time.time()
|
||||||
|
cmd = []
|
||||||
|
|
||||||
|
# XLD
|
||||||
if XLD:
|
if XLD:
|
||||||
xldDestDir = os.path.split(musicDest)[0]
|
xldDestDir = os.path.split(musicDest)[0]
|
||||||
cmd = [encoder]
|
cmd = [encoder]
|
||||||
@@ -202,10 +262,11 @@ def command(encoder,musicSource,musicDest,albumPath):
|
|||||||
cmd.extend(['-o'])
|
cmd.extend(['-o'])
|
||||||
cmd.extend([xldDestDir])
|
cmd.extend([xldDestDir])
|
||||||
|
|
||||||
|
# Lame
|
||||||
elif headphones.ENCODER == 'lame':
|
elif headphones.ENCODER == 'lame':
|
||||||
cmd = [encoder]
|
cmd = [encoder]
|
||||||
opts = []
|
opts = []
|
||||||
if headphones.ADVANCEDENCODER =='':
|
if not headphones.ADVANCEDENCODER:
|
||||||
opts.extend(['-h'])
|
opts.extend(['-h'])
|
||||||
if headphones.ENCODERVBRCBR=='cbr':
|
if headphones.ENCODERVBRCBR=='cbr':
|
||||||
opts.extend(['--resample', str(headphones.SAMPLINGFREQUENCY), '-b', str(headphones.BITRATE)])
|
opts.extend(['--resample', str(headphones.SAMPLINGFREQUENCY), '-b', str(headphones.BITRATE)])
|
||||||
@@ -219,10 +280,11 @@ def command(encoder,musicSource,musicDest,albumPath):
|
|||||||
opts.extend([musicDest])
|
opts.extend([musicDest])
|
||||||
cmd.extend(opts)
|
cmd.extend(opts)
|
||||||
|
|
||||||
|
# FFmpeg
|
||||||
elif headphones.ENCODER == 'ffmpeg':
|
elif headphones.ENCODER == 'ffmpeg':
|
||||||
cmd = [encoder, '-i', musicSource]
|
cmd = [encoder, '-i', musicSource]
|
||||||
opts = []
|
opts = []
|
||||||
if headphones.ADVANCEDENCODER =='':
|
if not headphones.ADVANCEDENCODER:
|
||||||
if headphones.ENCODEROUTPUTFORMAT=='ogg':
|
if headphones.ENCODEROUTPUTFORMAT=='ogg':
|
||||||
opts.extend(['-acodec', 'libvorbis'])
|
opts.extend(['-acodec', 'libvorbis'])
|
||||||
if headphones.ENCODEROUTPUTFORMAT=='m4a':
|
if headphones.ENCODEROUTPUTFORMAT=='m4a':
|
||||||
@@ -239,13 +301,30 @@ def command(encoder,musicSource,musicDest,albumPath):
|
|||||||
opts.extend([musicDest])
|
opts.extend([musicDest])
|
||||||
cmd.extend(opts)
|
cmd.extend(opts)
|
||||||
|
|
||||||
# Encode
|
# Libav
|
||||||
|
elif headphones.ENCODER == "libav":
|
||||||
|
cmd = [encoder, '-i', musicSource]
|
||||||
|
opts = []
|
||||||
|
if not headphones.ADVANCEDENCODER:
|
||||||
|
if headphones.ENCODEROUTPUTFORMAT=='ogg':
|
||||||
|
opts.extend(['-acodec', 'libvorbis'])
|
||||||
|
if headphones.ENCODEROUTPUTFORMAT=='m4a':
|
||||||
|
opts.extend(['-strict', 'experimental'])
|
||||||
|
if headphones.ENCODERVBRCBR=='cbr':
|
||||||
|
opts.extend(['-ar', str(headphones.SAMPLINGFREQUENCY), '-ab', str(headphones.BITRATE) + 'k'])
|
||||||
|
elif headphones.ENCODERVBRCBR=='vbr':
|
||||||
|
opts.extend(['-aq', str(headphones.ENCODERQUALITY)])
|
||||||
|
opts.extend(['-y', '-ac', '2', '-vn'])
|
||||||
|
else:
|
||||||
|
advanced = (headphones.ADVANCEDENCODER.split())
|
||||||
|
for tok in advanced:
|
||||||
|
opts.extend([tok.encode(headphones.SYS_ENCODING)])
|
||||||
|
opts.extend([musicDest])
|
||||||
|
cmd.extend(opts)
|
||||||
|
|
||||||
logger.info('Encoding %s...' % (musicSource.decode(headphones.SYS_ENCODING, 'replace')))
|
# Prevent Windows from opening a terminal window
|
||||||
logger.debug(subprocess.list2cmdline(cmd))
|
|
||||||
|
|
||||||
# stop windows opening the cmd
|
|
||||||
startupinfo = None
|
startupinfo = None
|
||||||
|
|
||||||
if headphones.SYS_PLATFORM == "win32":
|
if headphones.SYS_PLATFORM == "win32":
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
startupinfo = subprocess.STARTUPINFO()
|
||||||
try:
|
try:
|
||||||
@@ -253,12 +332,17 @@ def command(encoder,musicSource,musicDest,albumPath):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
|
startupinfo.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW
|
||||||
|
|
||||||
p = subprocess.Popen(cmd, startupinfo=startupinfo, stdin=open(os.devnull, 'rb'), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
# Encode
|
||||||
|
logger.info('Encoding %s...' % (musicSource.decode(headphones.SYS_ENCODING, 'replace')))
|
||||||
|
logger.debug(subprocess.list2cmdline(cmd))
|
||||||
|
|
||||||
stdout, stderr = p.communicate(headphones.ENCODER)
|
process = subprocess.Popen(cmd, startupinfo=startupinfo,
|
||||||
|
stdin=open(os.devnull, 'rb'), stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
stdout, stderr = process.communicate(headphones.ENCODER)
|
||||||
|
|
||||||
# error if return code not zero
|
# Error if return code not zero
|
||||||
if p.returncode:
|
if process.returncode:
|
||||||
logger.error('Encoding failed for %s' % (musicSource.decode(headphones.SYS_ENCODING, 'replace')))
|
logger.error('Encoding failed for %s' % (musicSource.decode(headphones.SYS_ENCODING, 'replace')))
|
||||||
out = stdout if stdout else stderr
|
out = stdout if stdout else stderr
|
||||||
out = out.decode(headphones.SYS_ENCODING, 'replace')
|
out = out.decode(headphones.SYS_ENCODING, 'replace')
|
||||||
@@ -268,7 +352,7 @@ def command(encoder,musicSource,musicDest,albumPath):
|
|||||||
logger.debug(out)
|
logger.debug(out)
|
||||||
encoded = False
|
encoded = False
|
||||||
else:
|
else:
|
||||||
logger.info('%s encoded in %s' % (musicSource.decode(headphones.SYS_ENCODING, 'replace'),getTimeEncode(startMusicTime)))
|
logger.info('%s encoded in %s', musicSource, getTimeEncode(startMusicTime))
|
||||||
encoded = True
|
encoded = True
|
||||||
|
|
||||||
return encoded
|
return encoded
|
||||||
@@ -280,4 +364,3 @@ def getTimeEncode(start):
|
|||||||
minutes = seconds / 60
|
minutes = seconds / 60
|
||||||
seconds -= 60*minutes
|
seconds -= 60*minutes
|
||||||
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||||
|
|
||||||
|
|||||||
@@ -13,38 +13,122 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from headphones import logger, helpers, common
|
from headphones import logger, helpers, common, request
|
||||||
from headphones.exceptions import ex
|
|
||||||
|
from xml.dom import minidom
|
||||||
|
from httplib import HTTPSConnection
|
||||||
|
from urllib import urlencode
|
||||||
|
from pynma import pynma
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import urllib
|
import urllib
|
||||||
import urllib2
|
import urllib2
|
||||||
import headphones
|
import headphones
|
||||||
from httplib import HTTPSConnection
|
|
||||||
from urllib import urlencode
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import lib.simplejson as simplejson
|
import gntp.notifier
|
||||||
from xml.dom import minidom
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
import oauth2 as oauth
|
||||||
|
import pythontwitter as twitter
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urlparse import parse_qsl #@UnusedImport
|
from urlparse import parse_qsl
|
||||||
except:
|
except ImportError:
|
||||||
from cgi import parse_qsl #@Reimport
|
from cgi import parse_qsl
|
||||||
|
|
||||||
import lib.oauth2 as oauth
|
class GROWL(object):
|
||||||
import lib.pythontwitter as twitter
|
"""
|
||||||
|
Growl notifications, for OS X
|
||||||
|
"""
|
||||||
|
|
||||||
class PROWL:
|
def __init__(self):
|
||||||
|
self.enabled = headphones.GROWL_ENABLED
|
||||||
|
self.host = headphones.GROWL_HOST
|
||||||
|
self.password = headphones.GROWL_PASSWORD
|
||||||
|
|
||||||
keys = []
|
def conf(self, options):
|
||||||
priority = []
|
return cherrypy.config['config'].get('Growl', options)
|
||||||
|
|
||||||
|
def notify(self, message, event):
|
||||||
|
if not self.enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Split host and port
|
||||||
|
if self.host == "":
|
||||||
|
host, port = "localhost", 23053
|
||||||
|
if ":" in self.host:
|
||||||
|
host, port = self.host.split(':', 1)
|
||||||
|
port = int(port)
|
||||||
|
else:
|
||||||
|
host, port = self.host, 23053
|
||||||
|
|
||||||
|
# If password is empty, assume none
|
||||||
|
if self.password == "":
|
||||||
|
password = None
|
||||||
|
else:
|
||||||
|
password = self.password
|
||||||
|
|
||||||
|
# Register notification
|
||||||
|
growl = gntp.notifier.GrowlNotifier(
|
||||||
|
applicationName='Headphones',
|
||||||
|
notifications=['New Event'],
|
||||||
|
defaultNotifications=['New Event'],
|
||||||
|
hostname=host,
|
||||||
|
port=port,
|
||||||
|
password=password
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
growl.register()
|
||||||
|
except gntp.notifier.errors.NetworkError:
|
||||||
|
logger.info(u'Growl notification failed: network error')
|
||||||
|
return
|
||||||
|
except gntp.notifier.errors.AuthError:
|
||||||
|
logger.info(u'Growl notification failed: authentication error')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send it, including an image
|
||||||
|
image_file = os.path.join(str(headphones.PROG_DIR), 'data/images/headphoneslogo.png')
|
||||||
|
|
||||||
|
with open(image_file, 'rb') as f:
|
||||||
|
image = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
growl.notify(
|
||||||
|
noteType='New Event',
|
||||||
|
title=event,
|
||||||
|
description=message,
|
||||||
|
icon=image
|
||||||
|
)
|
||||||
|
except gntp.notifier.errors.NetworkError:
|
||||||
|
logger.info(u'Growl notification failed: network error')
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(u"Growl notifications sent.")
|
||||||
|
|
||||||
|
def updateLibrary(self):
|
||||||
|
#For uniformity reasons not removed
|
||||||
|
return
|
||||||
|
|
||||||
|
def test(self, host, password):
|
||||||
|
self.enabled = True
|
||||||
|
self.host = host
|
||||||
|
self.password = password
|
||||||
|
|
||||||
|
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||||
|
|
||||||
|
class PROWL(object):
|
||||||
|
"""
|
||||||
|
Prowl notifications.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.enabled = headphones.PROWL_ENABLED
|
self.enabled = headphones.PROWL_ENABLED
|
||||||
self.keys = headphones.PROWL_KEYS
|
self.keys = headphones.PROWL_KEYS
|
||||||
self.priority = headphones.PROWL_PRIORITY
|
self.priority = headphones.PROWL_PRIORITY
|
||||||
pass
|
|
||||||
|
|
||||||
def conf(self, options):
|
def conf(self, options):
|
||||||
return cherrypy.config['config'].get('Prowl', options)
|
return cherrypy.config['config'].get('Prowl', options)
|
||||||
@@ -83,14 +167,29 @@ class PROWL:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def test(self, keys, priority):
|
def test(self, keys, priority):
|
||||||
|
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.keys = keys
|
self.keys = keys
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||||
|
|
||||||
class XBMC:
|
class MPC(object):
|
||||||
|
"""
|
||||||
|
MPC library update
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def notify( self ):
|
||||||
|
subprocess.call( ["mpc", "update"] )
|
||||||
|
|
||||||
|
|
||||||
|
class XBMC(object):
|
||||||
|
"""
|
||||||
|
XBMC notifications
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
@@ -99,60 +198,28 @@ class XBMC:
|
|||||||
self.password = headphones.XBMC_PASSWORD
|
self.password = headphones.XBMC_PASSWORD
|
||||||
|
|
||||||
def _sendhttp(self, host, command):
|
def _sendhttp(self, host, command):
|
||||||
|
|
||||||
username = self.username
|
|
||||||
password = self.password
|
|
||||||
|
|
||||||
url_command = urllib.urlencode(command)
|
url_command = urllib.urlencode(command)
|
||||||
|
|
||||||
url = host + '/xbmcCmds/xbmcHttp/?' + url_command
|
url = host + '/xbmcCmds/xbmcHttp/?' + url_command
|
||||||
|
|
||||||
req = urllib2.Request(url)
|
if self.password:
|
||||||
|
return request.request_content(url, auth=(self.username, self.password))
|
||||||
if password:
|
else:
|
||||||
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
|
return request.request_content(url)
|
||||||
req.add_header("Authorization", "Basic %s" % base64string)
|
|
||||||
|
|
||||||
logger.info('XBMC url: %s' % url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
handle = urllib2.urlopen(req)
|
|
||||||
except Exception, e:
|
|
||||||
logger.warn('Error opening XBMC url: %s' % e)
|
|
||||||
return
|
|
||||||
|
|
||||||
response = handle.read().decode(headphones.SYS_ENCODING)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def _sendjson(self, host, method, params={}):
|
def _sendjson(self, host, method, params={}):
|
||||||
data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
|
data = [{'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': params}]
|
||||||
data = simplejson.JSONEncoder().encode(data)
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
url = host + '/jsonrpc'
|
||||||
|
|
||||||
content = {'Content-Type': 'application/json', 'Content-Length': len(data)}
|
if self.password:
|
||||||
|
response = request.request_json(url, method="post", data=json.dumps(data), headers=headers, auth=(self.username, self.password))
|
||||||
|
else:
|
||||||
|
response = request.request_json(url, method="post", data=json.dumps(data), headers=headers)
|
||||||
|
|
||||||
req = urllib2.Request(host+'/jsonrpc', data, content)
|
if response:
|
||||||
|
|
||||||
if self.username and self.password:
|
|
||||||
base64string = base64.encodestring('%s:%s' % (self.username, self.password)).replace('\n', '')
|
|
||||||
req.add_header("Authorization", "Basic %s" % base64string)
|
|
||||||
|
|
||||||
try:
|
|
||||||
handle = urllib2.urlopen(req)
|
|
||||||
except Exception, e:
|
|
||||||
logger.warn('Error opening XBMC url: %s' % e)
|
|
||||||
return
|
|
||||||
|
|
||||||
response = simplejson.JSONDecoder().decode(handle.read())
|
|
||||||
|
|
||||||
try:
|
|
||||||
return response[0]['result']
|
return response[0]['result']
|
||||||
except:
|
|
||||||
logger.warn('XBMC returned error: %s' % response[0]['error'])
|
|
||||||
return
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
|
||||||
# From what I read you can't update the music library on a per directory or per path basis
|
# From what I read you can't update the music library on a per directory or per path basis
|
||||||
# so need to update the whole thing
|
# so need to update the whole thing
|
||||||
|
|
||||||
@@ -190,11 +257,51 @@ class XBMC:
|
|||||||
if not request:
|
if not request:
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
except:
|
except Exception:
|
||||||
logger.warn('Error sending notification request to XBMC')
|
logger.error('Error sending notification request to XBMC')
|
||||||
|
|
||||||
class Plex:
|
class LMS(object):
|
||||||
|
"""
|
||||||
|
Class for updating a Logitech Media Server
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.hosts = headphones.LMS_HOST
|
||||||
|
|
||||||
|
def _sendjson(self, host):
|
||||||
|
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]}
|
||||||
|
data = json.JSONEncoder().encode(data)
|
||||||
|
|
||||||
|
content = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
req = urllib2.Request(host+'/jsonrpc.js', data, content)
|
||||||
|
|
||||||
|
try:
|
||||||
|
handle = urllib2.urlopen(req)
|
||||||
|
except Exception, e:
|
||||||
|
logger.warn('Error opening LMS url: %s' % e)
|
||||||
|
return
|
||||||
|
|
||||||
|
response = json.JSONDecoder().decode(handle.read())
|
||||||
|
|
||||||
|
try:
|
||||||
|
return response['result']
|
||||||
|
except:
|
||||||
|
logger.warn('LMS returned error: %s' % response['error'])
|
||||||
|
return
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
|
||||||
|
hosts = [x.strip() for x in self.hosts.split(',')]
|
||||||
|
|
||||||
|
for host in hosts:
|
||||||
|
logger.info('Sending library rescan command to LMS @ '+host)
|
||||||
|
request = self._sendjson(host)
|
||||||
|
|
||||||
|
if not request:
|
||||||
|
logger.warn('Error sending rescan request to LMS')
|
||||||
|
|
||||||
|
class Plex(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.server_hosts = headphones.PLEX_SERVER_HOST
|
self.server_hosts = headphones.PLEX_SERVER_HOST
|
||||||
@@ -270,61 +377,53 @@ class Plex:
|
|||||||
for host in hosts:
|
for host in hosts:
|
||||||
logger.info('Sending notification command to Plex Media Server @ '+host)
|
logger.info('Sending notification command to Plex Media Server @ '+host)
|
||||||
try:
|
try:
|
||||||
notification = header + "," + message + "," + time + "," + albumartpath
|
notification = header + "," + message + "," + time + "," + albumartpath
|
||||||
notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'}
|
notifycommand = {'command': 'ExecBuiltIn', 'parameter': 'Notification('+notification+')'}
|
||||||
request = self._sendhttp(host, notifycommand)
|
request = self._sendhttp(host, notifycommand)
|
||||||
|
|
||||||
if not request:
|
if not request:
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
except:
|
except:
|
||||||
logger.warn('Error sending notification request to Plex Media Server')
|
logger.warn('Error sending notification request to Plex Media Server')
|
||||||
|
|
||||||
class NMA:
|
class NMA(object):
|
||||||
|
def notify(self, artist=None, album=None, snatched=None):
|
||||||
|
title = 'Headphones'
|
||||||
|
api = headphones.NMA_APIKEY
|
||||||
|
nma_priority = headphones.NMA_PRIORITY
|
||||||
|
|
||||||
def __init__(self):
|
logger.debug(u"NMA title: " + title)
|
||||||
|
logger.debug(u"NMA API: " + api)
|
||||||
|
logger.debug(u"NMA Priority: " + str(nma_priority))
|
||||||
|
|
||||||
self.apikey = headphones.NMA_APIKEY
|
if snatched:
|
||||||
self.priority = headphones.NMA_PRIORITY
|
event = snatched + " snatched!"
|
||||||
|
message = "Headphones has snatched: " + snatched
|
||||||
def _send(self, data):
|
|
||||||
|
|
||||||
url_data = urllib.urlencode(data)
|
|
||||||
url = 'https://www.notifymyandroid.com/publicapi/notify'
|
|
||||||
|
|
||||||
req = urllib2.Request(url, url_data)
|
|
||||||
|
|
||||||
try:
|
|
||||||
handle = urllib2.urlopen(req)
|
|
||||||
except Exception, e:
|
|
||||||
logger.warn('Error opening NotifyMyAndroid url: ' % e)
|
|
||||||
return
|
|
||||||
|
|
||||||
response = handle.read().decode(headphones.SYS_ENCODING)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def notify(self, artist=None, album=None, snatched_nzb=None):
|
|
||||||
|
|
||||||
apikey = self.apikey
|
|
||||||
priority = self.priority
|
|
||||||
|
|
||||||
if snatched_nzb:
|
|
||||||
event = snatched_nzb + " snatched!"
|
|
||||||
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
|
|
||||||
else:
|
else:
|
||||||
event = artist + ' - ' + album + ' complete!'
|
event = artist + ' - ' + album + ' complete!'
|
||||||
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||||
|
|
||||||
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
|
logger.debug(u"NMA event: " + event)
|
||||||
|
logger.debug(u"NMA message: " + message)
|
||||||
|
|
||||||
logger.info('Sending notification request to NotifyMyAndroid')
|
batch = False
|
||||||
request = self._send(data)
|
|
||||||
|
|
||||||
if not request:
|
p = pynma.PyNMA()
|
||||||
logger.warn('Error sending notification request to NotifyMyAndroid')
|
keys = api.split(',')
|
||||||
|
p.addkey(keys)
|
||||||
|
|
||||||
class PUSHBULLET:
|
if len(keys) > 1: batch = True
|
||||||
|
|
||||||
|
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
|
||||||
|
|
||||||
|
if not response[api][u'code'] == u'200':
|
||||||
|
logger.error(u'Could not send notification to NotifyMyAndroid')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class PUSHBULLET(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.apikey = headphones.PUSHBULLET_APIKEY
|
self.apikey = headphones.PUSHBULLET_APIKEY
|
||||||
@@ -377,18 +476,17 @@ class PUSHBULLET:
|
|||||||
|
|
||||||
self.notify('Main Screen Activate', 'Test Message')
|
self.notify('Main Screen Activate', 'Test Message')
|
||||||
|
|
||||||
|
class PUSHALOT(object):
|
||||||
class PUSHALOT:
|
|
||||||
|
|
||||||
def notify(self, message, event):
|
def notify(self, message, event):
|
||||||
if not headphones.PUSHALOT_ENABLED:
|
if not headphones.PUSHALOT_ENABLED:
|
||||||
return
|
return
|
||||||
|
|
||||||
pushalot_authorizationtoken = headphones.PUSHALOT_APIKEY
|
pushalot_authorizationtoken = headphones.PUSHALOT_APIKEY
|
||||||
|
|
||||||
logger.debug(u"Pushalot event: " + event)
|
logger.debug(u"Pushalot event: " + event)
|
||||||
logger.debug(u"Pushalot message: " + message)
|
logger.debug(u"Pushalot message: " + message)
|
||||||
logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
|
logger.debug(u"Pushalot api: " + pushalot_authorizationtoken)
|
||||||
|
|
||||||
http_handler = HTTPSConnection("pushalot.com")
|
http_handler = HTTPSConnection("pushalot.com")
|
||||||
|
|
||||||
@@ -417,7 +515,7 @@ class PUSHALOT:
|
|||||||
logger.info(u"Pushalot notification failed.")
|
logger.info(u"Pushalot notification failed.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Synoindex:
|
class Synoindex(object):
|
||||||
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
||||||
self.util_loc = util_loc
|
self.util_loc = util_loc
|
||||||
|
|
||||||
@@ -452,17 +550,18 @@ class Synoindex:
|
|||||||
if isinstance(path_list, list):
|
if isinstance(path_list, list):
|
||||||
for path in path_list:
|
for path in path_list:
|
||||||
self.notify(path)
|
self.notify(path)
|
||||||
class PUSHOVER:
|
|
||||||
|
|
||||||
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
class PUSHOVER(object):
|
||||||
keys = []
|
|
||||||
priority = []
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.enabled = headphones.PUSHOVER_ENABLED
|
self.enabled = headphones.PUSHOVER_ENABLED
|
||||||
self.keys = headphones.PUSHOVER_KEYS
|
self.keys = headphones.PUSHOVER_KEYS
|
||||||
self.priority = headphones.PUSHOVER_PRIORITY
|
self.priority = headphones.PUSHOVER_PRIORITY
|
||||||
pass
|
|
||||||
|
if headphones.PUSHOVER_APITOKEN:
|
||||||
|
self.application_token = headphones.PUSHOVER_APITOKEN
|
||||||
|
else:
|
||||||
|
self.application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||||
|
|
||||||
def conf(self, options):
|
def conf(self, options):
|
||||||
return cherrypy.config['config'].get('Pushover', options)
|
return cherrypy.config['config'].get('Pushover', options)
|
||||||
@@ -504,23 +603,23 @@ class PUSHOVER:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def test(self, keys, priority):
|
def test(self, keys, priority):
|
||||||
|
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.keys = keys
|
self.keys = keys
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
self.notify('Main Screen Activate', 'Test Message')
|
self.notify('Main Screen Activate', 'Test Message')
|
||||||
|
|
||||||
class TwitterNotifier:
|
class TwitterNotifier(object):
|
||||||
|
|
||||||
consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
|
||||||
consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
|
||||||
|
|
||||||
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
||||||
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
|
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
|
||||||
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
|
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
|
||||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||||
|
self.consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
||||||
|
|
||||||
def notify_snatch(self, title):
|
def notify_snatch(self, title):
|
||||||
if headphones.TWITTER_ONSNATCH:
|
if headphones.TWITTER_ONSNATCH:
|
||||||
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
|
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
|
||||||
@@ -614,4 +713,102 @@ class TwitterNotifier:
|
|||||||
|
|
||||||
return self._send_tweet(prefix+": "+message)
|
return self._send_tweet(prefix+": "+message)
|
||||||
|
|
||||||
notifier = TwitterNotifier
|
class OSX_NOTIFY(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
self.objc = __import__("objc")
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def swizzle(self, cls, SEL, func):
|
||||||
|
old_IMP = cls.instanceMethodForSelector_(SEL)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
return func(self, old_IMP, *args, **kwargs)
|
||||||
|
new_IMP = self.objc.selector(wrapper, selector=old_IMP.selector,
|
||||||
|
signature=old_IMP.signature)
|
||||||
|
self.objc.classAddMethod(cls, SEL, new_IMP)
|
||||||
|
|
||||||
|
def notify(self, title, subtitle=None, text=None, sound=True):
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.swizzle(self.objc.lookUpClass('NSBundle'),
|
||||||
|
b'bundleIdentifier',
|
||||||
|
self.swizzled_bundleIdentifier)
|
||||||
|
|
||||||
|
NSUserNotification = self.objc.lookUpClass('NSUserNotification')
|
||||||
|
NSUserNotificationCenter = self.objc.lookUpClass('NSUserNotificationCenter')
|
||||||
|
NSAutoreleasePool = self.objc.lookUpClass('NSAutoreleasePool')
|
||||||
|
|
||||||
|
if not NSUserNotification or not NSUserNotificationCenter:
|
||||||
|
return False
|
||||||
|
|
||||||
|
pool = NSAutoreleasePool.alloc().init()
|
||||||
|
|
||||||
|
notification = NSUserNotification.alloc().init()
|
||||||
|
notification.setTitle_(title)
|
||||||
|
if subtitle:
|
||||||
|
notification.setSubtitle_(subtitle)
|
||||||
|
if text:
|
||||||
|
notification.setInformativeText_(text)
|
||||||
|
if sound:
|
||||||
|
notification.setSoundName_("NSUserNotificationDefaultSoundName")
|
||||||
|
notification.setHasActionButton_(False)
|
||||||
|
|
||||||
|
notification_center = NSUserNotificationCenter.defaultUserNotificationCenter()
|
||||||
|
notification_center.deliverNotification_(notification)
|
||||||
|
|
||||||
|
del pool
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception, e:
|
||||||
|
logger.warn('Error sending OS X Notification: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def swizzled_bundleIdentifier(self, original, swizzled):
|
||||||
|
return 'ade.headphones.osxnotify'
|
||||||
|
|
||||||
|
class BOXCAR(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.url = 'https://new.boxcar.io/api/notifications'
|
||||||
|
|
||||||
|
def notify(self, title, message, rgid=None):
|
||||||
|
try:
|
||||||
|
if rgid:
|
||||||
|
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
||||||
|
|
||||||
|
data = urllib.urlencode({
|
||||||
|
'user_credentials': headphones.BOXCAR_TOKEN,
|
||||||
|
'notification[title]': title.encode('utf-8'),
|
||||||
|
'notification[long_message]': message.encode('utf-8'),
|
||||||
|
'notification[sound]': "done"
|
||||||
|
})
|
||||||
|
|
||||||
|
req = urllib2.Request(self.url)
|
||||||
|
handle = urllib2.urlopen(req, data)
|
||||||
|
handle.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
except urllib2.URLError as e:
|
||||||
|
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
class SubSonicNotifier(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.host = headphones.SUBSONIC_HOST
|
||||||
|
self.username = headphones.SUBSONIC_USERNAME
|
||||||
|
self.password = headphones.SUBSONIC_PASSWORD
|
||||||
|
|
||||||
|
def notify(self, albumpaths):
|
||||||
|
# Correct URL
|
||||||
|
if not self.host.lower().startswith("http"):
|
||||||
|
self.host = "http://" + self.host
|
||||||
|
|
||||||
|
if not self.host.lower().endswith("/"):
|
||||||
|
self.host = self.host + "/"
|
||||||
|
|
||||||
|
# Invoke request
|
||||||
|
request.request_response(self.host + "musicFolderSettings.view?scanNow",
|
||||||
|
auth=(self.username, self.password))
|
||||||
@@ -35,20 +35,28 @@ from headphones import logger
|
|||||||
def sendNZB(nzb):
|
def sendNZB(nzb):
|
||||||
|
|
||||||
addToTop = False
|
addToTop = False
|
||||||
nzbgetXMLrpc = "http://%(username)s:%(password)s@%(host)s/xmlrpc"
|
nzbgetXMLrpc = "%(username)s:%(password)s@%(host)s/xmlrpc"
|
||||||
|
|
||||||
if headphones.NZBGET_HOST == None:
|
if headphones.NZBGET_HOST == None:
|
||||||
logger.error(u"No NZBget host found in configuration. Please configure it.")
|
logger.error(u"No NZBget host found in configuration. Please configure it.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if headphones.NZBGET_HOST.startswith('https://'):
|
||||||
|
nzbgetXMLrpc = 'https://' + nzbgetXMLrpc
|
||||||
|
headphones.NZBGET_HOST.replace('https://','',1)
|
||||||
|
else:
|
||||||
|
nzbgetXMLrpc = 'http://' + nzbgetXMLrpc
|
||||||
|
headphones.NZBGET_HOST.replace('http://','',1)
|
||||||
|
|
||||||
|
|
||||||
url = nzbgetXMLrpc % {"host": headphones.NZBGET_HOST, "username": headphones.NZBGET_USERNAME, "password": headphones.NZBGET_PASSWORD}
|
url = nzbgetXMLrpc % {"host": headphones.NZBGET_HOST, "username": headphones.NZBGET_USERNAME, "password": headphones.NZBGET_PASSWORD}
|
||||||
|
|
||||||
nzbGetRPC = xmlrpclib.ServerProxy(url)
|
nzbGetRPC = xmlrpclib.ServerProxy(url)
|
||||||
try:
|
try:
|
||||||
if nzbGetRPC.writelog("INFO", "headphones connected to drop of %s any moment now." % (nzb.name + ".nzb")):
|
if nzbGetRPC.writelog("INFO", "headphones connected to drop of %s any moment now." % (nzb.name + ".nzb")):
|
||||||
logger.debug(u"Successful connected to NZBget")
|
logger.debug(u"Successfully connected to NZBget")
|
||||||
else:
|
else:
|
||||||
logger.error(u"Successful connected to NZBget, but unable to send a message" % (nzb.name + ".nzb"))
|
logger.info(u"Successfully connected to NZBget, but unable to send a message" % (nzb.name + ".nzb"))
|
||||||
|
|
||||||
except httplib.socket.error:
|
except httplib.socket.error:
|
||||||
logger.error(u"Please check your NZBget host and port (if it is running). NZBget is not responding to this combination")
|
logger.error(u"Please check your NZBget host and port (if it is running). NZBget is not responding to this combination")
|
||||||
@@ -61,25 +69,60 @@ def sendNZB(nzb):
|
|||||||
logger.error(u"Protocol Error: " + e.errmsg)
|
logger.error(u"Protocol Error: " + e.errmsg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# if it's a normal result need to download the NZB content
|
nzbcontent64 = None
|
||||||
if nzb.resultType == "nzb":
|
if nzb.resultType == "nzbdata":
|
||||||
genProvider = GenericProvider("")
|
|
||||||
data = genProvider.getURL(nzb.url)
|
|
||||||
if (data == None):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# if we get a raw data result thats even better
|
|
||||||
elif nzb.resultType == "nzbdata":
|
|
||||||
data = nzb.extraInfo[0]
|
data = nzb.extraInfo[0]
|
||||||
|
nzbcontent64 = standard_b64encode(data)
|
||||||
|
|
||||||
nzbcontent64 = standard_b64encode(data)
|
logger.info(u"Sending NZB to NZBget")
|
||||||
|
|
||||||
logger.error(u"Sending NZB to NZBget")
|
|
||||||
logger.debug(u"URL: " + url)
|
logger.debug(u"URL: " + url)
|
||||||
|
|
||||||
if nzbGetRPC.append(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, addToTop, nzbcontent64):
|
dupekey = ""
|
||||||
logger.debug(u"NZB sent to NZBget successfully")
|
dupescore = 0
|
||||||
return True
|
|
||||||
else:
|
try:
|
||||||
logger.error(u"NZBget could not add %s to the queue" % (nzb.name + ".nzb"))
|
# Find out if nzbget supports priority (Version 9.0+), old versions beginning with a 0.x will use the old command
|
||||||
|
nzbget_version_str = nzbGetRPC.version()
|
||||||
|
nzbget_version = int(nzbget_version_str[:nzbget_version_str.find(".")])
|
||||||
|
if nzbget_version == 0:
|
||||||
|
if nzbcontent64 is not None:
|
||||||
|
nzbget_result = nzbGetRPC.append(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, addToTop, nzbcontent64)
|
||||||
|
else:
|
||||||
|
if nzb.resultType == "nzb":
|
||||||
|
genProvider = GenericProvider("")
|
||||||
|
data = genProvider.getURL(nzb.url)
|
||||||
|
if (data == None):
|
||||||
|
return False
|
||||||
|
nzbcontent64 = standard_b64encode(data)
|
||||||
|
nzbget_result = nzbGetRPC.append(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, addToTop, nzbcontent64)
|
||||||
|
elif nzbget_version == 12:
|
||||||
|
if nzbcontent64 is not None:
|
||||||
|
nzbget_result = nzbGetRPC.append(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, headphones.NZBGET_PRIORITY, False,
|
||||||
|
nzbcontent64, False, dupekey, dupescore, "score")
|
||||||
|
else:
|
||||||
|
nzbget_result = nzbGetRPC.appendurl(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, headphones.NZBGET_PRIORITY, False,
|
||||||
|
nzb.url, False, dupekey, dupescore, "score")
|
||||||
|
# v13+ has a new combined append method that accepts both (url and content)
|
||||||
|
# also the return value has changed from boolean to integer
|
||||||
|
# (Positive number representing NZBID of the queue item. 0 and negative numbers represent error codes.)
|
||||||
|
elif nzbget_version >= 13:
|
||||||
|
nzbget_result = True if nzbGetRPC.append(nzb.name + ".nzb", nzbcontent64 if nzbcontent64 is not None else nzb.url,
|
||||||
|
headphones.NZBGET_CATEGORY, headphones.NZBGET_PRIORITY, False, False, dupekey, dupescore,
|
||||||
|
"score") > 0 else False
|
||||||
|
else:
|
||||||
|
if nzbcontent64 is not None:
|
||||||
|
nzbget_result = nzbGetRPC.append(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, headphones.NZBGET_PRIORITY, False,
|
||||||
|
nzbcontent64)
|
||||||
|
else:
|
||||||
|
nzbget_result = nzbGetRPC.appendurl(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, headphones.NZBGET_PRIORITY, False,
|
||||||
|
nzb.url)
|
||||||
|
|
||||||
|
if nzbget_result:
|
||||||
|
logger.debug(u"NZB sent to NZBget successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(u"NZBget could not add %s to the queue" % (nzb.name + ".nzb"))
|
||||||
|
return False
|
||||||
|
except:
|
||||||
|
logger.error(u"Connect Error to NZBget: could not add %s to the queue" % (nzb.name + ".nzb"))
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -13,22 +13,20 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import re
|
||||||
import threading
|
import shutil
|
||||||
import music_encoder
|
|
||||||
import urllib, shutil, re
|
|
||||||
import uuid
|
import uuid
|
||||||
from headphones import notifiers
|
import beets
|
||||||
import lib.beets as beets
|
import threading
|
||||||
from lib.beets import autotag
|
|
||||||
from lib.beets.mediafile import MediaFile
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
from headphones import db, albumart, librarysync, lyrics, logger, helpers
|
|
||||||
from headphones.helpers import sab_replace_dots, sab_replace_spaces
|
from beets import autotag
|
||||||
|
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||||
|
|
||||||
|
from headphones import notifiers, utorrent, transmission
|
||||||
|
from headphones import db, albumart, librarysync, lyrics
|
||||||
|
from headphones import logger, helpers, request, mb, music_encoder
|
||||||
|
|
||||||
postprocessor_lock = threading.Lock()
|
postprocessor_lock = threading.Lock()
|
||||||
|
|
||||||
@@ -44,30 +42,18 @@ def checkFolder():
|
|||||||
if album['FolderName']:
|
if album['FolderName']:
|
||||||
|
|
||||||
if album['Kind'] == 'nzb':
|
if album['Kind'] == 'nzb':
|
||||||
# We're now checking sab config options after sending to determine renaming - but we'll keep the
|
download_dir = headphones.DOWNLOAD_DIR
|
||||||
# iterations in just in case we can't read the config for some reason
|
else:
|
||||||
|
download_dir = headphones.DOWNLOAD_TORRENT_DIR
|
||||||
|
|
||||||
nzb_album_possibilities = [ album['FolderName'],
|
album_path = os.path.join(download_dir, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
||||||
sab_replace_dots(album['FolderName']),
|
logger.info("Checking if %s exists" % album_path)
|
||||||
sab_replace_spaces(album['FolderName']),
|
if os.path.exists(album_path):
|
||||||
sab_replace_spaces(sab_replace_dots(album['FolderName']))
|
logger.info('Found "' + album['FolderName'] + '" in ' + album['Kind'] + ' download folder. Verifying....')
|
||||||
]
|
verify(album['AlbumID'], album_path, album['Kind'])
|
||||||
|
|
||||||
for nzb_folder_name in nzb_album_possibilities:
|
else:
|
||||||
|
logger.info("No folder name found for " + album['Title'])
|
||||||
nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, nzb_folder_name).encode(headphones.SYS_ENCODING, 'replace')
|
|
||||||
|
|
||||||
if os.path.exists(nzb_album_path):
|
|
||||||
logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName'])
|
|
||||||
verify(album['AlbumID'], nzb_album_path, 'nzb')
|
|
||||||
|
|
||||||
if album['Kind'] == 'torrent':
|
|
||||||
|
|
||||||
torrent_album_path = os.path.join(headphones.DOWNLOAD_TORRENT_DIR, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
|
||||||
|
|
||||||
if os.path.exists(torrent_album_path):
|
|
||||||
logger.debug('Found %s in torrent download folder. Verifying....' % album['FolderName'])
|
|
||||||
verify(album['AlbumID'], torrent_album_path, 'torrent')
|
|
||||||
|
|
||||||
def verify(albumid, albumpath, Kind=None, forced=False):
|
def verify(albumid, albumpath, Kind=None, forced=False):
|
||||||
|
|
||||||
@@ -76,29 +62,42 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||||
|
|
||||||
if not release or not tracks:
|
if not release or not tracks:
|
||||||
#the result of a manual post-process on an album that hasn't been inserted
|
|
||||||
#from an RSS feed or etc
|
|
||||||
#TODO: This should be a call to a class method.. copied it out of importer with only minor changes
|
|
||||||
#TODO: odd things can happen when there are diacritic characters in the folder name, need to translate them?
|
|
||||||
import mb
|
|
||||||
|
|
||||||
release_list = None
|
release_list = None
|
||||||
|
|
||||||
|
# Fetch album information from MusicBrainz
|
||||||
try:
|
try:
|
||||||
release_list = mb.getReleaseGroup(albumid)
|
release_list = mb.getReleaseGroup(albumid)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.info('Unable to get release information for manual album with rgid: %s. Error: %s' % (albumid, e))
|
logger.error('Unable to get release information for manual album with rgid: %s. Error: %s', albumid, e)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not release_list:
|
if not release_list:
|
||||||
logger.info('Unable to get release information for manual album with rgid: %s' % albumid)
|
logger.error('Unable to get release information for manual album with rgid: %s', albumid)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Since we're just using this to create the bare minimum information to insert an artist/album combo, use the first release
|
# Since we're just using this to create the bare minimum information to
|
||||||
|
# insert an artist/album combo, use the first release
|
||||||
releaseid = release_list[0]['id']
|
releaseid = release_list[0]['id']
|
||||||
|
|
||||||
release_dict = mb.getRelease(releaseid)
|
release_dict = mb.getRelease(releaseid)
|
||||||
|
|
||||||
|
if not release_dict:
|
||||||
|
logger.error('Unable to get release information for manual album with rgid: %s. Cannot continue', albumid)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if the artist is added to the database. In case the database is
|
||||||
|
# frozen during post processing, new artists will not be processed. This
|
||||||
|
# prevents new artists from appearing suddenly. In case forced is True,
|
||||||
|
# this check is skipped, since it is assumed the user wants this.
|
||||||
|
if headphones.FREEZE_DB and not forced:
|
||||||
|
artist = myDB.select("SELECT ArtistName, ArtistID FROM artists WHERE ArtistId=? OR ArtistName=?", [release_dict['artist_id'], release_dict['artist_name']])
|
||||||
|
|
||||||
|
if not artist:
|
||||||
|
logger.warn("Continuing would add new artist '%s' (ID %s), " \
|
||||||
|
"but database is frozen. Will skip postprocessing for " \
|
||||||
|
"album with rgid: %s", release_dict['artist_name'],
|
||||||
|
release_dict['artist_id'], albumid)
|
||||||
|
return
|
||||||
|
|
||||||
logger.info(u"Now adding/updating artist: " + release_dict['artist_name'])
|
logger.info(u"Now adding/updating artist: " + release_dict['artist_name'])
|
||||||
|
|
||||||
if release_dict['artist_name'].startswith('The '):
|
if release_dict['artist_name'].startswith('The '):
|
||||||
@@ -106,7 +105,6 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
else:
|
else:
|
||||||
sortname = release_dict['artist_name']
|
sortname = release_dict['artist_name']
|
||||||
|
|
||||||
|
|
||||||
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
||||||
newValueDict = {"ArtistName": release_dict['artist_name'],
|
newValueDict = {"ArtistName": release_dict['artist_name'],
|
||||||
"ArtistSortName": sortname,
|
"ArtistSortName": sortname,
|
||||||
@@ -125,6 +123,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
controlValueDict = {"AlbumID": albumid}
|
controlValueDict = {"AlbumID": albumid}
|
||||||
|
|
||||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||||
|
"ReleaseID": albumid,
|
||||||
"ArtistName": release_dict['artist_name'],
|
"ArtistName": release_dict['artist_name'],
|
||||||
"AlbumTitle": release_dict['title'],
|
"AlbumTitle": release_dict['title'],
|
||||||
"AlbumASIN": release_dict['asin'],
|
"AlbumASIN": release_dict['asin'],
|
||||||
@@ -173,7 +172,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
elif files.lower().endswith('.cue'):
|
elif files.lower().endswith('.cue'):
|
||||||
downloaded_cuecount += 1
|
downloaded_cuecount += 1
|
||||||
# if any of the files end in *.part, we know the torrent isn't done yet. Process if forced, though
|
# if any of the files end in *.part, we know the torrent isn't done yet. Process if forced, though
|
||||||
elif files.lower().endswith('.part') and not forced:
|
elif files.lower().endswith(('.part', '.utpart')) and not forced:
|
||||||
logger.info("Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING, 'replace') + " isn't complete yet. Will try again on the next run")
|
logger.info("Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING, 'replace') + " isn't complete yet. Will try again on the next run")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -316,7 +315,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.warn(u'Could not identify album: %s. It may not be the intended album.' % albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.warn(u'Could not identify album: %s. It may not be the intended album.' % albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE AlbumID=?', [albumid])
|
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid])
|
||||||
processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath)
|
processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath)
|
||||||
if not processed:
|
if not processed:
|
||||||
renameUnprocessedFolder(albumpath)
|
renameUnprocessedFolder(albumpath)
|
||||||
@@ -350,6 +349,27 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
downloaded_track_list.append(os.path.join(r, files))
|
downloaded_track_list.append(os.path.join(r, files))
|
||||||
elif files.lower().endswith('.cue'):
|
elif files.lower().endswith('.cue'):
|
||||||
downloaded_cuecount += 1
|
downloaded_cuecount += 1
|
||||||
|
|
||||||
|
# Check if files are valid media files and are writeable, before the steps
|
||||||
|
# below are executed. This simplifies errors and prevents unfinished steps.
|
||||||
|
for downloaded_track in downloaded_track_list:
|
||||||
|
try:
|
||||||
|
media_file = MediaFile(downloaded_track)
|
||||||
|
except (FileTypeError, UnreadableFileError):
|
||||||
|
logger.error("Track file is not a valid media file: %s. Not continuing.", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Not sure if line(s) below are needed, since it is possible to not
|
||||||
|
# touch any files.
|
||||||
|
if headphones.EMBED_ALBUM_ART or headphones.CLEANUP_FILES or \
|
||||||
|
headphones.ADD_ALBUM_ART or headphones.CORRECT_METADATA or \
|
||||||
|
headphones.EMBED_LYRICS or headphones.RENAME_FILES or \
|
||||||
|
headphones.MOVE_FILES:
|
||||||
|
|
||||||
|
if not os.access(downloaded_track, os.W_OK):
|
||||||
|
logger.error("Track file is not writeable, which is required for some post processing steps: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
return
|
||||||
|
|
||||||
#start encoding
|
#start encoding
|
||||||
if headphones.MUSIC_ENCODER:
|
if headphones.MUSIC_ENCODER:
|
||||||
downloaded_track_list=music_encoder.encode(albumpath)
|
downloaded_track_list=music_encoder.encode(albumpath)
|
||||||
@@ -357,12 +377,16 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
if not downloaded_track_list:
|
if not downloaded_track_list:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
artwork = None
|
||||||
album_art_path = albumart.getAlbumArt(albumid)
|
album_art_path = albumart.getAlbumArt(albumid)
|
||||||
|
|
||||||
if headphones.EMBED_ALBUM_ART or headphones.ADD_ALBUM_ART:
|
if headphones.EMBED_ALBUM_ART or headphones.ADD_ALBUM_ART:
|
||||||
|
|
||||||
if album_art_path:
|
if album_art_path:
|
||||||
artwork = urllib.urlopen(album_art_path).read()
|
artwork = request.request_content(album_art_path)
|
||||||
if not album_art_path or len(artwork) < 100:
|
else:
|
||||||
|
artwork = None
|
||||||
|
|
||||||
|
if not album_art_path or not artwork or len(artwork) < 100:
|
||||||
logger.info("No suitable album art found from Amazon. Checking Last.FM....")
|
logger.info("No suitable album art found from Amazon. Checking Last.FM....")
|
||||||
artwork = albumart.getCachedArt(albumid)
|
artwork = albumart.getCachedArt(albumid)
|
||||||
if not artwork or len(artwork) < 100:
|
if not artwork or len(artwork) < 100:
|
||||||
@@ -375,6 +399,9 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
if headphones.CLEANUP_FILES:
|
if headphones.CLEANUP_FILES:
|
||||||
cleanupFiles(albumpath)
|
cleanupFiles(albumpath)
|
||||||
|
|
||||||
|
if headphones.KEEP_NFO:
|
||||||
|
renameNFO(albumpath)
|
||||||
|
|
||||||
if headphones.ADD_ALBUM_ART and artwork:
|
if headphones.ADD_ALBUM_ART and artwork:
|
||||||
addAlbumArt(artwork, albumpath, release)
|
addAlbumArt(artwork, albumpath, release)
|
||||||
|
|
||||||
@@ -399,7 +426,25 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
|
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
|
||||||
myDB.action('UPDATE snatched SET status = "Processed" WHERE AlbumID=?', [albumid])
|
myDB.action('UPDATE snatched SET status = "Processed" WHERE Status NOT LIKE "Seed%" and AlbumID=?', [albumid])
|
||||||
|
|
||||||
|
# Check if torrent has finished seeding
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 1 or headphones.TORRENT_DOWNLOADER == 2:
|
||||||
|
seed_snatched = myDB.action('SELECT * from snatched WHERE Status="Seed_Snatched" and AlbumID=?', [albumid]).fetchone()
|
||||||
|
if seed_snatched:
|
||||||
|
hash = seed_snatched['FolderName']
|
||||||
|
torrent_removed = False
|
||||||
|
logger.info(u'%s - %s. Checking if torrent has finished seeding and can be removed' % (release['ArtistName'], release['AlbumTitle']))
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 1:
|
||||||
|
torrent_removed = transmission.removeTorrent(hash, True)
|
||||||
|
else:
|
||||||
|
torrent_removed = utorrent.removeTorrent(hash, True)
|
||||||
|
|
||||||
|
# Torrent removed, delete the snatched record, else update Status for scheduled job to check
|
||||||
|
if torrent_removed:
|
||||||
|
myDB.action('DELETE from snatched WHERE status = "Seed_Snatched" and AlbumID=?', [albumid])
|
||||||
|
else:
|
||||||
|
myDB.action('UPDATE snatched SET status = "Seed_Processed" WHERE status = "Seed_Snatched" and AlbumID=?', [albumid])
|
||||||
|
|
||||||
# Update the have tracks for all created dirs:
|
# Update the have tracks for all created dirs:
|
||||||
for albumpath in albumpaths:
|
for albumpath in albumpaths:
|
||||||
@@ -407,35 +452,49 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
|
|
||||||
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
|
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
|
||||||
|
|
||||||
|
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||||
|
statusmessage = "Download and Postprocessing completed"
|
||||||
|
|
||||||
|
if headphones.GROWL_ENABLED:
|
||||||
|
logger.info(u"Growl request")
|
||||||
|
growl = notifiers.GROWL()
|
||||||
|
growl.notify(pushmessage, statusmessage)
|
||||||
|
|
||||||
if headphones.PROWL_ENABLED:
|
if headphones.PROWL_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Prowl request")
|
logger.info(u"Prowl request")
|
||||||
prowl = notifiers.PROWL()
|
prowl = notifiers.PROWL()
|
||||||
prowl.notify(pushmessage,"Download and Postprocessing completed")
|
prowl.notify(pushmessage, statusmessage)
|
||||||
|
|
||||||
if headphones.XBMC_ENABLED:
|
if headphones.XBMC_ENABLED:
|
||||||
xbmc = notifiers.XBMC()
|
xbmc = notifiers.XBMC()
|
||||||
if headphones.XBMC_UPDATE:
|
if headphones.XBMC_UPDATE:
|
||||||
xbmc.update()
|
xbmc.update()
|
||||||
if headphones.XBMC_NOTIFY:
|
if headphones.XBMC_NOTIFY:
|
||||||
xbmc.notify(release['ArtistName'], release['AlbumTitle'], album_art_path)
|
xbmc.notify(release['ArtistName'],
|
||||||
|
release['AlbumTitle'],
|
||||||
|
album_art_path)
|
||||||
|
|
||||||
|
if headphones.LMS_ENABLED:
|
||||||
|
lms = notifiers.LMS()
|
||||||
|
lms.update()
|
||||||
|
|
||||||
if headphones.PLEX_ENABLED:
|
if headphones.PLEX_ENABLED:
|
||||||
plex = notifiers.Plex()
|
plex = notifiers.Plex()
|
||||||
if headphones.PLEX_UPDATE:
|
if headphones.PLEX_UPDATE:
|
||||||
plex.update()
|
plex.update()
|
||||||
if headphones.PLEX_NOTIFY:
|
if headphones.PLEX_NOTIFY:
|
||||||
plex.notify(release['ArtistName'], release['AlbumTitle'], album_art_path)
|
plex.notify(release['ArtistName'],
|
||||||
|
release['AlbumTitle'],
|
||||||
|
album_art_path)
|
||||||
|
|
||||||
if headphones.NMA_ENABLED:
|
if headphones.NMA_ENABLED:
|
||||||
nma = notifiers.NMA()
|
nma = notifiers.NMA()
|
||||||
nma.notify(release['ArtistName'], release['AlbumTitle'])
|
nma.notify(release['ArtistName'], release['AlbumTitle'])
|
||||||
|
|
||||||
if headphones.PUSHALOT_ENABLED:
|
if headphones.PUSHALOT_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Pushalot request")
|
logger.info(u"Pushalot request")
|
||||||
pushalot = notifiers.PUSHALOT()
|
pushalot = notifiers.PUSHALOT()
|
||||||
pushalot.notify(pushmessage,"Download and Postprocessing completed")
|
pushalot.notify(pushmessage, statusmessage)
|
||||||
|
|
||||||
if headphones.SYNOINDEX_ENABLED:
|
if headphones.SYNOINDEX_ENABLED:
|
||||||
syno = notifiers.Synoindex()
|
syno = notifiers.Synoindex()
|
||||||
@@ -443,23 +502,42 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
syno.notify(albumpath)
|
syno.notify(albumpath)
|
||||||
|
|
||||||
if headphones.PUSHOVER_ENABLED:
|
if headphones.PUSHOVER_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Pushover request")
|
logger.info(u"Pushover request")
|
||||||
pushover = notifiers.PUSHOVER()
|
pushover = notifiers.PUSHOVER()
|
||||||
pushover.notify(pushmessage,"Download and Postprocessing completed")
|
pushover.notify(pushmessage, "Headphones")
|
||||||
|
|
||||||
if headphones.PUSHBULLET_ENABLED:
|
if headphones.PUSHBULLET_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"PushBullet request")
|
logger.info(u"PushBullet request")
|
||||||
pushbullet = notifiers.PUSHBULLET()
|
pushbullet = notifiers.PUSHBULLET()
|
||||||
pushbullet.notify(pushmessage, "Download and Postprocessing completed")
|
pushbullet.notify(pushmessage, "Download and Postprocessing completed")
|
||||||
|
|
||||||
if headphones.TWITTER_ENABLED:
|
if headphones.TWITTER_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Sending Twitter notification")
|
logger.info(u"Sending Twitter notification")
|
||||||
twitter = notifiers.TwitterNotifier()
|
twitter = notifiers.TwitterNotifier()
|
||||||
twitter.notify_download(pushmessage)
|
twitter.notify_download(pushmessage)
|
||||||
|
|
||||||
|
if headphones.OSX_NOTIFY_ENABLED:
|
||||||
|
logger.info(u"Sending OS X notification")
|
||||||
|
osx_notify = notifiers.OSX_NOTIFY()
|
||||||
|
osx_notify.notify(release['ArtistName'],
|
||||||
|
release['AlbumTitle'],
|
||||||
|
statusmessage)
|
||||||
|
|
||||||
|
if headphones.BOXCAR_ENABLED:
|
||||||
|
logger.info(u"Sending Boxcar2 notification")
|
||||||
|
boxcar = notifiers.BOXCAR()
|
||||||
|
boxcar.notify('Headphones processed: ' + pushmessage,
|
||||||
|
statusmessage, release['AlbumID'])
|
||||||
|
|
||||||
|
if headphones.SUBSONIC_ENABLED:
|
||||||
|
logger.info(u"Sending Subsonic update")
|
||||||
|
subsonic = notifiers.SubSonicNotifier()
|
||||||
|
subsonic.notify(albumpaths)
|
||||||
|
|
||||||
|
if headphones.MPC_ENABLED:
|
||||||
|
mpc = notifiers.MPC()
|
||||||
|
mpc.notify()
|
||||||
|
|
||||||
def embedAlbumArt(artwork, downloaded_track_list):
|
def embedAlbumArt(artwork, downloaded_track_list):
|
||||||
logger.info('Embedding album art')
|
logger.info('Embedding album art')
|
||||||
|
|
||||||
@@ -471,8 +549,13 @@ def embedAlbumArt(artwork, downloaded_track_list):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
logger.debug('Adding album art to: %s' % downloaded_track)
|
logger.debug('Adding album art to: %s' % downloaded_track)
|
||||||
f.art = artwork
|
|
||||||
f.save()
|
try:
|
||||||
|
f.art = artwork
|
||||||
|
f.save()
|
||||||
|
except Exception, e:
|
||||||
|
logger.error(u'Error embedding album art to: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
|
||||||
|
continue
|
||||||
|
|
||||||
def addAlbumArt(artwork, albumpath, release):
|
def addAlbumArt(artwork, albumpath, release):
|
||||||
logger.info('Adding album art to folder')
|
logger.info('Adding album art to folder')
|
||||||
@@ -490,35 +573,49 @@ def addAlbumArt(artwork, albumpath, release):
|
|||||||
'$year': year
|
'$year': year
|
||||||
}
|
}
|
||||||
|
|
||||||
album_art_name = helpers.replace_all(headphones.ALBUM_ART_FORMAT.strip(), values).replace('/','_') + ".jpg"
|
album_art_name = helpers.replace_all(headphones.ALBUM_ART_FORMAT.strip(), values) + ".jpg"
|
||||||
|
|
||||||
album_art_name = album_art_name.replace('?','_').replace(':', '_').encode(headphones.SYS_ENCODING, 'replace')
|
album_art_name = helpers.replace_illegal_chars(album_art_name).encode(headphones.SYS_ENCODING, 'replace')
|
||||||
|
|
||||||
if headphones.FILE_UNDERSCORES:
|
if headphones.FILE_UNDERSCORES:
|
||||||
album_art_name = album_art_name.replace(' ', '_')
|
album_art_name = album_art_name.replace(' ', '_')
|
||||||
|
|
||||||
if album_art_name.startswith('.'):
|
if album_art_name.startswith('.'):
|
||||||
album_art_name = album_art_name.replace(0, '_')
|
album_art_name = album_art_name.replace(".", "_", 1)
|
||||||
|
|
||||||
prev = os.umask(headphones.UMASK)
|
try:
|
||||||
file = open(os.path.join(albumpath, album_art_name), 'wb')
|
with open(os.path.join(albumpath, album_art_name), 'wb') as f:
|
||||||
file.write(artwork)
|
f.write(artwork)
|
||||||
file.close()
|
except IOError as e:
|
||||||
os.umask(prev)
|
logger.error('Error saving album art: %s', e)
|
||||||
|
return
|
||||||
|
|
||||||
def cleanupFiles(albumpath):
|
def cleanupFiles(albumpath):
|
||||||
logger.info('Cleaning up files')
|
logger.info('Cleaning up files')
|
||||||
|
|
||||||
for r,d,f in os.walk(albumpath):
|
for r,d,f in os.walk(albumpath):
|
||||||
for files in f:
|
for files in f:
|
||||||
if not any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
if not any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||||
logger.debug('Removing: %s' % files)
|
logger.debug('Removing: %s' % files)
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(r, files))
|
os.remove(os.path.join(r, files))
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
|
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||||
|
|
||||||
def moveFiles(albumpath, release, tracks):
|
def renameNFO(albumpath):
|
||||||
|
logger.info('Renaming NFO')
|
||||||
|
|
||||||
|
for r,d,f in os.walk(albumpath):
|
||||||
|
for file in f:
|
||||||
|
if file.lower().endswith('.nfo'):
|
||||||
|
logger.debug('Renaming: "%s" to "%s"' % (file.decode(headphones.SYS_ENCODING, 'replace'), file.decode(headphones.SYS_ENCODING, 'replace') + '-orig'))
|
||||||
|
try:
|
||||||
|
new_file_name = os.path.join(r, file)[:-3] + 'orig.nfo'
|
||||||
|
os.rename(os.path.join(r, file), new_file_name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u'Could not rename file: %s. Error: %s' % (os.path.join(r, file).decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||||
|
|
||||||
|
def moveFiles(albumpath, release, tracks):
|
||||||
try:
|
try:
|
||||||
year = release['ReleaseDate'][:4]
|
year = release['ReleaseDate'][:4]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@@ -542,29 +639,38 @@ def moveFiles(albumpath, release, tracks):
|
|||||||
else:
|
else:
|
||||||
firstchar = sortname[0]
|
firstchar = sortname[0]
|
||||||
|
|
||||||
|
for r,d,f in os.walk(albumpath):
|
||||||
|
try:
|
||||||
|
origfolder = os.path.basename(os.path.normpath(r).decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
except:
|
||||||
|
origfolder = u''
|
||||||
|
|
||||||
values = { '$Artist': artist,
|
values = { '$Artist': artist,
|
||||||
'$SortArtist': sortname,
|
'$SortArtist': sortname,
|
||||||
'$Album': album,
|
'$Album': album,
|
||||||
'$Year': year,
|
'$Year': year,
|
||||||
'$Type': releasetype,
|
'$Type': releasetype,
|
||||||
|
'$OriginalFolder': origfolder,
|
||||||
'$First': firstchar.upper(),
|
'$First': firstchar.upper(),
|
||||||
'$artist': artist.lower(),
|
'$artist': artist.lower(),
|
||||||
'$sortartist': sortname.lower(),
|
'$sortartist': sortname.lower(),
|
||||||
'$album': album.lower(),
|
'$album': album.lower(),
|
||||||
'$year': year,
|
'$year': year,
|
||||||
'$type': releasetype.lower(),
|
'$type': releasetype.lower(),
|
||||||
'$first': firstchar.lower()
|
'$first': firstchar.lower(),
|
||||||
|
'$originalfolder': origfolder.lower()
|
||||||
}
|
}
|
||||||
|
|
||||||
folder = helpers.replace_all(headphones.FOLDER_FORMAT.strip(), values)
|
folder = helpers.replace_all(headphones.FOLDER_FORMAT.strip(), values, normalize=True)
|
||||||
folder = folder.replace('./', '_/').replace(':','_').replace('?','_').replace('/.','/_').replace('<','_').replace('>','_').replace('|','_')
|
|
||||||
|
folder = helpers.replace_illegal_chars(folder, type="folder")
|
||||||
|
folder = folder.replace('./', '_/').replace('/.','/_')
|
||||||
|
|
||||||
if folder.endswith('.'):
|
if folder.endswith('.'):
|
||||||
folder = folder.replace(folder[len(folder)-1], '_')
|
folder = folder[:-1] + '_'
|
||||||
|
|
||||||
if folder.startswith('.'):
|
if folder.startswith('.'):
|
||||||
folder = folder.replace(0, '_')
|
folder = '_' + folder[1:]
|
||||||
|
|
||||||
# Grab our list of files early on so we can determine if we need to create
|
# Grab our list of files early on so we can determine if we need to create
|
||||||
# the lossy_dest_dir, lossless_dest_dir, or both
|
# the lossy_dest_dir, lossless_dest_dir, or both
|
||||||
@@ -603,17 +709,27 @@ def moveFiles(albumpath, release, tracks):
|
|||||||
# Only rename the folder if they use the album name, otherwise merge into existing folder
|
# Only rename the folder if they use the album name, otherwise merge into existing folder
|
||||||
if os.path.exists(lossless_destination_path) and 'album' in last_folder.lower():
|
if os.path.exists(lossless_destination_path) and 'album' in last_folder.lower():
|
||||||
|
|
||||||
temp_folder = folder
|
create_duplicate_folder = False
|
||||||
|
|
||||||
i = 1
|
if headphones.REPLACE_EXISTING_FOLDERS:
|
||||||
while True:
|
try:
|
||||||
newfolder = temp_folder + '[%i]' % i
|
shutil.rmtree(lossless_destination_path)
|
||||||
lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
|
except Exception, e:
|
||||||
if os.path.exists(lossless_destination_path):
|
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossless_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||||
i += 1
|
create_duplicate_folder = True
|
||||||
else:
|
|
||||||
temp_folder = newfolder
|
if not headphones.REPLACE_EXISTING_FOLDERS or create_duplicate_folder:
|
||||||
break
|
temp_folder = folder
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
newfolder = temp_folder + '[%i]' % i
|
||||||
|
lossless_destination_path = os.path.normpath(os.path.join(headphones.LOSSLESS_DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
|
||||||
|
if os.path.exists(lossless_destination_path):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
temp_folder = newfolder
|
||||||
|
break
|
||||||
|
|
||||||
if not os.path.exists(lossless_destination_path):
|
if not os.path.exists(lossless_destination_path):
|
||||||
try:
|
try:
|
||||||
@@ -626,17 +742,27 @@ def moveFiles(albumpath, release, tracks):
|
|||||||
if make_lossy_folder:
|
if make_lossy_folder:
|
||||||
if os.path.exists(lossy_destination_path) and 'album' in last_folder.lower():
|
if os.path.exists(lossy_destination_path) and 'album' in last_folder.lower():
|
||||||
|
|
||||||
temp_folder = folder
|
create_duplicate_folder = False
|
||||||
|
|
||||||
i = 1
|
if headphones.REPLACE_EXISTING_FOLDERS:
|
||||||
while True:
|
try:
|
||||||
newfolder = temp_folder + '[%i]' % i
|
shutil.rmtree(lossy_destination_path)
|
||||||
lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
|
except Exception, e:
|
||||||
if os.path.exists(lossy_destination_path):
|
logger.error("Error deleting existing folder: %s. Creating duplicate folder. Error: %s" % (lossy_destination_path.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||||
i += 1
|
create_duplicate_folder = True
|
||||||
else:
|
|
||||||
temp_folder = newfolder
|
if not headphones.REPLACE_EXISTING_FOLDERS or create_duplicate_folder:
|
||||||
break
|
temp_folder = folder
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
newfolder = temp_folder + '[%i]' % i
|
||||||
|
lossy_destination_path = os.path.normpath(os.path.join(headphones.DESTINATION_DIR, newfolder)).encode(headphones.SYS_ENCODING, 'replace')
|
||||||
|
if os.path.exists(lossy_destination_path):
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
temp_folder = newfolder
|
||||||
|
break
|
||||||
|
|
||||||
if not os.path.exists(lossy_destination_path):
|
if not os.path.exists(lossy_destination_path):
|
||||||
try:
|
try:
|
||||||
@@ -703,13 +829,13 @@ def moveFiles(albumpath, release, tracks):
|
|||||||
try:
|
try:
|
||||||
os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING, 'replace'), int(headphones.FOLDER_PERMISSIONS, 8))
|
os.chmod(os.path.normpath(temp_f).encode(headphones.SYS_ENCODING, 'replace'), int(headphones.FOLDER_PERMISSIONS, 8))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error("Error trying to change permissions on folder: %s" % temp_f.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.error("Error trying to change permissions on folder: %s. %s", temp_f, e)
|
||||||
|
|
||||||
# If we failed to move all the files out of the directory, this will fail too
|
# If we failed to move all the files out of the directory, this will fail too
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(albumpath)
|
shutil.rmtree(albumpath)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error('Could not remove directory: %s. %s' % (albumpath.decode(headphones.SYS_ENCODING, 'replace'), e))
|
logger.error('Could not remove directory: %s. %s', albumpath, e)
|
||||||
|
|
||||||
destination_paths = []
|
destination_paths = []
|
||||||
|
|
||||||
@@ -736,10 +862,10 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
|||||||
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
|
elif any(downloaded_track.lower().endswith('.' + x.lower()) for x in headphones.LOSSY_MEDIA_FORMATS):
|
||||||
lossy_items.append(beets.library.Item.from_path(downloaded_track))
|
lossy_items.append(beets.library.Item.from_path(downloaded_track))
|
||||||
else:
|
else:
|
||||||
logger.warn("Skipping: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " because it is not a mutagen friendly file format")
|
logger.warn("Skipping: %s because it is not a mutagen friendly file format", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
|
||||||
logger.error("Beets couldn't create an Item from: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " - not a media file?" + str(e))
|
logger.error("Beets couldn't create an Item from: %s - not a media file? %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e))
|
||||||
|
|
||||||
for items in [lossy_items, lossless_items]:
|
for items in [lossy_items, lossless_items]:
|
||||||
|
|
||||||
@@ -749,16 +875,16 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
|||||||
try:
|
try:
|
||||||
cur_artist, cur_album, candidates, rec = autotag.tag_album(items, search_artist=helpers.latinToAscii(release['ArtistName']), search_album=helpers.latinToAscii(release['AlbumTitle']))
|
cur_artist, cur_album, candidates, rec = autotag.tag_album(items, search_artist=helpers.latinToAscii(release['ArtistName']), search_album=helpers.latinToAscii(release['AlbumTitle']))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error('Error getting recommendation: %s. Not writing metadata' % e)
|
logger.error('Error getting recommendation: %s. Not writing metadata', e)
|
||||||
return
|
return
|
||||||
if rec == 'RECOMMEND_NONE':
|
if str(rec) == 'recommendation.none':
|
||||||
logger.warn('No accurate album match found for %s, %s - not writing metadata' % (release['ArtistName'], release['AlbumTitle']))
|
logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle'])
|
||||||
return
|
return
|
||||||
|
|
||||||
if candidates:
|
if candidates:
|
||||||
dist, info, mapping, extra_items, extra_tracks = candidates[0]
|
dist, info, mapping, extra_items, extra_tracks = candidates[0]
|
||||||
else:
|
else:
|
||||||
logger.warn('No accurate album match found for %s, %s - not writing metadata' % (release['ArtistName'], release['AlbumTitle']))
|
logger.warn('No accurate album match found for %s, %s - not writing metadata', release['ArtistName'], release['AlbumTitle'])
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info('Beets recommendation for tagging items: %s' % rec)
|
logger.info('Beets recommendation for tagging items: %s' % rec)
|
||||||
@@ -770,9 +896,9 @@ def correctMetadata(albumid, release, downloaded_track_list):
|
|||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
item.write()
|
item.write()
|
||||||
logger.info("Successfully applied metadata to: " + item.path.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info("Successfully applied metadata to: %s", item.path.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn("Error writing metadata to " + item.path.decode(headphones.SYS_ENCODING, 'replace') + ": " + str(e))
|
logger.warn("Error writing metadata to '%s': %s", item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e))
|
||||||
|
|
||||||
def embedLyrics(downloaded_track_list):
|
def embedLyrics(downloaded_track_list):
|
||||||
logger.info('Adding lyrics')
|
logger.info('Adding lyrics')
|
||||||
@@ -780,11 +906,11 @@ def embedLyrics(downloaded_track_list):
|
|||||||
# TODO: If adding lyrics for flac & lossy, only fetch the lyrics once
|
# TODO: If adding lyrics for flac & lossy, only fetch the lyrics once
|
||||||
# and apply it to both files
|
# and apply it to both files
|
||||||
for downloaded_track in downloaded_track_list:
|
for downloaded_track in downloaded_track_list:
|
||||||
|
track_title = downloaded_track.decode(headphones.SYS_ENCODING, 'replace')
|
||||||
try:
|
try:
|
||||||
f = MediaFile(downloaded_track)
|
f = MediaFile(downloaded_track)
|
||||||
except:
|
except:
|
||||||
logger.error('Could not read %s. Not checking lyrics' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.error('Could not read %s. Not checking lyrics', track_title)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if f.albumartist and f.title:
|
if f.albumartist and f.title:
|
||||||
@@ -792,13 +918,20 @@ def embedLyrics(downloaded_track_list):
|
|||||||
elif f.artist and f.title:
|
elif f.artist and f.title:
|
||||||
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
||||||
else:
|
else:
|
||||||
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics', track_title)
|
||||||
metalyrics = None
|
metalyrics = None
|
||||||
|
|
||||||
if lyrics:
|
if metalyrics:
|
||||||
logger.debug('Adding lyrics to: %s' % downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.debug('Adding lyrics to: %s', track_title)
|
||||||
f.lyrics = metalyrics
|
f.lyrics = metalyrics
|
||||||
f.save()
|
try:
|
||||||
|
f.save()
|
||||||
|
except:
|
||||||
|
logger.error('Cannot save lyrics to: %s. Skipping', track_title)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.debug('No lyrics found for track: %s', track_title)
|
||||||
|
|
||||||
|
|
||||||
def renameFiles(albumpath, downloaded_track_list, release):
|
def renameFiles(albumpath, downloaded_track_list, release):
|
||||||
logger.info('Renaming files')
|
logger.info('Renaming files')
|
||||||
@@ -812,7 +945,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
|||||||
try:
|
try:
|
||||||
f = MediaFile(downloaded_track)
|
f = MediaFile(downloaded_track)
|
||||||
except:
|
except:
|
||||||
logger.info("MediaFile couldn't parse: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info("MediaFile couldn't parse: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not f.disc:
|
if not f.disc:
|
||||||
@@ -867,14 +1000,13 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
|||||||
new_file_name = helpers.replace_all(headphones.FILE_FORMAT.strip(), values).replace('/','_') + ext
|
new_file_name = helpers.replace_all(headphones.FILE_FORMAT.strip(), values).replace('/','_') + ext
|
||||||
|
|
||||||
|
|
||||||
new_file_name = new_file_name.replace('?','_').replace(':', '_').encode(headphones.SYS_ENCODING, 'replace')
|
new_file_name = helpers.replace_illegal_chars(new_file_name).encode(headphones.SYS_ENCODING, 'replace')
|
||||||
new_file_name = new_file_name.replace('*','_')
|
|
||||||
|
|
||||||
if headphones.FILE_UNDERSCORES:
|
if headphones.FILE_UNDERSCORES:
|
||||||
new_file_name = new_file_name.replace(' ', '_')
|
new_file_name = new_file_name.replace(' ', '_')
|
||||||
|
|
||||||
if new_file_name.startswith('.'):
|
if new_file_name.startswith('.'):
|
||||||
new_file_name = new_file_name.replace(0, '_')
|
new_file_name = new_file_name.replace(".", "_", 1)
|
||||||
|
|
||||||
new_file = os.path.join(albumpath, new_file_name)
|
new_file = os.path.join(albumpath, new_file_name)
|
||||||
|
|
||||||
@@ -882,24 +1014,24 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
|||||||
logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " is not neccessary")
|
logger.debug("Renaming for: " + downloaded_track.decode(headphones.SYS_ENCODING, 'replace') + " is not neccessary")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logger.debug('Renaming %s ---> %s' % (downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace')))
|
logger.debug('Renaming %s ---> %s', downloaded_track.decode(headphones.SYS_ENCODING,'replace'), new_file_name.decode(headphones.SYS_ENCODING,'replace'))
|
||||||
try:
|
try:
|
||||||
os.rename(downloaded_track, new_file)
|
os.rename(downloaded_track, new_file)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error('Error renaming file: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e))
|
logger.error('Error renaming file: %s. Error: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def updateFilePermissions(albumpaths):
|
def updateFilePermissions(albumpaths):
|
||||||
|
|
||||||
for folder in albumpaths:
|
for folder in albumpaths:
|
||||||
logger.info("Updating file permissions in " + folder.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info("Updating file permissions in %s", folder)
|
||||||
for r,d,f in os.walk(folder):
|
for r,d,f in os.walk(folder):
|
||||||
for files in f:
|
for files in f:
|
||||||
full_path = os.path.join(r, files)
|
full_path = os.path.join(r, files)
|
||||||
try:
|
try:
|
||||||
os.chmod(full_path, int(headphones.FILE_PERMISSIONS, 8))
|
os.chmod(full_path, int(headphones.FILE_PERMISSIONS, 8))
|
||||||
except:
|
except:
|
||||||
logger.error("Could not change permissions for file: " + full_path.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.error("Could not change permissions for file: %s", full_path)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def renameUnprocessedFolder(albumpath):
|
def renameUnprocessedFolder(albumpath):
|
||||||
@@ -918,100 +1050,166 @@ def renameUnprocessedFolder(albumpath):
|
|||||||
os.rename(albumpath, new_folder_name)
|
os.rename(albumpath, new_folder_name)
|
||||||
return
|
return
|
||||||
|
|
||||||
def forcePostProcess():
|
def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
|
||||||
|
|
||||||
download_dirs = []
|
if album_dir:
|
||||||
if headphones.DOWNLOAD_DIR:
|
folders = [album_dir.encode(headphones.SYS_ENCODING, 'replace')]
|
||||||
download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING, 'replace'))
|
|
||||||
if headphones.DOWNLOAD_TORRENT_DIR:
|
|
||||||
download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace'))
|
|
||||||
|
|
||||||
# If DOWNLOAD_DIR and DOWNLOAD_TORRENT_DIR are the same, remove the duplicate to prevent us from trying to process the same folder twice.
|
|
||||||
download_dirs = list(set(download_dirs))
|
|
||||||
|
|
||||||
logger.info('Checking to see if there are any folders to process in download_dir(s): %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace'))
|
|
||||||
# Get a list of folders in the download_dir
|
|
||||||
folders = []
|
|
||||||
for download_dir in download_dirs:
|
|
||||||
if not os.path.isdir(download_dir):
|
|
||||||
logger.warn('Directory ' + download_dir.decode(headphones.SYS_ENCODING, 'replace') + ' does not exist. Skipping')
|
|
||||||
continue
|
|
||||||
for folder in os.listdir(download_dir):
|
|
||||||
path_to_folder = os.path.join(download_dir, folder)
|
|
||||||
if os.path.isdir(path_to_folder):
|
|
||||||
folders.append(path_to_folder)
|
|
||||||
|
|
||||||
if len(folders):
|
|
||||||
logger.info('Found %i folders to process' % len(folders))
|
|
||||||
else:
|
else:
|
||||||
logger.info('Found no folders to process in: %s' % str(download_dirs).decode(headphones.SYS_ENCODING, 'replace'))
|
download_dirs = []
|
||||||
|
if dir:
|
||||||
|
download_dirs.append(dir.encode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
if headphones.DOWNLOAD_DIR and not dir:
|
||||||
|
download_dirs.append(headphones.DOWNLOAD_DIR.encode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
if headphones.DOWNLOAD_TORRENT_DIR and not dir:
|
||||||
|
download_dirs.append(headphones.DOWNLOAD_TORRENT_DIR.encode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
|
||||||
|
# If DOWNLOAD_DIR and DOWNLOAD_TORRENT_DIR are the same, remove the duplicate to prevent us from trying to process the same folder twice.
|
||||||
|
download_dirs = list(set(download_dirs))
|
||||||
|
|
||||||
|
logger.info('Checking to see if there are any folders to process in download_dir(s): %s', download_dirs)
|
||||||
|
# Get a list of folders in the download_dir
|
||||||
|
folders = []
|
||||||
|
|
||||||
|
for download_dir in download_dirs:
|
||||||
|
if not os.path.isdir(download_dir):
|
||||||
|
logger.warn('Directory %s does not exist. Skipping', download_dir)
|
||||||
|
continue
|
||||||
|
for folder in os.listdir(download_dir):
|
||||||
|
path_to_folder = os.path.join(download_dir, folder)
|
||||||
|
|
||||||
|
if os.path.isdir(path_to_folder):
|
||||||
|
subfolders = helpers.expand_subfolders(path_to_folder)
|
||||||
|
|
||||||
|
if expand_subfolders and subfolders is not None:
|
||||||
|
folders.extend(subfolders)
|
||||||
|
else:
|
||||||
|
folders.append(path_to_folder)
|
||||||
|
|
||||||
|
if len(folders):
|
||||||
|
logger.info('Found %i folders to process', len(folders))
|
||||||
|
else:
|
||||||
|
logger.info('Found no folders to process in: %s', download_dirs)
|
||||||
|
|
||||||
# Parse the folder names to get artist album info
|
# Parse the folder names to get artist album info
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
|
|
||||||
folder_basename = os.path.basename(folder).decode(headphones.SYS_ENCODING, 'replace')
|
folder_basename = os.path.basename(folder).decode(headphones.SYS_ENCODING, 'replace')
|
||||||
|
logger.info('Processing: %s', folder_basename)
|
||||||
|
|
||||||
logger.info('Processing: %s' % folder_basename)
|
# Attempt 1: First try to see if there's a match in the snatched table,
|
||||||
|
# then we'll try to parse the foldername.
|
||||||
# First try to see if there's a match in the snatched table, then we'll try to parse the foldername
|
# TODO: Iterate through underscores -> spaces, spaces -> dots,
|
||||||
# TODO: Iterate through underscores -> spaces, spaces -> dots, underscores -> dots (this might be hit or miss since it assumes
|
# underscores -> dots (this might be hit or miss since it assumes all
|
||||||
# all spaces/underscores came from sab replacing values
|
# spaces/underscores came from sab replacing values
|
||||||
|
logger.debug('Attempting to find album in the snatched table')
|
||||||
snatched = myDB.action('SELECT AlbumID, Title, Kind, Status from snatched WHERE FolderName LIKE ?', [folder_basename]).fetchone()
|
snatched = myDB.action('SELECT AlbumID, Title, Kind, Status from snatched WHERE FolderName LIKE ?', [folder_basename]).fetchone()
|
||||||
|
|
||||||
if snatched:
|
if snatched:
|
||||||
if headphones.KEEP_TORRENT_FILES and snatched['Kind'] == 'torrent' and snatched['Status'] == 'Processed':
|
if headphones.KEEP_TORRENT_FILES and snatched['Kind'] == 'torrent' and snatched['Status'] == 'Processed':
|
||||||
logger.info(folder_basename + ' is a torrent folder being preserved for seeding and has already been processed. Skipping.')
|
logger.info('%s is a torrent folder being preserved for seeding and has already been processed. Skipping.', folder_basename)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
logger.info('Found a match in the database: %s. Verifying to make sure it is the correct album' % snatched['Title'])
|
logger.info('Found a match in the database: %s. Verifying to make sure it is the correct album', snatched['Title'])
|
||||||
verify(snatched['AlbumID'], folder, snatched['Kind'])
|
verify(snatched['AlbumID'], folder, snatched['Kind'])
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Try to parse the folder name into a valid format
|
# Attempt 2a: parse the folder name into a valid format
|
||||||
# TODO: Add metadata lookup
|
|
||||||
try:
|
try:
|
||||||
|
logger.debug('Attempting to extract name, album and year from folder name')
|
||||||
name, album, year = helpers.extract_data(folder_basename)
|
name, album, year = helpers.extract_data(folder_basename)
|
||||||
except:
|
except Exception as e:
|
||||||
name = None
|
name = album = year = None
|
||||||
|
|
||||||
if name and album and year:
|
|
||||||
|
|
||||||
|
if name and album:
|
||||||
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
|
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
|
||||||
if release:
|
if release:
|
||||||
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle']))
|
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
|
||||||
verify(release['AlbumID'], folder)
|
verify(release['AlbumID'], folder)
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
logger.info('Querying MusicBrainz for the release group id for: %s - %s' % (name, album))
|
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name, album)
|
||||||
from headphones import mb
|
|
||||||
try:
|
try:
|
||||||
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
||||||
except:
|
except:
|
||||||
logger.error('Can not get release information for this album')
|
logger.error('Can not get release information for this album')
|
||||||
continue
|
rgid = None
|
||||||
|
|
||||||
if rgid:
|
if rgid:
|
||||||
verify(rgid, folder)
|
verify(rgid, folder)
|
||||||
else:
|
|
||||||
logger.info('No match found on MusicBrainz for: %s - %s' % (name, album))
|
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
logger.info('No match found on MusicBrainz for: %s - %s', name, album)
|
||||||
|
|
||||||
else:
|
# Attempt 2b: deduce meta data into a valid format
|
||||||
try:
|
try:
|
||||||
possible_rgid = folder_basename[-36:]
|
logger.debug('Attempting to extract name, album and year from metadata')
|
||||||
# re pattern match: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
|
name, album, year = helpers.extract_metadata(folder)
|
||||||
rgid = uuid.UUID(possible_rgid)
|
except Exception as e:
|
||||||
|
name = album = year = None
|
||||||
|
|
||||||
except:
|
if name and album:
|
||||||
logger.info("Couldn't parse " + folder_basename + " into any valid format. If adding albums from another source, they must be in an 'Artist - Album [Year]' format, or end with the musicbrainz release group id")
|
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE ArtistName LIKE ? and AlbumTitle LIKE ?', [name, album]).fetchone()
|
||||||
|
if release:
|
||||||
|
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
|
||||||
|
verify(release['AlbumID'], folder)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.info('Querying MusicBrainz for the release group id for: %s - %s', name, album)
|
||||||
|
try:
|
||||||
|
rgid = mb.findAlbumID(helpers.latinToAscii(name), helpers.latinToAscii(album))
|
||||||
|
except:
|
||||||
|
logger.error('Can not get release information for this album')
|
||||||
|
rgid = None
|
||||||
|
|
||||||
|
if rgid:
|
||||||
|
verify(rgid, folder)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.info('No match found on MusicBrainz for: %s - %s', name, album)
|
||||||
|
|
||||||
|
# Attempt 3: strip release group id from filename
|
||||||
|
try:
|
||||||
|
logger.debug('Attempting to extract release group from folder name')
|
||||||
|
possible_rgid = folder_basename[-36:]
|
||||||
|
# re pattern match: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}
|
||||||
|
rgid = uuid.UUID(possible_rgid)
|
||||||
|
except:
|
||||||
|
logger.info("Couldn't parse '%s' into any valid format. If adding albums from another source, they must be in an 'Artist - Album [Year]' format, or end with the musicbrainz release group id", folder_basename)
|
||||||
|
rgid = possible_rgid = None
|
||||||
|
|
||||||
|
if rgid:
|
||||||
|
rgid = possible_rgid
|
||||||
|
release = myDB.action('SELECT ArtistName, AlbumTitle, AlbumID from albums WHERE AlbumID=?', [rgid]).fetchone()
|
||||||
|
if release:
|
||||||
|
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
|
||||||
|
verify(release['AlbumID'], folder, forced=True)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.info('Found a (possibly) valid Musicbrainz identifier in album folder name - continuing post-processing')
|
||||||
|
verify(rgid, folder, forced=True)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Attempt 4: Hail mary. Just assume the folder name is the album name if it doesn't have a separator in it
|
||||||
|
logger.debug('Attempt to extract album name by assuming it is the folder name')
|
||||||
|
|
||||||
if rgid:
|
if '-' not in folder_basename:
|
||||||
rgid = possible_rgid
|
release = myDB.action('SELECT AlbumID, ArtistName, AlbumTitle from albums WHERE AlbumTitle LIKE ?', [folder_basename]).fetchone()
|
||||||
release = myDB.action('SELECT ArtistName, AlbumTitle, AlbumID from albums WHERE AlbumID=?', [rgid]).fetchone()
|
if release:
|
||||||
if release:
|
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album', release['ArtistName'], release['AlbumTitle'])
|
||||||
logger.info('Found a match in the database: %s - %s. Verifying to make sure it is the correct album' % (release['ArtistName'], release['AlbumTitle']))
|
verify(release['AlbumID'], folder)
|
||||||
verify(release['AlbumID'], folder, forced=True)
|
continue
|
||||||
|
else:
|
||||||
|
logger.info('Querying MusicBrainz for the release group id for: %s', folder_basename)
|
||||||
|
try:
|
||||||
|
rgid = mb.findAlbumID(album=helpers.latinToAscii(folder_basename))
|
||||||
|
except:
|
||||||
|
logger.error('Can not get release information for this album')
|
||||||
|
rgid = None
|
||||||
|
|
||||||
|
if rgid:
|
||||||
|
verify(rgid, folder)
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
logger.info('Found a (possibly) valid Musicbrainz identifier in album folder name - continuing post-processing')
|
logger.info('No match found on MusicBrainz for: %s - %s', name, album)
|
||||||
verify(rgid, folder, forced=True)
|
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
# This file is part of Headphones.
|
||||||
|
#
|
||||||
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from headphones import logger
|
||||||
|
|
||||||
|
from xml.dom import minidom
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import feedparser
|
||||||
|
import headphones
|
||||||
|
|
||||||
|
def request_response(url, method="get", auto_raise=True,
|
||||||
|
whitelist_status_code=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Convenient wrapper for `requests.get', which will capture the exceptions and
|
||||||
|
log them. On success, the Response object is returned. In case of a
|
||||||
|
exception, None is returned.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Convert whitelist_status_code to a list if needed
|
||||||
|
if whitelist_status_code and type(whitelist_status_code) != list:
|
||||||
|
whitelist_status_code = [whitelist_status_code]
|
||||||
|
|
||||||
|
# Disable verification of SSL certificates if requested. Note: this could
|
||||||
|
# pose a security issue!
|
||||||
|
kwargs["verify"] = headphones.VERIFY_SSL_CERT
|
||||||
|
|
||||||
|
# Map method to the request.XXX method. This is a simple hack, but it allows
|
||||||
|
# requests to apply more magic per method. See lib/requests/api.py.
|
||||||
|
request_method = getattr(requests, method.lower())
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Request the URL
|
||||||
|
logger.debug("Requesting URL via %s method: %s", method.upper(), url)
|
||||||
|
response = request_method(url, **kwargs)
|
||||||
|
|
||||||
|
# If status code != OK, then raise exception, except if the status code
|
||||||
|
# is white listed.
|
||||||
|
if whitelist_status_code and auto_raise:
|
||||||
|
if response.status_code not in whitelist_status_code:
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except:
|
||||||
|
logger.debug("Response status code %d is not white " \
|
||||||
|
"listed, raised exception", response.status_code)
|
||||||
|
raise
|
||||||
|
elif auto_raise:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
return response
|
||||||
|
except requests.ConnectionError:
|
||||||
|
logger.error("Unable to connect to remote host. Check if the remote " \
|
||||||
|
"host is up and running.")
|
||||||
|
except requests.Timeout:
|
||||||
|
logger.error("Request timed out. The remote host did not respeond " \
|
||||||
|
"timely.")
|
||||||
|
except requests.HTTPError as e:
|
||||||
|
if e.response is not None:
|
||||||
|
if e.response.status_code >= 500:
|
||||||
|
cause = "remote server error"
|
||||||
|
elif e.response.status_code >= 400:
|
||||||
|
cause = "local client error"
|
||||||
|
else:
|
||||||
|
# I don't think we will end up here, but for completeness
|
||||||
|
cause = "unknown"
|
||||||
|
|
||||||
|
logger.error("Request raise HTTP error with status code %d (%s).",
|
||||||
|
e.response.status_code, cause)
|
||||||
|
|
||||||
|
# Debug response
|
||||||
|
if headphones.VERBOSE:
|
||||||
|
server_message(e.response)
|
||||||
|
else:
|
||||||
|
logger.error("Request raised HTTP error.")
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error("Request raised exception: %s", e)
|
||||||
|
|
||||||
|
def request_soup(url, **kwargs):
|
||||||
|
"""
|
||||||
|
Wrapper for `request_response', which will return a BeatifulSoup object if
|
||||||
|
no exceptions are raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parser = kwargs.pop("parser", "html5lib")
|
||||||
|
response = request_response(url, **kwargs)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
return BeautifulSoup(response.content, parser)
|
||||||
|
|
||||||
|
def request_minidom(url, **kwargs):
|
||||||
|
"""
|
||||||
|
Wrapper for `request_response', which will return a Minidom object if no
|
||||||
|
exceptions are raised.
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = request_response(url, **kwargs)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
return minidom.parseString(response.content)
|
||||||
|
|
||||||
|
def request_json(url, **kwargs):
|
||||||
|
"""
|
||||||
|
Wrapper for `request_response', which will decode the response as JSON
|
||||||
|
object and return the result, if no exceptions are raised.
|
||||||
|
|
||||||
|
As an option, a validator callback can be given, which should return True if
|
||||||
|
the result is valid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
validator = kwargs.pop("validator", None)
|
||||||
|
response = request_response(url, **kwargs)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
try:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if validator and not validator(result):
|
||||||
|
logger.error("JSON validation result failed")
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Response returned invalid JSON data")
|
||||||
|
|
||||||
|
# Debug response
|
||||||
|
if headphones.VERBOSE:
|
||||||
|
server_message(response)
|
||||||
|
|
||||||
|
def request_content(url, **kwargs):
|
||||||
|
"""
|
||||||
|
Wrapper for `request_response', which will return the raw content.
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = request_response(url, **kwargs)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
return response.content
|
||||||
|
|
||||||
|
def request_feed(url, **kwargs):
|
||||||
|
"""
|
||||||
|
Wrapper for `request_response', which will return a feed object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = request_response(url, **kwargs)
|
||||||
|
|
||||||
|
if response is not None:
|
||||||
|
return feedparser.parse(response.content)
|
||||||
|
|
||||||
|
def server_message(response):
|
||||||
|
"""
|
||||||
|
Extract server message from response and log in to logger with DEBUG level.
|
||||||
|
|
||||||
|
Some servers return extra information in the result. Try to parse it for
|
||||||
|
debugging purpose. Messages are limited to 150 characters, since it may
|
||||||
|
return the whole page in case of normal web page URLs
|
||||||
|
"""
|
||||||
|
|
||||||
|
message = None
|
||||||
|
|
||||||
|
# First attempt is to 'read' the response as HTML
|
||||||
|
if "text/html" in response.headers.get("content-type"):
|
||||||
|
try:
|
||||||
|
soup = BeautifulSoup(response.content, "html5lib")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Find body and cleanup common tags to grab content, which probably
|
||||||
|
# contains the message.
|
||||||
|
message = soup.find("body")
|
||||||
|
|
||||||
|
for element in ["header", "script", "footer", "nav", "input",
|
||||||
|
"textarea"]:
|
||||||
|
|
||||||
|
for tag in soup.find_all(element):
|
||||||
|
tag.replaceWith("")
|
||||||
|
|
||||||
|
message = message.text if message else soup.text
|
||||||
|
message = message.strip()
|
||||||
|
|
||||||
|
# Second attempt is to just take the response
|
||||||
|
if message is None:
|
||||||
|
message = response.content.strip()
|
||||||
|
|
||||||
|
if message:
|
||||||
|
# Truncate message if it is too long.
|
||||||
|
if len(message) > 150:
|
||||||
|
message = message[:150] + "..."
|
||||||
|
|
||||||
|
logger.debug("Server responded with message: %s", message)
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
## Stolen from Sick-Beard's sab.py ##
|
## Stolen from Sick-Beard's sab.py ##
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
import urllib, httplib
|
import MultipartPostHandler
|
||||||
import datetime
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
import datetime
|
||||||
from lib import MultipartPostHandler
|
import cookielib
|
||||||
import urllib2, cookielib
|
import urllib2
|
||||||
|
import httplib
|
||||||
|
import urllib
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
from headphones.common import USER_AGENT
|
from headphones.common import USER_AGENT
|
||||||
@@ -62,7 +62,7 @@ def sendNZB(nzb):
|
|||||||
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
||||||
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
||||||
params['mode'] = 'addfile'
|
params['mode'] = 'addfile'
|
||||||
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzbdata)}
|
multiPartParams = {"nzbfile": (helpers.latinToAscii(nzb.name)+".nzb", nzbdata)}
|
||||||
|
|
||||||
if not headphones.SAB_HOST.startswith('http'):
|
if not headphones.SAB_HOST.startswith('http'):
|
||||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||||
@@ -119,31 +119,6 @@ def sendNZB(nzb):
|
|||||||
|
|
||||||
if sabText == "ok":
|
if sabText == "ok":
|
||||||
logger.info(u"NZB sent to SAB successfully")
|
logger.info(u"NZB sent to SAB successfully")
|
||||||
if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
|
|
||||||
logger.info(u"Sending Prowl notification")
|
|
||||||
prowl = notifiers.PROWL()
|
|
||||||
prowl.notify(nzb.name,"Download started")
|
|
||||||
if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
|
|
||||||
logger.info(u"Sending Pushover notification")
|
|
||||||
prowl = notifiers.PUSHOVER()
|
|
||||||
prowl.notify(nzb.name,"Download started")
|
|
||||||
if headphones.PUSHBULLET_ENABLED and headphones.PUSHBULLET_ONSNATCH:
|
|
||||||
logger.info(u"Sending PushBullet notification")
|
|
||||||
pushbullet = notifiers.PUSHBULLET()
|
|
||||||
pushbullet.notify(nzb.name + " has been snatched!", "Download started")
|
|
||||||
if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
|
|
||||||
logger.info(u"Sending Twitter notification")
|
|
||||||
twitter = notifiers.TwitterNotifier()
|
|
||||||
twitter.notify_snatch(nzb.name)
|
|
||||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
|
||||||
logger.debug(u"Sending NMA notification")
|
|
||||||
nma = notifiers.NMA()
|
|
||||||
nma.notify(snatched_nzb=nzb.name)
|
|
||||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
|
||||||
logger.info(u"Sending Pushalot notification")
|
|
||||||
pushalot = notifiers.PUSHALOT()
|
|
||||||
pushalot.notify(nzb.name,"Download started")
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
elif sabText == "Missing authentication":
|
elif sabText == "Missing authentication":
|
||||||
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
||||||
|
|||||||
@@ -4,20 +4,26 @@
|
|||||||
# Headphones rutracker.org search
|
# Headphones rutracker.org search
|
||||||
# Functions called from searcher.py
|
# Functions called from searcher.py
|
||||||
|
|
||||||
import urllib
|
from headphones import logger, db, utorrent
|
||||||
import urllib2
|
|
||||||
import cookielib
|
from bencode import bencode as bencode, bdecode
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import headphones
|
|
||||||
from headphones import logger, db
|
|
||||||
import lib.bencode as bencode
|
|
||||||
import os
|
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
from hashlib import sha1
|
||||||
|
|
||||||
|
import headphones
|
||||||
|
import requests
|
||||||
|
import cookielib
|
||||||
|
import urllib2
|
||||||
|
import urllib
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
class Rutracker():
|
class Rutracker():
|
||||||
|
|
||||||
logged_in = False
|
logged_in = False
|
||||||
|
|
||||||
# Stores a number of login attempts to prevent recursion.
|
# Stores a number of login attempts to prevent recursion.
|
||||||
#login_counter = 0
|
#login_counter = 0
|
||||||
|
|
||||||
@@ -47,11 +53,10 @@ class Rutracker():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
||||||
except :
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Check if we're logged in
|
# Check if we're logged in
|
||||||
|
|
||||||
for cookie in self.cookiejar:
|
for cookie in self.cookiejar:
|
||||||
if cookie.name == 'bb_data':
|
if cookie.name == 'bb_data':
|
||||||
self.logged_in = True
|
self.logged_in = True
|
||||||
@@ -64,7 +69,6 @@ class Rutracker():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Build search url
|
# Build search url
|
||||||
|
|
||||||
searchterm = ''
|
searchterm = ''
|
||||||
if artist != 'Various Artists':
|
if artist != 'Various Artists':
|
||||||
searchterm = artist
|
searchterm = artist
|
||||||
@@ -83,14 +87,13 @@ class Rutracker():
|
|||||||
format = '+mp3||aac'
|
format = '+mp3||aac'
|
||||||
|
|
||||||
# sort by size, descending.
|
# sort by size, descending.
|
||||||
|
|
||||||
sort = '&o=7&s=2'
|
sort = '&o=7&s=2'
|
||||||
|
|
||||||
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
||||||
|
|
||||||
return searchurl
|
return searchurl
|
||||||
|
|
||||||
def search(self, searchurl, maxsize, minseeders, albumid, bitrate):
|
def search(self, searchurl, maxsize, minseeders, albumid):
|
||||||
"""
|
"""
|
||||||
Parse the search results and return valid torrent list
|
Parse the search results and return valid torrent list
|
||||||
"""
|
"""
|
||||||
@@ -111,25 +114,21 @@ class Rutracker():
|
|||||||
#logger.debug (soup.prettify())
|
#logger.debug (soup.prettify())
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
|
|
||||||
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
||||||
title = link.get_text()
|
title = link.get_text()
|
||||||
titles.append(title)
|
titles.append(title)
|
||||||
|
|
||||||
# Download URL
|
# Download URL
|
||||||
|
|
||||||
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
||||||
url = link.get('href')
|
url = link.get('href')
|
||||||
urls.append(url)
|
urls.append(url)
|
||||||
|
|
||||||
# Seeders
|
# Seeders
|
||||||
|
|
||||||
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
||||||
seeder = link.get_text()
|
seeder = link.get_text()
|
||||||
seeders.append(seeder)
|
seeders.append(seeder)
|
||||||
|
|
||||||
# Size
|
# Size
|
||||||
|
|
||||||
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
||||||
size = link.u.string
|
size = link.u.string
|
||||||
sizes.append(size)
|
sizes.append(size)
|
||||||
@@ -138,30 +137,33 @@ class Rutracker():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Combine lists
|
# Combine lists
|
||||||
|
|
||||||
torrentlist = zip(titles, urls, seeders, sizes)
|
torrentlist = zip(titles, urls, seeders, sizes)
|
||||||
|
|
||||||
# return if nothing found
|
# return if nothing found
|
||||||
|
|
||||||
if not torrentlist:
|
if not torrentlist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# get headphones track count for album, return if not found
|
# don't bother checking track counts anymore, let searcher filter instead
|
||||||
|
# leave code in just in case
|
||||||
|
check_track_count = False
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
if check_track_count:
|
||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
|
||||||
hptrackcount = len(tracks)
|
|
||||||
|
|
||||||
if not hptrackcount:
|
# get headphones track count for album, return if not found
|
||||||
logger.info('headphones track info not found, cannot compare to torrent')
|
myDB = db.DBConnection()
|
||||||
return False
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||||
|
hptrackcount = len(tracks)
|
||||||
|
|
||||||
# Return all valid entries, ignored, required words now checked in searcher.py
|
if not hptrackcount:
|
||||||
|
logger.info('headphones track info not found, cannot compare to torrent')
|
||||||
|
return False
|
||||||
|
|
||||||
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
# Return all valid entries, ignored, required words now checked in searcher.py
|
||||||
|
|
||||||
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
||||||
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
|
||||||
|
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
||||||
|
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
||||||
|
|
||||||
for torrent in torrentlist:
|
for torrent in torrentlist:
|
||||||
|
|
||||||
@@ -170,104 +172,101 @@ class Rutracker():
|
|||||||
seeders = torrent[2]
|
seeders = torrent[2]
|
||||||
size = torrent[3]
|
size = torrent[3]
|
||||||
|
|
||||||
title = returntitle.lower()
|
|
||||||
|
|
||||||
if int(size) <= maxsize and int(seeders) >= minseeders:
|
if int(size) <= maxsize and int(seeders) >= minseeders:
|
||||||
|
|
||||||
# Check torrent info
|
|
||||||
|
|
||||||
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
|
||||||
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
|
||||||
|
|
||||||
# Debug
|
|
||||||
#for cookie in self.cookiejar:
|
|
||||||
# logger.debug ('Cookie: %s' % cookie)
|
|
||||||
|
|
||||||
try:
|
|
||||||
page = self.opener.open(url)
|
|
||||||
torrent = page.read()
|
|
||||||
if torrent:
|
|
||||||
decoded = bencode.bdecode(torrent)
|
|
||||||
metainfo = decoded['info']
|
|
||||||
page.close ()
|
|
||||||
except Exception, e:
|
|
||||||
logger.error('Error getting torrent: %s' % e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# get torrent track count and check for cue
|
|
||||||
|
|
||||||
trackcount = 0
|
|
||||||
cuecount = 0
|
|
||||||
|
|
||||||
if 'files' in metainfo: # multi
|
|
||||||
for pathfile in metainfo['files']:
|
|
||||||
path = pathfile['path']
|
|
||||||
for file in path:
|
|
||||||
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
|
||||||
trackcount += 1
|
|
||||||
if '.cue' in file:
|
|
||||||
cuecount += 1
|
|
||||||
|
|
||||||
#Torrent topic page
|
#Torrent topic page
|
||||||
|
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
||||||
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
||||||
logger.debug ('torrent title: %s' % title)
|
|
||||||
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
|
||||||
logger.debug ('rutracker trackcount: %s' % trackcount)
|
|
||||||
|
|
||||||
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
# add to list
|
||||||
# This is for the case where we have a single .flac/.wav which can be split by cue
|
if not check_track_count:
|
||||||
# Not great, but shouldn't be doing this too often
|
|
||||||
|
|
||||||
totallogcount = 0
|
|
||||||
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
|
||||||
page = self.opener.open(topicurl, timeout=60)
|
|
||||||
soup = BeautifulSoup(page.read())
|
|
||||||
findtoc = soup.find_all(text='TOC of the extracted CD')
|
|
||||||
if not findtoc:
|
|
||||||
findtoc = soup.find_all(text='TOC извлечённого CD')
|
|
||||||
for toc in findtoc:
|
|
||||||
logcount = 0
|
|
||||||
for toccontent in toc.find_all_next(text=True):
|
|
||||||
cut_string = toccontent.split('|')
|
|
||||||
new_string = cut_string[0].lstrip().rstrip()
|
|
||||||
if new_string == '1' or new_string == '01':
|
|
||||||
logcount = 1
|
|
||||||
elif logcount > 0:
|
|
||||||
if new_string.isdigit():
|
|
||||||
logcount += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
totallogcount = totallogcount + logcount
|
|
||||||
|
|
||||||
if totallogcount > 0:
|
|
||||||
trackcount = totallogcount
|
|
||||||
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
|
||||||
|
|
||||||
# If torrent track count = hp track count then return torrent,
|
|
||||||
# if greater, check for deluxe/special/foreign editions
|
|
||||||
# if less, then allow if it's a single track with a cue
|
|
||||||
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if trackcount == hptrackcount:
|
|
||||||
valid = True
|
valid = True
|
||||||
elif trackcount > hptrackcount:
|
else:
|
||||||
if any(deluxe in title for deluxe in deluxelist):
|
|
||||||
|
# Check torrent info
|
||||||
|
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
#for cookie in self.cookiejar:
|
||||||
|
# logger.debug ('Cookie: %s' % cookie)
|
||||||
|
|
||||||
|
try:
|
||||||
|
page = self.opener.open(url)
|
||||||
|
torrent = page.read()
|
||||||
|
if torrent:
|
||||||
|
decoded = bdecode(torrent)
|
||||||
|
metainfo = decoded['info']
|
||||||
|
page.close ()
|
||||||
|
except Exception, e:
|
||||||
|
logger.error('Error getting torrent: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# get torrent track count and check for cue
|
||||||
|
trackcount = 0
|
||||||
|
cuecount = 0
|
||||||
|
|
||||||
|
if 'files' in metainfo: # multi
|
||||||
|
for pathfile in metainfo['files']:
|
||||||
|
path = pathfile['path']
|
||||||
|
for file in path:
|
||||||
|
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
||||||
|
trackcount += 1
|
||||||
|
if '.cue' in file:
|
||||||
|
cuecount += 1
|
||||||
|
|
||||||
|
title = returntitle.lower()
|
||||||
|
logger.debug ('torrent title: %s' % title)
|
||||||
|
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
||||||
|
logger.debug ('rutracker trackcount: %s' % trackcount)
|
||||||
|
|
||||||
|
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
||||||
|
# This is for the case where we have a single .flac/.wav which can be split by cue
|
||||||
|
# Not great, but shouldn't be doing this too often
|
||||||
|
totallogcount = 0
|
||||||
|
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
||||||
|
page = self.opener.open(topicurl, timeout=60)
|
||||||
|
soup = BeautifulSoup(page.read())
|
||||||
|
findtoc = soup.find_all(text='TOC of the extracted CD')
|
||||||
|
if not findtoc:
|
||||||
|
findtoc = soup.find_all(text='TOC извлечённого CD')
|
||||||
|
for toc in findtoc:
|
||||||
|
logcount = 0
|
||||||
|
for toccontent in toc.find_all_next(text=True):
|
||||||
|
cut_string = toccontent.split('|')
|
||||||
|
new_string = cut_string[0].lstrip().rstrip()
|
||||||
|
if new_string == '1' or new_string == '01':
|
||||||
|
logcount = 1
|
||||||
|
elif logcount > 0:
|
||||||
|
if new_string.isdigit():
|
||||||
|
logcount += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
totallogcount = totallogcount + logcount
|
||||||
|
|
||||||
|
if totallogcount > 0:
|
||||||
|
trackcount = totallogcount
|
||||||
|
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
||||||
|
|
||||||
|
# If torrent track count = hp track count then return torrent,
|
||||||
|
# if greater, check for deluxe/special/foreign editions
|
||||||
|
# if less, then allow if it's a single track with a cue
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
if trackcount == hptrackcount:
|
||||||
valid = True
|
valid = True
|
||||||
|
elif trackcount > hptrackcount:
|
||||||
|
if any(deluxe in title for deluxe in deluxelist):
|
||||||
|
valid = True
|
||||||
|
|
||||||
# Add to list
|
# Add to list
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
rulist.append((returntitle, size, topicurl))
|
rulist.append((returntitle, size, topicurl))
|
||||||
else:
|
else:
|
||||||
if topicurl:
|
if topicurl:
|
||||||
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
||||||
|
|
||||||
|
|
||||||
return rulist
|
return rulist
|
||||||
|
|
||||||
def get_torrent(self, url, savelocation=None):
|
def get_torrent(self, url, savelocation=None):
|
||||||
@@ -281,17 +280,70 @@ class Rutracker():
|
|||||||
prev = os.umask(headphones.UMASK)
|
prev = os.umask(headphones.UMASK)
|
||||||
page = self.opener.open(downloadurl)
|
page = self.opener.open(downloadurl)
|
||||||
torrent = page.read()
|
torrent = page.read()
|
||||||
|
decoded = bdecode(torrent)
|
||||||
|
metainfo = decoded['info']
|
||||||
|
tor_hash = sha1(bencode(metainfo)).hexdigest()
|
||||||
if savelocation:
|
if savelocation:
|
||||||
download_path = os.path.join(savelocation, torrent_name)
|
download_path = os.path.join(savelocation, torrent_name)
|
||||||
else:
|
else:
|
||||||
tempdir = mkdtemp(suffix='_rutracker_torrents')
|
tempdir = mkdtemp(suffix='_rutracker_torrents')
|
||||||
download_path = os.path.join(tempdir, torrent_name)
|
download_path = os.path.join(tempdir, torrent_name)
|
||||||
fp = open (download_path, 'wb')
|
|
||||||
fp.write (torrent)
|
with open(download_path, 'wb') as f:
|
||||||
fp.close ()
|
f.write(torrent)
|
||||||
os.umask(prev)
|
os.umask(prev)
|
||||||
except Exception, e:
|
|
||||||
logger.error('Error getting torrent: %s' % e)
|
# Add file to utorrent
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 2:
|
||||||
|
self.utorrent_add_file(download_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Error getting torrent: %s', e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return download_path
|
return download_path, tor_hash
|
||||||
|
|
||||||
|
#TODO get this working in utorrent.py
|
||||||
|
def utorrent_add_file(self, filename):
|
||||||
|
|
||||||
|
host = headphones.UTORRENT_HOST
|
||||||
|
if not host.startswith('http'):
|
||||||
|
host = 'http://' + host
|
||||||
|
if host.endswith('/'):
|
||||||
|
host = host[:-1]
|
||||||
|
if host.endswith('/gui'):
|
||||||
|
host = host[:-4]
|
||||||
|
|
||||||
|
base_url = host
|
||||||
|
username = headphones.UTORRENT_USERNAME
|
||||||
|
password = headphones.UTORRENT_PASSWORD
|
||||||
|
|
||||||
|
session = requests.Session()
|
||||||
|
url = base_url + '/gui/'
|
||||||
|
session.auth = (username, password)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = session.get(url + 'token.html')
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Error getting token')
|
||||||
|
return
|
||||||
|
|
||||||
|
if r.status_code == '401':
|
||||||
|
logger.debug('Error reaching utorrent')
|
||||||
|
return
|
||||||
|
|
||||||
|
regex = re.search(r'.+>([^<]+)</div></html>', r.text)
|
||||||
|
if regex is None:
|
||||||
|
logger.debug('Error reading token')
|
||||||
|
return
|
||||||
|
|
||||||
|
session.params = {'token': regex.group(1)}
|
||||||
|
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
try:
|
||||||
|
session.post(url, params={'action': 'add-file'},
|
||||||
|
files={'torrent_file': f})
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Error adding file to utorrent')
|
||||||
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# This file is part of Headphones.
|
||||||
|
#
|
||||||
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import headphones
|
||||||
|
from headphones import db, utorrent, transmission, logger
|
||||||
|
|
||||||
|
postprocessor_lock = threading.Lock()
|
||||||
|
|
||||||
|
# Remove Torrent + data if Post Processed and finished Seeding
|
||||||
|
def checkTorrentFinished():
|
||||||
|
|
||||||
|
logger.info("Checking if any torrents have finished seeding and can be removed")
|
||||||
|
|
||||||
|
with postprocessor_lock:
|
||||||
|
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
results = myDB.select('SELECT * from snatched WHERE Status="Seed_Processed"')
|
||||||
|
|
||||||
|
for album in results:
|
||||||
|
hash = album['FolderName']
|
||||||
|
albumid = album['AlbumID']
|
||||||
|
torrent_removed = False
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 1:
|
||||||
|
torrent_removed = transmission.removeTorrent(hash, True)
|
||||||
|
else:
|
||||||
|
torrent_removed = utorrent.removeTorrent(hash, True)
|
||||||
|
|
||||||
|
if torrent_removed:
|
||||||
|
myDB.action('DELETE from snatched WHERE status = "Seed_Processed" and AlbumID=?', [albumid])
|
||||||