fix merge conflict

This commit is contained in:
Ade
2014-06-28 10:30:56 +12:00
13 changed files with 266 additions and 69 deletions

View File

@@ -77,16 +77,16 @@ def main():
args = parser.parse_args()
if args.verbose:
headphones.VERBOSE = 2
elif args.quiet:
headphones.VERBOSE = 0
headphones.VERBOSE = True
if args.quiet:
headphones.QUIET = True
if args.daemon:
if sys.platform == 'win32':
print "Daemonize not supported under Windows, starting normally"
else:
headphones.DAEMON=True
headphones.VERBOSE = False
headphones.DAEMON = True
headphones.QUIET = True
if args.pidfile:
headphones.PIDFILE = str(args.pidfile)
@@ -101,7 +101,7 @@ def main():
try:
file(headphones.PIDFILE, 'w').write("pid\n")
except IOError, e:
raise SystemExit("Unable to write PID file: %s [%d]" % (e.strerror, e.errno))
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.")

View File

@@ -55,7 +55,6 @@
<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
%endif
@@ -71,10 +70,10 @@
<%def name="javascriptIncludes()">
<script src="js/libs/jquery.dataTables.min.js"></script>
<script>
function getArt() {
$("table#searchresults_table tr td#albumart img").each(function(){
$("table#searchresults_table tr td#albumart img").each(function(){
var id = $(this).attr('title');
var image = $(this);
if ( !image.hasClass('done') ) {
@@ -90,7 +89,7 @@
"bDestroy": true,
"aoColumnDefs": [
{ 'bSortable': false, 'aTargets': [ 0 ] }
],
],
"oLanguage": {
"sLengthMenu":"Show _MENU_ results per page",
"sEmptyTable": "No results",

View File

@@ -38,7 +38,8 @@ SIGNAL = None
SYS_PLATFORM = None
SYS_ENCODING = None
VERBOSE = 1
QUIET = False
VERBOSE = False
DAEMON = False
CREATEPID = False
PIDFILE= None
@@ -250,7 +251,7 @@ PLEX_UPDATE = False
PLEX_NOTIFY = False
NMA_ENABLED = False
NMA_APIKEY = None
NMA_PRIORITY = None
NMA_PRIORITY = 0
NMA_ONSNATCH = None
PUSHALOT_ENABLED = False
PUSHALOT_APIKEY = None
@@ -294,6 +295,8 @@ JOURNAL_MODE = None
UMASK = None
VERIFY_SSL_CERT = True
def CheckSection(sec):
""" Check if INI section exists, if not create it """
try:
@@ -340,7 +343,7 @@ def initialize():
with INIT_LOCK:
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
global __INITIALIZED__, FULL_PATH, PROG_DIR, VERBOSE, QUIET, DAEMON, SYS_PLATFORM, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, \
HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, HTTP_PROXY, LAUNCH_BROWSER, API_ENABLED, API_KEY, GIT_PATH, GIT_USER, GIT_BRANCH, DO_NOT_OVERRIDE_GIT_BRANCH, \
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, \
@@ -362,7 +365,7 @@ 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
PUSHALOT_ONSNATCH, SONGKICK_ENABLED, SONGKICK_APIKEY, SONGKICK_LOCATION, SONGKICK_FILTER_ENABLED, VERIFY_SSL_CERT
if __INITIALIZED__:
@@ -647,6 +650,8 @@ def initialize():
ALBUM_COMPLETION_PCT = check_setting_int(CFG, 'Advanced', 'album_completion_pct', 80)
VERIFY_SSL_CERT = bool(check_setting_int(CFG, 'Advanced', 'verify_ssl_cert', 1))
# update folder formats in the config & bump up config version
if CONFIG_VERSION == '0':
from headphones.helpers import replace_all
@@ -717,8 +722,9 @@ def initialize():
if VERBOSE:
sys.stderr.write('Unable to create the log directory. Logging to screen only.\n')
# Start the logger, silence console logging if we need to
logger.initLogger(verbose=VERBOSE)
# 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
@@ -1020,7 +1026,7 @@ def config_write():
new_config['NMA'] = {}
new_config['NMA']['nma_enabled'] = int(NMA_ENABLED)
new_config['NMA']['nma_apikey'] = NMA_APIKEY
new_config['NMA']['nma_priority'] = NMA_PRIORITY
new_config['NMA']['nma_priority'] = int(NMA_PRIORITY)
new_config['NMA']['nma_onsnatch'] = int(NMA_ONSNATCH)
new_config['Pushalot'] = {}
@@ -1098,6 +1104,7 @@ def config_write():
new_config['Advanced']['album_completion_pct'] = ALBUM_COMPLETION_PCT
new_config['Advanced']['cache_sizemb'] = CACHE_SIZEMB
new_config['Advanced']['journal_mode'] = JOURNAL_MODE
new_config['Advanced']['verify_ssl_cert'] = int(VERIFY_SSL_CERT)
new_config.write()

View File

@@ -505,7 +505,7 @@ def finalize_update(artistid, artistname, errors=False):
myDB = db.DBConnection()
latestalbum = myDB.action('SELECT AlbumTitle, ReleaseDate, AlbumID from albums WHERE ArtistID=? order by ReleaseDate DESC', [artistid]).fetchone()
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=?', [artistid]))
totaltracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND AlbumID IN (SELECT AlbumID FROM albums WHERE Status != "Ignored")', [artistid]))
#havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ?', [artist['artist_name']]))
havetracks = len(myDB.select('SELECT TrackTitle from tracks WHERE ArtistID=? AND Location IS NOT NULL', [artistid])) + len(myDB.select('SELECT TrackTitle from have WHERE ArtistName like ? AND Matched = "Failed"', [artistname]))
@@ -611,6 +611,7 @@ def addReleaseById(rid, rgid=None):
status = 'Wanted'
newValueDict = {"ArtistID": release_dict['artist_id'],
"ReleaseID": rgid,
"ArtistName": release_dict['artist_name'],
"AlbumTitle": release_dict['rg_title'],
"AlbumASIN": release_dict['asin'],

View File

@@ -43,19 +43,32 @@ class LogListHandler(logging.Handler):
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
def initLogger(verbose=1):
def initLogger(console=False, verbose=False):
"""
Setup logging for Headphones. It uses the logger instance with the name
'headphones'. Three log handlers are added:
* RotatingFileHandler: for the file headphones.log
* LogListHandler: for Web UI
* StreamHandler: for console (if verbose > 0)
* StreamHandler: for console (if console)
Console logging is only enabled if console is set to True.
"""
# Close and remove old handlers. This is required to reinit the loggers
# at runtime
for handler in logger.handlers[:]:
# Just make sure it is cleaned up.
if isinstance(handler, handlers.RotatingFileHandler):
handler.close()
elif isinstance(handler, logging.StreamHandler):
handler.flush()
logger.removeHandler(handler)
# Configure the logger to accept all messages
logger.propagate = False
logger.setLevel(logging.DEBUG if verbose == 2 else logging.INFO)
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
# Setup file logger
filename = os.path.join(headphones.LOG_DIR, FILENAME)
@@ -74,7 +87,7 @@ def initLogger(verbose=1):
logger.addHandler(loglist_handler)
# Setup console logger
if verbose:
if console:
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
console_handler = logging.StreamHandler()
console_handler.setFormatter(console_formatter)

View File

@@ -27,7 +27,7 @@ import time
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
@@ -380,34 +380,40 @@ class Plex:
logger.warn('Error sending notification request to Plex Media Server')
class NMA:
def notify(self, artist=None, album=None, snatched=None):
title = 'Headphones'
api = headphones.NMA_APIKEY
nma_priority = headphones.NMA_PRIORITY
def __init__(self):
logger.debug(u"NMA title: " + title)
logger.debug(u"NMA API: " + api)
logger.debug(u"NMA Priority: " + str(nma_priority))
self.apikey = headphones.NMA_APIKEY
self.priority = headphones.NMA_PRIORITY
def _send(self, data):
return request.request_content('https://www.notifymyandroid.com/publicapi/notify', data=data)
def notify(self, artist=None, album=None, snatched_nzb=None):
apikey = self.apikey
priority = self.priority
if snatched_nzb:
event = snatched_nzb + " snatched!"
description = "Headphones has snatched: " + snatched_nzb + " and has sent it to SABnzbd+"
if snatched:
event = snatched + " snatched!"
message = "Headphones has snatched: " + snatched
else:
event = artist + ' - ' + album + ' complete!'
description = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
message = "Headphones has downloaded and postprocessed: " + artist + ' [' + album + ']'
data = { 'apikey': apikey, 'application':'Headphones', 'event': event, 'description': description, 'priority': priority}
logger.debug(u"NMA event: " + event)
logger.debug(u"NMA message: " + message)
logger.info('Sending notification request to NotifyMyAndroid')
request = self._send(data)
batch = False
if not request:
logger.warn('Error sending notification request to NotifyMyAndroid')
p = pynma.PyNMA()
keys = api.split(',')
p.addkey(keys)
if len(keys) > 1: batch = True
response = p.push(title, event, message, priority=nma_priority, batch_mode=batch)
if not response[api][u'code'] == u'200':
logger.error(u'Could not send notification to NotifyMyAndroid')
return False
else:
return True
class PUSHBULLET:

View File

@@ -113,6 +113,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'],

View File

@@ -5,6 +5,7 @@ from bs4 import BeautifulSoup
import requests
import feedparser
import headphones
def request_response(url, method="get", auto_raise=True, whitelist_status_code=None, **kwargs):
"""
@@ -17,9 +18,13 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
if whitelist_status_code and type(whitelist_status_code) != list:
whitelist_status_code = [whitelist_status_code]
# Disable verification of SSL certificates if requested. Note: this could
# pose a security issue!
kwargs["verify"] = headphones.VERIFY_SSL_CERT
# Map method to the request.XXX method. This is a simple hack, but it allows
# requests to apply more magic per method. See lib/requests/api.py.
request_method = getattr(requests, method)
request_method = getattr(requests, method.lower())
try:
# Request the URL
@@ -45,7 +50,15 @@ def request_response(url, method="get", auto_raise=True, whitelist_status_code=N
logger.error("Request timed out.")
except requests.HTTPError, e:
if e.response is not None:
logger.error("Request raise HTTP error with status code: %d", e.response.status_code)
if e.response.status_code >= 500:
cause = "remote server error"
elif e.response.status_code >= 400:
cause = "local request 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)
else:
logger.error("Request raised HTTP error.")
except requests.RequestException, e:

View File

@@ -570,8 +570,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None):
data = request.request_json(
url='http://api.omgwtfnzbs.org/json/',
params=params, headers=headers,
validator=lambda x: type(x) == dict
params=params, headers=headers
)
# Parse response
@@ -738,7 +737,7 @@ def send_to_downloader(data, bestqual, album):
except Exception, e:
logger.exception("Unhandled exception")
else:
else:# if headphones.TORRENT_DOWNLOADER == 2:
logger.info("Sending torrent to uTorrent")
# rutracker needs cookies to be set, pass the .torrent file instead of url
@@ -801,7 +800,7 @@ def send_to_downloader(data, bestqual, album):
if headphones.NMA_ENABLED and headphones.NMA_ONSNATCH:
logger.info(u"Sending NMA notification")
nma = notifiers.NMA()
nma.notify(snatched_nzb=name)
nma.notify(snatched=name)
if headphones.PUSHALOT_ENABLED and headphones.PUSHALOT_ONSNATCH:
logger.info(u"Sending Pushalot notification")
pushalot = notifiers.PUSHALOT()
@@ -1447,7 +1446,7 @@ def preprocess(resultlist):
for result in resultlist:
if result[4] == 'torrent':
#Get out of here if we're using Transmission or uTorrent
#Get out of here if we're using Transmission
if headphones.TORRENT_DOWNLOADER == 1: ## if not a magnet link still need the .torrent to generate hash... uTorrent support labeling
return True, result
# get outta here if rutracker
@@ -1511,6 +1510,6 @@ def CalculateTorrentHash(link, data):
info = bdecode(data)["info"]
tor_hash = sha1(bencode(info)).hexdigest()
logger.info('Torrent Hash: ' + str(tor_hash))
logger.debug('Torrent Hash: ' + str(tor_hash))
return tor_hash

View File

@@ -13,17 +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/>.
import urllib
import urllib2
import urlparse
import cookielib
import json
import re
import os
import time
import urllib, urllib2, urlparse, cookielib
import json, re, os, time
import headphones
from headphones import logger, notifiers
from headphones import logger
class utorrentclient(object):
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
@@ -77,7 +72,7 @@ class utorrentclient(object):
return self._action(params)
def add_url(self, url):
#can recieve magnet or normal .torrent link
#can receive magnet or normal .torrent link
params = [('action', 'add-url'), ('s', url)]
return self._action(params)
@@ -141,17 +136,31 @@ class utorrentclient(object):
logger.debug('URL: ' + str(url))
logger.debug('uTorrent webUI raised the following error: ' + str(err))
def addTorrent(link, hash):
def labelTorrent(hash):
label = headphones.UTORRENT_LABEL
uTorrentClient = utorrentclient()
uTorrentClient.add_url(link)
time.sleep(1) #need to ensure file is loaded uTorrent...
uTorrentClient.setprops(hash,'label', label)
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
def dirTorrent(hash):
uTorrentClient = utorrentclient()
torrentList = uTorrentClient.list()
for torrent in torrentList[1].get('torrents'):
if (torrent[0].lower()==hash):
if (torrent[0].lower() == hash):
return torrent[26]
return False
def addTorrent(link, hash):
uTorrentClient = utorrentclient()
uTorrentClient.add_url(link)
labelTorrent(hash)
return dirTorrent(hash)

View File

@@ -749,6 +749,14 @@ class WebInterface(object):
raise cherrypy.HTTPRedirect("logs")
clearLogs.exposed = True
def toggleVerbose(self):
headphones.VERBOSE = not headphones.VERBOSE
logger.initLogger(not headphones.QUIET, headphones.VERBOSE)
logger.info("Verbose toggled, set to %s", headphones.VERBOSE)
logger.debug("If you read this message, debug logging is available")
raise cherrypy.HTTPRedirect("logs")
toggleVerbose.exposed = True
def getLog(self,iDisplayStart=0,iDisplayLength=100,iSortCol_0=0,sSortDir_0="desc",sSearch="",**kwargs):
iDisplayStart = int(iDisplayStart)

4
lib/pynma/__init__.py Normal file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/python
from pynma import PyNMA

137
lib/pynma/pynma.py Normal file
View File

@@ -0,0 +1,137 @@
#!/usr/bin/python
from xml.dom.minidom import parseString
from httplib import HTTPSConnection
from urllib import urlencode
__version__ = "0.1"
API_SERVER = 'nma.usk.bz'
ADD_PATH = '/publicapi/notify'
USER_AGENT="PyNMA/v%s"%__version__
def uniq_preserve(seq): # Dave Kirby
# Order preserving
seen = set()
return [x for x in seq if x not in seen and not seen.add(x)]
def uniq(seq):
# Not order preserving
return {}.fromkeys(seq).keys()
class PyNMA(object):
"""PyNMA(apikey=[], developerkey=None)
takes 2 optional arguments:
- (opt) apykey: might me a string containing 1 key or an array of keys
- (opt) developerkey: where you can store your developer key
"""
def __init__(self, apikey=[], developerkey=None):
self._developerkey = None
self.developerkey(developerkey)
if apikey:
if type(apikey) == str:
apikey = [apikey]
self._apikey = uniq(apikey)
def addkey(self, key):
"Add a key (register ?)"
if type(key) == str:
if not key in self._apikey:
self._apikey.append(key)
elif type(key) == list:
for k in key:
if not k in self._apikey:
self._apikey.append(k)
def delkey(self, key):
"Removes a key (unregister ?)"
if type(key) == str:
if key in self._apikey:
self._apikey.remove(key)
elif type(key) == list:
for k in key:
if key in self._apikey:
self._apikey.remove(k)
def developerkey(self, developerkey):
"Sets the developer key (and check it has the good length)"
if type(developerkey) == str and len(developerkey) == 48:
self._developerkey = developerkey
def push(self, application="", event="", description="", url="", priority=0, batch_mode=False):
"""Pushes a message on the registered API keys.
takes 5 arguments:
- (req) application: application name [256]
- (req) event: event name [1000]
- (req) description: description [10000]
- (opt) url: url [512]
- (opt) priority: from -2 (lowest) to 2 (highest) (def:0)
- (opt) batch_mode: call API 5 by 5 (def:False)
Warning: using batch_mode will return error only if all API keys are bad
cf: http://nma.usk.bz/api.php
"""
datas = {
'application': application[:256].encode('utf8'),
'event': event[:1024].encode('utf8'),
'description': description[:10000].encode('utf8'),
'priority': priority
}
if url:
datas['url'] = url[:512]
if self._developerkey:
datas['developerkey'] = self._developerkey
results = {}
if not batch_mode:
for key in self._apikey:
datas['apikey'] = key
res = self.callapi('POST', ADD_PATH, datas)
results[key] = res
else:
for i in range(0, len(self._apikey), 5):
datas['apikey'] = ",".join(self._apikey[i:i+5])
res = self.callapi('POST', ADD_PATH, datas)
results[datas['apikey']] = res
return results
def callapi(self, method, path, args):
headers = { 'User-Agent': USER_AGENT }
if method == "POST":
headers['Content-type'] = "application/x-www-form-urlencoded"
http_handler = HTTPSConnection(API_SERVER)
http_handler.request(method, path, urlencode(args), headers)
resp = http_handler.getresponse()
try:
res = self._parse_reponse(resp.read())
except Exception, e:
res = {'type': "pynmaerror",
'code': 600,
'message': str(e)
}
pass
return res
def _parse_reponse(self, response):
root = parseString(response).firstChild
for elem in root.childNodes:
if elem.nodeType == elem.TEXT_NODE: continue
if elem.tagName == 'success':
res = dict(elem.attributes.items())
res['message'] = ""
res['type'] = elem.tagName
return res
if elem.tagName == 'error':
res = dict(elem.attributes.items())
res['message'] = elem.firstChild.nodeValue
res['type'] = elem.tagName
return res