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 |
@@ -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
|
||||||
|
|||||||
@@ -19,26 +19,26 @@ import os, sys
|
|||||||
# Ensure lib added to path, before any other imports
|
# Ensure lib added to path, before any other imports
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||||
|
|
||||||
|
from headphones import webstart, logger
|
||||||
|
|
||||||
|
from configobj import ConfigObj
|
||||||
|
|
||||||
import locale
|
import locale
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
|
import argparse
|
||||||
from lib.configobj import ConfigObj
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
from headphones import webstart, logger
|
# Register signals, such as CTRL + C
|
||||||
|
|
||||||
try:
|
|
||||||
import argparse
|
|
||||||
except ImportError:
|
|
||||||
import lib.argparse as argparse
|
|
||||||
|
|
||||||
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'):
|
||||||
headphones.FULL_PATH = os.path.abspath(sys.executable)
|
headphones.FULL_PATH = os.path.abspath(sys.executable)
|
||||||
@@ -63,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')
|
||||||
@@ -77,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)
|
||||||
@@ -98,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:
|
||||||
@@ -128,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:
|
||||||
@@ -177,6 +178,7 @@ def main():
|
|||||||
headphones.SIGNAL = 'shutdown'
|
headphones.SIGNAL = 'shutdown'
|
||||||
else:
|
else:
|
||||||
logger.info('Received signal: %s', 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':
|
||||||
@@ -186,7 +188,6 @@ def main():
|
|||||||
|
|
||||||
headphones.SIGNAL = None
|
headphones.SIGNAL = None
|
||||||
|
|
||||||
return
|
# Call main()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
@@ -1,44 +1,65 @@
|
|||||||
#Headphones
|
#Headphones
|
||||||
|
|
||||||
###Installation and Notes
|
###Support & Discuss
|
||||||
|
|
||||||
This is a relatively early release of a third-party add-on for SABnzbd. It's been around for about a year, and while
|
You are free to join the HP support community on IRC where you can ask questions, hang around and discuss anything related to HP.
|
||||||
quite functional, there are still a lot of kinks to work out!
|
|
||||||
|
1. Use any IRC client and connect to the Freenode server.
|
||||||
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'.
|
2. Join #headphones
|
||||||
|
|
||||||
For additional startup options, type 'python Headphones.py -h' or 'python Headphones.py --help'
|
###Installation and Notes
|
||||||
|
|
||||||
###Screenshots
|
[Read our Wiki](../../wiki) on how to install and use HeadPhones properly.
|
||||||
|
|
||||||
Homepage (Artist Overview)
|
[**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:
|
||||||
|
|
||||||
One of the many settings pages....
|
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.
|
||||||
It might even know you better than you know yourself:
|
|
||||||
|
**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'.
|
||||||
Import Your Favorite Artists:
|
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).
|
||||||
|
|
||||||
Artist Search Results (also search by album!):
|
**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.
|
||||||

|
|
||||||
|
|
||||||
Artist Page with Bio & Album Overview:
|
###Screenshots
|
||||||
|
|
||||||

|
Homepage (Artist Overview)
|
||||||
|
|
||||||
Album Page with track overview:
|

|
||||||
|
|
||||||

|
One of the many settings pages....
|
||||||
|
|
||||||
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.
|
It might even know you better than you know yourself:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Import Your Favorite Artists:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Artist Search Results (also search by album!):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Artist Page with Bio & Album Overview:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Album Page with track overview:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
||||||
|
|||||||
@@ -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 |
@@ -13,7 +13,7 @@
|
|||||||
<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>
|
||||||
@@ -89,9 +89,15 @@
|
|||||||
<div id="albumImg">
|
<div id="albumImg">
|
||||||
<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']]))
|
||||||
@@ -275,9 +281,58 @@
|
|||||||
feedback.fadeIn();
|
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,13 +162,12 @@
|
|||||||
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"
|
||||||
else:
|
else:
|
||||||
songkick_filter_enabled = "false"
|
songkick_filter_enabled = "false"
|
||||||
|
|
||||||
if not headphones.SONGKICK_LOCATION:
|
if not headphones.SONGKICK_LOCATION:
|
||||||
songkick_location = "none"
|
songkick_location = "none"
|
||||||
else:
|
else:
|
||||||
@@ -197,11 +190,11 @@
|
|||||||
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": [
|
||||||
@@ -280,7 +304,7 @@
|
|||||||
resetFilters("albums");
|
resetFilters("albums");
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
initFancybox();
|
initFancybox();
|
||||||
},1500)
|
},1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@@ -288,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,16 +19,18 @@
|
|||||||
|
|
||||||
<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
|
||||||
@@ -58,11 +59,11 @@
|
|||||||
<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>
|
||||||
|
|
||||||
@@ -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> |
|
||||||
@@ -97,21 +104,39 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<%def name="javascriptIncludes()"></%def>
|
<%def name="javascriptIncludes()"></%def>
|
||||||
<%def name="headIncludes()"></%def>
|
<%def name="headIncludes()"></%def>
|
||||||
<%def name="headerIncludes()"></%def>
|
<%def name="headerIncludes()"></%def>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Variables */
|
/* Variables */
|
||||||
@base-font-face: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
@base-font-face: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif;
|
||||||
@alt-font-face: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
@alt-font-face: "Trebuchet MS", Helvetica, Arial, sans-serif;
|
||||||
@base-font-size: 12px;
|
@base-font-size: 12px;
|
||||||
@@ -10,20 +10,20 @@
|
|||||||
@msg-bg: #FFF6A9;
|
@msg-bg: #FFF6A9;
|
||||||
@msg-bg-success: #D3FFD7;
|
@msg-bg-success: #D3FFD7;
|
||||||
@msg-bg-error: #FFD3D3;
|
@msg-bg-error: #FFD3D3;
|
||||||
|
|
||||||
/* Mixins */
|
/* Mixins */
|
||||||
.rounded(@radius: 5px) {
|
.rounded(@radius: 5px) {
|
||||||
-moz-border-radius: @radius;
|
-moz-border-radius: @radius;
|
||||||
-webkit-border-radius: @radius;
|
-webkit-border-radius: @radius;
|
||||||
border-radius: @radius;
|
border-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedTop(@radius: 5px) {
|
.roundedTop(@radius: 5px) {
|
||||||
-moz-border-radius-topleft: @radius;
|
-moz-border-radius-topleft: @radius;
|
||||||
-moz-border-radius-topright: @radius;
|
-moz-border-radius-topright: @radius;
|
||||||
-webkit-border-top-right-radius: @radius;
|
-webkit-border-top-right-radius: @radius;
|
||||||
-webkit-border-top-left-radius: @radius;
|
-webkit-border-top-left-radius: @radius;
|
||||||
border-top-left-radius: @radius;
|
border-top-left-radius: @radius;
|
||||||
border-top-right-radius: @radius;
|
border-top-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedLeftTop(@radius: 5px) {
|
.roundedLeftTop(@radius: 5px) {
|
||||||
-moz-border-radius-topleft: @radius;
|
-moz-border-radius-topleft: @radius;
|
||||||
@@ -34,14 +34,14 @@
|
|||||||
-moz-border-radius-topright: @radius;
|
-moz-border-radius-topright: @radius;
|
||||||
-webkit-border-top-right-radius: @radius;
|
-webkit-border-top-right-radius: @radius;
|
||||||
border-top-right-radius: @radius;
|
border-top-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedBottom(@radius: 5px) {
|
.roundedBottom(@radius: 5px) {
|
||||||
-moz-border-radius-bottomleft: @radius;
|
-moz-border-radius-bottomleft: @radius;
|
||||||
-moz-border-radius-bottomright: @radius;
|
-moz-border-radius-bottomright: @radius;
|
||||||
-webkit-border-bottom-right-radius: @radius;
|
-webkit-border-bottom-right-radius: @radius;
|
||||||
-webkit-border-bottom-left-radius: @radius;
|
-webkit-border-bottom-left-radius: @radius;
|
||||||
border-bottom-left-radius: @radius;
|
border-bottom-left-radius: @radius;
|
||||||
border-bottom-right-radius: @radius;
|
border-bottom-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.roundedLeftBottom(@radius: 5px) {
|
.roundedLeftBottom(@radius: 5px) {
|
||||||
-moz-border-radius-bottomleft: @radius;
|
-moz-border-radius-bottomleft: @radius;
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
-moz-border-radius-bottomright: @radius;
|
-moz-border-radius-bottomright: @radius;
|
||||||
-webkit-border-bottom-right-radius: @radius;
|
-webkit-border-bottom-right-radius: @radius;
|
||||||
border-bottom-right-radius: @radius;
|
border-bottom-right-radius: @radius;
|
||||||
}
|
}
|
||||||
.shadow(@shadow: 0 17px 11px -1px #ced8d9) {
|
.shadow(@shadow: 0 17px 11px -1px #ced8d9) {
|
||||||
-moz-box-shadow: @shadow;
|
-moz-box-shadow: @shadow;
|
||||||
-webkit-box-shadow: @shadow;
|
-webkit-box-shadow: @shadow;
|
||||||
@@ -74,4 +74,4 @@
|
|||||||
-o-opacity:@opacity_percent / 100 !important;
|
-o-opacity:@opacity_percent / 100 !important;
|
||||||
opacity:@opacity_percent / 100 !important;
|
opacity:@opacity_percent / 100 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -495,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;
|
||||||
@@ -537,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;
|
||||||
@@ -568,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;
|
||||||
@@ -587,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;
|
||||||
@@ -597,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;
|
||||||
@@ -750,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;
|
||||||
@@ -1118,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;
|
||||||
@@ -1136,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;
|
||||||
}
|
}
|
||||||
@@ -1207,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;
|
||||||
@@ -1408,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()">
|
||||||
@@ -51,11 +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=${item['Title']}',$(this),'table')" data-success="${item['Title']} cleared from history"><img src="interfaces/default/images/trashcan.png" height="18" width="18" id="trashcan" title="Clear this item from the history"></a>
|
<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 |
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
resetFilters("artist or album");
|
$(document).ready(function() {
|
||||||
}
|
initThisPage();
|
||||||
|
});
|
||||||
$(document).ready(function() {
|
$(window).load(function(){
|
||||||
initThisPage();
|
initFancybox();
|
||||||
});
|
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,37 +67,49 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initHeader() {
|
function initHeader() {
|
||||||
//settings
|
//settings
|
||||||
var header = $("#container header");
|
var header = $("#container header");
|
||||||
var fadeSpeed = 100, fadeTo = 0.5, topDistance = 20;
|
var fadeSpeed = 100, fadeTo = 0.5, topDistance = 20;
|
||||||
@@ -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">
|
||||||
@@ -19,7 +27,7 @@ lossless<%inherit file="base.html"/>
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<br>
|
<br>
|
||||||
<div align="center">Refresh rate:
|
<div align="center">Refresh rate:
|
||||||
<select id="refreshrate" onchange="setRefresh()">
|
<select id="refreshrate" onchange="setRefresh()">
|
||||||
<option value="0" selected="selected">No Refresh</option>
|
<option value="0" selected="selected">No Refresh</option>
|
||||||
<option value="5">5 Seconds</option>
|
<option value="5">5 Seconds</option>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,28 +68,29 @@
|
|||||||
</%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",
|
||||||
"sEmptyTable": "No results",
|
"sEmptyTable": "No results",
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -5,21 +5,21 @@
|
|||||||
<div id="subhead_menu">
|
<div id="subhead_menu">
|
||||||
<a href="#" id="menu_link_scan" onclick="doAjaxCall('forceSearch',$(this))" data-success="Checking for wanted albums successful" data-error="Error checking wanted albums"><i class="fa fa-search"></i> Force Check</a>
|
<a href="#" id="menu_link_scan" onclick="doAjaxCall('forceSearch',$(this))" data-success="Checking for wanted albums successful" data-error="Error checking wanted albums"><i class="fa fa-search"></i> Force Check</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
<%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">
|
||||||
<div id="markalbum" style="top:0;">
|
<div id="markalbum" style="top:0;">
|
||||||
Mark selected albums as
|
Mark selected albums as
|
||||||
<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>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
%endfor
|
%endfor
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
@@ -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
|
||||||
@@ -106,11 +105,15 @@ 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
|
||||||
@@ -136,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
|
||||||
@@ -147,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
|
||||||
|
|
||||||
@@ -157,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
|
||||||
@@ -168,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
|
||||||
@@ -190,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
|
||||||
@@ -250,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
|
||||||
@@ -270,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
|
||||||
@@ -281,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:
|
||||||
@@ -333,61 +354,43 @@ 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, DO_NOT_OVERRIDE_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, PREFER_TORRENTS, OPEN_MAGNET_LINKS, \
|
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, REPLACE_EXISTING_FOLDERS, 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, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \
|
MUSIC_ENCODER, ADVANCEDENCODER, ENCODEROUTPUTFORMAT, ENCODERQUALITY, ENCODERVBRCBR, ENCODERLOSSLESS, ENCODER_MULTICORE, ENCODER_MULTICORE_COUNT, DELETE_LOSSLESS_FILES, \
|
||||||
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, \
|
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, LMS_ENABLED, LMS_HOST, 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('Growl')
|
|
||||||
CheckSection('Prowl')
|
|
||||||
CheckSection('Pushover')
|
|
||||||
CheckSection('PushBullet')
|
|
||||||
CheckSection('XBMC')
|
|
||||||
CheckSection('LMS')
|
|
||||||
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')
|
||||||
@@ -431,14 +434,18 @@ 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))
|
||||||
@@ -464,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', '')
|
||||||
@@ -496,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))
|
||||||
|
|
||||||
@@ -506,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', '')
|
||||||
@@ -520,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', '')
|
||||||
@@ -609,7 +624,20 @@ def initialize():
|
|||||||
TWITTER_USERNAME = check_setting_str(CFG, 'Twitter', 'twitter_username', '')
|
TWITTER_USERNAME = check_setting_str(CFG, 'Twitter', 'twitter_username', '')
|
||||||
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', '')
|
||||||
@@ -627,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
|
||||||
@@ -676,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':
|
||||||
@@ -697,8 +727,8 @@ def initialize():
|
|||||||
if VERBOSE:
|
if VERBOSE:
|
||||||
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
|
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.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
|
||||||
@@ -804,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
|
||||||
@@ -846,14 +880,18 @@ 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)
|
||||||
@@ -874,27 +912,39 @@ def config_write():
|
|||||||
|
|
||||||
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)
|
||||||
@@ -902,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
|
||||||
@@ -915,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)
|
||||||
@@ -928,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)
|
||||||
@@ -947,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
|
||||||
@@ -997,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'] = {}
|
||||||
@@ -1025,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
|
||||||
@@ -1065,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():
|
||||||
|
|
||||||
@@ -1076,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)
|
||||||
@@ -1085,15 +1155,19 @@ 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...", signum)
|
logger.info("Signal %i caught, saving and exiting...", signum)
|
||||||
shutdown()
|
shutdown()
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ def switch(AlbumID, ReleaseID):
|
|||||||
"ReleaseCountry": newalbumdata['ReleaseCountry'],
|
"ReleaseCountry": newalbumdata['ReleaseCountry'],
|
||||||
"ReleaseFormat": newalbumdata['ReleaseFormat']
|
"ReleaseFormat": newalbumdata['ReleaseFormat']
|
||||||
}
|
}
|
||||||
|
|
||||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||||
|
|
||||||
for track in newtrackdata:
|
for track in newtrackdata:
|
||||||
|
|
||||||
controlValueDict = {"TrackID": track['TrackID'],
|
controlValueDict = {"TrackID": track['TrackID'],
|
||||||
"AlbumID": AlbumID}
|
"AlbumID": AlbumID}
|
||||||
|
|
||||||
@@ -60,23 +60,23 @@ def switch(AlbumID, ReleaseID):
|
|||||||
"Format": track['Format'],
|
"Format": track['Format'],
|
||||||
"BitRate": track['BitRate']
|
"BitRate": track['BitRate']
|
||||||
}
|
}
|
||||||
|
|
||||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||||
|
|
||||||
# Mark albums as downloaded if they have at least 80% (by default, configurable) of the album
|
# Mark albums as downloaded if they have at least 80% (by default, configurable) of the album
|
||||||
total_track_count = len(newtrackdata)
|
total_track_count = len(newtrackdata)
|
||||||
have_track_count = len(myDB.select('SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [AlbumID]))
|
have_track_count = len(myDB.select('SELECT * from tracks WHERE AlbumID=? AND Location IS NOT NULL', [AlbumID]))
|
||||||
|
|
||||||
if oldalbumdata['Status'] == 'Skipped' and ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)):
|
if oldalbumdata['Status'] == 'Skipped' and ((have_track_count/float(total_track_count)) >= (headphones.ALBUM_COMPLETION_PCT/100.0)):
|
||||||
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']}
|
||||||
|
|
||||||
newValueDict = { "TotalTracks": totaltracks,
|
newValueDict = { "TotalTracks": totaltracks,
|
||||||
"HaveTracks": havetracks}
|
"HaveTracks": havetracks}
|
||||||
|
|
||||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||||
|
|||||||
@@ -13,37 +13,37 @@
|
|||||||
# 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 copy
|
|
||||||
|
|
||||||
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
import headphones
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
|
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',
|
'shutdown', 'restart', 'update', 'getArtistArt', 'getAlbumArt', 'getArtistInfo', 'getAlbumInfo',
|
||||||
'getArtistThumb', 'getAlbumThumb', 'choose_specific_download', 'download_specific_release']
|
'getArtistThumb', 'getAlbumThumb', 'choose_specific_download', 'download_specific_release']
|
||||||
|
|
||||||
class Api(object):
|
class Api(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.apikey = None
|
self.apikey = None
|
||||||
self.cmd = None
|
self.cmd = None
|
||||||
self.id = None
|
self.id = None
|
||||||
|
|
||||||
self.kwargs = None
|
self.kwargs = None
|
||||||
|
|
||||||
self.data = None
|
self.data = None
|
||||||
|
|
||||||
self.callback = None
|
self.callback = None
|
||||||
|
|
||||||
|
|
||||||
def checkParams(self,*args,**kwargs):
|
def checkParams(self,*args,**kwargs):
|
||||||
|
|
||||||
if not headphones.API_ENABLED:
|
if not headphones.API_ENABLED:
|
||||||
self.data = 'API not enabled'
|
self.data = 'API not enabled'
|
||||||
return
|
return
|
||||||
@@ -53,32 +53,32 @@ class Api(object):
|
|||||||
if len(headphones.API_KEY) != 32:
|
if len(headphones.API_KEY) != 32:
|
||||||
self.data = 'API key not generated correctly'
|
self.data = 'API key not generated correctly'
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'apikey' not in kwargs:
|
if 'apikey' not in kwargs:
|
||||||
self.data = 'Missing api key'
|
self.data = 'Missing api key'
|
||||||
return
|
return
|
||||||
|
|
||||||
if kwargs['apikey'] != headphones.API_KEY:
|
if kwargs['apikey'] != headphones.API_KEY:
|
||||||
self.data = 'Incorrect API key'
|
self.data = 'Incorrect API key'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.apikey = kwargs.pop('apikey')
|
self.apikey = kwargs.pop('apikey')
|
||||||
|
|
||||||
if 'cmd' not in kwargs:
|
if 'cmd' not in kwargs:
|
||||||
self.data = 'Missing parameter: cmd'
|
self.data = 'Missing parameter: cmd'
|
||||||
return
|
return
|
||||||
|
|
||||||
if kwargs['cmd'] not in cmd_list:
|
if kwargs['cmd'] not in cmd_list:
|
||||||
self.data = 'Unknown command: %s' % kwargs['cmd']
|
self.data = 'Unknown command: %s' % kwargs['cmd']
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.cmd = kwargs.pop('cmd')
|
self.cmd = kwargs.pop('cmd')
|
||||||
|
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
self.data = 'OK'
|
self.data = 'OK'
|
||||||
|
|
||||||
def fetchData(self):
|
def fetchData(self):
|
||||||
|
|
||||||
if self.data == 'OK':
|
if self.data == 'OK':
|
||||||
logger.info('Recieved API command: %s', self.cmd)
|
logger.info('Recieved API command: %s', self.cmd)
|
||||||
methodToCall = getattr(self, "_" + self.cmd)
|
methodToCall = getattr(self, "_" + self.cmd)
|
||||||
@@ -87,82 +87,82 @@ class Api(object):
|
|||||||
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:
|
||||||
return self.data
|
return self.data
|
||||||
|
|
||||||
def _dic_from_query(self,query):
|
def _dic_from_query(self,query):
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
rows = myDB.select(query)
|
rows = myDB.select(query)
|
||||||
|
|
||||||
rows_as_dic = []
|
rows_as_dic = []
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
row_as_dic = dict(zip(row.keys(), row))
|
row_as_dic = dict(zip(row.keys(), row))
|
||||||
rows_as_dic.append(row_as_dic)
|
rows_as_dic.append(row_as_dic)
|
||||||
|
|
||||||
return rows_as_dic
|
return rows_as_dic
|
||||||
|
|
||||||
def _getIndex(self, **kwargs):
|
def _getIndex(self, **kwargs):
|
||||||
|
|
||||||
self.data = self._dic_from_query('SELECT * from artists order by ArtistSortName COLLATE NOCASE')
|
self.data = self._dic_from_query('SELECT * from artists order by ArtistSortName COLLATE NOCASE')
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getArtist(self, **kwargs):
|
def _getArtist(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
artist = self._dic_from_query('SELECT * from artists WHERE ArtistID="' + self.id + '"')
|
artist = self._dic_from_query('SELECT * from artists WHERE ArtistID="' + self.id + '"')
|
||||||
albums = self._dic_from_query('SELECT * from albums WHERE ArtistID="' + self.id + '" order by ReleaseDate DESC')
|
albums = self._dic_from_query('SELECT * from albums WHERE ArtistID="' + self.id + '" order by ReleaseDate DESC')
|
||||||
description = self._dic_from_query('SELECT * from descriptions WHERE ArtistID="' + self.id + '"')
|
description = self._dic_from_query('SELECT * from descriptions WHERE ArtistID="' + self.id + '"')
|
||||||
|
|
||||||
self.data = { 'artist': artist, 'albums': albums, 'description' : description }
|
self.data = { 'artist': artist, 'albums': albums, 'description' : description }
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getAlbum(self, **kwargs):
|
def _getAlbum(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
album = self._dic_from_query('SELECT * from albums WHERE AlbumID="' + self.id + '"')
|
album = self._dic_from_query('SELECT * from albums WHERE AlbumID="' + self.id + '"')
|
||||||
tracks = self._dic_from_query('SELECT * from tracks WHERE AlbumID="' + self.id + '"')
|
tracks = self._dic_from_query('SELECT * from tracks WHERE AlbumID="' + self.id + '"')
|
||||||
description = self._dic_from_query('SELECT * from descriptions WHERE ReleaseGroupID="' + self.id + '"')
|
description = self._dic_from_query('SELECT * from descriptions WHERE ReleaseGroupID="' + self.id + '"')
|
||||||
|
|
||||||
self.data = { 'album' : album, 'tracks' : tracks, 'description' : description }
|
self.data = { 'album' : album, 'tracks' : tracks, 'description' : description }
|
||||||
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):
|
||||||
self.data = self._dic_from_query("SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC")
|
self.data = self._dic_from_query("SELECT * from albums WHERE ReleaseDate > date('now') order by ReleaseDate DESC")
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getWanted(self, **kwargs):
|
def _getWanted(self, **kwargs):
|
||||||
self.data = self._dic_from_query("SELECT * from albums WHERE Status='Wanted'")
|
self.data = self._dic_from_query("SELECT * from albums WHERE Status='Wanted'")
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getSimilar(self, **kwargs):
|
def _getSimilar(self, **kwargs):
|
||||||
self.data = self._dic_from_query('SELECT * from lastfmcloud')
|
self.data = self._dic_from_query('SELECT * from lastfmcloud')
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getLogs(self, **kwargs):
|
def _getLogs(self, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _findArtist(self, **kwargs):
|
def _findArtist(self, **kwargs):
|
||||||
if 'name' not in kwargs:
|
if 'name' not in kwargs:
|
||||||
self.data = 'Missing parameter: name'
|
self.data = 'Missing parameter: name'
|
||||||
@@ -171,7 +171,7 @@ class Api(object):
|
|||||||
limit = kwargs['limit']
|
limit = kwargs['limit']
|
||||||
else:
|
else:
|
||||||
limit=50
|
limit=50
|
||||||
|
|
||||||
self.data = mb.findArtist(kwargs['name'], limit)
|
self.data = mb.findArtist(kwargs['name'], limit)
|
||||||
|
|
||||||
def _findAlbum(self, **kwargs):
|
def _findAlbum(self, **kwargs):
|
||||||
@@ -182,216 +182,216 @@ class Api(object):
|
|||||||
limit = kwargs['limit']
|
limit = kwargs['limit']
|
||||||
else:
|
else:
|
||||||
limit=50
|
limit=50
|
||||||
|
|
||||||
self.data = mb.findRelease(kwargs['name'], limit)
|
self.data = mb.findRelease(kwargs['name'], limit)
|
||||||
|
|
||||||
def _addArtist(self, **kwargs):
|
def _addArtist(self, **kwargs):
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
importer.addArtisttoDB(self.id)
|
importer.addArtisttoDB(self.id)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.data = e
|
self.data = e
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _delArtist(self, **kwargs):
|
def _delArtist(self, **kwargs):
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
myDB.action('DELETE from artists WHERE ArtistID="' + self.id + '"')
|
myDB.action('DELETE from artists WHERE ArtistID="' + self.id + '"')
|
||||||
myDB.action('DELETE from albums WHERE ArtistID="' + self.id + '"')
|
myDB.action('DELETE from albums WHERE ArtistID="' + self.id + '"')
|
||||||
myDB.action('DELETE from tracks WHERE ArtistID="' + self.id + '"')
|
myDB.action('DELETE from tracks WHERE ArtistID="' + self.id + '"')
|
||||||
|
|
||||||
def _pauseArtist(self, **kwargs):
|
def _pauseArtist(self, **kwargs):
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
controlValueDict = {'ArtistID': self.id}
|
controlValueDict = {'ArtistID': self.id}
|
||||||
newValueDict = {'Status': 'Paused'}
|
newValueDict = {'Status': 'Paused'}
|
||||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||||
|
|
||||||
def _resumeArtist(self, **kwargs):
|
def _resumeArtist(self, **kwargs):
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
controlValueDict = {'ArtistID': self.id}
|
controlValueDict = {'ArtistID': self.id}
|
||||||
newValueDict = {'Status': 'Active'}
|
newValueDict = {'Status': 'Active'}
|
||||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||||
|
|
||||||
def _refreshArtist(self, **kwargs):
|
def _refreshArtist(self, **kwargs):
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
importer.addArtisttoDB(self.id)
|
importer.addArtisttoDB(self.id)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.data = e
|
self.data = e
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _addAlbum(self, **kwargs):
|
def _addAlbum(self, **kwargs):
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
importer.addReleaseById(self.id)
|
importer.addReleaseById(self.id)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
self.data = e
|
self.data = e
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _queueAlbum(self, **kwargs):
|
def _queueAlbum(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
if 'new' in kwargs:
|
if 'new' in kwargs:
|
||||||
new = kwargs['new']
|
new = kwargs['new']
|
||||||
else:
|
else:
|
||||||
new = False
|
new = False
|
||||||
|
|
||||||
if 'lossless' in kwargs:
|
if 'lossless' in kwargs:
|
||||||
lossless = kwargs['lossless']
|
lossless = kwargs['lossless']
|
||||||
else:
|
else:
|
||||||
lossless = False
|
lossless = False
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
controlValueDict = {'AlbumID': self.id}
|
controlValueDict = {'AlbumID': self.id}
|
||||||
if lossless:
|
if lossless:
|
||||||
newValueDict = {'Status': 'Wanted Lossless'}
|
newValueDict = {'Status': 'Wanted Lossless'}
|
||||||
else:
|
else:
|
||||||
newValueDict = {'Status': 'Wanted'}
|
newValueDict = {'Status': 'Wanted'}
|
||||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||||
searcher.searchforalbum(self.id, new)
|
searcher.searchforalbum(self.id, new)
|
||||||
|
|
||||||
def _unqueueAlbum(self, **kwargs):
|
def _unqueueAlbum(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
controlValueDict = {'AlbumID': self.id}
|
controlValueDict = {'AlbumID': self.id}
|
||||||
newValueDict = {'Status': 'Skipped'}
|
newValueDict = {'Status': 'Skipped'}
|
||||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||||
|
|
||||||
def _forceSearch(self, **kwargs):
|
def _forceSearch(self, **kwargs):
|
||||||
searcher.searchforalbum()
|
searcher.searchforalbum()
|
||||||
|
|
||||||
def _forceProcess(self, **kwargs):
|
def _forceProcess(self, **kwargs):
|
||||||
self.dir = None
|
self.dir = None
|
||||||
if 'dir' in kwargs:
|
if 'dir' in kwargs:
|
||||||
self.dir = kwargs['dir']
|
self.dir = kwargs['dir']
|
||||||
postprocessor.forcePostProcess(self.dir)
|
postprocessor.forcePostProcess(self.dir)
|
||||||
|
|
||||||
def _getVersion(self, **kwargs):
|
def _getVersion(self, **kwargs):
|
||||||
self.data = {
|
self.data = {
|
||||||
'git_path' : headphones.GIT_PATH,
|
'git_path' : headphones.GIT_PATH,
|
||||||
'install_type' : headphones.INSTALL_TYPE,
|
'install_type' : headphones.INSTALL_TYPE,
|
||||||
'current_version' : headphones.CURRENT_VERSION,
|
'current_version' : headphones.CURRENT_VERSION,
|
||||||
'latest_version' : headphones.LATEST_VERSION,
|
'latest_version' : headphones.LATEST_VERSION,
|
||||||
'commits_behind' : headphones.COMMITS_BEHIND,
|
'commits_behind' : headphones.COMMITS_BEHIND,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _checkGithub(self, **kwargs):
|
def _checkGithub(self, **kwargs):
|
||||||
versioncheck.checkGithub()
|
versioncheck.checkGithub()
|
||||||
self._getVersion()
|
self._getVersion()
|
||||||
|
|
||||||
def _shutdown(self, **kwargs):
|
def _shutdown(self, **kwargs):
|
||||||
headphones.SIGNAL = 'shutdown'
|
headphones.SIGNAL = 'shutdown'
|
||||||
|
|
||||||
def _restart(self, **kwargs):
|
def _restart(self, **kwargs):
|
||||||
headphones.SIGNAL = 'restart'
|
headphones.SIGNAL = 'restart'
|
||||||
|
|
||||||
def _update(self, **kwargs):
|
def _update(self, **kwargs):
|
||||||
headphones.SIGNAL = 'update'
|
headphones.SIGNAL = 'update'
|
||||||
|
|
||||||
def _getArtistArt(self, **kwargs):
|
def _getArtistArt(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
self.data = cache.getArtwork(ArtistID=self.id)
|
self.data = cache.getArtwork(ArtistID=self.id)
|
||||||
|
|
||||||
def _getAlbumArt(self, **kwargs):
|
def _getAlbumArt(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
self.data = cache.getArtwork(AlbumID=self.id)
|
self.data = cache.getArtwork(AlbumID=self.id)
|
||||||
|
|
||||||
def _getArtistInfo(self, **kwargs):
|
def _getArtistInfo(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
self.data = cache.getInfo(ArtistID=self.id)
|
self.data = cache.getInfo(ArtistID=self.id)
|
||||||
|
|
||||||
def _getAlbumInfo(self, **kwargs):
|
def _getAlbumInfo(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
self.data = cache.getInfo(AlbumID=self.id)
|
self.data = cache.getInfo(AlbumID=self.id)
|
||||||
|
|
||||||
def _getArtistThumb(self, **kwargs):
|
def _getArtistThumb(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
self.data = cache.getThumb(ArtistID=self.id)
|
self.data = cache.getThumb(ArtistID=self.id)
|
||||||
|
|
||||||
def _getAlbumThumb(self, **kwargs):
|
def _getAlbumThumb(self, **kwargs):
|
||||||
|
|
||||||
if 'id' not in kwargs:
|
if 'id' not in kwargs:
|
||||||
self.data = 'Missing parameter: id'
|
self.data = 'Missing parameter: id'
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
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):
|
def _choose_specific_download(self, **kwargs):
|
||||||
@@ -403,9 +403,9 @@ class Api(object):
|
|||||||
self.id = kwargs['id']
|
self.id = kwargs['id']
|
||||||
|
|
||||||
results = searcher.searchforalbum(self.id, choose_specific_download=True)
|
results = searcher.searchforalbum(self.id, choose_specific_download=True)
|
||||||
|
|
||||||
results_as_dicts = []
|
results_as_dicts = []
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
|
|
||||||
result_dict = {
|
result_dict = {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import headphones
|
|||||||
|
|
||||||
from headphones import db, helpers, logger, lastfm, request
|
from headphones import db, helpers, logger, lastfm, request
|
||||||
|
|
||||||
lastfm_apikey = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
||||||
|
|
||||||
class Cache(object):
|
class Cache(object):
|
||||||
"""
|
"""
|
||||||
@@ -42,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 = []
|
||||||
@@ -126,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'
|
||||||
|
|
||||||
@@ -152,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'
|
||||||
|
|
||||||
@@ -209,14 +206,14 @@ 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_apikey)
|
data = lastfm.request_lastfm("artist.getinfo", mbid=ArtistID, api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image_url = data['artist']['image'][-1]['#text']
|
image_url = data['artist']['image'][-1]['#text']
|
||||||
except Exception:
|
except (KeyError, IndexError):
|
||||||
logger.debug('No artist image found')
|
logger.debug('No artist image found')
|
||||||
image_url = None
|
image_url = None
|
||||||
|
|
||||||
@@ -227,21 +224,21 @@ class Cache(object):
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
self.id_type = 'album'
|
self.id_type = 'album'
|
||||||
data = lastfm.request_lastfm("album.getinfo", mbid=AlbumID, api_key=lastfm_apikey)
|
data = lastfm.request_lastfm("album.getinfo", mbid=AlbumID, api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image_url = data['artist']['image'][-1]['#text']
|
image_url = data['album']['image'][-1]['#text']
|
||||||
except Exception:
|
except (KeyError, IndexError):
|
||||||
logger.debug('No artist image found')
|
logger.debug('No album image found on last.fm')
|
||||||
image_url = None
|
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 artist thumbnail image found')
|
logger.debug('No album thumbnail image found on last.fm')
|
||||||
|
|
||||||
return {'artwork' : image_url, 'thumbnail' : thumb_url }
|
return {'artwork' : image_url, 'thumbnail' : thumb_url }
|
||||||
|
|
||||||
@@ -254,24 +251,24 @@ 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':
|
||||||
|
|
||||||
data = lastfm.request_lastfm("artist.getinfo", mbid=self.id, api_key=lastfm_apikey)
|
data = lastfm.request_lastfm("artist.getinfo", mbid=self.id, api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.info_summary = data['artist']['bio']['summary']
|
self.info_summary = data['artist']['bio']['summary']
|
||||||
except Exception:
|
except KeyError:
|
||||||
logger.debug('No artist bio summary found')
|
logger.debug('No artist bio summary found')
|
||||||
self.info_summary = None
|
self.info_summary = None
|
||||||
try:
|
try:
|
||||||
self.info_content = data['artist']['bio']['content']
|
self.info_content = data['artist']['bio']['content']
|
||||||
except Exception:
|
except KeyError:
|
||||||
logger.debug('No artist bio found')
|
logger.debug('No artist bio found')
|
||||||
self.info_content = None
|
self.info_content = None
|
||||||
try:
|
try:
|
||||||
image_url = data['artist']['image'][-1]['#text']
|
image_url = data['artist']['image'][-1]['#text']
|
||||||
except Exception:
|
except KeyError:
|
||||||
logger.debug('No artist image found')
|
logger.debug('No artist image found')
|
||||||
image_url = None
|
image_url = None
|
||||||
|
|
||||||
@@ -282,24 +279,24 @@ class Cache(object):
|
|||||||
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_apikey)
|
data = lastfm.request_lastfm("album.getinfo", artist=dbartist['ArtistName'], album=dbartist['AlbumTitle'], api_key=LASTFM_API_KEY)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.info_summary = data['album']['wiki']['summary']
|
self.info_summary = data['album']['wiki']['summary']
|
||||||
except Exception:
|
except KeyError:
|
||||||
logger.debug('No album summary found')
|
logger.debug('No album summary found')
|
||||||
self.info_summary = None
|
self.info_summary = None
|
||||||
try:
|
try:
|
||||||
self.info_content = data['album']['wiki']['content']
|
self.info_content = data['album']['wiki']['content']
|
||||||
except Exception:
|
except KeyError:
|
||||||
logger.debug('No album infomation found')
|
logger.debug('No album infomation found')
|
||||||
self.info_content = None
|
self.info_content = None
|
||||||
try:
|
try:
|
||||||
image_url = data['album']['image'][-1]['#text']
|
image_url = data['album']['image'][-1]['#text']
|
||||||
except Exception:
|
except KeyError:
|
||||||
logger.debug('No album image link found')
|
logger.debug('No album image link found')
|
||||||
image_url = None
|
image_url = None
|
||||||
|
|
||||||
@@ -359,10 +356,9 @@ class Cache(object):
|
|||||||
|
|
||||||
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: %s', e)
|
||||||
self.artwork_errors = True
|
self.artwork_errors = True
|
||||||
self.artwork_url = image_url
|
self.artwork_url = image_url
|
||||||
@@ -376,7 +372,7 @@ class Cache(object):
|
|||||||
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: %s' + 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
|
||||||
@@ -385,17 +381,16 @@ 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: %s', 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: %s', e)
|
||||||
self.thumb_errors = True
|
self.thumb_errors = True
|
||||||
self.thumb_url = image_url
|
self.thumb_url = image_url
|
||||||
|
|||||||
@@ -1,130 +1,130 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# 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/>.
|
||||||
|
|
||||||
#########################################
|
#########################################
|
||||||
## Stolen from Sick-Beard's classes.py ##
|
## Stolen from Sick-Beard's classes.py ##
|
||||||
#########################################
|
#########################################
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from common import USER_AGENT
|
from common import USER_AGENT
|
||||||
|
|
||||||
class HeadphonesURLopener(urllib.FancyURLopener):
|
class HeadphonesURLopener(urllib.FancyURLopener):
|
||||||
version = USER_AGENT
|
version = USER_AGENT
|
||||||
|
|
||||||
class AuthURLOpener(HeadphonesURLopener):
|
class AuthURLOpener(HeadphonesURLopener):
|
||||||
"""
|
"""
|
||||||
URLOpener class that supports http auth without needing interactive password entry.
|
URLOpener class that supports http auth without needing interactive password entry.
|
||||||
If the provided username/password don't work it simply fails.
|
If the provided username/password don't work it simply fails.
|
||||||
|
|
||||||
user: username to use for HTTP auth
|
user: username to use for HTTP auth
|
||||||
pw: password to use for HTTP auth
|
pw: password to use for HTTP auth
|
||||||
"""
|
"""
|
||||||
def __init__(self, user, pw):
|
def __init__(self, user, pw):
|
||||||
self.username = user
|
self.username = user
|
||||||
self.password = pw
|
self.password = pw
|
||||||
|
|
||||||
# remember if we've tried the username/password before
|
# remember if we've tried the username/password before
|
||||||
self.numTries = 0
|
self.numTries = 0
|
||||||
|
|
||||||
# call the base class
|
# call the base class
|
||||||
urllib.FancyURLopener.__init__(self)
|
urllib.FancyURLopener.__init__(self)
|
||||||
|
|
||||||
def prompt_user_passwd(self, host, realm):
|
def prompt_user_passwd(self, host, realm):
|
||||||
"""
|
"""
|
||||||
Override this function and instead of prompting just give the
|
Override this function and instead of prompting just give the
|
||||||
username/password that were provided when the class was instantiated.
|
username/password that were provided when the class was instantiated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# if this is the first try then provide a username/password
|
# if this is the first try then provide a username/password
|
||||||
if self.numTries == 0:
|
if self.numTries == 0:
|
||||||
self.numTries = 1
|
self.numTries = 1
|
||||||
return (self.username, self.password)
|
return (self.username, self.password)
|
||||||
|
|
||||||
# if we've tried before then return blank which cancels the request
|
# if we've tried before then return blank which cancels the request
|
||||||
else:
|
else:
|
||||||
return ('', '')
|
return ('', '')
|
||||||
|
|
||||||
# this is pretty much just a hack for convenience
|
# this is pretty much just a hack for convenience
|
||||||
def openit(self, url):
|
def openit(self, url):
|
||||||
self.numTries = 0
|
self.numTries = 0
|
||||||
return HeadphonesURLopener.open(self, url)
|
return HeadphonesURLopener.open(self, url)
|
||||||
|
|
||||||
class SearchResult:
|
class SearchResult:
|
||||||
"""
|
"""
|
||||||
Represents a search result from an indexer.
|
Represents a search result from an indexer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.provider = -1
|
self.provider = -1
|
||||||
|
|
||||||
# URL to the NZB/torrent file
|
# URL to the NZB/torrent file
|
||||||
self.url = ""
|
self.url = ""
|
||||||
|
|
||||||
# used by some providers to store extra info associated with the result
|
# used by some providers to store extra info associated with the result
|
||||||
self.extraInfo = []
|
self.extraInfo = []
|
||||||
|
|
||||||
# quality of the release
|
# quality of the release
|
||||||
self.quality = -1
|
self.quality = -1
|
||||||
|
|
||||||
# release name
|
# release name
|
||||||
self.name = ""
|
self.name = ""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
||||||
if self.provider == None:
|
if self.provider == None:
|
||||||
return "Invalid provider, unable to print self"
|
return "Invalid provider, unable to print self"
|
||||||
|
|
||||||
myString = self.provider.name + " @ " + self.url + "\n"
|
myString = self.provider.name + " @ " + self.url + "\n"
|
||||||
myString += "Extra Info:\n"
|
myString += "Extra Info:\n"
|
||||||
for extra in self.extraInfo:
|
for extra in self.extraInfo:
|
||||||
myString += " " + extra + "\n"
|
myString += " " + extra + "\n"
|
||||||
return myString
|
return myString
|
||||||
|
|
||||||
class NZBSearchResult(SearchResult):
|
class NZBSearchResult(SearchResult):
|
||||||
"""
|
"""
|
||||||
Regular NZB result with an URL to the NZB
|
Regular NZB result with an URL to the NZB
|
||||||
"""
|
"""
|
||||||
resultType = "nzb"
|
resultType = "nzb"
|
||||||
|
|
||||||
class NZBDataSearchResult(SearchResult):
|
class NZBDataSearchResult(SearchResult):
|
||||||
"""
|
"""
|
||||||
NZB result where the actual NZB XML data is stored in the extraInfo
|
NZB result where the actual NZB XML data is stored in the extraInfo
|
||||||
"""
|
"""
|
||||||
resultType = "nzbdata"
|
resultType = "nzbdata"
|
||||||
|
|
||||||
class TorrentSearchResult(SearchResult):
|
class TorrentSearchResult(SearchResult):
|
||||||
"""
|
"""
|
||||||
Torrent result with an URL to the torrent
|
Torrent result with an URL to the torrent
|
||||||
"""
|
"""
|
||||||
resultType = "torrent"
|
resultType = "torrent"
|
||||||
|
|
||||||
class Proper:
|
class Proper:
|
||||||
def __init__(self, name, url, date):
|
def __init__(self, name, url, date):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.url = url
|
self.url = url
|
||||||
self.date = date
|
self.date = date
|
||||||
self.provider = None
|
self.provider = None
|
||||||
self.quality = -1
|
self.quality = -1
|
||||||
|
|
||||||
self.tvdbid = -1
|
self.tvdbid = -1
|
||||||
self.season = -1
|
self.season = -1
|
||||||
self.episode = -1
|
self.episode = -1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.date)+" "+self.name+" "+str(self.season)+"x"+str(self.episode)+" of "+str(self.tvdbid)
|
return str(self.date)+" "+self.name+" "+str(self.season)+"x"+str(self.episode)+" of "+str(self.tvdbid)
|
||||||
|
|||||||
@@ -1,177 +1,177 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# 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/>.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Created on Aug 1, 2011
|
Created on Aug 1, 2011
|
||||||
|
|
||||||
@author: Michael
|
@author: Michael
|
||||||
'''
|
'''
|
||||||
import platform, operator, os, re
|
import platform, operator, os, re
|
||||||
|
|
||||||
from headphones import version
|
from headphones import version
|
||||||
|
|
||||||
#Identify Our Application
|
#Identify Our Application
|
||||||
USER_AGENT = 'Headphones/-'+version.HEADPHONES_VERSION+' ('+platform.system()+' '+platform.release()+')'
|
USER_AGENT = 'Headphones/-'+version.HEADPHONES_VERSION+' ('+platform.system()+' '+platform.release()+')'
|
||||||
|
|
||||||
### Notification Types
|
### Notification Types
|
||||||
NOTIFY_SNATCH = 1
|
NOTIFY_SNATCH = 1
|
||||||
NOTIFY_DOWNLOAD = 2
|
NOTIFY_DOWNLOAD = 2
|
||||||
|
|
||||||
notifyStrings = {}
|
notifyStrings = {}
|
||||||
notifyStrings[NOTIFY_SNATCH] = "Started Download"
|
notifyStrings[NOTIFY_SNATCH] = "Started Download"
|
||||||
notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished"
|
notifyStrings[NOTIFY_DOWNLOAD] = "Download Finished"
|
||||||
|
|
||||||
### Release statuses
|
### Release statuses
|
||||||
UNKNOWN = -1 # should never happen
|
UNKNOWN = -1 # should never happen
|
||||||
UNAIRED = 1 # releases that haven't dropped yet
|
UNAIRED = 1 # releases that haven't dropped yet
|
||||||
SNATCHED = 2 # qualified with quality
|
SNATCHED = 2 # qualified with quality
|
||||||
WANTED = 3 # releases we don't have but want to get
|
WANTED = 3 # releases we don't have but want to get
|
||||||
DOWNLOADED = 4 # qualified with quality
|
DOWNLOADED = 4 # qualified with quality
|
||||||
SKIPPED = 5 # releases we don't want
|
SKIPPED = 5 # releases we don't want
|
||||||
ARCHIVED = 6 # releases that you don't have locally (counts toward download completion stats)
|
ARCHIVED = 6 # releases that you don't have locally (counts toward download completion stats)
|
||||||
IGNORED = 7 # releases that you don't want included in your download stats
|
IGNORED = 7 # releases that you don't want included in your download stats
|
||||||
SNATCHED_PROPER = 9 # qualified with quality
|
SNATCHED_PROPER = 9 # qualified with quality
|
||||||
|
|
||||||
class Quality:
|
class Quality:
|
||||||
|
|
||||||
NONE = 0
|
NONE = 0
|
||||||
B192 = 1<<1 # 2
|
B192 = 1<<1 # 2
|
||||||
VBR = 1<<2 # 4
|
VBR = 1<<2 # 4
|
||||||
B256 = 1<<3 # 8
|
B256 = 1<<3 # 8
|
||||||
B320 = 1<<4 #16
|
B320 = 1<<4 #16
|
||||||
FLAC = 1<<5 #32
|
FLAC = 1<<5 #32
|
||||||
|
|
||||||
# put these bits at the other end of the spectrum, far enough out that they shouldn't interfere
|
# put these bits at the other end of the spectrum, far enough out that they shouldn't interfere
|
||||||
UNKNOWN = 1<<15
|
UNKNOWN = 1<<15
|
||||||
|
|
||||||
qualityStrings = {NONE: "N/A",
|
qualityStrings = {NONE: "N/A",
|
||||||
UNKNOWN: "Unknown",
|
UNKNOWN: "Unknown",
|
||||||
B192: "MP3 192",
|
B192: "MP3 192",
|
||||||
VBR: "MP3 VBR",
|
VBR: "MP3 VBR",
|
||||||
B256: "MP3 256",
|
B256: "MP3 256",
|
||||||
B320: "MP3 320",
|
B320: "MP3 320",
|
||||||
FLAC: "Flac"}
|
FLAC: "Flac"}
|
||||||
|
|
||||||
statusPrefixes = {DOWNLOADED: "Downloaded",
|
statusPrefixes = {DOWNLOADED: "Downloaded",
|
||||||
SNATCHED: "Snatched"}
|
SNATCHED: "Snatched"}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _getStatusStrings(status):
|
def _getStatusStrings(status):
|
||||||
toReturn = {}
|
toReturn = {}
|
||||||
for x in Quality.qualityStrings.keys():
|
for x in Quality.qualityStrings.keys():
|
||||||
toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")"
|
toReturn[Quality.compositeStatus(status, x)] = Quality.statusPrefixes[status]+" ("+Quality.qualityStrings[x]+")"
|
||||||
return toReturn
|
return toReturn
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def combineQualities(anyQualities, bestQualities):
|
def combineQualities(anyQualities, bestQualities):
|
||||||
anyQuality = 0
|
anyQuality = 0
|
||||||
bestQuality = 0
|
bestQuality = 0
|
||||||
if anyQualities:
|
if anyQualities:
|
||||||
anyQuality = reduce(operator.or_, anyQualities)
|
anyQuality = reduce(operator.or_, anyQualities)
|
||||||
if bestQualities:
|
if bestQualities:
|
||||||
bestQuality = reduce(operator.or_, bestQualities)
|
bestQuality = reduce(operator.or_, bestQualities)
|
||||||
return anyQuality | (bestQuality<<16)
|
return anyQuality | (bestQuality<<16)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def splitQuality(quality):
|
def splitQuality(quality):
|
||||||
anyQualities = []
|
anyQualities = []
|
||||||
bestQualities = []
|
bestQualities = []
|
||||||
for curQual in Quality.qualityStrings.keys():
|
for curQual in Quality.qualityStrings.keys():
|
||||||
if curQual & quality:
|
if curQual & quality:
|
||||||
anyQualities.append(curQual)
|
anyQualities.append(curQual)
|
||||||
if curQual<<16 & quality:
|
if curQual<<16 & quality:
|
||||||
bestQualities.append(curQual)
|
bestQualities.append(curQual)
|
||||||
|
|
||||||
return (anyQualities, bestQualities)
|
return (anyQualities, bestQualities)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def nameQuality(name):
|
def nameQuality(name):
|
||||||
|
|
||||||
name = os.path.basename(name)
|
name = os.path.basename(name)
|
||||||
|
|
||||||
# if we have our exact text then assume we put it there
|
# if we have our exact text then assume we put it there
|
||||||
for x in Quality.qualityStrings:
|
for x in Quality.qualityStrings:
|
||||||
if x == Quality.UNKNOWN:
|
if x == Quality.UNKNOWN:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W'
|
regex = '\W'+Quality.qualityStrings[x].replace(' ','\W')+'\W'
|
||||||
regex_match = re.search(regex, name, re.I)
|
regex_match = re.search(regex, name, re.I)
|
||||||
if regex_match:
|
if regex_match:
|
||||||
return x
|
return x
|
||||||
|
|
||||||
checkName = lambda list, func: func([re.search(x, name, re.I) for x in list])
|
checkName = lambda list, func: func([re.search(x, name, re.I) for x in list])
|
||||||
|
|
||||||
#TODO: fix quality checking here
|
#TODO: fix quality checking here
|
||||||
if checkName(["mp3", "192"], any) and not checkName(["flac"], all):
|
if checkName(["mp3", "192"], any) and not checkName(["flac"], all):
|
||||||
return Quality.B192
|
return Quality.B192
|
||||||
elif checkName(["mp3", "256"], any) and not checkName(["flac"], all):
|
elif checkName(["mp3", "256"], any) and not checkName(["flac"], all):
|
||||||
return Quality.B256
|
return Quality.B256
|
||||||
elif checkName(["mp3", "vbr"], any) and not checkName(["flac"], all):
|
elif checkName(["mp3", "vbr"], any) and not checkName(["flac"], all):
|
||||||
return Quality.VBR
|
return Quality.VBR
|
||||||
elif checkName(["mp3", "320"], any) and not checkName(["flac"], all):
|
elif checkName(["mp3", "320"], any) and not checkName(["flac"], all):
|
||||||
return Quality.B320
|
return Quality.B320
|
||||||
else:
|
else:
|
||||||
return Quality.UNKNOWN
|
return Quality.UNKNOWN
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def assumeQuality(name):
|
def assumeQuality(name):
|
||||||
|
|
||||||
if name.lower().endswith(".mp3"):
|
if name.lower().endswith(".mp3"):
|
||||||
return Quality.MP3
|
return Quality.MP3
|
||||||
elif name.lower().endswith(".flac"):
|
elif name.lower().endswith(".flac"):
|
||||||
return Quality.LOSSLESS
|
return Quality.LOSSLESS
|
||||||
else:
|
else:
|
||||||
return Quality.UNKNOWN
|
return Quality.UNKNOWN
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compositeStatus(status, quality):
|
def compositeStatus(status, quality):
|
||||||
return status + 100 * quality
|
return status + 100 * quality
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def qualityDownloaded(status):
|
def qualityDownloaded(status):
|
||||||
return (status - DOWNLOADED) / 100
|
return (status - DOWNLOADED) / 100
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def splitCompositeStatus(status):
|
def splitCompositeStatus(status):
|
||||||
"""Returns a tuple containing (status, quality)"""
|
"""Returns a tuple containing (status, quality)"""
|
||||||
for x in sorted(Quality.qualityStrings.keys(), reverse=True):
|
for x in sorted(Quality.qualityStrings.keys(), reverse=True):
|
||||||
if status > x*100:
|
if status > x*100:
|
||||||
return (status-x*100, x)
|
return (status-x*100, x)
|
||||||
|
|
||||||
return (Quality.NONE, status)
|
return (Quality.NONE, status)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def statusFromName(name, assume=True):
|
def statusFromName(name, assume=True):
|
||||||
quality = Quality.nameQuality(name)
|
quality = Quality.nameQuality(name)
|
||||||
if assume and quality == Quality.UNKNOWN:
|
if assume and quality == Quality.UNKNOWN:
|
||||||
quality = Quality.assumeQuality(name)
|
quality = Quality.assumeQuality(name)
|
||||||
return Quality.compositeStatus(DOWNLOADED, quality)
|
return Quality.compositeStatus(DOWNLOADED, quality)
|
||||||
|
|
||||||
DOWNLOADED = None
|
DOWNLOADED = None
|
||||||
SNATCHED = None
|
SNATCHED = None
|
||||||
SNATCHED_PROPER = None
|
SNATCHED_PROPER = None
|
||||||
|
|
||||||
Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()]
|
Quality.DOWNLOADED = [Quality.compositeStatus(DOWNLOADED, x) for x in Quality.qualityStrings.keys()]
|
||||||
Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()]
|
Quality.SNATCHED = [Quality.compositeStatus(SNATCHED, x) for x in Quality.qualityStrings.keys()]
|
||||||
Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()]
|
Quality.SNATCHED_PROPER = [Quality.compositeStatus(SNATCHED_PROPER, x) for x in Quality.qualityStrings.keys()]
|
||||||
|
|
||||||
MP3 = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR], [])
|
MP3 = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR], [])
|
||||||
LOSSLESS = Quality.combineQualities([Quality.FLAC], [])
|
LOSSLESS = Quality.combineQualities([Quality.FLAC], [])
|
||||||
ANY = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR, Quality.FLAC], [])
|
ANY = Quality.combineQualities([Quality.B192, Quality.B256, Quality.B320, Quality.VBR, Quality.FLAC], [])
|
||||||
|
|
||||||
qualityPresets = (MP3, LOSSLESS, ANY)
|
qualityPresets = (MP3, LOSSLESS, ANY)
|
||||||
qualityPresetStrings = {MP3: "MP3 (All bitrates 192+)",
|
qualityPresetStrings = {MP3: "MP3 (All bitrates 192+)",
|
||||||
LOSSLESS: "Lossless (flac)",
|
LOSSLESS: "Lossless (flac)",
|
||||||
ANY: "Any"}
|
ANY: "Any"}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from headphones import logger
|
|||||||
def dbFilename(filename="headphones.db"):
|
def dbFilename(filename="headphones.db"):
|
||||||
|
|
||||||
return os.path.join(headphones.DATA_DIR, filename)
|
return os.path.join(headphones.DATA_DIR, filename)
|
||||||
|
|
||||||
def getCacheSize():
|
def getCacheSize():
|
||||||
#this will protect against typecasting problems produced by empty string and None settings
|
#this will protect against typecasting problems produced by empty string and None settings
|
||||||
if not headphones.CACHE_SIZEMB:
|
if not headphones.CACHE_SIZEMB:
|
||||||
@@ -42,68 +42,63 @@ def getCacheSize():
|
|||||||
class DBConnection:
|
class DBConnection:
|
||||||
|
|
||||||
def __init__(self, filename="headphones.db"):
|
def __init__(self, filename="headphones.db"):
|
||||||
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.connection = sqlite3.connect(dbFilename(filename), timeout=20)
|
self.connection = sqlite3.connect(dbFilename(filename), timeout=20)
|
||||||
#don't wait for the disk to finish writing
|
#don't wait for the disk to finish writing
|
||||||
self.connection.execute("PRAGMA synchronous = OFF")
|
self.connection.execute("PRAGMA synchronous = OFF")
|
||||||
#journal disabled since we never do rollbacks
|
#journal disabled since we never do rollbacks
|
||||||
self.connection.execute("PRAGMA journal_mode = %s" % headphones.JOURNAL_MODE)
|
self.connection.execute("PRAGMA journal_mode = %s" % headphones.JOURNAL_MODE)
|
||||||
#64mb of cache memory,probably need to make it user configurable
|
#64mb of cache memory,probably need to make it user configurable
|
||||||
self.connection.execute("PRAGMA cache_size=-%s" % (getCacheSize()*1024))
|
self.connection.execute("PRAGMA cache_size=-%s" % (getCacheSize()*1024))
|
||||||
self.connection.row_factory = sqlite3.Row
|
self.connection.row_factory = sqlite3.Row
|
||||||
|
|
||||||
def action(self, query, args=None):
|
def action(self, query, args=None):
|
||||||
|
|
||||||
if query == None:
|
if query == None:
|
||||||
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
|
||||||
|
|
||||||
def upsert(self, tableName, valueDict, keyDict):
|
def upsert(self, tableName, valueDict, keyDict):
|
||||||
|
|
||||||
changesBefore = self.connection.total_changes
|
changesBefore = self.connection.total_changes
|
||||||
|
|
||||||
genParams = lambda myDict : [x + " = ?" for x in myDict.keys()]
|
genParams = lambda myDict : [x + " = ?" for x in myDict.keys()]
|
||||||
|
|
||||||
query = "UPDATE "+tableName+" SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict))
|
query = "UPDATE "+tableName+" SET " + ", ".join(genParams(valueDict)) + " WHERE " + " AND ".join(genParams(keyDict))
|
||||||
|
|
||||||
self.action(query, valueDict.values() + keyDict.values())
|
self.action(query, valueDict.values() + keyDict.values())
|
||||||
|
|
||||||
if self.connection.total_changes == changesBefore:
|
if self.connection.total_changes == changesBefore:
|
||||||
query = "INSERT INTO "+tableName+" (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \
|
query = "INSERT INTO "+tableName+" (" + ", ".join(valueDict.keys() + keyDict.keys()) + ")" + \
|
||||||
" VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")"
|
" VALUES (" + ", ".join(["?"] * len(valueDict.keys() + keyDict.keys())) + ")"
|
||||||
|
|||||||
@@ -1,41 +1,24 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# 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):
|
class HeadphonesException(Exception):
|
||||||
"""
|
"""
|
||||||
Returns a string from the exception text if it exists.
|
Generic Headphones Exception - should never be thrown, only subclassed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# sanity check
|
class NewzbinAPIThrottled(HeadphonesException):
|
||||||
if not e.args or not e.args[0]:
|
"""
|
||||||
return ""
|
Newzbin has throttled us, deal with it
|
||||||
|
"""
|
||||||
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):
|
|
||||||
"Generic Headphones Exception - should never be thrown, only subclassed"
|
|
||||||
|
|
||||||
class NewzbinAPIThrottled(HeadphonesException):
|
|
||||||
"Newzbin has throttled us, deal with it"
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from headphones import logger
|
|||||||
def getXldProfile(xldProfile):
|
def getXldProfile(xldProfile):
|
||||||
xldProfileNotFound = xldProfile
|
xldProfileNotFound = xldProfile
|
||||||
expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist')
|
expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist')
|
||||||
try:
|
try:
|
||||||
preferences = plistlib.Plist.fromFile(expandedPath)
|
preferences = plistlib.Plist.fromFile(expandedPath)
|
||||||
except (expat.ExpatError):
|
except (expat.ExpatError):
|
||||||
os.system("/usr/bin/plutil -convert xml1 %s" % expandedPath )
|
os.system("/usr/bin/plutil -convert xml1 %s" % expandedPath )
|
||||||
@@ -61,7 +61,7 @@ def getXldProfile(xldProfile):
|
|||||||
elif 'TVBR' in ShortDesc:
|
elif 'TVBR' in ShortDesc:
|
||||||
XLDAacOutput2_VBRQuality = int(profile.get('XLDAacOutput2_VBRQuality'))
|
XLDAacOutput2_VBRQuality = int(profile.get('XLDAacOutput2_VBRQuality'))
|
||||||
if XLDAacOutput2_VBRQuality > 122:
|
if XLDAacOutput2_VBRQuality > 122:
|
||||||
xldBitrate = 320
|
xldBitrate = 320
|
||||||
elif XLDAacOutput2_VBRQuality > 113 and XLDAacOutput2_VBRQuality <= 122:
|
elif XLDAacOutput2_VBRQuality > 113 and XLDAacOutput2_VBRQuality <= 122:
|
||||||
xldBitrate = 285
|
xldBitrate = 285
|
||||||
elif XLDAacOutput2_VBRQuality > 104 and XLDAacOutput2_VBRQuality <= 113:
|
elif XLDAacOutput2_VBRQuality > 104 and XLDAacOutput2_VBRQuality <= 113:
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import time
|
|||||||
import shutil
|
import shutil
|
||||||
import datetime
|
import datetime
|
||||||
import headphones
|
import headphones
|
||||||
|
import unicodedata
|
||||||
|
import sys
|
||||||
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||||
@@ -168,12 +170,17 @@ 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
|
||||||
|
|
||||||
@@ -274,7 +281,7 @@ def expand_subfolders(f):
|
|||||||
difference = max(path_depths) - min(path_depths)
|
difference = max(path_depths) - min(path_depths)
|
||||||
|
|
||||||
if difference > 0:
|
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))
|
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
|
# 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
|
# directory may contain separate CD's and maybe some extra's. The
|
||||||
@@ -283,7 +290,7 @@ def expand_subfolders(f):
|
|||||||
extra_media_folders = [ media_folder[:min(path_depths)] for media_folder in media_folders if len(media_folder) > min(path_depths) ]
|
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 ]))
|
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)
|
logger.info("Please look at the following folder(s), since they cause the depth difference: %s", extra_media_folders)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Convert back to paths and remove duplicates, which may be there after
|
# Convert back to paths and remove duplicates, which may be there after
|
||||||
@@ -297,7 +304,7 @@ def expand_subfolders(f):
|
|||||||
logger.debug("Did not expand subfolder, as it resulted in one folder.")
|
logger.debug("Did not expand subfolder, as it resulted in one folder.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug("Expanded subfolders in folder: " % media_folders)
|
logger.debug("Expanded subfolders in folder: %s", media_folders)
|
||||||
return media_folders
|
return media_folders
|
||||||
|
|
||||||
def extract_data(s):
|
def extract_data(s):
|
||||||
@@ -360,7 +367,7 @@ def extract_metadata(f):
|
|||||||
# Try to read the file info
|
# Try to read the file info
|
||||||
try:
|
try:
|
||||||
media_file = MediaFile(os.path.join(root, file))
|
media_file = MediaFile(os.path.join(root, file))
|
||||||
except FileTypeError, UnreadableFileError:
|
except (FileTypeError, UnreadableFileError):
|
||||||
# Probably not a media file
|
# Probably not a media file
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -374,14 +381,14 @@ def extract_metadata(f):
|
|||||||
|
|
||||||
# Verify results
|
# Verify results
|
||||||
if len(results) == 0:
|
if len(results) == 0:
|
||||||
logger.info("No metadata in media files found, ignoring")
|
logger.info("No metadata in media files found, ignoring.")
|
||||||
return (None, None, None)
|
return (None, None, None)
|
||||||
|
|
||||||
# Require that some percentage of files have tags
|
# Require that some percentage of files have tags
|
||||||
count_ratio = 0.75
|
count_ratio = 0.75
|
||||||
|
|
||||||
if count < (count_ratio * len(results)):
|
if count < (count_ratio * len(results)):
|
||||||
logger.info("Counted %d media files, but only %d have tags, ignoring" % (count, len(results)))
|
logger.info("Counted %d media files, but only %d have tags, ignoring.", count, len(results))
|
||||||
return (None, None, None)
|
return (None, None, None)
|
||||||
|
|
||||||
# Count distinct values
|
# Count distinct values
|
||||||
@@ -399,7 +406,7 @@ def extract_metadata(f):
|
|||||||
old_album = new_albums[index]
|
old_album = new_albums[index]
|
||||||
new_albums[index] = RE_CD_ALBUM.sub("", album).strip()
|
new_albums[index] = RE_CD_ALBUM.sub("", album).strip()
|
||||||
|
|
||||||
logger.debug("Stripped albumd number identifier: %s -> %s" % (old_album, new_albums[index]))
|
logger.debug("Stripped albumd number identifier: %s -> %s", old_album, new_albums[index])
|
||||||
|
|
||||||
# Remove duplicates
|
# Remove duplicates
|
||||||
new_albums = list(set(new_albums))
|
new_albums = list(set(new_albums))
|
||||||
@@ -417,7 +424,7 @@ def extract_metadata(f):
|
|||||||
if len(artists) > 1 and len(albums) == 1:
|
if len(artists) > 1 and len(albums) == 1:
|
||||||
split_artists = [ RE_FEATURING.split(artist) for artist in artists ]
|
split_artists = [ RE_FEATURING.split(artist) for artist in artists ]
|
||||||
featurings = [ len(split_artist) - 1 for split_artist in split_artists ]
|
featurings = [ len(split_artist) - 1 for split_artist in split_artists ]
|
||||||
logger.info("Album seem to feature %d different artists" % sum(featurings))
|
logger.info("Album seem to feature %d different artists", sum(featurings))
|
||||||
|
|
||||||
if sum(featurings) > 0:
|
if sum(featurings) > 0:
|
||||||
# Find the artist of which the least splits have been generated.
|
# Find the artist of which the least splits have been generated.
|
||||||
@@ -429,8 +436,8 @@ def extract_metadata(f):
|
|||||||
return (artist, albums[0], years[0])
|
return (artist, albums[0], years[0])
|
||||||
|
|
||||||
# Not sure what to do here.
|
# 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.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))
|
logger.debug("Artists: %s, Albums: %s, Years: %s", artists, albums, years)
|
||||||
|
|
||||||
return (None, None, None)
|
return (None, None, None)
|
||||||
|
|
||||||
@@ -486,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
|
||||||
@@ -596,9 +603,11 @@ 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 Exception, e:
|
with open(ssl_cert, 'w') as f:
|
||||||
|
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||||
|
except IOError as e:
|
||||||
logger.error("Error creating SSL key and certificate: %s", e)
|
logger.error("Error creating SSL key and certificate: %s", e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -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 *
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import os
|
|
||||||
from beets.mediafile import MediaFile
|
|
||||||
|
|
||||||
import headphones
|
|
||||||
from headphones import logger, helpers, db, mb, lastfm
|
from headphones import logger, helpers, db, mb, lastfm
|
||||||
|
|
||||||
blacklisted_special_artist_names = ['[anonymous]','[data]','[no artist]','[traditional]','[unknown]','Various Artists']
|
from beets.mediafile import MediaFile
|
||||||
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']
|
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import headphones
|
||||||
|
|
||||||
|
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
|
||||||
@@ -208,10 +211,12 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
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']])
|
||||||
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [items['AlbumID']])
|
myDB.action('DELETE from releases WHERE ReleaseGroupID=?', [items['AlbumID']])
|
||||||
logger.info("[%s] Removing all references to release group %s to reflect MusicBrainz" % (artist['artist_name'], items['AlbumID']))
|
logger.info("[%s] Removing all references to release group %s to reflect MusicBrainz refresh" % (artist['artist_name'], items['AlbumID']))
|
||||||
force_repackage = 1
|
if not extrasonly:
|
||||||
|
force_repackage = 1
|
||||||
else:
|
else:
|
||||||
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'])
|
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 = []
|
album_searches = []
|
||||||
@@ -224,63 +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']])
|
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
|
||||||
@@ -311,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
|
||||||
@@ -378,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()
|
||||||
|
|
||||||
@@ -399,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"
|
||||||
@@ -420,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']}
|
||||||
|
|
||||||
@@ -469,10 +484,35 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
|||||||
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}
|
||||||
|
|
||||||
@@ -493,27 +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'])
|
|
||||||
|
|
||||||
# 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 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
|
||||||
@@ -527,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 %s: %s', rid, 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 %s: no dict', rid)
|
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']
|
||||||
@@ -549,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'],
|
||||||
@@ -565,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)
|
||||||
@@ -588,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'],
|
||||||
@@ -619,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!")
|
||||||
@@ -659,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'],
|
||||||
@@ -701,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:
|
||||||
|
|||||||
@@ -21,16 +21,17 @@ from headphones import db, logger, request
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
ENTRY_POINT = 'http://ws.audioscrobbler.com/2.0/'
|
TIMEOUT = 60 # seconds
|
||||||
API_KEY = '395e6ec6bb557382fc41fde867bce66f'
|
ENTRY_POINT = "http://ws.audioscrobbler.com/2.0/"
|
||||||
|
API_KEY = "395e6ec6bb557382fc41fde867bce66f"
|
||||||
|
|
||||||
def request_lastfm(method, **kwargs):
|
def request_lastfm(method, **kwargs):
|
||||||
"""
|
"""
|
||||||
Call a Last.FM API method. Automatically sets the method and API key. Method
|
Call a Last.FM API method. Automatically sets the method and API key. Method
|
||||||
will return the result if no error occured.
|
will return the result if no error occured.
|
||||||
|
|
||||||
By default, this method will request the JSON format, since it is lighter
|
By default, this method will request the JSON format, since it is more
|
||||||
than XML.
|
lightweight than XML.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Prepare request
|
# Prepare request
|
||||||
@@ -40,7 +41,9 @@ def request_lastfm(method, **kwargs):
|
|||||||
|
|
||||||
# Send request
|
# Send request
|
||||||
logger.debug("Calling Last.FM method: %s", method)
|
logger.debug("Calling Last.FM method: %s", method)
|
||||||
data = request.request_json(ENTRY_POINT, timeout=20, params=kwargs)
|
logger.debug("Last.FM call parameters: %s", kwargs)
|
||||||
|
|
||||||
|
data = request.request_json(ENTRY_POINT, timeout=TIMEOUT, params=kwargs)
|
||||||
|
|
||||||
# Parse response and check for errors.
|
# Parse response and check for errors.
|
||||||
if not data:
|
if not data:
|
||||||
@@ -48,20 +51,20 @@ def request_lastfm(method, **kwargs):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if "error" in data:
|
if "error" in data:
|
||||||
logger.debug("Last.FM returned an error: %s", data["message"])
|
logger.error("Last.FM returned an error: %s", data["message"])
|
||||||
return
|
return
|
||||||
|
|
||||||
return data
|
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")
|
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'])
|
data = request_lastfm("artist.getsimilar", mbid=result["ArtistId"])
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
if data and "similarartists" in data:
|
if data and "similarartists" in data:
|
||||||
@@ -94,13 +97,13 @@ def getSimilar():
|
|||||||
artist_name, artist_mbid = item[0]
|
artist_name, artist_mbid = item[0]
|
||||||
count = item[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))
|
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.")
|
logger.warn("Last.FM username not set, not importing artists.")
|
||||||
@@ -129,7 +132,7 @@ def getArtists():
|
|||||||
|
|
||||||
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")
|
||||||
|
|
||||||
logger.info("Fetching top artists from Last.FM for tag: %s", tag)
|
logger.info("Fetching top artists from Last.FM for tag: %s", tag)
|
||||||
data = request_lastfm("tag.gettopartists", limit=limit, tag=tag)
|
data = request_lastfm("tag.gettopartists", limit=limit, tag=tag)
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
from beets.mediafile import MediaFile
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
|
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||||
|
|
||||||
from headphones import db, logger, helpers, importer, lastfm
|
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
|
||||||
@@ -27,18 +27,18 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
|
|
||||||
if cron and not headphones.LIBRARYSCAN:
|
if cron and not headphones.LIBRARYSCAN:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not dir:
|
if not dir:
|
||||||
if not headphones.MUSIC_DIR:
|
if not headphones.MUSIC_DIR:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
dir = headphones.MUSIC_DIR
|
dir = headphones.MUSIC_DIR
|
||||||
|
|
||||||
# If we're appending a dir, it's coming from the post processor which is
|
# If we're appending a dir, it's coming from the post processor which is
|
||||||
# already bytestring
|
# already bytestring
|
||||||
if not append:
|
if not append:
|
||||||
dir = dir.encode(headphones.SYS_ENCODING)
|
dir = dir.encode(headphones.SYS_ENCODING)
|
||||||
|
|
||||||
if not os.path.isdir(dir):
|
if not os.path.isdir(dir):
|
||||||
logger.warn('Cannot find directory: %s. Not scanning' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.warn('Cannot find directory: %s. Not scanning' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
return
|
return
|
||||||
@@ -47,21 +47,21 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
new_artists = []
|
new_artists = []
|
||||||
|
|
||||||
logger.info('Scanning music directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info('Scanning music directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
|
||||||
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)
|
||||||
if not os.path.isfile(encoded_track_string):
|
if not os.path.isfile(encoded_track_string):
|
||||||
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, track['Location']])
|
myDB.action('UPDATE tracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, track['Location']])
|
||||||
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, track['Location']])
|
myDB.action('UPDATE alltracks SET Location=?, BitRate=?, Format=? WHERE Location=?', [None, None, None, track['Location']])
|
||||||
|
|
||||||
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
|
||||||
@@ -71,21 +71,21 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
###############myDB.action('DELETE from have')
|
###############myDB.action('DELETE from have')
|
||||||
|
|
||||||
bitrates = []
|
bitrates = []
|
||||||
|
|
||||||
song_list = []
|
song_list = []
|
||||||
new_song_count = 0
|
new_song_count = 0
|
||||||
file_count = 0
|
file_count = 0
|
||||||
|
|
||||||
latest_subdirectory = []
|
latest_subdirectory = []
|
||||||
|
|
||||||
for r,d,f in os.walk(dir):
|
for r,d,f in os.walk(dir):
|
||||||
#need to abuse slicing to get a copy of the list, doing it directly will skip the element after a deleted one
|
#need to abuse slicing to get a copy of the list, doing it directly will skip the element after a deleted one
|
||||||
#using a list comprehension will not work correctly for nested subdirectories (os.walk keeps its original list)
|
#using a list comprehension will not work correctly for nested subdirectories (os.walk keeps its original list)
|
||||||
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,15 +104,14 @@ 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
|
||||||
if f.bitrate:
|
if f.bitrate:
|
||||||
bitrates.append(f.bitrate)
|
bitrates.append(f.bitrate)
|
||||||
|
|
||||||
# Use the album artist over the artist if available
|
# Use the album artist over the artist if available
|
||||||
if f.albumartist:
|
if f.albumartist:
|
||||||
f_artist = f.albumartist
|
f_artist = f.albumartist
|
||||||
@@ -120,8 +119,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
f_artist = f.artist
|
f_artist = f.artist
|
||||||
else:
|
else:
|
||||||
f_artist = None
|
f_artist = None
|
||||||
|
|
||||||
# Add the song to our song list -
|
# Add the song to our song list -
|
||||||
# TODO: skip adding songs without the minimum requisite information (just a matter of putting together the right if statements)
|
# TODO: skip adding songs without the minimum requisite information (just a matter of putting together the right if statements)
|
||||||
|
|
||||||
if f_artist and f.album and f.title:
|
if f_artist and f.album and f.title:
|
||||||
@@ -144,7 +143,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
'Format' : f.format,
|
'Format' : f.format,
|
||||||
'CleanName' : CleanName
|
'CleanName' : CleanName
|
||||||
}
|
}
|
||||||
|
|
||||||
#song_list.append(song_dict)
|
#song_list.append(song_dict)
|
||||||
check_exist_song = myDB.action("SELECT * FROM have WHERE Location=?", [unicode_song_path]).fetchone()
|
check_exist_song = myDB.action("SELECT * FROM have WHERE Location=?", [unicode_song_path]).fetchone()
|
||||||
#Only attempt to match songs that are new, haven't yet been matched, or metadata has changed.
|
#Only attempt to match songs that are new, haven't yet been matched, or metadata has changed.
|
||||||
@@ -182,17 +181,17 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
song_list = myDB.action("SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"])
|
song_list = myDB.action("SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"])
|
||||||
total_number_of_songs = myDB.action("SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"]).fetchone()[0]
|
total_number_of_songs = myDB.action("SELECT COUNT(*) FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"]).fetchone()[0]
|
||||||
logger.info("Found " + str(total_number_of_songs) + " new/modified tracks in: '" + dir.decode(headphones.SYS_ENCODING, 'replace') + "'. Matching tracks to the appropriate releases....")
|
logger.info("Found " + str(total_number_of_songs) + " new/modified tracks in: '" + dir.decode(headphones.SYS_ENCODING, 'replace') + "'. Matching tracks to the appropriate releases....")
|
||||||
|
|
||||||
# Sort the song_list by most vague (e.g. no trackid or releaseid) to most specific (both trackid & releaseid)
|
# Sort the song_list by most vague (e.g. no trackid or releaseid) to most specific (both trackid & releaseid)
|
||||||
# When we insert into the database, the tracks with the most specific information will overwrite the more general matches
|
# When we insert into the database, the tracks with the most specific information will overwrite the more general matches
|
||||||
|
|
||||||
##############song_list = helpers.multikeysort(song_list, ['ReleaseID', 'TrackID'])
|
##############song_list = helpers.multikeysort(song_list, ['ReleaseID', 'TrackID'])
|
||||||
song_list = helpers.multikeysort(song_list, ['ArtistName', 'AlbumTitle'])
|
song_list = helpers.multikeysort(song_list, ['ArtistName', 'AlbumTitle'])
|
||||||
|
|
||||||
# We'll use this to give a % completion, just because the track matching might take a while
|
# We'll use this to give a % completion, just because the track matching might take a while
|
||||||
song_count = 0
|
song_count = 0
|
||||||
latest_artist = []
|
latest_artist = []
|
||||||
|
|
||||||
for song in song_list:
|
for song in song_list:
|
||||||
|
|
||||||
latest_artist.append(song['ArtistName'])
|
latest_artist.append(song['ArtistName'])
|
||||||
@@ -200,26 +199,27 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
logger.info("Now matching songs by %s" % song['ArtistName'])
|
logger.info("Now matching songs by %s" % song['ArtistName'])
|
||||||
elif latest_artist[song_count] != latest_artist[song_count-1] and song_count !=0:
|
elif latest_artist[song_count] != latest_artist[song_count-1] and song_count !=0:
|
||||||
logger.info("Now matching songs by %s" % song['ArtistName'])
|
logger.info("Now matching songs by %s" % song['ArtistName'])
|
||||||
|
|
||||||
#print song['ArtistName']+' - '+song['AlbumTitle']+' - '+song['TrackTitle']
|
#print song['ArtistName']+' - '+song['AlbumTitle']+' - '+song['TrackTitle']
|
||||||
song_count += 1
|
song_count += 1
|
||||||
completion_percentage = float(song_count)/total_number_of_songs * 100
|
completion_percentage = float(song_count)/total_number_of_songs * 100
|
||||||
|
|
||||||
if completion_percentage%10 == 0:
|
if completion_percentage%10 == 0:
|
||||||
logger.info("Track matching is " + str(completion_percentage) + "% complete")
|
logger.info("Track matching is " + str(completion_percentage) + "% complete")
|
||||||
|
|
||||||
#THE "MORE-SPECIFIC" CLAUSES HERE HAVE ALL BEEN REMOVED. WHEN RUNNING A LIBRARY SCAN, THE ONLY CLAUSES THAT
|
#THE "MORE-SPECIFIC" CLAUSES HERE HAVE ALL BEEN REMOVED. WHEN RUNNING A LIBRARY SCAN, THE ONLY CLAUSES THAT
|
||||||
#EVER GOT HIT WERE [ARTIST/ALBUM/TRACK] OR CLEANNAME. ARTISTID & RELEASEID ARE NEVER PASSED TO THIS FUNCTION,
|
#EVER GOT HIT WERE [ARTIST/ALBUM/TRACK] OR CLEANNAME. ARTISTID & RELEASEID ARE NEVER PASSED TO THIS FUNCTION,
|
||||||
#ARE NEVER FOUND, AND THE OTHER CLAUSES WERE NEVER HIT. FURTHERMORE, OTHER MATCHING FUNCTIONS IN THIS PROGRAM
|
#ARE NEVER FOUND, AND THE OTHER CLAUSES WERE NEVER HIT. FURTHERMORE, OTHER MATCHING FUNCTIONS IN THIS PROGRAM
|
||||||
#(IMPORTER.PY, MB.PY) SIMPLY DO A [ARTIST/ALBUM/TRACK] OR CLEANNAME MATCH, SO IT'S ALL CONSISTENT.
|
#(IMPORTER.PY, MB.PY) SIMPLY DO A [ARTIST/ALBUM/TRACK] OR CLEANNAME MATCH, SO IT'S ALL CONSISTENT.
|
||||||
|
|
||||||
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'],
|
||||||
'TrackTitle' : track['TrackTitle'] }
|
'TrackTitle' : track['TrackTitle'] }
|
||||||
newValueDict = { 'Location' : song['Location'],
|
newValueDict = { 'Location' : song['Location'],
|
||||||
'BitRate' : song['BitRate'],
|
'BitRate' : song['BitRate'],
|
||||||
'Format' : song['Format'] }
|
'Format' : song['Format'] }
|
||||||
@@ -228,25 +228,27 @@ 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:
|
||||||
controlValueDict = { 'CleanName' : track['CleanName']}
|
controlValueDict = { 'CleanName' : track['CleanName']}
|
||||||
newValueDict = { 'Location' : song['Location'],
|
newValueDict = { 'Location' : song['Location'],
|
||||||
'BitRate' : song['BitRate'],
|
'BitRate' : song['BitRate'],
|
||||||
'Format' : song['Format'] }
|
'Format' : song['Format'] }
|
||||||
myDB.upsert("tracks", newValueDict, controlValueDict)
|
myDB.upsert("tracks", newValueDict, controlValueDict)
|
||||||
|
|
||||||
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:
|
||||||
controlValueDict = { 'ArtistName' : alltrack['ArtistName'],
|
controlValueDict = { 'ArtistName' : alltrack['ArtistName'],
|
||||||
'AlbumTitle' : alltrack['AlbumTitle'],
|
'AlbumTitle' : alltrack['AlbumTitle'],
|
||||||
@@ -262,52 +264,55 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
else:
|
else:
|
||||||
alltrack = myDB.action('SELECT CleanName, AlbumID from alltracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
alltrack = myDB.action('SELECT CleanName, AlbumID from alltracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
||||||
if alltrack:
|
if alltrack:
|
||||||
controlValueDict = { 'CleanName' : alltrack['CleanName']}
|
controlValueDict = { 'CleanName' : alltrack['CleanName']}
|
||||||
newValueDict = { 'Location' : song['Location'],
|
newValueDict = { 'Location' : song['Location'],
|
||||||
'BitRate' : song['BitRate'],
|
'BitRate' : song['BitRate'],
|
||||||
'Format' : song['Format'] }
|
'Format' : song['Format'] }
|
||||||
myDB.upsert("alltracks", newValueDict, controlValueDict)
|
myDB.upsert("alltracks", newValueDict, controlValueDict)
|
||||||
|
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
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"}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
|
||||||
#######myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [song['ArtistName'], song['AlbumTitle'], song['TrackNumber'], song['TrackTitle'], song['TrackLength'], song['BitRate'], song['Genre'], song['Date'], song['TrackID'], song['Location'], CleanName, song['Format']])
|
#######myDB.action('INSERT INTO have (ArtistName, AlbumTitle, TrackNumber, TrackTitle, TrackLength, BitRate, Genre, Date, TrackID, Location, CleanName, Format) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [song['ArtistName'], song['AlbumTitle'], song['TrackNumber'], song['TrackTitle'], song['TrackLength'], song['BitRate'], song['Genre'], song['Date'], song['TrackID'], song['Location'], CleanName, song['Format']])
|
||||||
|
|
||||||
logger.info('Completed matching tracks from directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info('Completed matching tracks from directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
|
||||||
|
|
||||||
if not append:
|
if not append:
|
||||||
logger.info('Updating scanned artist track counts')
|
logger.info('Updating scanned artist track counts')
|
||||||
|
|
||||||
# Clean up the new artist list
|
# Clean up the new artist list
|
||||||
unique_artists = {}.fromkeys(new_artists).keys()
|
unique_artists = {}.fromkeys(new_artists).keys()
|
||||||
current_artists = myDB.select('SELECT ArtistName, ArtistID from artists')
|
current_artists = myDB.select('SELECT ArtistName, ArtistID from artists')
|
||||||
|
|
||||||
#There was a bug where artists with special characters (-,') would show up in new artists.
|
#There was a bug where artists with special characters (-,') would show up in new artists.
|
||||||
artist_list = [f for f in unique_artists if helpers.cleanName(f).lower() not in [helpers.cleanName(x[0]).lower() for x in current_artists]]
|
artist_list = [f for f in unique_artists if helpers.cleanName(f).lower() not in [helpers.cleanName(x[0]).lower() for x in current_artists]]
|
||||||
artists_checked = [f for f in unique_artists if helpers.cleanName(f).lower() in [helpers.cleanName(x[0]).lower() for x in current_artists]]
|
artists_checked = [f for f in unique_artists if helpers.cleanName(f).lower() in [helpers.cleanName(x[0]).lower() for x in current_artists]]
|
||||||
|
|
||||||
# Update track counts
|
# Update track counts
|
||||||
|
|
||||||
for artist in artists_checked:
|
for artist in artists_checked:
|
||||||
# Have tracks are selected from tracks table and not all tracks because of duplicates
|
# Have tracks are selected from tracks table and not all tracks because of duplicates
|
||||||
# We update the track count upon an album switch to compliment this
|
# We update the track count upon an album switch to compliment this
|
||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistName like ? AND Location IS NOT NULL', [artist])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artist]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistName like ? AND Location IS NOT NULL', [artist])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artist]))
|
||||||
#Note, some people complain about having "artist have tracks" > # of tracks total in artist official releases
|
#Note, some people complain about having "artist have tracks" > # of tracks total in artist official releases
|
||||||
# (can fix by getting rid of second len statement)
|
# (can fix by getting rid of second len statement)
|
||||||
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistName=?', [havetracks, artist])
|
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistName=?', [havetracks, artist])
|
||||||
|
|
||||||
logger.info('Found %i new artists' % len(artist_list))
|
logger.info('Found %i new artists' % len(artist_list))
|
||||||
|
|
||||||
if len(artist_list):
|
if len(artist_list):
|
||||||
if headphones.ADD_ARTISTS:
|
if headphones.ADD_ARTISTS:
|
||||||
logger.info('Importing %i new artists' % len(artist_list))
|
logger.info('Importing %i new artists' % len(artist_list))
|
||||||
@@ -317,23 +322,23 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
#myDB.action('DELETE from newartists')
|
#myDB.action('DELETE from newartists')
|
||||||
for artist in artist_list:
|
for artist in artist_list:
|
||||||
myDB.action('INSERT OR IGNORE INTO newartists VALUES (?)', [artist])
|
myDB.action('INSERT OR IGNORE INTO newartists VALUES (?)', [artist])
|
||||||
|
|
||||||
if headphones.DETECT_BITRATE:
|
if headphones.DETECT_BITRATE:
|
||||||
headphones.PREFERRED_BITRATE = sum(bitrates)/len(bitrates)/1000
|
headphones.PREFERRED_BITRATE = sum(bitrates)/len(bitrates)/1000
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# If we're appending a new album to the database, update the artists total track counts
|
# If we're appending a new album to the database, update the artists total track counts
|
||||||
logger.info('Updating artist track counts')
|
logger.info('Updating artist track counts')
|
||||||
|
|
||||||
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:
|
if not append:
|
||||||
|
update_album_status()
|
||||||
lastfm.getSimilar()
|
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')
|
||||||
@@ -355,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,24 +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 os
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
import traceback
|
|
||||||
import threading
|
|
||||||
import headphones
|
|
||||||
|
|
||||||
from logging import handlers
|
|
||||||
|
|
||||||
from headphones import helpers
|
from headphones import helpers
|
||||||
|
|
||||||
|
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
|
# These settings are for file logging only
|
||||||
FILENAME = 'headphones.log'
|
FILENAME = "headphones.log"
|
||||||
MAX_SIZE = 1000000 # 1 MB
|
MAX_SIZE = 1000000 # 1 MB
|
||||||
MAX_FILES = 5
|
MAX_FILES = 5
|
||||||
|
|
||||||
# Headphones logger
|
# Headphones logger
|
||||||
logger = logging.getLogger('headphones')
|
logger = logging.getLogger("headphones")
|
||||||
|
|
||||||
|
# Global queue of multiprocessing logging
|
||||||
|
queue = multiprocessing.Queue()
|
||||||
|
|
||||||
class LogListHandler(logging.Handler):
|
class LogListHandler(logging.Handler):
|
||||||
"""
|
"""
|
||||||
@@ -43,19 +49,65 @@ class LogListHandler(logging.Handler):
|
|||||||
|
|
||||||
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
||||||
|
|
||||||
def initLogger(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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
queue_listener = QueueListener(queue, *logger.handlers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
queue_listener.start()
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
queue_listener.stop()
|
||||||
|
|
||||||
|
def initMultiprocessing():
|
||||||
|
"""
|
||||||
|
Remove all handlers and add QueueHandler on top. This should only be called
|
||||||
|
inside a multiprocessing worker process, since it changes the logger
|
||||||
|
completely.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for handler in logger.handlers[:]:
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
|
||||||
|
queue_handler = QueueHandler(queue)
|
||||||
|
queue_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
logger.addHandler(queue_handler)
|
||||||
|
|
||||||
|
# Change current thread name for log record
|
||||||
|
threading.current_thread().name = multiprocessing.current_process().name
|
||||||
|
|
||||||
|
def initLogger(console=False, verbose=False):
|
||||||
"""
|
"""
|
||||||
Setup logging for Headphones. It uses the logger instance with the name
|
Setup logging for Headphones. It uses the logger instance with the name
|
||||||
'headphones'. Three log handlers are added:
|
'headphones'. Three log handlers are added:
|
||||||
|
|
||||||
* RotatingFileHandler: for the file headphones.log
|
* RotatingFileHandler: for the file headphones.log
|
||||||
* LogListHandler: for Web UI
|
* LogListHandler: for Web UI
|
||||||
* StreamHandler: for console (if verbose > 0)
|
* StreamHandler: for console (if console)
|
||||||
|
|
||||||
|
Console logging is only enabled if console is set to True.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
logger.removeHandler(handler)
|
||||||
|
|
||||||
# Configure the logger to accept all messages
|
# Configure the logger to accept all messages
|
||||||
logger.propagate = False
|
logger.propagate = False
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
|
|
||||||
# Setup file logger
|
# Setup file logger
|
||||||
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
filename = os.path.join(headphones.LOG_DIR, FILENAME)
|
||||||
@@ -69,20 +121,16 @@ def initLogger(verbose=1):
|
|||||||
|
|
||||||
# Add list logger
|
# Add list logger
|
||||||
loglist_handler = LogListHandler()
|
loglist_handler = LogListHandler()
|
||||||
loglist_handler.setLevel(logging.INFO)
|
loglist_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
logger.addHandler(loglist_handler)
|
logger.addHandler(loglist_handler)
|
||||||
|
|
||||||
# Setup console logger
|
# Setup console logger
|
||||||
if verbose:
|
if console:
|
||||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = logging.StreamHandler()
|
||||||
console_handler.setFormatter(console_formatter)
|
console_handler.setFormatter(console_formatter)
|
||||||
|
console_handler.setLevel(logging.DEBUG)
|
||||||
if verbose == 1:
|
|
||||||
console_handler.setLevel(logging.INFO)
|
|
||||||
elif verbose == 2:
|
|
||||||
console_handler.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
logger.addHandler(console_handler)
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
|||||||
@@ -27,26 +27,26 @@ def getLyrics(artist, song):
|
|||||||
|
|
||||||
url = 'http://lyrics.wikia.com/api.php'
|
url = 'http://lyrics.wikia.com/api.php'
|
||||||
data = request.request_minidom(url, params=params)
|
data = request.request_minidom(url, params=params)
|
||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
url = data.getElementsByTagName("url")
|
url = data.getElementsByTagName("url")
|
||||||
|
|
||||||
if url:
|
if url:
|
||||||
lyricsurl = url[0].firstChild.nodeValue
|
lyricsurl = url[0].firstChild.nodeValue
|
||||||
else:
|
else:
|
||||||
logger.info('No lyrics found for %s - %s' % (artist, song))
|
logger.info('No lyrics found for %s - %s' % (artist, song))
|
||||||
return
|
return
|
||||||
|
|
||||||
lyricspage = request.request_content(lyricsurl)
|
lyricspage = request.request_content(lyricsurl)
|
||||||
|
|
||||||
if not lyricspage:
|
if not lyricspage:
|
||||||
logger.warn('Error fetching lyrics from: %s' % lyricsurl)
|
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)
|
||||||
|
|
||||||
if not m:
|
if not m:
|
||||||
m = re.compile('''<div class='lyricbox'><span style="padding:1em"><a href="/Category:Instrumental" title="Instrumental">''').search(lyricspage)
|
m = re.compile('''<div class='lyricbox'><span style="padding:1em"><a href="/Category:Instrumental" title="Instrumental">''').search(lyricspage)
|
||||||
if m:
|
if m:
|
||||||
@@ -54,10 +54,10 @@ def getLyrics(artist, song):
|
|||||||
else:
|
else:
|
||||||
logger.warn('Cannot find lyrics on: %s' % lyricsurl)
|
logger.warn('Cannot find lyrics on: %s' % lyricsurl)
|
||||||
return
|
return
|
||||||
|
|
||||||
lyrics = convert_html_entities(m.group(1)).replace('<br />', '\n')
|
lyrics = convert_html_entities(m.group(1)).replace('<br />', '\n')
|
||||||
lyrics = re.sub('<.*?>', '', lyrics)
|
lyrics = re.sub('<.*?>', '', lyrics)
|
||||||
|
|
||||||
return lyrics
|
return lyrics
|
||||||
|
|
||||||
def convert_html_entities(s):
|
def convert_html_entities(s):
|
||||||
|
|||||||
@@ -13,28 +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/>.
|
||||||
|
|
||||||
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():
|
||||||
|
|
||||||
mbuser = None
|
mbuser = None
|
||||||
mbpass = None
|
mbpass = None
|
||||||
|
|
||||||
if headphones.MIRROR == "musicbrainz.org":
|
if headphones.MIRROR == "musicbrainz.org":
|
||||||
mbhost = "musicbrainz.org"
|
mbhost = "musicbrainz.org"
|
||||||
mbport = 80
|
mbport = 80
|
||||||
@@ -51,7 +53,7 @@ def startmb():
|
|||||||
sleepytime = 0
|
sleepytime = 0
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
musicbrainzngs.set_useragent("headphones","0.0","https://github.com/rembo10/headphones")
|
musicbrainzngs.set_useragent("headphones","0.0","https://github.com/rembo10/headphones")
|
||||||
musicbrainzngs.set_hostname(mbhost + ":" + str(mbport))
|
musicbrainzngs.set_hostname(mbhost + ":" + str(mbport))
|
||||||
if sleepytime == 0:
|
if sleepytime == 0:
|
||||||
@@ -66,29 +68,31 @@ def startmb():
|
|||||||
logger.warn("No username or password set for VIP server")
|
logger.warn("No username or password set for VIP server")
|
||||||
else:
|
else:
|
||||||
musicbrainzngs.hpauth(mbuser,mbpass)
|
musicbrainzngs.hpauth(mbuser,mbpass)
|
||||||
|
|
||||||
logger.debug('Using the following server values: MBHost: %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
|
||||||
|
|
||||||
def findArtist(name, limit=1):
|
def findArtist(name, limit=1):
|
||||||
|
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
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)
|
||||||
|
|
||||||
if not artistResults:
|
if not artistResults:
|
||||||
return False
|
return False
|
||||||
for result in artistResults:
|
for result in artistResults:
|
||||||
if 'disambiguation' in result:
|
if 'disambiguation' in result:
|
||||||
uniquename = unicode(result['sort-name'] + " (" + result['disambiguation'] + ")")
|
uniquename = unicode(result['sort-name'] + " (" + result['disambiguation'] + ")")
|
||||||
@@ -96,7 +100,7 @@ def findArtist(name, limit=1):
|
|||||||
uniquename = unicode(result['sort-name'])
|
uniquename = unicode(result['sort-name'])
|
||||||
if result['name'] != uniquename and limit == 1:
|
if result['name'] != uniquename and limit == 1:
|
||||||
logger.info('Found an artist with a disambiguation: %s - doing an album based search' % name)
|
logger.info('Found an artist with a disambiguation: %s - doing an album based search' % name)
|
||||||
artistdict = findArtistbyAlbum(name)
|
artistdict = findArtistbyAlbum(name)
|
||||||
if not artistdict:
|
if not artistdict:
|
||||||
logger.info('Cannot determine the best match from an artist/album search. Using top match instead')
|
logger.info('Cannot determine the best match from an artist/album search. Using top match instead')
|
||||||
artistlist.append({
|
artistlist.append({
|
||||||
@@ -106,10 +110,10 @@ def findArtist(name, limit=1):
|
|||||||
'id': unicode(result['id']),
|
'id': unicode(result['id']),
|
||||||
# 'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
|
# 'url': unicode("http://musicbrainz.org/artist/" + result['id']),#probably needs to be changed
|
||||||
# 'score': int(result['ext:score'])
|
# 'score': int(result['ext:score'])
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
artistlist.append(artistdict)
|
artistlist.append(artistdict)
|
||||||
else:
|
else:
|
||||||
artistlist.append({
|
artistlist.append({
|
||||||
'name': unicode(result['sort-name']),
|
'name': unicode(result['sort-name']),
|
||||||
'uniquename': uniquename,
|
'uniquename': uniquename,
|
||||||
@@ -118,71 +122,119 @@ def findArtist(name, limit=1):
|
|||||||
'score': int(result['ext:score'])
|
'score': int(result['ext:score'])
|
||||||
})
|
})
|
||||||
return artistlist
|
return artistlist
|
||||||
|
|
||||||
def findRelease(name, limit=1):
|
|
||||||
|
|
||||||
with mb_lock:
|
def findRelease(name, limit=1, artist=None):
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
def getArtist(artistid, extrasonly=False):
|
def getArtist(artistid, extrasonly=False):
|
||||||
|
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
artist_dict = {}
|
artist_dict = {}
|
||||||
|
|
||||||
artist = None
|
artist = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
limit = 200
|
limit = 200
|
||||||
artist = musicbrainzngs.get_artist_by_id(artistid)['artist']
|
artist = musicbrainzngs.get_artist_by_id(artistid)['artist']
|
||||||
newRgs = None
|
newRgs = None
|
||||||
artist['release-group-list'] = []
|
artist['release-group-list'] = []
|
||||||
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:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not artist:
|
if not artist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#if 'disambiguation' in artist:
|
#if 'disambiguation' in artist:
|
||||||
# uniquename = unicode(artist['sort-name'] + " (" + artist['disambiguation'] + ")")
|
# uniquename = unicode(artist['sort-name'] + " (" + artist['disambiguation'] + ")")
|
||||||
#else:
|
#else:
|
||||||
# uniquename = unicode(artist['sort-name'])
|
# uniquename = unicode(artist['sort-name'])
|
||||||
|
|
||||||
artist_dict['artist_name'] = unicode(artist['name'])
|
artist_dict['artist_name'] = unicode(artist['name'])
|
||||||
|
|
||||||
# Not using the following values anywhere yet so we don't need to grab them.
|
# Not using the following values anywhere yet so we don't need to grab them.
|
||||||
# Was causing an exception to be raised if they didn't exist.
|
# Was causing an exception to be raised if they didn't exist.
|
||||||
#
|
#
|
||||||
#artist_dict['artist_sortname'] = unicode(artist['sort-name'])
|
#artist_dict['artist_sortname'] = unicode(artist['sort-name'])
|
||||||
#artist_dict['artist_uniquename'] = uniquename
|
#artist_dict['artist_uniquename'] = uniquename
|
||||||
#artist_dict['artist_type'] = unicode(artist['type'])
|
#artist_dict['artist_type'] = unicode(artist['type'])
|
||||||
@@ -193,22 +245,22 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
# if 'begin' in artist['life-span']:
|
# if 'begin' in artist['life-span']:
|
||||||
# artist_dict['artist_begindate'] = unicode(artist['life-span']['begin'])
|
# artist_dict['artist_begindate'] = unicode(artist['life-span']['begin'])
|
||||||
# if 'end' in artist['life-span']:
|
# if 'end' in artist['life-span']:
|
||||||
# artist_dict['artist_enddate'] = unicode(artist['life-span']['end'])
|
# artist_dict['artist_enddate'] = unicode(artist['life-span']['end'])
|
||||||
|
|
||||||
|
|
||||||
releasegroups = []
|
releasegroups = []
|
||||||
|
|
||||||
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']),
|
||||||
'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'])
|
||||||
})
|
})
|
||||||
|
|
||||||
# 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 over global option
|
||||||
# Global options are set when adding a new artist
|
# Global options are set when adding a new artist
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
@@ -218,110 +270,130 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
includeExtras = db_artist['IncludeExtras']
|
includeExtras = db_artist['IncludeExtras']
|
||||||
except IndexError:
|
except IndexError:
|
||||||
includeExtras = False
|
includeExtras = 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", "other"]
|
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
|
||||||
|
|
||||||
return artist_dict
|
return artist_dict
|
||||||
|
|
||||||
def getReleaseGroup(rgid):
|
def getReleaseGroup(rgid):
|
||||||
"""
|
"""
|
||||||
Returns a list of releases in a release group
|
Returns a list of releases in a release group
|
||||||
"""
|
"""
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
|
|
||||||
releaselist = []
|
releaselist = []
|
||||||
|
|
||||||
releaseGroup = None
|
releaseGroup = None
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
if not releaseGroup:
|
if not releaseGroup:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return releaseGroup['release-list']
|
return releaseGroup['release-list']
|
||||||
|
|
||||||
def getRelease(releaseid, include_artist_info=True):
|
def getRelease(releaseid, include_artist_info=True):
|
||||||
"""
|
"""
|
||||||
Deep release search to get track info
|
Deep release search to get track info
|
||||||
"""
|
"""
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
|
|
||||||
release = {}
|
release = {}
|
||||||
results = None
|
results = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if include_artist_info:
|
if include_artist_info:
|
||||||
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)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
release['title'] = unicode(results['title'])
|
release['title'] = unicode(results['title'])
|
||||||
release['id'] = unicode(results['id'])
|
release['id'] = unicode(results['id'])
|
||||||
release['asin'] = unicode(results['asin']) if 'asin' in results else None
|
release['asin'] = unicode(results['asin']) if 'asin' in results else None
|
||||||
release['date'] = unicode(results['date']) if 'date' in results else None
|
release['date'] = unicode(results['date']) if 'date' in results else None
|
||||||
try:
|
try:
|
||||||
release['format'] = unicode(results['medium-list'][0]['format'])
|
release['format'] = unicode(results['medium-list'][0]['format'])
|
||||||
except:
|
except:
|
||||||
release['format'] = u'Unknown'
|
release['format'] = u'Unknown'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
release['country'] = unicode(results['country'])
|
release['country'] = unicode(results['country'])
|
||||||
except:
|
except:
|
||||||
release['country'] = u'Unknown'
|
release['country'] = u'Unknown'
|
||||||
|
|
||||||
|
|
||||||
if include_artist_info:
|
if include_artist_info:
|
||||||
|
|
||||||
if 'release-group' in results:
|
if 'release-group' in results:
|
||||||
release['rgid'] = unicode(results['release-group']['id'])
|
release['rgid'] = unicode(results['release-group']['id'])
|
||||||
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")
|
||||||
|
|
||||||
@@ -329,7 +401,7 @@ def getRelease(releaseid, include_artist_info=True):
|
|||||||
release['artist_id'] = unicode(results['artist-credit'][0]['artist']['id'])
|
release['artist_id'] = unicode(results['artist-credit'][0]['artist']['id'])
|
||||||
|
|
||||||
release['tracks'] = getTracksFromRelease(results)
|
release['tracks'] = getTracksFromRelease(results)
|
||||||
|
|
||||||
return release
|
return release
|
||||||
|
|
||||||
def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
||||||
@@ -345,12 +417,12 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
break #may want to raise an exception here instead ?
|
break #may want to raise an exception here instead ?
|
||||||
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
|
||||||
|
|
||||||
if not results or len(results) == 0:
|
if not results or len(results) == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -382,7 +454,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
#all official releases should have the Official status included
|
#all official releases should have the Official status included
|
||||||
if not includeExtras and releasedata.get('status') != 'Official':
|
if not includeExtras and releasedata.get('status') != 'Official':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
release = {}
|
release = {}
|
||||||
rel_id_check = releasedata['id']
|
rel_id_check = releasedata['id']
|
||||||
artistid = unicode(releasedata['artist-credit'][0]['artist']['id'])
|
artistid = unicode(releasedata['artist-credit'][0]['artist']['id'])
|
||||||
@@ -395,12 +467,16 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
release['AlbumTitle'] = unicode(releasedata['title'])
|
release['AlbumTitle'] = unicode(releasedata['title'])
|
||||||
release['AlbumID'] = unicode(rgid)
|
release['AlbumID'] = unicode(rgid)
|
||||||
release['AlbumASIN'] = unicode(releasedata['asin']) if 'asin' in releasedata else None
|
release['AlbumASIN'] = unicode(releasedata['asin']) if 'asin' in releasedata else None
|
||||||
release['ReleaseDate'] = unicode(releasedata['date']) if 'date' in releasedata else None
|
release['ReleaseDate'] = unicode(releasedata['date']) if 'date' in releasedata else None
|
||||||
release['ReleaseID'] = releasedata['id']
|
release['ReleaseID'] = releasedata['id']
|
||||||
if 'release-group' not in releasedata:
|
if 'release-group' not in releasedata:
|
||||||
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:
|
||||||
@@ -409,7 +485,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
else:
|
else:
|
||||||
logger.warn('Release ' + releasedata['id'] + ' has no Artists associated.')
|
logger.warn('Release ' + releasedata['id'] + ' has no Artists associated.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
release['ReleaseCountry'] = unicode(releasedata['country']) if 'country' in releasedata else u'Unknown'
|
release['ReleaseCountry'] = unicode(releasedata['country']) if 'country' in releasedata else u'Unknown'
|
||||||
#assuming that the list will contain media and that the format will be consistent
|
#assuming that the list will contain media and that the format will be consistent
|
||||||
@@ -428,7 +504,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
release['ReleaseFormat'] = unicode(packaged_medium)
|
release['ReleaseFormat'] = unicode(packaged_medium)
|
||||||
except:
|
except:
|
||||||
release['ReleaseFormat'] = u'Unknown'
|
release['ReleaseFormat'] = u'Unknown'
|
||||||
|
|
||||||
release['Tracks'] = getTracksFromRelease(releasedata)
|
release['Tracks'] = getTracksFromRelease(releasedata)
|
||||||
|
|
||||||
# What we're doing here now is first updating the allalbums & alltracks table to the most
|
# What we're doing here now is first updating the allalbums & alltracks table to the most
|
||||||
@@ -448,11 +524,11 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
myDB.upsert("allalbums", newValueDict, controlValueDict)
|
myDB.upsert("allalbums", newValueDict, controlValueDict)
|
||||||
|
|
||||||
for track in release['Tracks']:
|
for track in release['Tracks']:
|
||||||
|
|
||||||
cleanname = helpers.cleanName(release['ArtistName'] + ' ' + release['AlbumTitle'] + ' ' + track['title'])
|
cleanname = helpers.cleanName(release['ArtistName'] + ' ' + release['AlbumTitle'] + ' ' + track['title'])
|
||||||
|
|
||||||
controlValueDict = {"TrackID": track['id'],
|
controlValueDict = {"TrackID": track['id'],
|
||||||
"ReleaseID": release['ReleaseID']}
|
"ReleaseID": release['ReleaseID']}
|
||||||
|
|
||||||
@@ -466,20 +542,20 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
"TrackNumber": track['number'],
|
"TrackNumber": track['number'],
|
||||||
"CleanName": cleanname
|
"CleanName": cleanname
|
||||||
}
|
}
|
||||||
|
|
||||||
match = myDB.action('SELECT Location, BitRate, Format from have WHERE CleanName=?', [cleanname]).fetchone()
|
match = myDB.action('SELECT Location, BitRate, Format from have WHERE CleanName=?', [cleanname]).fetchone()
|
||||||
|
|
||||||
if not match:
|
if not match:
|
||||||
match = myDB.action('SELECT Location, BitRate, Format from have WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [release['ArtistName'], release['AlbumTitle'], track['title']]).fetchone()
|
match = myDB.action('SELECT Location, BitRate, Format from have WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [release['ArtistName'], release['AlbumTitle'], track['title']]).fetchone()
|
||||||
#if not match:
|
#if not match:
|
||||||
#match = myDB.action('SELECT Location, BitRate, Format from have WHERE TrackID=?', [track['id']]).fetchone()
|
#match = myDB.action('SELECT Location, BitRate, Format from have WHERE TrackID=?', [track['id']]).fetchone()
|
||||||
if match:
|
if match:
|
||||||
newValueDict['Location'] = match['Location']
|
newValueDict['Location'] = match['Location']
|
||||||
newValueDict['BitRate'] = match['BitRate']
|
newValueDict['BitRate'] = match['BitRate']
|
||||||
newValueDict['Format'] = match['Format']
|
newValueDict['Format'] = match['Format']
|
||||||
#myDB.action('UPDATE have SET Matched="True" WHERE Location=?', [match['Location']])
|
#myDB.action('UPDATE have SET Matched="True" WHERE Location=?', [match['Location']])
|
||||||
myDB.action('UPDATE have SET Matched=? WHERE Location=?', (release['AlbumID'], match['Location']))
|
myDB.action('UPDATE have SET Matched=? WHERE Location=?', (release['AlbumID'], match['Location']))
|
||||||
|
|
||||||
myDB.upsert("alltracks", newValueDict, controlValueDict)
|
myDB.upsert("alltracks", newValueDict, controlValueDict)
|
||||||
num_new_releases = num_new_releases + 1
|
num_new_releases = num_new_releases + 1
|
||||||
#print releasedata['title']
|
#print releasedata['title']
|
||||||
@@ -512,19 +588,19 @@ def getTracksFromRelease(release):
|
|||||||
'url': u"http://musicbrainz.org/track/" + track['recording']['id'],
|
'url': u"http://musicbrainz.org/track/" + track['recording']['id'],
|
||||||
'duration': int(track['length']) if 'length' in track else 0
|
'duration': int(track['length']) if 'length' in track else 0
|
||||||
})
|
})
|
||||||
totalTracks += 1
|
totalTracks += 1
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
# Used when there is a disambiguation
|
# Used when there is a disambiguation
|
||||||
def findArtistbyAlbum(name):
|
def findArtistbyAlbum(name):
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
artist = myDB.action('SELECT AlbumTitle from have WHERE ArtistName=? AND AlbumTitle IS NOT NULL ORDER BY RANDOM()', [name]).fetchone()
|
artist = myDB.action('SELECT AlbumTitle from have WHERE ArtistName=? AND AlbumTitle IS NOT NULL ORDER BY RANDOM()', [name]).fetchone()
|
||||||
|
|
||||||
if not artist:
|
if not artist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Probably not neccessary but just want to double check
|
# Probably not neccessary but just want to double check
|
||||||
if not artist['AlbumTitle']:
|
if not artist['AlbumTitle']:
|
||||||
return False
|
return False
|
||||||
@@ -532,20 +608,20 @@ def findArtistbyAlbum(name):
|
|||||||
term = '"'+artist['AlbumTitle']+'" AND artist:"'+name+'"'
|
term = '"'+artist['AlbumTitle']+'" AND artist:"'+name+'"'
|
||||||
|
|
||||||
results = None
|
results = None
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
artist_dict = {}
|
artist_dict = {}
|
||||||
for releaseGroup in results:
|
for releaseGroup in results:
|
||||||
newArtist = releaseGroup['artist-credit'][0]['artist']
|
newArtist = releaseGroup['artist-credit'][0]['artist']
|
||||||
# Only need the artist ID if we're doing an artist+album lookup
|
# Only need the artist ID if we're doing an artist+album lookup
|
||||||
#if 'disambiguation' in newArtist:
|
#if 'disambiguation' in newArtist:
|
||||||
# uniquename = unicode(newArtist['sort-name'] + " (" + newArtist['disambiguation'] + ")")
|
# uniquename = unicode(newArtist['sort-name'] + " (" + newArtist['disambiguation'] + ")")
|
||||||
@@ -557,21 +633,30 @@ def findArtistbyAlbum(name):
|
|||||||
#artist_dict['url'] = u'http://musicbrainz.org/artist/' + newArtist['id']
|
#artist_dict['url'] = u'http://musicbrainz.org/artist/' + newArtist['id']
|
||||||
#artist_dict['score'] = int(releaseGroup['ext:score'])
|
#artist_dict['score'] = int(releaseGroup['ext:score'])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return artist_dict
|
return artist_dict
|
||||||
|
|
||||||
def findAlbumID(artist=None, album=None):
|
def findAlbumID(artist=None, album=None):
|
||||||
|
|
||||||
results = None
|
results = None
|
||||||
|
chars = set('!?*-')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if album and artist:
|
if album and artist:
|
||||||
term = '"'+album+'" AND artist:"'+artist+'"'
|
if any((c in chars) for c in album):
|
||||||
|
album = '"'+album+'"'
|
||||||
|
if any((c in chars) for c in artist):
|
||||||
|
artist = '"'+artist+'"'
|
||||||
|
criteria = {'release': album.lower()}
|
||||||
|
criteria['artist'] = artist.lower()
|
||||||
else:
|
else:
|
||||||
term = album
|
if any((c in chars) for c in album):
|
||||||
results = musicbrainzngs.search_release_groups(term,1).get('release-group-list')
|
album = '"'+album+'"'
|
||||||
except WebServiceError, e:
|
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)
|
||||||
|
|
||||||
@@ -579,6 +664,6 @@ def findAlbumID(artist=None, album=None):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if len(results) < 1:
|
if len(results) < 1:
|
||||||
return False
|
return False
|
||||||
rgid = unicode(results[0]['id'])
|
rgid = unicode(results[0]['id'])
|
||||||
return rgid
|
return rgid
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def encode(albumPath):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
os.mkdir(tempDirEncode)
|
os.mkdir(tempDirEncode)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.exception("Unable to create temporary directory")
|
logger.exception("Unable to create temporary directory")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -96,13 +96,18 @@ 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 = []
|
jobs = []
|
||||||
|
|
||||||
for music in musicFiles:
|
for music in musicFiles:
|
||||||
infoMusic=MediaFile(music)
|
infoMusic = MediaFile(music)
|
||||||
encode = False
|
encode = False
|
||||||
|
|
||||||
if XLD:
|
if XLD:
|
||||||
@@ -141,29 +146,32 @@ def encode(albumPath):
|
|||||||
|
|
||||||
# Encode music files
|
# Encode music files
|
||||||
if len(jobs) > 0:
|
if len(jobs) > 0:
|
||||||
|
processes = 1
|
||||||
|
|
||||||
|
# Use multicore if enabled
|
||||||
if headphones.ENCODER_MULTICORE:
|
if headphones.ENCODER_MULTICORE:
|
||||||
if headphones.ENCODER_MULTICORE_COUNT == 0:
|
if headphones.ENCODER_MULTICORE_COUNT == 0:
|
||||||
processes = multiprocessing.cpu_count()
|
processes = multiprocessing.cpu_count()
|
||||||
else:
|
else:
|
||||||
processes = headphones.ENCODER_MULTICORE_COUNT
|
processes = headphones.ENCODER_MULTICORE_COUNT
|
||||||
|
|
||||||
logger.debug("Multi-core encoding enabled, %d processes", processes)
|
logger.debug("Multi-core encoding enabled, spawning %d processes",
|
||||||
else:
|
processes)
|
||||||
processes = 1
|
|
||||||
|
|
||||||
# Use multiprocessing only if it's worth the overhead. and if it is
|
# Use multiprocessing only if it's worth the overhead. and if it is
|
||||||
# enabled. If not, then use the old fashioned way.
|
# enabled. If not, then use the old fashioned way.
|
||||||
if processes > 1:
|
if processes > 1:
|
||||||
pool = multiprocessing.Pool(processes=processes)
|
with logger.listener():
|
||||||
results = pool.map_async(command_map, jobs)
|
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
|
# No new processes will be created, so close it and wait for all
|
||||||
# processes to finish
|
# processes to finish
|
||||||
pool.close()
|
pool.close()
|
||||||
pool.join()
|
pool.join()
|
||||||
|
|
||||||
# Retrieve the results
|
# Retrieve the results
|
||||||
results = results.get()
|
results = results.get()
|
||||||
else:
|
else:
|
||||||
results = map(command_map, jobs)
|
results = map(command_map, jobs)
|
||||||
|
|
||||||
@@ -178,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)
|
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:
|
||||||
@@ -204,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)
|
||||||
@@ -220,19 +228,31 @@ def encode(albumPath):
|
|||||||
|
|
||||||
def command_map(args):
|
def command_map(args):
|
||||||
"""
|
"""
|
||||||
This method is used for the multiprocessing.map() method as a wrapper.
|
Wrapper for the '[multiprocessing.]map()' method, to unpack the arguments
|
||||||
|
and wrap exceptions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Initialize multiprocessing logger
|
||||||
|
if multiprocessing.current_process().name != "MainProcess":
|
||||||
|
logger.initMultiprocessing()
|
||||||
|
|
||||||
|
# Start encoding
|
||||||
try:
|
try:
|
||||||
return command(*args)
|
return command(*args)
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.exception("Encoder exception, will return failed")
|
logger.exception("Encoder raised an exception.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def command(encoder, musicSource ,musicDest, albumPath):
|
def command(encoder, musicSource, musicDest, albumPath):
|
||||||
cmd=[]
|
"""
|
||||||
startMusicTime=time.time()
|
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]
|
||||||
@@ -242,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)])
|
||||||
@@ -259,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':
|
||||||
@@ -279,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:
|
||||||
@@ -293,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')
|
||||||
@@ -319,5 +363,4 @@ def getTimeEncode(start):
|
|||||||
seconds -= 3600*hours
|
seconds -= 3600*hours
|
||||||
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,32 +13,36 @@
|
|||||||
# 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, request
|
||||||
|
|
||||||
|
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
|
||||||
import simplejson
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
import gntp.notifier
|
import gntp.notifier
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
from xml.dom import minidom
|
import oauth2 as oauth
|
||||||
from httplib import HTTPSConnection
|
import pythontwitter as twitter
|
||||||
from urllib import urlencode
|
|
||||||
|
|
||||||
import lib.oauth2 as oauth
|
|
||||||
import lib.pythontwitter as twitter
|
|
||||||
|
|
||||||
from headphones import logger, helpers, common, request
|
|
||||||
from headphones.exceptions import ex
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urlparse import parse_qsl
|
from urlparse import parse_qsl
|
||||||
except:
|
except ImportError:
|
||||||
from cgi import parse_qsl
|
from cgi import parse_qsl
|
||||||
|
|
||||||
class GROWL:
|
class GROWL(object):
|
||||||
|
"""
|
||||||
|
Growl notifications, for OS X
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.enabled = headphones.GROWL_ENABLED
|
self.enabled = headphones.GROWL_ENABLED
|
||||||
@@ -88,7 +92,9 @@ class GROWL:
|
|||||||
|
|
||||||
# Send it, including an image
|
# Send it, including an image
|
||||||
image_file = os.path.join(str(headphones.PROG_DIR), 'data/images/headphoneslogo.png')
|
image_file = os.path.join(str(headphones.PROG_DIR), 'data/images/headphoneslogo.png')
|
||||||
image = open(image_file, 'rb').read()
|
|
||||||
|
with open(image_file, 'rb') as f:
|
||||||
|
image = f.read()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
growl.notify(
|
growl.notify(
|
||||||
@@ -114,15 +120,15 @@ class GROWL:
|
|||||||
|
|
||||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||||
|
|
||||||
class PROWL:
|
class PROWL(object):
|
||||||
|
"""
|
||||||
keys = []
|
Prowl notifications.
|
||||||
priority = []
|
"""
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
def conf(self, options):
|
def conf(self, options):
|
||||||
return cherrypy.config['config'].get('Prowl', options)
|
return cherrypy.config['config'].get('Prowl', options)
|
||||||
@@ -132,7 +138,7 @@ class PROWL:
|
|||||||
return
|
return
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.prowlapp.com")
|
http_handler = HTTPSConnection("api.prowlapp.com")
|
||||||
|
|
||||||
data = {'apikey': headphones.PROWL_KEYS,
|
data = {'apikey': headphones.PROWL_KEYS,
|
||||||
'application': 'Headphones',
|
'application': 'Headphones',
|
||||||
'event': event,
|
'event': event,
|
||||||
@@ -149,7 +155,7 @@ class PROWL:
|
|||||||
if request_status == 200:
|
if request_status == 200:
|
||||||
logger.info(u"Prowl notifications sent.")
|
logger.info(u"Prowl notifications sent.")
|
||||||
return True
|
return True
|
||||||
elif request_status == 401:
|
elif request_status == 401:
|
||||||
logger.info(u"Prowl auth failed: %s" % response.reason)
|
logger.info(u"Prowl auth failed: %s" % response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@@ -161,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):
|
||||||
|
|
||||||
@@ -191,9 +212,9 @@ class XBMC:
|
|||||||
url = host + '/jsonrpc'
|
url = host + '/jsonrpc'
|
||||||
|
|
||||||
if self.password:
|
if self.password:
|
||||||
response = request.request_json(req, method="POST", data=simplejson.dumps(data), headers=headers, auth=(self.username, self.password))
|
response = request.request_json(url, method="post", data=json.dumps(data), headers=headers, auth=(self.username, self.password))
|
||||||
else:
|
else:
|
||||||
response = request.request_json(req, method="POST", data=simplejson.dumps(data), headers=headers)
|
response = request.request_json(url, method="post", data=json.dumps(data), headers=headers)
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
return response[0]['result']
|
return response[0]['result']
|
||||||
@@ -207,10 +228,10 @@ class XBMC:
|
|||||||
for host in hosts:
|
for host in hosts:
|
||||||
logger.info('Sending library update command to XBMC @ '+host)
|
logger.info('Sending library update command to XBMC @ '+host)
|
||||||
request = self._sendjson(host, 'AudioLibrary.Scan')
|
request = self._sendjson(host, 'AudioLibrary.Scan')
|
||||||
|
|
||||||
if not request:
|
if not request:
|
||||||
logger.warn('Error sending update request to XBMC')
|
logger.warn('Error sending update request to XBMC')
|
||||||
|
|
||||||
def notify(self, artist, album, albumartpath):
|
def notify(self, artist, album, albumartpath):
|
||||||
|
|
||||||
hosts = [x.strip() for x in self.hosts.split(',')]
|
hosts = [x.strip() for x in self.hosts.split(',')]
|
||||||
@@ -236,21 +257,22 @@ 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 LMS:
|
|
||||||
|
|
||||||
#Class for updating a Logitech Media Server
|
class LMS(object):
|
||||||
|
"""
|
||||||
|
Class for updating a Logitech Media Server
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.hosts = headphones.LMS_HOST
|
self.hosts = headphones.LMS_HOST
|
||||||
|
|
||||||
def _sendjson(self, host):
|
|
||||||
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]} #Had a lot of trouble with simplejson, but this works.
|
|
||||||
data = simplejson.JSONEncoder().encode(data)
|
|
||||||
|
|
||||||
content = {'Content-Type': 'application/json', 'Content-Length': len(data)}
|
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)
|
req = urllib2.Request(host+'/jsonrpc.js', data, content)
|
||||||
|
|
||||||
@@ -260,34 +282,28 @@ class LMS:
|
|||||||
logger.warn('Error opening LMS url: %s' % e)
|
logger.warn('Error opening LMS url: %s' % e)
|
||||||
return
|
return
|
||||||
|
|
||||||
response = simplejson.JSONDecoder().decode(handle.read())
|
response = json.JSONDecoder().decode(handle.read())
|
||||||
server_result = simplejson.dumps(response)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return response[0]['result']
|
return response['result']
|
||||||
except:
|
except:
|
||||||
logger.warn('LMS returned error: %s' % response[0]['error'])
|
logger.warn('LMS returned error: %s' % response['error'])
|
||||||
return
|
return
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
|
||||||
#Send the ["rescan"] command to an LMS server.
|
|
||||||
#Note that the command must be prefixed with the 'player' that the command is aimed at,
|
|
||||||
#But with this being a request for the server to update its library, the player is blank, so ""
|
|
||||||
|
|
||||||
hosts = [x.strip() for x in self.hosts.split(',')]
|
hosts = [x.strip() for x in self.hosts.split(',')]
|
||||||
|
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
logger.info('Sending library rescan command to LMS @ '+host)
|
logger.info('Sending library rescan command to LMS @ '+host)
|
||||||
request = self._sendjson(host)
|
request = self._sendjson(host)
|
||||||
|
|
||||||
if not request:
|
if not request:
|
||||||
logger.warn('Error sending rescan request to LMS')
|
logger.warn('Error sending rescan request to LMS')
|
||||||
|
|
||||||
class Plex:
|
class Plex(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.server_hosts = headphones.PLEX_SERVER_HOST
|
self.server_hosts = headphones.PLEX_SERVER_HOST
|
||||||
self.client_hosts = headphones.PLEX_CLIENT_HOST
|
self.client_hosts = headphones.PLEX_CLIENT_HOST
|
||||||
self.username = headphones.PLEX_USERNAME
|
self.username = headphones.PLEX_USERNAME
|
||||||
@@ -297,31 +313,31 @@ class Plex:
|
|||||||
|
|
||||||
username = self.username
|
username = self.username
|
||||||
password = self.password
|
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)
|
req = urllib2.Request(url)
|
||||||
|
|
||||||
if password:
|
if password:
|
||||||
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
|
base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
|
||||||
req.add_header("Authorization", "Basic %s" % base64string)
|
req.add_header("Authorization", "Basic %s" % base64string)
|
||||||
|
|
||||||
logger.info('Plex url: %s' % url)
|
logger.info('Plex url: %s' % url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handle = urllib2.urlopen(req)
|
handle = urllib2.urlopen(req)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn('Error opening Plex url: %s' % e)
|
logger.warn('Error opening Plex url: %s' % e)
|
||||||
return
|
return
|
||||||
|
|
||||||
response = handle.read().decode(headphones.SYS_ENCODING)
|
response = handle.read().decode(headphones.SYS_ENCODING)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -349,7 +365,7 @@ class Plex:
|
|||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.warn("Error updating library section for Plex Media Server: %s" % e)
|
logger.warn("Error updating library section for Plex Media Server: %s" % e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def notify(self, artist, album, albumartpath):
|
def notify(self, artist, album, albumartpath):
|
||||||
|
|
||||||
hosts = [x.strip() for x in self.client_hosts.split(',')]
|
hosts = [x.strip() for x in self.client_hosts.split(',')]
|
||||||
@@ -361,47 +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)
|
||||||
self.apikey = headphones.NMA_APIKEY
|
logger.debug(u"NMA Priority: " + str(nma_priority))
|
||||||
self.priority = headphones.NMA_PRIORITY
|
|
||||||
|
if snatched:
|
||||||
def _send(self, data):
|
event = snatched + " snatched!"
|
||||||
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
|
message = "Headphones has snatched: " + snatched
|
||||||
|
|
||||||
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.info('Sending notification request to NotifyMyAndroid')
|
logger.debug(u"NMA event: " + event)
|
||||||
request = self._send(data)
|
logger.debug(u"NMA message: " + message)
|
||||||
|
|
||||||
if not request:
|
|
||||||
logger.warn('Error sending notification request to NotifyMyAndroid')
|
|
||||||
|
|
||||||
class PUSHBULLET:
|
batch = False
|
||||||
|
|
||||||
|
p = pynma.PyNMA()
|
||||||
|
keys = api.split(',')
|
||||||
|
p.addkey(keys)
|
||||||
|
|
||||||
|
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
|
||||||
@@ -415,7 +437,7 @@ class PUSHBULLET:
|
|||||||
return
|
return
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.pushbullet.com")
|
http_handler = HTTPSConnection("api.pushbullet.com")
|
||||||
|
|
||||||
data = {'device_iden': headphones.PUSHBULLET_DEVICEID,
|
data = {'device_iden': headphones.PUSHBULLET_DEVICEID,
|
||||||
'type': "note",
|
'type': "note",
|
||||||
'title': "Headphones",
|
'title': "Headphones",
|
||||||
@@ -435,7 +457,7 @@ class PUSHBULLET:
|
|||||||
if request_status == 200:
|
if request_status == 200:
|
||||||
logger.info(u"PushBullet notifications sent.")
|
logger.info(u"PushBullet notifications sent.")
|
||||||
return True
|
return True
|
||||||
elif request_status >= 400 and request_status < 500:
|
elif request_status >= 400 and request_status < 500:
|
||||||
logger.info(u"PushBullet request failed: %s" % response.reason)
|
logger.info(u"PushBullet request failed: %s" % response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@@ -454,22 +476,21 @@ 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")
|
||||||
|
|
||||||
data = {'AuthorizationToken': pushalot_authorizationtoken,
|
data = {'AuthorizationToken': pushalot_authorizationtoken,
|
||||||
'Title': event.encode('utf-8'),
|
'Title': event.encode('utf-8'),
|
||||||
'Body': message.encode("utf-8") }
|
'Body': message.encode("utf-8") }
|
||||||
|
|
||||||
@@ -487,14 +508,14 @@ class PUSHALOT:
|
|||||||
if request_status == 200:
|
if request_status == 200:
|
||||||
logger.info(u"Pushalot notifications sent.")
|
logger.info(u"Pushalot notifications sent.")
|
||||||
return True
|
return True
|
||||||
elif request_status == 410:
|
elif request_status == 410:
|
||||||
logger.info(u"Pushalot auth failed: %s" % response.reason)
|
logger.info(u"Pushalot auth failed: %s" % response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
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
|
||||||
|
|
||||||
@@ -529,19 +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
|
||||||
|
|
||||||
if headphones.PUSHOVER_APITOKEN:
|
if headphones.PUSHOVER_APITOKEN:
|
||||||
self.application_token = headphones.PUSHOVER_APITOKEN
|
self.application_token = headphones.PUSHOVER_APITOKEN
|
||||||
pass
|
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)
|
||||||
@@ -551,8 +571,8 @@ class PUSHOVER:
|
|||||||
return
|
return
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.pushover.net")
|
http_handler = HTTPSConnection("api.pushover.net")
|
||||||
|
|
||||||
data = {'token': self.application_token,
|
data = {'token': self.application_token,
|
||||||
'user': headphones.PUSHOVER_KEYS,
|
'user': headphones.PUSHOVER_KEYS,
|
||||||
'title': event,
|
'title': event,
|
||||||
'message': message.encode("utf-8"),
|
'message': message.encode("utf-8"),
|
||||||
@@ -571,7 +591,7 @@ class PUSHOVER:
|
|||||||
if request_status == 200:
|
if request_status == 200:
|
||||||
logger.info(u"Pushover notifications sent.")
|
logger.info(u"Pushover notifications sent.")
|
||||||
return True
|
return True
|
||||||
elif request_status >= 400 and request_status < 500:
|
elif request_status >= 400 and request_status < 500:
|
||||||
logger.info(u"Pushover request failed: %s" % response.reason)
|
logger.info(u"Pushover request failed: %s" % response.reason)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@@ -583,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:
|
|
||||||
|
|
||||||
consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
class TwitterNotifier(object):
|
||||||
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())
|
||||||
@@ -612,37 +632,37 @@ class TwitterNotifier:
|
|||||||
return self._notifyTwitter("This is a test notification from Headphones at "+helpers.now(), force=True)
|
return self._notifyTwitter("This is a test notification from Headphones at "+helpers.now(), force=True)
|
||||||
|
|
||||||
def _get_authorization(self):
|
def _get_authorization(self):
|
||||||
|
|
||||||
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
|
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
|
||||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||||
oauth_client = oauth.Client(oauth_consumer)
|
oauth_client = oauth.Client(oauth_consumer)
|
||||||
|
|
||||||
logger.info('Requesting temp token from Twitter')
|
logger.info('Requesting temp token from Twitter')
|
||||||
|
|
||||||
resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET')
|
resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET')
|
||||||
|
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
logger.info('Invalid respond from Twitter requesting temp token: %s' % resp['status'])
|
logger.info('Invalid respond from Twitter requesting temp token: %s' % resp['status'])
|
||||||
else:
|
else:
|
||||||
request_token = dict(parse_qsl(content))
|
request_token = dict(parse_qsl(content))
|
||||||
|
|
||||||
headphones.TWITTER_USERNAME = request_token['oauth_token']
|
headphones.TWITTER_USERNAME = request_token['oauth_token']
|
||||||
headphones.TWITTER_PASSWORD = request_token['oauth_token_secret']
|
headphones.TWITTER_PASSWORD = request_token['oauth_token_secret']
|
||||||
|
|
||||||
return self.AUTHORIZATION_URL+"?oauth_token="+ request_token['oauth_token']
|
return self.AUTHORIZATION_URL+"?oauth_token="+ request_token['oauth_token']
|
||||||
|
|
||||||
def _get_credentials(self, key):
|
def _get_credentials(self, key):
|
||||||
request_token = {}
|
request_token = {}
|
||||||
|
|
||||||
request_token['oauth_token'] = headphones.TWITTER_USERNAME
|
request_token['oauth_token'] = headphones.TWITTER_USERNAME
|
||||||
request_token['oauth_token_secret'] = headphones.TWITTER_PASSWORD
|
request_token['oauth_token_secret'] = headphones.TWITTER_PASSWORD
|
||||||
request_token['oauth_callback_confirmed'] = 'true'
|
request_token['oauth_callback_confirmed'] = 'true'
|
||||||
|
|
||||||
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
|
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
|
||||||
token.set_verifier(key)
|
token.set_verifier(key)
|
||||||
|
|
||||||
logger.info('Generating and signing request for an access token using key '+key)
|
logger.info('Generating and signing request for an access token using key '+key)
|
||||||
|
|
||||||
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
|
signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable
|
||||||
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
|
||||||
logger.info('oauth_consumer: '+str(oauth_consumer))
|
logger.info('oauth_consumer: '+str(oauth_consumer))
|
||||||
@@ -650,10 +670,10 @@ class TwitterNotifier:
|
|||||||
logger.info('oauth_client: '+str(oauth_client))
|
logger.info('oauth_client: '+str(oauth_client))
|
||||||
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
|
resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key)
|
||||||
logger.info('resp, content: '+str(resp)+','+str(content))
|
logger.info('resp, content: '+str(resp)+','+str(content))
|
||||||
|
|
||||||
access_token = dict(parse_qsl(content))
|
access_token = dict(parse_qsl(content))
|
||||||
logger.info('access_token: '+str(access_token))
|
logger.info('access_token: '+str(access_token))
|
||||||
|
|
||||||
logger.info('resp[status] = '+str(resp['status']))
|
logger.info('resp[status] = '+str(resp['status']))
|
||||||
if resp['status'] != '200':
|
if resp['status'] != '200':
|
||||||
logger.info('The request for a token with did not succeed: '+str(resp['status']), logger.ERROR)
|
logger.info('The request for a token with did not succeed: '+str(resp['status']), logger.ERROR)
|
||||||
@@ -664,33 +684,131 @@ class TwitterNotifier:
|
|||||||
headphones.TWITTER_USERNAME = access_token['oauth_token']
|
headphones.TWITTER_USERNAME = access_token['oauth_token']
|
||||||
headphones.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
headphones.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _send_tweet(self, message=None):
|
def _send_tweet(self, message=None):
|
||||||
|
|
||||||
username=self.consumer_key
|
username=self.consumer_key
|
||||||
password=self.consumer_secret
|
password=self.consumer_secret
|
||||||
access_token_key=headphones.TWITTER_USERNAME
|
access_token_key=headphones.TWITTER_USERNAME
|
||||||
access_token_secret=headphones.TWITTER_PASSWORD
|
access_token_secret=headphones.TWITTER_PASSWORD
|
||||||
|
|
||||||
logger.info(u"Sending tweet: "+message)
|
logger.info(u"Sending tweet: "+message)
|
||||||
|
|
||||||
api = twitter.Api(username, password, access_token_key, access_token_secret)
|
api = twitter.Api(username, password, access_token_key, access_token_secret)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api.PostUpdate(message)
|
api.PostUpdate(message)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.info(u"Error Sending Tweet: %s" % e)
|
logger.info(u"Error Sending Tweet: %s" % e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _notifyTwitter(self, message='', force=False):
|
def _notifyTwitter(self, message='', force=False):
|
||||||
prefix = headphones.TWITTER_PREFIX
|
prefix = headphones.TWITTER_PREFIX
|
||||||
|
|
||||||
if not headphones.TWITTER_ENABLED and not force:
|
if not headphones.TWITTER_ENABLED and not force:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
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,12 +35,20 @@ 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)
|
||||||
@@ -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.info(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
|
||||||
|
|||||||
@@ -1,3 +1,18 @@
|
|||||||
|
# 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 headphones import logger
|
||||||
|
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
@@ -5,8 +20,10 @@ from bs4 import BeautifulSoup
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import feedparser
|
import feedparser
|
||||||
|
import headphones
|
||||||
|
|
||||||
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs):
|
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
|
Convenient wrapper for `requests.get', which will capture the exceptions and
|
||||||
log them. On success, the Response object is returned. In case of a
|
log them. On success, the Response object is returned. In case of a
|
||||||
@@ -17,9 +34,13 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
|||||||
if whitelist_status_code and type(whitelist_status_code) != list:
|
if whitelist_status_code and type(whitelist_status_code) != list:
|
||||||
whitelist_status_code = [whitelist_status_code]
|
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
|
# 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.
|
# requests to apply more magic per method. See lib/requests/api.py.
|
||||||
request_method = getattr(requests, method)
|
request_method = getattr(requests, method.lower())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Request the URL
|
# Request the URL
|
||||||
@@ -30,22 +51,41 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
|||||||
# is white listed.
|
# is white listed.
|
||||||
if whitelist_status_code and auto_raise:
|
if whitelist_status_code and auto_raise:
|
||||||
if response.status_code not in whitelist_status_code:
|
if response.status_code not in whitelist_status_code:
|
||||||
logger.debug("Response status code %d is not white listed, raising exception", response.status_code)
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
except:
|
||||||
|
logger.debug("Response status code %d is not white " \
|
||||||
|
"listed, raised exception", response.status_code)
|
||||||
|
raise
|
||||||
elif auto_raise:
|
elif auto_raise:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
except requests.ConnectionError:
|
except requests.ConnectionError:
|
||||||
logger.error("Unable to connect to remote host.")
|
logger.error("Unable to connect to remote host. Check if the remote " \
|
||||||
|
"host is up and running.")
|
||||||
except requests.Timeout:
|
except requests.Timeout:
|
||||||
logger.error("Request timed out.")
|
logger.error("Request timed out. The remote host did not respeond " \
|
||||||
except requests.HTTPError, e:
|
"timely.")
|
||||||
|
except requests.HTTPError as e:
|
||||||
if e.response is not None:
|
if e.response is not None:
|
||||||
logger.error("Request raise HTTP error with status code: %d", e.response.status_code)
|
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:
|
else:
|
||||||
logger.error("Request raised HTTP error.")
|
logger.error("Request raised HTTP error.")
|
||||||
except requests.RequestException, e:
|
except requests.RequestException as e:
|
||||||
logger.error("Request raised exception: %s", e)
|
logger.error("Request raised exception: %s", e)
|
||||||
|
|
||||||
def request_soup(url, **kwargs):
|
def request_soup(url, **kwargs):
|
||||||
@@ -88,12 +128,16 @@ def request_json(url, **kwargs):
|
|||||||
result = response.json()
|
result = response.json()
|
||||||
|
|
||||||
if validator and not validator(result):
|
if validator and not validator(result):
|
||||||
logger.error("JSON validation result vailed")
|
logger.error("JSON validation result failed")
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.error("Response returned invalid JSON data")
|
logger.error("Response returned invalid JSON data")
|
||||||
|
|
||||||
|
# Debug response
|
||||||
|
if headphones.VERBOSE:
|
||||||
|
server_message(response)
|
||||||
|
|
||||||
def request_content(url, **kwargs):
|
def request_content(url, **kwargs):
|
||||||
"""
|
"""
|
||||||
Wrapper for `request_response', which will return the raw content.
|
Wrapper for `request_response', which will return the raw content.
|
||||||
@@ -112,4 +156,46 @@ def request_feed(url, **kwargs):
|
|||||||
response = request_response(url, **kwargs)
|
response = request_response(url, **kwargs)
|
||||||
|
|
||||||
if response is not None:
|
if response is not None:
|
||||||
return feedparser.parse(response.content)
|
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)
|
||||||
@@ -1,191 +1,162 @@
|
|||||||
# This file is part of Headphones.
|
# This file is part of Headphones.
|
||||||
#
|
#
|
||||||
# Headphones is free software: you can redistribute it and/or modify
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# Headphones is distributed in the hope that it will be useful,
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# 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/>.
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
## Stolen from Sick-Beard's sab.py ##
|
## Stolen from Sick-Beard's sab.py ##
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
import urllib, httplib
|
import MultipartPostHandler
|
||||||
import datetime
|
import headphones
|
||||||
|
import datetime
|
||||||
import headphones
|
import cookielib
|
||||||
|
import urllib2
|
||||||
from lib import MultipartPostHandler
|
import httplib
|
||||||
import urllib2, cookielib
|
import urllib
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
from headphones.common import USER_AGENT
|
from headphones.common import USER_AGENT
|
||||||
from headphones import logger
|
from headphones import logger
|
||||||
from headphones import notifiers, helpers
|
from headphones import notifiers, helpers
|
||||||
|
|
||||||
def sendNZB(nzb):
|
def sendNZB(nzb):
|
||||||
|
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
if headphones.SAB_USERNAME:
|
if headphones.SAB_USERNAME:
|
||||||
params['ma_username'] = headphones.SAB_USERNAME
|
params['ma_username'] = headphones.SAB_USERNAME
|
||||||
if headphones.SAB_PASSWORD:
|
if headphones.SAB_PASSWORD:
|
||||||
params['ma_password'] = headphones.SAB_PASSWORD
|
params['ma_password'] = headphones.SAB_PASSWORD
|
||||||
if headphones.SAB_APIKEY:
|
if headphones.SAB_APIKEY:
|
||||||
params['apikey'] = headphones.SAB_APIKEY
|
params['apikey'] = headphones.SAB_APIKEY
|
||||||
if headphones.SAB_CATEGORY:
|
if headphones.SAB_CATEGORY:
|
||||||
params['cat'] = headphones.SAB_CATEGORY
|
params['cat'] = headphones.SAB_CATEGORY
|
||||||
|
|
||||||
# if it's a normal result we just pass SAB the URL
|
# if it's a normal result we just pass SAB the URL
|
||||||
if nzb.resultType == "nzb":
|
if nzb.resultType == "nzb":
|
||||||
# for newzbin results send the ID to sab specifically
|
# for newzbin results send the ID to sab specifically
|
||||||
if nzb.provider.getID() == 'newzbin':
|
if nzb.provider.getID() == 'newzbin':
|
||||||
id = nzb.provider.getIDFromURL(nzb.url)
|
id = nzb.provider.getIDFromURL(nzb.url)
|
||||||
if not id:
|
if not id:
|
||||||
logger.info("Unable to send NZB to sab, can't find ID in URL "+str(nzb.url))
|
logger.info("Unable to send NZB to sab, can't find ID in URL "+str(nzb.url))
|
||||||
return False
|
return False
|
||||||
params['mode'] = 'addid'
|
params['mode'] = 'addid'
|
||||||
params['name'] = id
|
params['name'] = id
|
||||||
else:
|
else:
|
||||||
params['mode'] = 'addurl'
|
params['mode'] = 'addurl'
|
||||||
params['name'] = nzb.url
|
params['name'] = nzb.url
|
||||||
|
|
||||||
# if we get a raw data result we want to upload it to SAB
|
# if we get a raw data result we want to upload it to SAB
|
||||||
elif nzb.resultType == "nzbdata":
|
elif nzb.resultType == "nzbdata":
|
||||||
# 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
|
||||||
|
|
||||||
if headphones.SAB_HOST.endswith('/'):
|
if headphones.SAB_HOST.endswith('/'):
|
||||||
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
||||||
|
|
||||||
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if nzb.resultType == "nzb":
|
if nzb.resultType == "nzb":
|
||||||
f = urllib.urlopen(url)
|
f = urllib.urlopen(url)
|
||||||
elif nzb.resultType == "nzbdata":
|
elif nzb.resultType == "nzbdata":
|
||||||
cookies = cookielib.CookieJar()
|
cookies = cookielib.CookieJar()
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
|
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
|
||||||
MultipartPostHandler.MultipartPostHandler)
|
MultipartPostHandler.MultipartPostHandler)
|
||||||
|
|
||||||
req = urllib2.Request(url,
|
req = urllib2.Request(url,
|
||||||
multiPartParams,
|
multiPartParams,
|
||||||
headers={'User-Agent': USER_AGENT})
|
headers={'User-Agent': USER_AGENT})
|
||||||
|
|
||||||
f = opener.open(req)
|
f = opener.open(req)
|
||||||
|
|
||||||
except (EOFError, IOError), e:
|
except (EOFError, IOError), e:
|
||||||
logger.error(u"Unable to connect to SAB with URL: %s" % url)
|
logger.error(u"Unable to connect to SAB with URL: %s" % url)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except httplib.InvalidURL, e:
|
except httplib.InvalidURL, e:
|
||||||
logger.error(u"Invalid SAB host, check your config. Current host: %s" % headphones.SAB_HOST)
|
logger.error(u"Invalid SAB host, check your config. Current host: %s" % headphones.SAB_HOST)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error(u"Error: " + str(e))
|
logger.error(u"Error: " + str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if f == None:
|
if f == None:
|
||||||
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = f.readlines()
|
result = f.readlines()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.info(u"Error trying to get result from SAB, NZB not sent: ")
|
logger.info(u"Error trying to get result from SAB, NZB not sent: ")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
logger.info(u"No data returned from SABnzbd, NZB not sent")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
sabText = result[0].strip()
|
sabText = result[0].strip()
|
||||||
|
|
||||||
logger.info(u"Result text from SAB: " + sabText)
|
logger.info(u"Result text from SAB: " + sabText)
|
||||||
|
|
||||||
if sabText == "ok":
|
if sabText == "ok":
|
||||||
logger.info(u"NZB sent to SAB successfully")
|
logger.info(u"NZB sent to SAB successfully")
|
||||||
if headphones.GROWL_ENABLED and headphones.GROWL_ONSNATCH:
|
return True
|
||||||
logger.info(u"Sending Growl notification")
|
elif sabText == "Missing authentication":
|
||||||
growl = notifiers.GROWL()
|
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
||||||
growl.notify(nzb.name,"Download started")
|
return False
|
||||||
if headphones.PROWL_ENABLED and headphones.PROWL_ONSNATCH:
|
else:
|
||||||
logger.info(u"Sending Prowl notification")
|
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
|
||||||
prowl = notifiers.PROWL()
|
return False
|
||||||
prowl.notify(nzb.name,"Download started")
|
|
||||||
if headphones.PUSHOVER_ENABLED and headphones.PUSHOVER_ONSNATCH:
|
def checkConfig():
|
||||||
logger.info(u"Sending Pushover notification")
|
|
||||||
prowl = notifiers.PUSHOVER()
|
params = { 'mode' : 'get_config',
|
||||||
prowl.notify(nzb.name,"Download started")
|
'section' : 'misc'
|
||||||
if headphones.PUSHBULLET_ENABLED and headphones.PUSHBULLET_ONSNATCH:
|
}
|
||||||
logger.info(u"Sending PushBullet notification")
|
|
||||||
pushbullet = notifiers.PUSHBULLET()
|
if headphones.SAB_USERNAME:
|
||||||
pushbullet.notify(nzb.name + " has been snatched!", "Download started")
|
params['ma_username'] = headphones.SAB_USERNAME
|
||||||
if headphones.TWITTER_ENABLED and headphones.TWITTER_ONSNATCH:
|
if headphones.SAB_PASSWORD:
|
||||||
logger.info(u"Sending Twitter notification")
|
params['ma_password'] = headphones.SAB_PASSWORD
|
||||||
twitter = notifiers.TwitterNotifier()
|
if headphones.SAB_APIKEY:
|
||||||
twitter.notify_snatch(nzb.name)
|
params['apikey'] = headphones.SAB_APIKEY
|
||||||
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
|
|
||||||
logger.debug(u"Sending NMA notification")
|
if not headphones.SAB_HOST.startswith('http'):
|
||||||
nma = notifiers.NMA()
|
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||||
nma.notify(snatched_nzb=nzb.name)
|
|
||||||
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
|
if headphones.SAB_HOST.endswith('/'):
|
||||||
logger.info(u"Sending Pushalot notification")
|
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
||||||
pushalot = notifiers.PUSHALOT()
|
|
||||||
pushalot.notify(nzb.name,"Download started")
|
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
||||||
|
|
||||||
return True
|
try:
|
||||||
elif sabText == "Missing authentication":
|
f = urllib.urlopen(url).read()
|
||||||
logger.info(u"Incorrect username/password sent to SAB, NZB not sent")
|
except Exception, e:
|
||||||
return False
|
logger.warn("Unable to read SABnzbd config file - cannot determine renaming options (might affect auto & forced post processing)")
|
||||||
else:
|
return (0, 0)
|
||||||
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
|
|
||||||
return False
|
config_options = ast.literal_eval(f)
|
||||||
|
|
||||||
def checkConfig():
|
replace_spaces = config_options['misc']['replace_spaces']
|
||||||
|
replace_dots = config_options['misc']['replace_dots']
|
||||||
params = { 'mode' : 'get_config',
|
|
||||||
'section' : 'misc'
|
return (replace_spaces, replace_dots)
|
||||||
}
|
|
||||||
|
|
||||||
if headphones.SAB_USERNAME:
|
|
||||||
params['ma_username'] = headphones.SAB_USERNAME
|
|
||||||
if headphones.SAB_PASSWORD:
|
|
||||||
params['ma_password'] = headphones.SAB_PASSWORD
|
|
||||||
if headphones.SAB_APIKEY:
|
|
||||||
params['apikey'] = headphones.SAB_APIKEY
|
|
||||||
|
|
||||||
if not headphones.SAB_HOST.startswith('http'):
|
|
||||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
|
||||||
|
|
||||||
if headphones.SAB_HOST.endswith('/'):
|
|
||||||
headphones.SAB_HOST = headphones.SAB_HOST[0:len(headphones.SAB_HOST)-1]
|
|
||||||
|
|
||||||
url = headphones.SAB_HOST + "/" + "api?" + urllib.urlencode(params)
|
|
||||||
|
|
||||||
try:
|
|
||||||
f = urllib.urlopen(url).read()
|
|
||||||
except Exception, e:
|
|
||||||
logger.warn("Unable to read SABnzbd config file - cannot determine renaming options (might affect auto & forced post processing)")
|
|
||||||
return (0, 0)
|
|
||||||
|
|
||||||
config_options = ast.literal_eval(f)
|
|
||||||
|
|
||||||
replace_spaces = config_options['misc']['replace_spaces']
|
|
||||||
replace_dots = config_options['misc']['replace_dots']
|
|
||||||
|
|
||||||
return (replace_spaces, replace_dots)
|
|
||||||
|
|||||||
@@ -4,23 +4,29 @@
|
|||||||
# 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
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.cookiejar = cookielib.CookieJar()
|
self.cookiejar = cookielib.CookieJar()
|
||||||
@@ -29,42 +35,40 @@ class Rutracker():
|
|||||||
|
|
||||||
def login(self, login, password):
|
def login(self, login, password):
|
||||||
"""Implements tracker login procedure."""
|
"""Implements tracker login procedure."""
|
||||||
|
|
||||||
self.logged_in = False
|
self.logged_in = False
|
||||||
|
|
||||||
if login is None or password is None:
|
if login is None or password is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
#self.login_counter += 1
|
#self.login_counter += 1
|
||||||
|
|
||||||
# No recursion wanted.
|
# No recursion wanted.
|
||||||
#if self.login_counter > 1:
|
#if self.login_counter > 1:
|
||||||
# return False
|
# return False
|
||||||
|
|
||||||
params = urllib.urlencode({"login_username" : login,
|
params = urllib.urlencode({"login_username" : login,
|
||||||
"login_password" : password,
|
"login_password" : password,
|
||||||
"login" : "Вход"})
|
"login" : "Вход"})
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
return self.logged_in
|
return self.logged_in
|
||||||
|
|
||||||
def searchurl(self, artist, album, year, format):
|
def searchurl(self, artist, album, year, format):
|
||||||
"""
|
"""
|
||||||
Return the search url
|
Return the search url
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Build search url
|
# Build search url
|
||||||
|
|
||||||
searchterm = ''
|
searchterm = ''
|
||||||
if artist != 'Various Artists':
|
if artist != 'Various Artists':
|
||||||
searchterm = artist
|
searchterm = artist
|
||||||
@@ -72,202 +76,197 @@ class Rutracker():
|
|||||||
searchterm = searchterm + album
|
searchterm = searchterm + album
|
||||||
searchterm = searchterm + ' '
|
searchterm = searchterm + ' '
|
||||||
searchterm = searchterm + year
|
searchterm = searchterm + year
|
||||||
|
|
||||||
providerurl = "http://rutracker.org/forum/tracker.php"
|
providerurl = "http://rutracker.org/forum/tracker.php"
|
||||||
|
|
||||||
if format == 'lossless':
|
if format == 'lossless':
|
||||||
format = '+lossless'
|
format = '+lossless'
|
||||||
elif format == 'lossless+mp3':
|
elif format == 'lossless+mp3':
|
||||||
format = '+lossless||mp3||aac'
|
format = '+lossless||mp3||aac'
|
||||||
else:
|
else:
|
||||||
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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
titles = []
|
titles = []
|
||||||
urls = []
|
urls = []
|
||||||
seeders = []
|
seeders = []
|
||||||
sizes = []
|
sizes = []
|
||||||
torrentlist = []
|
torrentlist = []
|
||||||
rulist = []
|
rulist = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
page = self.opener.open(searchurl, timeout=60)
|
page = self.opener.open(searchurl, timeout=60)
|
||||||
soup = BeautifulSoup(page.read())
|
soup = BeautifulSoup(page.read())
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
#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)
|
||||||
|
|
||||||
except :
|
except :
|
||||||
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
|
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
|
||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
|
||||||
hptrackcount = len(tracks)
|
|
||||||
|
|
||||||
if not hptrackcount:
|
|
||||||
logger.info('headphones track info not found, cannot compare to torrent')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Return all valid entries, ignored, required words now checked in searcher.py
|
|
||||||
|
|
||||||
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
|
||||||
|
|
||||||
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
# don't bother checking track counts anymore, let searcher filter instead
|
||||||
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
# leave code in just in case
|
||||||
|
check_track_count = False
|
||||||
|
|
||||||
|
if check_track_count:
|
||||||
|
|
||||||
|
# get headphones track count for album, return if not found
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||||
|
hptrackcount = len(tracks)
|
||||||
|
|
||||||
|
if not hptrackcount:
|
||||||
|
logger.info('headphones track info not found, cannot compare to torrent')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Return all valid entries, ignored, required words now checked in searcher.py
|
||||||
|
|
||||||
|
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
||||||
|
|
||||||
|
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
||||||
|
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
||||||
|
|
||||||
for torrent in torrentlist:
|
for torrent in torrentlist:
|
||||||
|
|
||||||
returntitle = torrent[0].encode('utf-8')
|
returntitle = torrent[0].encode('utf-8')
|
||||||
url = torrent[1]
|
url = torrent[1]
|
||||||
seeders = torrent[2]
|
seeders = torrent[2]
|
||||||
size = torrent[3]
|
size = torrent[3]
|
||||||
|
|
||||||
title = returntitle.lower()
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
if int(size) <= maxsize and int(seeders) >= minseeders:
|
||||||
# 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
|
#Torrent topic page
|
||||||
|
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
||||||
totallogcount = 0
|
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
||||||
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
|
||||||
page = self.opener.open(topicurl, timeout=60)
|
# add to list
|
||||||
soup = BeautifulSoup(page.read())
|
if not check_track_count:
|
||||||
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
|
||||||
|
|
||||||
|
|||||||