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 |
@@ -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,13 +1,35 @@
|
|||||||
#Headphones
|
#Headphones
|
||||||
|
|
||||||
|
###Support & Discuss
|
||||||
|
|
||||||
|
You are free to join the HP support community on IRC where you can ask questions, hang around and discuss anything related to HP.
|
||||||
|
|
||||||
|
1. Use any IRC client and connect to the Freenode server.
|
||||||
|
2. Join #headphones
|
||||||
|
|
||||||
###Installation and Notes
|
###Installation and Notes
|
||||||
|
|
||||||
This is a relatively early release of a third-party add-on for SABnzbd. It's been around for about a year, and while
|
[Read our Wiki](../../wiki) on how to install and use HeadPhones properly.
|
||||||
quite functional, there are still a lot of kinks to work out!
|
|
||||||
|
|
||||||
To run it, just double-click the Headphones.py file (in Windows - you may need to right click and click 'Open With' -> Python) or launch a terminal, cd into the Headphones directory and run 'python headphones.py'.
|
[**Troubleshooting** page](../../wiki/TroubleShooting) in the wiki can help you with comon problems.
|
||||||
|
|
||||||
|
**Issues** can be reported on the GitHub issue tracker considering these rules:
|
||||||
|
|
||||||
|
1. Analyze your log, you just might find the solution yourself!
|
||||||
|
2. You read the wiki and searched existing issues, but this is not solving your problem.
|
||||||
|
3. Post the issue with a clear title, description and the HP log and use [proper markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your text (code/log in code blocks).
|
||||||
|
4. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
|
||||||
|
|
||||||
|
**Feature requests** can be reported on the GitHub issue tracker too:
|
||||||
|
|
||||||
|
1. Search for similar existing 'issues', feature requests can be recognized by the label 'Request'.
|
||||||
|
2. If a similar Request exists, post a comment (+1, or add a new idea to the existing request), otherwise you can create a new one.
|
||||||
|
|
||||||
|
If you **comply with these rules** you can [post your request/issue](http://github.com/rembo10/headphones/issues).
|
||||||
|
|
||||||
|
**Support** the project by implementing new features, solving support tickets and provide bug fixes.
|
||||||
|
If you change something in the code always make a PR to the developer branch instead of the master branch.
|
||||||
|
|
||||||
For additional startup options, type 'python Headphones.py -h' or 'python Headphones.py --help'
|
|
||||||
|
|
||||||
###Screenshots
|
###Screenshots
|
||||||
|
|
||||||
@@ -39,6 +61,5 @@ Album Page with track overview:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
If you run into any issues, use the GitHub issue tracker at: http://github.com/rembo10/headphones/issues.
|
|
||||||
|
|
||||||
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
This is free software under the GPL v3 open source license - so feel free to do with it what you wish.
|
||||||
|
|||||||
@@ -1,318 +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.gradeI {
|
|
||||||
background-color: #bebebe;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.display tr.even.gradeI {
|
|
||||||
background-color: #bebebe;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 |
@@ -90,8 +90,14 @@
|
|||||||
<img height="200" alt="" class="albumArt" src="artwork/album/${album['AlbumID']}">
|
<img height="200" alt="" class="albumArt" src="artwork/album/${album['AlbumID']}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1><a href="http://musicbrainz.org/release-group/${album['AlbumID']}">${album['AlbumTitle']}</a></h1>
|
<h1 id="albumname">
|
||||||
<h2><a href="http://musicbrainz.org/artist/${album['ArtistID']}">${album['ArtistName']}</a></h2>
|
<a href="http://musicbrainz.org/release-group/${album['AlbumID']}" id="albumnamelink">${album['AlbumTitle']}</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2 id="artistname">
|
||||||
|
<a href="http://musicbrainz.org/artist/${album['ArtistID']}" id="artistnamelink">${album['ArtistName']}</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<%
|
<%
|
||||||
totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
|
totalduration = myDB.action("SELECT SUM(TrackDuration) FROM tracks WHERE AlbumID=?", [album['AlbumID']]).fetchone()[0]
|
||||||
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
|
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
|
||||||
@@ -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>
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
%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>
|
||||||
@@ -134,7 +128,7 @@
|
|||||||
%if album['Status'] == 'Skipped' or album['Status'] == 'Ignored':
|
%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
|
||||||
@@ -156,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()">
|
||||||
@@ -171,7 +162,6 @@
|
|||||||
getInfo(elem,id,'artist');
|
getInfo(elem,id,'artist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<%
|
<%
|
||||||
if headphones.SONGKICK_FILTER_ENABLED:
|
if headphones.SONGKICK_FILTER_ENABLED:
|
||||||
songkick_filter_enabled = "true"
|
songkick_filter_enabled = "true"
|
||||||
@@ -200,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;
|
||||||
|
|
||||||
@@ -240,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": [
|
||||||
@@ -283,7 +304,7 @@
|
|||||||
resetFilters("albums");
|
resetFilters("albums");
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
initFancybox();
|
initFancybox();
|
||||||
},1500)
|
},1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@@ -291,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,18 +104,36 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -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%; }
|
|
||||||
@@ -503,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;
|
||||||
@@ -545,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;
|
||||||
@@ -576,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;
|
||||||
@@ -595,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;
|
||||||
@@ -605,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;
|
||||||
@@ -758,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;
|
||||||
@@ -1126,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;
|
||||||
@@ -1144,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;
|
||||||
}
|
}
|
||||||
@@ -1215,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;
|
||||||
@@ -1416,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");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
resetFilters("artist or album");
|
initThisPage();
|
||||||
}
|
});
|
||||||
|
$(window).load(function(){
|
||||||
$(document).ready(function() {
|
initFancybox();
|
||||||
initThisPage();
|
refreshLoadArtist();
|
||||||
});
|
});
|
||||||
$(window).load(function(){
|
|
||||||
initFancybox();
|
|
||||||
refreshLoadArtist();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|||||||
@@ -1,648 +0,0 @@
|
|||||||
/*
|
|
||||||
* File: jquery.dataTables.min.js
|
|
||||||
* Version: 1.8.1
|
|
||||||
* Author: Allan Jardine (www.sprymedia.co.uk)
|
|
||||||
* Info: www.datatables.net
|
|
||||||
*
|
|
||||||
* Copyright 2008-2011 Allan Jardine, all rights reserved.
|
|
||||||
*
|
|
||||||
* This source file is free software, under either the GPL v2 license or a
|
|
||||||
* BSD style license, as supplied with this software.
|
|
||||||
*
|
|
||||||
* This source file is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
|
|
||||||
*/
|
|
||||||
(function (i, wa, p) {
|
|
||||||
i.fn.dataTableSettings = [];
|
|
||||||
var D = i.fn.dataTableSettings; i.fn.dataTableExt = {};
|
|
||||||
var o = i.fn.dataTableExt;
|
|
||||||
o.sVersion = "1.8.1";
|
|
||||||
o.sErrMode = "alert";
|
|
||||||
o.iApiIndex = 0; o.oApi = {};
|
|
||||||
o.afnFiltering = [];
|
|
||||||
o.aoFeatures = [];
|
|
||||||
o.ofnSearch = {};
|
|
||||||
o.afnSortData = [];
|
|
||||||
o.oStdClasses = {
|
|
||||||
sPagePrevEnabled: "paginate_enabled_previous",
|
|
||||||
sPagePrevDisabled: "paginate_disabled_previous",
|
|
||||||
sPageNextEnabled: "paginate_enabled_next",
|
|
||||||
sPageNextDisabled: "paginate_disabled_next",
|
|
||||||
sPageJUINext: "", sPageJUIPrev: "",
|
|
||||||
sPageButton: "paginate_button",
|
|
||||||
sPageButtonActive: "paginate_active",
|
|
||||||
sPageButtonStaticDisabled: "paginate_button paginate_button_disabled",
|
|
||||||
sPageFirst: "first",
|
|
||||||
sPagePrevious: "previous",
|
|
||||||
sPageNext: "next",
|
|
||||||
sPageLast: "last",
|
|
||||||
sStripOdd: "odd",
|
|
||||||
sStripEven: "even",
|
|
||||||
sRowEmpty: "dataTables_empty",
|
|
||||||
sWrapper: "dataTables_wrapper",
|
|
||||||
sFilter: "dataTables_filter",
|
|
||||||
sInfo: "dataTables_info",
|
|
||||||
sPaging: "dataTables_paginate paging_",
|
|
||||||
sLength: "dataTables_length",
|
|
||||||
sProcessing: "dataTables_processing",
|
|
||||||
sSortAsc: "sorting_asc",
|
|
||||||
sSortDesc: "sorting_desc",
|
|
||||||
sSortable: "sorting",
|
|
||||||
sSortableAsc: "sorting_asc_disabled",
|
|
||||||
sSortableDesc: "sorting_desc_disabled",
|
|
||||||
sSortableNone: "sorting_disabled",
|
|
||||||
sSortColumn: "sorting_",
|
|
||||||
sSortJUIAsc: "",
|
|
||||||
sSortJUIDesc: "",
|
|
||||||
sSortJUI: "",
|
|
||||||
sSortJUIAscAllowed: "",
|
|
||||||
sSortJUIDescAllowed: "", sSortJUIWrapper: "", sSortIcon: "", sScrollWrapper: "dataTables_scroll",
|
|
||||||
sScrollHead: "dataTables_scrollHead",
|
|
||||||
sScrollHeadInner: "dataTables_scrollHeadInner",
|
|
||||||
sScrollBody: "dataTables_scrollBody",
|
|
||||||
sScrollFoot: "dataTables_scrollFoot",
|
|
||||||
sScrollFootInner: "dataTables_scrollFootInner",
|
|
||||||
sFooterTH: ""
|
|
||||||
}; o.oJUIClasses = { sPagePrevEnabled: "fg-button ui-button ui-state-default ui-corner-left",
|
|
||||||
sPagePrevDisabled: "fg-button ui-button ui-state-default ui-corner-left ui-state-disabled",
|
|
||||||
sPageNextEnabled: "fg-button ui-button ui-state-default ui-corner-right",
|
|
||||||
sPageNextDisabled: "fg-button ui-button ui-state-default ui-corner-right ui-state-disabled",
|
|
||||||
sPageJUINext: "ui-icon ui-icon-circle-arrow-e",
|
|
||||||
sPageJUIPrev: "ui-icon ui-icon-circle-arrow-w",
|
|
||||||
sPageButton: "fg-button ui-button ui-state-default",
|
|
||||||
sPageButtonActive: "fg-button ui-button ui-state-default ui-state-disabled",
|
|
||||||
sPageButtonStaticDisabled: "fg-button ui-button ui-state-default ui-state-disabled",
|
|
||||||
sPageFirst: "first ui-corner-tl ui-corner-bl",
|
|
||||||
sPagePrevious: "previous",
|
|
||||||
sPageNext: "next",
|
|
||||||
sPageLast: "last ui-corner-tr ui-corner-br",
|
|
||||||
sStripOdd: "odd",
|
|
||||||
sStripEven: "even",
|
|
||||||
sRowEmpty: "dataTables_empty",
|
|
||||||
sWrapper: "dataTables_wrapper",
|
|
||||||
sFilter: "dataTables_filter",
|
|
||||||
sInfo: "dataTables_info",
|
|
||||||
sPaging: "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",
|
|
||||||
sLength: "dataTables_length",
|
|
||||||
sProcessing: "dataTables_processing",
|
|
||||||
sSortAsc: "ui-state-default",
|
|
||||||
sSortDesc: "ui-state-default",
|
|
||||||
sSortable: "ui-state-default",
|
|
||||||
sSortableAsc: "ui-state-default",
|
|
||||||
sSortableDesc: "ui-state-default",
|
|
||||||
sSortableNone: "ui-state-default",
|
|
||||||
sSortColumn: "sorting_",
|
|
||||||
sSortJUIAsc: "css_right ui-icon ui-icon-triangle-1-n",
|
|
||||||
sSortJUIDesc: "css_right ui-icon ui-icon-triangle-1-s",
|
|
||||||
sSortJUI: "css_right ui-icon ui-icon-carat-2-n-s",
|
|
||||||
sSortJUIAscAllowed: "css_right ui-icon ui-icon-carat-1-n",
|
|
||||||
sSortJUIDescAllowed: "css_right ui-icon ui-icon-carat-1-s",
|
|
||||||
sSortJUIWrapper: "DataTables_sort_wrapper",
|
|
||||||
sSortIcon: "DataTables_sort_icon",
|
|
||||||
sScrollWrapper: "dataTables_scroll",
|
|
||||||
sScrollHead: "dataTables_scrollHead ui-state-default",
|
|
||||||
sScrollHeadInner: "dataTables_scrollHeadInner",
|
|
||||||
sScrollBody: "dataTables_scrollBody",
|
|
||||||
sScrollFoot: "dataTables_scrollFoot ui-state-default",
|
|
||||||
sScrollFootInner: "dataTables_scrollFootInner",
|
|
||||||
sFooterTH: "ui-state-default"
|
|
||||||
}; o.oPagination = { two_button: { fnInit: function (g, l, r) {
|
|
||||||
var s, w, y; if (g.bJUI) {
|
|
||||||
s = p.createElement("a"); w = p.createElement("a"); y = p.createElement("span"); y.className = g.oClasses.sPageJUINext; w.appendChild(y); y = p.createElement("span"); y.className = g.oClasses.sPageJUIPrev;
|
|
||||||
s.appendChild(y)
|
|
||||||
}
|
|
||||||
else { s = p.createElement("div"); w = p.createElement("div") } s.className = g.oClasses.sPagePrevDisabled; w.className = g.oClasses.sPageNextDisabled; s.title = g.oLanguage.oPaginate.sPrevious; w.title = g.oLanguage.oPaginate.sNext; l.appendChild(s); l.appendChild(w); i(s).bind("click.DT", function () { g.oApi._fnPageChange(g, "previous") && r(g) }); i(w).bind("click.DT", function () { g.oApi._fnPageChange(g, "next") && r(g) }); i(s).bind("selectstart.DT", function () { return false }); i(w).bind("selectstart.DT", function () { return false });
|
|
||||||
|
|
||||||
if (g.sTableId !== "" && typeof g.aanFeatures.p == "undefined") {
|
|
||||||
l.setAttribute("id", g.sTableId + "_paginate");
|
|
||||||
s.setAttribute("id", g.sTableId + "_previous");
|
|
||||||
w.setAttribute("id", g.sTableId + "_next")
|
|
||||||
}
|
|
||||||
}, fnUpdate: function (g) {
|
|
||||||
if (g.aanFeatures.p) for (var l = g.aanFeatures.p, r = 0, s = l.length; r < s; r++)
|
|
||||||
if (l[r].childNodes.length !== 0) {
|
|
||||||
l[r].childNodes[0].className = g._iDisplayStart === 0 ? g.oClasses.sPagePrevDisabled : g.oClasses.sPagePrevEnabled; l[r].childNodes[1].className = g.fnDisplayEnd() == g.fnRecordsDisplay() ? g.oClasses.sPageNextDisabled :
|
|
||||||
g.oClasses.sPageNextEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, iFullNumbersShowPages: 5, full_numbers: { fnInit: function (g, l, r) {
|
|
||||||
var s = p.createElement("span"), w = p.createElement("span"), y = p.createElement("span"), G = p.createElement("span"), x = p.createElement("span"); s.innerHTML = g.oLanguage.oPaginate.sFirst; w.innerHTML = g.oLanguage.oPaginate.sPrevious; G.innerHTML = g.oLanguage.oPaginate.sNext; x.innerHTML = g.oLanguage.oPaginate.sLast; var v = g.oClasses; s.className = v.sPageButton + " " + v.sPageFirst; w.className = v.sPageButton + " " + v.sPagePrevious; G.className =
|
|
||||||
v.sPageButton + " " + v.sPageNext; x.className = v.sPageButton + " " + v.sPageLast; l.appendChild(s); l.appendChild(w); l.appendChild(y); l.appendChild(G); l.appendChild(x); i(s).bind("click.DT", function () { g.oApi._fnPageChange(g, "first") && r(g) }); i(w).bind("click.DT", function () { g.oApi._fnPageChange(g, "previous") && r(g) }); i(G).bind("click.DT", function () { g.oApi._fnPageChange(g, "next") && r(g) }); i(x).bind("click.DT", function () { g.oApi._fnPageChange(g, "last") && r(g) }); i("span", l).bind("mousedown.DT", function () { return false }).bind("selectstart.DT",
|
|
||||||
|
|
||||||
function () { return false }); if (g.sTableId !== "" && typeof g.aanFeatures.p == "undefined") { l.setAttribute("id", g.sTableId + "_paginate"); s.setAttribute("id", g.sTableId + "_first"); w.setAttribute("id", g.sTableId + "_previous"); G.setAttribute("id", g.sTableId + "_next"); x.setAttribute("id", g.sTableId + "_last") }
|
|
||||||
}, fnUpdate: function (g, l) {
|
|
||||||
if (g.aanFeatures.p) {
|
|
||||||
var r = o.oPagination.iFullNumbersShowPages, s = Math.floor(r / 2), w = Math.ceil(g.fnRecordsDisplay() / g._iDisplayLength), y = Math.ceil(g._iDisplayStart / g._iDisplayLength) + 1, G =
|
|
||||||
"", x, v = g.oClasses; if (w < r) { s = 1; x = w } else if (y <= s) { s = 1; x = r } else if (y >= w - s) { s = w - r + 1; x = w } else { s = y - Math.ceil(r / 2) + 1; x = s + r - 1 } for (r = s; r <= x; r++) G += y != r ? '<span class="' + v.sPageButton + '">' + r + "</span>" : '<span class="' + v.sPageButtonActive + '">' + r + "</span>"; x = g.aanFeatures.p; var z, Y = function (L) { g._iDisplayStart = (this.innerHTML * 1 - 1) * g._iDisplayLength; l(g); L.preventDefault() }, V = function () { return false }; r = 0; for (s = x.length; r < s; r++) if (x[r].childNodes.length !== 0) {
|
|
||||||
z = i("span:eq(2)", x[r]); z.html(G); i("span", z).bind("click.DT",
|
|
||||||
Y).bind("mousedown.DT", V).bind("selectstart.DT", V); z = x[r].getElementsByTagName("span"); z = [z[0], z[1], z[z.length - 2], z[z.length - 1]]; i(z).removeClass(v.sPageButton + " " + v.sPageButtonActive + " " + v.sPageButtonStaticDisabled); if (y == 1) { z[0].className += " " + v.sPageButtonStaticDisabled; z[1].className += " " + v.sPageButtonStaticDisabled } else { z[0].className += " " + v.sPageButton; z[1].className += " " + v.sPageButton } if (w === 0 || y == w || g._iDisplayLength == -1) {
|
|
||||||
z[2].className += " " + v.sPageButtonStaticDisabled; z[3].className += " " +
|
|
||||||
v.sPageButtonStaticDisabled
|
|
||||||
} else { z[2].className += " " + v.sPageButton; z[3].className += " " + v.sPageButton }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}; o.oSort = { "string-asc": function (g, l) { if (typeof g != "string") g = ""; if (typeof l != "string") l = ""; g = g.toLowerCase(); l = l.toLowerCase(); return g < l ? -1 : g > l ? 1 : 0 }, "string-desc": function (g, l) { if (typeof g != "string") g = ""; if (typeof l != "string") l = ""; g = g.toLowerCase(); l = l.toLowerCase(); return g < l ? 1 : g > l ? -1 : 0 }, "html-asc": function (g, l) {
|
|
||||||
g = g.replace(/<.*?>/g, "").toLowerCase(); l = l.replace(/<.*?>/g, "").toLowerCase(); return g <
|
|
||||||
l ? -1 : g > l ? 1 : 0
|
|
||||||
}, "html-desc": function (g, l) { g = g.replace(/<.*?>/g, "").toLowerCase(); l = l.replace(/<.*?>/g, "").toLowerCase(); return g < l ? 1 : g > l ? -1 : 0 }, "date-asc": function (g, l) { g = Date.parse(g); l = Date.parse(l); if (isNaN(g) || g === "") g = Date.parse("01/01/1970 00:00:00"); if (isNaN(l) || l === "") l = Date.parse("01/01/1970 00:00:00"); return g - l }, "date-desc": function (g, l) {
|
|
||||||
g = Date.parse(g); l = Date.parse(l); if (isNaN(g) || g === "") g = Date.parse("01/01/1970 00:00:00"); if (isNaN(l) || l === "") l = Date.parse("01/01/1970 00:00:00"); return l -
|
|
||||||
g
|
|
||||||
}, "numeric-asc": function (g, l) { return (g == "-" || g === "" ? 0 : g * 1) - (l == "-" || l === "" ? 0 : l * 1) }, "numeric-desc": function (g, l) { return (l == "-" || l === "" ? 0 : l * 1) - (g == "-" || g === "" ? 0 : g * 1) }
|
|
||||||
}; o.aTypes = [function (g) { if (typeof g == "number") return "numeric"; else if (typeof g != "string") return null; var l, r = false; l = g.charAt(0); if ("0123456789-".indexOf(l) == -1) return null; for (var s = 1; s < g.length; s++) { l = g.charAt(s); if ("0123456789.".indexOf(l) == -1) return null; if (l == ".") { if (r) return null; r = true } } return "numeric" }, function (g) {
|
|
||||||
var l = Date.parse(g);
|
|
||||||
|
|
||||||
if (l !== null && !isNaN(l) || typeof g == "string" && g.length === 0) return "date"; return null
|
|
||||||
}, function (g) { if (typeof g == "string" && g.indexOf("<") != -1 && g.indexOf(">") != -1) return "html"; return null } ]; o.fnVersionCheck = function (g) { var l = function (x, v) { for (; x.length < v; ) x += "0"; return x }, r = o.sVersion.split("."); g = g.split("."); for (var s = "", w = "", y = 0, G = g.length; y < G; y++) { s += l(r[y], 3); w += l(g[y], 3) } return parseInt(s, 10) >= parseInt(w, 10) }; o._oExternConfig = { iNextUnique: 0 }; i.fn.dataTable = function (g) {
|
|
||||||
function l() {
|
|
||||||
this.fnRecordsTotal =
|
|
||||||
function () { return this.oFeatures.bServerSide ? parseInt(this._iRecordsTotal, 10) : this.aiDisplayMaster.length }; this.fnRecordsDisplay = function () { return this.oFeatures.bServerSide ? parseInt(this._iRecordsDisplay, 10) : this.aiDisplay.length }; this.fnDisplayEnd = function () { return this.oFeatures.bServerSide ? this.oFeatures.bPaginate === false || this._iDisplayLength == -1 ? this._iDisplayStart + this.aiDisplay.length : Math.min(this._iDisplayStart + this._iDisplayLength, this._iRecordsDisplay) : this._iDisplayEnd }; this.sInstance =
|
|
||||||
this.oInstance = null; this.oFeatures = { bPaginate: true, bLengthChange: true, bFilter: true, bSort: true, bInfo: true, bAutoWidth: true, bProcessing: false, bSortClasses: true, bStateSave: false, bServerSide: false, bDeferRender: false }; this.oScroll = { sX: "", sXInner: "", sY: "", bCollapse: false, bInfinite: false, iLoadGap: 100, iBarWidth: 0, bAutoCss: true }; this.aanFeatures = [];
|
|
||||||
this.oLanguage = { sProcessing: "Processing...", sLengthMenu: "Show _MENU_ artists per page", sZeroRecords: "No matching records found", sEmptyTable: "",
|
|
||||||
sLoadingRecords: "Loading...", sInfo: "Showing _START_ to _END_ of _TOTAL_ artists", sInfoEmpty: "Showing 0 to 0 of 0 artists", sInfoFiltered: "(filtered from _MAX_ total artists)", sInfoPostFix: "", sSearch: "Filter:", sUrl: "", oPaginate: { sFirst: "First", sPrevious: "Previous", sNext: "Next", sLast: "Last" }, fnInfoCallback: null
|
|
||||||
}; this.aoData = []; this.aiDisplay = []; this.aiDisplayMaster = []; this.aoColumns = []; this.aoHeader = []; this.aoFooter = []; this.iNextId = 0; this.asDataSearch = []; this.oPreviousSearch = { sSearch: "", bRegex: false,
|
|
||||||
bSmart: true
|
|
||||||
}; this.aoPreSearchCols = []; this.aaSorting = [[0, "asc", 0]]; this.aaSortingFixed = null; this.asStripClasses = []; this.asDestoryStrips = []; this.sDestroyWidth = 0; this.fnFooterCallback = this.fnHeaderCallback = this.fnRowCallback = null; this.aoDrawCallback = []; this.fnInitComplete = this.fnPreDrawCallback = null; this.sTableId = ""; this.nTableWrapper = this.nTBody = this.nTFoot = this.nTHead = this.nTable = null; this.bInitialised = this.bDeferLoading = false; this.aoOpenRows = []; this.sDom = "lfrtip"; this.sPaginationType = "two_button";
|
|
||||||
this.iCookieDuration = 7200; this.sCookiePrefix = "SpryMedia_DataTables_"; this.fnCookieCallback = null; this.aoStateSave = []; this.aoStateLoad = []; this.sAjaxSource = this.oLoadedState = null; this.sAjaxDataProp = "aaData"; this.bAjaxDataGet = true; this.jqXHR = null; this.fnServerData = function (a, b, c, d) { d.jqXHR = i.ajax({ url: a, data: b, success: c, dataType: "json", cache: false, error: function (f, e) { e == "parsererror" && alert("DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.") } }) };
|
|
||||||
this.fnFormatNumber = function (a) { if (a < 1E3) return a; else { var b = a + ""; a = b.split(""); var c = ""; b = b.length; for (var d = 0; d < b; d++) { if (d % 3 === 0 && d !== 0) c = "," + c; c = a[b - d - 1] + c } } return c }; this.aLengthMenu = [10, 25, 50, 100]; this.bDrawing = this.iDraw = 0; this.iDrawError = -1; this._iDisplayLength = 10; this._iDisplayStart = 0; this._iDisplayEnd = 10; this._iRecordsDisplay = this._iRecordsTotal = 0; this.bJUI = false; this.oClasses = o.oStdClasses; this.bSortCellsTop = this.bSorted = this.bFiltered = false; this.oInit = null
|
|
||||||
} function r(a) {
|
|
||||||
return function () {
|
|
||||||
var b =
|
|
||||||
[A(this[o.iApiIndex])].concat(Array.prototype.slice.call(arguments)); return o.oApi[a].apply(this, b)
|
|
||||||
}
|
|
||||||
} function s(a) {
|
|
||||||
var b, c, d = a.iInitDisplayStart; if (a.bInitialised === false) setTimeout(function () { s(a) }, 200); else {
|
|
||||||
xa(a); V(a); L(a, a.aoHeader); a.nTFoot && L(a, a.aoFooter);
|
|
||||||
K(a, true); a.oFeatures.bAutoWidth && ea(a);
|
|
||||||
b = 0; for (c = a.aoColumns.length; b < c; b++)
|
|
||||||
if (a.aoColumns[b].sWidth !== null) a.aoColumns[b].nTh.style.width = u(a.aoColumns[b].sWidth);
|
|
||||||
if (a.oFeatures.bSort) R(a);
|
|
||||||
else if (a.oFeatures.bFilter) M(a, a.oPreviousSearch);
|
|
||||||
else { a.aiDisplay = a.aiDisplayMaster.slice(); E(a); C(a) } if (a.sAjaxSource !== null && !a.oFeatures.bServerSide) a.fnServerData.call(a.oInstance, a.sAjaxSource, [], function (f) { var e = f; if (a.sAjaxDataProp !== "") e = Z(a.sAjaxDataProp)(f); for (b = 0; b < e.length; b++) v(a, e[b]); a.iInitDisplayStart = d; if (a.oFeatures.bSort) R(a); else { a.aiDisplay = a.aiDisplayMaster.slice(); E(a); C(a) } K(a, false); w(a, f) }, a); else if (!a.oFeatures.bServerSide) { K(a, false); w(a) }
|
|
||||||
}
|
|
||||||
} function w(a, b) {
|
|
||||||
a._bInitComplete = true; if (typeof a.fnInitComplete == "function") typeof b !=
|
|
||||||
"undefined" ? a.fnInitComplete.call(a.oInstance, a, b) : a.fnInitComplete.call(a.oInstance, a)
|
|
||||||
} function y(a, b, c) {
|
|
||||||
n(a.oLanguage, b, "sProcessing"); n(a.oLanguage, b, "sLengthMenu"); n(a.oLanguage, b, "sEmptyTable"); n(a.oLanguage, b, "sLoadingRecords"); n(a.oLanguage, b, "sZeroRecords"); n(a.oLanguage, b, "sInfo"); n(a.oLanguage, b, "sInfoEmpty"); n(a.oLanguage, b, "sInfoFiltered"); n(a.oLanguage, b, "sInfoPostFix"); n(a.oLanguage, b, "sSearch"); if (typeof b.oPaginate != "undefined") {
|
|
||||||
n(a.oLanguage.oPaginate, b.oPaginate, "sFirst"); n(a.oLanguage.oPaginate,
|
|
||||||
b.oPaginate, "sPrevious"); n(a.oLanguage.oPaginate, b.oPaginate, "sNext"); n(a.oLanguage.oPaginate, b.oPaginate, "sLast")
|
|
||||||
} typeof b.sEmptyTable == "undefined" && typeof b.sZeroRecords != "undefined" && n(a.oLanguage, b, "sZeroRecords", "sEmptyTable"); typeof b.sLoadingRecords == "undefined" && typeof b.sZeroRecords != "undefined" && n(a.oLanguage, b, "sZeroRecords", "sLoadingRecords"); c && s(a)
|
|
||||||
} function G(a, b) {
|
|
||||||
var c = a.aoColumns.length; b = { sType: null, _bAutoType: true, bVisible: true, bSearchable: true, bSortable: true, asSorting: ["asc", "desc"],
|
|
||||||
sSortingClass: a.oClasses.sSortable, sSortingClassJUI: a.oClasses.sSortJUI, sTitle: b ? b.innerHTML : "", sName: "", sWidth: null, sWidthOrig: null, sClass: null, fnRender: null, bUseRendered: true, iDataSort: c, mDataProp: c, fnGetData: null, fnSetData: null, sSortDataType: "std", sDefaultContent: null, sContentPadding: "", nTh: b ? b : p.createElement("th"), nTf: null
|
|
||||||
}; a.aoColumns.push(b); if (typeof a.aoPreSearchCols[c] == "undefined" || a.aoPreSearchCols[c] === null) a.aoPreSearchCols[c] = { sSearch: "", bRegex: false, bSmart: true }; else {
|
|
||||||
if (typeof a.aoPreSearchCols[c].bRegex ==
|
|
||||||
"undefined") a.aoPreSearchCols[c].bRegex = true; if (typeof a.aoPreSearchCols[c].bSmart == "undefined") a.aoPreSearchCols[c].bSmart = true
|
|
||||||
} x(a, c, null)
|
|
||||||
} function x(a, b, c) {
|
|
||||||
b = a.aoColumns[b]; if (typeof c != "undefined" && c !== null) {
|
|
||||||
if (typeof c.sType != "undefined") { b.sType = c.sType; b._bAutoType = false } n(b, c, "bVisible"); n(b, c, "bSearchable"); n(b, c, "bSortable"); n(b, c, "sTitle"); n(b, c, "sName"); n(b, c, "sWidth"); n(b, c, "sWidth", "sWidthOrig"); n(b, c, "sClass"); n(b, c, "fnRender"); n(b, c, "bUseRendered"); n(b, c, "iDataSort"); n(b, c, "mDataProp");
|
|
||||||
n(b, c, "asSorting"); n(b, c, "sSortDataType"); n(b, c, "sDefaultContent"); n(b, c, "sContentPadding")
|
|
||||||
} b.fnGetData = Z(b.mDataProp); b.fnSetData = ya(b.mDataProp); if (!a.oFeatures.bSort) b.bSortable = false; if (!b.bSortable || i.inArray("asc", b.asSorting) == -1 && i.inArray("desc", b.asSorting) == -1) { b.sSortingClass = a.oClasses.sSortableNone; b.sSortingClassJUI = "" } else if (b.bSortable || i.inArray("asc", b.asSorting) == -1 && i.inArray("desc", b.asSorting) == -1) { b.sSortingClass = a.oClasses.sSortable; b.sSortingClassJUI = a.oClasses.sSortJUI } else if (i.inArray("asc",
|
|
||||||
b.asSorting) != -1 && i.inArray("desc", b.asSorting) == -1) { b.sSortingClass = a.oClasses.sSortableAsc; b.sSortingClassJUI = a.oClasses.sSortJUIAscAllowed } else if (i.inArray("asc", b.asSorting) == -1 && i.inArray("desc", b.asSorting) != -1) { b.sSortingClass = a.oClasses.sSortableDesc; b.sSortingClassJUI = a.oClasses.sSortJUIDescAllowed }
|
|
||||||
} function v(a, b) {
|
|
||||||
var c; c = typeof b.length == "number" ? b.slice() : i.extend(true, {}, b); b = a.aoData.length; var d = { nTr: null, _iId: a.iNextId++, _aData: c, _anHidden: [], _sRowStripe: "" }; a.aoData.push(d); for (var f,
|
|
||||||
e = 0, h = a.aoColumns.length; e < h; e++) { c = a.aoColumns[e]; typeof c.fnRender == "function" && c.bUseRendered && c.mDataProp !== null && N(a, b, e, c.fnRender({ iDataRow: b, iDataColumn: e, aData: d._aData, oSettings: a })); if (c._bAutoType && c.sType != "string") { f = H(a, b, e, "type"); if (f !== null && f !== "") { f = fa(f); if (c.sType === null) c.sType = f; else if (c.sType != f) c.sType = "string" } } } a.aiDisplayMaster.push(b); a.oFeatures.bDeferRender || z(a, b); return b
|
|
||||||
} function z(a, b) {
|
|
||||||
var c = a.aoData[b], d; if (c.nTr === null) {
|
|
||||||
c.nTr = p.createElement("tr"); typeof c._aData.DT_RowId !=
|
|
||||||
"undefined" && c.nTr.setAttribute("id", c._aData.DT_RowId); typeof c._aData.DT_RowClass != "undefined" && i(c.nTr).addClass(c._aData.DT_RowClass); for (var f = 0, e = a.aoColumns.length; f < e; f++) {
|
|
||||||
var h = a.aoColumns[f]; d = p.createElement("td"); d.innerHTML = typeof h.fnRender == "function" && (!h.bUseRendered || h.mDataProp === null) ? h.fnRender({ iDataRow: b, iDataColumn: f, aData: c._aData, oSettings: a }) : H(a, b, f, "display"); if (h.sClass !== null) d.className = h.sClass; if (h.bVisible) { c.nTr.appendChild(d); c._anHidden[f] = null } else c._anHidden[f] =
|
|
||||||
d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function Y(a) {
|
|
||||||
var b, c, d, f, e, h, j, k, m; if (a.bDeferLoading || a.sAjaxSource === null) { j = a.nTBody.childNodes; b = 0; for (c = j.length; b < c; b++) if (j[b].nodeName.toUpperCase() == "TR") { k = a.aoData.length; a.aoData.push({ nTr: j[b], _iId: a.iNextId++, _aData: [], _anHidden: [], _sRowStripe: "" }); a.aiDisplayMaster.push(k); h = j[b].childNodes; d = e = 0; for (f = h.length; d < f; d++) { m = h[d].nodeName.toUpperCase(); if (m == "TD" || m == "TH") { N(a, k, e, i.trim(h[d].innerHTML)); e++ } } } } j = $(a); h = []; b = 0; for (c = j.length; b < c; b++) {
|
|
||||||
d = 0; for (f = j[b].childNodes.length; d <
|
|
||||||
f; d++) { e = j[b].childNodes[d]; m = e.nodeName.toUpperCase(); if (m == "TD" || m == "TH") h.push(e) }
|
|
||||||
} h.length != j.length * a.aoColumns.length && J(a, 1, "Unexpected number of TD elements. Expected " + j.length * a.aoColumns.length + " and got " + h.length + ". DataTables does not support rowspan / colspan in the table body, and there must be one cell for each row/column combination."); d = 0; for (f = a.aoColumns.length; d < f; d++) {
|
|
||||||
if (a.aoColumns[d].sTitle === null) a.aoColumns[d].sTitle = a.aoColumns[d].nTh.innerHTML; j = a.aoColumns[d]._bAutoType;
|
|
||||||
m = typeof a.aoColumns[d].fnRender == "function"; e = a.aoColumns[d].sClass !== null; k = a.aoColumns[d].bVisible; var t, q; if (j || m || e || !k) {
|
|
||||||
b = 0; for (c = a.aoData.length; b < c; b++) {
|
|
||||||
t = h[b * f + d]; if (j && a.aoColumns[d].sType != "string") { q = H(a, b, d, "type"); if (q !== "") { q = fa(q); if (a.aoColumns[d].sType === null) a.aoColumns[d].sType = q; else if (a.aoColumns[d].sType != q) a.aoColumns[d].sType = "string" } } if (m) {
|
|
||||||
q = a.aoColumns[d].fnRender({ iDataRow: b, iDataColumn: d, aData: a.aoData[b]._aData, oSettings: a }); t.innerHTML = q; a.aoColumns[d].bUseRendered &&
|
|
||||||
N(a, b, d, q)
|
|
||||||
} if (e) t.className += " " + a.aoColumns[d].sClass; if (k) a.aoData[b]._anHidden[d] = null; else { a.aoData[b]._anHidden[d] = t; t.parentNode.removeChild(t) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function V(a) {
|
|
||||||
var b, c, d; a.nTHead.getElementsByTagName("tr"); if (a.nTHead.getElementsByTagName("th").length !== 0) { b = 0; for (d = a.aoColumns.length; b < d; b++) { c = a.aoColumns[b].nTh; a.aoColumns[b].sClass !== null && i(c).addClass(a.aoColumns[b].sClass); if (a.aoColumns[b].sTitle != c.innerHTML) c.innerHTML = a.aoColumns[b].sTitle } } else {
|
|
||||||
var f = p.createElement("tr"); b = 0;
|
|
||||||
for (d = a.aoColumns.length; b < d; b++) { c = a.aoColumns[b].nTh; c.innerHTML = a.aoColumns[b].sTitle; a.aoColumns[b].sClass !== null && i(c).addClass(a.aoColumns[b].sClass); f.appendChild(c) } i(a.nTHead).html("")[0].appendChild(f); W(a.aoHeader, a.nTHead)
|
|
||||||
} if (a.bJUI) { b = 0; for (d = a.aoColumns.length; b < d; b++) { c = a.aoColumns[b].nTh; f = p.createElement("div"); f.className = a.oClasses.sSortJUIWrapper; i(c).contents().appendTo(f); var e = p.createElement("span"); e.className = a.oClasses.sSortIcon; f.appendChild(e); c.appendChild(f) } } d = function () {
|
|
||||||
this.onselectstart =
|
|
||||||
function () { return false }; return false
|
|
||||||
}; if (a.oFeatures.bSort) for (b = 0; b < a.aoColumns.length; b++) if (a.aoColumns[b].bSortable !== false) { ga(a, a.aoColumns[b].nTh, b); i(a.aoColumns[b].nTh).bind("mousedown.DT", d) } else i(a.aoColumns[b].nTh).addClass(a.oClasses.sSortableNone); a.oClasses.sFooterTH !== "" && i(">tr>th", a.nTFoot).addClass(a.oClasses.sFooterTH); if (a.nTFoot !== null) { c = S(a, null, a.aoFooter); b = 0; for (d = a.aoColumns.length; b < d; b++) if (typeof c[b] != "undefined") a.aoColumns[b].nTf = c[b] }
|
|
||||||
} function L(a, b, c) {
|
|
||||||
var d, f,
|
|
||||||
e, h = [], j = [], k = a.aoColumns.length; if (typeof c == "undefined") c = false; d = 0; for (f = b.length; d < f; d++) { h[d] = b[d].slice(); h[d].nTr = b[d].nTr; for (e = k - 1; e >= 0; e--) !a.aoColumns[e].bVisible && !c && h[d].splice(e, 1); j.push([]) } d = 0; for (f = h.length; d < f; d++) {
|
|
||||||
if (h[d].nTr) { a = 0; for (e = h[d].nTr.childNodes.length; a < e; a++) h[d].nTr.removeChild(h[d].nTr.childNodes[0]) } e = 0; for (b = h[d].length; e < b; e++) {
|
|
||||||
k = c = 1; if (typeof j[d][e] == "undefined") {
|
|
||||||
h[d].nTr.appendChild(h[d][e].cell); for (j[d][e] = 1; typeof h[d + c] != "undefined" && h[d][e].cell == h[d +
|
|
||||||
c][e].cell; ) { j[d + c][e] = 1; c++ } for (; typeof h[d][e + k] != "undefined" && h[d][e].cell == h[d][e + k].cell; ) { for (a = 0; a < c; a++) j[d + a][e + k] = 1; k++ } h[d][e].cell.setAttribute("rowspan", c); h[d][e].cell.setAttribute("colspan", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function C(a) {
|
|
||||||
var b, c, d = [], f = 0, e = false; b = a.asStripClasses.length; c = a.aoOpenRows.length; if (!(a.fnPreDrawCallback !== null && a.fnPreDrawCallback.call(a.oInstance, a) === false)) {
|
|
||||||
a.bDrawing = true; if (typeof a.iInitDisplayStart != "undefined" && a.iInitDisplayStart != -1) {
|
|
||||||
a._iDisplayStart = a.oFeatures.bServerSide ?
|
|
||||||
a.iInitDisplayStart : a.iInitDisplayStart >= a.fnRecordsDisplay() ? 0 : a.iInitDisplayStart; a.iInitDisplayStart = -1; E(a)
|
|
||||||
} if (a.bDeferLoading) { a.bDeferLoading = false; a.iDraw++ } else if (a.oFeatures.bServerSide) { if (!a.bDestroying && !za(a)) return } else a.iDraw++; if (a.aiDisplay.length !== 0) {
|
|
||||||
var h = a._iDisplayStart, j = a._iDisplayEnd; if (a.oFeatures.bServerSide) { h = 0; j = a.aoData.length } for (h = h; h < j; h++) {
|
|
||||||
var k = a.aoData[a.aiDisplay[h]]; k.nTr === null && z(a, a.aiDisplay[h]); var m = k.nTr; if (b !== 0) {
|
|
||||||
var t = a.asStripClasses[f % b]; if (k._sRowStripe !=
|
|
||||||
t) { i(m).removeClass(k._sRowStripe).addClass(t); k._sRowStripe = t }
|
|
||||||
} if (typeof a.fnRowCallback == "function") { m = a.fnRowCallback.call(a.oInstance, m, a.aoData[a.aiDisplay[h]]._aData, f, h); if (!m && !e) { J(a, 0, "A node was not returned by fnRowCallback"); e = true } } d.push(m); f++; if (c !== 0) for (k = 0; k < c; k++) m == a.aoOpenRows[k].nParent && d.push(a.aoOpenRows[k].nTr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d[0] = p.createElement("tr"); if (typeof a.asStripClasses[0] != "undefined") d[0].className = a.asStripClasses[0]; e = a.oLanguage.sZeroRecords.replace("_MAX_", a.fnFormatNumber(a.fnRecordsTotal()));
|
|
||||||
if (a.iDraw == 1 && a.sAjaxSource !== null && !a.oFeatures.bServerSide) e = a.oLanguage.sLoadingRecords; else if (typeof a.oLanguage.sEmptyTable != "undefined" && a.fnRecordsTotal() === 0) e = a.oLanguage.sEmptyTable; b = p.createElement("td"); b.setAttribute("valign", "top"); b.colSpan = X(a); b.className = a.oClasses.sRowEmpty; b.innerHTML = e; d[f].appendChild(b)
|
|
||||||
} typeof a.fnHeaderCallback == "function" && a.fnHeaderCallback.call(a.oInstance, i(">tr", a.nTHead)[0], aa(a), a._iDisplayStart, a.fnDisplayEnd(), a.aiDisplay); typeof a.fnFooterCallback ==
|
|
||||||
"function" && a.fnFooterCallback.call(a.oInstance, i(">tr", a.nTFoot)[0], aa(a), a._iDisplayStart, a.fnDisplayEnd(), a.aiDisplay); f = p.createDocumentFragment(); b = p.createDocumentFragment(); if (a.nTBody) { e = a.nTBody.parentNode; b.appendChild(a.nTBody); if (!a.oScroll.bInfinite || !a._bInitComplete || a.bSorted || a.bFiltered) { c = a.nTBody.childNodes; for (b = c.length - 1; b >= 0; b--) c[b].parentNode.removeChild(c[b]) } b = 0; for (c = d.length; b < c; b++) f.appendChild(d[b]); a.nTBody.appendChild(f); e !== null && e.appendChild(a.nTBody) } for (b = a.aoDrawCallback.length -
|
|
||||||
1; b >= 0; b--) a.aoDrawCallback[b].fn.call(a.oInstance, a); a.bSorted = false; a.bFiltered = false; a.bDrawing = false; if (a.oFeatures.bServerSide) { K(a, false); typeof a._bInitComplete == "undefined" && w(a) }
|
|
||||||
}
|
|
||||||
} function ba(a) { if (a.oFeatures.bSort) R(a, a.oPreviousSearch); else if (a.oFeatures.bFilter) M(a, a.oPreviousSearch); else { E(a); C(a) } } function za(a) {
|
|
||||||
if (a.bAjaxDataGet) {
|
|
||||||
K(a, true); var b = a.aoColumns.length, c = [], d, f; a.iDraw++; c.push({ name: "sEcho", value: a.iDraw }); c.push({ name: "iColumns", value: b }); c.push({ name: "sColumns", value: ha(a) });
|
|
||||||
c.push({ name: "iDisplayStart", value: a._iDisplayStart }); c.push({ name: "iDisplayLength", value: a.oFeatures.bPaginate !== false ? a._iDisplayLength : -1 }); for (f = 0; f < b; f++) { d = a.aoColumns[f].mDataProp; c.push({ name: "mDataProp_" + f, value: typeof d == "function" ? "function" : d }) } if (a.oFeatures.bFilter !== false) {
|
|
||||||
c.push({ name: "sSearch", value: a.oPreviousSearch.sSearch }); c.push({ name: "bRegex", value: a.oPreviousSearch.bRegex }); for (f = 0; f < b; f++) {
|
|
||||||
c.push({ name: "sSearch_" + f, value: a.aoPreSearchCols[f].sSearch }); c.push({ name: "bRegex_" +
|
|
||||||
f, value: a.aoPreSearchCols[f].bRegex
|
|
||||||
}); c.push({ name: "bSearchable_" + f, value: a.aoColumns[f].bSearchable })
|
|
||||||
}
|
|
||||||
} if (a.oFeatures.bSort !== false) {
|
|
||||||
d = a.aaSortingFixed !== null ? a.aaSortingFixed.length : 0; var e = a.aaSorting.length; c.push({ name: "iSortingCols", value: d + e }); for (f = 0; f < d; f++) { c.push({ name: "iSortCol_" + f, value: a.aaSortingFixed[f][0] }); c.push({ name: "sSortDir_" + f, value: a.aaSortingFixed[f][1] }) } for (f = 0; f < e; f++) { c.push({ name: "iSortCol_" + (f + d), value: a.aaSorting[f][0] }); c.push({ name: "sSortDir_" + (f + d), value: a.aaSorting[f][1] }) } for (f =
|
|
||||||
0; f < b; f++) c.push({ name: "bSortable_" + f, value: a.aoColumns[f].bSortable })
|
|
||||||
} a.fnServerData.call(a.oInstance, a.sAjaxSource, c, function (h) { Aa(a, h) }, a); return false
|
|
||||||
} else return true
|
|
||||||
} function Aa(a, b) {
|
|
||||||
if (typeof b.sEcho != "undefined") if (b.sEcho * 1 < a.iDraw) return; else a.iDraw = b.sEcho * 1; if (!a.oScroll.bInfinite || a.oScroll.bInfinite && (a.bSorted || a.bFiltered)) ia(a); a._iRecordsTotal = b.iTotalRecords; a._iRecordsDisplay = b.iTotalDisplayRecords; var c = ha(a); if (c = typeof b.sColumns != "undefined" && c !== "" && b.sColumns != c) var d =
|
|
||||||
Ba(a, b.sColumns); b = Z(a.sAjaxDataProp)(b); for (var f = 0, e = b.length; f < e; f++) if (c) { for (var h = [], j = 0, k = a.aoColumns.length; j < k; j++) h.push(b[f][d[j]]); v(a, h) } else v(a, b[f]); a.aiDisplay = a.aiDisplayMaster.slice(); a.bAjaxDataGet = false; C(a); a.bAjaxDataGet = true; K(a, false)
|
|
||||||
} function xa(a) {
|
|
||||||
var b = p.createElement("div"); a.nTable.parentNode.insertBefore(b, a.nTable); a.nTableWrapper = p.createElement("div"); a.nTableWrapper.className = a.oClasses.sWrapper; a.sTableId !== "" && a.nTableWrapper.setAttribute("id", a.sTableId + "_wrapper");
|
|
||||||
a.nTableReinsertBefore = a.nTable.nextSibling; for (var c = a.nTableWrapper, d = a.sDom.split(""), f, e, h, j, k, m, t, q = 0; q < d.length; q++) {
|
|
||||||
e = 0; h = d[q]; if (h == "<") {
|
|
||||||
j = p.createElement("div"); k = d[q + 1]; if (k == "'" || k == '"') {
|
|
||||||
m = ""; for (t = 2; d[q + t] != k; ) { m += d[q + t]; t++ } if (m == "H") m = "fg-toolbar ui-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix"; else if (m == "F") m = "fg-toolbar ui-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix"; if (m.indexOf(".") != -1) {
|
|
||||||
k = m.split("."); j.setAttribute("id", k[0].substr(1,
|
|
||||||
k[0].length - 1)); j.className = k[1]
|
|
||||||
} else if (m.charAt(0) == "#") j.setAttribute("id", m.substr(1, m.length - 1)); else j.className = m; q += t
|
|
||||||
} c.appendChild(j); c = j
|
|
||||||
} else if (h == ">") c = c.parentNode; else if (h == "l" && a.oFeatures.bPaginate && a.oFeatures.bLengthChange) { f = Ca(a); e = 1 } else if (h == "f" && a.oFeatures.bFilter) { f = Da(a); e = 1 } else if (h == "r" && a.oFeatures.bProcessing) { f = Ea(a); e = 1 } else if (h == "t") { f = Fa(a); e = 1 } else if (h == "i" && a.oFeatures.bInfo) { f = Ga(a); e = 1 } else if (h == "p" && a.oFeatures.bPaginate) { f = Ha(a); e = 1 } else if (o.aoFeatures.length !==
|
|
||||||
0) { j = o.aoFeatures; t = 0; for (k = j.length; t < k; t++) if (h == j[t].cFeature) { if (f = j[t].fnInit(a)) e = 1; break } } if (e == 1 && f !== null) { if (typeof a.aanFeatures[h] != "object") a.aanFeatures[h] = []; a.aanFeatures[h].push(f); c.appendChild(f) }
|
|
||||||
} b.parentNode.replaceChild(a.nTableWrapper, b)
|
|
||||||
} function Fa(a) {
|
|
||||||
if (a.oScroll.sX === "" && a.oScroll.sY === "") return a.nTable; var b = p.createElement("div"), c = p.createElement("div"), d = p.createElement("div"), f = p.createElement("div"), e = p.createElement("div"), h = p.createElement("div"), j = a.nTable.cloneNode(false),
|
|
||||||
k = a.nTable.cloneNode(false), m = a.nTable.getElementsByTagName("thead")[0], t = a.nTable.getElementsByTagName("tfoot").length === 0 ? null : a.nTable.getElementsByTagName("tfoot")[0], q = typeof g.bJQueryUI != "undefined" && g.bJQueryUI ? o.oJUIClasses : o.oStdClasses; c.appendChild(d); e.appendChild(h); f.appendChild(a.nTable); b.appendChild(c); b.appendChild(f); d.appendChild(j); j.appendChild(m); if (t !== null) { b.appendChild(e); h.appendChild(k); k.appendChild(t) } b.className = q.sScrollWrapper; c.className = q.sScrollHead; d.className =
|
|
||||||
q.sScrollHeadInner; f.className = q.sScrollBody; e.className = q.sScrollFoot; h.className = q.sScrollFootInner; if (a.oScroll.bAutoCss) { c.style.overflow = "hidden"; c.style.position = "relative"; e.style.overflow = "hidden"; f.style.overflow = "auto" } c.style.border = "0"; c.style.width = "100%"; e.style.border = "0"; d.style.width = "150%"; j.removeAttribute("id"); j.style.marginLeft = "0"; a.nTable.style.marginLeft = "0"; if (t !== null) { k.removeAttribute("id"); k.style.marginLeft = "0" } d = i(">caption", a.nTable); h = 0; for (k = d.length; h < k; h++) j.appendChild(d[h]);
|
|
||||||
if (a.oScroll.sX !== "") { c.style.width = u(a.oScroll.sX); f.style.width = u(a.oScroll.sX); if (t !== null) e.style.width = u(a.oScroll.sX); i(f).scroll(function () { c.scrollLeft = this.scrollLeft; if (t !== null) e.scrollLeft = this.scrollLeft }) } if (a.oScroll.sY !== "") f.style.height = u(a.oScroll.sY); a.aoDrawCallback.push({ fn: Ia, sName: "scrolling" }); a.oScroll.bInfinite && i(f).scroll(function () {
|
|
||||||
if (!a.bDrawing) if (i(this).scrollTop() + i(this).height() > i(a.nTable).height() - a.oScroll.iLoadGap) if (a.fnDisplayEnd() < a.fnRecordsDisplay()) {
|
|
||||||
ja(a,
|
|
||||||
"next"); E(a); C(a)
|
|
||||||
}
|
|
||||||
}); a.nScrollHead = c; a.nScrollFoot = e; return b
|
|
||||||
} function Ia(a) {
|
|
||||||
var b = a.nScrollHead.getElementsByTagName("div")[0], c = b.getElementsByTagName("table")[0], d = a.nTable.parentNode, f, e, h, j, k, m, t, q, I = []; h = a.nTable.getElementsByTagName("thead"); h.length > 0 && a.nTable.removeChild(h[0]); if (a.nTFoot !== null) { k = a.nTable.getElementsByTagName("tfoot"); k.length > 0 && a.nTable.removeChild(k[0]) } h = a.nTHead.cloneNode(true); a.nTable.insertBefore(h, a.nTable.childNodes[0]); if (a.nTFoot !== null) {
|
|
||||||
k = a.nTFoot.cloneNode(true);
|
|
||||||
a.nTable.insertBefore(k, a.nTable.childNodes[1])
|
|
||||||
} if (a.oScroll.sX === "") { d.style.width = "100%"; b.parentNode.style.width = "100%" } var O = S(a, h); f = 0; for (e = O.length; f < e; f++) { t = Ja(a, f); O[f].style.width = a.aoColumns[t].sWidth } a.nTFoot !== null && P(function (B) { B.style.width = "" }, k.getElementsByTagName("tr")); f = i(a.nTable).outerWidth(); if (a.oScroll.sX === "") { a.nTable.style.width = "100%"; if (i.browser.msie && i.browser.version <= 7) a.nTable.style.width = u(i(a.nTable).outerWidth() - a.oScroll.iBarWidth) } else if (a.oScroll.sXInner !==
|
|
||||||
"") a.nTable.style.width = u(a.oScroll.sXInner); else if (f == i(d).width() && i(d).height() < i(a.nTable).height()) { a.nTable.style.width = u(f - a.oScroll.iBarWidth); if (i(a.nTable).outerWidth() > f - a.oScroll.iBarWidth) a.nTable.style.width = u(f) } else a.nTable.style.width = u(f); f = i(a.nTable).outerWidth(); if (a.oScroll.sX === "") { d.style.width = u(f + a.oScroll.iBarWidth); b.parentNode.style.width = u(f + a.oScroll.iBarWidth) } e = a.nTHead.getElementsByTagName("tr"); h = h.getElementsByTagName("tr"); P(function (B, F) {
|
|
||||||
m = B.style; m.paddingTop =
|
|
||||||
"0"; m.paddingBottom = "0"; m.borderTopWidth = "0"; m.borderBottomWidth = "0"; m.height = 0; q = i(B).width(); F.style.width = u(q); I.push(q)
|
|
||||||
}, h, e); i(h).height(0); if (a.nTFoot !== null) { j = k.getElementsByTagName("tr"); k = a.nTFoot.getElementsByTagName("tr"); P(function (B, F) { m = B.style; m.paddingTop = "0"; m.paddingBottom = "0"; m.borderTopWidth = "0"; m.borderBottomWidth = "0"; m.height = 0; q = i(B).width(); F.style.width = u(q); I.push(q) }, j, k); i(j).height(0) } P(function (B) { B.innerHTML = ""; B.style.width = u(I.shift()) }, h); a.nTFoot !== null && P(function (B) {
|
|
||||||
B.innerHTML =
|
|
||||||
""; B.style.width = u(I.shift())
|
|
||||||
}, j); if (i(a.nTable).outerWidth() < f) if (a.oScroll.sX === "") J(a, 1, "The table cannot fit into the current element which will cause column misalignment. It is suggested that you enable x-scrolling or increase the width the table has in which to be drawn"); else a.oScroll.sXInner !== "" && J(a, 1, "The table cannot fit into the current element which will cause column misalignment. It is suggested that you increase the sScrollXInner property to allow it to draw in a larger area, or simply remove that parameter to allow automatic calculation");
|
|
||||||
if (a.oScroll.sY === "") if (i.browser.msie && i.browser.version <= 7) d.style.height = u(a.nTable.offsetHeight + a.oScroll.iBarWidth); if (a.oScroll.sY !== "" && a.oScroll.bCollapse) { d.style.height = u(a.oScroll.sY); j = a.oScroll.sX !== "" && a.nTable.offsetWidth > d.offsetWidth ? a.oScroll.iBarWidth : 0; if (a.nTable.offsetHeight < d.offsetHeight) d.style.height = u(i(a.nTable).height() + j) } j = i(a.nTable).outerWidth(); c.style.width = u(j); b.style.width = u(j + a.oScroll.iBarWidth); if (a.nTFoot !== null) {
|
|
||||||
b = a.nScrollFoot.getElementsByTagName("div")[0];
|
|
||||||
c = b.getElementsByTagName("table")[0]; b.style.width = u(a.nTable.offsetWidth + a.oScroll.iBarWidth); c.style.width = u(a.nTable.offsetWidth)
|
|
||||||
} if (a.bSorted || a.bFiltered) d.scrollTop = 0
|
|
||||||
}
|
|
||||||
// sFilter
|
|
||||||
function ca(a) { if (a.oFeatures.bAutoWidth === false) return false; ea(a); for (var b = 0, c = a.aoColumns.length; b < c; b++) a.aoColumns[b].nTh.style.width = a.aoColumns[b].sWidth } function Da(a) {
|
|
||||||
var b = a.oLanguage.sSearch;
|
|
||||||
b = b.indexOf("_INPUT_") !== -1 ? b.replace("_INPUT_", '<input type="text" />') : b === "" ? '<input type="text" />' : b + ' <input type="text" />';
|
|
||||||
var c = p.createElement("div");
|
|
||||||
c.className = a.oClasses.sFilter;
|
|
||||||
c.innerHTML = "<div>" + b + "</div>";
|
|
||||||
a.sTableId !== "" && typeof a.aanFeatures.f == "undefined" && c.setAttribute("id", a.sTableId + "_filter");
|
|
||||||
b = i("input", c);
|
|
||||||
b.val(a.oPreviousSearch.sSearch.replace('"', """));
|
|
||||||
b.bind("keyup.DT", function () {
|
|
||||||
for (var d = a.aanFeatures.f, f = 0, e = d.length;
|
|
||||||
f < e; f++)
|
|
||||||
d[f] != this.parentNode && i("input", d[f]).val(this.value);
|
|
||||||
this.value != a.oPreviousSearch.sSearch && M(a, {
|
|
||||||
sSearch: this.value, bRegex: a.oPreviousSearch.bRegex, bSmart: a.oPreviousSearch.bSmart
|
|
||||||
})
|
|
||||||
});
|
|
||||||
b.bind("keypress.DT", function (d) {
|
|
||||||
if (d.keyCode == 13) return false
|
|
||||||
});
|
|
||||||
return c
|
|
||||||
|
|
||||||
} function M(a, b, c) { Ka(a, b.sSearch, c, b.bRegex, b.bSmart); for (b = 0; b < a.aoPreSearchCols.length; b++) La(a, a.aoPreSearchCols[b].sSearch, b, a.aoPreSearchCols[b].bRegex, a.aoPreSearchCols[b].bSmart); o.afnFiltering.length !== 0 && Ma(a); a.bFiltered = true; a._iDisplayStart = 0; E(a); C(a); ka(a, 0) } function Ma(a) {
|
|
||||||
for (var b = o.afnFiltering, c = 0, d = b.length; c < d; c++) for (var f = 0, e = 0, h = a.aiDisplay.length; e < h; e++) {
|
|
||||||
var j = a.aiDisplay[e - f];
|
|
||||||
if (!b[c](a, da(a, j, "filter"),
|
|
||||||
j)) {
|
|
||||||
a.aiDisplay.splice(e - f, 1); f++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function La(a, b, c, d, f) { if (b !== "") { var e = 0; b = la(b, d, f); for (d = a.aiDisplay.length - 1; d >= 0; d--) { f = ma(H(a, a.aiDisplay[d], c, "filter"), a.aoColumns[c].sType); if (!b.test(f)) { a.aiDisplay.splice(d, 1); e++ } } } } function Ka(a, b, c, d, f) {
|
|
||||||
var e = la(b, d, f); if (typeof c == "undefined" || c === null) c = 0; if (o.afnFiltering.length !== 0) c = 1; if (b.length <= 0) { a.aiDisplay.splice(0, a.aiDisplay.length); a.aiDisplay = a.aiDisplayMaster.slice() } else if (a.aiDisplay.length == a.aiDisplayMaster.length || a.oPreviousSearch.sSearch.length >
|
|
||||||
b.length || c == 1 || b.indexOf(a.oPreviousSearch.sSearch) !== 0) { a.aiDisplay.splice(0, a.aiDisplay.length); ka(a, 1); for (c = 0; c < a.aiDisplayMaster.length; c++) e.test(a.asDataSearch[c]) && a.aiDisplay.push(a.aiDisplayMaster[c]) } else { var h = 0; for (c = 0; c < a.asDataSearch.length; c++) if (!e.test(a.asDataSearch[c])) { a.aiDisplay.splice(c - h, 1); h++ } } a.oPreviousSearch.sSearch = b; a.oPreviousSearch.bRegex = d; a.oPreviousSearch.bSmart = f
|
|
||||||
} function ka(a, b) {
|
|
||||||
a.asDataSearch.splice(0, a.asDataSearch.length); b = typeof b != "undefined" && b == 1 ? a.aiDisplayMaster :
|
|
||||||
a.aiDisplay; for (var c = 0, d = b.length; c < d; c++) a.asDataSearch[c] = na(a, da(a, b[c], "filter"))
|
|
||||||
} function na(a, b) { var c = ""; if (typeof a.__nTmpFilter == "undefined") a.__nTmpFilter = p.createElement("div"); for (var d = a.__nTmpFilter, f = 0, e = a.aoColumns.length; f < e; f++) if (a.aoColumns[f].bSearchable) c += ma(b[f], a.aoColumns[f].sType) + " "; if (c.indexOf("&") !== -1) { d.innerHTML = c; c = d.textContent ? d.textContent : d.innerText; c = c.replace(/\n/g, " ").replace(/\r/g, "") } return c } function la(a, b, c) {
|
|
||||||
if (c) {
|
|
||||||
a = b ? a.split(" ") : oa(a).split(" ");
|
|
||||||
a = "^(?=.*?" + a.join(")(?=.*?") + ").*$"; return new RegExp(a, "i")
|
|
||||||
} else { a = b ? a : oa(a); return new RegExp(a, "i") }
|
|
||||||
} function ma(a, b) { if (typeof o.ofnSearch[b] == "function") return o.ofnSearch[b](a); else if (b == "html") return a.replace(/\n/g, " ").replace(/<.*?>/g, ""); else if (typeof a == "string") return a.replace(/\n/g, " "); else if (a === null) return ""; return a } function R(a, b) {
|
|
||||||
var c, d, f, e, h = [], j = [], k = o.oSort; d = a.aoData; var m = a.aoColumns; if (!a.oFeatures.bServerSide && (a.aaSorting.length !== 0 || a.aaSortingFixed !== null)) {
|
|
||||||
h = a.aaSortingFixed !==
|
|
||||||
null ? a.aaSortingFixed.concat(a.aaSorting) : a.aaSorting.slice(); for (c = 0; c < h.length; c++) { var t = h[c][0]; f = pa(a, t); e = a.aoColumns[t].sSortDataType; if (typeof o.afnSortData[e] != "undefined") { var q = o.afnSortData[e](a, t, f); f = 0; for (e = d.length; f < e; f++) N(a, f, t, q[f]) } } c = 0; for (d = a.aiDisplayMaster.length; c < d; c++) j[a.aiDisplayMaster[c]] = c; var I = h.length; a.aiDisplayMaster.sort(function (O, B) {
|
|
||||||
var F, qa; for (c = 0; c < I; c++) {
|
|
||||||
F = m[h[c][0]].iDataSort; qa = m[F].sType; F = k[(qa ? qa : "string") + "-" + h[c][1]](H(a, O, F, "sort"), H(a, B, F, "sort"));
|
|
||||||
if (F !== 0) return F
|
|
||||||
} return k["numeric-asc"](j[O], j[B])
|
|
||||||
})
|
|
||||||
} if ((typeof b == "undefined" || b) && !a.oFeatures.bDeferRender) T(a); a.bSorted = true; if (a.oFeatures.bFilter) M(a, a.oPreviousSearch, 1); else { a.aiDisplay = a.aiDisplayMaster.slice(); a._iDisplayStart = 0; E(a); C(a) }
|
|
||||||
} function ga(a, b, c, d) {
|
|
||||||
i(b).bind("click.DT", function (f) {
|
|
||||||
if (a.aoColumns[c].bSortable !== false) {
|
|
||||||
var e = function () {
|
|
||||||
var h, j; if (f.shiftKey) {
|
|
||||||
for (var k = false, m = 0; m < a.aaSorting.length; m++) if (a.aaSorting[m][0] == c) {
|
|
||||||
k = true; h = a.aaSorting[m][0]; j = a.aaSorting[m][2] +
|
|
||||||
1; if (typeof a.aoColumns[h].asSorting[j] == "undefined") a.aaSorting.splice(m, 1); else { a.aaSorting[m][1] = a.aoColumns[h].asSorting[j]; a.aaSorting[m][2] = j } break
|
|
||||||
} k === false && a.aaSorting.push([c, a.aoColumns[c].asSorting[0], 0])
|
|
||||||
} else if (a.aaSorting.length == 1 && a.aaSorting[0][0] == c) { h = a.aaSorting[0][0]; j = a.aaSorting[0][2] + 1; if (typeof a.aoColumns[h].asSorting[j] == "undefined") j = 0; a.aaSorting[0][1] = a.aoColumns[h].asSorting[j]; a.aaSorting[0][2] = j } else {
|
|
||||||
a.aaSorting.splice(0, a.aaSorting.length); a.aaSorting.push([c, a.aoColumns[c].asSorting[0],
|
|
||||||
0])
|
|
||||||
} R(a)
|
|
||||||
}; if (a.oFeatures.bProcessing) { K(a, true); setTimeout(function () { e(); a.oFeatures.bServerSide || K(a, false) }, 0) } else e(); typeof d == "function" && d(a)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} function T(a) {
|
|
||||||
var b, c, d, f, e, h = a.aoColumns.length, j = a.oClasses; for (b = 0; b < h; b++) a.aoColumns[b].bSortable && i(a.aoColumns[b].nTh).removeClass(j.sSortAsc + " " + j.sSortDesc + " " + a.aoColumns[b].sSortingClass); f = a.aaSortingFixed !== null ? a.aaSortingFixed.concat(a.aaSorting) : a.aaSorting.slice(); for (b = 0; b < a.aoColumns.length; b++) if (a.aoColumns[b].bSortable) {
|
|
||||||
e = a.aoColumns[b].sSortingClass;
|
|
||||||
d = -1; for (c = 0; c < f.length; c++) if (f[c][0] == b) { e = f[c][1] == "asc" ? j.sSortAsc : j.sSortDesc; d = c; break } i(a.aoColumns[b].nTh).addClass(e); if (a.bJUI) { c = i("span", a.aoColumns[b].nTh); c.removeClass(j.sSortJUIAsc + " " + j.sSortJUIDesc + " " + j.sSortJUI + " " + j.sSortJUIAscAllowed + " " + j.sSortJUIDescAllowed); c.addClass(d == -1 ? a.aoColumns[b].sSortingClassJUI : f[d][1] == "asc" ? j.sSortJUIAsc : j.sSortJUIDesc) }
|
|
||||||
} else i(a.aoColumns[b].nTh).addClass(a.aoColumns[b].sSortingClass); e = j.sSortColumn; if (a.oFeatures.bSort && a.oFeatures.bSortClasses) {
|
|
||||||
d =
|
|
||||||
Q(a); if (a.oFeatures.bDeferRender) i(d).removeClass(e + "1 " + e + "2 " + e + "3"); else if (d.length >= h) for (b = 0; b < h; b++) if (d[b].className.indexOf(e + "1") != -1) { c = 0; for (a = d.length / h; c < a; c++) d[h * c + b].className = i.trim(d[h * c + b].className.replace(e + "1", "")) } else if (d[b].className.indexOf(e + "2") != -1) { c = 0; for (a = d.length / h; c < a; c++) d[h * c + b].className = i.trim(d[h * c + b].className.replace(e + "2", "")) } else if (d[b].className.indexOf(e + "3") != -1) {
|
|
||||||
c = 0; for (a = d.length / h; c < a; c++) d[h * c + b].className = i.trim(d[h * c + b].className.replace(" " +
|
|
||||||
e + "3", ""))
|
|
||||||
} j = 1; var k; for (b = 0; b < f.length; b++) { k = parseInt(f[b][0], 10); c = 0; for (a = d.length / h; c < a; c++) d[h * c + k].className += " " + e + j; j < 3 && j++ }
|
|
||||||
}
|
|
||||||
} function Ha(a) { if (a.oScroll.bInfinite) return null; var b = p.createElement("div"); b.className = a.oClasses.sPaging + a.sPaginationType; o.oPagination[a.sPaginationType].fnInit(a, b, function (c) { E(c); C(c) }); typeof a.aanFeatures.p == "undefined" && a.aoDrawCallback.push({ fn: function (c) { o.oPagination[c.sPaginationType].fnUpdate(c, function (d) { E(d); C(d) }) }, sName: "pagination" }); return b }
|
|
||||||
function ja(a, b) {
|
|
||||||
var c = a._iDisplayStart; if (b == "first") a._iDisplayStart = 0; else if (b == "previous") { a._iDisplayStart = a._iDisplayLength >= 0 ? a._iDisplayStart - a._iDisplayLength : 0; if (a._iDisplayStart < 0) a._iDisplayStart = 0 } else if (b == "next") if (a._iDisplayLength >= 0) { if (a._iDisplayStart + a._iDisplayLength < a.fnRecordsDisplay()) a._iDisplayStart += a._iDisplayLength } else a._iDisplayStart = 0; else if (b == "last") if (a._iDisplayLength >= 0) { b = parseInt((a.fnRecordsDisplay() - 1) / a._iDisplayLength, 10) + 1; a._iDisplayStart = (b - 1) * a._iDisplayLength } else a._iDisplayStart =
|
|
||||||
0; else J(a, 0, "Unknown paging action: " + b); return c != a._iDisplayStart
|
|
||||||
} function Ga(a) { var b = p.createElement("div"); b.className = a.oClasses.sInfo; if (typeof a.aanFeatures.i == "undefined") { a.aoDrawCallback.push({ fn: Na, sName: "information" }); a.sTableId !== "" && b.setAttribute("id", a.sTableId + "_info") } return b } function Na(a) {
|
|
||||||
if (!(!a.oFeatures.bInfo || a.aanFeatures.i.length === 0)) {
|
|
||||||
var b = a._iDisplayStart + 1, c = a.fnDisplayEnd(), d = a.fnRecordsTotal(), f = a.fnRecordsDisplay(), e = a.fnFormatNumber(b), h = a.fnFormatNumber(c), j =
|
|
||||||
a.fnFormatNumber(d), k = a.fnFormatNumber(f); if (a.oScroll.bInfinite) e = a.fnFormatNumber(1); e = a.fnRecordsDisplay() === 0 && a.fnRecordsDisplay() == a.fnRecordsTotal() ? a.oLanguage.sInfoEmpty + a.oLanguage.sInfoPostFix : a.fnRecordsDisplay() === 0 ? a.oLanguage.sInfoEmpty + " " + a.oLanguage.sInfoFiltered.replace("_MAX_", j) + a.oLanguage.sInfoPostFix : a.fnRecordsDisplay() == a.fnRecordsTotal() ? a.oLanguage.sInfo.replace("_START_", e).replace("_END_", h).replace("_TOTAL_", k) + a.oLanguage.sInfoPostFix : a.oLanguage.sInfo.replace("_START_",
|
|
||||||
e).replace("_END_", h).replace("_TOTAL_", k) + " " + a.oLanguage.sInfoFiltered.replace("_MAX_", a.fnFormatNumber(a.fnRecordsTotal())) + a.oLanguage.sInfoPostFix; if (a.oLanguage.fnInfoCallback !== null) e = a.oLanguage.fnInfoCallback(a, b, c, d, f, e); a = a.aanFeatures.i; b = 0; for (c = a.length; b < c; b++) i(a[b]).html(e)
|
|
||||||
}
|
|
||||||
} function Ca(a) {
|
|
||||||
if (a.oScroll.bInfinite) return null; var b = '<select size="1" ' + (a.sTableId === "" ? "" : 'name="' + a.sTableId + '_length"') + ">", c, d; if (a.aLengthMenu.length == 2 && typeof a.aLengthMenu[0] == "object" && typeof a.aLengthMenu[1] ==
|
|
||||||
"object") { c = 0; for (d = a.aLengthMenu[0].length; c < d; c++) b += '<option value="' + a.aLengthMenu[0][c] + '">' + a.aLengthMenu[1][c] + "</option>" } else { c = 0; for (d = a.aLengthMenu.length; c < d; c++) b += '<option value="' + a.aLengthMenu[c] + '">' + a.aLengthMenu[c] + "</option>" } b += "</select>"; var f = p.createElement("div"); a.sTableId !== "" && typeof a.aanFeatures.l == "undefined" && f.setAttribute("id", a.sTableId + "_length"); f.className = a.oClasses.sLength; f.innerHTML = "<label>" + a.oLanguage.sLengthMenu.replace("_MENU_", b) + "</label>"; i('select option[value="' +
|
|
||||||
a._iDisplayLength + '"]', f).attr("selected", true); i("select", f).bind("change.DT", function () { var e = i(this).val(), h = a.aanFeatures.l; c = 0; for (d = h.length; c < d; c++) h[c] != this.parentNode && i("select", h[c]).val(e); a._iDisplayLength = parseInt(e, 10); E(a); if (a.fnDisplayEnd() == a.fnRecordsDisplay()) { a._iDisplayStart = a.fnDisplayEnd() - a._iDisplayLength; if (a._iDisplayStart < 0) a._iDisplayStart = 0 } if (a._iDisplayLength == -1) a._iDisplayStart = 0; C(a) }); return f
|
|
||||||
} function Ea(a) {
|
|
||||||
var b = p.createElement("div"); a.sTableId !== "" && typeof a.aanFeatures.r ==
|
|
||||||
"undefined" && b.setAttribute("id", a.sTableId + "_processing"); b.innerHTML = a.oLanguage.sProcessing; b.className = a.oClasses.sProcessing; a.nTable.parentNode.insertBefore(b, a.nTable); return b
|
|
||||||
} function K(a, b) { if (a.oFeatures.bProcessing) { a = a.aanFeatures.r; for (var c = 0, d = a.length; c < d; c++) a[c].style.visibility = b ? "visible" : "hidden" } } function Ja(a, b) { for (var c = -1, d = 0; d < a.aoColumns.length; d++) { a.aoColumns[d].bVisible === true && c++; if (c == b) return d } return null } function pa(a, b) {
|
|
||||||
for (var c = -1, d = 0; d < a.aoColumns.length; d++) {
|
|
||||||
a.aoColumns[d].bVisible ===
|
|
||||||
true && c++; if (d == b) return a.aoColumns[d].bVisible === true ? c : null
|
|
||||||
} return null
|
|
||||||
} function U(a, b) { var c, d; c = a._iDisplayStart; for (d = a._iDisplayEnd; c < d; c++) if (a.aoData[a.aiDisplay[c]].nTr == b) return a.aiDisplay[c]; c = 0; for (d = a.aoData.length; c < d; c++) if (a.aoData[c].nTr == b) return c; return null } function X(a) { for (var b = 0, c = 0; c < a.aoColumns.length; c++) a.aoColumns[c].bVisible === true && b++; return b } function E(a) {
|
|
||||||
a._iDisplayEnd = a.oFeatures.bPaginate === false ? a.aiDisplay.length : a._iDisplayStart + a._iDisplayLength > a.aiDisplay.length ||
|
|
||||||
a._iDisplayLength == -1 ? a.aiDisplay.length : a._iDisplayStart + a._iDisplayLength
|
|
||||||
} function Oa(a, b) { if (!a || a === null || a === "") return 0; if (typeof b == "undefined") b = p.getElementsByTagName("body")[0]; var c = p.createElement("div"); c.style.width = u(a); b.appendChild(c); a = c.offsetWidth; b.removeChild(c); return a } function ea(a) {
|
|
||||||
var b = 0, c, d = 0, f = a.aoColumns.length, e, h = i("th", a.nTHead); for (e = 0; e < f; e++) if (a.aoColumns[e].bVisible) {
|
|
||||||
d++; if (a.aoColumns[e].sWidth !== null) {
|
|
||||||
c = Oa(a.aoColumns[e].sWidthOrig, a.nTable.parentNode); if (c !==
|
|
||||||
null) a.aoColumns[e].sWidth = u(c); b++
|
|
||||||
}
|
|
||||||
} if (f == h.length && b === 0 && d == f && a.oScroll.sX === "" && a.oScroll.sY === "") for (e = 0; e < a.aoColumns.length; e++) { c = i(h[e]).width(); if (c !== null) a.aoColumns[e].sWidth = u(c) } else {
|
|
||||||
b = a.nTable.cloneNode(false); e = a.nTHead.cloneNode(true); d = p.createElement("tbody"); c = p.createElement("tr"); b.removeAttribute("id"); b.appendChild(e); if (a.nTFoot !== null) { b.appendChild(a.nTFoot.cloneNode(true)); P(function (k) { k.style.width = "" }, b.getElementsByTagName("tr")) } b.appendChild(d); d.appendChild(c);
|
|
||||||
d = i("thead th", b); if (d.length === 0) d = i("tbody tr:eq(0)>td", b); h = S(a, e); for (e = d = 0; e < f; e++) { var j = a.aoColumns[e]; if (j.bVisible && j.sWidthOrig !== null && j.sWidthOrig !== "") h[e - d].style.width = u(j.sWidthOrig); else if (j.bVisible) h[e - d].style.width = ""; else d++ } for (e = 0; e < f; e++) if (a.aoColumns[e].bVisible) { d = Pa(a, e); if (d !== null) { d = d.cloneNode(true); if (a.aoColumns[e].sContentPadding !== "") d.innerHTML += a.aoColumns[e].sContentPadding; c.appendChild(d) } } f = a.nTable.parentNode; f.appendChild(b); if (a.oScroll.sX !== "" && a.oScroll.sXInner !==
|
|
||||||
"") b.style.width = u(a.oScroll.sXInner); else if (a.oScroll.sX !== "") { b.style.width = ""; if (i(b).width() < f.offsetWidth) b.style.width = u(f.offsetWidth) } else if (a.oScroll.sY !== "") b.style.width = u(f.offsetWidth); b.style.visibility = "hidden"; Qa(a, b); f = i("tbody tr:eq(0)", b).children(); if (f.length === 0) f = S(a, i("thead", b)[0]); if (a.oScroll.sX !== "") {
|
|
||||||
for (e = d = c = 0; e < a.aoColumns.length; e++) if (a.aoColumns[e].bVisible) {
|
|
||||||
c += a.aoColumns[e].sWidthOrig === null ? i(f[d]).outerWidth() : parseInt(a.aoColumns[e].sWidth.replace("px", ""),
|
|
||||||
10) + (i(f[d]).outerWidth() - i(f[d]).width()); d++
|
|
||||||
} b.style.width = u(c); a.nTable.style.width = u(c)
|
|
||||||
} for (e = d = 0; e < a.aoColumns.length; e++) if (a.aoColumns[e].bVisible) { c = i(f[d]).width(); if (c !== null && c > 0) a.aoColumns[e].sWidth = u(c); d++ } a.nTable.style.width = u(i(b).outerWidth()); b.parentNode.removeChild(b)
|
|
||||||
}
|
|
||||||
} function Qa(a, b) { if (a.oScroll.sX === "" && a.oScroll.sY !== "") { i(b).width(); b.style.width = u(i(b).outerWidth() - a.oScroll.iBarWidth) } else if (a.oScroll.sX !== "") b.style.width = u(i(b).outerWidth()) } function Pa(a, b) {
|
|
||||||
var c =
|
|
||||||
Ra(a, b); if (c < 0) return null; if (a.aoData[c].nTr === null) { var d = p.createElement("td"); d.innerHTML = H(a, c, b, ""); return d } return Q(a, c)[b]
|
|
||||||
} function Ra(a, b) { for (var c = -1, d = -1, f = 0; f < a.aoData.length; f++) { var e = H(a, f, b, "display") + ""; e = e.replace(/<.*?>/g, ""); if (e.length > c) { c = e.length; d = f } } return d } function u(a) { if (a === null) return "0px"; if (typeof a == "number") { if (a < 0) return "0px"; return a + "px" } var b = a.charCodeAt(a.length - 1); if (b < 48 || b > 57) return a; return a + "px" } function Va(a, b) {
|
|
||||||
if (a.length != b.length) return 1; for (var c =
|
|
||||||
0; c < a.length; c++) if (a[c] != b[c]) return 2; return 0
|
|
||||||
} function fa(a) { for (var b = o.aTypes, c = b.length, d = 0; d < c; d++) { var f = b[d](a); if (f !== null) return f } return "string" } function A(a) { for (var b = 0; b < D.length; b++) if (D[b].nTable == a) return D[b]; return null } function aa(a) { for (var b = [], c = a.aoData.length, d = 0; d < c; d++) b.push(a.aoData[d]._aData); return b } function $(a) { for (var b = [], c = 0, d = a.aoData.length; c < d; c++) a.aoData[c].nTr !== null && b.push(a.aoData[c].nTr); return b } function Q(a, b) {
|
|
||||||
var c = [], d, f, e, h, j; f = 0; var k = a.aoData.length;
|
|
||||||
if (typeof b != "undefined") { f = b; k = b + 1 } for (f = f; f < k; f++) { j = a.aoData[f]; if (j.nTr !== null) { b = []; e = 0; for (h = j.nTr.childNodes.length; e < h; e++) { d = j.nTr.childNodes[e].nodeName.toLowerCase(); if (d == "td" || d == "th") b.push(j.nTr.childNodes[e]) } e = d = 0; for (h = a.aoColumns.length; e < h; e++) if (a.aoColumns[e].bVisible) c.push(b[e - d]); else { c.push(j._anHidden[e]); d++ } } } return c
|
|
||||||
} function oa(a) { return a.replace(new RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^)", "g"), "\\$1") } function ra(a, b) {
|
|
||||||
for (var c = -1, d =
|
|
||||||
0, f = a.length; d < f; d++) if (a[d] == b) c = d; else a[d] > b && a[d]--; c != -1 && a.splice(c, 1)
|
|
||||||
} function Ba(a, b) { b = b.split(","); for (var c = [], d = 0, f = a.aoColumns.length; d < f; d++) for (var e = 0; e < f; e++) if (a.aoColumns[d].sName == b[e]) { c.push(e); break } return c } function ha(a) { for (var b = "", c = 0, d = a.aoColumns.length; c < d; c++) b += a.aoColumns[c].sName + ","; if (b.length == d) return ""; return b.slice(0, -1) } function J(a, b, c) {
|
|
||||||
a = a.sTableId === "" ? "DataTables warning: " + c : "DataTables warning (table id = '" + a.sTableId + "'): " + c; if (b === 0) if (o.sErrMode ==
|
|
||||||
"alert") alert(a); else throw a; else typeof console != "undefined" && typeof console.log != "undefined" && console.log(a)
|
|
||||||
} function ia(a) { a.aoData.splice(0, a.aoData.length); a.aiDisplayMaster.splice(0, a.aiDisplayMaster.length); a.aiDisplay.splice(0, a.aiDisplay.length); E(a) } function sa(a) {
|
|
||||||
if (!(!a.oFeatures.bStateSave || typeof a.bDestroying != "undefined")) {
|
|
||||||
var b, c, d, f = "{"; f += '"iCreate":' + (new Date).getTime() + ","; f += '"iStart":' + (a.oScroll.bInfinite ? 0 : a._iDisplayStart) + ","; f += '"iEnd":' + (a.oScroll.bInfinite ? a._iDisplayLength :
|
|
||||||
a._iDisplayEnd) + ","; f += '"iLength":' + a._iDisplayLength + ","; f += '"sFilter":"' + encodeURIComponent(a.oPreviousSearch.sSearch) + '",'; f += '"sFilterEsc":' + !a.oPreviousSearch.bRegex + ","; f += '"aaSorting":[ '; for (b = 0; b < a.aaSorting.length; b++) f += "[" + a.aaSorting[b][0] + ',"' + a.aaSorting[b][1] + '"],'; f = f.substring(0, f.length - 1); f += "],"; f += '"aaSearchCols":[ '; for (b = 0; b < a.aoPreSearchCols.length; b++) f += '["' + encodeURIComponent(a.aoPreSearchCols[b].sSearch) + '",' + !a.aoPreSearchCols[b].bRegex + "],"; f = f.substring(0, f.length -
|
|
||||||
1); f += "],"; f += '"abVisCols":[ '; for (b = 0; b < a.aoColumns.length; b++) f += a.aoColumns[b].bVisible + ","; f = f.substring(0, f.length - 1); f += "]"; b = 0; for (c = a.aoStateSave.length; b < c; b++) { d = a.aoStateSave[b].fn(a, f); if (d !== "") f = d } f += "}"; Sa(a.sCookiePrefix + a.sInstance, f, a.iCookieDuration, a.sCookiePrefix, a.fnCookieCallback)
|
|
||||||
}
|
|
||||||
} function Ta(a, b) {
|
|
||||||
if (a.oFeatures.bStateSave) {
|
|
||||||
var c, d, f; d = ta(a.sCookiePrefix + a.sInstance); if (d !== null && d !== "") {
|
|
||||||
try { c = typeof i.parseJSON == "function" ? i.parseJSON(d.replace(/'/g, '"')) : eval("(" + d + ")") } catch (e) { return } d =
|
|
||||||
0; for (f = a.aoStateLoad.length; d < f; d++) if (!a.aoStateLoad[d].fn(a, c)) return; a.oLoadedState = i.extend(true, {}, c); a._iDisplayStart = c.iStart; a.iInitDisplayStart = c.iStart; a._iDisplayEnd = c.iEnd; a._iDisplayLength = c.iLength; a.oPreviousSearch.sSearch = decodeURIComponent(c.sFilter); a.aaSorting = c.aaSorting.slice(); a.saved_aaSorting = c.aaSorting.slice(); if (typeof c.sFilterEsc != "undefined") a.oPreviousSearch.bRegex = !c.sFilterEsc; if (typeof c.aaSearchCols != "undefined") for (d = 0; d < c.aaSearchCols.length; d++) a.aoPreSearchCols[d] =
|
|
||||||
{ sSearch: decodeURIComponent(c.aaSearchCols[d][0]), bRegex: !c.aaSearchCols[d][1] }; if (typeof c.abVisCols != "undefined") { b.saved_aoColumns = []; for (d = 0; d < c.abVisCols.length; d++) { b.saved_aoColumns[d] = {}; b.saved_aoColumns[d].bVisible = c.abVisCols[d] } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} function Sa(a, b, c, d, f) {
|
|
||||||
var e = new Date; e.setTime(e.getTime() + c * 1E3); c = wa.location.pathname.split("/"); a = a + "_" + c.pop().replace(/[\/:]/g, "").toLowerCase(); var h; if (f !== null) {
|
|
||||||
h = typeof i.parseJSON == "function" ? i.parseJSON(b) : eval("(" + b + ")"); b = f(a, h, e.toGMTString(),
|
|
||||||
c.join("/") + "/")
|
|
||||||
} else b = a + "=" + encodeURIComponent(b) + "; expires=" + e.toGMTString() + "; path=" + c.join("/") + "/"; f = ""; e = 9999999999999; if ((ta(a) !== null ? p.cookie.length : b.length + p.cookie.length) + 10 > 4096) {
|
|
||||||
a = p.cookie.split(";"); for (var j = 0, k = a.length; j < k; j++) if (a[j].indexOf(d) != -1) { var m = a[j].split("="); try { h = eval("(" + decodeURIComponent(m[1]) + ")") } catch (t) { continue } if (typeof h.iCreate != "undefined" && h.iCreate < e) { f = m[0]; e = h.iCreate } } if (f !== "") p.cookie = f + "=; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=" + c.join("/") +
|
|
||||||
"/"
|
|
||||||
} p.cookie = b
|
|
||||||
} function ta(a) { var b = wa.location.pathname.split("/"); a = a + "_" + b[b.length - 1].replace(/[\/:]/g, "").toLowerCase() + "="; b = p.cookie.split(";"); for (var c = 0; c < b.length; c++) { for (var d = b[c]; d.charAt(0) == " "; ) d = d.substring(1, d.length); if (d.indexOf(a) === 0) return decodeURIComponent(d.substring(a.length, d.length)) } return null } function W(a, b) {
|
|
||||||
b = b.getElementsByTagName("tr"); var c, d, f, e, h, j, k, m, t = function (O, B, F) { for (; typeof O[B][F] != "undefined"; ) F++; return F }; a.splice(0, a.length); d = 0; for (j = b.length; d <
|
|
||||||
j; d++) a.push([]); d = 0; for (j = b.length; d < j; d++) { f = 0; for (k = b[d].childNodes.length; f < k; f++) { c = b[d].childNodes[f]; if (c.nodeName.toUpperCase() == "TD" || c.nodeName.toUpperCase() == "TH") { var q = c.getAttribute("colspan") * 1, I = c.getAttribute("rowspan") * 1; q = !q || q === 0 || q === 1 ? 1 : q; I = !I || I === 0 || I === 1 ? 1 : I; m = t(a, d, 0); for (h = 0; h < q; h++) for (e = 0; e < I; e++) { a[d + e][m + h] = { cell: c, unique: q == 1 ? true : false }; a[d + e].nTr = b[d] } } } }
|
|
||||||
} function S(a, b, c) {
|
|
||||||
var d = []; if (typeof c == "undefined") { c = a.aoHeader; if (typeof b != "undefined") { c = []; W(c, b) } } b = 0;
|
|
||||||
for (var f = c.length; b < f; b++) for (var e = 0, h = c[b].length; e < h; e++) if (c[b][e].unique && (typeof d[e] == "undefined" || !a.bSortCellsTop)) d[e] = c[b][e].cell; return d
|
|
||||||
} function Ua() {
|
|
||||||
var a = p.createElement("p"), b = a.style; b.width = "100%"; b.height = "200px"; var c = p.createElement("div"); b = c.style; b.position = "absolute"; b.top = "0px"; b.left = "0px"; b.visibility = "hidden"; b.width = "200px"; b.height = "150px"; b.overflow = "hidden"; c.appendChild(a); p.body.appendChild(c); b = a.offsetWidth; c.style.overflow = "scroll"; a = a.offsetWidth; if (b == a) a =
|
|
||||||
c.clientWidth; p.body.removeChild(c); return b - a
|
|
||||||
} function P(a, b, c) { for (var d = 0, f = b.length; d < f; d++) for (var e = 0, h = b[d].childNodes.length; e < h; e++) if (b[d].childNodes[e].nodeType == 1) typeof c != "undefined" ? a(b[d].childNodes[e], c[d].childNodes[e]) : a(b[d].childNodes[e]) } function n(a, b, c, d) { if (typeof d == "undefined") d = c; if (typeof b[c] != "undefined") a[d] = b[c] } function da(a, b, c) { for (var d = [], f = 0, e = a.aoColumns.length; f < e; f++) d.push(H(a, b, f, c)); return d } function H(a, b, c, d) {
|
|
||||||
var f = a.aoColumns[c]; if ((c = f.fnGetData(a.aoData[b]._aData)) ===
|
|
||||||
undefined) { if (a.iDrawError != a.iDraw && f.sDefaultContent === null) { J(a, 0, "Requested unknown parameter '" + f.mDataProp + "' from the data source for row " + b); a.iDrawError = a.iDraw } return f.sDefaultContent } if (c === null && f.sDefaultContent !== null) c = f.sDefaultContent; if (d == "display" && c === null) return ""; return c
|
|
||||||
} function N(a, b, c, d) { a.aoColumns[c].fnSetData(a.aoData[b]._aData, d) } function Z(a) {
|
|
||||||
if (a === null) return function () { return null }; else if (typeof a == "function") return function (c) { return a(c) }; else if (typeof a ==
|
|
||||||
"string" && a.indexOf(".") != -1) { var b = a.split("."); return b.length == 2 ? function (c) { return c[b[0]][b[1]] } : b.length == 3 ? function (c) { return c[b[0]][b[1]][b[2]] } : function (c) { for (var d = 0, f = b.length; d < f; d++) c = c[b[d]]; return c } } else return function (c) { return c[a] }
|
|
||||||
} function ya(a) {
|
|
||||||
if (a === null) return function () { }; else if (typeof a == "function") return function (c, d) { return a(c, d) }; else if (typeof a == "string" && a.indexOf(".") != -1) {
|
|
||||||
var b = a.split("."); return b.length == 2 ? function (c, d) { c[b[0]][b[1]] = d } : b.length == 3 ? function (c,
|
|
||||||
d) { c[b[0]][b[1]][b[2]] = d } : function (c, d) { for (var f = 0, e = b.length - 1; f < e; f++) c = c[b[f]]; c[b[b.length - 1]] = d }
|
|
||||||
} else return function (c, d) { c[a] = d }
|
|
||||||
} this.oApi = {}; this.fnDraw = function (a) { var b = A(this[o.iApiIndex]); if (typeof a != "undefined" && a === false) { E(b); C(b) } else ba(b) }; this.fnFilter = function (a, b, c, d, f) {
|
|
||||||
var e = A(this[o.iApiIndex]); if (e.oFeatures.bFilter) {
|
|
||||||
if (typeof c == "undefined") c = false; if (typeof d == "undefined") d = true; if (typeof f == "undefined") f = true; if (typeof b == "undefined" || b === null) {
|
|
||||||
M(e, { sSearch: a, bRegex: c,
|
|
||||||
bSmart: d
|
|
||||||
}, 1); if (f && typeof e.aanFeatures.f != "undefined") { b = e.aanFeatures.f; c = 0; for (d = b.length; c < d; c++) i("input", b[c]).val(a) }
|
|
||||||
} else { e.aoPreSearchCols[b].sSearch = a; e.aoPreSearchCols[b].bRegex = c; e.aoPreSearchCols[b].bSmart = d; M(e, e.oPreviousSearch, 1) }
|
|
||||||
}
|
|
||||||
}; this.fnSettings = function () { return A(this[o.iApiIndex]) }; this.fnVersionCheck = o.fnVersionCheck; this.fnSort = function (a) { var b = A(this[o.iApiIndex]); b.aaSorting = a; R(b) }; this.fnSortListener = function (a, b, c) { ga(A(this[o.iApiIndex]), a, b, c) }; this.fnAddData = function (a,
|
|
||||||
b) { if (a.length === 0) return []; var c = [], d, f = A(this[o.iApiIndex]); if (typeof a[0] == "object") for (var e = 0; e < a.length; e++) { d = v(f, a[e]); if (d == -1) return c; c.push(d) } else { d = v(f, a); if (d == -1) return c; c.push(d) } f.aiDisplay = f.aiDisplayMaster.slice(); if (typeof b == "undefined" || b) ba(f); return c }; this.fnDeleteRow = function (a, b, c) {
|
|
||||||
var d = A(this[o.iApiIndex]); a = typeof a == "object" ? U(d, a) : a; var f = d.aoData.splice(a, 1), e = i.inArray(a, d.aiDisplay); d.asDataSearch.splice(e, 1); ra(d.aiDisplayMaster, a); ra(d.aiDisplay, a); typeof b ==
|
|
||||||
"function" && b.call(this, d, f); if (d._iDisplayStart >= d.aiDisplay.length) { d._iDisplayStart -= d._iDisplayLength; if (d._iDisplayStart < 0) d._iDisplayStart = 0 } if (typeof c == "undefined" || c) { E(d); C(d) } return f
|
|
||||||
}; this.fnClearTable = function (a) { var b = A(this[o.iApiIndex]); ia(b); if (typeof a == "undefined" || a) C(b) }; this.fnOpen = function (a, b, c) {
|
|
||||||
var d = A(this[o.iApiIndex]); this.fnClose(a); var f = p.createElement("tr"), e = p.createElement("td"); f.appendChild(e); e.className = c; e.colSpan = X(d); if (typeof b.jquery != "undefined" || typeof b ==
|
|
||||||
"object") e.appendChild(b); else e.innerHTML = b; b = i("tr", d.nTBody); i.inArray(a, b) != -1 && i(f).insertAfter(a); d.aoOpenRows.push({ nTr: f, nParent: a }); return f
|
|
||||||
}; this.fnClose = function (a) { for (var b = A(this[o.iApiIndex]), c = 0; c < b.aoOpenRows.length; c++) if (b.aoOpenRows[c].nParent == a) { (a = b.aoOpenRows[c].nTr.parentNode) && a.removeChild(b.aoOpenRows[c].nTr); b.aoOpenRows.splice(c, 1); return 0 } return 1 }; this.fnGetData = function (a, b) {
|
|
||||||
var c = A(this[o.iApiIndex]); if (typeof a != "undefined") {
|
|
||||||
a = typeof a == "object" ? U(c, a) : a; if (typeof b !=
|
|
||||||
"undefined") return H(c, a, b, ""); return typeof c.aoData[a] != "undefined" ? c.aoData[a]._aData : null
|
|
||||||
} return aa(c)
|
|
||||||
}; this.fnGetNodes = function (a) { var b = A(this[o.iApiIndex]); if (typeof a != "undefined") return typeof b.aoData[a] != "undefined" ? b.aoData[a].nTr : null; return $(b) }; this.fnGetPosition = function (a) { var b = A(this[o.iApiIndex]), c = a.nodeName.toUpperCase(); if (c == "TR") return U(b, a); else if (c == "TD" || c == "TH") { c = U(b, a.parentNode); for (var d = Q(b, c), f = 0; f < b.aoColumns.length; f++) if (d[f] == a) return [c, pa(b, f), f] } return null };
|
|
||||||
this.fnUpdate = function (a, b, c, d, f) {
|
|
||||||
var e = A(this[o.iApiIndex]); b = typeof b == "object" ? U(e, b) : b; if (i.isArray(a) && typeof a == "object") { e.aoData[b]._aData = a.slice(); for (c = 0; c < e.aoColumns.length; c++) this.fnUpdate(H(e, b, c), b, c, false, false) } else if (typeof a == "object") { e.aoData[b]._aData = i.extend(true, {}, a); for (c = 0; c < e.aoColumns.length; c++) this.fnUpdate(H(e, b, c), b, c, false, false) } else {
|
|
||||||
a = a; N(e, b, c, a); if (e.aoColumns[c].fnRender !== null) {
|
|
||||||
a = e.aoColumns[c].fnRender({ iDataRow: b, iDataColumn: c, aData: e.aoData[b]._aData,
|
|
||||||
oSettings: e
|
|
||||||
}); e.aoColumns[c].bUseRendered && N(e, b, c, a)
|
|
||||||
} if (e.aoData[b].nTr !== null) Q(e, b)[c].innerHTML = a
|
|
||||||
} c = i.inArray(b, e.aiDisplay); e.asDataSearch[c] = na(e, da(e, b, "filter")); if (typeof f == "undefined" || f) ca(e); if (typeof d == "undefined" || d) ba(e); return 0
|
|
||||||
}; this.fnSetColumnVis = function (a, b, c) {
|
|
||||||
var d = A(this[o.iApiIndex]), f, e; e = d.aoColumns.length; var h, j; if (d.aoColumns[a].bVisible != b) {
|
|
||||||
if (b) {
|
|
||||||
for (f = j = 0; f < a; f++) d.aoColumns[f].bVisible && j++; j = j >= X(d); if (!j) for (f = a; f < e; f++) if (d.aoColumns[f].bVisible) { h = f; break } f = 0;
|
|
||||||
for (e = d.aoData.length; f < e; f++) if (d.aoData[f].nTr !== null) j ? d.aoData[f].nTr.appendChild(d.aoData[f]._anHidden[a]) : d.aoData[f].nTr.insertBefore(d.aoData[f]._anHidden[a], Q(d, f)[h])
|
|
||||||
} else { f = 0; for (e = d.aoData.length; f < e; f++) if (d.aoData[f].nTr !== null) { h = Q(d, f)[a]; d.aoData[f]._anHidden[a] = h; h.parentNode.removeChild(h) } } d.aoColumns[a].bVisible = b; L(d, d.aoHeader); d.nTFoot && L(d, d.aoFooter); f = 0; for (e = d.aoOpenRows.length; f < e; f++) d.aoOpenRows[f].nTr.colSpan = X(d); if (typeof c == "undefined" || c) { ca(d); C(d) } sa(d)
|
|
||||||
}
|
|
||||||
}; this.fnPageChange =
|
|
||||||
function (a, b) { var c = A(this[o.iApiIndex]); ja(c, a); E(c); if (typeof b == "undefined" || b) C(c) }; this.fnDestroy = function () {
|
|
||||||
var a = A(this[o.iApiIndex]), b = a.nTableWrapper.parentNode, c = a.nTBody, d, f; a.bDestroying = true; d = 0; for (f = a.aoColumns.length; d < f; d++) a.aoColumns[d].bVisible === false && this.fnSetColumnVis(d, true); i(a.nTableWrapper).find("*").andSelf().unbind(".DT"); i("tbody>tr>td." + a.oClasses.sRowEmpty, a.nTable).parent().remove(); if (a.nTable != a.nTHead.parentNode) { i(">thead", a.nTable).remove(); a.nTable.appendChild(a.nTHead) } if (a.nTFoot &&
|
|
||||||
a.nTable != a.nTFoot.parentNode) { i(">tfoot", a.nTable).remove(); a.nTable.appendChild(a.nTFoot) } a.nTable.parentNode.removeChild(a.nTable); i(a.nTableWrapper).remove(); a.aaSorting = []; a.aaSortingFixed = []; T(a); i($(a)).removeClass(a.asStripClasses.join(" ")); if (a.bJUI) {
|
|
||||||
i("th", a.nTHead).removeClass([o.oStdClasses.sSortable, o.oJUIClasses.sSortableAsc, o.oJUIClasses.sSortableDesc, o.oJUIClasses.sSortableNone].join(" ")); i("th span." + o.oJUIClasses.sSortIcon, a.nTHead).remove(); i("th", a.nTHead).each(function () {
|
|
||||||
var e =
|
|
||||||
i("div." + o.oJUIClasses.sSortJUIWrapper, this), h = e.contents(); i(this).append(h); e.remove()
|
|
||||||
})
|
|
||||||
} else i("th", a.nTHead).removeClass([o.oStdClasses.sSortable, o.oStdClasses.sSortableAsc, o.oStdClasses.sSortableDesc, o.oStdClasses.sSortableNone].join(" ")); a.nTableReinsertBefore ? b.insertBefore(a.nTable, a.nTableReinsertBefore) : b.appendChild(a.nTable); d = 0; for (f = a.aoData.length; d < f; d++) a.aoData[d].nTr !== null && c.appendChild(a.aoData[d].nTr); if (a.oFeatures.bAutoWidth === true) a.nTable.style.width = u(a.sDestroyWidth);
|
|
||||||
i(">tr:even", c).addClass(a.asDestoryStrips[0]); i(">tr:odd", c).addClass(a.asDestoryStrips[1]); d = 0; for (f = D.length; d < f; d++) D[d] == a && D.splice(d, 1); a = null
|
|
||||||
};
|
|
||||||
this.fnAdjustColumnSizing = function (a) { var b = A(this[o.iApiIndex]); ca(b); if (typeof a == "undefined" || a) this.fnDraw(false); else if (b.oScroll.sX !== "" || b.oScroll.sY !== "") this.oApi._fnScrollDraw(b) }; for (var ua in o.oApi) if (ua) this[ua] = r(ua); this.oApi._fnExternApiFunc = r; this.oApi._fnInitalise = s; this.oApi._fnInitComplete = w; this.oApi._fnLanguageProcess = y; this.oApi._fnAddColumn =
|
|
||||||
G;
|
|
||||||
this.oApi._fnColumnOptions = x; this.oApi._fnAddData = v; this.oApi._fnCreateTr = z; this.oApi._fnGatherData = Y; this.oApi._fnBuildHead = V; this.oApi._fnDrawHead = L; this.oApi._fnDraw = C; this.oApi._fnReDraw = ba; this.oApi._fnAjaxUpdate = za; this.oApi._fnAjaxUpdateDraw = Aa; this.oApi._fnAddOptionsHtml = xa; this.oApi._fnFeatureHtmlTable = Fa; this.oApi._fnScrollDraw = Ia; this.oApi._fnAjustColumnSizing = ca; this.oApi._fnFeatureHtmlFilter = Da; this.oApi._fnFilterComplete = M; this.oApi._fnFilterCustom = Ma; this.oApi._fnFilterColumn = La;
|
|
||||||
this.oApi._fnFilter = Ka; this.oApi._fnBuildSearchArray = ka; this.oApi._fnBuildSearchRow = na; this.oApi._fnFilterCreateSearch = la; this.oApi._fnDataToSearch = ma; this.oApi._fnSort = R; this.oApi._fnSortAttachListener = ga; this.oApi._fnSortingClasses = T; this.oApi._fnFeatureHtmlPaginate = Ha; this.oApi._fnPageChange = ja; this.oApi._fnFeatureHtmlInfo = Ga; this.oApi._fnUpdateInfo = Na; this.oApi._fnFeatureHtmlLength = Ca; this.oApi._fnFeatureHtmlProcessing = Ea; this.oApi._fnProcessingDisplay = K; this.oApi._fnVisibleToColumnIndex = Ja; this.oApi._fnColumnIndexToVisible =
|
|
||||||
pa;
|
|
||||||
this.oApi._fnNodeToDataIndex = U;
|
|
||||||
this.oApi._fnVisbleColumns = X; this.oApi._fnCalculateEnd = E; this.oApi._fnConvertToWidth = Oa; this.oApi._fnCalculateColumnWidths = ea; this.oApi._fnScrollingWidthAdjust = Qa; this.oApi._fnGetWidestNode = Pa; this.oApi._fnGetMaxLenString = Ra; this.oApi._fnStringToCss = u; this.oApi._fnArrayCmp = Va; this.oApi._fnDetectType = fa; this.oApi._fnSettingsFromNode = A; this.oApi._fnGetDataMaster = aa; this.oApi._fnGetTrNodes = $; this.oApi._fnGetTdNodes = Q; this.oApi._fnEscapeRegex = oa; this.oApi._fnDeleteIndex =
|
|
||||||
ra;
|
|
||||||
this.oApi._fnReOrderIndex = Ba;
|
|
||||||
this.oApi._fnColumnOrdering = ha;
|
|
||||||
this.oApi._fnLog = J;
|
|
||||||
this.oApi._fnClearTable = ia;
|
|
||||||
this.oApi._fnSaveState = sa;
|
|
||||||
this.oApi._fnLoadState = Ta;
|
|
||||||
this.oApi._fnCreateCookie = Sa;
|
|
||||||
this.oApi._fnReadCookie = ta;
|
|
||||||
this.oApi._fnDetectHeader = W; this.oApi._fnGetUniqueThs = S; this.oApi._fnScrollBarWidth = Ua; this.oApi._fnApplyToChildren = P; this.oApi._fnMap = n; this.oApi._fnGetRowData = da; this.oApi._fnGetCellData = H; this.oApi._fnSetCellData = N; this.oApi._fnGetObjectDataFn = Z; this.oApi._fnSetObjectDataFn = ya; var va =
|
|
||||||
this;
|
|
||||||
return this.each(function () {
|
|
||||||
var a = 0, b, c, d, f; a = 0; for (b = D.length; a < b; a++) {
|
|
||||||
if (D[a].nTable == this) if (typeof g == "undefined" || typeof g.bRetrieve != "undefined" && g.bRetrieve === true) return D[a].oInstance; else if (typeof g.bDestroy != "undefined" && g.bDestroy === true) { D[a].oInstance.fnDestroy(); break } else {
|
|
||||||
J(D[a], 0, "Cannot reinitialise DataTable.\n\nTo retrieve the DataTables object for this table, please pass either no arguments to the dataTable() function, or set bRetrieve to true. Alternatively, to destory the old table and create a new one, set bDestroy to true (note that a lot of changes to the configuration can be made through the API which is usually much faster).");
|
|
||||||
return
|
|
||||||
} if (D[a].sTableId !== "" && D[a].sTableId == this.getAttribute("id")) { D.splice(a, 1); break }
|
|
||||||
} var e = new l; D.push(e); var h = false, j = false; a = this.getAttribute("id"); if (a !== null) { e.sTableId = a; e.sInstance = a } else e.sInstance = o._oExternConfig.iNextUnique++; if (this.nodeName.toLowerCase() != "table") J(e, 0, "Attempted to initialise DataTables on a node which is not a table: " + this.nodeName); else {
|
|
||||||
e.nTable = this; e.oInstance = va.length == 1 ? va : i(this).dataTable(); e.oApi = va.oApi; e.sDestroyWidth = i(this).width(); if (typeof g !=
|
|
||||||
"undefined" && g !== null) {
|
|
||||||
e.oInit = g; n(e.oFeatures, g, "bPaginate");
|
|
||||||
n(e.oFeatures, g, "bLengthChange");
|
|
||||||
n(e.oFeatures, g, "bFilter");
|
|
||||||
n(e.oFeatures, g, "bSort");
|
|
||||||
n(e.oFeatures, g, "bInfo");
|
|
||||||
n(e.oFeatures, g, "bProcessing");
|
|
||||||
n(e.oFeatures, g, "bAutoWidth");
|
|
||||||
n(e.oFeatures, g, "bSortClasses");
|
|
||||||
n(e.oFeatures, g, "bServerSide");
|
|
||||||
n(e.oFeatures, g, "bDeferRender");
|
|
||||||
n(e.oScroll, g, "sScrollX", "sX");
|
|
||||||
n(e.oScroll, g, "sScrollXInner", "sXInner");
|
|
||||||
n(e.oScroll, g, "sScrollY", "sY");
|
|
||||||
n(e.oScroll, g, "bScrollCollapse", "bCollapse");
|
|
||||||
n(e.oScroll, g, "bScrollInfinite", "bInfinite");
|
|
||||||
n(e.oScroll, g, "iScrollLoadGap", "iLoadGap");
|
|
||||||
n(e.oScroll, g, "bScrollAutoCss", "bAutoCss");
|
|
||||||
n(e, g, "asStripClasses");
|
|
||||||
n(e, g, "fnPreDrawCallback");
|
|
||||||
n(e, g, "fnRowCallback"); n(e, g, "fnHeaderCallback"); n(e, g, "fnFooterCallback"); n(e, g, "fnCookieCallback"); n(e, g, "fnInitComplete"); n(e, g, "fnServerData"); n(e, g, "fnFormatNumber"); n(e, g, "aaSorting"); n(e, g, "aaSortingFixed"); n(e, g, "aLengthMenu"); n(e, g, "sPaginationType"); n(e, g, "sAjaxSource"); n(e, g, "sAjaxDataProp"); n(e, g, "iCookieDuration"); n(e, g, "sCookiePrefix");
|
|
||||||
n(e, g, "sDom");
|
|
||||||
n(e, g, "bSortCellsTop");
|
|
||||||
n(e, g, "oSearch", "oPreviousSearch");
|
|
||||||
n(e, g, "aoSearchCols", "aoPreSearchCols");
|
|
||||||
n(e, g, "iDisplayLength", "_iDisplayLength");
|
|
||||||
n(e, g, "bJQueryUI", "bJUI");
|
|
||||||
n(e.oLanguage, g, "fnInfoCallback");
|
|
||||||
typeof g.fnDrawCallback == "function" && e.aoDrawCallback.push({
|
|
||||||
fn: g.fnDrawCallback, sName: "user"
|
|
||||||
});
|
|
||||||
typeof g.fnStateSaveCallback == "function" && e.aoStateSave.push({
|
|
||||||
fn: g.fnStateSaveCallback, sName: "user"
|
|
||||||
});
|
|
||||||
typeof g.fnStateLoadCallback == "function" && e.aoStateLoad.push({
|
|
||||||
fn: g.fnStateLoadCallback, sName: "user"
|
|
||||||
});
|
|
||||||
if (e.oFeatures.bServerSide && e.oFeatures.bSort && e.oFeatures.bSortClasses) e.aoDrawCallback.push({ fn: T, sName: "server_side_sort_classes" }); else e.oFeatures.bDeferRender && e.aoDrawCallback.push({ fn: T, sName: "defer_sort_classes" }); if (typeof g.bJQueryUI != "undefined" && g.bJQueryUI) { e.oClasses = o.oJUIClasses; if (typeof g.sDom == "undefined") e.sDom = '<"H"lfr>t<"F"ip>' } if (e.oScroll.sX !== "" || e.oScroll.sY !== "") e.oScroll.iBarWidth = Ua(); if (typeof g.iDisplayStart != "undefined" && typeof e.iInitDisplayStart == "undefined") {
|
|
||||||
e.iInitDisplayStart = g.iDisplayStart; e._iDisplayStart = g.iDisplayStart
|
|
||||||
} if (typeof g.bStateSave != "undefined") { e.oFeatures.bStateSave = g.bStateSave; Ta(e, g); e.aoDrawCallback.push({ fn: sa, sName: "state_save" }) } if (typeof g.iDeferLoading != "undefined") { e.bDeferLoading = true; e._iRecordsTotal = g.iDeferLoading; e._iRecordsDisplay = g.iDeferLoading } if (typeof g.aaData != "undefined") j = true; if (typeof g != "undefined" && typeof g.aoData != "undefined") g.aoColumns = g.aoData; if (typeof g.oLanguage != "undefined") if (typeof g.oLanguage.sUrl != "undefined" && g.oLanguage.sUrl !==
|
|
||||||
"") { e.oLanguage.sUrl = g.oLanguage.sUrl; i.getJSON(e.oLanguage.sUrl, null, function (t) { y(e, t, true) }); h = true } else y(e, g.oLanguage, false)
|
|
||||||
} else g = {}; if (typeof g.asStripClasses == "undefined") { e.asStripClasses.push(e.oClasses.sStripOdd); e.asStripClasses.push(e.oClasses.sStripEven) } c = false; d = i(">tbody>tr", this); a = 0; for (b = e.asStripClasses.length; a < b; a++) if (d.filter(":lt(2)").hasClass(e.asStripClasses[a])) { c = true; break } if (c) {
|
|
||||||
e.asDestoryStrips = ["", ""]; if (i(d[0]).hasClass(e.oClasses.sStripOdd)) e.asDestoryStrips[0] +=
|
|
||||||
e.oClasses.sStripOdd + " "; if (i(d[0]).hasClass(e.oClasses.sStripEven)) e.asDestoryStrips[0] += e.oClasses.sStripEven; if (i(d[1]).hasClass(e.oClasses.sStripOdd)) e.asDestoryStrips[1] += e.oClasses.sStripOdd + " "; if (i(d[1]).hasClass(e.oClasses.sStripEven)) e.asDestoryStrips[1] += e.oClasses.sStripEven; d.removeClass(e.asStripClasses.join(" "))
|
|
||||||
} c = []; var k; a = this.getElementsByTagName("thead"); if (a.length !== 0) { W(e.aoHeader, a[0]); c = S(e) } if (typeof g.aoColumns == "undefined") { k = []; a = 0; for (b = c.length; a < b; a++) k.push(null) } else k =
|
|
||||||
g.aoColumns; a = 0; for (b = k.length; a < b; a++) { if (typeof g.saved_aoColumns != "undefined" && g.saved_aoColumns.length == b) { if (k[a] === null) k[a] = {}; k[a].bVisible = g.saved_aoColumns[a].bVisible } G(e, c ? c[a] : null) } if (typeof g.aoColumnDefs != "undefined") for (a = g.aoColumnDefs.length - 1; a >= 0; a--) {
|
|
||||||
var m = g.aoColumnDefs[a].aTargets; i.isArray(m) || J(e, 1, "aTargets must be an array of targets, not a " + typeof m); c = 0; for (d = m.length; c < d; c++) if (typeof m[c] == "number" && m[c] >= 0) { for (; e.aoColumns.length <= m[c]; ) G(e); x(e, m[c], g.aoColumnDefs[a]) } else if (typeof m[c] ==
|
|
||||||
"number" && m[c] < 0) x(e, e.aoColumns.length + m[c], g.aoColumnDefs[a]); else if (typeof m[c] == "string") { b = 0; for (f = e.aoColumns.length; b < f; b++) if (m[c] == "_all" || i(e.aoColumns[b].nTh).hasClass(m[c])) x(e, b, g.aoColumnDefs[a]) }
|
|
||||||
} if (typeof k != "undefined") { a = 0; for (b = k.length; a < b; a++) x(e, a, k[a]) } a = 0; for (b = e.aaSorting.length; a < b; a++) {
|
|
||||||
if (e.aaSorting[a][0] >= e.aoColumns.length)
|
|
||||||
e.aaSorting[a][0] = 0; k = e.aoColumns[e.aaSorting[a][0]];
|
|
||||||
if (typeof e.aaSorting[a][2] == "undefined") e.aaSorting[a][2] = 0;
|
|
||||||
if (typeof g.aaSorting == "undefined" && typeof e.saved_aaSorting == "undefined")
|
|
||||||
e.aaSorting[a][1] = k.asSorting[0];
|
|
||||||
c = 0; for (d = k.asSorting.length;
|
|
||||||
c < d; c++) if (e.aaSorting[a][1] == k.asSorting[c]) {
|
|
||||||
e.aaSorting[a][2] = c; break
|
|
||||||
}
|
|
||||||
} T(e); a = i(">thead", this); if (a.length === 0) {
|
|
||||||
a = [p.createElement("thead")]; this.appendChild(a[0])
|
|
||||||
} e.nTHead = a[0]; a = i(">tbody", this);
|
|
||||||
if (a.length === 0) {
|
|
||||||
a = [p.createElement("tbody")];
|
|
||||||
this.appendChild(a[0])
|
|
||||||
} e.nTBody = a[0]; a = i(">tfoot", this);
|
|
||||||
if (a.length > 0) { e.nTFoot = a[0]; W(e.aoFooter, e.nTFoot) }
|
|
||||||
if (j)
|
|
||||||
for (a = 0; a < g.aaData.length; a++) v(e, g.aaData[a]);
|
|
||||||
else Y(e); e.aiDisplay = e.aiDisplayMaster.slice(); e.bInitialised = true; h === false && s(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})(jQuery, window, document);
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
window.log = function(){
|
|
||||||
log.history = log.history || [];
|
|
||||||
log.history.push(arguments);
|
|
||||||
arguments.callee = arguments.callee.caller;
|
|
||||||
if(this.console) console.log( Array.prototype.slice.call(arguments) );
|
|
||||||
};
|
|
||||||
(function(b){function c(){}for(var d="assert,count,debug,dir,dirxml,error,exception,group,groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,time,timeEnd,trace,warn".split(","),a;a=d.pop();)b[a]=b[a]||c})(window.console=window.console||{});
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-string-asc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
var y = b.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-string-desc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
var y = b.match(/title="(.*?)"/)[1].toLowerCase();
|
|
||||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-numeric-asc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
var y = b.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
x = parseFloat( x );
|
|
||||||
y = parseFloat( y );
|
|
||||||
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
jQuery.fn.dataTableExt.oSort['title-numeric-desc'] = function(a,b) {
|
|
||||||
var x = a.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
var y = b.match(/title="*(-?[0-9]+)/)[1];
|
|
||||||
x = parseFloat( x );
|
|
||||||
y = parseFloat( y );
|
|
||||||
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
|
||||||
};
|
|
||||||
|
|
||||||
function toggle(source) {
|
|
||||||
checkboxes = document.getElementsByClassName('checkbox');
|
|
||||||
for(var i in checkboxes)
|
|
||||||
checkboxes[i].checked = source.checked;
|
|
||||||
}
|
|
||||||
@@ -67,32 +67,44 @@ function getInfo(elem,id,type) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageLinks(elem,id,type) {
|
function getImageLinks(elem,id,type,unveil) {
|
||||||
|
|
||||||
if ( type == 'artist' ) {
|
if ( type == 'artist' ) {
|
||||||
var infoURL = "getImageLinks?ArtistID=" + id;
|
var infoURL = "getImageLinks?ArtistID=" + id;
|
||||||
} else {
|
} else {
|
||||||
var infoURL = "getImageLinks?AlbumID=" + id;
|
var infoURL = "getImageLinks?AlbumID=" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Data from the cache by ID
|
// Get Data from the cache by ID
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: infoURL,
|
url: infoURL,
|
||||||
cache: true,
|
cache: true,
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function(data){
|
success: function(data){
|
||||||
if ( data.thumbnail == "" || data.thumbnail == undefined ) {
|
if (!data) {
|
||||||
|
// Invalid response
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.thumbnail) {
|
||||||
var thumbnail = "interfaces/default/images/no-cover-artist.png";
|
var thumbnail = "interfaces/default/images/no-cover-artist.png";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var thumbnail = data.thumbnail;
|
var thumbnail = data.thumbnail;
|
||||||
}
|
}
|
||||||
if ( data.artwork == "" || data.artwork == undefined ) {
|
if (!data.artwork) {
|
||||||
var artwork = "interfaces/default/images/no-cover-artist.png";
|
var artwork = "interfaces/default/images/no-cover-artist.png";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var artwork = data.artwork;
|
var artwork = data.artwork;
|
||||||
}
|
}
|
||||||
$(elem).attr("src", thumbnail);
|
|
||||||
|
if (unveil) {
|
||||||
|
$(elem).attr("data-src", thumbnail);
|
||||||
|
$(elem).unveil();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(elem).attr("src", thumbnail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -228,6 +240,21 @@ function showMsg(msg,loader,timeout,ms) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showArtistMsg(msg) {
|
||||||
|
var feedback = $("#ajaxMsg2");
|
||||||
|
update = $("#updatebar");
|
||||||
|
if ( update.is(":visible") ) {
|
||||||
|
var height = update.height() + 35;
|
||||||
|
feedback.css("bottom",height + "px");
|
||||||
|
} else {
|
||||||
|
feedback.removeAttr("style");
|
||||||
|
}
|
||||||
|
feedback.fadeIn();
|
||||||
|
var message = $("<i class='fa fa-refresh fa-spin'></i> " + msg + "</div>");
|
||||||
|
feedback.css("padding","14px 10px")
|
||||||
|
$(feedback).prepend(message);
|
||||||
|
}
|
||||||
|
|
||||||
function doAjaxCall(url,elem,reload,form) {
|
function doAjaxCall(url,elem,reload,form) {
|
||||||
// Set Message
|
// Set Message
|
||||||
feedback = $("#ajaxMsg");
|
feedback = $("#ajaxMsg");
|
||||||
@@ -334,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() {
|
||||||
@@ -355,11 +380,6 @@ function initFancybox() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
|
||||||
initHeader();
|
|
||||||
preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
init();
|
initHeader();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
<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()">
|
||||||
|
|||||||
@@ -11,36 +11,49 @@
|
|||||||
<th id="albumart"></th>
|
<th id="albumart"></th>
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<th id="albumname">Album Name</th>
|
<th id="albumname">Album Name</th>
|
||||||
|
<th id="artistnamesmall">Artist Name</th>
|
||||||
|
<th id="format">Format</th>
|
||||||
|
<th id="tracks">Tracks</th>
|
||||||
|
<th id="reldate">Date</th>
|
||||||
|
<th id="scoresmall">Score</th>
|
||||||
|
%else:
|
||||||
|
<th id="artistname">Artist Name</th>
|
||||||
|
<th id="score">Score</th>
|
||||||
%endif
|
%endif
|
||||||
<th id="artistname">Artist Name</th>
|
<th id="mb"></th>
|
||||||
<th id="score">Score</th>
|
|
||||||
<th id="add"></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
%if searchresults:
|
%if searchresults:
|
||||||
%for result in searchresults:
|
%for result in searchresults:
|
||||||
<%
|
<%
|
||||||
if result['score'] == 100:
|
if result['score'] == 100:
|
||||||
grade = 'A'
|
grade = 'A'
|
||||||
else:
|
else:
|
||||||
grade = 'Z'
|
grade = 'Z'
|
||||||
%>
|
|
||||||
|
if type == 'album':
|
||||||
|
albuminfo = 'Type: ' + result['rgtype'] + ', Country: ' + result['country']
|
||||||
|
caa_group_url = "http://coverartarchive.org/release-group/%s/front-250.jpg" %result['rgid']
|
||||||
|
%>
|
||||||
<tr class="grade${grade}">
|
<tr class="grade${grade}">
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<td id="albumart"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50"></div></td>
|
<td id="albumart" style=" text-align: center; vertical-align: middle;"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50" onerror="this.onerror=null;this.src='${caa_group_url}'"></div></td>
|
||||||
%else:
|
%else:
|
||||||
<td id="albumart"><div id="artistImg"><img title="${result['id']}" class="albumArt" height="50" width="50"></div></td>
|
<td id="albumart"><div id="artistImg"><img title="${result['id']}" class="albumArt" height="50" width="50"></div></td>
|
||||||
%endif
|
%endif
|
||||||
%if type == 'album':
|
%if type == 'album':
|
||||||
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
|
<td id="albumname"><a href="addReleaseById?rid=${result['albumid']}&rgid=${result['rgid']}" title="${albuminfo}">${result['title']}</a></td>
|
||||||
%endif
|
<td id="artistnamesmall"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||||
<td id="artistname"><a href="${result['url']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
<td id="format">${result['formats']}</td>
|
||||||
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
|
<td id="tracks">${result['tracks']}</td>
|
||||||
%if type == 'album':
|
<td id="reldate">${result['date']}</td>
|
||||||
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
|
<td id="score"><a href="${result['albumurl']} "title="View on MusicBrainz"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></a></td>
|
||||||
%else:
|
<td id="musicbrainz" style=" text-align: center; line-height: 0; vertical-align: middle;"><a href="${result['albumurl']}"><img src="interfaces/default/images/MusicBrainz_Album_Icon.png" title="View on MusicBrainz" height="20" width="20"></a></td>
|
||||||
<td id="add"><a href="addArtist?artistid=${result['id']}"><i class="fa fa-plus"></i> Add this artist</a></td>
|
%else:
|
||||||
|
<td id="artistname"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||||
|
<td id="score"><a href="${result['url']} "title="View on MusicBrainz"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></a></td>
|
||||||
|
<td id="musicbrainz" style=" text-align: center; line-height: 0; vertical-align: middle;"><a href="${result['url']}"><img src="interfaces/default/images/MusicBrainz_Artist_Icon.png" title="View on MusicBrainz" height="20" width="20"></a></td>
|
||||||
%endif
|
%endif
|
||||||
</tr>
|
</tr>
|
||||||
%endfor
|
%endfor
|
||||||
@@ -55,27 +68,28 @@
|
|||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="javascriptIncludes()">
|
<%def name="javascriptIncludes()">
|
||||||
|
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script type="text/javascript">
|
||||||
function getArt() {
|
|
||||||
$("table#searchresults_table tr td#albumart img").each(function(){
|
function getArt() {
|
||||||
var id = $(this).attr('title');
|
$("table#searchresults_table tr td#albumart img").each(function(){
|
||||||
var image = $(this);
|
var id = $(this).attr('title');
|
||||||
if ( !image.hasClass('done') ) {
|
var image = $(this);
|
||||||
image.addClass('done');
|
|
||||||
getImageLinks(image,id,"${type}");
|
if (!image.hasClass('done')) {
|
||||||
}
|
image.addClass('done');
|
||||||
});
|
getImageLinks(image, id, "${type}", true);
|
||||||
}
|
}
|
||||||
function initThisPage() {
|
});
|
||||||
getArt();
|
}
|
||||||
$('#searchresults_table').dataTable(
|
function initThisPage() {
|
||||||
{
|
getArt();
|
||||||
|
$('#searchresults_table').dataTable({
|
||||||
"bDestroy": true,
|
"bDestroy": true,
|
||||||
"aoColumnDefs": [
|
"aoColumnDefs": [
|
||||||
{ 'bSortable': false, 'aTargets': [ 0,3 ] }
|
{ 'bSortable': false, 'aTargets': [ 0 ] }
|
||||||
],
|
],
|
||||||
"oLanguage": {
|
"oLanguage": {
|
||||||
"sLengthMenu":"Show _MENU_ results per page",
|
"sLengthMenu":"Show _MENU_ results per page",
|
||||||
@@ -88,13 +102,14 @@
|
|||||||
"sPaginationType": "full_numbers",
|
"sPaginationType": "full_numbers",
|
||||||
"aaSorting": []
|
"aaSorting": []
|
||||||
});
|
});
|
||||||
|
$('#searchresults_table').on("draw.dt", function () {
|
||||||
|
$("img").unveil();
|
||||||
|
});
|
||||||
resetFilters("album");
|
resetFilters("album");
|
||||||
}
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
initThisPage();
|
initFancybox();
|
||||||
});
|
initThisPage();
|
||||||
$(window).load(function(){
|
});
|
||||||
initFancybox();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<%def name="body()">
|
<%def name="body()">
|
||||||
|
<div id="paddingheader">
|
||||||
<div class="title">
|
|
||||||
<h1 class="clearfix"><i class="fa fa-heart"></i> Wanted Albums</h1>
|
<h1 class="clearfix"><i class="fa fa-heart"></i> Wanted Albums</h1>
|
||||||
</div>
|
</div>
|
||||||
<form action="markAlbums" method="get" id="markAlbums">
|
<form action="markAlbums" method="get" id="markAlbums">
|
||||||
@@ -41,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>
|
||||||
@@ -71,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>
|
||||||
@@ -89,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,16 +15,17 @@
|
|||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
import os, sys, subprocess
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import itertools
|
import itertools
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
from lib.apscheduler.scheduler import Scheduler
|
from apscheduler.scheduler import Scheduler
|
||||||
from lib.configobj import ConfigObj
|
from configobj import ConfigObj
|
||||||
|
|
||||||
from headphones import versioncheck, logger, version
|
from headphones import versioncheck, logger, version
|
||||||
from headphones.common import *
|
from headphones.common import *
|
||||||
@@ -38,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
|
||||||
@@ -103,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
|
||||||
@@ -133,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
|
||||||
@@ -144,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
|
||||||
|
|
||||||
@@ -154,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
|
||||||
@@ -165,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
|
||||||
@@ -187,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
|
||||||
@@ -247,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
|
||||||
@@ -273,6 +284,10 @@ OSX_NOTIFY_APP = None
|
|||||||
BOXCAR_ENABLED = False
|
BOXCAR_ENABLED = False
|
||||||
BOXCAR_ONSNATCH = False
|
BOXCAR_ONSNATCH = False
|
||||||
BOXCAR_TOKEN = None
|
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
|
||||||
@@ -284,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:
|
||||||
@@ -336,18 +354,18 @@ 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, \
|
||||||
@@ -356,43 +374,23 @@ def initialize():
|
|||||||
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('OSX_Notify')
|
|
||||||
CheckSection('Boxcar')
|
|
||||||
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')
|
||||||
@@ -436,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))
|
||||||
@@ -469,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', '')
|
||||||
@@ -501,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))
|
||||||
|
|
||||||
@@ -511,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', '')
|
||||||
@@ -525,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', '')
|
||||||
@@ -623,6 +633,11 @@ def initialize():
|
|||||||
BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
|
BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
|
||||||
BOXCAR_TOKEN = check_setting_str(CFG, 'Boxcar', 'boxcar_token', '')
|
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', '')
|
||||||
@@ -640,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
|
||||||
@@ -689,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':
|
||||||
@@ -710,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
|
||||||
@@ -817,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
|
||||||
@@ -859,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)
|
||||||
@@ -887,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)
|
||||||
@@ -915,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
|
||||||
@@ -928,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)
|
||||||
@@ -941,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)
|
||||||
@@ -960,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
|
||||||
@@ -1010,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'] = {}
|
||||||
@@ -1048,6 +1083,12 @@ def config_write():
|
|||||||
new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
|
new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
|
||||||
new_config['Boxcar']['boxcar_token'] = BOXCAR_TOKEN
|
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
|
||||||
@@ -1088,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():
|
||||||
|
|
||||||
@@ -1099,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)
|
||||||
@@ -1111,6 +1158,10 @@ def start():
|
|||||||
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
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def switch(AlbumID, ReleaseID):
|
|||||||
|
|
||||||
# Update have track counts on index
|
# Update have track counts on index
|
||||||
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']]))
|
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [newalbumdata['ArtistID']]))
|
||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [newalbumdata['ArtistID']]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [newalbumdata['ArtistID']]))
|
||||||
|
|
||||||
controlValueDict = {"ArtistID": newalbumdata['ArtistID']}
|
controlValueDict = {"ArtistID": newalbumdata['ArtistID']}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import headphones
|
|
||||||
|
|
||||||
from headphones import db, mb, importer, searcher, cache, postprocessor, versioncheck, logger
|
from headphones import db, mb, importer, searcher, cache, postprocessor, versioncheck, logger
|
||||||
|
|
||||||
import lib.simplejson as simplejson
|
|
||||||
from xml.dom.minidom import Document
|
from xml.dom.minidom import Document
|
||||||
|
|
||||||
|
import headphones
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
||||||
'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist',
|
'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist',
|
||||||
@@ -87,10 +87,10 @@ 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:
|
||||||
@@ -145,7 +145,7 @@ class Api(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def _getHistory(self, **kwargs):
|
def _getHistory(self, **kwargs):
|
||||||
self.data = self._dic_from_query('SELECT * from snatched order by DateAdded DESC')
|
self.data = self._dic_from_query('SELECT * from snatched WHERE status NOT LIKE "Seed%" order by DateAdded DESC')
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getUpcoming(self, **kwargs):
|
def _getUpcoming(self, **kwargs):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -59,37 +59,32 @@ class DBConnection:
|
|||||||
return
|
return
|
||||||
|
|
||||||
sqlResult = None
|
sqlResult = None
|
||||||
attempt = 0
|
|
||||||
|
|
||||||
while attempt < 5:
|
try:
|
||||||
try:
|
with self.connection as c:
|
||||||
if args == None:
|
if args == None:
|
||||||
#logger.debug(self.filename+": "+query)
|
sqlResult = c.execute(query)
|
||||||
sqlResult = self.connection.execute(query)
|
|
||||||
else:
|
else:
|
||||||
#logger.debug(self.filename+": "+query+" with args "+str(args))
|
sqlResult = c.execute(query, args)
|
||||||
sqlResult = self.connection.execute(query, args)
|
|
||||||
self.connection.commit()
|
except sqlite3.OperationalError, e:
|
||||||
break
|
if "unable to open database file" in e.message or "database is locked" in e.message:
|
||||||
except sqlite3.OperationalError, e:
|
logger.warn('Database Error: %s', e)
|
||||||
if "unable to open database file" in e.message or "database is locked" in e.message:
|
else:
|
||||||
logger.warn('Database Error: %s', e)
|
logger.error('Database error: %s', e)
|
||||||
attempt += 1
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
|
||||||
logger.error('Database error: %s', e)
|
|
||||||
raise
|
|
||||||
except sqlite3.DatabaseError, e:
|
|
||||||
logger.error('Fatal Error executing %s :: %s', query, e)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
except sqlite3.DatabaseError, e:
|
||||||
|
logger.error('Fatal Error executing %s :: %s', query, e)
|
||||||
|
raise
|
||||||
|
|
||||||
return sqlResult
|
return sqlResult
|
||||||
|
|
||||||
def select(self, query, args=None):
|
def select(self, query, args=None):
|
||||||
|
|
||||||
sqlResults = self.action(query, args).fetchall()
|
sqlResults = self.action(query, args).fetchall()
|
||||||
|
|
||||||
if sqlResults == None:
|
if sqlResults == None or sqlResults == [None]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return sqlResults
|
return sqlResults
|
||||||
|
|||||||
@@ -13,29 +13,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
def ex(e):
|
|
||||||
"""
|
|
||||||
Returns a string from the exception text if it exists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# sanity check
|
|
||||||
if not e.args or not e.args[0]:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
e_message = e.args[0]
|
|
||||||
|
|
||||||
# if fixStupidEncodings doesn't fix it then maybe it's not a string, in which case we'll try printing it anyway
|
|
||||||
if not e_message:
|
|
||||||
try:
|
|
||||||
e_message = str(e.args[0])
|
|
||||||
except:
|
|
||||||
e_message = ""
|
|
||||||
|
|
||||||
return e_message
|
|
||||||
|
|
||||||
|
|
||||||
class HeadphonesException(Exception):
|
class HeadphonesException(Exception):
|
||||||
"Generic Headphones Exception - should never be thrown, only subclassed"
|
"""
|
||||||
|
Generic Headphones Exception - should never be thrown, only subclassed
|
||||||
|
"""
|
||||||
|
|
||||||
class NewzbinAPIThrottled(HeadphonesException):
|
class NewzbinAPIThrottled(HeadphonesException):
|
||||||
"Newzbin has throttled us, deal with it"
|
"""
|
||||||
|
Newzbin has throttled us, deal with it
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -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,13 +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:
|
||||||
elif extrasonly:
|
force_repackage = 1
|
||||||
# Not really sure what we're doing here but don't want to log the message below if we're fetching extras only
|
|
||||||
pass
|
|
||||||
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 = []
|
||||||
@@ -227,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
|
||||||
@@ -314,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
|
||||||
@@ -381,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()
|
||||||
|
|
||||||
@@ -402,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"
|
||||||
@@ -423,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']}
|
||||||
|
|
||||||
@@ -472,6 +484,7 @@ 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)
|
finalize_update(artistid, artist['artist_name'], errors)
|
||||||
|
|
||||||
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
||||||
@@ -497,7 +510,7 @@ def finalize_update(artistid, artistname, errors=False):
|
|||||||
myDB = db.DBConnection()
|
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"', [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]))
|
||||||
|
|
||||||
@@ -520,10 +533,23 @@ def finalize_update(artistid, artistname, errors=False):
|
|||||||
|
|
||||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||||
|
|
||||||
def addReleaseById(rid):
|
def addReleaseById(rid, rgid=None):
|
||||||
|
|
||||||
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
|
||||||
@@ -537,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']
|
||||||
@@ -559,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'],
|
||||||
@@ -575,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)
|
||||||
@@ -598,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'],
|
||||||
@@ -629,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!")
|
||||||
@@ -669,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'],
|
||||||
@@ -711,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
|
||||||
@@ -50,7 +50,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
|
|
||||||
if not append:
|
if not append:
|
||||||
# Clean up bad filepaths
|
# Clean up bad filepaths
|
||||||
tracks = myDB.select('SELECT Location, TrackID from alltracks WHERE Location IS NOT NULL')
|
tracks = myDB.select('SELECT Location from alltracks WHERE Location IS NOT NULL UNION SELECT Location from tracks WHERE Location IS NOT NULL')
|
||||||
|
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING)
|
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING)
|
||||||
@@ -61,7 +61,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
del_have_tracks = myDB.select('SELECT Location, Matched, ArtistName from have')
|
del_have_tracks = myDB.select('SELECT Location, Matched, ArtistName from have')
|
||||||
|
|
||||||
for track in del_have_tracks:
|
for track in del_have_tracks:
|
||||||
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING)
|
encoded_track_string = track['Location'].encode(headphones.SYS_ENCODING, 'replace')
|
||||||
if not os.path.isfile(encoded_track_string):
|
if not os.path.isfile(encoded_track_string):
|
||||||
if track['ArtistName']:
|
if track['ArtistName']:
|
||||||
#Make sure deleted files get accounted for when updating artist track counts
|
#Make sure deleted files get accounted for when updating artist track counts
|
||||||
@@ -84,8 +84,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
for directory in d[:]:
|
for directory in d[:]:
|
||||||
if directory.startswith("."):
|
if directory.startswith("."):
|
||||||
d.remove(directory)
|
d.remove(directory)
|
||||||
for files in f:
|
|
||||||
|
|
||||||
|
for files in f:
|
||||||
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
||||||
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
if any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||||
|
|
||||||
@@ -104,9 +104,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
# Try to read the metadata
|
# Try to read the metadata
|
||||||
try:
|
try:
|
||||||
f = MediaFile(song)
|
f = MediaFile(song)
|
||||||
|
except (FileTypeError, UnreadableFileError):
|
||||||
except:
|
logger.error("Cannot read file media file '%s'. It may be corrupted or not a media file.", unicode_song_path)
|
||||||
logger.error('Cannot read file: ' + unicode_song_path)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Grab the bitrates for the auto detect bit rate option
|
# Grab the bitrates for the auto detect bit rate option
|
||||||
@@ -216,6 +215,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
if song['ArtistName'] and song['AlbumTitle'] and song['TrackTitle']:
|
if song['ArtistName'] and song['AlbumTitle'] and song['TrackTitle']:
|
||||||
|
|
||||||
track = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
track = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from tracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
||||||
|
have_updated = False
|
||||||
if track:
|
if track:
|
||||||
controlValueDict = { 'ArtistName' : track['ArtistName'],
|
controlValueDict = { 'ArtistName' : track['ArtistName'],
|
||||||
'AlbumTitle' : track['AlbumTitle'],
|
'AlbumTitle' : track['AlbumTitle'],
|
||||||
@@ -228,6 +228,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
have_updated = True
|
||||||
else:
|
else:
|
||||||
track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
||||||
if track:
|
if track:
|
||||||
@@ -240,11 +241,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
have_updated = True
|
||||||
else:
|
else:
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : "Failed"}
|
newValueDict2 = { 'Matched' : "Failed"}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
have_updated = True
|
||||||
|
|
||||||
alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
alltrack = myDB.action('SELECT ArtistName, AlbumTitle, TrackTitle, AlbumID from alltracks WHERE ArtistName LIKE ? AND AlbumTitle LIKE ? AND TrackTitle LIKE ?', [song['ArtistName'], song['AlbumTitle'], song['TrackTitle']]).fetchone()
|
||||||
if alltrack:
|
if alltrack:
|
||||||
@@ -272,9 +274,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
else:
|
else:
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
# alltracks may not exist if adding album manually, have should only be set to failed if not already updated in tracks
|
||||||
newValueDict2 = { 'Matched' : "Failed"}
|
if not have_updated:
|
||||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
|
newValueDict2 = { 'Matched' : "Failed"}
|
||||||
|
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
controlValueDict2 = { 'Location' : song['Location']}
|
controlValueDict2 = { 'Location' : song['Location']}
|
||||||
newValueDict2 = { 'Matched' : "Failed"}
|
newValueDict2 = { 'Matched' : "Failed"}
|
||||||
@@ -328,12 +333,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
|||||||
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [ArtistID])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [ArtistName]))
|
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [ArtistID])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [ArtistName]))
|
||||||
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, ArtistID])
|
myDB.action('UPDATE artists SET HaveTracks=? WHERE ArtistID=?', [havetracks, ArtistID])
|
||||||
|
|
||||||
update_album_status()
|
|
||||||
if not append:
|
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 if verbose == 2 else logging.INFO)
|
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)
|
||||||
@@ -74,7 +126,7 @@ def initLogger(verbose=1):
|
|||||||
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)
|
||||||
|
|||||||
@@ -13,19 +13,23 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
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():
|
||||||
@@ -75,13 +79,15 @@ def findArtist(name, limit=1):
|
|||||||
artistlist = []
|
artistlist = []
|
||||||
artistResults = None
|
artistResults = None
|
||||||
|
|
||||||
chars = set('!?*')
|
chars = set('!?*-')
|
||||||
if any((c in chars) for c in name):
|
if any((c in chars) for c in name):
|
||||||
name = '"'+name+'"'
|
name = '"'+name+'"'
|
||||||
|
|
||||||
|
criteria = {'artist': name.lower()}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
artistResults = musicbrainzngs.search_artists(query='artist:'+name,limit=limit)['artist-list']
|
artistResults = musicbrainzngs.search_artists(limit=limit, **criteria)['artist-list']
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -117,33 +123,81 @@ def findArtist(name, limit=1):
|
|||||||
})
|
})
|
||||||
return artistlist
|
return artistlist
|
||||||
|
|
||||||
def findRelease(name, limit=1):
|
def findRelease(name, limit=1, artist=None):
|
||||||
|
|
||||||
with mb_lock:
|
with mb_lock:
|
||||||
releaselist = []
|
releaselist = []
|
||||||
releaseResults = None
|
releaseResults = None
|
||||||
|
|
||||||
chars = set('!?')
|
# additional artist search
|
||||||
|
if not artist and ':' in name:
|
||||||
|
name, artist = name.rsplit(":",1)
|
||||||
|
|
||||||
|
chars = set('!?*-')
|
||||||
if any((c in chars) for c in name):
|
if any((c in chars) for c in name):
|
||||||
name = '"'+name+'"'
|
name = '"'+name+'"'
|
||||||
|
if artist and any((c in chars) for c in artist):
|
||||||
|
artist = '"'+artist+'"'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit)['release-list']
|
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit,artist=artist)['release-list']
|
||||||
except WebServiceError, e: #need to update exceptions
|
except musicbrainzngs.WebServiceError as e: #need to update exceptions
|
||||||
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
if not releaseResults:
|
if not releaseResults:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for result in releaseResults:
|
for result in releaseResults:
|
||||||
releaselist.append({
|
|
||||||
|
title = result['title']
|
||||||
|
if 'disambiguation' in result:
|
||||||
|
title += ' (' + result['disambiguation'] + ')'
|
||||||
|
|
||||||
|
# Get formats and track counts
|
||||||
|
format_dict = OrderedDict()
|
||||||
|
formats = ''
|
||||||
|
tracks = ''
|
||||||
|
if 'medium-list' in result:
|
||||||
|
for medium in result['medium-list']:
|
||||||
|
if 'format' in medium:
|
||||||
|
format = medium['format']
|
||||||
|
if format not in format_dict:
|
||||||
|
format_dict[format] = 0
|
||||||
|
format_dict[format] += 1
|
||||||
|
if 'track-count' in medium:
|
||||||
|
if tracks:
|
||||||
|
tracks += ' + '
|
||||||
|
tracks += str(medium['track-count'])
|
||||||
|
for format, count in format_dict.items():
|
||||||
|
if formats:
|
||||||
|
formats += ' + '
|
||||||
|
if count > 1:
|
||||||
|
formats += str(count) + 'x'
|
||||||
|
formats += format
|
||||||
|
|
||||||
|
rg_type = ''
|
||||||
|
if 'type' in result['release-group']:
|
||||||
|
rg_type = result['release-group']['type']
|
||||||
|
if rg_type == 'Album' and 'secondary-type-list' in result['release-group']:
|
||||||
|
secondary_type = result['release-group']['secondary-type-list'][0]
|
||||||
|
if secondary_type != rg_type:
|
||||||
|
rg_type = secondary_type
|
||||||
|
|
||||||
|
releaselist.append({
|
||||||
'uniquename': unicode(result['artist-credit'][0]['artist']['name']),
|
'uniquename': unicode(result['artist-credit'][0]['artist']['name']),
|
||||||
'title': unicode(result['title']),
|
'title': unicode(title),
|
||||||
'id': unicode(result['artist-credit'][0]['artist']['id']),
|
'id': unicode(result['artist-credit'][0]['artist']['id']),
|
||||||
'albumid': unicode(result['id']),
|
'albumid': unicode(result['id']),
|
||||||
'url': unicode("http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),#probably needs to be changed
|
'url': unicode("http://musicbrainz.org/artist/" + result['artist-credit'][0]['artist']['id']),#probably needs to be changed
|
||||||
'albumurl': unicode("http://musicbrainz.org/release/" + result['id']),#probably needs to be changed
|
'albumurl': unicode("http://musicbrainz.org/release/" + result['id']),#probably needs to be changed
|
||||||
'score': int(result['ext:score'])
|
'score': int(result['ext:score']),
|
||||||
|
'date': unicode(result['date']) if 'date' in result else '',
|
||||||
|
'country': unicode(result['country']) if 'country' in result else '',
|
||||||
|
'formats': unicode(formats),
|
||||||
|
'tracks': unicode(tracks),
|
||||||
|
'rgid': unicode(result['release-group']['id']),
|
||||||
|
'rgtype': unicode(rg_type)
|
||||||
})
|
})
|
||||||
return releaselist
|
return releaselist
|
||||||
|
|
||||||
@@ -162,7 +216,7 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
while newRgs == None or len(newRgs) >= limit:
|
while newRgs == None or len(newRgs) >= limit:
|
||||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type="album",offset=len(artist['release-group-list']),limit=limit)['release-group-list']
|
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type="album",offset=len(artist['release-group-list']),limit=limit)['release-group-list']
|
||||||
artist['release-group-list'] += newRgs
|
artist['release-group-list'] += newRgs
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
except Exception,e:
|
except Exception,e:
|
||||||
@@ -219,14 +273,18 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
|
|
||||||
if includeExtras:
|
if includeExtras:
|
||||||
|
|
||||||
# Need to convert extras string from something like '2,5.6' to ['ep','live','remix']
|
# Need to convert extras string from something like '2,5.6' to ['ep','live','remix'] (append new extras to end)
|
||||||
extras = db_artist['Extras']
|
if db_artist['Extras']:
|
||||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "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
|
||||||
|
|
||||||
@@ -240,16 +298,23 @@ def getArtist(artistid, extrasonly=False):
|
|||||||
while newRgs == None or len(newRgs) >= limit:
|
while newRgs == None or len(newRgs) >= limit:
|
||||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type=include,offset=len(mb_extras_list),limit=limit)['release-group-list']
|
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type=include,offset=len(mb_extras_list),limit=limit)['release-group-list']
|
||||||
mb_extras_list += newRgs
|
mb_extras_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)
|
||||||
|
|
||||||
for rg in mb_extras_list:
|
for rg in mb_extras_list:
|
||||||
|
|
||||||
|
rg_type = rg['type']
|
||||||
|
if rg_type == 'Album' and 'secondary-type-list' in rg:
|
||||||
|
secondary_type = rg['secondary-type-list'][0]
|
||||||
|
if secondary_type != rg_type:
|
||||||
|
rg_type = secondary_type
|
||||||
|
|
||||||
releasegroups.append({
|
releasegroups.append({
|
||||||
'title': unicode(rg['title']),
|
'title': unicode(rg['title']),
|
||||||
'id': unicode(rg['id']),
|
'id': unicode(rg['id']),
|
||||||
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
|
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
|
||||||
'type': unicode(rg['type'])
|
'type': unicode(rg_type)
|
||||||
})
|
})
|
||||||
|
|
||||||
artist_dict['releasegroups'] = releasegroups
|
artist_dict['releasegroups'] = releasegroups
|
||||||
@@ -268,7 +333,7 @@ def getReleaseGroup(rgid):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid,["artists","releases","media","discids",])['release-group']
|
releaseGroup = musicbrainzngs.get_release_group_by_id(rgid,["artists","releases","media","discids",])['release-group']
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -291,7 +356,7 @@ def getRelease(releaseid, include_artist_info=True):
|
|||||||
results = musicbrainzngs.get_release_by_id(releaseid,["artists","release-groups","media","recordings"]).get('release')
|
results = musicbrainzngs.get_release_by_id(releaseid,["artists","release-groups","media","recordings"]).get('release')
|
||||||
else:
|
else:
|
||||||
results = musicbrainzngs.get_release_by_id(releaseid,["media","recordings"]).get('release')
|
results = musicbrainzngs.get_release_by_id(releaseid,["media","recordings"]).get('release')
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed (%s)' % (releaseid, str(e)))
|
logger.warn('Attempt to retrieve information from MusicBrainz for release "%s" failed (%s)' % (releaseid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -320,8 +385,15 @@ def getRelease(releaseid, include_artist_info=True):
|
|||||||
release['rg_title'] = unicode(results['release-group']['title'])
|
release['rg_title'] = unicode(results['release-group']['title'])
|
||||||
try:
|
try:
|
||||||
release['rg_type'] = unicode(results['release-group']['type'])
|
release['rg_type'] = unicode(results['release-group']['type'])
|
||||||
|
|
||||||
|
if release['rg_type'] == 'Album' and 'secondary-type-list' in results['release-group']:
|
||||||
|
secondary_type = unicode(results['release-group']['secondary-type-list'][0])
|
||||||
|
if secondary_type != release['rg_type']:
|
||||||
|
release['rg_type'] = secondary_type
|
||||||
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
release['rg_type'] = u'Unknown'
|
release['rg_type'] = u'Unknown'
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.warn("Release " + releaseid + "had no ReleaseGroup associated")
|
logger.warn("Release " + releaseid + "had no ReleaseGroup associated")
|
||||||
|
|
||||||
@@ -346,7 +418,7 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
newResults = newResults['release-list']
|
newResults = newResults['release-list']
|
||||||
results += newResults
|
results += newResults
|
||||||
|
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
logger.warn('Attempt to retrieve information from MusicBrainz for release group "%s" failed (%s)' % (rgid, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
return False
|
return False
|
||||||
@@ -401,6 +473,10 @@ def get_new_releases(rgid,includeExtras=False,forcefull=False):
|
|||||||
raise Exception('No release group associated with release id ' + releasedata['id'] + ' album id' + rgid)
|
raise Exception('No release group associated with release id ' + releasedata['id'] + ' album id' + rgid)
|
||||||
release['Type'] = unicode(releasedata['release-group']['type'])
|
release['Type'] = unicode(releasedata['release-group']['type'])
|
||||||
|
|
||||||
|
if release['Type'] == 'Album' and 'secondary-type-list' in releasedata['release-group']:
|
||||||
|
secondary_type = unicode(releasedata['release-group']['secondary-type-list'][0])
|
||||||
|
if secondary_type != release['Type']:
|
||||||
|
release['Type'] = secondary_type
|
||||||
|
|
||||||
#making the assumption that the most important artist will be first in the list
|
#making the assumption that the most important artist will be first in the list
|
||||||
if 'artist-credit' in releasedata:
|
if 'artist-credit' in releasedata:
|
||||||
@@ -535,7 +611,7 @@ def findArtistbyAlbum(name):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
results = musicbrainzngs.search_release_groups(term).get('release-group-list')
|
results = musicbrainzngs.search_release_groups(term).get('release-group-list')
|
||||||
except WebServiceError, e:
|
except musicbrainzngs.WebServiceError as e:
|
||||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -564,14 +640,23 @@ def findArtistbyAlbum(name):
|
|||||||
def findAlbumID(artist=None, album=None):
|
def findAlbumID(artist=None, album=None):
|
||||||
|
|
||||||
results = None
|
results = None
|
||||||
|
chars = set('!?*-')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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)
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
@@ -320,4 +364,3 @@ def getTimeEncode(start):
|
|||||||
minutes = seconds / 60
|
minutes = seconds / 60
|
||||||
seconds -= 60*minutes
|
seconds -= 60*minutes
|
||||||
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||||
|
|
||||||
|
|||||||
@@ -13,33 +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 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
|
||||||
@@ -89,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(
|
||||||
@@ -115,10 +120,10 @@ 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
|
||||||
@@ -162,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):
|
||||||
|
|
||||||
@@ -192,9 +212,9 @@ class XBMC:
|
|||||||
url = host + '/jsonrpc'
|
url = host + '/jsonrpc'
|
||||||
|
|
||||||
if self.password:
|
if self.password:
|
||||||
response = request.request_json(url, 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(url, 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']
|
||||||
@@ -237,20 +257,20 @@ 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 LMS(object):
|
||||||
|
"""
|
||||||
#Class for updating a Logitech Media Server
|
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):
|
def _sendjson(self, host):
|
||||||
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]}
|
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]}
|
||||||
data = simplejson.JSONEncoder().encode(data)
|
data = json.JSONEncoder().encode(data)
|
||||||
|
|
||||||
content = {'Content-Type': 'application/json'}
|
content = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
@@ -262,7 +282,7 @@ 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())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return response['result']
|
return response['result']
|
||||||
@@ -281,8 +301,7 @@ class LMS:
|
|||||||
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
|
||||||
@@ -368,37 +387,43 @@ class Plex:
|
|||||||
except:
|
except:
|
||||||
logger.warn('Error sending notification request to Plex Media Server')
|
logger.warn('Error sending notification request to Plex Media Server')
|
||||||
|
|
||||||
class NMA:
|
class NMA(object):
|
||||||
|
def notify(self, artist=None, album=None, snatched=None):
|
||||||
|
title = 'Headphones'
|
||||||
|
api = headphones.NMA_APIKEY
|
||||||
|
nma_priority = headphones.NMA_PRIORITY
|
||||||
|
|
||||||
def __init__(self):
|
logger.debug(u"NMA title: " + title)
|
||||||
|
logger.debug(u"NMA API: " + api)
|
||||||
|
logger.debug(u"NMA Priority: " + str(nma_priority))
|
||||||
|
|
||||||
self.apikey = headphones.NMA_APIKEY
|
if snatched:
|
||||||
self.priority = headphones.NMA_PRIORITY
|
event = snatched + " snatched!"
|
||||||
|
message = "Headphones has snatched: " + snatched
|
||||||
def _send(self, data):
|
|
||||||
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
|
|
||||||
|
|
||||||
def notify(self, artist=None, album=None, snatched_nzb=None):
|
|
||||||
|
|
||||||
apikey = self.apikey
|
|
||||||
priority = self.priority
|
|
||||||
|
|
||||||
if snatched_nzb:
|
|
||||||
event = snatched_nzb + " snatched!"
|
|
||||||
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
|
|
||||||
else:
|
else:
|
||||||
event = artist + ' - ' + album + ' complete!'
|
event = artist + ' - ' + album + ' complete!'
|
||||||
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
|
||||||
|
|
||||||
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
|
logger.debug(u"NMA event: " + event)
|
||||||
|
logger.debug(u"NMA message: " + message)
|
||||||
|
|
||||||
logger.info('Sending notification request to NotifyMyAndroid')
|
batch = False
|
||||||
request = self._send(data)
|
|
||||||
|
|
||||||
if not request:
|
p = pynma.PyNMA()
|
||||||
logger.warn('Error sending notification request to NotifyMyAndroid')
|
keys = api.split(',')
|
||||||
|
p.addkey(keys)
|
||||||
|
|
||||||
class PUSHBULLET:
|
if len(keys) > 1: batch = True
|
||||||
|
|
||||||
|
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
|
||||||
|
|
||||||
|
if not response[api][u'code'] == u'200':
|
||||||
|
logger.error(u'Could not send notification to NotifyMyAndroid')
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class PUSHBULLET(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.apikey = headphones.PUSHBULLET_APIKEY
|
self.apikey = headphones.PUSHBULLET_APIKEY
|
||||||
@@ -451,7 +476,7 @@ class PUSHBULLET:
|
|||||||
|
|
||||||
self.notify('Main Screen Activate', 'Test Message')
|
self.notify('Main Screen Activate', 'Test Message')
|
||||||
|
|
||||||
class PUSHALOT:
|
class PUSHALOT(object):
|
||||||
|
|
||||||
def notify(self, message, event):
|
def notify(self, message, event):
|
||||||
if not headphones.PUSHALOT_ENABLED:
|
if not headphones.PUSHALOT_ENABLED:
|
||||||
@@ -490,7 +515,7 @@ class PUSHALOT:
|
|||||||
logger.info(u"Pushalot notification failed.")
|
logger.info(u"Pushalot notification failed.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Synoindex:
|
class Synoindex(object):
|
||||||
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
||||||
self.util_loc = util_loc
|
self.util_loc = util_loc
|
||||||
|
|
||||||
@@ -526,19 +551,17 @@ class Synoindex:
|
|||||||
for path in path_list:
|
for path in path_list:
|
||||||
self.notify(path)
|
self.notify(path)
|
||||||
|
|
||||||
class PUSHOVER:
|
class PUSHOVER(object):
|
||||||
|
|
||||||
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
|
||||||
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)
|
||||||
@@ -580,23 +603,23 @@ class PUSHOVER:
|
|||||||
return
|
return
|
||||||
|
|
||||||
def test(self, keys, priority):
|
def test(self, keys, priority):
|
||||||
|
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.keys = keys
|
self.keys = keys
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
|
|
||||||
self.notify('Main Screen Activate', 'Test Message')
|
self.notify('Main Screen Activate', 'Test Message')
|
||||||
|
|
||||||
class TwitterNotifier:
|
class TwitterNotifier(object):
|
||||||
|
|
||||||
consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
|
||||||
consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
|
||||||
|
|
||||||
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
||||||
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
|
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
|
||||||
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
|
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
|
||||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||||
|
self.consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
||||||
|
|
||||||
def notify_snatch(self, title):
|
def notify_snatch(self, title):
|
||||||
if headphones.TWITTER_ONSNATCH:
|
if headphones.TWITTER_ONSNATCH:
|
||||||
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
|
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
|
||||||
@@ -690,11 +713,7 @@ class TwitterNotifier:
|
|||||||
|
|
||||||
return self._send_tweet(prefix+": "+message)
|
return self._send_tweet(prefix+": "+message)
|
||||||
|
|
||||||
notifier = TwitterNotifier
|
class OSX_NOTIFY(object):
|
||||||
|
|
||||||
class OSX_NOTIFY:
|
|
||||||
|
|
||||||
objc = None
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
try:
|
try:
|
||||||
@@ -749,14 +768,12 @@ class OSX_NOTIFY:
|
|||||||
def swizzled_bundleIdentifier(self, original, swizzled):
|
def swizzled_bundleIdentifier(self, original, swizzled):
|
||||||
return 'ade.headphones.osxnotify'
|
return 'ade.headphones.osxnotify'
|
||||||
|
|
||||||
class BOXCAR:
|
class BOXCAR(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.url = 'https://new.boxcar.io/api/notifications'
|
self.url = 'https://new.boxcar.io/api/notifications'
|
||||||
|
|
||||||
def notify(self, title, message, rgid=None):
|
def notify(self, title, message, rgid=None):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if rgid:
|
if rgid:
|
||||||
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
||||||
@@ -773,6 +790,25 @@ class BOXCAR:
|
|||||||
handle.close()
|
handle.close()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except urllib2.URLError, e:
|
except urllib2.URLError as e:
|
||||||
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||||
return False
|
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
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import headphones
|
|||||||
from beets import autotag
|
from beets import autotag
|
||||||
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||||
|
|
||||||
from headphones import notifiers
|
from headphones import notifiers, utorrent, transmission
|
||||||
from headphones import db, albumart, librarysync, lyrics
|
from headphones import db, albumart, librarysync, lyrics
|
||||||
from headphones import logger, helpers, request, mb, music_encoder
|
from headphones import logger, helpers, request, mb, music_encoder
|
||||||
|
|
||||||
@@ -42,30 +42,18 @@ def checkFolder():
|
|||||||
if album['FolderName']:
|
if album['FolderName']:
|
||||||
|
|
||||||
if album['Kind'] == 'nzb':
|
if album['Kind'] == 'nzb':
|
||||||
# We're now checking sab config options after sending to determine renaming - but we'll keep the
|
download_dir = headphones.DOWNLOAD_DIR
|
||||||
# iterations in just in case we can't read the config for some reason
|
else:
|
||||||
|
download_dir = headphones.DOWNLOAD_TORRENT_DIR
|
||||||
|
|
||||||
nzb_album_possibilities = [ album['FolderName'],
|
album_path = os.path.join(download_dir, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
||||||
helpers.sab_replace_dots(album['FolderName']),
|
logger.info("Checking if %s exists" % album_path)
|
||||||
helpers.sab_replace_spaces(album['FolderName']),
|
if os.path.exists(album_path):
|
||||||
helpers.sab_replace_spaces(sab_replace_dots(album['FolderName']))
|
logger.info('Found "' + album['FolderName'] + '" in ' + album['Kind'] + ' download folder. Verifying....')
|
||||||
]
|
verify(album['AlbumID'], album_path, album['Kind'])
|
||||||
|
|
||||||
for nzb_folder_name in nzb_album_possibilities:
|
else:
|
||||||
|
logger.info("No folder name found for " + album['Title'])
|
||||||
nzb_album_path = os.path.join(headphones.DOWNLOAD_DIR, nzb_folder_name).encode(headphones.SYS_ENCODING, 'replace')
|
|
||||||
|
|
||||||
if os.path.exists(nzb_album_path):
|
|
||||||
logger.debug('Found %s in NZB download folder. Verifying....' % album['FolderName'])
|
|
||||||
verify(album['AlbumID'], nzb_album_path, 'nzb')
|
|
||||||
|
|
||||||
if album['Kind'] == 'torrent':
|
|
||||||
|
|
||||||
torrent_album_path = os.path.join(headphones.DOWNLOAD_TORRENT_DIR, album['FolderName']).encode(headphones.SYS_ENCODING,'replace')
|
|
||||||
|
|
||||||
if os.path.exists(torrent_album_path):
|
|
||||||
logger.debug('Found %s in torrent download folder. Verifying....' % album['FolderName'])
|
|
||||||
verify(album['AlbumID'], torrent_album_path, 'torrent')
|
|
||||||
|
|
||||||
def verify(albumid, albumpath, Kind=None, forced=False):
|
def verify(albumid, albumpath, Kind=None, forced=False):
|
||||||
|
|
||||||
@@ -74,27 +62,42 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||||
|
|
||||||
if not release or not tracks:
|
if not release or not tracks:
|
||||||
#the result of a manual post-process on an album that hasn't been inserted
|
|
||||||
#from an RSS feed or etc
|
|
||||||
#TODO: This should be a call to a class method.. copied it out of importer with only minor changes
|
|
||||||
#TODO: odd things can happen when there are diacritic characters in the folder name, need to translate them?
|
|
||||||
release_list = None
|
release_list = None
|
||||||
|
|
||||||
|
# Fetch album information from MusicBrainz
|
||||||
try:
|
try:
|
||||||
release_list = mb.getReleaseGroup(albumid)
|
release_list = mb.getReleaseGroup(albumid)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.info('Unable to get release information for manual album with rgid: %s. Error: %s' % (albumid, e))
|
logger.error('Unable to get release information for manual album with rgid: %s. Error: %s', albumid, e)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not release_list:
|
if not release_list:
|
||||||
logger.info('Unable to get release information for manual album with rgid: %s' % albumid)
|
logger.error('Unable to get release information for manual album with rgid: %s', albumid)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Since we're just using this to create the bare minimum information to insert an artist/album combo, use the first release
|
# Since we're just using this to create the bare minimum information to
|
||||||
|
# insert an artist/album combo, use the first release
|
||||||
releaseid = release_list[0]['id']
|
releaseid = release_list[0]['id']
|
||||||
|
|
||||||
release_dict = mb.getRelease(releaseid)
|
release_dict = mb.getRelease(releaseid)
|
||||||
|
|
||||||
|
if not release_dict:
|
||||||
|
logger.error('Unable to get release information for manual album with rgid: %s. Cannot continue', albumid)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if the artist is added to the database. In case the database is
|
||||||
|
# frozen during post processing, new artists will not be processed. This
|
||||||
|
# prevents new artists from appearing suddenly. In case forced is True,
|
||||||
|
# this check is skipped, since it is assumed the user wants this.
|
||||||
|
if headphones.FREEZE_DB and not forced:
|
||||||
|
artist = myDB.select("SELECT ArtistName, ArtistID FROM artists WHERE ArtistId=? OR ArtistName=?", [release_dict['artist_id'], release_dict['artist_name']])
|
||||||
|
|
||||||
|
if not artist:
|
||||||
|
logger.warn("Continuing would add new artist '%s' (ID %s), " \
|
||||||
|
"but database is frozen. Will skip postprocessing for " \
|
||||||
|
"album with rgid: %s", release_dict['artist_name'],
|
||||||
|
release_dict['artist_id'], albumid)
|
||||||
|
return
|
||||||
|
|
||||||
logger.info(u"Now adding/updating artist: " + release_dict['artist_name'])
|
logger.info(u"Now adding/updating artist: " + release_dict['artist_name'])
|
||||||
|
|
||||||
if release_dict['artist_name'].startswith('The '):
|
if release_dict['artist_name'].startswith('The '):
|
||||||
@@ -102,7 +105,6 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
else:
|
else:
|
||||||
sortname = release_dict['artist_name']
|
sortname = release_dict['artist_name']
|
||||||
|
|
||||||
|
|
||||||
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
||||||
newValueDict = {"ArtistName": release_dict['artist_name'],
|
newValueDict = {"ArtistName": release_dict['artist_name'],
|
||||||
"ArtistSortName": sortname,
|
"ArtistSortName": sortname,
|
||||||
@@ -121,6 +123,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
controlValueDict = {"AlbumID": albumid}
|
controlValueDict = {"AlbumID": albumid}
|
||||||
|
|
||||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||||
|
"ReleaseID": albumid,
|
||||||
"ArtistName": release_dict['artist_name'],
|
"ArtistName": release_dict['artist_name'],
|
||||||
"AlbumTitle": release_dict['title'],
|
"AlbumTitle": release_dict['title'],
|
||||||
"AlbumASIN": release_dict['asin'],
|
"AlbumASIN": release_dict['asin'],
|
||||||
@@ -169,7 +172,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
elif files.lower().endswith('.cue'):
|
elif files.lower().endswith('.cue'):
|
||||||
downloaded_cuecount += 1
|
downloaded_cuecount += 1
|
||||||
# if any of the files end in *.part, we know the torrent isn't done yet. Process if forced, though
|
# if any of the files end in *.part, we know the torrent isn't done yet. Process if forced, though
|
||||||
elif files.lower().endswith('.part') and not forced:
|
elif files.lower().endswith(('.part', '.utpart')) and not forced:
|
||||||
logger.info("Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING, 'replace') + " isn't complete yet. Will try again on the next run")
|
logger.info("Looks like " + os.path.basename(albumpath).decode(headphones.SYS_ENCODING, 'replace') + " isn't complete yet. Will try again on the next run")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -312,7 +315,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logger.warn(u'Could not identify album: %s. It may not be the intended album.' % albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.warn(u'Could not identify album: %s. It may not be the intended album.' % albumpath.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE AlbumID=?', [albumid])
|
myDB.action('UPDATE snatched SET status = "Unprocessed" WHERE status NOT LIKE "Seed%" and AlbumID=?', [albumid])
|
||||||
processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath)
|
processed = re.search(r' \(Unprocessed\)(?:\[\d+\])?', albumpath)
|
||||||
if not processed:
|
if not processed:
|
||||||
renameUnprocessedFolder(albumpath)
|
renameUnprocessedFolder(albumpath)
|
||||||
@@ -364,7 +367,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
headphones.MOVE_FILES:
|
headphones.MOVE_FILES:
|
||||||
|
|
||||||
if not os.access(downloaded_track, os.W_OK):
|
if not os.access(downloaded_track, os.W_OK):
|
||||||
logger.error("Track file is not writeable, which is equired for some post processing steps: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.error("Track file is not writeable, which is required for some post processing steps: %s", downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
return
|
return
|
||||||
|
|
||||||
#start encoding
|
#start encoding
|
||||||
@@ -396,6 +399,9 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
if headphones.CLEANUP_FILES:
|
if headphones.CLEANUP_FILES:
|
||||||
cleanupFiles(albumpath)
|
cleanupFiles(albumpath)
|
||||||
|
|
||||||
|
if headphones.KEEP_NFO:
|
||||||
|
renameNFO(albumpath)
|
||||||
|
|
||||||
if headphones.ADD_ALBUM_ART and artwork:
|
if headphones.ADD_ALBUM_ART and artwork:
|
||||||
addAlbumArt(artwork, albumpath, release)
|
addAlbumArt(artwork, albumpath, release)
|
||||||
|
|
||||||
@@ -420,7 +426,25 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
|
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
|
myDB.action('UPDATE albums SET status = "Downloaded" WHERE AlbumID=?', [albumid])
|
||||||
myDB.action('UPDATE snatched SET status = "Processed" WHERE AlbumID=?', [albumid])
|
myDB.action('UPDATE snatched SET status = "Processed" WHERE Status NOT LIKE "Seed%" and AlbumID=?', [albumid])
|
||||||
|
|
||||||
|
# Check if torrent has finished seeding
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 1 or headphones.TORRENT_DOWNLOADER == 2:
|
||||||
|
seed_snatched = myDB.action('SELECT * from snatched WHERE Status="Seed_Snatched" and AlbumID=?', [albumid]).fetchone()
|
||||||
|
if seed_snatched:
|
||||||
|
hash = seed_snatched['FolderName']
|
||||||
|
torrent_removed = False
|
||||||
|
logger.info(u'%s - %s. Checking if torrent has finished seeding and can be removed' % (release['ArtistName'], release['AlbumTitle']))
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 1:
|
||||||
|
torrent_removed = transmission.removeTorrent(hash, True)
|
||||||
|
else:
|
||||||
|
torrent_removed = utorrent.removeTorrent(hash, True)
|
||||||
|
|
||||||
|
# Torrent removed, delete the snatched record, else update Status for scheduled job to check
|
||||||
|
if torrent_removed:
|
||||||
|
myDB.action('DELETE from snatched WHERE status = "Seed_Snatched" and AlbumID=?', [albumid])
|
||||||
|
else:
|
||||||
|
myDB.action('UPDATE snatched SET status = "Seed_Processed" WHERE status = "Seed_Snatched" and AlbumID=?', [albumid])
|
||||||
|
|
||||||
# Update the have tracks for all created dirs:
|
# Update the have tracks for all created dirs:
|
||||||
for albumpath in albumpaths:
|
for albumpath in albumpaths:
|
||||||
@@ -428,24 +452,27 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
|
|
||||||
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
|
logger.info(u'Post-processing for %s - %s complete' % (release['ArtistName'], release['AlbumTitle']))
|
||||||
|
|
||||||
|
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||||
|
statusmessage = "Download and Postprocessing completed"
|
||||||
|
|
||||||
if headphones.GROWL_ENABLED:
|
if headphones.GROWL_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Growl request")
|
logger.info(u"Growl request")
|
||||||
growl = notifiers.GROWL()
|
growl = notifiers.GROWL()
|
||||||
growl.notify(pushmessage,"Download and Postprocessing completed")
|
growl.notify(pushmessage, statusmessage)
|
||||||
|
|
||||||
if headphones.PROWL_ENABLED:
|
if headphones.PROWL_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Prowl request")
|
logger.info(u"Prowl request")
|
||||||
prowl = notifiers.PROWL()
|
prowl = notifiers.PROWL()
|
||||||
prowl.notify(pushmessage,"Download and Postprocessing completed")
|
prowl.notify(pushmessage, statusmessage)
|
||||||
|
|
||||||
if headphones.XBMC_ENABLED:
|
if headphones.XBMC_ENABLED:
|
||||||
xbmc = notifiers.XBMC()
|
xbmc = notifiers.XBMC()
|
||||||
if headphones.XBMC_UPDATE:
|
if headphones.XBMC_UPDATE:
|
||||||
xbmc.update()
|
xbmc.update()
|
||||||
if headphones.XBMC_NOTIFY:
|
if headphones.XBMC_NOTIFY:
|
||||||
xbmc.notify(release['ArtistName'], release['AlbumTitle'], album_art_path)
|
xbmc.notify(release['ArtistName'],
|
||||||
|
release['AlbumTitle'],
|
||||||
|
album_art_path)
|
||||||
|
|
||||||
if headphones.LMS_ENABLED:
|
if headphones.LMS_ENABLED:
|
||||||
lms = notifiers.LMS()
|
lms = notifiers.LMS()
|
||||||
@@ -456,17 +483,18 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
if headphones.PLEX_UPDATE:
|
if headphones.PLEX_UPDATE:
|
||||||
plex.update()
|
plex.update()
|
||||||
if headphones.PLEX_NOTIFY:
|
if headphones.PLEX_NOTIFY:
|
||||||
plex.notify(release['ArtistName'], release['AlbumTitle'], album_art_path)
|
plex.notify(release['ArtistName'],
|
||||||
|
release['AlbumTitle'],
|
||||||
|
album_art_path)
|
||||||
|
|
||||||
if headphones.NMA_ENABLED:
|
if headphones.NMA_ENABLED:
|
||||||
nma = notifiers.NMA()
|
nma = notifiers.NMA()
|
||||||
nma.notify(release['ArtistName'], release['AlbumTitle'])
|
nma.notify(release['ArtistName'], release['AlbumTitle'])
|
||||||
|
|
||||||
if headphones.PUSHALOT_ENABLED:
|
if headphones.PUSHALOT_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Pushalot request")
|
logger.info(u"Pushalot request")
|
||||||
pushalot = notifiers.PUSHALOT()
|
pushalot = notifiers.PUSHALOT()
|
||||||
pushalot.notify(pushmessage,"Download and Postprocessing completed")
|
pushalot.notify(pushmessage, statusmessage)
|
||||||
|
|
||||||
if headphones.SYNOINDEX_ENABLED:
|
if headphones.SYNOINDEX_ENABLED:
|
||||||
syno = notifiers.Synoindex()
|
syno = notifiers.Synoindex()
|
||||||
@@ -474,19 +502,16 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
syno.notify(albumpath)
|
syno.notify(albumpath)
|
||||||
|
|
||||||
if headphones.PUSHOVER_ENABLED:
|
if headphones.PUSHOVER_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Pushover request")
|
logger.info(u"Pushover request")
|
||||||
pushover = notifiers.PUSHOVER()
|
pushover = notifiers.PUSHOVER()
|
||||||
pushover.notify(pushmessage,"Headphones")
|
pushover.notify(pushmessage, "Headphones")
|
||||||
|
|
||||||
if headphones.PUSHBULLET_ENABLED:
|
if headphones.PUSHBULLET_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"PushBullet request")
|
logger.info(u"PushBullet request")
|
||||||
pushbullet = notifiers.PUSHBULLET()
|
pushbullet = notifiers.PUSHBULLET()
|
||||||
pushbullet.notify(pushmessage, "Download and Postprocessing completed")
|
pushbullet.notify(pushmessage, "Download and Postprocessing completed")
|
||||||
|
|
||||||
if headphones.TWITTER_ENABLED:
|
if headphones.TWITTER_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Sending Twitter notification")
|
logger.info(u"Sending Twitter notification")
|
||||||
twitter = notifiers.TwitterNotifier()
|
twitter = notifiers.TwitterNotifier()
|
||||||
twitter.notify_download(pushmessage)
|
twitter.notify_download(pushmessage)
|
||||||
@@ -494,13 +519,24 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
|||||||
if headphones.OSX_NOTIFY_ENABLED:
|
if headphones.OSX_NOTIFY_ENABLED:
|
||||||
logger.info(u"Sending OS X notification")
|
logger.info(u"Sending OS X notification")
|
||||||
osx_notify = notifiers.OSX_NOTIFY()
|
osx_notify = notifiers.OSX_NOTIFY()
|
||||||
osx_notify.notify(release['ArtistName'], release['AlbumTitle'], "Download and Postprocessing completed")
|
osx_notify.notify(release['ArtistName'],
|
||||||
|
release['AlbumTitle'],
|
||||||
|
statusmessage)
|
||||||
|
|
||||||
if headphones.BOXCAR_ENABLED:
|
if headphones.BOXCAR_ENABLED:
|
||||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
|
||||||
logger.info(u"Sending Boxcar2 notification")
|
logger.info(u"Sending Boxcar2 notification")
|
||||||
boxcar = notifiers.BOXCAR()
|
boxcar = notifiers.BOXCAR()
|
||||||
boxcar.notify('Headphones processed: ' + pushmessage, "Download and Postprocessing completed", release['AlbumID'])
|
boxcar.notify('Headphones processed: ' + pushmessage,
|
||||||
|
statusmessage, release['AlbumID'])
|
||||||
|
|
||||||
|
if headphones.SUBSONIC_ENABLED:
|
||||||
|
logger.info(u"Sending Subsonic update")
|
||||||
|
subsonic = notifiers.SubSonicNotifier()
|
||||||
|
subsonic.notify(albumpaths)
|
||||||
|
|
||||||
|
if headphones.MPC_ENABLED:
|
||||||
|
mpc = notifiers.MPC()
|
||||||
|
mpc.notify()
|
||||||
|
|
||||||
def embedAlbumArt(artwork, downloaded_track_list):
|
def embedAlbumArt(artwork, downloaded_track_list):
|
||||||
logger.info('Embedding album art')
|
logger.info('Embedding album art')
|
||||||
@@ -518,7 +554,7 @@ def embedAlbumArt(artwork, downloaded_track_list):
|
|||||||
f.art = artwork
|
f.art = artwork
|
||||||
f.save()
|
f.save()
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logger.error(u'Error ebedding album art to: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
|
logger.error(u'Error embedding album art to: %s. Error: %s' % (downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), str(e)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def addAlbumArt(artwork, albumpath, release):
|
def addAlbumArt(artwork, albumpath, release):
|
||||||
@@ -545,29 +581,41 @@ def addAlbumArt(artwork, albumpath, release):
|
|||||||
album_art_name = album_art_name.replace(' ', '_')
|
album_art_name = album_art_name.replace(' ', '_')
|
||||||
|
|
||||||
if album_art_name.startswith('.'):
|
if album_art_name.startswith('.'):
|
||||||
album_art_name = album_art_name.replace(0, '_')
|
album_art_name = album_art_name.replace(".", "_", 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file = open(os.path.join(albumpath, album_art_name), 'wb')
|
with open(os.path.join(albumpath, album_art_name), 'wb') as f:
|
||||||
file.write(artwork)
|
f.write(artwork)
|
||||||
file.close()
|
except IOError as e:
|
||||||
except Exception, e:
|
logger.error('Error saving album art: %s', e)
|
||||||
logger.error('Error saving album art: %s' % str(e))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def cleanupFiles(albumpath):
|
def cleanupFiles(albumpath):
|
||||||
logger.info('Cleaning up files')
|
logger.info('Cleaning up files')
|
||||||
|
|
||||||
for r,d,f in os.walk(albumpath):
|
for r,d,f in os.walk(albumpath):
|
||||||
for files in f:
|
for files in f:
|
||||||
if not any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
if not any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||||
logger.debug('Removing: %s' % files)
|
logger.debug('Removing: %s' % files)
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(r, files))
|
os.remove(os.path.join(r, files))
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
|
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||||
|
|
||||||
def moveFiles(albumpath, release, tracks):
|
def renameNFO(albumpath):
|
||||||
|
logger.info('Renaming NFO')
|
||||||
|
|
||||||
|
for r,d,f in os.walk(albumpath):
|
||||||
|
for file in f:
|
||||||
|
if file.lower().endswith('.nfo'):
|
||||||
|
logger.debug('Renaming: "%s" to "%s"' % (file.decode(headphones.SYS_ENCODING, 'replace'), file.decode(headphones.SYS_ENCODING, 'replace') + '-orig'))
|
||||||
|
try:
|
||||||
|
new_file_name = os.path.join(r, file)[:-3] + 'orig.nfo'
|
||||||
|
os.rename(os.path.join(r, file), new_file_name)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u'Could not rename file: %s. Error: %s' % (os.path.join(r, file).decode(headphones.SYS_ENCODING, 'replace'), e))
|
||||||
|
|
||||||
|
def moveFiles(albumpath, release, tracks):
|
||||||
try:
|
try:
|
||||||
year = release['ReleaseDate'][:4]
|
year = release['ReleaseDate'][:4]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
@@ -591,22 +639,29 @@ def moveFiles(albumpath, release, tracks):
|
|||||||
else:
|
else:
|
||||||
firstchar = sortname[0]
|
firstchar = sortname[0]
|
||||||
|
|
||||||
|
for r,d,f in os.walk(albumpath):
|
||||||
|
try:
|
||||||
|
origfolder = os.path.basename(os.path.normpath(r).decode(headphones.SYS_ENCODING, 'replace'))
|
||||||
|
except:
|
||||||
|
origfolder = u''
|
||||||
|
|
||||||
values = { '$Artist': artist,
|
values = { '$Artist': artist,
|
||||||
'$SortArtist': sortname,
|
'$SortArtist': sortname,
|
||||||
'$Album': album,
|
'$Album': album,
|
||||||
'$Year': year,
|
'$Year': year,
|
||||||
'$Type': releasetype,
|
'$Type': releasetype,
|
||||||
|
'$OriginalFolder': origfolder,
|
||||||
'$First': firstchar.upper(),
|
'$First': firstchar.upper(),
|
||||||
'$artist': artist.lower(),
|
'$artist': artist.lower(),
|
||||||
'$sortartist': sortname.lower(),
|
'$sortartist': sortname.lower(),
|
||||||
'$album': album.lower(),
|
'$album': album.lower(),
|
||||||
'$year': year,
|
'$year': year,
|
||||||
'$type': releasetype.lower(),
|
'$type': releasetype.lower(),
|
||||||
'$first': firstchar.lower()
|
'$first': firstchar.lower(),
|
||||||
|
'$originalfolder': origfolder.lower()
|
||||||
}
|
}
|
||||||
|
|
||||||
folder = helpers.replace_all(headphones.FOLDER_FORMAT.strip(), values)
|
folder = helpers.replace_all(headphones.FOLDER_FORMAT.strip(), values, normalize=True)
|
||||||
|
|
||||||
folder = helpers.replace_illegal_chars(folder, type="folder")
|
folder = helpers.replace_illegal_chars(folder, type="folder")
|
||||||
folder = folder.replace('./', '_/').replace('/.','/_')
|
folder = folder.replace('./', '_/').replace('/.','/_')
|
||||||
@@ -851,11 +906,11 @@ def embedLyrics(downloaded_track_list):
|
|||||||
# TODO: If adding lyrics for flac & lossy, only fetch the lyrics once
|
# TODO: If adding lyrics for flac & lossy, only fetch the lyrics once
|
||||||
# and apply it to both files
|
# and apply it to both files
|
||||||
for downloaded_track in downloaded_track_list:
|
for downloaded_track in downloaded_track_list:
|
||||||
|
track_title = downloaded_track.decode(headphones.SYS_ENCODING, 'replace')
|
||||||
try:
|
try:
|
||||||
f = MediaFile(downloaded_track)
|
f = MediaFile(downloaded_track)
|
||||||
except:
|
except:
|
||||||
logger.error('Could not read %s. Not checking lyrics', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.error('Could not read %s. Not checking lyrics', track_title)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if f.albumartist and f.title:
|
if f.albumartist and f.title:
|
||||||
@@ -863,17 +918,20 @@ def embedLyrics(downloaded_track_list):
|
|||||||
elif f.artist and f.title:
|
elif f.artist and f.title:
|
||||||
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
||||||
else:
|
else:
|
||||||
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.info('No artist/track metadata found for track: %s. Not fetching lyrics', track_title)
|
||||||
metalyrics = None
|
metalyrics = None
|
||||||
|
|
||||||
if lyrics:
|
if metalyrics:
|
||||||
logger.debug('Adding lyrics to: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.debug('Adding lyrics to: %s', track_title)
|
||||||
f.lyrics = metalyrics
|
f.lyrics = metalyrics
|
||||||
try:
|
try:
|
||||||
f.save()
|
f.save()
|
||||||
except:
|
except:
|
||||||
logger.error('Cannot save lyrics to: %s. Skipping', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
logger.error('Cannot save lyrics to: %s. Skipping', track_title)
|
||||||
continue
|
continue
|
||||||
|
else:
|
||||||
|
logger.debug('No lyrics found for track: %s', track_title)
|
||||||
|
|
||||||
|
|
||||||
def renameFiles(albumpath, downloaded_track_list, release):
|
def renameFiles(albumpath, downloaded_track_list, release):
|
||||||
logger.info('Renaming files')
|
logger.info('Renaming files')
|
||||||
@@ -948,7 +1006,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
|||||||
new_file_name = new_file_name.replace(' ', '_')
|
new_file_name = new_file_name.replace(' ', '_')
|
||||||
|
|
||||||
if new_file_name.startswith('.'):
|
if new_file_name.startswith('.'):
|
||||||
new_file_name = new_file_name.replace(0, '_')
|
new_file_name = new_file_name.replace(".", "_", 1)
|
||||||
|
|
||||||
new_file = os.path.join(albumpath, new_file_name)
|
new_file = os.path.join(albumpath, new_file_name)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -33,22 +54,38 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
|||||||
try:
|
try:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
except:
|
except:
|
||||||
logger.debug("Response status code %d is not white listed, raised exception", response.status_code)
|
logger.debug("Response status code %d is not white " \
|
||||||
|
"listed, raised exception", response.status_code)
|
||||||
raise
|
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):
|
||||||
@@ -91,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.
|
||||||
@@ -116,3 +157,45 @@ def request_feed(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)
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
## Stolen from Sick-Beard's sab.py ##
|
## Stolen from Sick-Beard's sab.py ##
|
||||||
#####################################
|
#####################################
|
||||||
|
|
||||||
import urllib, httplib
|
import MultipartPostHandler
|
||||||
import datetime
|
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
import datetime
|
||||||
from lib import MultipartPostHandler
|
import cookielib
|
||||||
import urllib2, cookielib
|
import urllib2
|
||||||
|
import httplib
|
||||||
|
import urllib
|
||||||
import ast
|
import ast
|
||||||
|
|
||||||
from headphones.common import USER_AGENT
|
from headphones.common import USER_AGENT
|
||||||
@@ -62,7 +62,7 @@ def sendNZB(nzb):
|
|||||||
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
# Sanitize the file a bit, since we can only use ascii chars with MultiPartPostHandler
|
||||||
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
||||||
params['mode'] = 'addfile'
|
params['mode'] = 'addfile'
|
||||||
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzbdata)}
|
multiPartParams = {"nzbfile": (helpers.latinToAscii(nzb.name)+".nzb", nzbdata)}
|
||||||
|
|
||||||
if not headphones.SAB_HOST.startswith('http'):
|
if not headphones.SAB_HOST.startswith('http'):
|
||||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||||
|
|||||||
@@ -4,20 +4,26 @@
|
|||||||
# Headphones rutracker.org search
|
# Headphones rutracker.org search
|
||||||
# Functions called from searcher.py
|
# Functions called from searcher.py
|
||||||
|
|
||||||
import urllib
|
from headphones import logger, db, utorrent
|
||||||
import urllib2
|
|
||||||
import cookielib
|
from bencode import bencode as bencode, bdecode
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import headphones
|
|
||||||
from headphones import logger, db
|
|
||||||
import lib.bencode as bencode
|
|
||||||
import os
|
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
from hashlib import sha1
|
||||||
|
|
||||||
|
import headphones
|
||||||
|
import requests
|
||||||
|
import cookielib
|
||||||
|
import urllib2
|
||||||
|
import urllib
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
|
||||||
class Rutracker():
|
class Rutracker():
|
||||||
|
|
||||||
logged_in = False
|
logged_in = False
|
||||||
|
|
||||||
# Stores a number of login attempts to prevent recursion.
|
# Stores a number of login attempts to prevent recursion.
|
||||||
#login_counter = 0
|
#login_counter = 0
|
||||||
|
|
||||||
@@ -47,11 +53,10 @@ class Rutracker():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
||||||
except :
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Check if we're logged in
|
# Check if we're logged in
|
||||||
|
|
||||||
for cookie in self.cookiejar:
|
for cookie in self.cookiejar:
|
||||||
if cookie.name == 'bb_data':
|
if cookie.name == 'bb_data':
|
||||||
self.logged_in = True
|
self.logged_in = True
|
||||||
@@ -64,7 +69,6 @@ class Rutracker():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Build search url
|
# Build search url
|
||||||
|
|
||||||
searchterm = ''
|
searchterm = ''
|
||||||
if artist != 'Various Artists':
|
if artist != 'Various Artists':
|
||||||
searchterm = artist
|
searchterm = artist
|
||||||
@@ -83,14 +87,13 @@ class Rutracker():
|
|||||||
format = '+mp3||aac'
|
format = '+mp3||aac'
|
||||||
|
|
||||||
# sort by size, descending.
|
# sort by size, descending.
|
||||||
|
|
||||||
sort = '&o=7&s=2'
|
sort = '&o=7&s=2'
|
||||||
|
|
||||||
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
searchurl = "%s?nm=%s%s%s" % (providerurl, urllib.quote(searchterm), format, sort)
|
||||||
|
|
||||||
return searchurl
|
return searchurl
|
||||||
|
|
||||||
def search(self, searchurl, maxsize, minseeders, albumid, bitrate):
|
def search(self, searchurl, maxsize, minseeders, albumid):
|
||||||
"""
|
"""
|
||||||
Parse the search results and return valid torrent list
|
Parse the search results and return valid torrent list
|
||||||
"""
|
"""
|
||||||
@@ -111,25 +114,21 @@ class Rutracker():
|
|||||||
#logger.debug (soup.prettify())
|
#logger.debug (soup.prettify())
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
|
|
||||||
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
for link in soup.find_all('a', attrs={'class' : 'med tLink hl-tags bold'}):
|
||||||
title = link.get_text()
|
title = link.get_text()
|
||||||
titles.append(title)
|
titles.append(title)
|
||||||
|
|
||||||
# Download URL
|
# Download URL
|
||||||
|
|
||||||
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
for link in soup.find_all('a', attrs={'class' : 'small tr-dl dl-stub'}):
|
||||||
url = link.get('href')
|
url = link.get('href')
|
||||||
urls.append(url)
|
urls.append(url)
|
||||||
|
|
||||||
# Seeders
|
# Seeders
|
||||||
|
|
||||||
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
for link in soup.find_all('b', attrs={'class' : 'seedmed'}):
|
||||||
seeder = link.get_text()
|
seeder = link.get_text()
|
||||||
seeders.append(seeder)
|
seeders.append(seeder)
|
||||||
|
|
||||||
# Size
|
# Size
|
||||||
|
|
||||||
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
for link in soup.find_all('td', attrs={'class' : 'row4 small nowrap tor-size'}):
|
||||||
size = link.u.string
|
size = link.u.string
|
||||||
sizes.append(size)
|
sizes.append(size)
|
||||||
@@ -138,30 +137,33 @@ class Rutracker():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Combine lists
|
# Combine lists
|
||||||
|
|
||||||
torrentlist = zip(titles, urls, seeders, sizes)
|
torrentlist = zip(titles, urls, seeders, sizes)
|
||||||
|
|
||||||
# return if nothing found
|
# return if nothing found
|
||||||
|
|
||||||
if not torrentlist:
|
if not torrentlist:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# get headphones track count for album, return if not found
|
# don't bother checking track counts anymore, let searcher filter instead
|
||||||
|
# leave code in just in case
|
||||||
|
check_track_count = False
|
||||||
|
|
||||||
myDB = db.DBConnection()
|
if check_track_count:
|
||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
|
||||||
hptrackcount = len(tracks)
|
|
||||||
|
|
||||||
if not hptrackcount:
|
# get headphones track count for album, return if not found
|
||||||
logger.info('headphones track info not found, cannot compare to torrent')
|
myDB = db.DBConnection()
|
||||||
return False
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||||
|
hptrackcount = len(tracks)
|
||||||
|
|
||||||
# Return all valid entries, ignored, required words now checked in searcher.py
|
if not hptrackcount:
|
||||||
|
logger.info('headphones track info not found, cannot compare to torrent')
|
||||||
|
return False
|
||||||
|
|
||||||
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
# Return all valid entries, ignored, required words now checked in searcher.py
|
||||||
|
|
||||||
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
#unwantedlist = ['promo', 'vinyl', '[lp]', 'songbook', 'tvrip', 'hdtv', 'dvd']
|
||||||
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
|
||||||
|
formatlist = ['ape', 'flac', 'ogg', 'm4a', 'aac', 'mp3', 'wav', 'aif']
|
||||||
|
deluxelist = ['deluxe', 'edition', 'japanese', 'exclusive']
|
||||||
|
|
||||||
for torrent in torrentlist:
|
for torrent in torrentlist:
|
||||||
|
|
||||||
@@ -170,104 +172,101 @@ class Rutracker():
|
|||||||
seeders = torrent[2]
|
seeders = torrent[2]
|
||||||
size = torrent[3]
|
size = torrent[3]
|
||||||
|
|
||||||
title = returntitle.lower()
|
|
||||||
|
|
||||||
if int(size) <= maxsize and int(seeders) >= minseeders:
|
if int(size) <= maxsize and int(seeders) >= minseeders:
|
||||||
|
|
||||||
# Check torrent info
|
|
||||||
|
|
||||||
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
|
||||||
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
|
||||||
|
|
||||||
# Debug
|
|
||||||
#for cookie in self.cookiejar:
|
|
||||||
# logger.debug ('Cookie: %s' % cookie)
|
|
||||||
|
|
||||||
try:
|
|
||||||
page = self.opener.open(url)
|
|
||||||
torrent = page.read()
|
|
||||||
if torrent:
|
|
||||||
decoded = bencode.bdecode(torrent)
|
|
||||||
metainfo = decoded['info']
|
|
||||||
page.close ()
|
|
||||||
except Exception, e:
|
|
||||||
logger.error('Error getting torrent: %s' % e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# get torrent track count and check for cue
|
|
||||||
|
|
||||||
trackcount = 0
|
|
||||||
cuecount = 0
|
|
||||||
|
|
||||||
if 'files' in metainfo: # multi
|
|
||||||
for pathfile in metainfo['files']:
|
|
||||||
path = pathfile['path']
|
|
||||||
for file in path:
|
|
||||||
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
|
||||||
trackcount += 1
|
|
||||||
if '.cue' in file:
|
|
||||||
cuecount += 1
|
|
||||||
|
|
||||||
#Torrent topic page
|
#Torrent topic page
|
||||||
|
torrent_id = dict([part.split('=') for part in urlparse(url)[4].split('&')])['t']
|
||||||
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
topicurl = 'http://rutracker.org/forum/viewtopic.php?t=' + torrent_id
|
||||||
logger.debug ('torrent title: %s' % title)
|
|
||||||
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
|
||||||
logger.debug ('rutracker trackcount: %s' % trackcount)
|
|
||||||
|
|
||||||
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
# add to list
|
||||||
# This is for the case where we have a single .flac/.wav which can be split by cue
|
if not check_track_count:
|
||||||
# Not great, but shouldn't be doing this too often
|
|
||||||
|
|
||||||
totallogcount = 0
|
|
||||||
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
|
||||||
page = self.opener.open(topicurl, timeout=60)
|
|
||||||
soup = BeautifulSoup(page.read())
|
|
||||||
findtoc = soup.find_all(text='TOC of the extracted CD')
|
|
||||||
if not findtoc:
|
|
||||||
findtoc = soup.find_all(text='TOC извлечённого CD')
|
|
||||||
for toc in findtoc:
|
|
||||||
logcount = 0
|
|
||||||
for toccontent in toc.find_all_next(text=True):
|
|
||||||
cut_string = toccontent.split('|')
|
|
||||||
new_string = cut_string[0].lstrip().rstrip()
|
|
||||||
if new_string == '1' or new_string == '01':
|
|
||||||
logcount = 1
|
|
||||||
elif logcount > 0:
|
|
||||||
if new_string.isdigit():
|
|
||||||
logcount += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
totallogcount = totallogcount + logcount
|
|
||||||
|
|
||||||
if totallogcount > 0:
|
|
||||||
trackcount = totallogcount
|
|
||||||
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
|
||||||
|
|
||||||
# If torrent track count = hp track count then return torrent,
|
|
||||||
# if greater, check for deluxe/special/foreign editions
|
|
||||||
# if less, then allow if it's a single track with a cue
|
|
||||||
|
|
||||||
valid = False
|
|
||||||
|
|
||||||
if trackcount == hptrackcount:
|
|
||||||
valid = True
|
valid = True
|
||||||
elif trackcount > hptrackcount:
|
else:
|
||||||
if any(deluxe in title for deluxe in deluxelist):
|
|
||||||
|
# Check torrent info
|
||||||
|
self.cookiejar.set_cookie(cookielib.Cookie(version=0, name='bb_dl', value=torrent_id, port=None, port_specified=False, domain='.rutracker.org', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False))
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
#for cookie in self.cookiejar:
|
||||||
|
# logger.debug ('Cookie: %s' % cookie)
|
||||||
|
|
||||||
|
try:
|
||||||
|
page = self.opener.open(url)
|
||||||
|
torrent = page.read()
|
||||||
|
if torrent:
|
||||||
|
decoded = bdecode(torrent)
|
||||||
|
metainfo = decoded['info']
|
||||||
|
page.close ()
|
||||||
|
except Exception, e:
|
||||||
|
logger.error('Error getting torrent: %s' % e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# get torrent track count and check for cue
|
||||||
|
trackcount = 0
|
||||||
|
cuecount = 0
|
||||||
|
|
||||||
|
if 'files' in metainfo: # multi
|
||||||
|
for pathfile in metainfo['files']:
|
||||||
|
path = pathfile['path']
|
||||||
|
for file in path:
|
||||||
|
if any(file.lower().endswith('.' + x.lower()) for x in formatlist):
|
||||||
|
trackcount += 1
|
||||||
|
if '.cue' in file:
|
||||||
|
cuecount += 1
|
||||||
|
|
||||||
|
title = returntitle.lower()
|
||||||
|
logger.debug ('torrent title: %s' % title)
|
||||||
|
logger.debug ('headphones trackcount: %s' % hptrackcount)
|
||||||
|
logger.debug ('rutracker trackcount: %s' % trackcount)
|
||||||
|
|
||||||
|
# If torrent track count less than headphones track count, and there's a cue, then attempt to get track count from log(s)
|
||||||
|
# This is for the case where we have a single .flac/.wav which can be split by cue
|
||||||
|
# Not great, but shouldn't be doing this too often
|
||||||
|
totallogcount = 0
|
||||||
|
if trackcount < hptrackcount and cuecount > 0 and cuecount < hptrackcount:
|
||||||
|
page = self.opener.open(topicurl, timeout=60)
|
||||||
|
soup = BeautifulSoup(page.read())
|
||||||
|
findtoc = soup.find_all(text='TOC of the extracted CD')
|
||||||
|
if not findtoc:
|
||||||
|
findtoc = soup.find_all(text='TOC извлечённого CD')
|
||||||
|
for toc in findtoc:
|
||||||
|
logcount = 0
|
||||||
|
for toccontent in toc.find_all_next(text=True):
|
||||||
|
cut_string = toccontent.split('|')
|
||||||
|
new_string = cut_string[0].lstrip().rstrip()
|
||||||
|
if new_string == '1' or new_string == '01':
|
||||||
|
logcount = 1
|
||||||
|
elif logcount > 0:
|
||||||
|
if new_string.isdigit():
|
||||||
|
logcount += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
totallogcount = totallogcount + logcount
|
||||||
|
|
||||||
|
if totallogcount > 0:
|
||||||
|
trackcount = totallogcount
|
||||||
|
logger.debug ('rutracker logtrackcount: %s' % totallogcount)
|
||||||
|
|
||||||
|
# If torrent track count = hp track count then return torrent,
|
||||||
|
# if greater, check for deluxe/special/foreign editions
|
||||||
|
# if less, then allow if it's a single track with a cue
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
if trackcount == hptrackcount:
|
||||||
valid = True
|
valid = True
|
||||||
|
elif trackcount > hptrackcount:
|
||||||
|
if any(deluxe in title for deluxe in deluxelist):
|
||||||
|
valid = True
|
||||||
|
|
||||||
# Add to list
|
# Add to list
|
||||||
|
|
||||||
if valid:
|
if valid:
|
||||||
rulist.append((returntitle, size, topicurl))
|
rulist.append((returntitle, size, topicurl))
|
||||||
else:
|
else:
|
||||||
if topicurl:
|
if topicurl:
|
||||||
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
logger.info(u'<a href="%s">Torrent</a> found with %s tracks but the selected headphones release has %s tracks, skipping for rutracker.org' % (topicurl, trackcount, hptrackcount))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
logger.info('%s is larger than the maxsize or has too little seeders for this category, skipping. (Size: %i bytes, Seeders: %i)' % (returntitle, int(size), int(seeders)))
|
||||||
|
|
||||||
|
|
||||||
return rulist
|
return rulist
|
||||||
|
|
||||||
def get_torrent(self, url, savelocation=None):
|
def get_torrent(self, url, savelocation=None):
|
||||||
@@ -281,17 +280,70 @@ class Rutracker():
|
|||||||
prev = os.umask(headphones.UMASK)
|
prev = os.umask(headphones.UMASK)
|
||||||
page = self.opener.open(downloadurl)
|
page = self.opener.open(downloadurl)
|
||||||
torrent = page.read()
|
torrent = page.read()
|
||||||
|
decoded = bdecode(torrent)
|
||||||
|
metainfo = decoded['info']
|
||||||
|
tor_hash = sha1(bencode(metainfo)).hexdigest()
|
||||||
if savelocation:
|
if savelocation:
|
||||||
download_path = os.path.join(savelocation, torrent_name)
|
download_path = os.path.join(savelocation, torrent_name)
|
||||||
else:
|
else:
|
||||||
tempdir = mkdtemp(suffix='_rutracker_torrents')
|
tempdir = mkdtemp(suffix='_rutracker_torrents')
|
||||||
download_path = os.path.join(tempdir, torrent_name)
|
download_path = os.path.join(tempdir, torrent_name)
|
||||||
fp = open (download_path, 'wb')
|
|
||||||
fp.write (torrent)
|
with open(download_path, 'wb') as f:
|
||||||
fp.close ()
|
f.write(torrent)
|
||||||
os.umask(prev)
|
os.umask(prev)
|
||||||
except Exception, e:
|
|
||||||
logger.error('Error getting torrent: %s' % e)
|
# Add file to utorrent
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 2:
|
||||||
|
self.utorrent_add_file(download_path)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Error getting torrent: %s', e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return download_path
|
return download_path, tor_hash
|
||||||
|
|
||||||
|
#TODO get this working in utorrent.py
|
||||||
|
def utorrent_add_file(self, filename):
|
||||||
|
|
||||||
|
host = headphones.UTORRENT_HOST
|
||||||
|
if not host.startswith('http'):
|
||||||
|
host = 'http://' + host
|
||||||
|
if host.endswith('/'):
|
||||||
|
host = host[:-1]
|
||||||
|
if host.endswith('/gui'):
|
||||||
|
host = host[:-4]
|
||||||
|
|
||||||
|
base_url = host
|
||||||
|
username = headphones.UTORRENT_USERNAME
|
||||||
|
password = headphones.UTORRENT_PASSWORD
|
||||||
|
|
||||||
|
session = requests.Session()
|
||||||
|
url = base_url + '/gui/'
|
||||||
|
session.auth = (username, password)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = session.get(url + 'token.html')
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Error getting token')
|
||||||
|
return
|
||||||
|
|
||||||
|
if r.status_code == '401':
|
||||||
|
logger.debug('Error reaching utorrent')
|
||||||
|
return
|
||||||
|
|
||||||
|
regex = re.search(r'.+>([^<]+)</div></html>', r.text)
|
||||||
|
if regex is None:
|
||||||
|
logger.debug('Error reading token')
|
||||||
|
return
|
||||||
|
|
||||||
|
session.params = {'token': regex.group(1)}
|
||||||
|
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
try:
|
||||||
|
session.post(url, params={'action': 'add-file'},
|
||||||
|
files={'torrent_file': f})
|
||||||
|
except Exception:
|
||||||
|
logger.exception('Error adding file to utorrent')
|
||||||
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# This file is part of Headphones.
|
||||||
|
#
|
||||||
|
# Headphones is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Headphones is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import headphones
|
||||||
|
from headphones import db, utorrent, transmission, logger
|
||||||
|
|
||||||
|
postprocessor_lock = threading.Lock()
|
||||||
|
|
||||||
|
# Remove Torrent + data if Post Processed and finished Seeding
|
||||||
|
def checkTorrentFinished():
|
||||||
|
|
||||||
|
logger.info("Checking if any torrents have finished seeding and can be removed")
|
||||||
|
|
||||||
|
with postprocessor_lock:
|
||||||
|
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
results = myDB.select('SELECT * from snatched WHERE Status="Seed_Processed"')
|
||||||
|
|
||||||
|
for album in results:
|
||||||
|
hash = album['FolderName']
|
||||||
|
albumid = album['AlbumID']
|
||||||
|
torrent_removed = False
|
||||||
|
if headphones.TORRENT_DOWNLOADER == 1:
|
||||||
|
torrent_removed = transmission.removeTorrent(hash, True)
|
||||||
|
else:
|
||||||
|
torrent_removed = utorrent.removeTorrent(hash, True)
|
||||||
|
|
||||||
|
if torrent_removed:
|
||||||
|
myDB.action('DELETE from snatched WHERE status = "Seed_Processed" and AlbumID=?', [albumid])
|
||||||
@@ -13,15 +13,14 @@
|
|||||||
# 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, notifiers, request
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
import base64
|
import base64
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
import simplejson as json
|
|
||||||
|
|
||||||
from headphones import logger, notifiers, request
|
|
||||||
|
|
||||||
# This is just a simple script to send torrents to transmission. The
|
# This is just a simple script to send torrents to transmission. The
|
||||||
# intention is to turn this into a class where we can check the state
|
# intention is to turn this into a class where we can check the state
|
||||||
# of the download, set the download dir, etc.
|
# of the download, set the download dir, etc.
|
||||||
@@ -30,7 +29,13 @@ from headphones import logger, notifiers, request
|
|||||||
|
|
||||||
def addTorrent(link):
|
def addTorrent(link):
|
||||||
method = 'torrent-add'
|
method = 'torrent-add'
|
||||||
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
|
||||||
|
if link.endswith('.torrent'):
|
||||||
|
with open(link, 'rb') as f:
|
||||||
|
metainfo = str(base64.b64encode(f.read()))
|
||||||
|
arguments = {'metainfo': metainfo, 'download-dir':headphones.DOWNLOAD_TORRENT_DIR}
|
||||||
|
else:
|
||||||
|
arguments = {'filename': link, 'download-dir': headphones.DOWNLOAD_TORRENT_DIR}
|
||||||
|
|
||||||
response = torrentAction(method,arguments)
|
response = torrentAction(method,arguments)
|
||||||
|
|
||||||
@@ -40,10 +45,10 @@ def addTorrent(link):
|
|||||||
if response['result'] == 'success':
|
if response['result'] == 'success':
|
||||||
if 'torrent-added' in response['arguments']:
|
if 'torrent-added' in response['arguments']:
|
||||||
name = response['arguments']['torrent-added']['name']
|
name = response['arguments']['torrent-added']['name']
|
||||||
retid = response['arguments']['torrent-added']['id']
|
retid = response['arguments']['torrent-added']['hashString']
|
||||||
elif 'torrent-duplicate' in response['arguments']:
|
elif 'torrent-duplicate' in response['arguments']:
|
||||||
name = response['arguments']['torrent-duplicate']['name']
|
name = response['arguments']['torrent-duplicate']['name']
|
||||||
retid = response['arguments']['torrent-duplicate']['id']
|
retid = response['arguments']['torrent-duplicate']['hashString']
|
||||||
else:
|
else:
|
||||||
name = link
|
name = link
|
||||||
retid = False
|
retid = False
|
||||||
@@ -75,6 +80,46 @@ def getTorrentFolder(torrentid):
|
|||||||
|
|
||||||
return torrent_folder_name
|
return torrent_folder_name
|
||||||
|
|
||||||
|
def setSeedRatio(torrentid, ratio):
|
||||||
|
method = 'torrent-set'
|
||||||
|
if ratio != 0:
|
||||||
|
arguments = {'seedRatioLimit': ratio, 'seedRatioMode': 1, 'ids': torrentid}
|
||||||
|
else:
|
||||||
|
arguments = {'seedRatioMode': 2, 'ids': torrentid}
|
||||||
|
|
||||||
|
response = torrentAction(method, arguments)
|
||||||
|
if not response:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def removeTorrent(torrentid, remove_data = False):
|
||||||
|
|
||||||
|
method = 'torrent-get'
|
||||||
|
arguments = { 'ids': torrentid, 'fields': ['isFinished', 'name']}
|
||||||
|
|
||||||
|
response = torrentAction(method, arguments)
|
||||||
|
if not response:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
finished = response['arguments']['torrents'][0]['isFinished']
|
||||||
|
name = response['arguments']['torrents'][0]['name']
|
||||||
|
|
||||||
|
if finished:
|
||||||
|
logger.info('%s has finished seeding, removing torrent and data' % name)
|
||||||
|
method = 'torrent-remove'
|
||||||
|
if remove_data:
|
||||||
|
arguments = {'delete-local-data': True, 'ids': torrentid}
|
||||||
|
else:
|
||||||
|
arguments = {'ids': torrentid}
|
||||||
|
response = torrentAction(method, arguments)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.info('%s has not finished seeding yet, torrent will not be removed, will try again on next run' % name)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def torrentAction(method, arguments):
|
def torrentAction(method, arguments):
|
||||||
|
|
||||||
host = headphones.TRANSMISSION_HOST
|
host = headphones.TRANSMISSION_HOST
|
||||||
|
|||||||
@@ -12,3 +12,245 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import urllib, urllib2, urlparse, cookielib
|
||||||
|
import json, re, os, time
|
||||||
|
|
||||||
|
import headphones
|
||||||
|
|
||||||
|
from headphones import logger
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
class utorrentclient(object):
|
||||||
|
|
||||||
|
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
|
||||||
|
UTSetting = namedtuple("UTSetting", ["name", "int", "str", "access"])
|
||||||
|
|
||||||
|
def __init__(self, base_url = None, username = None, password = None,):
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
self.base_url = host
|
||||||
|
self.username = headphones.UTORRENT_USERNAME
|
||||||
|
self.password = headphones.UTORRENT_PASSWORD
|
||||||
|
self.opener = self._make_opener('uTorrent', self.base_url, self.username, self.password)
|
||||||
|
self.token = self._get_token()
|
||||||
|
#TODO refresh token, when necessary
|
||||||
|
|
||||||
|
def _make_opener(self, realm, base_url, username, password):
|
||||||
|
"""uTorrent API need HTTP Basic Auth and cookie support for token verify."""
|
||||||
|
auth = urllib2.HTTPBasicAuthHandler()
|
||||||
|
auth.add_password(realm=realm,uri=base_url,user=username,passwd=password)
|
||||||
|
opener = urllib2.build_opener(auth)
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
|
||||||
|
cookie_jar = cookielib.CookieJar()
|
||||||
|
cookie_handler = urllib2.HTTPCookieProcessor(cookie_jar)
|
||||||
|
|
||||||
|
handlers = [auth, cookie_handler]
|
||||||
|
opener = urllib2.build_opener(*handlers)
|
||||||
|
return opener
|
||||||
|
|
||||||
|
def _get_token(self):
|
||||||
|
url = urlparse.urljoin(self.base_url, 'gui/token.html')
|
||||||
|
try:
|
||||||
|
response = self.opener.open(url)
|
||||||
|
except urllib2.HTTPError as err:
|
||||||
|
logger.debug('URL: ' + str(url))
|
||||||
|
logger.debug('Error getting Token. uTorrent responded with error: ' + str(err))
|
||||||
|
match = re.search(utorrentclient.TOKEN_REGEX, response.read())
|
||||||
|
return match.group(1)
|
||||||
|
|
||||||
|
def list(self, **kwargs):
|
||||||
|
params = [('list', '1')]
|
||||||
|
params += kwargs.items()
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def add_url(self, url):
|
||||||
|
#can receive magnet or normal .torrent link
|
||||||
|
params = [('action', 'add-url'), ('s', url)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def start(self, *hashes):
|
||||||
|
params = [('action', 'start'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def stop(self, *hashes):
|
||||||
|
params = [('action', 'stop'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def pause(self, *hashes):
|
||||||
|
params = [('action', 'pause'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def forcestart(self, *hashes):
|
||||||
|
params = [('action', 'forcestart'), ]
|
||||||
|
for hash in hashes:
|
||||||
|
params.append(('hash', hash))
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def getfiles(self, hash):
|
||||||
|
params = [('action', 'getfiles'), ('hash', hash)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def getprops(self, hash):
|
||||||
|
params = [('action', 'getprops'), ('hash', hash)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def setprops(self, hash, s, v):
|
||||||
|
params = [('action', 'setprops'), ('hash', hash), ("s", s), ("v", v)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def setprio(self, hash, priority, *files):
|
||||||
|
params = [('action', 'setprio'), ('hash', hash), ('p', str(priority))]
|
||||||
|
for file_index in files:
|
||||||
|
params.append(('f', str(file_index)))
|
||||||
|
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def get_settings(self, key=None):
|
||||||
|
params = [('action', 'getsettings'), ]
|
||||||
|
status, value = self._action(params)
|
||||||
|
settings = {}
|
||||||
|
for args in value['settings']:
|
||||||
|
settings[args[0]] = self.UTSetting(*args)
|
||||||
|
if key:
|
||||||
|
return settings[key]
|
||||||
|
return settings
|
||||||
|
|
||||||
|
def remove(self, hash, remove_data = False):
|
||||||
|
if remove_data:
|
||||||
|
params = [('action', 'removedata'), ('hash', hash)]
|
||||||
|
else:
|
||||||
|
params = [('action', 'remove'), ('hash', hash)]
|
||||||
|
return self._action(params)
|
||||||
|
|
||||||
|
def _action(self, params, body=None, content_type=None):
|
||||||
|
url = self.base_url + '/gui/' + '?token=' + self.token + '&' + urllib.urlencode(params)
|
||||||
|
request = urllib2.Request(url)
|
||||||
|
|
||||||
|
if body:
|
||||||
|
request.add_data(body)
|
||||||
|
request.add_header('Content-length', len(body))
|
||||||
|
if content_type:
|
||||||
|
request.add_header('Content-type', content_type)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = self.opener.open(request)
|
||||||
|
return response.code, json.loads(response.read())
|
||||||
|
except urllib2.HTTPError as err:
|
||||||
|
logger.debug('URL: ' + str(url))
|
||||||
|
logger.debug('uTorrent webUI raised the following error: ' + str(err))
|
||||||
|
|
||||||
|
def labelTorrent(hash):
|
||||||
|
label = headphones.UTORRENT_LABEL
|
||||||
|
uTorrentClient = utorrentclient()
|
||||||
|
if label:
|
||||||
|
uTorrentClient.setprops(hash,'label',label)
|
||||||
|
|
||||||
|
def removeTorrent(hash, remove_data = False):
|
||||||
|
uTorrentClient = utorrentclient()
|
||||||
|
status, torrentList = uTorrentClient.list()
|
||||||
|
torrents = torrentList['torrents']
|
||||||
|
for torrent in torrents:
|
||||||
|
if torrent[0].lower() == hash:
|
||||||
|
if torrent[21] == 'Finished':
|
||||||
|
logger.info('%s has finished seeding, removing torrent and data' % torrent[2])
|
||||||
|
uTorrentClient.remove(hash, remove_data)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.info('%s has not finished seeding yet, torrent will not be removed, will try again on next run' % torrent[2])
|
||||||
|
return False
|
||||||
|
return False
|
||||||
|
|
||||||
|
def setSeedRatio(hash, ratio):
|
||||||
|
uTorrentClient = utorrentclient()
|
||||||
|
uTorrentClient.setprops(hash, 'seed_override', '1')
|
||||||
|
if ratio != 0:
|
||||||
|
uTorrentClient.setprops(hash,'seed_ratio', ratio * 10)
|
||||||
|
else:
|
||||||
|
# TODO passing -1 should be unlimited
|
||||||
|
uTorrentClient.setprops(hash,'seed_ratio', -10)
|
||||||
|
|
||||||
|
def dirTorrent(hash, cacheid=None, return_name=None):
|
||||||
|
|
||||||
|
uTorrentClient = utorrentclient()
|
||||||
|
|
||||||
|
if not cacheid:
|
||||||
|
status, torrentList = uTorrentClient.list()
|
||||||
|
else:
|
||||||
|
params = [('list', '1'), ('cid', cacheid)]
|
||||||
|
status, torrentList = uTorrentClient._action(params)
|
||||||
|
|
||||||
|
if 'torrentp' in torrentList:
|
||||||
|
torrents = torrentList['torrentp']
|
||||||
|
else:
|
||||||
|
torrents = torrentList['torrents']
|
||||||
|
|
||||||
|
cacheid = torrentList['torrentc']
|
||||||
|
|
||||||
|
for torrent in torrents:
|
||||||
|
if (torrent[0].lower() == hash):
|
||||||
|
if not return_name:
|
||||||
|
return torrent[26], cacheid
|
||||||
|
else:
|
||||||
|
return torrent[2], cacheid
|
||||||
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def addTorrent(link, hash):
|
||||||
|
uTorrentClient = utorrentclient()
|
||||||
|
|
||||||
|
# Get Active Directory from settings
|
||||||
|
active_dir, completed_dir = getSettingsDirectories()
|
||||||
|
|
||||||
|
if not active_dir:
|
||||||
|
logger.error('Could not get "Put new downloads in:" directory from uTorrent settings, please ensure it is set')
|
||||||
|
return None
|
||||||
|
|
||||||
|
uTorrentClient.add_url(link)
|
||||||
|
|
||||||
|
# Get Torrent Folder Name
|
||||||
|
torrent_folder, cacheid = dirTorrent(hash)
|
||||||
|
|
||||||
|
# If there's no folder yet then it's probably a magnet, try until folder is populated
|
||||||
|
if torrent_folder == active_dir or not torrent_folder:
|
||||||
|
tries = 1
|
||||||
|
while (torrent_folder == active_dir or torrent_folder == None) and tries <= 10:
|
||||||
|
tries += 1
|
||||||
|
time.sleep(6)
|
||||||
|
torrent_folder, cacheid = dirTorrent(hash, cacheid)
|
||||||
|
|
||||||
|
if torrent_folder == active_dir or not torrent_folder:
|
||||||
|
torrent_folder, cacheid = dirTorrent(hash, cacheid, return_name=True)
|
||||||
|
labelTorrent(hash)
|
||||||
|
return torrent_folder
|
||||||
|
else:
|
||||||
|
labelTorrent(hash)
|
||||||
|
return os.path.basename(os.path.normpath(torrent_folder))
|
||||||
|
|
||||||
|
def getSettingsDirectories():
|
||||||
|
uTorrentClient = utorrentclient()
|
||||||
|
settings = uTorrentClient.get_settings()
|
||||||
|
active = None
|
||||||
|
completed = None
|
||||||
|
if 'dir_active_download' in settings:
|
||||||
|
active = settings['dir_active_download'][2]
|
||||||
|
if 'dir_completed_download' in settings:
|
||||||
|
completed = settings['dir_completed_download'][2]
|
||||||
|
return active, completed
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,14 @@
|
|||||||
# 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 platform, subprocess, re, os, tarfile
|
import re
|
||||||
|
import os
|
||||||
|
import tarfile
|
||||||
|
import platform
|
||||||
import headphones
|
import headphones
|
||||||
from headphones import logger, version, request
|
import subprocess
|
||||||
from headphones.exceptions import ex
|
|
||||||
|
|
||||||
import lib.simplejson as simplejson
|
from headphones import logger, version, request
|
||||||
|
|
||||||
def runGit(args):
|
def runGit(args):
|
||||||
|
|
||||||
@@ -61,7 +62,6 @@ def runGit(args):
|
|||||||
def getVersion():
|
def getVersion():
|
||||||
|
|
||||||
if version.HEADPHONES_VERSION.startswith('win32build'):
|
if version.HEADPHONES_VERSION.startswith('win32build'):
|
||||||
|
|
||||||
headphones.INSTALL_TYPE = 'win'
|
headphones.INSTALL_TYPE = 'win'
|
||||||
|
|
||||||
# Don't have a way to update exe yet, but don't want to set VERSION to None
|
# Don't have a way to update exe yet, but don't want to set VERSION to None
|
||||||
@@ -76,7 +76,7 @@ def getVersion():
|
|||||||
logger.error('Couldn\'t find latest installed version.')
|
logger.error('Couldn\'t find latest installed version.')
|
||||||
cur_commit_hash = None
|
cur_commit_hash = None
|
||||||
|
|
||||||
cur_commit_hash = output
|
cur_commit_hash = str(output)
|
||||||
|
|
||||||
if not re.match('^[a-z0-9]+$', cur_commit_hash):
|
if not re.match('^[a-z0-9]+$', cur_commit_hash):
|
||||||
logger.error('Output doesn\'t look like a hash, not using it')
|
logger.error('Output doesn\'t look like a hash, not using it')
|
||||||
@@ -107,9 +107,8 @@ def getVersion():
|
|||||||
if not os.path.isfile(version_file):
|
if not os.path.isfile(version_file):
|
||||||
return None, 'master'
|
return None, 'master'
|
||||||
|
|
||||||
fp = open(version_file, 'r')
|
with open(version_file, 'r') as f:
|
||||||
current_version = fp.read().strip(' \n\r')
|
current_version = f.read().strip(' \n\r')
|
||||||
fp.close()
|
|
||||||
|
|
||||||
if current_version:
|
if current_version:
|
||||||
return current_version, headphones.GIT_BRANCH
|
return current_version, headphones.GIT_BRANCH
|
||||||
@@ -136,6 +135,10 @@ def checkGithub():
|
|||||||
logger.info('You are running an unknown version of Headphones. Run the updater to identify your version')
|
logger.info('You are running an unknown version of Headphones. Run the updater to identify your version')
|
||||||
return headphones.LATEST_VERSION
|
return headphones.LATEST_VERSION
|
||||||
|
|
||||||
|
if headphones.LATEST_VERSION == headphones.CURRENT_VERSION:
|
||||||
|
logger.info('Headphones is up to date')
|
||||||
|
return headphones.LATEST_VERSION
|
||||||
|
|
||||||
logger.info('Comparing currently installed version with latest GitHub version')
|
logger.info('Comparing currently installed version with latest GitHub version')
|
||||||
url = 'https://api.github.com/repos/%s/headphones/compare/%s...%s' % (headphones.GIT_USER, headphones.LATEST_VERSION, headphones.CURRENT_VERSION)
|
url = 'https://api.github.com/repos/%s/headphones/compare/%s...%s' % (headphones.GIT_USER, headphones.LATEST_VERSION, headphones.CURRENT_VERSION)
|
||||||
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
|
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
|
||||||
@@ -193,9 +196,8 @@ def update():
|
|||||||
tar_download_path = os.path.join(headphones.PROG_DIR, download_name)
|
tar_download_path = os.path.join(headphones.PROG_DIR, download_name)
|
||||||
|
|
||||||
# Save tar to disk
|
# Save tar to disk
|
||||||
f = open(tar_download_path, 'wb')
|
with open(tar_download_path, 'wb') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
f.close()
|
|
||||||
|
|
||||||
# Extract the tar to update folder
|
# Extract the tar to update folder
|
||||||
logger.info('Extracting file: ' + tar_download_path)
|
logger.info('Extracting file: ' + tar_download_path)
|
||||||
@@ -227,9 +229,9 @@ def update():
|
|||||||
|
|
||||||
# Update version.txt
|
# Update version.txt
|
||||||
try:
|
try:
|
||||||
ver_file = open(version_path, 'w')
|
with open(version_path, 'w') as f:
|
||||||
ver_file.write(str(headphones.LATEST_VERSION))
|
f.write(str(headphones.LATEST_VERSION))
|
||||||
ver_file.close()
|
except IOError as e:
|
||||||
except IOError, e:
|
logger.error("Unable to write current version to version.txt, " \
|
||||||
logger.error("Unable to write current version to version.txt, update not complete: "+ex(e))
|
"update not complete: ", e)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -15,29 +15,29 @@
|
|||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
import os
|
from headphones import logger, searcher, db, importer, mb, lastfm, librarysync, helpers, notifiers
|
||||||
import cherrypy
|
from headphones.helpers import checked, radio,today, cleanName
|
||||||
|
|
||||||
from mako.template import Template
|
from mako.template import Template
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
from mako import exceptions
|
from mako import exceptions
|
||||||
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import string
|
|
||||||
import json
|
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import string
|
||||||
|
import cherrypy
|
||||||
|
import threading
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
from headphones import logger, searcher, db, importer, mb, lastfm, librarysync, helpers, notifiers
|
try:
|
||||||
from headphones.helpers import checked, radio,today, cleanName
|
from collections import OrderedDict
|
||||||
|
except ImportError:
|
||||||
import lib.simplejson as simplejson
|
# Python 2.6.x fallback, from libs
|
||||||
|
import OrderedDict
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def serve_template(templatename, **kwargs):
|
def serve_template(templatename, **kwargs):
|
||||||
|
|
||||||
@@ -84,18 +84,18 @@ class WebInterface(object):
|
|||||||
if not artist:
|
if not artist:
|
||||||
raise cherrypy.HTTPRedirect("home")
|
raise cherrypy.HTTPRedirect("home")
|
||||||
|
|
||||||
# Serve the extras up as a dict to make things easier for new templates
|
# Serve the extras up as a dict to make things easier for new templates (append new extras to the end)
|
||||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other"]
|
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other", "djmix", "mixtape_street", "broadcast", "interview", "demo"]
|
||||||
extras_dict = {}
|
if artist['Extras']:
|
||||||
|
artist_extras = map(int, artist['Extras'].split(','))
|
||||||
if not artist['Extras']:
|
|
||||||
artist_extras = ""
|
|
||||||
else:
|
else:
|
||||||
artist_extras = artist['Extras']
|
artist_extras = []
|
||||||
|
|
||||||
|
extras_dict = OrderedDict()
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for extra in extras_list:
|
for extra in extras_list:
|
||||||
if str(i) in artist_extras:
|
if i in artist_extras:
|
||||||
extras_dict[extra] = "checked"
|
extras_dict[extra] = "checked"
|
||||||
else:
|
else:
|
||||||
extras_dict[extra] = ""
|
extras_dict[extra] = ""
|
||||||
@@ -110,6 +110,19 @@ class WebInterface(object):
|
|||||||
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
||||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=? ORDER BY CAST(TrackNumber AS INTEGER)', [AlbumID])
|
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=? ORDER BY CAST(TrackNumber AS INTEGER)', [AlbumID])
|
||||||
description = myDB.action('SELECT * from descriptions WHERE ReleaseGroupID=?', [AlbumID]).fetchone()
|
description = myDB.action('SELECT * from descriptions WHERE ReleaseGroupID=?', [AlbumID]).fetchone()
|
||||||
|
|
||||||
|
retry = 0
|
||||||
|
while retry < 5:
|
||||||
|
if not album:
|
||||||
|
time.sleep(1)
|
||||||
|
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
||||||
|
retry += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not album:
|
||||||
|
raise cherrypy.HTTPRedirect("home")
|
||||||
|
|
||||||
if not album['ArtistName']:
|
if not album['ArtistName']:
|
||||||
title = ' - '
|
title = ' - '
|
||||||
else:
|
else:
|
||||||
@@ -143,12 +156,12 @@ class WebInterface(object):
|
|||||||
#
|
#
|
||||||
# If they are, we need to convert kwargs to string format
|
# If they are, we need to convert kwargs to string format
|
||||||
if not newstyle:
|
if not newstyle:
|
||||||
extras = "1,2,3,4,5,6,7,8,9"
|
extras = "1,2,3,4,5,6,7,8,9,10,11,12,13,14"
|
||||||
else:
|
else:
|
||||||
temp_extras_list = []
|
temp_extras_list = []
|
||||||
# TODO: Put these extras as a global variable
|
# TODO: Put these extras as a global variable
|
||||||
i = 1
|
i = 1
|
||||||
for extra in ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other"]:
|
for extra in ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other", "djmix", "mixtape_street", "broadcast", "interview", "demo"]:
|
||||||
if extra in kwargs:
|
if extra in kwargs:
|
||||||
temp_extras_list.append(i)
|
temp_extras_list.append(i)
|
||||||
i += 1
|
i += 1
|
||||||
@@ -269,7 +282,11 @@ class WebInterface(object):
|
|||||||
searcher.searchforalbum(mbid, new=True)
|
searcher.searchforalbum(mbid, new=True)
|
||||||
if action == 'WantedLossless':
|
if action == 'WantedLossless':
|
||||||
searcher.searchforalbum(mbid, lossless=True)
|
searcher.searchforalbum(mbid, lossless=True)
|
||||||
myDB.action('UPDATE artists SET TotalTracks=(SELECT COUNT(*) FROM tracks, artists WHERE tracks.ArtistName = artists.ArtistName AND AlbumTitle IN (SELECT AlbumTitle FROM albums WHERE Status != "Ignored")) WHERE ArtistID=(SELECT ArtistID FROM albums WHERE AlbumID=?)', [mbid])
|
if ArtistID:
|
||||||
|
ArtistIDT = ArtistID
|
||||||
|
else:
|
||||||
|
ArtistIDT = myDB.action('SELECT ArtistID FROM albums WHERE AlbumID=?', [mbid]).fetchone()[0]
|
||||||
|
myDB.action('UPDATE artists SET TotalTracks=(SELECT COUNT(*) FROM tracks WHERE ArtistID = ? AND AlbumTitle IN (SELECT AlbumTitle FROM albums WHERE Status != "Ignored")) WHERE ArtistID = ?', [ArtistIDT, ArtistIDT])
|
||||||
if ArtistID:
|
if ArtistID:
|
||||||
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
|
raise cherrypy.HTTPRedirect("artistPage?ArtistID=%s" % ArtistID)
|
||||||
else:
|
else:
|
||||||
@@ -282,8 +299,8 @@ class WebInterface(object):
|
|||||||
if action == "ignore":
|
if action == "ignore":
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
for artist in args:
|
for artist in args:
|
||||||
myDB.action('DELETE FROM newartists WHERE ArtistName=?', [artist])
|
myDB.action('DELETE FROM newartists WHERE ArtistName=?', [artist.decode(headphones.SYS_ENCODING, 'replace')])
|
||||||
myDB.action('UPDATE have SET Matched="Ignored" WHERE ArtistName=?', [artist])
|
myDB.action('UPDATE have SET Matched="Ignored" WHERE ArtistName=?', [artist.decode(headphones.SYS_ENCODING, 'replace')])
|
||||||
logger.info("Artist %s removed from new artist list and set to ignored" % artist)
|
logger.info("Artist %s removed from new artist list and set to ignored" % artist)
|
||||||
raise cherrypy.HTTPRedirect("home")
|
raise cherrypy.HTTPRedirect("home")
|
||||||
addArtists.exposed = True
|
addArtists.exposed = True
|
||||||
@@ -321,7 +338,7 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
results_as_dicts.append(result_dict)
|
results_as_dicts.append(result_dict)
|
||||||
|
|
||||||
s = simplejson.dumps(results_as_dicts)
|
s = json.dumps(results_as_dicts)
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
return s
|
return s
|
||||||
|
|
||||||
@@ -361,6 +378,12 @@ class WebInterface(object):
|
|||||||
def deleteAlbum(self, AlbumID, ArtistID=None):
|
def deleteAlbum(self, AlbumID, ArtistID=None):
|
||||||
logger.info(u"Deleting all traces of album: " + AlbumID)
|
logger.info(u"Deleting all traces of album: " + AlbumID)
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
|
||||||
|
myDB.action('DELETE from have WHERE Matched=?', [AlbumID])
|
||||||
|
album = myDB.action('SELECT ArtistName, AlbumTitle from albums where AlbumID=?', [AlbumID]).fetchone()
|
||||||
|
if album:
|
||||||
|
myDB.action('DELETE from have WHERE ArtistName=? AND AlbumTitle=?', [album['ArtistName'], album['AlbumTitle']])
|
||||||
|
|
||||||
myDB.action('DELETE from albums WHERE AlbumID=?', [AlbumID])
|
myDB.action('DELETE from albums WHERE AlbumID=?', [AlbumID])
|
||||||
myDB.action('DELETE from tracks WHERE AlbumID=?', [AlbumID])
|
myDB.action('DELETE from tracks WHERE AlbumID=?', [AlbumID])
|
||||||
myDB.action('DELETE from allalbums WHERE AlbumID=?', [AlbumID])
|
myDB.action('DELETE from allalbums WHERE AlbumID=?', [AlbumID])
|
||||||
@@ -443,8 +466,9 @@ class WebInterface(object):
|
|||||||
have_album_dictionary.append(have_dict)
|
have_album_dictionary.append(have_dict)
|
||||||
headphones_albums = myDB.select('SELECT ArtistName, AlbumTitle from albums ORDER BY ArtistName')
|
headphones_albums = myDB.select('SELECT ArtistName, AlbumTitle from albums ORDER BY ArtistName')
|
||||||
for albums in headphones_albums:
|
for albums in headphones_albums:
|
||||||
headphones_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'] }
|
if albums['ArtistName'] and albums['AlbumTitle']:
|
||||||
headphones_album_dictionary.append(headphones_dict)
|
headphones_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'] }
|
||||||
|
headphones_album_dictionary.append(headphones_dict)
|
||||||
#unmatchedalbums = [f for f in have_album_dictionary if f not in [x for x in headphones_album_dictionary]]
|
#unmatchedalbums = [f for f in have_album_dictionary if f not in [x for x in headphones_album_dictionary]]
|
||||||
|
|
||||||
check = set([(cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) for d in headphones_album_dictionary])
|
check = set([(cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) for d in headphones_album_dictionary])
|
||||||
@@ -718,7 +742,7 @@ class WebInterface(object):
|
|||||||
|
|
||||||
def history(self):
|
def history(self):
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
history = myDB.select('''SELECT * from snatched order by DateAdded DESC''')
|
history = myDB.select('''SELECT * from snatched WHERE Status NOT LIKE "Seed%" order by DateAdded DESC''')
|
||||||
return serve_template(templatename="history.html", title="History", history=history)
|
return serve_template(templatename="history.html", title="History", history=history)
|
||||||
history.exposed = True
|
history.exposed = True
|
||||||
|
|
||||||
@@ -732,6 +756,14 @@ class WebInterface(object):
|
|||||||
raise cherrypy.HTTPRedirect("logs")
|
raise cherrypy.HTTPRedirect("logs")
|
||||||
clearLogs.exposed = True
|
clearLogs.exposed = True
|
||||||
|
|
||||||
|
def toggleVerbose(self):
|
||||||
|
headphones.VERBOSE = not headphones.VERBOSE
|
||||||
|
logger.initLogger(not headphones.QUIET, headphones.VERBOSE)
|
||||||
|
logger.info("Verbose toggled, set to %s", headphones.VERBOSE)
|
||||||
|
logger.debug("If you read this message, debug logging is available")
|
||||||
|
raise cherrypy.HTTPRedirect("logs")
|
||||||
|
toggleVerbose.exposed = True
|
||||||
|
|
||||||
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
|
||||||
|
|
||||||
iDisplayStart = int(iDisplayStart)
|
iDisplayStart = int(iDisplayStart)
|
||||||
@@ -753,12 +785,11 @@ class WebInterface(object):
|
|||||||
rows = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
|
rows = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
|
||||||
rows = [[row[0],row[2],row[1]] for row in rows]
|
rows = [[row[0],row[2],row[1]] for row in rows]
|
||||||
|
|
||||||
dict = {'iTotalDisplayRecords':len(filtered),
|
return json.dumps({
|
||||||
'iTotalRecords':len(headphones.LOG_LIST),
|
'iTotalDisplayRecords':len(filtered),
|
||||||
'aaData':rows,
|
'iTotalRecords':len(headphones.LOG_LIST),
|
||||||
}
|
'aaData':rows,
|
||||||
s = simplejson.dumps(dict)
|
})
|
||||||
return s
|
|
||||||
getLog.exposed = True
|
getLog.exposed = True
|
||||||
|
|
||||||
def getArtists_json(self,iDisplayStart=0,iDisplayLength=100,sSearch="",iSortCol_0='0',sSortDir_0='asc',**kwargs):
|
def getArtists_json(self,iDisplayStart=0,iDisplayLength=100,sSearch="",iSortCol_0='0',sSortDir_0='asc',**kwargs):
|
||||||
@@ -831,7 +862,7 @@ class WebInterface(object):
|
|||||||
'iTotalRecords':totalcount,
|
'iTotalRecords':totalcount,
|
||||||
'aaData':rows,
|
'aaData':rows,
|
||||||
}
|
}
|
||||||
s = simplejson.dumps(dict)
|
s = json.dumps(dict)
|
||||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||||
return s
|
return s
|
||||||
getArtists_json.exposed=True
|
getArtists_json.exposed=True
|
||||||
@@ -850,18 +881,39 @@ class WebInterface(object):
|
|||||||
return json_albums
|
return json_albums
|
||||||
getAlbumsByArtist_json.exposed=True
|
getAlbumsByArtist_json.exposed=True
|
||||||
|
|
||||||
|
def getArtistjson(self, ArtistID, **kwargs):
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
artist = myDB.action('SELECT * FROM artists WHERE ArtistID=?', [ArtistID]).fetchone()
|
||||||
|
artist_json = json.dumps({
|
||||||
|
'ArtistName': artist['ArtistName'],
|
||||||
|
'Status': artist['Status']
|
||||||
|
})
|
||||||
|
return artist_json
|
||||||
|
getArtistjson.exposed=True
|
||||||
|
|
||||||
|
def getAlbumjson(self, AlbumID, **kwargs):
|
||||||
|
myDB = db.DBConnection()
|
||||||
|
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
|
||||||
|
album_json = json.dumps({
|
||||||
|
'AlbumTitle': album['AlbumTitle'],
|
||||||
|
'ArtistName': album['ArtistName'],
|
||||||
|
'Status': album['Status']
|
||||||
|
})
|
||||||
|
return album_json
|
||||||
|
getAlbumjson.exposed=True
|
||||||
|
|
||||||
def clearhistory(self, type=None, date_added=None, title=None):
|
def clearhistory(self, type=None, date_added=None, title=None):
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
if type:
|
if type:
|
||||||
if type == 'all':
|
if type == 'all':
|
||||||
logger.info(u"Clearing all history")
|
logger.info(u"Clearing all history")
|
||||||
myDB.action('DELETE from snatched')
|
myDB.action('DELETE from snatched WHERE Status NOT LIKE "Seed%"')
|
||||||
else:
|
else:
|
||||||
logger.info(u"Clearing history where status is %s" % type)
|
logger.info(u"Clearing history where status is %s" % type)
|
||||||
myDB.action('DELETE from snatched WHERE Status=?', [type])
|
myDB.action('DELETE from snatched WHERE Status=?', [type])
|
||||||
else:
|
else:
|
||||||
logger.info(u"Deleting '%s' from history" % title)
|
logger.info(u"Deleting '%s' from history" % title)
|
||||||
myDB.action('DELETE from snatched WHERE Title=? AND DateAdded=?', [title, date_added])
|
myDB.action('DELETE from snatched WHERE Status NOT LIKE "Seed%" AND Title=? AND DateAdded=?', [title, date_added])
|
||||||
raise cherrypy.HTTPRedirect("history")
|
raise cherrypy.HTTPRedirect("history")
|
||||||
clearhistory.exposed = True
|
clearhistory.exposed = True
|
||||||
|
|
||||||
@@ -914,7 +966,7 @@ class WebInterface(object):
|
|||||||
"download_scan_interval" : headphones.DOWNLOAD_SCAN_INTERVAL,
|
"download_scan_interval" : headphones.DOWNLOAD_SCAN_INTERVAL,
|
||||||
"update_db_interval" : headphones.UPDATE_DB_INTERVAL,
|
"update_db_interval" : headphones.UPDATE_DB_INTERVAL,
|
||||||
"mb_ignore_age" : headphones.MB_IGNORE_AGE,
|
"mb_ignore_age" : headphones.MB_IGNORE_AGE,
|
||||||
"nzb_search_interval" : headphones.SEARCH_INTERVAL,
|
"search_interval" : headphones.SEARCH_INTERVAL,
|
||||||
"libraryscan_interval" : headphones.LIBRARYSCAN_INTERVAL,
|
"libraryscan_interval" : headphones.LIBRARYSCAN_INTERVAL,
|
||||||
"sab_host" : headphones.SAB_HOST,
|
"sab_host" : headphones.SAB_HOST,
|
||||||
"sab_user" : headphones.SAB_USERNAME,
|
"sab_user" : headphones.SAB_USERNAME,
|
||||||
@@ -925,12 +977,14 @@ class WebInterface(object):
|
|||||||
"nzbget_user" : headphones.NZBGET_USERNAME,
|
"nzbget_user" : headphones.NZBGET_USERNAME,
|
||||||
"nzbget_pass" : headphones.NZBGET_PASSWORD,
|
"nzbget_pass" : headphones.NZBGET_PASSWORD,
|
||||||
"nzbget_cat" : headphones.NZBGET_CATEGORY,
|
"nzbget_cat" : headphones.NZBGET_CATEGORY,
|
||||||
|
"nzbget_priority" : headphones.NZBGET_PRIORITY,
|
||||||
"transmission_host" : headphones.TRANSMISSION_HOST,
|
"transmission_host" : headphones.TRANSMISSION_HOST,
|
||||||
"transmission_user" : headphones.TRANSMISSION_USERNAME,
|
"transmission_user" : headphones.TRANSMISSION_USERNAME,
|
||||||
"transmission_pass" : headphones.TRANSMISSION_PASSWORD,
|
"transmission_pass" : headphones.TRANSMISSION_PASSWORD,
|
||||||
"utorrent_host" : headphones.UTORRENT_HOST,
|
"utorrent_host" : headphones.UTORRENT_HOST,
|
||||||
"utorrent_user" : headphones.UTORRENT_USERNAME,
|
"utorrent_user" : headphones.UTORRENT_USERNAME,
|
||||||
"utorrent_pass" : headphones.UTORRENT_PASSWORD,
|
"utorrent_pass" : headphones.UTORRENT_PASSWORD,
|
||||||
|
"utorrent_label" : headphones.UTORRENT_LABEL,
|
||||||
"nzb_downloader_sabnzbd" : radio(headphones.NZB_DOWNLOADER, 0),
|
"nzb_downloader_sabnzbd" : radio(headphones.NZB_DOWNLOADER, 0),
|
||||||
"nzb_downloader_nzbget" : radio(headphones.NZB_DOWNLOADER, 1),
|
"nzb_downloader_nzbget" : radio(headphones.NZB_DOWNLOADER, 1),
|
||||||
"nzb_downloader_blackhole" : radio(headphones.NZB_DOWNLOADER, 2),
|
"nzb_downloader_blackhole" : radio(headphones.NZB_DOWNLOADER, 2),
|
||||||
@@ -950,9 +1004,6 @@ class WebInterface(object):
|
|||||||
"use_nzbsorg" : checked(headphones.NZBSORG),
|
"use_nzbsorg" : checked(headphones.NZBSORG),
|
||||||
"nzbsorg_uid" : headphones.NZBSORG_UID,
|
"nzbsorg_uid" : headphones.NZBSORG_UID,
|
||||||
"nzbsorg_hash" : headphones.NZBSORG_HASH,
|
"nzbsorg_hash" : headphones.NZBSORG_HASH,
|
||||||
"use_nzbsrus" : checked(headphones.NZBSRUS),
|
|
||||||
"nzbsrus_uid" : headphones.NZBSRUS_UID,
|
|
||||||
"nzbsrus_apikey" : headphones.NZBSRUS_APIKEY,
|
|
||||||
"use_omgwtfnzbs" : checked(headphones.OMGWTFNZBS),
|
"use_omgwtfnzbs" : checked(headphones.OMGWTFNZBS),
|
||||||
"omgwtfnzbs_uid" : headphones.OMGWTFNZBS_UID,
|
"omgwtfnzbs_uid" : headphones.OMGWTFNZBS_UID,
|
||||||
"omgwtfnzbs_apikey" : headphones.OMGWTFNZBS_APIKEY,
|
"omgwtfnzbs_apikey" : headphones.OMGWTFNZBS_APIKEY,
|
||||||
@@ -962,20 +1013,26 @@ class WebInterface(object):
|
|||||||
"torrentblackhole_dir" : headphones.TORRENTBLACKHOLE_DIR,
|
"torrentblackhole_dir" : headphones.TORRENTBLACKHOLE_DIR,
|
||||||
"download_torrent_dir" : headphones.DOWNLOAD_TORRENT_DIR,
|
"download_torrent_dir" : headphones.DOWNLOAD_TORRENT_DIR,
|
||||||
"numberofseeders" : headphones.NUMBEROFSEEDERS,
|
"numberofseeders" : headphones.NUMBEROFSEEDERS,
|
||||||
"use_isohunt" : checked(headphones.ISOHUNT),
|
|
||||||
"use_kat" : checked(headphones.KAT),
|
"use_kat" : checked(headphones.KAT),
|
||||||
|
"kat_proxy_url" : headphones.KAT_PROXY_URL,
|
||||||
|
"kat_ratio": headphones.KAT_RATIO,
|
||||||
"use_piratebay" : checked(headphones.PIRATEBAY),
|
"use_piratebay" : checked(headphones.PIRATEBAY),
|
||||||
"piratebay_proxy_url" : headphones.PIRATEBAY_PROXY_URL,
|
"piratebay_proxy_url" : headphones.PIRATEBAY_PROXY_URL,
|
||||||
|
"piratebay_ratio": headphones.PIRATEBAY_RATIO,
|
||||||
"use_mininova" : checked(headphones.MININOVA),
|
"use_mininova" : checked(headphones.MININOVA),
|
||||||
|
"mininova_ratio": headphones.MININOVA_RATIO,
|
||||||
"use_waffles" : checked(headphones.WAFFLES),
|
"use_waffles" : checked(headphones.WAFFLES),
|
||||||
"waffles_uid" : headphones.WAFFLES_UID,
|
"waffles_uid" : headphones.WAFFLES_UID,
|
||||||
"waffles_passkey": headphones.WAFFLES_PASSKEY,
|
"waffles_passkey": headphones.WAFFLES_PASSKEY,
|
||||||
|
"waffles_ratio": headphones.WAFFLES_RATIO,
|
||||||
"use_rutracker" : checked(headphones.RUTRACKER),
|
"use_rutracker" : checked(headphones.RUTRACKER),
|
||||||
"rutracker_user" : headphones.RUTRACKER_USER,
|
"rutracker_user" : headphones.RUTRACKER_USER,
|
||||||
"rutracker_password": headphones.RUTRACKER_PASSWORD,
|
"rutracker_password": headphones.RUTRACKER_PASSWORD,
|
||||||
|
"rutracker_ratio": headphones.RUTRACKER_RATIO,
|
||||||
"use_whatcd" : checked(headphones.WHATCD),
|
"use_whatcd" : checked(headphones.WHATCD),
|
||||||
"whatcd_username" : headphones.WHATCD_USERNAME,
|
"whatcd_username" : headphones.WHATCD_USERNAME,
|
||||||
"whatcd_password": headphones.WHATCD_PASSWORD,
|
"whatcd_password": headphones.WHATCD_PASSWORD,
|
||||||
|
"whatcd_ratio": headphones.WHATCD_RATIO,
|
||||||
"pref_qual_0" : radio(headphones.PREFERRED_QUALITY, 0),
|
"pref_qual_0" : radio(headphones.PREFERRED_QUALITY, 0),
|
||||||
"pref_qual_1" : radio(headphones.PREFERRED_QUALITY, 1),
|
"pref_qual_1" : radio(headphones.PREFERRED_QUALITY, 1),
|
||||||
"pref_qual_3" : radio(headphones.PREFERRED_QUALITY, 3),
|
"pref_qual_3" : radio(headphones.PREFERRED_QUALITY, 3),
|
||||||
@@ -985,10 +1042,14 @@ class WebInterface(object):
|
|||||||
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
|
"pref_bitrate_low" : headphones.PREFERRED_BITRATE_LOW_BUFFER,
|
||||||
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
"pref_bitrate_allow_lossless" : checked(headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS),
|
||||||
"detect_bitrate" : checked(headphones.DETECT_BITRATE),
|
"detect_bitrate" : checked(headphones.DETECT_BITRATE),
|
||||||
|
"lossless_bitrate_from" : headphones.LOSSLESS_BITRATE_FROM,
|
||||||
|
"lossless_bitrate_to" : headphones.LOSSLESS_BITRATE_TO,
|
||||||
|
"freeze_db" : checked(headphones.FREEZE_DB),
|
||||||
"move_files" : checked(headphones.MOVE_FILES),
|
"move_files" : checked(headphones.MOVE_FILES),
|
||||||
"rename_files" : checked(headphones.RENAME_FILES),
|
"rename_files" : checked(headphones.RENAME_FILES),
|
||||||
"correct_metadata" : checked(headphones.CORRECT_METADATA),
|
"correct_metadata" : checked(headphones.CORRECT_METADATA),
|
||||||
"cleanup_files" : checked(headphones.CLEANUP_FILES),
|
"cleanup_files" : checked(headphones.CLEANUP_FILES),
|
||||||
|
"keep_nfo" : checked(headphones.KEEP_NFO),
|
||||||
"add_album_art" : checked(headphones.ADD_ALBUM_ART),
|
"add_album_art" : checked(headphones.ADD_ALBUM_ART),
|
||||||
"album_art_format" : headphones.ALBUM_ART_FORMAT,
|
"album_art_format" : headphones.ALBUM_ART_FORMAT,
|
||||||
"embed_album_art" : checked(headphones.EMBED_ALBUM_ART),
|
"embed_album_art" : checked(headphones.EMBED_ALBUM_ART),
|
||||||
@@ -1064,6 +1125,10 @@ class WebInterface(object):
|
|||||||
"pushbullet_onsnatch": checked(headphones.PUSHBULLET_ONSNATCH),
|
"pushbullet_onsnatch": checked(headphones.PUSHBULLET_ONSNATCH),
|
||||||
"pushbullet_apikey": headphones.PUSHBULLET_APIKEY,
|
"pushbullet_apikey": headphones.PUSHBULLET_APIKEY,
|
||||||
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
|
"pushbullet_deviceid": headphones.PUSHBULLET_DEVICEID,
|
||||||
|
"subsonic_enabled": checked(headphones.SUBSONIC_ENABLED),
|
||||||
|
"subsonic_host": headphones.SUBSONIC_HOST,
|
||||||
|
"subsonic_username": headphones.SUBSONIC_USERNAME,
|
||||||
|
"subsonic_password": headphones.SUBSONIC_PASSWORD,
|
||||||
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
|
"twitter_enabled": checked(headphones.TWITTER_ENABLED),
|
||||||
"twitter_onsnatch": checked(headphones.TWITTER_ONSNATCH),
|
"twitter_onsnatch": checked(headphones.TWITTER_ONSNATCH),
|
||||||
"osx_notify_enabled": checked(headphones.OSX_NOTIFY_ENABLED),
|
"osx_notify_enabled": checked(headphones.OSX_NOTIFY_ENABLED),
|
||||||
@@ -1085,16 +1150,22 @@ class WebInterface(object):
|
|||||||
"songkick_filter_enabled": checked(headphones.SONGKICK_FILTER_ENABLED),
|
"songkick_filter_enabled": checked(headphones.SONGKICK_FILTER_ENABLED),
|
||||||
"cache_sizemb": headphones.CACHE_SIZEMB,
|
"cache_sizemb": headphones.CACHE_SIZEMB,
|
||||||
"file_permissions": headphones.FILE_PERMISSIONS,
|
"file_permissions": headphones.FILE_PERMISSIONS,
|
||||||
"folder_permissions": headphones.FOLDER_PERMISSIONS
|
"folder_permissions": headphones.FOLDER_PERMISSIONS,
|
||||||
|
"mpc_enabled": checked(headphones.MPC_ENABLED)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Need to convert EXTRAS to a dictionary we can pass to the config: it'll come in as a string like 2,5,6,8
|
# Need to convert EXTRAS to a dictionary we can pass to the config: it'll come in as a string like 2,5,6,8 (append new extras to the end)
|
||||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other"]
|
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other", "djmix", "mixtape_street", "broadcast", "interview", "demo"]
|
||||||
extras_dict = {}
|
if headphones.EXTRAS:
|
||||||
|
extras = map(int, headphones.EXTRAS.split(','))
|
||||||
|
else:
|
||||||
|
extras = []
|
||||||
|
|
||||||
|
extras_dict = OrderedDict()
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for extra in extras_list:
|
for extra in extras_list:
|
||||||
if str(i) in headphones.EXTRAS:
|
if i in extras:
|
||||||
extras_dict[extra] = "checked"
|
extras_dict[extra] = "checked"
|
||||||
else:
|
else:
|
||||||
extras_dict[extra] = ""
|
extras_dict[extra] = ""
|
||||||
@@ -1106,23 +1177,23 @@ class WebInterface(object):
|
|||||||
config.exposed = True
|
config.exposed = True
|
||||||
|
|
||||||
def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, api_enabled=0, api_key=None,
|
def configUpdate(self, http_host='0.0.0.0', http_username=None, http_port=8181, http_password=None, launch_browser=0, api_enabled=0, api_key=None,
|
||||||
download_scan_interval=None, update_db_interval=None, mb_ignore_age=None, nzb_search_interval=None, libraryscan_interval=None, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None,
|
download_scan_interval=None, update_db_interval=None, mb_ignore_age=None, search_interval=None, libraryscan_interval=None, sab_host=None, sab_username=None, sab_apikey=None, sab_password=None,
|
||||||
sab_category=None, nzbget_host=None, nzbget_username=None, nzbget_password=None, nzbget_category=None, transmission_host=None, transmission_username=None, transmission_password=None,
|
sab_category=None, nzbget_host=None, nzbget_username=None, nzbget_password=None, nzbget_category=None, nzbget_priority=0, transmission_host=None, transmission_username=None, transmission_password=None,
|
||||||
utorrent_host=None, utorrent_username=None, utorrent_password=None, nzb_downloader=0, torrent_downloader=0, download_dir=None, blackhole_dir=None, usenet_retention=None,
|
utorrent_host=None, utorrent_username=None, utorrent_password=None, utorrent_label=None,nzb_downloader=0, torrent_downloader=0, download_dir=None, blackhole_dir=None, usenet_retention=None,
|
||||||
use_headphones_indexer=0, newznab=0, newznab_host=None, newznab_apikey=None, newznab_enabled=0, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, nzbsrus=0, nzbsrus_uid=None, nzbsrus_apikey=None, omgwtfnzbs=0, omgwtfnzbs_uid=None, omgwtfnzbs_apikey=None,
|
use_headphones_indexer=0, newznab=0, newznab_host=None, newznab_apikey=None, newznab_enabled=0, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, omgwtfnzbs=0, omgwtfnzbs_uid=None, omgwtfnzbs_apikey=None,
|
||||||
preferred_words=None, required_words=None, ignored_words=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=None,
|
preferred_words=None, required_words=None, ignored_words=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, freeze_db=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=None,
|
||||||
numberofseeders=None, use_piratebay=0, piratebay_proxy_url=None, use_isohunt=0, use_kat=0, use_mininova=0, waffles=0, waffles_uid=None, waffles_passkey=None, whatcd=0, whatcd_username=None, whatcd_password=None,
|
numberofseeders=None, use_piratebay=0, piratebay_proxy_url=None, piratebay_ratio=None, use_kat=0, kat_proxy_url=None, kat_ratio=None, use_mininova=0, mininova_ratio=None, waffles=0, waffles_uid=None, waffles_passkey=None, waffles_ratio=None, whatcd=0, whatcd_username=None, whatcd_password=None, whatcd_ratio=None,
|
||||||
rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, replace_existing_folders=False,
|
rutracker=0, rutracker_user=None, rutracker_password=None, rutracker_ratio=None, rename_files=0, correct_metadata=0, cleanup_files=0, keep_nfo=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, replace_existing_folders=False,
|
||||||
destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, file_underscores=0, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0,
|
destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, file_underscores=0, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0, remix=0, spokenword=0, audiobook=0, other=0, djmix=0, mixtape_street=0, broadcast=0, interview=0, demo=0,
|
||||||
remix=0, spokenword=0, audiobook=0, other=0, autowant_upcoming=False, autowant_all=False, keep_torrent_files=False, prefer_torrents=0, open_magnet_links=0, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None,
|
autowant_upcoming=False, autowant_all=False, keep_torrent_files=False, prefer_torrents=0, open_magnet_links=0, interface=None, log_dir=None, cache_dir=None, music_encoder=0, encoder=None, xldprofile=None,
|
||||||
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0,
|
bitrate=None, samplingfrequency=None, encoderfolder=None, advancedencoder=None, encoderoutputformat=None, encodervbrcbr=None, encoderquality=None, encoderlossless=0, subsonic_enabled=False, subsonic_host=None, subsonic_username=None, subsonic_password=None,
|
||||||
delete_lossless_files=0, growl_enabled=0, growl_onsnatch=0, growl_host=None, growl_password=None, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
|
delete_lossless_files=0, growl_enabled=0, growl_onsnatch=0, growl_host=None, growl_password=None, prowl_enabled=0, prowl_onsnatch=0, prowl_keys=None, prowl_priority=0, xbmc_enabled=0, xbmc_host=None, xbmc_username=None, xbmc_password=None,
|
||||||
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
|
xbmc_update=0, xbmc_notify=0, nma_enabled=False, nma_apikey=None, nma_priority=0, nma_onsnatch=0, pushalot_enabled=False, pushalot_apikey=None, pushalot_onsnatch=0, synoindex_enabled=False, lms_enabled=0, lms_host=None,
|
||||||
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
|
pushover_enabled=0, pushover_onsnatch=0, pushover_keys=None, pushover_priority=0, pushover_apitoken=None, pushbullet_enabled=0, pushbullet_onsnatch=0, pushbullet_apikey=None, pushbullet_deviceid=None, twitter_enabled=0, twitter_onsnatch=0,
|
||||||
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
|
osx_notify_enabled=0, osx_notify_onsnatch=0, osx_notify_app=None, boxcar_enabled=0, boxcar_onsnatch=0, boxcar_token=None, mirror=None, customhost=None, customport=None, customsleep=None, hpuser=None, hppass=None,
|
||||||
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None, file_permissions=None, folder_permissions=None,
|
preferred_bitrate_high_buffer=None, preferred_bitrate_low_buffer=None, preferred_bitrate_allow_lossless=0, lossless_bitrate_from=None, lossless_bitrate_to=None, cache_sizemb=None, enable_https=0, https_cert=None, https_key=None,
|
||||||
plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
|
file_permissions=None, folder_permissions=None, plex_enabled=0, plex_server_host=None, plex_client_host=None, plex_username=None, plex_password=None, plex_update=0, plex_notify=0,
|
||||||
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, **kwargs):
|
songkick_enabled=0, songkick_apikey=None, songkick_location=None, songkick_filter_enabled=0, encoder_multicore=False, encoder_multicore_count=0, mpc_enabled=False, **kwargs ):
|
||||||
|
|
||||||
headphones.HTTP_HOST = http_host
|
headphones.HTTP_HOST = http_host
|
||||||
headphones.HTTP_PORT = http_port
|
headphones.HTTP_PORT = http_port
|
||||||
@@ -1137,7 +1208,7 @@ class WebInterface(object):
|
|||||||
headphones.DOWNLOAD_SCAN_INTERVAL = download_scan_interval
|
headphones.DOWNLOAD_SCAN_INTERVAL = download_scan_interval
|
||||||
headphones.UPDATE_DB_INTERVAL = update_db_interval
|
headphones.UPDATE_DB_INTERVAL = update_db_interval
|
||||||
headphones.MB_IGNORE_AGE = mb_ignore_age
|
headphones.MB_IGNORE_AGE = mb_ignore_age
|
||||||
headphones.SEARCH_INTERVAL = nzb_search_interval
|
headphones.SEARCH_INTERVAL = search_interval
|
||||||
headphones.LIBRARYSCAN_INTERVAL = libraryscan_interval
|
headphones.LIBRARYSCAN_INTERVAL = libraryscan_interval
|
||||||
headphones.SAB_HOST = sab_host
|
headphones.SAB_HOST = sab_host
|
||||||
headphones.SAB_USERNAME = sab_username
|
headphones.SAB_USERNAME = sab_username
|
||||||
@@ -1148,12 +1219,14 @@ class WebInterface(object):
|
|||||||
headphones.NZBGET_USERNAME = nzbget_username
|
headphones.NZBGET_USERNAME = nzbget_username
|
||||||
headphones.NZBGET_PASSWORD = nzbget_password
|
headphones.NZBGET_PASSWORD = nzbget_password
|
||||||
headphones.NZBGET_CATEGORY = nzbget_category
|
headphones.NZBGET_CATEGORY = nzbget_category
|
||||||
|
headphones.NZBGET_PRIORITY = int(nzbget_priority)
|
||||||
headphones.TRANSMISSION_HOST = transmission_host
|
headphones.TRANSMISSION_HOST = transmission_host
|
||||||
headphones.TRANSMISSION_USERNAME = transmission_username
|
headphones.TRANSMISSION_USERNAME = transmission_username
|
||||||
headphones.TRANSMISSION_PASSWORD = transmission_password
|
headphones.TRANSMISSION_PASSWORD = transmission_password
|
||||||
headphones.UTORRENT_HOST = utorrent_host
|
headphones.UTORRENT_HOST = utorrent_host
|
||||||
headphones.UTORRENT_USERNAME = utorrent_username
|
headphones.UTORRENT_USERNAME = utorrent_username
|
||||||
headphones.UTORRENT_PASSWORD = utorrent_password
|
headphones.UTORRENT_PASSWORD = utorrent_password
|
||||||
|
headphones.UTORRENT_LABEL = utorrent_label
|
||||||
headphones.NZB_DOWNLOADER = int(nzb_downloader)
|
headphones.NZB_DOWNLOADER = int(nzb_downloader)
|
||||||
headphones.TORRENT_DOWNLOADER = int(torrent_downloader)
|
headphones.TORRENT_DOWNLOADER = int(torrent_downloader)
|
||||||
headphones.DOWNLOAD_DIR = download_dir
|
headphones.DOWNLOAD_DIR = download_dir
|
||||||
@@ -1167,9 +1240,6 @@ class WebInterface(object):
|
|||||||
headphones.NZBSORG = nzbsorg
|
headphones.NZBSORG = nzbsorg
|
||||||
headphones.NZBSORG_UID = nzbsorg_uid
|
headphones.NZBSORG_UID = nzbsorg_uid
|
||||||
headphones.NZBSORG_HASH = nzbsorg_hash
|
headphones.NZBSORG_HASH = nzbsorg_hash
|
||||||
headphones.NZBSRUS = nzbsrus
|
|
||||||
headphones.NZBSRUS_UID = nzbsrus_uid
|
|
||||||
headphones.NZBSRUS_APIKEY = nzbsrus_apikey
|
|
||||||
headphones.OMGWTFNZBS = omgwtfnzbs
|
headphones.OMGWTFNZBS = omgwtfnzbs
|
||||||
headphones.OMGWTFNZBS_UID = omgwtfnzbs_uid
|
headphones.OMGWTFNZBS_UID = omgwtfnzbs_uid
|
||||||
headphones.OMGWTFNZBS_APIKEY = omgwtfnzbs_apikey
|
headphones.OMGWTFNZBS_APIKEY = omgwtfnzbs_apikey
|
||||||
@@ -1179,30 +1249,40 @@ class WebInterface(object):
|
|||||||
headphones.TORRENTBLACKHOLE_DIR = torrentblackhole_dir
|
headphones.TORRENTBLACKHOLE_DIR = torrentblackhole_dir
|
||||||
headphones.NUMBEROFSEEDERS = numberofseeders
|
headphones.NUMBEROFSEEDERS = numberofseeders
|
||||||
headphones.DOWNLOAD_TORRENT_DIR = download_torrent_dir
|
headphones.DOWNLOAD_TORRENT_DIR = download_torrent_dir
|
||||||
headphones.ISOHUNT = use_isohunt
|
|
||||||
headphones.KAT = use_kat
|
headphones.KAT = use_kat
|
||||||
|
headphones.KAT_PROXY_URL = kat_proxy_url
|
||||||
|
headphones.KAT_RATIO = kat_ratio
|
||||||
headphones.PIRATEBAY = use_piratebay
|
headphones.PIRATEBAY = use_piratebay
|
||||||
headphones.PIRATEBAY_PROXY_URL = piratebay_proxy_url
|
headphones.PIRATEBAY_PROXY_URL = piratebay_proxy_url
|
||||||
|
headphones.PIRATEBAY_RATIO = piratebay_ratio
|
||||||
headphones.MININOVA = use_mininova
|
headphones.MININOVA = use_mininova
|
||||||
|
headphones.MININOVA_RATIO = mininova_ratio
|
||||||
headphones.WAFFLES = waffles
|
headphones.WAFFLES = waffles
|
||||||
headphones.WAFFLES_UID = waffles_uid
|
headphones.WAFFLES_UID = waffles_uid
|
||||||
headphones.WAFFLES_PASSKEY = waffles_passkey
|
headphones.WAFFLES_PASSKEY = waffles_passkey
|
||||||
|
headphones.WAFFLES_RATIO = waffles_ratio
|
||||||
headphones.RUTRACKER = rutracker
|
headphones.RUTRACKER = rutracker
|
||||||
headphones.RUTRACKER_USER = rutracker_user
|
headphones.RUTRACKER_USER = rutracker_user
|
||||||
headphones.RUTRACKER_PASSWORD = rutracker_password
|
headphones.RUTRACKER_PASSWORD = rutracker_password
|
||||||
|
headphones.RUTRACKER_RATIO = rutracker_ratio
|
||||||
headphones.WHATCD = whatcd
|
headphones.WHATCD = whatcd
|
||||||
headphones.WHATCD_USERNAME = whatcd_username
|
headphones.WHATCD_USERNAME = whatcd_username
|
||||||
headphones.WHATCD_PASSWORD = whatcd_password
|
headphones.WHATCD_PASSWORD = whatcd_password
|
||||||
|
headphones.WHATCD_RATIO = whatcd_ratio
|
||||||
headphones.PREFERRED_QUALITY = int(preferred_quality)
|
headphones.PREFERRED_QUALITY = int(preferred_quality)
|
||||||
headphones.PREFERRED_BITRATE = preferred_bitrate
|
headphones.PREFERRED_BITRATE = preferred_bitrate
|
||||||
headphones.PREFERRED_BITRATE_HIGH_BUFFER = preferred_bitrate_high_buffer
|
headphones.PREFERRED_BITRATE_HIGH_BUFFER = preferred_bitrate_high_buffer
|
||||||
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
|
headphones.PREFERRED_BITRATE_LOW_BUFFER = preferred_bitrate_low_buffer
|
||||||
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
|
headphones.PREFERRED_BITRATE_ALLOW_LOSSLESS = preferred_bitrate_allow_lossless
|
||||||
headphones.DETECT_BITRATE = detect_bitrate
|
headphones.DETECT_BITRATE = detect_bitrate
|
||||||
|
headphones.LOSSLESS_BITRATE_FROM = lossless_bitrate_from
|
||||||
|
headphones.LOSSLESS_BITRATE_TO = lossless_bitrate_to
|
||||||
|
headphones.FREEZE_DB = freeze_db
|
||||||
headphones.MOVE_FILES = move_files
|
headphones.MOVE_FILES = move_files
|
||||||
headphones.CORRECT_METADATA = correct_metadata
|
headphones.CORRECT_METADATA = correct_metadata
|
||||||
headphones.RENAME_FILES = rename_files
|
headphones.RENAME_FILES = rename_files
|
||||||
headphones.CLEANUP_FILES = cleanup_files
|
headphones.CLEANUP_FILES = cleanup_files
|
||||||
|
headphones.KEEP_NFO = keep_nfo
|
||||||
headphones.ADD_ALBUM_ART = add_album_art
|
headphones.ADD_ALBUM_ART = add_album_art
|
||||||
headphones.ALBUM_ART_FORMAT = album_art_format
|
headphones.ALBUM_ART_FORMAT = album_art_format
|
||||||
headphones.EMBED_ALBUM_ART = embed_album_art
|
headphones.EMBED_ALBUM_ART = embed_album_art
|
||||||
@@ -1276,6 +1356,10 @@ class WebInterface(object):
|
|||||||
headphones.PUSHBULLET_ONSNATCH = pushbullet_onsnatch
|
headphones.PUSHBULLET_ONSNATCH = pushbullet_onsnatch
|
||||||
headphones.PUSHBULLET_APIKEY = pushbullet_apikey
|
headphones.PUSHBULLET_APIKEY = pushbullet_apikey
|
||||||
headphones.PUSHBULLET_DEVICEID = pushbullet_deviceid
|
headphones.PUSHBULLET_DEVICEID = pushbullet_deviceid
|
||||||
|
headphones.SUBSONIC_ENABLED = subsonic_enabled
|
||||||
|
headphones.SUBSONIC_HOST = subsonic_host
|
||||||
|
headphones.SUBSONIC_USERNAME = subsonic_username
|
||||||
|
headphones.SUBSONIC_PASSWORD = subsonic_password
|
||||||
headphones.SONGKICK_ENABLED = songkick_enabled
|
headphones.SONGKICK_ENABLED = songkick_enabled
|
||||||
headphones.SONGKICK_APIKEY = songkick_apikey
|
headphones.SONGKICK_APIKEY = songkick_apikey
|
||||||
headphones.SONGKICK_LOCATION = songkick_location
|
headphones.SONGKICK_LOCATION = songkick_location
|
||||||
@@ -1291,6 +1375,8 @@ class WebInterface(object):
|
|||||||
headphones.BOXCAR_ONSNATCH = boxcar_onsnatch
|
headphones.BOXCAR_ONSNATCH = boxcar_onsnatch
|
||||||
headphones.BOXCAR_TOKEN = boxcar_token
|
headphones.BOXCAR_TOKEN = boxcar_token
|
||||||
|
|
||||||
|
headphones.MPC_ENABLED = mpc_enabled
|
||||||
|
|
||||||
headphones.MIRROR = mirror
|
headphones.MIRROR = mirror
|
||||||
headphones.CUSTOMHOST = customhost
|
headphones.CUSTOMHOST = customhost
|
||||||
headphones.CUSTOMPORT = customport
|
headphones.CUSTOMPORT = customport
|
||||||
@@ -1317,9 +1403,9 @@ class WebInterface(object):
|
|||||||
|
|
||||||
headphones.EXTRA_NEWZNABS.append((newznab_host, newznab_api, newznab_enabled))
|
headphones.EXTRA_NEWZNABS.append((newznab_host, newznab_api, newznab_enabled))
|
||||||
|
|
||||||
# Convert the extras to list then string. Coming in as 0 or 1
|
# Convert the extras to list then string. Coming in as 0 or 1 (append new extras to the end)
|
||||||
temp_extras_list = []
|
temp_extras_list = []
|
||||||
extras_list = [single, ep, compilation, soundtrack, live, remix, spokenword, audiobook, other]
|
extras_list = [single, ep, compilation, soundtrack, live, remix, spokenword, audiobook, other, djmix, mixtape_street, broadcast, interview, demo]
|
||||||
|
|
||||||
i = 1
|
i = 1
|
||||||
for extra in extras_list:
|
for extra in extras_list:
|
||||||
@@ -1344,25 +1430,22 @@ class WebInterface(object):
|
|||||||
|
|
||||||
configUpdate.exposed = True
|
configUpdate.exposed = True
|
||||||
|
|
||||||
def shutdown(self):
|
def do_state_change(self, signal, title, timer):
|
||||||
headphones.SIGNAL = 'shutdown'
|
headphones.SIGNAL = signal
|
||||||
message = 'Shutting Down...'
|
message = title + '...'
|
||||||
return serve_template(templatename="shutdown.html", title="Shutting Down", message=message, timer=15)
|
return serve_template(templatename="shutdown.html", title=title,
|
||||||
return page
|
message=message, timer=timer)
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
return self.do_state_change('shutdown', 'Shutting Down', 15)
|
||||||
shutdown.exposed = True
|
shutdown.exposed = True
|
||||||
|
|
||||||
def restart(self):
|
def restart(self):
|
||||||
headphones.SIGNAL = 'restart'
|
return self.do_state_change('restart', 'Restarting', 30)
|
||||||
message = 'Restarting...'
|
|
||||||
return serve_template(templatename="shutdown.html", title="Restarting", message=message, timer=30)
|
|
||||||
restart.exposed = True
|
restart.exposed = True
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
headphones.SIGNAL = 'update'
|
return self.do_state_change('update', 'Updating', 120)
|
||||||
message = 'Updating...'
|
|
||||||
return serve_template(templatename="shutdown.html", title="Updating", message=message, timer=120)
|
|
||||||
return page
|
|
||||||
update.exposed = True
|
update.exposed = True
|
||||||
|
|
||||||
def extras(self):
|
def extras(self):
|
||||||
@@ -1372,9 +1455,12 @@ class WebInterface(object):
|
|||||||
return page
|
return page
|
||||||
extras.exposed = True
|
extras.exposed = True
|
||||||
|
|
||||||
def addReleaseById(self, rid):
|
def addReleaseById(self, rid, rgid=None):
|
||||||
threading.Thread(target=importer.addReleaseById, args=[rid]).start()
|
threading.Thread(target=importer.addReleaseById, args=[rid, rgid]).start()
|
||||||
raise cherrypy.HTTPRedirect("home")
|
if rgid:
|
||||||
|
raise cherrypy.HTTPRedirect("albumPage?AlbumID=%s" % rgid)
|
||||||
|
else:
|
||||||
|
raise cherrypy.HTTPRedirect("home")
|
||||||
addReleaseById.exposed = True
|
addReleaseById.exposed = True
|
||||||
|
|
||||||
def updateCloud(self):
|
def updateCloud(self):
|
||||||
@@ -1403,7 +1489,7 @@ class WebInterface(object):
|
|||||||
from headphones import cache
|
from headphones import cache
|
||||||
info_dict = cache.getInfo(ArtistID, AlbumID)
|
info_dict = cache.getInfo(ArtistID, AlbumID)
|
||||||
|
|
||||||
return simplejson.dumps(info_dict)
|
return json.dumps(info_dict)
|
||||||
|
|
||||||
getInfo.exposed = True
|
getInfo.exposed = True
|
||||||
|
|
||||||
@@ -1427,7 +1513,18 @@ class WebInterface(object):
|
|||||||
from headphones import cache
|
from headphones import cache
|
||||||
image_dict = cache.getImageLinks(ArtistID, AlbumID)
|
image_dict = cache.getImageLinks(ArtistID, AlbumID)
|
||||||
|
|
||||||
return simplejson.dumps(image_dict)
|
# Return the Cover Art Archive urls if not found on last.fm
|
||||||
|
if AlbumID and not image_dict:
|
||||||
|
image_url = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID
|
||||||
|
thumb_url = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID
|
||||||
|
image_dict = {'artwork' : image_url, 'thumbnail' : thumb_url}
|
||||||
|
elif AlbumID and (not image_dict['artwork'] or not image_dict['thumbnail']):
|
||||||
|
if not image_dict['artwork']:
|
||||||
|
image_dict['artwork'] = "http://coverartarchive.org/release/%s/front-500.jpg" % AlbumID
|
||||||
|
if not image_dict['thumbnail']:
|
||||||
|
image_dict['thumbnail'] = "http://coverartarchive.org/release/%s/front-250.jpg" % AlbumID
|
||||||
|
|
||||||
|
return json.dumps(image_dict)
|
||||||
|
|
||||||
getImageLinks.exposed = True
|
getImageLinks.exposed = True
|
||||||
|
|
||||||
|
|||||||
@@ -15,18 +15,18 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import cherrypy
|
import cherrypy
|
||||||
|
|
||||||
import headphones
|
import headphones
|
||||||
|
|
||||||
from headphones import logger
|
from headphones import logger
|
||||||
from headphones.webserve import WebInterface
|
from headphones.webserve import WebInterface
|
||||||
from headphones.helpers import create_https_certificates
|
from headphones.helpers import create_https_certificates
|
||||||
|
|
||||||
def initialize(options={}):
|
def initialize(options=None):
|
||||||
|
if options is None:
|
||||||
|
options = {}
|
||||||
|
|
||||||
#HTTPS stuff stolen from sickbeard
|
# HTTPS stuff stolen from sickbeard
|
||||||
enable_https = options['enable_https']
|
enable_https = options['enable_https']
|
||||||
https_cert = options['https_cert']
|
https_cert = options['https_cert']
|
||||||
https_key = options['https_key']
|
https_key = options['https_key']
|
||||||
@@ -45,15 +45,15 @@ def initialize(options={}):
|
|||||||
enable_https = False
|
enable_https = False
|
||||||
|
|
||||||
options_dict = {
|
options_dict = {
|
||||||
'log.screen': False,
|
'server.socket_port': options['http_port'],
|
||||||
'server.thread_pool': 10,
|
'server.socket_host': options['http_host'],
|
||||||
'server.socket_port': options['http_port'],
|
'server.thread_pool': 10,
|
||||||
'server.socket_host': options['http_host'],
|
'tools.encode.on': True,
|
||||||
'engine.autoreload_on': False,
|
'tools.encode.encoding': 'utf-8',
|
||||||
'tools.encode.on' : True,
|
'tools.decode.on': True,
|
||||||
'tools.encode.encoding' : 'utf-8',
|
'log.screen': False,
|
||||||
'tools.decode.on' : True,
|
'engine.autoreload_on': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
if enable_https:
|
if enable_https:
|
||||||
options_dict['server.ssl_certificate'] = https_cert
|
options_dict['server.ssl_certificate'] = https_cert
|
||||||
@@ -62,7 +62,8 @@ def initialize(options={}):
|
|||||||
else:
|
else:
|
||||||
protocol = "http"
|
protocol = "http"
|
||||||
|
|
||||||
logger.info("Starting Headphones on %s://%s:%d/", protocol, options['http_host'], options['http_port'])
|
logger.info("Starting Headphones web server on %s://%s:%d/", protocol,
|
||||||
|
options['http_host'], options['http_port'])
|
||||||
cherrypy.config.update(options_dict)
|
cherrypy.config.update(options_dict)
|
||||||
|
|
||||||
conf = {
|
conf = {
|
||||||
@@ -88,7 +89,8 @@ def initialize(options={}):
|
|||||||
},
|
},
|
||||||
'/favicon.ico':{
|
'/favicon.ico':{
|
||||||
'tools.staticfile.on': True,
|
'tools.staticfile.on': True,
|
||||||
'tools.staticfile.filename': os.path.join(os.path.abspath(os.curdir),"images" + os.sep + "favicon.ico")
|
'tools.staticfile.filename': os.path.join(os.path.abspath(
|
||||||
|
os.curdir), "images" + os.sep + "favicon.ico")
|
||||||
},
|
},
|
||||||
'/cache':{
|
'/cache':{
|
||||||
'tools.staticdir.on': True,
|
'tools.staticdir.on': True,
|
||||||
@@ -96,28 +98,29 @@ def initialize(options={}):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options['http_password'] != "":
|
if options['http_password']:
|
||||||
|
logger.info("Web server authentication is enabled, username is '%s'", options['http_username'])
|
||||||
|
|
||||||
conf['/'].update({
|
conf['/'].update({
|
||||||
'tools.auth_basic.on': True,
|
'tools.auth_basic.on': True,
|
||||||
'tools.auth_basic.realm': 'Headphones',
|
'tools.auth_basic.realm': 'Headphones web server',
|
||||||
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict(
|
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic \
|
||||||
{options['http_username']:options['http_password']})
|
.checkpassword_dict({
|
||||||
|
options['http_username']: options['http_password']
|
||||||
|
})
|
||||||
})
|
})
|
||||||
conf['/api'] = { 'tools.auth_basic.on': False }
|
conf['/api'] = { 'tools.auth_basic.on': False }
|
||||||
|
|
||||||
|
|
||||||
# Prevent time-outs
|
# Prevent time-outs
|
||||||
cherrypy.engine.timeout_monitor.unsubscribe()
|
cherrypy.engine.timeout_monitor.unsubscribe()
|
||||||
|
cherrypy.tree.mount(WebInterface(), options['http_root'], config=conf)
|
||||||
cherrypy.tree.mount(WebInterface(), options['http_root'], config = conf)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cherrypy.process.servers.check_port(options['http_host'], options['http_port'])
|
cherrypy.process.servers.check_port(options['http_host'], options['http_port'])
|
||||||
cherrypy.server.start()
|
cherrypy.server.start()
|
||||||
except IOError:
|
except IOError:
|
||||||
print 'Failed to start on port: %i. Is something else running?' % (options['http_port'])
|
sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port']))
|
||||||
sys.exit(0)
|
sys.exit(1)
|
||||||
|
|
||||||
cherrypy.server.wait()
|
cherrypy.server.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ status_cmd="${name}_status"
|
|||||||
stop_cmd="${name}_stop"
|
stop_cmd="${name}_stop"
|
||||||
|
|
||||||
command="/usr/sbin/daemon"
|
command="/usr/sbin/daemon"
|
||||||
command_args="-f -p ${headphones_pid} python ${headphones_dir}/Headphones.py ${headphones_flags} --quiet"
|
command_args="-f -p ${headphones_pid} python ${headphones_dir}/Headphones.py ${headphones_flags} --quiet --nolaunch"
|
||||||
|
|
||||||
# Ensure user is root when running this script.
|
# Ensure user is root when running this script.
|
||||||
if [ `id -u` != "0" ]; then
|
if [ `id -u` != "0" ]; then
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>/usr/bin/python</string>
|
<string>/usr/bin/python</string>
|
||||||
<string>/Applications/Headphones/headphones.py</string>
|
<string>/Applications/Headphones/headphones.py</string>
|
||||||
<string>-q</string>
|
<string>--quiet</string>
|
||||||
<string>-d</string>
|
<string>--daemon</string>
|
||||||
|
<string>--nolaunch</string>
|
||||||
</array>
|
</array>
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|||||||