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 |
@@ -0,0 +1 @@
|
||||
* text=auto
|
||||
@@ -28,8 +28,12 @@ ehthumbs.db
|
||||
Icon?
|
||||
Thumbs.db
|
||||
|
||||
#ignore thumbnails created by windows
|
||||
Thumbs.db
|
||||
#Ignore files generated by PyCharm
|
||||
.idea/*
|
||||
|
||||
#Ignore files generated by vi
|
||||
*.swp
|
||||
|
||||
#Ignore files build by Visual Studio
|
||||
*.obj
|
||||
*.exe
|
||||
|
||||
@@ -19,26 +19,26 @@ import os, sys
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||
|
||||
from headphones import webstart, logger
|
||||
|
||||
from configobj import ConfigObj
|
||||
|
||||
import locale
|
||||
import time
|
||||
import signal
|
||||
|
||||
from lib.configobj import ConfigObj
|
||||
|
||||
import argparse
|
||||
import headphones
|
||||
|
||||
from headphones import webstart, logger
|
||||
|
||||
try:
|
||||
import argparse
|
||||
except ImportError:
|
||||
import lib.argparse as argparse
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, headphones.sig_handler)
|
||||
signal.signal(signal.SIGTERM, headphones.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Headphones application entry point. Parses arguments, setups encoding and
|
||||
initializes the application.
|
||||
"""
|
||||
|
||||
# Fixed paths to Headphones
|
||||
if hasattr(sys, 'frozen'):
|
||||
headphones.FULL_PATH = os.path.abspath(sys.executable)
|
||||
@@ -63,7 +63,7 @@ def main():
|
||||
headphones.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# 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('-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||
@@ -83,7 +83,7 @@ def main():
|
||||
|
||||
if args.daemon:
|
||||
if sys.platform == 'win32':
|
||||
print "Daemonize not supported under Windows, starting normally"
|
||||
sys.stderr.write("Daemonizing not supported under Windows, starting normally\n")
|
||||
else:
|
||||
headphones.DAEMON = True
|
||||
headphones.QUIET = True
|
||||
@@ -98,13 +98,16 @@ def main():
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the file properly
|
||||
if headphones.DAEMON:
|
||||
headphones.CREATEPID = True
|
||||
|
||||
try:
|
||||
file(headphones.PIDFILE, 'w').write("pid\n")
|
||||
except IOError, e:
|
||||
with open(headphones.PIDFILE, 'w') as fp:
|
||||
fp.write("pid\n")
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to write PID file: %s [%d]", e.strerror, e.errno)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation disabled.")
|
||||
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
headphones.DATA_DIR = args.datadir
|
||||
else:
|
||||
@@ -128,47 +131,45 @@ def main():
|
||||
|
||||
# Put the database in the DATA_DIR
|
||||
headphones.DB_FILE = os.path.join(headphones.DATA_DIR, 'headphones.db')
|
||||
|
||||
headphones.CFG = ConfigObj(headphones.CONFIG_FILE, encoding='utf-8')
|
||||
|
||||
# Read config & start logging
|
||||
# Read config and start logging
|
||||
headphones.initialize()
|
||||
|
||||
if headphones.DAEMON:
|
||||
if sys.platform == "win32":
|
||||
print "Daemonize not supported under Windows, starting normally"
|
||||
else:
|
||||
headphones.daemonize()
|
||||
headphones.daemonize()
|
||||
|
||||
#configure the connection to the musicbrainz database
|
||||
# Configure the connection to the musicbrainz database
|
||||
headphones.mb.startmb()
|
||||
|
||||
# Force the http port if neccessary
|
||||
if 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:
|
||||
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({
|
||||
'http_port': http_port,
|
||||
'http_host': headphones.HTTP_HOST,
|
||||
'http_root': headphones.HTTP_ROOT,
|
||||
'http_proxy': headphones.HTTP_PROXY,
|
||||
'enable_https': headphones.ENABLE_HTTPS,
|
||||
'https_cert': headphones.HTTPS_CERT,
|
||||
'https_key': headphones.HTTPS_KEY,
|
||||
'http_username': headphones.HTTP_USERNAME,
|
||||
'http_password': headphones.HTTP_PASSWORD,
|
||||
})
|
||||
'http_port': http_port,
|
||||
'http_host': headphones.HTTP_HOST,
|
||||
'http_root': headphones.HTTP_ROOT,
|
||||
'http_proxy': headphones.HTTP_PROXY,
|
||||
'enable_https': headphones.ENABLE_HTTPS,
|
||||
'https_cert': headphones.HTTPS_CERT,
|
||||
'https_key': headphones.HTTPS_KEY,
|
||||
'http_username': headphones.HTTP_USERNAME,
|
||||
'http_password': headphones.HTTP_PASSWORD,
|
||||
})
|
||||
|
||||
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
|
||||
headphones.start()
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
while True:
|
||||
if not headphones.SIGNAL:
|
||||
try:
|
||||
@@ -177,6 +178,7 @@ def main():
|
||||
headphones.SIGNAL = 'shutdown'
|
||||
else:
|
||||
logger.info('Received signal: %s', headphones.SIGNAL)
|
||||
|
||||
if headphones.SIGNAL == 'shutdown':
|
||||
headphones.shutdown()
|
||||
elif headphones.SIGNAL == 'restart':
|
||||
@@ -186,7 +188,6 @@ def main():
|
||||
|
||||
headphones.SIGNAL = None
|
||||
|
||||
return
|
||||
|
||||
# Call main()
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,13 +1,35 @@
|
||||
#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
|
||||
|
||||
This is a relatively early release of a third-party add-on for SABnzbd. It's been around for about a year, and while
|
||||
quite functional, there are still a lot of kinks to work out!
|
||||
[Read our Wiki](../../wiki) on how to install and use HeadPhones properly.
|
||||
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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']}">
|
||||
</div>
|
||||
|
||||
<h1><a href="http://musicbrainz.org/release-group/${album['AlbumID']}">${album['AlbumTitle']}</a></h1>
|
||||
<h2><a href="http://musicbrainz.org/artist/${album['ArtistID']}">${album['ArtistName']}</a></h2>
|
||||
<h1 id="albumname">
|
||||
<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]
|
||||
totaltracks = len(myDB.select("SELECT TrackTitle from tracks WHERE AlbumID=?", [album['AlbumID']]))
|
||||
@@ -275,9 +281,58 @@
|
||||
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() {
|
||||
getAlbumInfo();
|
||||
initThisPage();
|
||||
checkAlbumStatus();
|
||||
refreshInterval = setInterval(function(){
|
||||
checkAlbumStatus();
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
%else:
|
||||
<a id="menu_link_getextra" href="#"><i class="fa fa-plus"></i> Get Extras</a>
|
||||
%endif
|
||||
<div id="dialog" title="Choose Which Extras to Fetch" style="display:none" class="configtable">
|
||||
<form action="getExtras" method="get" class="form">
|
||||
<input type="hidden" name="ArtistID" value="${artist['ArtistID']}">
|
||||
<input type="hidden" name="newstyle" value="true">
|
||||
%for extra in extras:
|
||||
<input type="checkbox" id="${extra}" name="${extra}" value="1" ${extras[extra]} />${string.capwords(extra)}<br>
|
||||
%endfor
|
||||
<br>
|
||||
<input id="submit" type="submit" value="Fetch Extras">
|
||||
</form>
|
||||
</div>
|
||||
<div id="dialog" title="Choose Which Extras to Fetch" style="display:none" class="configtable">
|
||||
<form action="getExtras" method="get" class="form">
|
||||
<input type="hidden" name="ArtistID" value="${artist['ArtistID']}">
|
||||
<input type="hidden" name="newstyle" value="true">
|
||||
%for extra in extras:
|
||||
<input type="checkbox" id="${extra}" name="${extra}" value="1" ${extras[extra]} />${string.capwords(extra)}<br>
|
||||
%endfor
|
||||
<br>
|
||||
<input id="submit" type="submit" value="Fetch Extras">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="home" class="back">« Back to overview</a>
|
||||
@@ -128,7 +128,7 @@
|
||||
%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>]
|
||||
%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:
|
||||
[<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
|
||||
@@ -190,11 +190,11 @@
|
||||
function(data){
|
||||
if (data['resultsPage'].totalEntries >= 1) {
|
||||
|
||||
if( ${songkick_filter_enabled} ) {
|
||||
data.resultsPage.results.event = $.grep(data.resultsPage.results.event, function(element,index){
|
||||
return element.venue.metroArea.id == ${songkick_location};
|
||||
});
|
||||
}
|
||||
if( ${songkick_filter_enabled} ) {
|
||||
data.resultsPage.results.event = $.grep(data.resultsPage.results.event, function(element,index){
|
||||
return element.venue.metroArea.id == ${songkick_location};
|
||||
});
|
||||
}
|
||||
|
||||
var tourDate;
|
||||
|
||||
@@ -265,12 +265,13 @@
|
||||
}
|
||||
|
||||
function initThisPage() {
|
||||
$('#menu_link_getextra').click(function() {
|
||||
$('#menu_link_getextra').click(function(event) {
|
||||
$('#dialog').dialog();
|
||||
return false;
|
||||
event.preventDefault();
|
||||
});
|
||||
$('#menu_link_modifyextra').click(function() {
|
||||
$('#dialog').dialog();
|
||||
event.preventDefault();
|
||||
});
|
||||
$('#album_table').dataTable({
|
||||
"bDestroy": true,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from headphones import version
|
||||
%>
|
||||
<!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 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
|
||||
<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
|
||||
@@ -20,12 +19,14 @@
|
||||
|
||||
<link rel="shortcut icon" href="images/favicon.ico">
|
||||
<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/jquery-ui.css">
|
||||
<link rel="stylesheet" href="interfaces/default/css/font-awesome.min.css">
|
||||
${next.headIncludes()}
|
||||
|
||||
<script src="js/libs/modernizr-1.7.min.js"></script>
|
||||
<script src="js/libs/modernizr-2.8.3.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
@@ -58,11 +59,11 @@
|
||||
<form action="search" method="get">
|
||||
<input type="text" value="" placeholder="Search" onfocus="if(this.value==this.defaultValue) this.value='';" name="name" />
|
||||
<i class='fa fa-search mini-icon'></i>
|
||||
<select name="type">
|
||||
<select name="type" id="search_type">
|
||||
<option value="artist">Artist</option>
|
||||
<option value="album">Album</option>
|
||||
</select>
|
||||
<input type="submit" value="Add"/>
|
||||
<input type="submit" value="Search"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +78,12 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<small>
|
||||
<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>
|
||||
</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/common.js"></script>
|
||||
|
||||
${next.javascriptIncludes()}
|
||||
|
||||
<script src="js/plugins.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 -->
|
||||
|
||||
<!-- 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>
|
||||
</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;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 960px;
|
||||
width: 990px;
|
||||
}
|
||||
header #logo {
|
||||
float: left;
|
||||
@@ -758,7 +758,7 @@ div#searchbar input[type=text] {
|
||||
line-height: normal;
|
||||
margin-right: 5px;
|
||||
padding: 4px 5px 4px 25px;
|
||||
width: 150px;
|
||||
width: 185px;
|
||||
}
|
||||
div#searchbar .mini-icon {
|
||||
color: #999;
|
||||
@@ -1126,13 +1126,34 @@ div#artistheader h2 a {
|
||||
padding: 2px 10px;
|
||||
}
|
||||
#searchresults_table th#albumname {
|
||||
min-width: 225px;
|
||||
min-width: 250px;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
#searchresults_table th#artistname {
|
||||
min-width: 325px;
|
||||
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 {
|
||||
background: url("../images/loader_black.gif") no-repeat scroll center center #ffffff;
|
||||
border: 3px solid #FFFFFF;
|
||||
@@ -1144,15 +1165,40 @@ div#artistheader h2 a {
|
||||
width: 50px;
|
||||
}
|
||||
#searchresults_table td#albumname {
|
||||
min-width: 200px;
|
||||
min-width: 250px;
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
}
|
||||
#searchresults_table td#artistname {
|
||||
min-width: 300px;
|
||||
text-align: left;
|
||||
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 {
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -1215,6 +1261,13 @@ div#artistheader h2 a {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
#info {
|
||||
float: left;
|
||||
text-align: left;
|
||||
margin-left: 10px;
|
||||
margin-top: -5px;
|
||||
color: #cccccc;
|
||||
}
|
||||
#actions {
|
||||
float: right;
|
||||
text-align: right;
|
||||
@@ -1416,6 +1469,11 @@ div#artistheader h3 span {
|
||||
min-width: 75px;
|
||||
text-align: center;
|
||||
}
|
||||
#searchresults_table th#scoresmall {
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
#track_table td#bitrate,
|
||||
#track_table td#format {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -938,6 +938,13 @@ div#artistheader {
|
||||
float: left;
|
||||
text-align: left;
|
||||
}
|
||||
#info {
|
||||
float: left;
|
||||
text-align: left;
|
||||
margin-left: 10px;
|
||||
margin-top: -5px;
|
||||
color: @border-color;
|
||||
}
|
||||
#actions {
|
||||
float: right;
|
||||
text-align: right;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%inherit file="base.html"/>
|
||||
<%!
|
||||
from headphones import helpers
|
||||
import cgi
|
||||
%>
|
||||
|
||||
<%def name="headerIncludes()">
|
||||
@@ -51,11 +52,11 @@
|
||||
%>
|
||||
<tr class="grade${grade}">
|
||||
<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="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="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="action">[<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${item['AlbumID']}&redirect=history', $(this),'table')" data-success="Retrying download of '${cgi.escape(item['Title'], quote=True)}'">retry</a>][<a href="#" onclick="doAjaxCall('queueAlbum?AlbumID=${item['AlbumID']}&new=True&redirect=history',$(this),'table')" data-success="Looking for a new version of '${cgi.escape(item['Title'], quote=True)}'">new</a>]</td>
|
||||
<td id="delete"><a href="#" onclick="doAjaxCall('clearhistory?date_added=${item['DateAdded']}&title=${cgi.escape(item['Title'], quote=True)}',$(this),'table')" data-success="${cgi.escape(item['Title'], quote=True)} cleared from history"><img src="interfaces/default/images/trashcan.png" height="18" width="18" id="trashcan" title="Clear this item from the history"></a>
|
||||
</tr>
|
||||
%endfor
|
||||
</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 name="javascriptIncludes()">
|
||||
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||
<script>
|
||||
function initThisPage() {
|
||||
$('#artist_table').dataTable({
|
||||
"bDestroy": true,
|
||||
"aoColumnDefs": [
|
||||
{
|
||||
"bSortable": false,
|
||||
"aTargets": [ 0 ],
|
||||
"mData":"ArtistID",
|
||||
"mRender": function ( data, type, full ) {
|
||||
return '<div id="artistImg"><img class="albumArt" alt="" id="'+ 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>'
|
||||
function initThisPage() {
|
||||
$('#artist_table').dataTable({
|
||||
"bDestroy": true,
|
||||
"aoColumnDefs": [
|
||||
{
|
||||
"bSortable": false,
|
||||
"aTargets": [0],
|
||||
"mData":"ArtistID",
|
||||
"mRender": function ( data, type, full ) {
|
||||
return '<div id="artistImg"><img class="albumArt" alt="" id="'+ data + '" data-src="artwork/thumbs/artist/' + data + '"/></div>';
|
||||
}
|
||||
},
|
||||
{"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,
|
||||
"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) } )
|
||||
{
|
||||
"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": " ",
|
||||
},
|
||||
"fnInitComplete": function(oSettings, json)
|
||||
{
|
||||
}});
|
||||
"bStateSave": true,
|
||||
"iDisplayLength": 50,
|
||||
"sPaginationType": "full_numbers",
|
||||
"bProcessing": true,
|
||||
"bServerSide": true,
|
||||
"sAjaxSource": 'getArtists.json',
|
||||
"fnRowCallback": function(nRow, aData, iDisplayIndex, iDisplayIndexFull) {
|
||||
$('td', nRow).closest('tr').addClass(aData['Grade'])
|
||||
nRow.children[0].id = 'albumart';
|
||||
nRow.children[1].id = 'name';
|
||||
nRow.children[2].id = 'status'
|
||||
nRow.children[3].id = 'album'
|
||||
nRow.children[4].id = 'have'
|
||||
return nRow;
|
||||
},
|
||||
"fnServerData": function ( sSource, aoData, fnCallback ) {
|
||||
/* Add some extra data to the sender */
|
||||
$.getJSON( sSource, aoData, function (json) { fnCallback(json) } )
|
||||
},
|
||||
"fnInitComplete": function(oSettings, json)
|
||||
{
|
||||
}
|
||||
});
|
||||
$('#artist_table').on("draw.dt", function () {
|
||||
$("img").unveil();
|
||||
});
|
||||
|
||||
resetFilters("artist or album");
|
||||
}
|
||||
|
||||
|
||||
resetFilters("artist or album");
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
initThisPage();
|
||||
});
|
||||
$(window).load(function(){
|
||||
initFancybox();
|
||||
refreshLoadArtist();
|
||||
});
|
||||
$(document).ready(function() {
|
||||
initThisPage();
|
||||
});
|
||||
$(window).load(function(){
|
||||
initFancybox();
|
||||
refreshLoadArtist();
|
||||
});
|
||||
</script>
|
||||
|
||||
</%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' ) {
|
||||
var infoURL = "getImageLinks?ArtistID=" + id;
|
||||
} else {
|
||||
var infoURL = "getImageLinks?AlbumID=" + id;
|
||||
}
|
||||
|
||||
// Get Data from the cache by ID
|
||||
$.ajax({
|
||||
url: infoURL,
|
||||
cache: true,
|
||||
dataType: "json",
|
||||
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";
|
||||
}
|
||||
else {
|
||||
var thumbnail = data.thumbnail;
|
||||
}
|
||||
if ( data.artwork == "" || data.artwork == undefined ) {
|
||||
if (!data.artwork) {
|
||||
var artwork = "interfaces/default/images/no-cover-artist.png";
|
||||
}
|
||||
else {
|
||||
var artwork = data.artwork;
|
||||
}
|
||||
$(elem).attr("src", thumbnail);
|
||||
|
||||
if (unveil) {
|
||||
$(elem).attr("data-src", thumbnail);
|
||||
$(elem).unveil();
|
||||
}
|
||||
else {
|
||||
$(elem).attr("src", thumbnail);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -359,12 +371,6 @@ function resetFilters(text){
|
||||
}
|
||||
}
|
||||
|
||||
function preventDefault(){
|
||||
$("a[href='#']").live('click', function(){
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function initFancybox() {
|
||||
if ( $("a[rel=dialog]").length > 0 ) {
|
||||
$.getScript('interfaces/default/js/fancybox/jquery.fancybox-1.3.4.js', function() {
|
||||
@@ -374,11 +380,6 @@ function initFancybox() {
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
initHeader();
|
||||
preventDefault();
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
init();
|
||||
initHeader();
|
||||
});
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
<option value="300">5 Minutes</option>
|
||||
<option value="600">10 Minutes</option>
|
||||
</select></div>
|
||||
|
||||
</%def>
|
||||
|
||||
<%def name="headIncludes()">
|
||||
|
||||
@@ -11,36 +11,49 @@
|
||||
<th id="albumart"></th>
|
||||
%if type == 'album':
|
||||
<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
|
||||
<th id="artistname">Artist Name</th>
|
||||
<th id="score">Score</th>
|
||||
<th id="add"></th>
|
||||
<th id="mb"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
%if searchresults:
|
||||
%for result in searchresults:
|
||||
<%
|
||||
if result['score'] == 100:
|
||||
grade = 'A'
|
||||
else:
|
||||
grade = 'Z'
|
||||
%>
|
||||
%for result in searchresults:
|
||||
<%
|
||||
if result['score'] == 100:
|
||||
grade = 'A'
|
||||
else:
|
||||
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}">
|
||||
%if type == 'album':
|
||||
<td id="albumart"><div id="artistImg"><img title="${result['albumid']}" class="albumArt" height="50" width="50"></div></td>
|
||||
%else:
|
||||
<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:
|
||||
<td id="albumart"><div id="artistImg"><img title="${result['id']}" class="albumArt" height="50" width="50"></div></td>
|
||||
%endif
|
||||
%if type == 'album':
|
||||
<td id="albumname"><a href="${result['albumurl']}">${result['title']}</a></td>
|
||||
%endif
|
||||
<td id="artistname"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||
<td id="score"><div class="bar"><div class="score" style="width: ${result['score']}px">${result['score']}</div></div></td>
|
||||
%if type == 'album':
|
||||
<td id="add"><a href="addReleaseById?rid=${result['albumid']}"><i class="fa fa-plus"></i> Add this album</a></td>
|
||||
%else:
|
||||
<td id="add"><a href="${result['url']}"></i> View on MusicBrainz</a></td>
|
||||
<td id="albumname"><a href="addReleaseById?rid=${result['albumid']}&rgid=${result['rgid']}" title="${albuminfo}">${result['title']}</a></td>
|
||||
<td id="artistnamesmall"><a href="addArtist?artistid=${result['id']}" title="${result['uniquename']}">${result['uniquename']}</a></td>
|
||||
<td id="format">${result['formats']}</td>
|
||||
<td id="tracks">${result['tracks']}</td>
|
||||
<td id="reldate">${result['date']}</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>
|
||||
<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>
|
||||
%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
|
||||
</tr>
|
||||
%endfor
|
||||
@@ -55,27 +68,28 @@
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
|
||||
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||
|
||||
<script>
|
||||
function getArt() {
|
||||
$("table#searchresults_table tr td#albumart img").each(function(){
|
||||
var id = $(this).attr('title');
|
||||
var image = $(this);
|
||||
if ( !image.hasClass('done') ) {
|
||||
image.addClass('done');
|
||||
getImageLinks(image,id,"${type}");
|
||||
}
|
||||
});
|
||||
}
|
||||
function initThisPage() {
|
||||
getArt();
|
||||
$('#searchresults_table').dataTable(
|
||||
{
|
||||
<script type="text/javascript">
|
||||
|
||||
function getArt() {
|
||||
$("table#searchresults_table tr td#albumart img").each(function(){
|
||||
var id = $(this).attr('title');
|
||||
var image = $(this);
|
||||
|
||||
if (!image.hasClass('done')) {
|
||||
image.addClass('done');
|
||||
getImageLinks(image, id, "${type}", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
function initThisPage() {
|
||||
getArt();
|
||||
$('#searchresults_table').dataTable({
|
||||
"bDestroy": true,
|
||||
"aoColumnDefs": [
|
||||
{ 'bSortable': false, 'aTargets': [ 0,3 ] }
|
||||
{ 'bSortable': false, 'aTargets': [ 0 ] }
|
||||
],
|
||||
"oLanguage": {
|
||||
"sLengthMenu":"Show _MENU_ results per page",
|
||||
@@ -88,13 +102,14 @@
|
||||
"sPaginationType": "full_numbers",
|
||||
"aaSorting": []
|
||||
});
|
||||
$('#searchresults_table').on("draw.dt", function () {
|
||||
$("img").unveil();
|
||||
});
|
||||
resetFilters("album");
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initThisPage();
|
||||
});
|
||||
$(window).load(function(){
|
||||
initFancybox();
|
||||
});
|
||||
}
|
||||
$(document).ready(function(){
|
||||
initFancybox();
|
||||
initThisPage();
|
||||
});
|
||||
</script>
|
||||
</%def>
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
|
||||
|
||||
<%def name="body()">
|
||||
|
||||
<div class="title">
|
||||
<div id="paddingheader">
|
||||
<h1 class="clearfix"><i class="fa fa-heart"></i> Wanted Albums</h1>
|
||||
</div>
|
||||
<form action="markAlbums" method="get" id="markAlbums">
|
||||
@@ -41,7 +40,7 @@
|
||||
%for album in wanted:
|
||||
<tr class="gradeZ">
|
||||
<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="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
||||
<td id="reldate">${album['ReleaseDate']}</td>
|
||||
@@ -71,7 +70,7 @@
|
||||
<tbody>
|
||||
%for album in upcoming:
|
||||
<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="albumname"><a href="albumPage?AlbumID=${album['AlbumID']}">${album['AlbumTitle']}</a></td>
|
||||
<td id="reldate">${album['ReleaseDate']}</td>
|
||||
@@ -89,24 +88,25 @@
|
||||
</%def>
|
||||
|
||||
<%def name="javascriptIncludes()">
|
||||
<script src="js/libs/jquery.unveil.min.js"></script>
|
||||
<script src="js/libs/jquery.dataTables.min.js"></script>
|
||||
<script>
|
||||
function initThisPage() {
|
||||
|
||||
$('#wanted_table').dataTable({
|
||||
"oLanguage": {
|
||||
"sEmptyTable": " "
|
||||
},
|
||||
"bDestroy":true,
|
||||
"bFilter": false,
|
||||
"bInfo": false,
|
||||
"bPaginate": false
|
||||
function initThisPage() {
|
||||
$("img").unveil();
|
||||
$('#wanted_table').dataTable({
|
||||
"oLanguage": {
|
||||
"sEmptyTable": " "
|
||||
},
|
||||
"bDestroy":true,
|
||||
"bFilter": false,
|
||||
"bInfo": false,
|
||||
"bPaginate": false
|
||||
});
|
||||
resetFilters("artists");
|
||||
initActions();
|
||||
}
|
||||
$(document).ready(function() {
|
||||
initThisPage();
|
||||
});
|
||||
resetFilters("artists");
|
||||
initActions();
|
||||
}
|
||||
$(document).ready(function() {
|
||||
initThisPage();
|
||||
});
|
||||
</script>
|
||||
</%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
|
||||
|
||||
import os, sys, subprocess
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import threading
|
||||
import webbrowser
|
||||
import sqlite3
|
||||
import itertools
|
||||
import cherrypy
|
||||
|
||||
from lib.apscheduler.scheduler import Scheduler
|
||||
from lib.configobj import ConfigObj
|
||||
from apscheduler.scheduler import Scheduler
|
||||
from configobj import ConfigObj
|
||||
|
||||
from headphones import versioncheck, logger, version
|
||||
from headphones.common import *
|
||||
@@ -108,9 +109,11 @@ LOSSLESS_BITRATE_FROM = None
|
||||
LOSSLESS_BITRATE_TO = None
|
||||
ADD_ARTISTS = False
|
||||
CORRECT_METADATA = False
|
||||
FREEZE_DB = False
|
||||
MOVE_FILES = False
|
||||
RENAME_FILES = False
|
||||
CLEANUP_FILES = False
|
||||
KEEP_NFO = False
|
||||
ADD_ALBUM_ART = False
|
||||
ALBUM_ART_FORMAT = None
|
||||
EMBED_ALBUM_ART = False
|
||||
@@ -136,6 +139,7 @@ LIBRARYSCAN_INTERVAL = 300
|
||||
DOWNLOAD_SCAN_INTERVAL = 5
|
||||
UPDATE_DB_INTERVAL = 24
|
||||
MB_IGNORE_AGE = 365
|
||||
TORRENT_REMOVAL_INTERVAL = 720
|
||||
|
||||
SAB_HOST = None
|
||||
SAB_USERNAME = None
|
||||
@@ -147,6 +151,7 @@ NZBGET_USERNAME = None
|
||||
NZBGET_PASSWORD = None
|
||||
NZBGET_CATEGORY = None
|
||||
NZBGET_HOST = None
|
||||
NZBGET_PRIORITY = 0
|
||||
|
||||
HEADPHONES_INDEXER = False
|
||||
|
||||
@@ -169,10 +174,6 @@ NZBSORG = False
|
||||
NZBSORG_UID = None
|
||||
NZBSORG_HASH = None
|
||||
|
||||
NZBSRUS = False
|
||||
NZBSRUS_UID = None
|
||||
NZBSRUS_APIKEY = None
|
||||
|
||||
OMGWTFNZBS = False
|
||||
OMGWTFNZBS_UID = None
|
||||
OMGWTFNZBS_APIKEY = None
|
||||
@@ -191,20 +192,26 @@ ALBUM_COMPLETION_PCT = None # This is used in importer.py to determine how co
|
||||
|
||||
TORRENTBLACKHOLE_DIR = None
|
||||
NUMBEROFSEEDERS = 10
|
||||
ISOHUNT = None
|
||||
KAT = None
|
||||
KAT_PROXY_URL = None
|
||||
KAT_RATIO = None
|
||||
MININOVA = None
|
||||
MININOVA_RATIO = None
|
||||
PIRATEBAY = None
|
||||
PIRATEBAY_PROXY_URL = None
|
||||
PIRATEBAY_RATIO = None
|
||||
WAFFLES = None
|
||||
WAFFLES_UID = None
|
||||
WAFFLES_PASSKEY = None
|
||||
WAFFLES_RATIO = None
|
||||
RUTRACKER = None
|
||||
RUTRACKER_USER = None
|
||||
RUTRACKER_PASSWORD = None
|
||||
RUTRACKER_RATIO = None
|
||||
WHATCD = None
|
||||
WHATCD_USERNAME = None
|
||||
WHATCD_PASSWORD = None
|
||||
WHATCD_RATIO = None
|
||||
DOWNLOAD_TORRENT_DIR = None
|
||||
|
||||
INTERFACE = None
|
||||
@@ -277,6 +284,10 @@ OSX_NOTIFY_APP = None
|
||||
BOXCAR_ENABLED = False
|
||||
BOXCAR_ONSNATCH = False
|
||||
BOXCAR_TOKEN = None
|
||||
SUBSONIC_ENABLED = False
|
||||
SUBSONIC_HOST = None
|
||||
SUBSONIC_USERNAME = None
|
||||
SUBSONIC_PASSWORD = None
|
||||
MIRRORLIST = ["musicbrainz.org","headphones","custom"]
|
||||
MIRROR = None
|
||||
CUSTOMHOST = None
|
||||
@@ -346,15 +357,15 @@ def initialize():
|
||||
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, \
|
||||
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, \
|
||||
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, \
|
||||
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, 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, \
|
||||
TORRENTBLACKHOLE_DIR, NUMBEROFSEEDERS, ISOHUNT, KAT, PIRATEBAY, PIRATEBAY_PROXY_URL, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \
|
||||
RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, 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, \
|
||||
NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \
|
||||
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, RUTRACKER_RATIO, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, WHATCD_RATIO, DOWNLOAD_TORRENT_DIR, \
|
||||
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_PRIORITY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \
|
||||
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, \
|
||||
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, \
|
||||
@@ -365,41 +376,21 @@ def initialize():
|
||||
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, 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, \
|
||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT
|
||||
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, SUBSONIC_ENABLED, SUBSONIC_HOST, SUBSONIC_USERNAME, SUBSONIC_PASSWORD, VERIFY_SSL_CERT
|
||||
|
||||
|
||||
if __INITIALIZED__:
|
||||
return False
|
||||
|
||||
# Make sure all the config sections exist
|
||||
CheckSection('General')
|
||||
CheckSection('SABnzbd')
|
||||
CheckSection('NZBget')
|
||||
CheckSection('Transmission')
|
||||
CheckSection('uTorrent')
|
||||
CheckSection('Headphones')
|
||||
CheckSection('Newznab')
|
||||
CheckSection('NZBsorg')
|
||||
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')
|
||||
for section in ('General', 'SABnzbd', 'NZBget', 'Transmission',
|
||||
'uTorrent', 'Headphones', 'Newznab', 'NZBsorg',
|
||||
'omgwtfnzbs', 'Piratebay', 'Kat', 'Mininova', 'Waffles',
|
||||
'Rutracker', 'What.cd', 'Growl', 'Prowl', 'Pushover',
|
||||
'PushBullet', 'XBMC', 'LMS', 'Plex', 'NMA', 'Pushalot',
|
||||
'Synoindex', 'Twitter', 'OSX_Notify', 'Boxcar',
|
||||
'Songkick', 'Advanced'):
|
||||
CheckSection(section)
|
||||
|
||||
# Set global variables based on config file or use defaults
|
||||
CONFIG_VERSION = check_setting_str(CFG, 'General', 'config_version', '0')
|
||||
@@ -447,12 +438,14 @@ def initialize():
|
||||
LOSSLESS_BITRATE_TO = check_setting_int(CFG, 'General', 'lossless_bitrate_to', '')
|
||||
ADD_ARTISTS = bool(check_setting_int(CFG, 'General', 'auto_add_artists', 1))
|
||||
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))
|
||||
RENAME_FILES = bool(check_setting_int(CFG, 'General', 'rename_files', 0))
|
||||
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_UNDERSCORES = bool(check_setting_int(CFG, 'General', 'file_underscores', 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))
|
||||
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))
|
||||
@@ -478,27 +471,37 @@ def initialize():
|
||||
DOWNLOAD_SCAN_INTERVAL = check_setting_int(CFG, 'General', 'download_scan_interval', 5)
|
||||
UPDATE_DB_INTERVAL = check_setting_int(CFG, 'General', 'update_db_interval', 24)
|
||||
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', '')
|
||||
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', '')
|
||||
|
||||
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_UID = check_setting_str(CFG, 'Waffles', 'waffles_uid', '')
|
||||
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_USER = check_setting_str(CFG, 'Rutracker', 'rutracker_user', '')
|
||||
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_USERNAME = check_setting_str(CFG, 'What.cd', 'whatcd_username', '')
|
||||
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_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '')
|
||||
@@ -510,6 +513,7 @@ def initialize():
|
||||
NZBGET_PASSWORD = check_setting_str(CFG, 'NZBget', 'nzbget_password', '')
|
||||
NZBGET_CATEGORY = check_setting_str(CFG, 'NZBget', 'nzbget_category', '')
|
||||
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))
|
||||
|
||||
@@ -535,10 +539,6 @@ def initialize():
|
||||
NZBSORG_UID = check_setting_str(CFG, 'NZBsorg', 'nzbsorg_uid', '')
|
||||
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_UID = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_uid', '')
|
||||
OMGWTFNZBS_APIKEY = check_setting_str(CFG, 'omgwtfnzbs', 'omgwtfnzbs_apikey', '')
|
||||
@@ -633,6 +633,11 @@ def initialize():
|
||||
BOXCAR_ONSNATCH = bool(check_setting_int(CFG, 'Boxcar', 'boxcar_onsnatch', 0))
|
||||
BOXCAR_TOKEN = check_setting_str(CFG, 'Boxcar', 'boxcar_token', '')
|
||||
|
||||
SUBSONIC_ENABLED = bool(check_setting_int(CFG, 'Subsonic', 'subsonic_enabled', 0))
|
||||
SUBSONIC_HOST = check_setting_str(CFG, 'Subsonic', 'subsonic_host', '')
|
||||
SUBSONIC_USERNAME = check_setting_str(CFG, 'Subsonic', 'subsonic_username', '')
|
||||
SUBSONIC_PASSWORD = check_setting_str(CFG, 'Subsonic', 'subsonic_password', '')
|
||||
|
||||
SONGKICK_ENABLED = bool(check_setting_int(CFG, 'Songkick', 'songkick_enabled', 1))
|
||||
SONGKICK_APIKEY = check_setting_str(CFG, 'Songkick', 'songkick_apikey', 'nd1We7dFW2RqxPw8')
|
||||
SONGKICK_LOCATION = check_setting_str(CFG, 'Songkick', 'songkick_location', '')
|
||||
@@ -701,10 +706,10 @@ def initialize():
|
||||
CONFIG_VERSION = '3'
|
||||
|
||||
if CONFIG_VERSION == '3':
|
||||
#Update the BLACKHOLE option to the NZB_DOWNLOADER format
|
||||
if BLACKHOLE:
|
||||
NZB_DOWNLOADER = 2
|
||||
CONFIG_VERSION = '4'
|
||||
#Update the BLACKHOLE option to the NZB_DOWNLOADER format
|
||||
if BLACKHOLE:
|
||||
NZB_DOWNLOADER = 2
|
||||
CONFIG_VERSION = '4'
|
||||
|
||||
# Enable Headphones Indexer if they have a VIP account
|
||||
if CONFIG_VERSION == '4':
|
||||
@@ -724,7 +729,6 @@ def initialize():
|
||||
|
||||
# Start the logger, disable console if needed
|
||||
logger.initLogger(console=not QUIET, verbose=VERBOSE)
|
||||
logger.initLogger(console=not QUIET, verbose=False)
|
||||
|
||||
if not CACHE_DIR:
|
||||
# Put the cache dir in the data dir for now
|
||||
@@ -830,10 +834,14 @@ def launch_browser(host, port, root):
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
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.filename = CONFIG_FILE
|
||||
@@ -876,12 +884,14 @@ def config_write():
|
||||
new_config['General']['lossless_bitrate_to'] = LOSSLESS_BITRATE_TO
|
||||
new_config['General']['auto_add_artists'] = int(ADD_ARTISTS)
|
||||
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']['rename_files'] = int(RENAME_FILES)
|
||||
new_config['General']['folder_format'] = FOLDER_FORMAT
|
||||
new_config['General']['file_format'] = FILE_FORMAT
|
||||
new_config['General']['file_underscores'] = int(FILE_UNDERSCORES)
|
||||
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']['album_art_format'] = ALBUM_ART_FORMAT
|
||||
new_config['General']['embed_album_art'] = int(EMBED_ALBUM_ART)
|
||||
@@ -902,27 +912,39 @@ def config_write():
|
||||
|
||||
new_config['General']['numberofseeders'] = NUMBEROFSEEDERS
|
||||
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['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']['waffles'] = int(WAFFLES)
|
||||
new_config['Waffles']['waffles_uid'] = WAFFLES_UID
|
||||
new_config['Waffles']['waffles_passkey'] = WAFFLES_PASSKEY
|
||||
new_config['Waffles']['waffles_ratio'] = WAFFLES_RATIO
|
||||
|
||||
new_config['Rutracker'] = {}
|
||||
new_config['Rutracker']['rutracker'] = int(RUTRACKER)
|
||||
new_config['Rutracker']['rutracker_user'] = RUTRACKER_USER
|
||||
new_config['Rutracker']['rutracker_password'] = RUTRACKER_PASSWORD
|
||||
new_config['Rutracker']['rutracker_ratio'] = RUTRACKER_RATIO
|
||||
|
||||
new_config['What.cd'] = {}
|
||||
new_config['What.cd']['whatcd'] = int(WHATCD)
|
||||
new_config['What.cd']['whatcd_username'] = WHATCD_USERNAME
|
||||
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']['libraryscan'] = int(LIBRARYSCAN)
|
||||
@@ -930,6 +952,7 @@ def config_write():
|
||||
new_config['General']['download_scan_interval'] = DOWNLOAD_SCAN_INTERVAL
|
||||
new_config['General']['update_db_interval'] = UPDATE_DB_INTERVAL
|
||||
new_config['General']['mb_ignore_age'] = MB_IGNORE_AGE
|
||||
new_config['General']['torrent_removal_interval'] = TORRENT_REMOVAL_INTERVAL
|
||||
|
||||
new_config['SABnzbd'] = {}
|
||||
new_config['SABnzbd']['sab_host'] = SAB_HOST
|
||||
@@ -943,6 +966,7 @@ def config_write():
|
||||
new_config['NZBget']['nzbget_password'] = NZBGET_PASSWORD
|
||||
new_config['NZBget']['nzbget_category'] = NZBGET_CATEGORY
|
||||
new_config['NZBget']['nzbget_host'] = NZBGET_HOST
|
||||
new_config['NZBget']['nzbget_priority'] = NZBGET_PRIORITY
|
||||
|
||||
new_config['Headphones'] = {}
|
||||
new_config['Headphones']['headphones_indexer'] = int(HEADPHONES_INDEXER)
|
||||
@@ -976,11 +1000,6 @@ def config_write():
|
||||
new_config['NZBsorg']['nzbsorg_uid'] = NZBSORG_UID
|
||||
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']['omgwtfnzbs'] = int(OMGWTFNZBS)
|
||||
new_config['omgwtfnzbs']['omgwtfnzbs_uid'] = OMGWTFNZBS_UID
|
||||
@@ -1064,6 +1083,12 @@ def config_write():
|
||||
new_config['Boxcar']['boxcar_onsnatch'] = int(BOXCAR_ONSNATCH)
|
||||
new_config['Boxcar']['boxcar_token'] = BOXCAR_TOKEN
|
||||
|
||||
new_config['Subsonic'] = {}
|
||||
new_config['Subsonic']['subsonic_enabled'] = int(SUBSONIC_ENABLED)
|
||||
new_config['Subsonic']['subsonic_host'] = SUBSONIC_HOST
|
||||
new_config['Subsonic']['subsonic_username'] = SUBSONIC_USERNAME
|
||||
new_config['Subsonic']['subsonic_password'] = SUBSONIC_PASSWORD
|
||||
|
||||
new_config['Songkick'] = {}
|
||||
new_config['Songkick']['songkick_enabled'] = int(SONGKICK_ENABLED)
|
||||
new_config['Songkick']['songkick_apikey'] = SONGKICK_APIKEY
|
||||
@@ -1106,8 +1131,13 @@ def config_write():
|
||||
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():
|
||||
|
||||
@@ -1116,7 +1146,7 @@ def start():
|
||||
if __INITIALIZED__:
|
||||
|
||||
# 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(searcher.searchforalbum, minutes=SEARCH_INTERVAL)
|
||||
@@ -1128,6 +1158,10 @@ def start():
|
||||
if DOWNLOAD_SCAN_INTERVAL > 0:
|
||||
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()
|
||||
|
||||
started = True
|
||||
|
||||
@@ -72,7 +72,7 @@ def switch(AlbumID, ReleaseID):
|
||||
|
||||
# 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']]))
|
||||
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']}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import headphones
|
||||
|
||||
from headphones import db, mb, importer, searcher, cache, postprocessor, versioncheck, logger
|
||||
|
||||
import lib.simplejson as simplejson
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
import headphones
|
||||
import copy
|
||||
import json
|
||||
|
||||
cmd_list = [ 'getIndex', 'getArtist', 'getAlbum', 'getUpcoming', 'getWanted', 'getSimilar', 'getHistory', 'getLogs',
|
||||
'findArtist', 'findAlbum', 'addArtist', 'delArtist', 'pauseArtist', 'resumeArtist', 'refreshArtist',
|
||||
@@ -87,10 +87,10 @@ class Api(object):
|
||||
if type(self.data) == type(''):
|
||||
return self.data
|
||||
else:
|
||||
return simplejson.dumps(self.data)
|
||||
return json.dumps(self.data)
|
||||
else:
|
||||
self.callback = self.kwargs['callback']
|
||||
self.data = simplejson.dumps(self.data)
|
||||
self.data = json.dumps(self.data)
|
||||
self.data = self.callback + '(' + self.data + ');'
|
||||
return self.data
|
||||
else:
|
||||
@@ -145,7 +145,7 @@ class Api(object):
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
def _getUpcoming(self, **kwargs):
|
||||
|
||||
@@ -20,7 +20,7 @@ import headphones
|
||||
|
||||
from headphones import db, helpers, logger, lastfm, request
|
||||
|
||||
lastfm_apikey = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
||||
LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
|
||||
|
||||
class Cache(object):
|
||||
"""
|
||||
@@ -42,25 +42,22 @@ class Cache(object):
|
||||
|
||||
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):
|
||||
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):
|
||||
files = []
|
||||
@@ -126,9 +123,9 @@ class Cache(object):
|
||||
return thumb_url
|
||||
|
||||
def get_artwork_from_cache(self, ArtistID=None, AlbumID=None):
|
||||
'''
|
||||
"""
|
||||
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
||||
'''
|
||||
"""
|
||||
|
||||
self.query_type = 'artwork'
|
||||
|
||||
@@ -152,9 +149,9 @@ class Cache(object):
|
||||
return None
|
||||
|
||||
def get_thumb_from_cache(self, ArtistID=None, AlbumID=None):
|
||||
'''
|
||||
"""
|
||||
Pass a musicbrainz id to this function (either ArtistID or AlbumID)
|
||||
'''
|
||||
"""
|
||||
|
||||
self.query_type = 'thumb'
|
||||
|
||||
@@ -209,14 +206,14 @@ class Cache(object):
|
||||
if ArtistID:
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
try:
|
||||
image_url = data['artist']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except (KeyError, IndexError):
|
||||
logger.debug('No artist image found')
|
||||
image_url = None
|
||||
|
||||
@@ -227,21 +224,21 @@ class Cache(object):
|
||||
else:
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
try:
|
||||
image_url = data['artist']['image'][-1]['#text']
|
||||
except Exception:
|
||||
logger.debug('No artist image found')
|
||||
image_url = data['album']['image'][-1]['#text']
|
||||
except (KeyError, IndexError):
|
||||
logger.debug('No album image found on last.fm')
|
||||
image_url = None
|
||||
|
||||
thumb_url = self._get_thumb_url(data)
|
||||
|
||||
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 }
|
||||
|
||||
@@ -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
|
||||
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:
|
||||
return
|
||||
|
||||
try:
|
||||
self.info_summary = data['artist']['bio']['summary']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No artist bio summary found')
|
||||
self.info_summary = None
|
||||
try:
|
||||
self.info_content = data['artist']['bio']['content']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No artist bio found')
|
||||
self.info_content = None
|
||||
try:
|
||||
image_url = data['artist']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No artist image found')
|
||||
image_url = None
|
||||
|
||||
@@ -282,24 +279,24 @@ class Cache(object):
|
||||
else:
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
try:
|
||||
self.info_summary = data['album']['wiki']['summary']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No album summary found')
|
||||
self.info_summary = None
|
||||
try:
|
||||
self.info_content = data['album']['wiki']['content']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No album infomation found')
|
||||
self.info_content = None
|
||||
try:
|
||||
image_url = data['album']['image'][-1]['#text']
|
||||
except Exception:
|
||||
except KeyError:
|
||||
logger.debug('No album image link found')
|
||||
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)
|
||||
try:
|
||||
f = open(artwork_path, 'wb')
|
||||
f.write(artwork)
|
||||
f.close()
|
||||
except Exception, e:
|
||||
with open(artwork_path, 'wb') as f:
|
||||
f.write(artwork)
|
||||
except IOError as e:
|
||||
logger.error('Unable to write to the cache dir: %s', e)
|
||||
self.artwork_errors = True
|
||||
self.artwork_url = image_url
|
||||
@@ -376,7 +372,7 @@ class Cache(object):
|
||||
if not os.path.isdir(self.path_to_art_cache):
|
||||
try:
|
||||
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)
|
||||
self.thumb_errors = True
|
||||
self.thumb_url = thumb_url
|
||||
@@ -385,17 +381,16 @@ class Cache(object):
|
||||
for thumb_file in self.thumb_files:
|
||||
try:
|
||||
os.remove(thumb_file)
|
||||
except:
|
||||
except Exception as e:
|
||||
logger.error('Error deleting file from the cache: %s', thumb_file)
|
||||
|
||||
ext = os.path.splitext(image_url)[1]
|
||||
|
||||
thumb_path = os.path.join(self.path_to_art_cache, 'T_' + self.id + '.' + helpers.today() + ext)
|
||||
try:
|
||||
f = open(thumb_path, 'wb')
|
||||
f.write(artwork)
|
||||
f.close()
|
||||
except Exception, e:
|
||||
with open(thumb_path, 'wb') as f:
|
||||
f.write(artwork)
|
||||
except IOError as e:
|
||||
logger.error('Unable to write to the cache dir: %s', e)
|
||||
self.thumb_errors = True
|
||||
self.thumb_url = image_url
|
||||
|
||||
@@ -59,37 +59,32 @@ class DBConnection:
|
||||
return
|
||||
|
||||
sqlResult = None
|
||||
attempt = 0
|
||||
|
||||
while attempt < 5:
|
||||
try:
|
||||
try:
|
||||
with self.connection as c:
|
||||
if args == None:
|
||||
#logger.debug(self.filename+": "+query)
|
||||
sqlResult = self.connection.execute(query)
|
||||
sqlResult = c.execute(query)
|
||||
else:
|
||||
#logger.debug(self.filename+": "+query+" with args "+str(args))
|
||||
sqlResult = self.connection.execute(query, args)
|
||||
self.connection.commit()
|
||||
break
|
||||
except sqlite3.OperationalError, e:
|
||||
if "unable to open database file" in e.message or "database is locked" in e.message:
|
||||
logger.warn('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)
|
||||
sqlResult = c.execute(query, args)
|
||||
|
||||
except sqlite3.OperationalError, e:
|
||||
if "unable to open database file" in e.message or "database is locked" in e.message:
|
||||
logger.warn('Database Error: %s', e)
|
||||
else:
|
||||
logger.error('Database error: %s', e)
|
||||
raise
|
||||
|
||||
except sqlite3.DatabaseError, e:
|
||||
logger.error('Fatal Error executing %s :: %s', query, e)
|
||||
raise
|
||||
|
||||
return sqlResult
|
||||
|
||||
def select(self, query, args=None):
|
||||
|
||||
sqlResults = self.action(query, args).fetchall()
|
||||
|
||||
if sqlResults == None:
|
||||
if sqlResults == None or sqlResults == [None]:
|
||||
return []
|
||||
|
||||
return sqlResults
|
||||
|
||||
@@ -13,29 +13,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# 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):
|
||||
"Generic Headphones Exception - should never be thrown, only subclassed"
|
||||
"""
|
||||
Generic Headphones Exception - should never be thrown, only subclassed
|
||||
"""
|
||||
|
||||
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 datetime
|
||||
import headphones
|
||||
import unicodedata
|
||||
import sys
|
||||
|
||||
from operator import itemgetter
|
||||
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||
@@ -168,12 +170,17 @@ def piratesize(size):
|
||||
|
||||
return size
|
||||
|
||||
def replace_all(text, dic):
|
||||
def replace_all(text, dic, normalize=False):
|
||||
|
||||
if not text:
|
||||
return ''
|
||||
|
||||
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)
|
||||
return text
|
||||
|
||||
@@ -596,9 +603,11 @@ def create_https_certificates(ssl_cert, ssl_key):
|
||||
|
||||
# Save the key and certificate to disk
|
||||
try:
|
||||
open(ssl_key, 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
open(ssl_cert, 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
|
||||
except Exception, e:
|
||||
with open(ssl_key, 'w') as f:
|
||||
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
|
||||
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)
|
||||
return False
|
||||
|
||||
|
||||
@@ -13,24 +13,26 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# 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
|
||||
|
||||
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']
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
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):
|
||||
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# 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):
|
||||
try:
|
||||
artist = artist.decode('utf-8', 'replace')
|
||||
except:
|
||||
except Exception:
|
||||
logger.warn("Unable to convert artist to unicode so cannot do a database lookup")
|
||||
continue
|
||||
|
||||
@@ -68,7 +70,6 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
|
||||
try:
|
||||
artistid = results[0]['id']
|
||||
|
||||
except IndexError:
|
||||
logger.info('MusicBrainz query turned up no matches for: %s' % artist)
|
||||
continue
|
||||
@@ -98,9 +99,10 @@ def artistlist_to_mbids(artistlist, forced=False):
|
||||
|
||||
# Update the similar artist tag cloud:
|
||||
logger.info('Updating artist information from Last.fm')
|
||||
|
||||
try:
|
||||
lastfm.getSimilar()
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.warn('Failed to update arist information from Last.fm: %s' % e)
|
||||
|
||||
def addArtistIDListToDB(artistidlist):
|
||||
@@ -129,11 +131,9 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
# We need the current minimal info in the database instantly
|
||||
# so we don't throw a 500 error when we redirect to the artistPage
|
||||
|
||||
controlValueDict = {"ArtistID": artistid}
|
||||
|
||||
# Don't replace a known artist name with an "Artist ID" placeholder
|
||||
|
||||
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
|
||||
@@ -182,24 +182,27 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
# See if we need to grab extras. Artist specific extras take precedence over global option
|
||||
# Global options are set when adding a new artist
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# See if we need to grab extras. Artist specific extras take precedence
|
||||
# over global option. Global options are set when adding a new artist
|
||||
try:
|
||||
db_artist = myDB.action('SELECT IncludeExtras, Extras from artists WHERE ArtistID=?', [artistid]).fetchone()
|
||||
includeExtras = db_artist['IncludeExtras']
|
||||
except IndexError:
|
||||
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 = []
|
||||
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']:
|
||||
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:
|
||||
if items['AlbumID'] not in group_list:
|
||||
# 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 alltracks WHERE AlbumID=?", [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']))
|
||||
force_repackage = 1
|
||||
elif extrasonly:
|
||||
# Not really sure what we're doing here but don't want to log the message below if we're fetching extras only
|
||||
pass
|
||||
logger.info("[%s] Removing all references to release group %s to reflect MusicBrainz refresh" % (artist['artist_name'], items['AlbumID']))
|
||||
if not extrasonly:
|
||||
force_repackage = 1
|
||||
else:
|
||||
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
|
||||
album_searches = []
|
||||
@@ -230,7 +232,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
rg_exists = myDB.action("SELECT * from albums WHERE AlbumID=?", [rg['id']]).fetchone()
|
||||
|
||||
if not forcefull:
|
||||
|
||||
new_release_group = False
|
||||
|
||||
try:
|
||||
@@ -241,12 +242,10 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
|
||||
if new_release_group:
|
||||
|
||||
logger.info("[%s] Now adding: %s (New Release Group)" % (artist['artist_name'], rg['title']))
|
||||
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)
|
||||
@@ -260,33 +259,35 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
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))
|
||||
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))
|
||||
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:
|
||||
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:
|
||||
new_releases = new_releases
|
||||
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)
|
||||
|
||||
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 allalbums 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 releases WHERE ReleaseGroupID=?', [rg['id']])
|
||||
|
||||
# This will be used later to build a hybrid release
|
||||
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']])
|
||||
|
||||
# Build the dictionary for the fullreleaselist
|
||||
for items in find_hybrid_releases:
|
||||
if items['ReleaseID'] != rg['id']: #don't include hybrid information, since that's what we're replacing
|
||||
@@ -317,14 +318,12 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
newValueDict['Tracks'] = hybrid_track_array
|
||||
fullreleaselist.append(newValueDict)
|
||||
|
||||
#print fullreleaselist
|
||||
|
||||
# Basically just do the same thing again for the hybrid release
|
||||
# This may end up being called with an empty fullreleaselist
|
||||
try:
|
||||
hybridrelease = getHybridRelease(fullreleaselist)
|
||||
logger.info('[%s] Packaging %s releases into hybrid title' % (artist['artist_name'], rg['title']))
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
errors = True
|
||||
logger.warn('[%s] Unable to get hybrid release information for %s: %s' % (artist['artist_name'],rg['title'],e))
|
||||
continue
|
||||
@@ -384,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 is a release, check the ReleaseID against the AlbumID to see if they differ (user updated)
|
||||
# check if the album already exists
|
||||
|
||||
if not rg_exists:
|
||||
releaseid = rg['id']
|
||||
else:
|
||||
releaseid = rg_exists['ReleaseID']
|
||||
if not releaseid:
|
||||
releaseid = rg['id']
|
||||
|
||||
album = myDB.action('SELECT * from allalbums WHERE ReleaseID=?', [releaseid]).fetchone()
|
||||
|
||||
@@ -429,11 +429,17 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
|
||||
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)
|
||||
|
||||
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'],
|
||||
"AlbumID": rg['id']}
|
||||
|
||||
@@ -478,6 +484,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
|
||||
if skip_log == 0:
|
||||
logger.info(u"[%s] No new releases, so no changes made to %s" % (artist['artist_name'], rg['title']))
|
||||
|
||||
time.sleep(3)
|
||||
finalize_update(artistid, artist['artist_name'], errors)
|
||||
|
||||
logger.info(u"Seeing if we need album art for: %s" % artist['artist_name'])
|
||||
@@ -526,10 +533,23 @@ def finalize_update(artistid, artistname, errors=False):
|
||||
|
||||
myDB.upsert("artists", newValueDict, controlValueDict)
|
||||
|
||||
def addReleaseById(rid):
|
||||
def addReleaseById(rid, rgid=None):
|
||||
|
||||
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
|
||||
artistid = None
|
||||
release_dict = None
|
||||
@@ -543,11 +563,15 @@ def addReleaseById(rid):
|
||||
logger.debug("Didn't find releaseID " + rid + " in the cache. Looking up its ReleaseGroupID")
|
||||
try:
|
||||
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)
|
||||
if status == 'Loading':
|
||||
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||
return
|
||||
if not release_dict:
|
||||
logger.info('Unable to get release information for Release %s: no dict', rid)
|
||||
if status == 'Loading':
|
||||
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||
return
|
||||
|
||||
rgid = release_dict['rgid']
|
||||
@@ -565,7 +589,6 @@ def addReleaseById(rid):
|
||||
else:
|
||||
sortname = release_dict['artist_name']
|
||||
|
||||
|
||||
logger.info(u"Now manually adding: " + release_dict['artist_name'] + " - with status Paused")
|
||||
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
||||
newValueDict = {"ArtistName": release_dict['artist_name'],
|
||||
@@ -581,12 +604,16 @@ def addReleaseById(rid):
|
||||
|
||||
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.")
|
||||
if status == 'Loading':
|
||||
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||
return
|
||||
|
||||
if not rg_exists 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
|
||||
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
|
||||
logger.info(u"Now adding-by-id album (" + release_dict['title'] + ") from id: " + rgid)
|
||||
controlValueDict = {"AlbumID": rgid}
|
||||
if status != 'Loading':
|
||||
status = 'Wanted'
|
||||
|
||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||
"ReleaseID": rgid,
|
||||
@@ -595,8 +622,9 @@ def addReleaseById(rid):
|
||||
"AlbumASIN": release_dict['asin'],
|
||||
"ReleaseDate": release_dict['date'],
|
||||
"DateAdded": helpers.today(),
|
||||
"Status": 'Wanted',
|
||||
"Type": release_dict['rg_type']
|
||||
"Status": status,
|
||||
"Type": release_dict['rg_type'],
|
||||
"ReleaseID": rid
|
||||
}
|
||||
|
||||
myDB.upsert("albums", newValueDict, controlValueDict)
|
||||
@@ -605,7 +633,6 @@ def addReleaseById(rid):
|
||||
myDB.action('INSERT INTO releases VALUES( ?, ?)', [rid, release_dict['rgid']])
|
||||
|
||||
for track in release_dict['tracks']:
|
||||
|
||||
cleanname = helpers.cleanName(release_dict['artist_name'] + ' ' + release_dict['rg_title'] + ' ' + track['title'])
|
||||
|
||||
controlValueDict = {"TrackID": track['id'],
|
||||
@@ -636,11 +663,19 @@ def addReleaseById(rid):
|
||||
|
||||
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
|
||||
searcher.searchforalbum(rgid, False)
|
||||
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.")
|
||||
if status == 'Loading':
|
||||
myDB.action("DELETE FROM albums WHERE AlbumID=?", [rgid])
|
||||
return
|
||||
else:
|
||||
logger.info('Release ' + str(rid) + " already exists in the database!")
|
||||
@@ -676,39 +711,43 @@ def updateFormat():
|
||||
|
||||
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:
|
||||
raise Exception("getHybridRelease was called with an empty fullreleaselist")
|
||||
raise ValueError("Empty fullreleaselist")
|
||||
|
||||
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:
|
||||
|
||||
formats = {
|
||||
'2xVinyl': '2',
|
||||
'Vinyl': '2',
|
||||
'CD': '0',
|
||||
'Cassette': '3',
|
||||
'2xCD': '1',
|
||||
'Digital Media': '0'
|
||||
}
|
||||
|
||||
countries = {
|
||||
'US': '0',
|
||||
'GB': '1',
|
||||
'JP': '2',
|
||||
}
|
||||
|
||||
# Find values for format and country
|
||||
try:
|
||||
format = int(formats[release['Format']])
|
||||
except:
|
||||
except (ValueError, KeyError):
|
||||
format = 3
|
||||
|
||||
try:
|
||||
country = int(countries[release['Country']])
|
||||
except:
|
||||
except (ValueError, KeyError):
|
||||
country = 3
|
||||
|
||||
# Create record
|
||||
release_dict = {
|
||||
'hasasin': bool(release['AlbumASIN']),
|
||||
'asin': release['AlbumASIN'],
|
||||
@@ -718,15 +757,19 @@ def getHybridRelease(fullreleaselist):
|
||||
'format': format,
|
||||
'country': country,
|
||||
'tracks': release['Tracks']
|
||||
}
|
||||
}
|
||||
|
||||
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):
|
||||
# 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:
|
||||
return 'None';#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
|
||||
return 'None';
|
||||
|
||||
if releaseDate.count('-') == 2:
|
||||
return releaseDate
|
||||
elif releaseDate.count('-') == 1:
|
||||
|
||||
@@ -21,16 +21,17 @@ from headphones import db, logger, request
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
ENTRY_POINT = 'http://ws.audioscrobbler.com/2.0/'
|
||||
API_KEY = '395e6ec6bb557382fc41fde867bce66f'
|
||||
TIMEOUT = 60 # seconds
|
||||
ENTRY_POINT = "http://ws.audioscrobbler.com/2.0/"
|
||||
API_KEY = "395e6ec6bb557382fc41fde867bce66f"
|
||||
|
||||
def request_lastfm(method, **kwargs):
|
||||
"""
|
||||
Call a Last.FM API method. Automatically sets the method and API key. Method
|
||||
will return the result if no error occured.
|
||||
|
||||
By default, this method will request the JSON format, since it is lighter
|
||||
than XML.
|
||||
By default, this method will request the JSON format, since it is more
|
||||
lightweight than XML.
|
||||
"""
|
||||
|
||||
# Prepare request
|
||||
@@ -40,7 +41,9 @@ def request_lastfm(method, **kwargs):
|
||||
|
||||
# Send request
|
||||
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.
|
||||
if not data:
|
||||
@@ -48,20 +51,20 @@ def request_lastfm(method, **kwargs):
|
||||
return
|
||||
|
||||
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 data
|
||||
|
||||
def getSimilar():
|
||||
myDB = db.DBConnection()
|
||||
results = myDB.select('SELECT ArtistID from artists ORDER BY HaveTracks DESC')
|
||||
results = myDB.select("SELECT ArtistID from artists ORDER BY HaveTracks DESC")
|
||||
|
||||
logger.info("Fetching similar artists from Last.FM for tag cloud")
|
||||
artistlist = []
|
||||
|
||||
for result in results[:12]:
|
||||
data = request_lastfm("artist.getsimilar", mbid=result['ArtistId'])
|
||||
data = request_lastfm("artist.getsimilar", mbid=result["ArtistId"])
|
||||
time.sleep(10)
|
||||
|
||||
if data and "similarartists" in data:
|
||||
@@ -94,13 +97,13 @@ def getSimilar():
|
||||
artist_name, artist_mbid = item[0]
|
||||
count = item[1]
|
||||
|
||||
myDB.action('INSERT INTO lastfmcloud VALUES( ?, ?, ?)', [artist_name, artist_mbid, count])
|
||||
myDB.action("INSERT INTO lastfmcloud VALUES( ?, ?, ?)", [artist_name, artist_mbid, count])
|
||||
|
||||
logger.debug("Inserted %d artists into Last.FM tag cloud", len(top_list))
|
||||
|
||||
def getArtists():
|
||||
myDB = db.DBConnection()
|
||||
results = myDB.select('SELECT ArtistID from artists')
|
||||
results = myDB.select("SELECT ArtistID from artists")
|
||||
|
||||
if not headphones.LASTFM_USERNAME:
|
||||
logger.warn("Last.FM username not set, not importing artists.")
|
||||
@@ -129,7 +132,7 @@ def getArtists():
|
||||
|
||||
def getTagTopArtists(tag, limit=50):
|
||||
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)
|
||||
data = request_lastfm("tag.gettopartists", limit=limit, tag=tag)
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
|
||||
import os
|
||||
import glob
|
||||
|
||||
from beets.mediafile import MediaFile
|
||||
|
||||
import headphones
|
||||
|
||||
from beets.mediafile import MediaFile, FileTypeError, UnreadableFileError
|
||||
|
||||
from headphones import db, logger, helpers, importer, lastfm
|
||||
|
||||
# You can scan a single directory and append it to the current library by specifying append=True, ArtistID & ArtistName
|
||||
@@ -50,7 +50,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
|
||||
if not append:
|
||||
# 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:
|
||||
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')
|
||||
|
||||
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 track['ArtistName']:
|
||||
#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[:]:
|
||||
if directory.startswith("."):
|
||||
d.remove(directory)
|
||||
for files in f:
|
||||
|
||||
for files in f:
|
||||
# MEDIA_FORMATS = music file extensions, e.g. mp3, flac, etc
|
||||
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:
|
||||
f = MediaFile(song)
|
||||
|
||||
except:
|
||||
logger.error('Cannot read file: ' + unicode_song_path)
|
||||
except (FileTypeError, UnreadableFileError):
|
||||
logger.error("Cannot read file media file '%s'. It may be corrupted or not a media file.", unicode_song_path)
|
||||
continue
|
||||
|
||||
# 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']:
|
||||
|
||||
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:
|
||||
controlValueDict = { 'ArtistName' : track['ArtistName'],
|
||||
'AlbumTitle' : track['AlbumTitle'],
|
||||
@@ -228,6 +228,7 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
have_updated = True
|
||||
else:
|
||||
track = myDB.action('SELECT CleanName, AlbumID from tracks WHERE CleanName LIKE ?', [song['CleanName']]).fetchone()
|
||||
if track:
|
||||
@@ -240,11 +241,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : track['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
have_updated = True
|
||||
else:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
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()
|
||||
if alltrack:
|
||||
@@ -272,9 +274,12 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
|
||||
newValueDict2 = { 'Matched' : alltrack['AlbumID']}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
else:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
# alltracks may not exist if adding album manually, have should only be set to failed if not already updated in tracks
|
||||
if not have_updated:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
myDB.upsert("have", newValueDict2, controlValueDict2)
|
||||
|
||||
else:
|
||||
controlValueDict2 = { 'Location' : song['Location']}
|
||||
newValueDict2 = { 'Matched' : "Failed"}
|
||||
|
||||
@@ -13,24 +13,30 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# 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 logutils.queue import QueueHandler, QueueListener
|
||||
from logging import handlers
|
||||
|
||||
import multiprocessing
|
||||
import contextlib
|
||||
import headphones
|
||||
import threading
|
||||
import traceback
|
||||
import logging
|
||||
import sys
|
||||
import os
|
||||
|
||||
# These settings are for file logging only
|
||||
FILENAME = 'headphones.log'
|
||||
FILENAME = "headphones.log"
|
||||
MAX_SIZE = 1000000 # 1 MB
|
||||
MAX_FILES = 5
|
||||
|
||||
# Headphones logger
|
||||
logger = logging.getLogger('headphones')
|
||||
logger = logging.getLogger("headphones")
|
||||
|
||||
# Global queue of multiprocessing logging
|
||||
queue = multiprocessing.Queue()
|
||||
|
||||
class LogListHandler(logging.Handler):
|
||||
"""
|
||||
@@ -43,6 +49,39 @@ class LogListHandler(logging.Handler):
|
||||
|
||||
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
|
||||
|
||||
@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
|
||||
|
||||
@@ -13,19 +13,23 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# 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.helpers import multikeysort, replace_all
|
||||
|
||||
import lib.musicbrainzngs as musicbrainzngs
|
||||
from lib.musicbrainzngs import WebServiceError
|
||||
import time
|
||||
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()
|
||||
|
||||
|
||||
# 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
|
||||
def startmb():
|
||||
@@ -75,13 +79,15 @@ def findArtist(name, limit=1):
|
||||
artistlist = []
|
||||
artistResults = None
|
||||
|
||||
chars = set('!?*')
|
||||
chars = set('!?*-')
|
||||
if any((c in chars) for c in name):
|
||||
name = '"'+name+'"'
|
||||
|
||||
criteria = {'artist': name.lower()}
|
||||
|
||||
try:
|
||||
artistResults = musicbrainzngs.search_artists(query='artist:'+name,limit=limit)['artist-list']
|
||||
except WebServiceError, e:
|
||||
artistResults = musicbrainzngs.search_artists(limit=limit, **criteria)['artist-list']
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
|
||||
time.sleep(5)
|
||||
|
||||
@@ -117,33 +123,81 @@ def findArtist(name, limit=1):
|
||||
})
|
||||
return artistlist
|
||||
|
||||
def findRelease(name, limit=1):
|
||||
def findRelease(name, limit=1, artist=None):
|
||||
|
||||
with mb_lock:
|
||||
releaselist = []
|
||||
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):
|
||||
name = '"'+name+'"'
|
||||
if artist and any((c in chars) for c in artist):
|
||||
artist = '"'+artist+'"'
|
||||
|
||||
try:
|
||||
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit)['release-list']
|
||||
except WebServiceError, e: #need to update exceptions
|
||||
releaseResults = musicbrainzngs.search_releases(query=name,limit=limit,artist=artist)['release-list']
|
||||
except musicbrainzngs.WebServiceError as e: #need to update exceptions
|
||||
logger.warn('Attempt to query MusicBrainz for "%s" failed: %s' % (name, str(e)))
|
||||
time.sleep(5)
|
||||
|
||||
if not releaseResults:
|
||||
return False
|
||||
|
||||
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']),
|
||||
'title': unicode(result['title']),
|
||||
'title': unicode(title),
|
||||
'id': unicode(result['artist-credit'][0]['artist']['id']),
|
||||
'albumid': unicode(result['id']),
|
||||
'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
|
||||
'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
|
||||
|
||||
@@ -162,7 +216,7 @@ def getArtist(artistid, extrasonly=False):
|
||||
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']
|
||||
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)))
|
||||
time.sleep(5)
|
||||
except Exception,e:
|
||||
@@ -219,14 +273,18 @@ def getArtist(artistid, extrasonly=False):
|
||||
|
||||
if includeExtras:
|
||||
|
||||
# Need to convert extras string from something like '2,5.6' to ['ep','live','remix']
|
||||
extras = db_artist['Extras']
|
||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other"]
|
||||
# Need to convert extras string from something like '2,5.6' to ['ep','live','remix'] (append new extras to end)
|
||||
if db_artist['Extras']:
|
||||
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 = []
|
||||
|
||||
i = 1
|
||||
for extra in extras_list:
|
||||
if str(i) in extras:
|
||||
if i in extras:
|
||||
includes.append(extra)
|
||||
i += 1
|
||||
|
||||
@@ -240,16 +298,23 @@ def getArtist(artistid, extrasonly=False):
|
||||
while newRgs == None or len(newRgs) >= limit:
|
||||
newRgs = musicbrainzngs.browse_release_groups(artistid,release_type=include,offset=len(mb_extras_list),limit=limit)['release-group-list']
|
||||
mb_extras_list += newRgs
|
||||
except WebServiceError, e:
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to retrieve artist information from MusicBrainz failed for artistid: %s (%s)' % (artistid, str(e)))
|
||||
time.sleep(5)
|
||||
|
||||
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({
|
||||
'title': unicode(rg['title']),
|
||||
'id': unicode(rg['id']),
|
||||
'url': u"http://musicbrainz.org/release-group/" + rg['id'],
|
||||
'type': unicode(rg['type'])
|
||||
'type': unicode(rg_type)
|
||||
})
|
||||
|
||||
artist_dict['releasegroups'] = releasegroups
|
||||
@@ -268,7 +333,7 @@ def getReleaseGroup(rgid):
|
||||
|
||||
try:
|
||||
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)))
|
||||
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')
|
||||
else:
|
||||
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)))
|
||||
time.sleep(5)
|
||||
|
||||
@@ -320,8 +385,15 @@ def getRelease(releaseid, include_artist_info=True):
|
||||
release['rg_title'] = unicode(results['release-group']['title'])
|
||||
try:
|
||||
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:
|
||||
release['rg_type'] = u'Unknown'
|
||||
|
||||
else:
|
||||
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']
|
||||
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)))
|
||||
time.sleep(5)
|
||||
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)
|
||||
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
|
||||
if 'artist-credit' in releasedata:
|
||||
@@ -535,7 +611,7 @@ def findArtistbyAlbum(name):
|
||||
|
||||
try:
|
||||
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)))
|
||||
time.sleep(5)
|
||||
|
||||
@@ -564,14 +640,23 @@ def findArtistbyAlbum(name):
|
||||
def findAlbumID(artist=None, album=None):
|
||||
|
||||
results = None
|
||||
chars = set('!?*-')
|
||||
|
||||
try:
|
||||
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:
|
||||
term = album
|
||||
results = musicbrainzngs.search_release_groups(term,1).get('release-group-list')
|
||||
except WebServiceError, e:
|
||||
if any((c in chars) for c in album):
|
||||
album = '"'+album+'"'
|
||||
criteria = {'release': album.lower()}
|
||||
|
||||
results = musicbrainzngs.search_release_groups(limit=1, **criteria).get('release-group-list')
|
||||
except musicbrainzngs.WebServiceError as e:
|
||||
logger.warn('Attempt to query MusicBrainz for %s - %s failed (%s)' % (artist, album, str(e)))
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ def encode(albumPath):
|
||||
time.sleep(1)
|
||||
|
||||
os.mkdir(tempDirEncode)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.exception("Unable to create temporary directory")
|
||||
return None
|
||||
|
||||
@@ -96,13 +96,18 @@ def encode(albumPath):
|
||||
encoder = "C:/Program Files/ffmpeg/bin/ffmpeg.exe"
|
||||
else:
|
||||
encoder="ffmpeg"
|
||||
elif headphones.ENCODER == 'libav':
|
||||
if headphones.SYS_PLATFORM == "win32":
|
||||
encoder = "C:/Program Files/libav/bin/avconv.exe"
|
||||
else:
|
||||
encoder="avconv"
|
||||
|
||||
i=0
|
||||
encoder_failed = False
|
||||
jobs = []
|
||||
|
||||
for music in musicFiles:
|
||||
infoMusic=MediaFile(music)
|
||||
infoMusic = MediaFile(music)
|
||||
encode = False
|
||||
|
||||
if XLD:
|
||||
@@ -141,29 +146,32 @@ def encode(albumPath):
|
||||
|
||||
# Encode music files
|
||||
if len(jobs) > 0:
|
||||
processes = 1
|
||||
|
||||
# Use multicore if enabled
|
||||
if headphones.ENCODER_MULTICORE:
|
||||
if headphones.ENCODER_MULTICORE_COUNT == 0:
|
||||
processes = multiprocessing.cpu_count()
|
||||
else:
|
||||
processes = headphones.ENCODER_MULTICORE_COUNT
|
||||
|
||||
logger.debug("Multi-core encoding enabled, %d processes", processes)
|
||||
else:
|
||||
processes = 1
|
||||
logger.debug("Multi-core encoding enabled, spawning %d processes",
|
||||
processes)
|
||||
|
||||
# Use multiprocessing only if it's worth the overhead. and if it is
|
||||
# enabled. If not, then use the old fashioned way.
|
||||
if processes > 1:
|
||||
pool = multiprocessing.Pool(processes=processes)
|
||||
results = pool.map_async(command_map, jobs)
|
||||
with logger.listener():
|
||||
pool = multiprocessing.Pool(processes=processes)
|
||||
results = pool.map_async(command_map, jobs)
|
||||
|
||||
# No new processes will be created, so close it and wait for all
|
||||
# processes to finish
|
||||
pool.close()
|
||||
pool.join()
|
||||
# No new processes will be created, so close it and wait for all
|
||||
# processes to finish
|
||||
pool.close()
|
||||
pool.join()
|
||||
|
||||
# Retrieve the results
|
||||
results = results.get()
|
||||
# Retrieve the results
|
||||
results = results.get()
|
||||
else:
|
||||
results = map(command_map, jobs)
|
||||
|
||||
@@ -178,7 +186,7 @@ def encode(albumPath):
|
||||
for dest in musicTempFiles:
|
||||
if not os.path.exists(dest):
|
||||
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
|
||||
if not encoder_failed and musicTempFiles:
|
||||
@@ -204,7 +212,7 @@ def encode(albumPath):
|
||||
|
||||
# Return with error if any encoding errors
|
||||
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
|
||||
|
||||
time.sleep(1)
|
||||
@@ -220,19 +228,31 @@ def encode(albumPath):
|
||||
|
||||
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:
|
||||
return command(*args)
|
||||
except Exception, e:
|
||||
logger.exception("Encoder exception, will return failed")
|
||||
except Exception as e:
|
||||
logger.exception("Encoder raised an exception.")
|
||||
return False
|
||||
|
||||
def command(encoder, musicSource ,musicDest, albumPath):
|
||||
cmd=[]
|
||||
startMusicTime=time.time()
|
||||
def command(encoder, musicSource, musicDest, albumPath):
|
||||
"""
|
||||
Encode a given music file with a certain encoder. Returns True on success,
|
||||
or False otherwise.
|
||||
"""
|
||||
|
||||
startMusicTime = time.time()
|
||||
cmd = []
|
||||
|
||||
# XLD
|
||||
if XLD:
|
||||
xldDestDir = os.path.split(musicDest)[0]
|
||||
cmd = [encoder]
|
||||
@@ -242,10 +262,11 @@ def command(encoder, musicSource ,musicDest, albumPath):
|
||||
cmd.extend(['-o'])
|
||||
cmd.extend([xldDestDir])
|
||||
|
||||
# Lame
|
||||
elif headphones.ENCODER == 'lame':
|
||||
cmd = [encoder]
|
||||
opts = []
|
||||
if headphones.ADVANCEDENCODER =='':
|
||||
if not headphones.ADVANCEDENCODER:
|
||||
opts.extend(['-h'])
|
||||
if headphones.ENCODERVBRCBR=='cbr':
|
||||
opts.extend(['--resample', str(headphones.SAMPLINGFREQUENCY), '-b', str(headphones.BITRATE)])
|
||||
@@ -259,10 +280,11 @@ def command(encoder, musicSource ,musicDest, albumPath):
|
||||
opts.extend([musicDest])
|
||||
cmd.extend(opts)
|
||||
|
||||
# FFmpeg
|
||||
elif headphones.ENCODER == 'ffmpeg':
|
||||
cmd = [encoder, '-i', musicSource]
|
||||
opts = []
|
||||
if headphones.ADVANCEDENCODER =='':
|
||||
if not headphones.ADVANCEDENCODER:
|
||||
if headphones.ENCODEROUTPUTFORMAT=='ogg':
|
||||
opts.extend(['-acodec', 'libvorbis'])
|
||||
if headphones.ENCODEROUTPUTFORMAT=='m4a':
|
||||
@@ -279,13 +301,30 @@ def command(encoder, musicSource ,musicDest, albumPath):
|
||||
opts.extend([musicDest])
|
||||
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')))
|
||||
logger.debug(subprocess.list2cmdline(cmd))
|
||||
|
||||
# stop windows opening the cmd
|
||||
# Prevent Windows from opening a terminal window
|
||||
startupinfo = None
|
||||
|
||||
if headphones.SYS_PLATFORM == "win32":
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
try:
|
||||
@@ -293,12 +332,17 @@ def command(encoder, musicSource ,musicDest, albumPath):
|
||||
except AttributeError:
|
||||
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
|
||||
if p.returncode:
|
||||
# Error if return code not zero
|
||||
if process.returncode:
|
||||
logger.error('Encoding failed for %s' % (musicSource.decode(headphones.SYS_ENCODING, 'replace')))
|
||||
out = stdout if stdout else stderr
|
||||
out = out.decode(headphones.SYS_ENCODING, 'replace')
|
||||
@@ -320,4 +364,3 @@ def getTimeEncode(start):
|
||||
minutes = seconds / 60
|
||||
seconds -= 60*minutes
|
||||
return "%02d:%02d:%02d" % (hours, minutes, seconds)
|
||||
|
||||
|
||||
@@ -13,33 +13,36 @@
|
||||
# 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, helpers, common, request
|
||||
|
||||
from xml.dom import minidom
|
||||
from httplib import HTTPSConnection
|
||||
from urllib import urlencode
|
||||
from pynma import pynma
|
||||
|
||||
import base64
|
||||
import cherrypy
|
||||
import urllib
|
||||
import urllib2
|
||||
import headphones
|
||||
import simplejson
|
||||
import os.path
|
||||
import subprocess
|
||||
import gntp.notifier
|
||||
import time
|
||||
import json
|
||||
|
||||
from xml.dom import minidom
|
||||
from httplib import HTTPSConnection
|
||||
from urllib import urlencode
|
||||
from lib.pynma import pynma
|
||||
import lib.oauth2 as oauth
|
||||
import lib.pythontwitter as twitter
|
||||
|
||||
from headphones import logger, helpers, common, request
|
||||
from headphones.exceptions import ex
|
||||
import oauth2 as oauth
|
||||
import pythontwitter as twitter
|
||||
|
||||
try:
|
||||
from urlparse import parse_qsl
|
||||
except:
|
||||
except ImportError:
|
||||
from cgi import parse_qsl
|
||||
|
||||
class GROWL:
|
||||
class GROWL(object):
|
||||
"""
|
||||
Growl notifications, for OS X
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = headphones.GROWL_ENABLED
|
||||
@@ -89,7 +92,9 @@ class GROWL:
|
||||
|
||||
# Send it, including an image
|
||||
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:
|
||||
growl.notify(
|
||||
@@ -115,10 +120,10 @@ class GROWL:
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
class PROWL:
|
||||
|
||||
keys = []
|
||||
priority = []
|
||||
class PROWL(object):
|
||||
"""
|
||||
Prowl notifications.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = headphones.PROWL_ENABLED
|
||||
@@ -162,25 +167,29 @@ class PROWL:
|
||||
return
|
||||
|
||||
def test(self, keys, priority):
|
||||
|
||||
self.enabled = True
|
||||
self.keys = keys
|
||||
self.priority = priority
|
||||
|
||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||
|
||||
class MPC:
|
||||
class MPC(object):
|
||||
"""
|
||||
MPC library update
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
pass
|
||||
|
||||
def notify( self ):
|
||||
|
||||
subprocess.call( ["mpc", "update"] )
|
||||
|
||||
|
||||
class XBMC:
|
||||
class XBMC(object):
|
||||
"""
|
||||
XBMC notifications
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -203,9 +212,9 @@ class XBMC:
|
||||
url = host + '/jsonrpc'
|
||||
|
||||
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:
|
||||
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:
|
||||
return response[0]['result']
|
||||
@@ -248,20 +257,20 @@ class XBMC:
|
||||
if not request:
|
||||
raise Exception
|
||||
|
||||
except:
|
||||
logger.warn('Error sending notification request to XBMC')
|
||||
except Exception:
|
||||
logger.error('Error sending notification request to XBMC')
|
||||
|
||||
class LMS:
|
||||
|
||||
#Class for updating a Logitech Media Server
|
||||
class LMS(object):
|
||||
"""
|
||||
Class for updating a Logitech Media Server
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.hosts = headphones.LMS_HOST
|
||||
|
||||
def _sendjson(self, host):
|
||||
data = {'id': 1, 'method': 'slim.request', 'params': ["",["rescan"]]}
|
||||
data = simplejson.JSONEncoder().encode(data)
|
||||
data = json.JSONEncoder().encode(data)
|
||||
|
||||
content = {'Content-Type': 'application/json'}
|
||||
|
||||
@@ -273,7 +282,7 @@ class LMS:
|
||||
logger.warn('Error opening LMS url: %s' % e)
|
||||
return
|
||||
|
||||
response = simplejson.JSONDecoder().decode(handle.read())
|
||||
response = json.JSONDecoder().decode(handle.read())
|
||||
|
||||
try:
|
||||
return response['result']
|
||||
@@ -292,8 +301,7 @@ class LMS:
|
||||
if not request:
|
||||
logger.warn('Error sending rescan request to LMS')
|
||||
|
||||
class Plex:
|
||||
|
||||
class Plex(object):
|
||||
def __init__(self):
|
||||
|
||||
self.server_hosts = headphones.PLEX_SERVER_HOST
|
||||
@@ -379,7 +387,7 @@ class Plex:
|
||||
except:
|
||||
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
|
||||
@@ -415,7 +423,7 @@ class NMA:
|
||||
else:
|
||||
return True
|
||||
|
||||
class PUSHBULLET:
|
||||
class PUSHBULLET(object):
|
||||
|
||||
def __init__(self):
|
||||
self.apikey = headphones.PUSHBULLET_APIKEY
|
||||
@@ -468,7 +476,7 @@ class PUSHBULLET:
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
class PUSHALOT:
|
||||
class PUSHALOT(object):
|
||||
|
||||
def notify(self, message, event):
|
||||
if not headphones.PUSHALOT_ENABLED:
|
||||
@@ -507,7 +515,7 @@ class PUSHALOT:
|
||||
logger.info(u"Pushalot notification failed.")
|
||||
return False
|
||||
|
||||
class Synoindex:
|
||||
class Synoindex(object):
|
||||
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
|
||||
self.util_loc = util_loc
|
||||
|
||||
@@ -543,19 +551,17 @@ class Synoindex:
|
||||
for path in path_list:
|
||||
self.notify(path)
|
||||
|
||||
class PUSHOVER:
|
||||
|
||||
application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||
keys = []
|
||||
priority = []
|
||||
class PUSHOVER(object):
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = headphones.PUSHOVER_ENABLED
|
||||
self.keys = headphones.PUSHOVER_KEYS
|
||||
self.priority = headphones.PUSHOVER_PRIORITY
|
||||
|
||||
if headphones.PUSHOVER_APITOKEN:
|
||||
self.application_token = headphones.PUSHOVER_APITOKEN
|
||||
pass
|
||||
else:
|
||||
self.application_token = "LdPCoy0dqC21ktsbEyAVCcwvQiVlsz"
|
||||
|
||||
def conf(self, options):
|
||||
return cherrypy.config['config'].get('Pushover', options)
|
||||
@@ -597,23 +603,23 @@ class PUSHOVER:
|
||||
return
|
||||
|
||||
def test(self, keys, priority):
|
||||
|
||||
self.enabled = True
|
||||
self.keys = keys
|
||||
self.priority = priority
|
||||
|
||||
self.notify('Main Screen Activate', 'Test Message')
|
||||
|
||||
class TwitterNotifier:
|
||||
|
||||
consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||
consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
||||
class TwitterNotifier(object):
|
||||
|
||||
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
|
||||
ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token'
|
||||
AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize'
|
||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||
|
||||
def __init__(self):
|
||||
self.consumer_key = "oYKnp2ddX5gbARjqX8ZAAg"
|
||||
self.consumer_secret = "A4Xkw9i5SjHbTk7XT8zzOPqivhj9MmRDR9Qn95YA9sk"
|
||||
|
||||
def notify_snatch(self, title):
|
||||
if headphones.TWITTER_ONSNATCH:
|
||||
self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+title+' at '+helpers.now())
|
||||
@@ -707,11 +713,7 @@ class TwitterNotifier:
|
||||
|
||||
return self._send_tweet(prefix+": "+message)
|
||||
|
||||
notifier = TwitterNotifier
|
||||
|
||||
class OSX_NOTIFY:
|
||||
|
||||
objc = None
|
||||
class OSX_NOTIFY(object):
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
@@ -766,14 +768,12 @@ class OSX_NOTIFY:
|
||||
def swizzled_bundleIdentifier(self, original, swizzled):
|
||||
return 'ade.headphones.osxnotify'
|
||||
|
||||
class BOXCAR:
|
||||
class BOXCAR(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.url = 'https://new.boxcar.io/api/notifications'
|
||||
|
||||
def notify(self, title, message, rgid=None):
|
||||
|
||||
try:
|
||||
if rgid:
|
||||
message += '<br></br><a href="http://musicbrainz.org/release-group/%s">MusicBrainz</a>' % rgid
|
||||
@@ -790,6 +790,25 @@ class BOXCAR:
|
||||
handle.close()
|
||||
return True
|
||||
|
||||
except urllib2.URLError, e:
|
||||
except urllib2.URLError as e:
|
||||
logger.warn('Error sending Boxcar2 Notification: %s' % e)
|
||||
return False
|
||||
|
||||
class SubSonicNotifier(object):
|
||||
|
||||
def __init__(self):
|
||||
self.host = headphones.SUBSONIC_HOST
|
||||
self.username = headphones.SUBSONIC_USERNAME
|
||||
self.password = headphones.SUBSONIC_PASSWORD
|
||||
|
||||
def notify(self, albumpaths):
|
||||
# Correct URL
|
||||
if not self.host.lower().startswith("http"):
|
||||
self.host = "http://" + self.host
|
||||
|
||||
if not self.host.lower().endswith("/"):
|
||||
self.host = self.host + "/"
|
||||
|
||||
# Invoke request
|
||||
request.request_response(self.host + "musicFolderSettings.view?scanNow",
|
||||
auth=(self.username, self.password))
|
||||
@@ -35,12 +35,20 @@ from headphones import logger
|
||||
def sendNZB(nzb):
|
||||
|
||||
addToTop = False
|
||||
nzbgetXMLrpc = "http://%(username)s:%(password)s@%(host)s/xmlrpc"
|
||||
nzbgetXMLrpc = "%(username)s:%(password)s@%(host)s/xmlrpc"
|
||||
|
||||
if headphones.NZBGET_HOST == None:
|
||||
logger.error(u"No NZBget host found in configuration. Please configure it.")
|
||||
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}
|
||||
|
||||
nzbGetRPC = xmlrpclib.ServerProxy(url)
|
||||
@@ -61,25 +69,60 @@ def sendNZB(nzb):
|
||||
logger.error(u"Protocol Error: " + e.errmsg)
|
||||
return False
|
||||
|
||||
# if it's a normal result need to download the NZB content
|
||||
if nzb.resultType == "nzb":
|
||||
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":
|
||||
nzbcontent64 = None
|
||||
if nzb.resultType == "nzbdata":
|
||||
data = nzb.extraInfo[0]
|
||||
|
||||
nzbcontent64 = standard_b64encode(data)
|
||||
nzbcontent64 = standard_b64encode(data)
|
||||
|
||||
logger.info(u"Sending NZB to NZBget")
|
||||
logger.debug(u"URL: " + url)
|
||||
|
||||
if nzbGetRPC.append(nzb.name + ".nzb", headphones.NZBGET_CATEGORY, addToTop, nzbcontent64):
|
||||
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"))
|
||||
dupekey = ""
|
||||
dupescore = 0
|
||||
|
||||
try:
|
||||
# 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
|
||||
|
||||
@@ -24,7 +24,7 @@ import headphones
|
||||
from beets import autotag
|
||||
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 logger, helpers, request, mb, music_encoder
|
||||
|
||||
@@ -62,31 +62,42 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
tracks = myDB.select('SELECT * from tracks WHERE AlbumID=?', [albumid])
|
||||
|
||||
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
|
||||
|
||||
# Fetch album information from MusicBrainz
|
||||
try:
|
||||
release_list = mb.getReleaseGroup(albumid)
|
||||
except Exception, e:
|
||||
logger.error('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
|
||||
|
||||
if not release_list:
|
||||
logger.error('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
|
||||
|
||||
# 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']
|
||||
|
||||
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)
|
||||
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'])
|
||||
|
||||
if release_dict['artist_name'].startswith('The '):
|
||||
@@ -94,7 +105,6 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
else:
|
||||
sortname = release_dict['artist_name']
|
||||
|
||||
|
||||
controlValueDict = {"ArtistID": release_dict['artist_id']}
|
||||
newValueDict = {"ArtistName": release_dict['artist_name'],
|
||||
"ArtistSortName": sortname,
|
||||
@@ -113,6 +123,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
controlValueDict = {"AlbumID": albumid}
|
||||
|
||||
newValueDict = {"ArtistID": release_dict['artist_id'],
|
||||
"ReleaseID": albumid,
|
||||
"ArtistName": release_dict['artist_name'],
|
||||
"AlbumTitle": release_dict['title'],
|
||||
"AlbumASIN": release_dict['asin'],
|
||||
@@ -161,7 +172,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
elif files.lower().endswith('.cue'):
|
||||
downloaded_cuecount += 1
|
||||
# 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")
|
||||
return
|
||||
|
||||
@@ -304,7 +315,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
|
||||
return
|
||||
|
||||
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)
|
||||
if not processed:
|
||||
renameUnprocessedFolder(albumpath)
|
||||
@@ -388,6 +399,9 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
if headphones.CLEANUP_FILES:
|
||||
cleanupFiles(albumpath)
|
||||
|
||||
if headphones.KEEP_NFO:
|
||||
renameNFO(albumpath)
|
||||
|
||||
if headphones.ADD_ALBUM_ART and artwork:
|
||||
addAlbumArt(artwork, albumpath, release)
|
||||
|
||||
@@ -412,7 +426,25 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
|
||||
myDB = db.DBConnection()
|
||||
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:
|
||||
for albumpath in albumpaths:
|
||||
@@ -420,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']))
|
||||
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
statusmessage = "Download and Postprocessing completed"
|
||||
|
||||
if headphones.GROWL_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Growl request")
|
||||
growl = notifiers.GROWL()
|
||||
growl.notify(pushmessage,"Download and Postprocessing completed")
|
||||
growl.notify(pushmessage, statusmessage)
|
||||
|
||||
if headphones.PROWL_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Prowl request")
|
||||
prowl = notifiers.PROWL()
|
||||
prowl.notify(pushmessage,"Download and Postprocessing completed")
|
||||
prowl.notify(pushmessage, statusmessage)
|
||||
|
||||
if headphones.XBMC_ENABLED:
|
||||
xbmc = notifiers.XBMC()
|
||||
if headphones.XBMC_UPDATE:
|
||||
xbmc.update()
|
||||
if headphones.XBMC_NOTIFY:
|
||||
xbmc.notify(release['ArtistName'], release['AlbumTitle'], album_art_path)
|
||||
xbmc.notify(release['ArtistName'],
|
||||
release['AlbumTitle'],
|
||||
album_art_path)
|
||||
|
||||
if headphones.LMS_ENABLED:
|
||||
lms = notifiers.LMS()
|
||||
@@ -448,17 +483,18 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
if headphones.PLEX_UPDATE:
|
||||
plex.update()
|
||||
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:
|
||||
nma = notifiers.NMA()
|
||||
nma.notify(release['ArtistName'], release['AlbumTitle'])
|
||||
|
||||
if headphones.PUSHALOT_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Pushalot request")
|
||||
pushalot = notifiers.PUSHALOT()
|
||||
pushalot.notify(pushmessage,"Download and Postprocessing completed")
|
||||
pushalot.notify(pushmessage, statusmessage)
|
||||
|
||||
if headphones.SYNOINDEX_ENABLED:
|
||||
syno = notifiers.Synoindex()
|
||||
@@ -466,19 +502,16 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
syno.notify(albumpath)
|
||||
|
||||
if headphones.PUSHOVER_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Pushover request")
|
||||
pushover = notifiers.PUSHOVER()
|
||||
pushover.notify(pushmessage,"Headphones")
|
||||
pushover.notify(pushmessage, "Headphones")
|
||||
|
||||
if headphones.PUSHBULLET_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"PushBullet request")
|
||||
pushbullet = notifiers.PUSHBULLET()
|
||||
pushbullet.notify(pushmessage, "Download and Postprocessing completed")
|
||||
|
||||
if headphones.TWITTER_ENABLED:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Sending Twitter notification")
|
||||
twitter = notifiers.TwitterNotifier()
|
||||
twitter.notify_download(pushmessage)
|
||||
@@ -486,13 +519,20 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
|
||||
if headphones.OSX_NOTIFY_ENABLED:
|
||||
logger.info(u"Sending OS X notification")
|
||||
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:
|
||||
pushmessage = release['ArtistName'] + ' - ' + release['AlbumTitle']
|
||||
logger.info(u"Sending Boxcar2 notification")
|
||||
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()
|
||||
@@ -514,7 +554,7 @@ def embedAlbumArt(artwork, downloaded_track_list):
|
||||
f.art = artwork
|
||||
f.save()
|
||||
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
|
||||
|
||||
def addAlbumArt(artwork, albumpath, release):
|
||||
@@ -541,29 +581,41 @@ def addAlbumArt(artwork, albumpath, release):
|
||||
album_art_name = album_art_name.replace(' ', '_')
|
||||
|
||||
if album_art_name.startswith('.'):
|
||||
album_art_name = album_art_name.replace(0, '_')
|
||||
album_art_name = album_art_name.replace(".", "_", 1)
|
||||
|
||||
try:
|
||||
file = open(os.path.join(albumpath, album_art_name), 'wb')
|
||||
file.write(artwork)
|
||||
file.close()
|
||||
except Exception, e:
|
||||
logger.error('Error saving album art: %s' % str(e))
|
||||
with open(os.path.join(albumpath, album_art_name), 'wb') as f:
|
||||
f.write(artwork)
|
||||
except IOError as e:
|
||||
logger.error('Error saving album art: %s', e)
|
||||
return
|
||||
|
||||
def cleanupFiles(albumpath):
|
||||
logger.info('Cleaning up files')
|
||||
|
||||
for r,d,f in os.walk(albumpath):
|
||||
for files in f:
|
||||
if not any(files.lower().endswith('.' + x.lower()) for x in headphones.MEDIA_FORMATS):
|
||||
logger.debug('Removing: %s' % files)
|
||||
try:
|
||||
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))
|
||||
|
||||
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:
|
||||
year = release['ReleaseDate'][:4]
|
||||
except TypeError:
|
||||
@@ -587,22 +639,29 @@ def moveFiles(albumpath, release, tracks):
|
||||
else:
|
||||
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,
|
||||
'$SortArtist': sortname,
|
||||
'$Album': album,
|
||||
'$Year': year,
|
||||
'$Type': releasetype,
|
||||
'$OriginalFolder': origfolder,
|
||||
'$First': firstchar.upper(),
|
||||
'$artist': artist.lower(),
|
||||
'$sortartist': sortname.lower(),
|
||||
'$album': album.lower(),
|
||||
'$year': year,
|
||||
'$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 = folder.replace('./', '_/').replace('/.','/_')
|
||||
@@ -847,11 +906,11 @@ def embedLyrics(downloaded_track_list):
|
||||
# TODO: If adding lyrics for flac & lossy, only fetch the lyrics once
|
||||
# and apply it to both files
|
||||
for downloaded_track in downloaded_track_list:
|
||||
|
||||
track_title = downloaded_track.decode(headphones.SYS_ENCODING, 'replace')
|
||||
try:
|
||||
f = MediaFile(downloaded_track)
|
||||
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
|
||||
|
||||
if f.albumartist and f.title:
|
||||
@@ -859,17 +918,20 @@ def embedLyrics(downloaded_track_list):
|
||||
elif f.artist and f.title:
|
||||
metalyrics = lyrics.getLyrics(f.artist, f.title)
|
||||
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
|
||||
|
||||
if lyrics:
|
||||
logger.debug('Adding lyrics to: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'))
|
||||
if metalyrics:
|
||||
logger.debug('Adding lyrics to: %s', track_title)
|
||||
f.lyrics = metalyrics
|
||||
try:
|
||||
f.save()
|
||||
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
|
||||
else:
|
||||
logger.debug('No lyrics found for track: %s', track_title)
|
||||
|
||||
|
||||
def renameFiles(albumpath, downloaded_track_list, release):
|
||||
logger.info('Renaming files')
|
||||
@@ -944,7 +1006,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
|
||||
new_file_name = new_file_name.replace(' ', '_')
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -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 xml.dom import minidom
|
||||
@@ -7,7 +22,8 @@ import requests
|
||||
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
|
||||
log them. On success, the Response object is returned. In case of a
|
||||
@@ -38,30 +54,38 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
|
||||
try:
|
||||
response.raise_for_status()
|
||||
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
|
||||
elif auto_raise:
|
||||
response.raise_for_status()
|
||||
|
||||
return response
|
||||
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:
|
||||
logger.error("Request timed out.")
|
||||
except requests.HTTPError, e:
|
||||
logger.error("Request timed out. The remote host did not respeond " \
|
||||
"timely.")
|
||||
except requests.HTTPError as e:
|
||||
if e.response is not None:
|
||||
if e.response.status_code >= 500:
|
||||
cause = "remote server error"
|
||||
elif e.response.status_code >= 400:
|
||||
cause = "local request error"
|
||||
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)
|
||||
logger.error("Request raise HTTP error with status code %d (%s).",
|
||||
e.response.status_code, cause)
|
||||
|
||||
# Debug response
|
||||
if headphones.VERBOSE:
|
||||
server_message(e.response)
|
||||
else:
|
||||
logger.error("Request raised HTTP error.")
|
||||
except requests.RequestException, e:
|
||||
except requests.RequestException as e:
|
||||
logger.error("Request raised exception: %s", e)
|
||||
|
||||
def request_soup(url, **kwargs):
|
||||
@@ -104,12 +128,16 @@ def request_json(url, **kwargs):
|
||||
result = response.json()
|
||||
|
||||
if validator and not validator(result):
|
||||
logger.error("JSON validation result vailed")
|
||||
logger.error("JSON validation result failed")
|
||||
else:
|
||||
return result
|
||||
except ValueError:
|
||||
logger.error("Response returned invalid JSON data")
|
||||
|
||||
# Debug response
|
||||
if headphones.VERBOSE:
|
||||
server_message(response)
|
||||
|
||||
def request_content(url, **kwargs):
|
||||
"""
|
||||
Wrapper for `request_response', which will return the raw content.
|
||||
@@ -129,3 +157,45 @@ def request_feed(url, **kwargs):
|
||||
|
||||
if response is not None:
|
||||
return feedparser.parse(response.content)
|
||||
|
||||
def server_message(response):
|
||||
"""
|
||||
Extract server message from response and log in to logger with DEBUG level.
|
||||
|
||||
Some servers return extra information in the result. Try to parse it for
|
||||
debugging purpose. Messages are limited to 150 characters, since it may
|
||||
return the whole page in case of normal web page URLs
|
||||
"""
|
||||
|
||||
message = None
|
||||
|
||||
# First attempt is to 'read' the response as HTML
|
||||
if "text/html" in response.headers.get("content-type"):
|
||||
try:
|
||||
soup = BeautifulSoup(response.content, "html5lib")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Find body and cleanup common tags to grab content, which probably
|
||||
# contains the message.
|
||||
message = soup.find("body")
|
||||
|
||||
for element in ["header", "script", "footer", "nav", "input",
|
||||
"textarea"]:
|
||||
|
||||
for tag in soup.find_all(element):
|
||||
tag.replaceWith("")
|
||||
|
||||
message = message.text if message else soup.text
|
||||
message = message.strip()
|
||||
|
||||
# Second attempt is to just take the response
|
||||
if message is None:
|
||||
message = response.content.strip()
|
||||
|
||||
if message:
|
||||
# Truncate message if it is too long.
|
||||
if len(message) > 150:
|
||||
message = message[:150] + "..."
|
||||
|
||||
logger.debug("Server responded with message: %s", message)
|
||||
@@ -17,13 +17,13 @@
|
||||
## Stolen from Sick-Beard's sab.py ##
|
||||
#####################################
|
||||
|
||||
import urllib, httplib
|
||||
import datetime
|
||||
|
||||
import MultipartPostHandler
|
||||
import headphones
|
||||
|
||||
from lib import MultipartPostHandler
|
||||
import urllib2, cookielib
|
||||
import datetime
|
||||
import cookielib
|
||||
import urllib2
|
||||
import httplib
|
||||
import urllib
|
||||
import ast
|
||||
|
||||
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
|
||||
nzbdata = helpers.latinToAscii(nzb.extraInfo[0])
|
||||
params['mode'] = 'addfile'
|
||||
multiPartParams = {"nzbfile": (nzb.name+".nzb", nzbdata)}
|
||||
multiPartParams = {"nzbfile": (helpers.latinToAscii(nzb.name)+".nzb", nzbdata)}
|
||||
|
||||
if not headphones.SAB_HOST.startswith('http'):
|
||||
headphones.SAB_HOST = 'http://' + headphones.SAB_HOST
|
||||
|
||||
@@ -4,16 +4,21 @@
|
||||
# Headphones rutracker.org search
|
||||
# Functions called from searcher.py
|
||||
|
||||
import urllib
|
||||
import urllib2
|
||||
import cookielib
|
||||
from headphones import logger, db, utorrent
|
||||
|
||||
from bencode import bencode as bencode, bdecode
|
||||
from urlparse import urlparse
|
||||
from bs4 import BeautifulSoup
|
||||
import headphones
|
||||
from headphones import logger, db
|
||||
import lib.bencode as bencode
|
||||
import os
|
||||
from tempfile import mkdtemp
|
||||
from hashlib import sha1
|
||||
|
||||
import headphones
|
||||
import requests
|
||||
import cookielib
|
||||
import urllib2
|
||||
import urllib
|
||||
import re
|
||||
import os
|
||||
|
||||
class Rutracker():
|
||||
|
||||
@@ -48,7 +53,7 @@ class Rutracker():
|
||||
|
||||
try:
|
||||
self.opener.open("http://login.rutracker.org/forum/login.php", params)
|
||||
except :
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Check if we're logged in
|
||||
@@ -88,7 +93,7 @@ class Rutracker():
|
||||
|
||||
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
|
||||
"""
|
||||
@@ -189,7 +194,7 @@ class Rutracker():
|
||||
page = self.opener.open(url)
|
||||
torrent = page.read()
|
||||
if torrent:
|
||||
decoded = bencode.bdecode(torrent)
|
||||
decoded = bdecode(torrent)
|
||||
metainfo = decoded['info']
|
||||
page.close ()
|
||||
except Exception, e:
|
||||
@@ -275,17 +280,70 @@ class Rutracker():
|
||||
prev = os.umask(headphones.UMASK)
|
||||
page = self.opener.open(downloadurl)
|
||||
torrent = page.read()
|
||||
decoded = bdecode(torrent)
|
||||
metainfo = decoded['info']
|
||||
tor_hash = sha1(bencode(metainfo)).hexdigest()
|
||||
if savelocation:
|
||||
download_path = os.path.join(savelocation, torrent_name)
|
||||
else:
|
||||
tempdir = mkdtemp(suffix='_rutracker_torrents')
|
||||
download_path = os.path.join(tempdir, torrent_name)
|
||||
fp = open (download_path, 'wb')
|
||||
fp.write (torrent)
|
||||
fp.close ()
|
||||
|
||||
with open(download_path, 'wb') as f:
|
||||
f.write(torrent)
|
||||
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 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
|
||||
# along with Headphones. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from headphones import logger, notifiers, request
|
||||
|
||||
import re
|
||||
import time
|
||||
import json
|
||||
import base64
|
||||
import headphones
|
||||
|
||||
import simplejson as json
|
||||
|
||||
from headphones import logger, notifiers, request
|
||||
|
||||
# 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
|
||||
# of the download, set the download dir, etc.
|
||||
@@ -32,9 +31,8 @@ def addTorrent(link):
|
||||
method = 'torrent-add'
|
||||
|
||||
if link.endswith('.torrent'):
|
||||
f = open(link,'rb')
|
||||
metainfo = str(base64.b64encode(f.read()))
|
||||
f.close()
|
||||
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}
|
||||
@@ -47,10 +45,10 @@ def addTorrent(link):
|
||||
if response['result'] == 'success':
|
||||
if 'torrent-added' in response['arguments']:
|
||||
name = response['arguments']['torrent-added']['name']
|
||||
retid = response['arguments']['torrent-added']['id']
|
||||
retid = response['arguments']['torrent-added']['hashString']
|
||||
elif 'torrent-duplicate' in response['arguments']:
|
||||
name = response['arguments']['torrent-duplicate']['name']
|
||||
retid = response['arguments']['torrent-duplicate']['id']
|
||||
retid = response['arguments']['torrent-duplicate']['hashString']
|
||||
else:
|
||||
name = link
|
||||
retid = False
|
||||
@@ -82,6 +80,46 @@ def getTorrentFolder(torrentid):
|
||||
|
||||
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):
|
||||
|
||||
host = headphones.TRANSMISSION_HOST
|
||||
|
||||
@@ -19,9 +19,12 @@ 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,):
|
||||
|
||||
@@ -119,6 +122,23 @@ class utorrentclient(object):
|
||||
|
||||
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)
|
||||
@@ -136,31 +156,101 @@ class utorrentclient(object):
|
||||
logger.debug('URL: ' + str(url))
|
||||
logger.debug('uTorrent webUI raised the following error: ' + str(err))
|
||||
|
||||
|
||||
def labelTorrent(hash):
|
||||
label = headphones.UTORRENT_LABEL
|
||||
uTorrentClient = utorrentclient()
|
||||
settinglabel = True
|
||||
while settinglabel:
|
||||
torrentList = uTorrentClient.list()
|
||||
for torrent in torrentList[1].get('torrents'):
|
||||
if (torrent[0].lower() == hash):
|
||||
uTorrentClient.setprops(hash,'label',label)
|
||||
settinglabel = False
|
||||
return True
|
||||
if label:
|
||||
uTorrentClient.setprops(hash,'label',label)
|
||||
|
||||
|
||||
def dirTorrent(hash):
|
||||
def removeTorrent(hash, remove_data = False):
|
||||
uTorrentClient = utorrentclient()
|
||||
torrentList = uTorrentClient.list()
|
||||
for torrent in torrentList[1].get('torrents'):
|
||||
if (torrent[0].lower() == hash):
|
||||
return torrent[26]
|
||||
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)
|
||||
labelTorrent(hash)
|
||||
return dirTorrent(hash)
|
||||
|
||||
# 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
|
||||
# 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
|
||||
from headphones import logger, version, request
|
||||
from headphones.exceptions import ex
|
||||
import subprocess
|
||||
|
||||
import lib.simplejson as simplejson
|
||||
from headphones import logger, version, request
|
||||
|
||||
def runGit(args):
|
||||
|
||||
@@ -61,7 +62,6 @@ def runGit(args):
|
||||
def getVersion():
|
||||
|
||||
if version.HEADPHONES_VERSION.startswith('win32build'):
|
||||
|
||||
headphones.INSTALL_TYPE = 'win'
|
||||
|
||||
# Don't have a way to update exe yet, but don't want to set VERSION to None
|
||||
@@ -107,9 +107,8 @@ def getVersion():
|
||||
if not os.path.isfile(version_file):
|
||||
return None, 'master'
|
||||
|
||||
fp = open(version_file, 'r')
|
||||
current_version = fp.read().strip(' \n\r')
|
||||
fp.close()
|
||||
with open(version_file, 'r') as f:
|
||||
current_version = f.read().strip(' \n\r')
|
||||
|
||||
if current_version:
|
||||
return current_version, headphones.GIT_BRANCH
|
||||
@@ -197,9 +196,8 @@ def update():
|
||||
tar_download_path = os.path.join(headphones.PROG_DIR, download_name)
|
||||
|
||||
# Save tar to disk
|
||||
f = open(tar_download_path, 'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
with open(tar_download_path, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
# Extract the tar to update folder
|
||||
logger.info('Extracting file: ' + tar_download_path)
|
||||
@@ -231,9 +229,9 @@ def update():
|
||||
|
||||
# Update version.txt
|
||||
try:
|
||||
ver_file = open(version_path, 'w')
|
||||
ver_file.write(str(headphones.LATEST_VERSION))
|
||||
ver_file.close()
|
||||
except IOError, e:
|
||||
logger.error("Unable to write current version to version.txt, update not complete: "+ex(e))
|
||||
with open(version_path, 'w') as f:
|
||||
f.write(str(headphones.LATEST_VERSION))
|
||||
except IOError as e:
|
||||
logger.error("Unable to write current version to version.txt, " \
|
||||
"update not complete: ", e)
|
||||
return
|
||||
|
||||
@@ -15,29 +15,29 @@
|
||||
|
||||
# NZBGet support added by CurlyMo <curlymoo1@gmail.com> as a part of XBian - XBMC on the Raspberry Pi
|
||||
|
||||
import os
|
||||
import cherrypy
|
||||
from headphones import logger, searcher, db, importer, mb, lastfm, librarysync, helpers, notifiers
|
||||
from headphones.helpers import checked, radio,today, cleanName
|
||||
|
||||
from mako.template import Template
|
||||
from mako.lookup import TemplateLookup
|
||||
from mako import exceptions
|
||||
|
||||
import time
|
||||
import threading
|
||||
import string
|
||||
import json
|
||||
from operator import itemgetter
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import string
|
||||
import cherrypy
|
||||
import threading
|
||||
import headphones
|
||||
|
||||
from headphones import logger, searcher, db, importer, mb, lastfm, librarysync, helpers, notifiers
|
||||
from headphones.helpers import checked, radio,today, cleanName
|
||||
|
||||
import lib.simplejson as simplejson
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
# Python 2.6.x fallback, from libs
|
||||
import OrderedDict
|
||||
|
||||
def serve_template(templatename, **kwargs):
|
||||
|
||||
@@ -84,18 +84,18 @@ class WebInterface(object):
|
||||
if not artist:
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
|
||||
# Serve the extras up as a dict to make things easier for new templates
|
||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other"]
|
||||
extras_dict = {}
|
||||
|
||||
if not artist['Extras']:
|
||||
artist_extras = ""
|
||||
# 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", "djmix", "mixtape_street", "broadcast", "interview", "demo"]
|
||||
if artist['Extras']:
|
||||
artist_extras = map(int, artist['Extras'].split(','))
|
||||
else:
|
||||
artist_extras = artist['Extras']
|
||||
artist_extras = []
|
||||
|
||||
extras_dict = OrderedDict()
|
||||
|
||||
i = 1
|
||||
for extra in extras_list:
|
||||
if str(i) in artist_extras:
|
||||
if i in artist_extras:
|
||||
extras_dict[extra] = "checked"
|
||||
else:
|
||||
extras_dict[extra] = ""
|
||||
@@ -110,6 +110,19 @@ class WebInterface(object):
|
||||
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])
|
||||
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']:
|
||||
title = ' - '
|
||||
else:
|
||||
@@ -143,12 +156,12 @@ class WebInterface(object):
|
||||
#
|
||||
# If they are, we need to convert kwargs to string format
|
||||
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:
|
||||
temp_extras_list = []
|
||||
# TODO: Put these extras as a global variable
|
||||
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:
|
||||
temp_extras_list.append(i)
|
||||
i += 1
|
||||
@@ -286,8 +299,8 @@ class WebInterface(object):
|
||||
if action == "ignore":
|
||||
myDB = db.DBConnection()
|
||||
for artist in args:
|
||||
myDB.action('DELETE FROM newartists WHERE ArtistName=?', [artist])
|
||||
myDB.action('UPDATE have SET Matched="Ignored" 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.decode(headphones.SYS_ENCODING, 'replace')])
|
||||
logger.info("Artist %s removed from new artist list and set to ignored" % artist)
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
addArtists.exposed = True
|
||||
@@ -325,7 +338,7 @@ class WebInterface(object):
|
||||
}
|
||||
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'
|
||||
return s
|
||||
|
||||
@@ -365,6 +378,12 @@ class WebInterface(object):
|
||||
def deleteAlbum(self, AlbumID, ArtistID=None):
|
||||
logger.info(u"Deleting all traces of album: " + AlbumID)
|
||||
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 tracks WHERE AlbumID=?', [AlbumID])
|
||||
myDB.action('DELETE from allalbums WHERE AlbumID=?', [AlbumID])
|
||||
@@ -447,8 +466,9 @@ class WebInterface(object):
|
||||
have_album_dictionary.append(have_dict)
|
||||
headphones_albums = myDB.select('SELECT ArtistName, AlbumTitle from albums ORDER BY ArtistName')
|
||||
for albums in headphones_albums:
|
||||
headphones_dict = { 'ArtistName' : albums['ArtistName'], 'AlbumTitle' : albums['AlbumTitle'] }
|
||||
headphones_album_dictionary.append(headphones_dict)
|
||||
if albums['ArtistName'] and albums['AlbumTitle']:
|
||||
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]]
|
||||
|
||||
check = set([(cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) for d in headphones_album_dictionary])
|
||||
@@ -722,7 +742,7 @@ class WebInterface(object):
|
||||
|
||||
def history(self):
|
||||
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)
|
||||
history.exposed = True
|
||||
|
||||
@@ -765,12 +785,11 @@ class WebInterface(object):
|
||||
rows = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
|
||||
rows = [[row[0],row[2],row[1]] for row in rows]
|
||||
|
||||
dict = {'iTotalDisplayRecords':len(filtered),
|
||||
'iTotalRecords':len(headphones.LOG_LIST),
|
||||
'aaData':rows,
|
||||
}
|
||||
s = simplejson.dumps(dict)
|
||||
return s
|
||||
return json.dumps({
|
||||
'iTotalDisplayRecords':len(filtered),
|
||||
'iTotalRecords':len(headphones.LOG_LIST),
|
||||
'aaData':rows,
|
||||
})
|
||||
getLog.exposed = True
|
||||
|
||||
def getArtists_json(self,iDisplayStart=0,iDisplayLength=100,sSearch="",iSortCol_0='0',sSortDir_0='asc',**kwargs):
|
||||
@@ -843,7 +862,7 @@ class WebInterface(object):
|
||||
'iTotalRecords':totalcount,
|
||||
'aaData':rows,
|
||||
}
|
||||
s = simplejson.dumps(dict)
|
||||
s = json.dumps(dict)
|
||||
cherrypy.response.headers['Content-type'] = 'application/json'
|
||||
return s
|
||||
getArtists_json.exposed=True
|
||||
@@ -872,18 +891,29 @@ class WebInterface(object):
|
||||
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):
|
||||
myDB = db.DBConnection()
|
||||
if type:
|
||||
if type == 'all':
|
||||
logger.info(u"Clearing all history")
|
||||
myDB.action('DELETE from snatched')
|
||||
myDB.action('DELETE from snatched WHERE Status NOT LIKE "Seed%"')
|
||||
else:
|
||||
logger.info(u"Clearing history where status is %s" % type)
|
||||
myDB.action('DELETE from snatched WHERE Status=?', [type])
|
||||
else:
|
||||
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")
|
||||
clearhistory.exposed = True
|
||||
|
||||
@@ -936,7 +966,7 @@ class WebInterface(object):
|
||||
"download_scan_interval" : headphones.DOWNLOAD_SCAN_INTERVAL,
|
||||
"update_db_interval" : headphones.UPDATE_DB_INTERVAL,
|
||||
"mb_ignore_age" : headphones.MB_IGNORE_AGE,
|
||||
"nzb_search_interval" : headphones.SEARCH_INTERVAL,
|
||||
"search_interval" : headphones.SEARCH_INTERVAL,
|
||||
"libraryscan_interval" : headphones.LIBRARYSCAN_INTERVAL,
|
||||
"sab_host" : headphones.SAB_HOST,
|
||||
"sab_user" : headphones.SAB_USERNAME,
|
||||
@@ -947,6 +977,7 @@ class WebInterface(object):
|
||||
"nzbget_user" : headphones.NZBGET_USERNAME,
|
||||
"nzbget_pass" : headphones.NZBGET_PASSWORD,
|
||||
"nzbget_cat" : headphones.NZBGET_CATEGORY,
|
||||
"nzbget_priority" : headphones.NZBGET_PRIORITY,
|
||||
"transmission_host" : headphones.TRANSMISSION_HOST,
|
||||
"transmission_user" : headphones.TRANSMISSION_USERNAME,
|
||||
"transmission_pass" : headphones.TRANSMISSION_PASSWORD,
|
||||
@@ -973,9 +1004,6 @@ class WebInterface(object):
|
||||
"use_nzbsorg" : checked(headphones.NZBSORG),
|
||||
"nzbsorg_uid" : headphones.NZBSORG_UID,
|
||||
"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),
|
||||
"omgwtfnzbs_uid" : headphones.OMGWTFNZBS_UID,
|
||||
"omgwtfnzbs_apikey" : headphones.OMGWTFNZBS_APIKEY,
|
||||
@@ -985,20 +1013,26 @@ class WebInterface(object):
|
||||
"torrentblackhole_dir" : headphones.TORRENTBLACKHOLE_DIR,
|
||||
"download_torrent_dir" : headphones.DOWNLOAD_TORRENT_DIR,
|
||||
"numberofseeders" : headphones.NUMBEROFSEEDERS,
|
||||
"use_isohunt" : checked(headphones.ISOHUNT),
|
||||
"use_kat" : checked(headphones.KAT),
|
||||
"kat_proxy_url" : headphones.KAT_PROXY_URL,
|
||||
"kat_ratio": headphones.KAT_RATIO,
|
||||
"use_piratebay" : checked(headphones.PIRATEBAY),
|
||||
"piratebay_proxy_url" : headphones.PIRATEBAY_PROXY_URL,
|
||||
"piratebay_ratio": headphones.PIRATEBAY_RATIO,
|
||||
"use_mininova" : checked(headphones.MININOVA),
|
||||
"mininova_ratio": headphones.MININOVA_RATIO,
|
||||
"use_waffles" : checked(headphones.WAFFLES),
|
||||
"waffles_uid" : headphones.WAFFLES_UID,
|
||||
"waffles_passkey": headphones.WAFFLES_PASSKEY,
|
||||
"waffles_ratio": headphones.WAFFLES_RATIO,
|
||||
"use_rutracker" : checked(headphones.RUTRACKER),
|
||||
"rutracker_user" : headphones.RUTRACKER_USER,
|
||||
"rutracker_password": headphones.RUTRACKER_PASSWORD,
|
||||
"rutracker_ratio": headphones.RUTRACKER_RATIO,
|
||||
"use_whatcd" : checked(headphones.WHATCD),
|
||||
"whatcd_username" : headphones.WHATCD_USERNAME,
|
||||
"whatcd_password": headphones.WHATCD_PASSWORD,
|
||||
"whatcd_ratio": headphones.WHATCD_RATIO,
|
||||
"pref_qual_0" : radio(headphones.PREFERRED_QUALITY, 0),
|
||||
"pref_qual_1" : radio(headphones.PREFERRED_QUALITY, 1),
|
||||
"pref_qual_3" : radio(headphones.PREFERRED_QUALITY, 3),
|
||||
@@ -1010,10 +1044,12 @@ class WebInterface(object):
|
||||
"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),
|
||||
"rename_files" : checked(headphones.RENAME_FILES),
|
||||
"correct_metadata" : checked(headphones.CORRECT_METADATA),
|
||||
"cleanup_files" : checked(headphones.CLEANUP_FILES),
|
||||
"keep_nfo" : checked(headphones.KEEP_NFO),
|
||||
"add_album_art" : checked(headphones.ADD_ALBUM_ART),
|
||||
"album_art_format" : headphones.ALBUM_ART_FORMAT,
|
||||
"embed_album_art" : checked(headphones.EMBED_ALBUM_ART),
|
||||
@@ -1089,6 +1125,10 @@ class WebInterface(object):
|
||||
"pushbullet_onsnatch": checked(headphones.PUSHBULLET_ONSNATCH),
|
||||
"pushbullet_apikey": headphones.PUSHBULLET_APIKEY,
|
||||
"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_onsnatch": checked(headphones.TWITTER_ONSNATCH),
|
||||
"osx_notify_enabled": checked(headphones.OSX_NOTIFY_ENABLED),
|
||||
@@ -1114,13 +1154,18 @@ class WebInterface(object):
|
||||
"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
|
||||
extras_list = ["single", "ep", "compilation", "soundtrack", "live", "remix", "spokenword", "audiobook", "other"]
|
||||
extras_dict = {}
|
||||
# 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", "djmix", "mixtape_street", "broadcast", "interview", "demo"]
|
||||
if headphones.EXTRAS:
|
||||
extras = map(int, headphones.EXTRAS.split(','))
|
||||
else:
|
||||
extras = []
|
||||
|
||||
extras_dict = OrderedDict()
|
||||
|
||||
i = 1
|
||||
for extra in extras_list:
|
||||
if str(i) in headphones.EXTRAS:
|
||||
if i in extras:
|
||||
extras_dict[extra] = "checked"
|
||||
else:
|
||||
extras_dict[extra] = ""
|
||||
@@ -1132,16 +1177,16 @@ class WebInterface(object):
|
||||
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,
|
||||
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,
|
||||
sab_category=None, nzbget_host=None, nzbget_username=None, nzbget_password=None, nzbget_category=None, transmission_host=None, transmission_username=None, transmission_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, nzbget_priority=0, transmission_host=None, transmission_username=None, transmission_password=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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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, 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,
|
||||
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, freeze_db=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=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, 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, remix=0, spokenword=0, audiobook=0, other=0, djmix=0, mixtape_street=0, broadcast=0, interview=0, demo=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,
|
||||
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,
|
||||
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,
|
||||
@@ -1163,7 +1208,7 @@ class WebInterface(object):
|
||||
headphones.DOWNLOAD_SCAN_INTERVAL = download_scan_interval
|
||||
headphones.UPDATE_DB_INTERVAL = update_db_interval
|
||||
headphones.MB_IGNORE_AGE = mb_ignore_age
|
||||
headphones.SEARCH_INTERVAL = nzb_search_interval
|
||||
headphones.SEARCH_INTERVAL = search_interval
|
||||
headphones.LIBRARYSCAN_INTERVAL = libraryscan_interval
|
||||
headphones.SAB_HOST = sab_host
|
||||
headphones.SAB_USERNAME = sab_username
|
||||
@@ -1174,6 +1219,7 @@ class WebInterface(object):
|
||||
headphones.NZBGET_USERNAME = nzbget_username
|
||||
headphones.NZBGET_PASSWORD = nzbget_password
|
||||
headphones.NZBGET_CATEGORY = nzbget_category
|
||||
headphones.NZBGET_PRIORITY = int(nzbget_priority)
|
||||
headphones.TRANSMISSION_HOST = transmission_host
|
||||
headphones.TRANSMISSION_USERNAME = transmission_username
|
||||
headphones.TRANSMISSION_PASSWORD = transmission_password
|
||||
@@ -1194,9 +1240,6 @@ class WebInterface(object):
|
||||
headphones.NZBSORG = nzbsorg
|
||||
headphones.NZBSORG_UID = nzbsorg_uid
|
||||
headphones.NZBSORG_HASH = nzbsorg_hash
|
||||
headphones.NZBSRUS = nzbsrus
|
||||
headphones.NZBSRUS_UID = nzbsrus_uid
|
||||
headphones.NZBSRUS_APIKEY = nzbsrus_apikey
|
||||
headphones.OMGWTFNZBS = omgwtfnzbs
|
||||
headphones.OMGWTFNZBS_UID = omgwtfnzbs_uid
|
||||
headphones.OMGWTFNZBS_APIKEY = omgwtfnzbs_apikey
|
||||
@@ -1206,20 +1249,26 @@ class WebInterface(object):
|
||||
headphones.TORRENTBLACKHOLE_DIR = torrentblackhole_dir
|
||||
headphones.NUMBEROFSEEDERS = numberofseeders
|
||||
headphones.DOWNLOAD_TORRENT_DIR = download_torrent_dir
|
||||
headphones.ISOHUNT = use_isohunt
|
||||
headphones.KAT = use_kat
|
||||
headphones.KAT_PROXY_URL = kat_proxy_url
|
||||
headphones.KAT_RATIO = kat_ratio
|
||||
headphones.PIRATEBAY = use_piratebay
|
||||
headphones.PIRATEBAY_PROXY_URL = piratebay_proxy_url
|
||||
headphones.PIRATEBAY_RATIO = piratebay_ratio
|
||||
headphones.MININOVA = use_mininova
|
||||
headphones.MININOVA_RATIO = mininova_ratio
|
||||
headphones.WAFFLES = waffles
|
||||
headphones.WAFFLES_UID = waffles_uid
|
||||
headphones.WAFFLES_PASSKEY = waffles_passkey
|
||||
headphones.WAFFLES_RATIO = waffles_ratio
|
||||
headphones.RUTRACKER = rutracker
|
||||
headphones.RUTRACKER_USER = rutracker_user
|
||||
headphones.RUTRACKER_PASSWORD = rutracker_password
|
||||
headphones.RUTRACKER_RATIO = rutracker_ratio
|
||||
headphones.WHATCD = whatcd
|
||||
headphones.WHATCD_USERNAME = whatcd_username
|
||||
headphones.WHATCD_PASSWORD = whatcd_password
|
||||
headphones.WHATCD_RATIO = whatcd_ratio
|
||||
headphones.PREFERRED_QUALITY = int(preferred_quality)
|
||||
headphones.PREFERRED_BITRATE = preferred_bitrate
|
||||
headphones.PREFERRED_BITRATE_HIGH_BUFFER = preferred_bitrate_high_buffer
|
||||
@@ -1228,10 +1277,12 @@ class WebInterface(object):
|
||||
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.CORRECT_METADATA = correct_metadata
|
||||
headphones.RENAME_FILES = rename_files
|
||||
headphones.CLEANUP_FILES = cleanup_files
|
||||
headphones.KEEP_NFO = keep_nfo
|
||||
headphones.ADD_ALBUM_ART = add_album_art
|
||||
headphones.ALBUM_ART_FORMAT = album_art_format
|
||||
headphones.EMBED_ALBUM_ART = embed_album_art
|
||||
@@ -1305,6 +1356,10 @@ class WebInterface(object):
|
||||
headphones.PUSHBULLET_ONSNATCH = pushbullet_onsnatch
|
||||
headphones.PUSHBULLET_APIKEY = pushbullet_apikey
|
||||
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_APIKEY = songkick_apikey
|
||||
headphones.SONGKICK_LOCATION = songkick_location
|
||||
@@ -1348,9 +1403,9 @@ class WebInterface(object):
|
||||
|
||||
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 = []
|
||||
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
|
||||
for extra in extras_list:
|
||||
@@ -1375,25 +1430,22 @@ class WebInterface(object):
|
||||
|
||||
configUpdate.exposed = True
|
||||
|
||||
def shutdown(self):
|
||||
headphones.SIGNAL = 'shutdown'
|
||||
message = 'Shutting Down...'
|
||||
return serve_template(templatename="shutdown.html", title="Shutting Down", message=message, timer=15)
|
||||
return page
|
||||
def do_state_change(self, signal, title, timer):
|
||||
headphones.SIGNAL = signal
|
||||
message = title + '...'
|
||||
return serve_template(templatename="shutdown.html", title=title,
|
||||
message=message, timer=timer)
|
||||
|
||||
def shutdown(self):
|
||||
return self.do_state_change('shutdown', 'Shutting Down', 15)
|
||||
shutdown.exposed = True
|
||||
|
||||
def restart(self):
|
||||
headphones.SIGNAL = 'restart'
|
||||
message = 'Restarting...'
|
||||
return serve_template(templatename="shutdown.html", title="Restarting", message=message, timer=30)
|
||||
return self.do_state_change('restart', 'Restarting', 30)
|
||||
restart.exposed = True
|
||||
|
||||
def update(self):
|
||||
headphones.SIGNAL = 'update'
|
||||
message = 'Updating...'
|
||||
return serve_template(templatename="shutdown.html", title="Updating", message=message, timer=120)
|
||||
return page
|
||||
return self.do_state_change('update', 'Updating', 120)
|
||||
update.exposed = True
|
||||
|
||||
def extras(self):
|
||||
@@ -1403,9 +1455,12 @@ class WebInterface(object):
|
||||
return page
|
||||
extras.exposed = True
|
||||
|
||||
def addReleaseById(self, rid):
|
||||
threading.Thread(target=importer.addReleaseById, args=[rid]).start()
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
def addReleaseById(self, rid, rgid=None):
|
||||
threading.Thread(target=importer.addReleaseById, args=[rid, rgid]).start()
|
||||
if rgid:
|
||||
raise cherrypy.HTTPRedirect("albumPage?AlbumID=%s" % rgid)
|
||||
else:
|
||||
raise cherrypy.HTTPRedirect("home")
|
||||
addReleaseById.exposed = True
|
||||
|
||||
def updateCloud(self):
|
||||
@@ -1434,7 +1489,7 @@ class WebInterface(object):
|
||||
from headphones import cache
|
||||
info_dict = cache.getInfo(ArtistID, AlbumID)
|
||||
|
||||
return simplejson.dumps(info_dict)
|
||||
return json.dumps(info_dict)
|
||||
|
||||
getInfo.exposed = True
|
||||
|
||||
@@ -1458,7 +1513,18 @@ class WebInterface(object):
|
||||
from headphones import cache
|
||||
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
|
||||
|
||||
|
||||
@@ -15,18 +15,18 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import cherrypy
|
||||
|
||||
import headphones
|
||||
|
||||
from headphones import logger
|
||||
from headphones.webserve import WebInterface
|
||||
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']
|
||||
https_cert = options['https_cert']
|
||||
https_key = options['https_key']
|
||||
@@ -45,15 +45,15 @@ def initialize(options={}):
|
||||
enable_https = False
|
||||
|
||||
options_dict = {
|
||||
'log.screen': False,
|
||||
'server.thread_pool': 10,
|
||||
'server.socket_port': options['http_port'],
|
||||
'server.socket_host': options['http_host'],
|
||||
'engine.autoreload_on': False,
|
||||
'tools.encode.on' : True,
|
||||
'tools.encode.encoding' : 'utf-8',
|
||||
'tools.decode.on' : True,
|
||||
}
|
||||
'server.socket_port': options['http_port'],
|
||||
'server.socket_host': options['http_host'],
|
||||
'server.thread_pool': 10,
|
||||
'tools.encode.on': True,
|
||||
'tools.encode.encoding': 'utf-8',
|
||||
'tools.decode.on': True,
|
||||
'log.screen': False,
|
||||
'engine.autoreload_on': False,
|
||||
}
|
||||
|
||||
if enable_https:
|
||||
options_dict['server.ssl_certificate'] = https_cert
|
||||
@@ -62,7 +62,8 @@ def initialize(options={}):
|
||||
else:
|
||||
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)
|
||||
|
||||
conf = {
|
||||
@@ -88,7 +89,8 @@ def initialize(options={}):
|
||||
},
|
||||
'/favicon.ico':{
|
||||
'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':{
|
||||
'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({
|
||||
'tools.auth_basic.on': True,
|
||||
'tools.auth_basic.realm': 'Headphones',
|
||||
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict(
|
||||
{options['http_username']:options['http_password']})
|
||||
'tools.auth_basic.realm': 'Headphones web server',
|
||||
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic \
|
||||
.checkpassword_dict({
|
||||
options['http_username']: options['http_password']
|
||||
})
|
||||
})
|
||||
conf['/api'] = { 'tools.auth_basic.on': False }
|
||||
|
||||
|
||||
# Prevent time-outs
|
||||
cherrypy.engine.timeout_monitor.unsubscribe()
|
||||
|
||||
cherrypy.tree.mount(WebInterface(), options['http_root'], config = conf)
|
||||
cherrypy.tree.mount(WebInterface(), options['http_root'], config=conf)
|
||||
|
||||
try:
|
||||
cherrypy.process.servers.check_port(options['http_host'], options['http_port'])
|
||||
cherrypy.server.start()
|
||||
except IOError:
|
||||
print 'Failed to start on port: %i. Is something else running?' % (options['http_port'])
|
||||
sys.exit(0)
|
||||
sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port']))
|
||||
sys.exit(1)
|
||||
|
||||
cherrypy.server.wait()
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
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.
|
||||
if [ `id -u` != "0" ]; then
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
<array>
|
||||
<string>/usr/bin/python</string>
|
||||
<string>/Applications/Headphones/headphones.py</string>
|
||||
<string>-q</string>
|
||||
<string>-d</string>
|
||||
<string>--quiet</string>
|
||||
<string>--daemon</string>
|
||||
<string>--nolaunch</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||