autopep8 E301,E302,E303 too many / too few blank lines

This commit is contained in:
Jesse Mullan
2014-10-27 10:56:17 -07:00
parent a040f38a3f
commit 5a4bff5be8
30 changed files with 184 additions and 26 deletions

View File

@@ -22,6 +22,7 @@ from headphones import db, helpers, logger, lastfm, request
LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4"
class Cache(object):
"""
This class deals with getting, storing and serving up artwork (album
@@ -92,7 +93,6 @@ class Cache(object):
return days_old
def _is_current(self, filename=None, date=None):
if filename:
@@ -431,6 +431,7 @@ class Cache(object):
self.thumb_errors = True
self.thumb_url = image_url
def getArtwork(ArtistID=None, AlbumID=None):
c = Cache()
@@ -445,6 +446,7 @@ def getArtwork(ArtistID=None, AlbumID=None):
artwork_file = os.path.basename(artwork_path)
return "cache/artwork/" + artwork_file
def getThumb(ArtistID=None, AlbumID=None):
c = Cache()
@@ -459,6 +461,7 @@ def getThumb(ArtistID=None, AlbumID=None):
thumbnail_file = os.path.basename(artwork_path)
return "cache/artwork/" + thumbnail_file
def getInfo(ArtistID=None, AlbumID=None):
c = Cache()
@@ -467,6 +470,7 @@ def getInfo(ArtistID=None, AlbumID=None):
return info_dict
def getImageLinks(ArtistID=None, AlbumID=None):
c = Cache()

View File

@@ -24,9 +24,11 @@ import datetime
from common import USER_AGENT
class HeadphonesURLopener(urllib.FancyURLopener):
version = USER_AGENT
class AuthURLOpener(HeadphonesURLopener):
"""
URLOpener class that supports http auth without needing interactive password entry.
@@ -35,6 +37,7 @@ class AuthURLOpener(HeadphonesURLopener):
user: username to use for HTTP auth
pw: password to use for HTTP auth
"""
def __init__(self, user, pw):
self.username = user
self.password = pw
@@ -65,6 +68,7 @@ class AuthURLOpener(HeadphonesURLopener):
self.numTries = 0
return HeadphonesURLopener.open(self, url)
class SearchResult:
"""
Represents a search result from an indexer.
@@ -96,24 +100,28 @@ class SearchResult:
myString += " " + extra + "\n"
return myString
class NZBSearchResult(SearchResult):
"""
Regular NZB result with an URL to the NZB
"""
resultType = "nzb"
class NZBDataSearchResult(SearchResult):
"""
NZB result where the actual NZB XML data is stored in the extraInfo
"""
resultType = "nzbdata"
class TorrentSearchResult(SearchResult):
"""
Torrent result with an URL to the torrent
"""
resultType = "torrent"
class Proper:
def __init__(self, name, url, date):
self.name = name

View File

@@ -44,6 +44,7 @@ ARCHIVED = 6 # releases that you don't have locally (counts toward download comp
IGNORED = 7 # releases that you don't want included in your download stats
SNATCHED_PROPER = 9 # qualified with quality
class Quality:
NONE = 0

View File

@@ -4,6 +4,7 @@ import os
import re
from configobj import ConfigObj
def bool_int(value):
"""
Casts a config value into a 0 or 1
@@ -230,6 +231,7 @@ _config_definitions = {
'XLDPROFILE': (str, 'General', '')
}
class Config(object):
""" Wraps access to particular values in a config file """

View File

@@ -69,6 +69,7 @@ WAVE_FILE_TYPE_BY_EXTENSION = {
#SHNTOOL_COMPATIBLE = ('Waveform Audio', 'WavPack', 'Free Lossless Audio Codec')
SHNTOOL_COMPATIBLE = ('Free Lossless Audio Codec')
def check_splitter(command):
'''Check xld or shntools installed'''
try:
@@ -82,6 +83,7 @@ def check_splitter(command):
return False
return True
def split_baby(split_file, split_cmd):
'''Let's split baby'''
logger.info('Splitting %s...', split_file.decode(headphones.SYS_ENCODING, 'replace'))
@@ -115,6 +117,7 @@ def split_baby(split_file, split_cmd):
logger.info('Split success %s', split_file.decode(headphones.SYS_ENCODING, 'replace'))
return True
def check_list(list, ignore=0):
'''Checks a list for None elements. If list have None (after ignore index) then it should pass only if all elements
are None threreafter. Returns a tuple without the None entries.'''
@@ -146,12 +149,14 @@ def check_list(list, ignore=0):
return tuple(list1+list2)
def trim_cue_entry(string):
'''Removes leading and trailing "s.'''
if string[0] == '"' and string[-1] == '"':
string = string[1:-1]
return string
def int_to_str(value, length=2):
'''Converts integer to string eg 3 to "03"'''
try:
@@ -164,6 +169,7 @@ def int_to_str(value, length=2):
content = '0' + content
return content
def split_file_list(ext=None):
file_list = [None for m in range(100)]
if ext and ext[0] != '.':
@@ -260,6 +266,7 @@ class Directory:
else:
self.content.append(File(self.path + os.sep + i))
class File:
def __init__(self, path):
self.path = path
@@ -285,6 +292,7 @@ class File:
return content
class CueFile(File):
def __init__(self, path):
@@ -434,6 +442,7 @@ class CueFile(File):
content += '\n'
return content
class MetaFile(File):
def __init__(self, path):
File.__init__(self, path)
@@ -498,6 +507,7 @@ class MetaFile(File):
'''Returns tracks count'''
return len(self.content['tracks']) - self.content['tracks'].count(None)
class WaveFile(File):
def __init__(self, path, track_nr=None):
File.__init__(self, path)
@@ -537,6 +547,7 @@ class WaveFile(File):
if self.type == 'Free Lossless Audio Codec':
return FLAC(self.name)
def split(albumpath):
os.chdir(albumpath)

View File

@@ -28,10 +28,12 @@ import headphones
from headphones import logger
def dbFilename(filename="headphones.db"):
return os.path.join(headphones.DATA_DIR, filename)
def getCacheSize():
#this will protect against typecasting problems produced by empty string and None settings
if not headphones.CONFIG.CACHE_SIZEMB:
@@ -39,6 +41,7 @@ def getCacheSize():
return 0
return int(headphones.CONFIG.CACHE_SIZEMB)
class DBConnection:
def __init__(self, filename="headphones.db"):

View File

@@ -13,11 +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/>.
class HeadphonesException(Exception):
"""
Generic Headphones Exception - should never be thrown, only subclassed
"""
class NewzbinAPIThrottled(HeadphonesException):
"""
Newzbin has throttled us, deal with it

View File

@@ -5,6 +5,7 @@ import xml.parsers.expat as expat
import commands
from headphones import logger
def getXldProfile(xldProfile):
xldProfileNotFound = xldProfile
expandedPath = os.path.expanduser('~/Library/Preferences/jp.tmkk.XLD.plist')

View File

@@ -31,6 +31,7 @@ RE_FEATURING = re.compile(r"[fF]t\.|[fF]eaturing|[fF]eat\.|\b[wW]ith\b|&|vs\.")
RE_CD_ALBUM = re.compile(r"\(?((CD|disc)\s*[0-9]+)\)?", re.I)
RE_CD = re.compile(r"^(CD|dics)\s*[0-9]+$", re.I)
def multikeysort(items, columns):
comparers = [ ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) for col in columns]
@@ -44,12 +45,14 @@ def multikeysort(items, columns):
return sorted(items, cmp=comparer)
def checked(variable):
if variable:
return 'Checked'
else:
return ''
def radio(variable, pos):
if variable == pos:
@@ -57,6 +60,7 @@ def radio(variable, pos):
else:
return ''
def latinToAscii(unicrap):
"""
From couch potato
@@ -98,6 +102,7 @@ def latinToAscii(unicrap):
r += str(i)
return r
def convert_milliseconds(ms):
seconds = ms/1000
@@ -109,6 +114,7 @@ def convert_milliseconds(ms):
return minutes
def convert_seconds(s):
gmtime = time.gmtime(s)
@@ -119,15 +125,18 @@ def convert_seconds(s):
return minutes
def today():
today = datetime.date.today()
yyyymmdd = datetime.date.isoformat(today)
return yyyymmdd
def now():
now = datetime.datetime.now()
return now.strftime("%Y-%m-%d %H:%M:%S")
def get_age(date):
try:
@@ -142,17 +151,20 @@ def get_age(date):
return days_old
def bytes_to_mb(bytes):
mb = int(bytes)/1048576
size = '%.1f MB' % mb
return size
def mb_to_bytes(mb_str):
result = re.search('^(\d+(?:\.\d+)?)\s?(?:mb)?', mb_str, flags=re.I)
if result:
return int(float(result.group(1))*1048576)
def piratesize(size):
split = size.split(" ")
factor = float(split[0])
@@ -170,6 +182,7 @@ def piratesize(size):
return size
def replace_all(text, dic, normalize=False):
if not text:
@@ -187,6 +200,7 @@ def replace_all(text, dic, normalize=False):
text = text.replace(i, j)
return text
def replace_illegal_chars(string, type="file"):
if type == "file":
string = re.sub('[\?"*:|<>/]', '_', string)
@@ -195,6 +209,7 @@ def replace_illegal_chars(string, type="file"):
return string
def cleanName(string):
pass1 = latinToAscii(string).lower()
@@ -202,6 +217,7 @@ def cleanName(string):
return out_string
def cleanTitle(title):
title = re.sub('[\.\-\/\_]', ' ', title).lower()
@@ -213,6 +229,7 @@ def cleanTitle(title):
return title
def split_path(f):
"""
Split a path into components, starting with the drive letter (if any). Given
@@ -244,6 +261,7 @@ def split_path(f):
# Done
return components
def expand_subfolders(f):
"""
Try to expand a given folder and search for subfolders containing media
@@ -310,6 +328,7 @@ def expand_subfolders(f):
logger.debug("Expanded subfolders in folder: %s", media_folders)
return media_folders
def extract_data(s):
s = s.replace('_', ' ')
@@ -337,6 +356,7 @@ def extract_data(s):
else:
return (None, None, None)
def extract_metadata(f):
"""
Scan all files in the given directory and decide on an artist, album and
@@ -435,6 +455,7 @@ def extract_metadata(f):
return (None, None, None)
def get_downloaded_track_list(albumpath):
"""
Return a list of audio files for the given directory.
@@ -449,6 +470,7 @@ def get_downloaded_track_list(albumpath):
return downloaded_track_list
def preserve_torrent_direcory(albumpath):
"""
Copy torrent directory to headphones-modified to keep files for seeding.
@@ -465,6 +487,7 @@ def preserve_torrent_direcory(albumpath):
". Not continuing. Error: " + str(e))
return None
def cue_split(albumpath):
"""
Attempts to check and split audio files by a cue for the given directory.
@@ -504,6 +527,7 @@ def cue_split(albumpath):
return False
def extract_logline(s):
# Default log format
pattern = re.compile(r'(?P<timestamp>.*?)\s\-\s(?P<level>.*?)\s*\:\:\s(?P<thread>.*?)\s\:\s(?P<message>.*)', re.VERBOSE)
@@ -517,6 +541,7 @@ def extract_logline(s):
else:
return None
def extract_song_data(s):
#headphones default format
@@ -548,6 +573,7 @@ def extract_song_data(s):
logger.info("Couldn't parse %s into a valid Newbin format", s)
return (name, album, year)
def smartMove(src, dest, delete=True):
from headphones import logger
@@ -588,11 +614,15 @@ def smartMove(src, dest, delete=True):
# TODO: Grab config values from sab to know when these options are checked. For now we'll just iterate through all combinations
def sab_replace_dots(name):
return name.replace('.', ' ')
def sab_replace_spaces(name):
return name.replace(' ', '_')
def sab_sanitize_foldername(name):
""" Return foldername with dodgy chars converted to safe ones
Remove any leading and trailing dot and space characters
@@ -634,12 +664,14 @@ def sab_sanitize_foldername(name):
return name
def split_string(mystring, splitvar=','):
mylist = []
for each_word in mystring.split(splitvar):
mylist.append(each_word.strip())
return mylist
def create_https_certificates(ssl_cert, ssl_key):
"""
Stolen from SickBeard (http://github.com/midgetspy/Sick-Beard):

View File

@@ -32,6 +32,7 @@ blacklisted_special_artists = ['f731ccc4-e22a-43af-a747-64213329e088',
'125ec42a-7229-4250-afc5-e057484327fe',
'89ad4ac3-39f7-470e-963a-56509c546377']
def is_exists(artistid):
myDB = db.DBConnection()
@@ -52,7 +53,6 @@ def artistlist_to_mbids(artistlist, forced=False):
if not artist and not (artist == ' '):
continue
# If adding artists through Manage New Artists, they're coming through as non-unicode (utf-8?)
# and screwing everything up
if not isinstance(artist, unicode):
@@ -105,12 +105,14 @@ def artistlist_to_mbids(artistlist, forced=False):
except Exception as e:
logger.warn('Failed to update arist information from Last.fm: %s' % e)
def addArtistIDListToDB(artistidlist):
# Used to add a list of artist IDs to the database in a single thread
logger.debug("Importer: Adding artist ids %s" % artistidlist)
for artistid in artistidlist:
addArtisttoDB(artistid)
def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
# Putting this here to get around the circular import. We're using this to update thumbnails for artist/albums
@@ -172,7 +174,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
else:
sortname = artist['artist_name']
logger.info(u"Now adding/updating: " + artist['artist_name'])
controlValueDict = {"ArtistID": artistid}
newValueDict = {"ArtistName": artist['artist_name'],
@@ -240,7 +241,6 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
check_release_date = None
new_release_group = True
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)
@@ -504,6 +504,7 @@ def addArtisttoDB(artistid, extrasonly=False, forcefull=False):
for album_search in album_searches:
searcher.searchforalbum(albumid=album_search)
def finalize_update(artistid, artistname, errors=False):
# Moving this little bit to it's own function so we can update have tracks & latest album when deleting extras
@@ -533,6 +534,7 @@ def finalize_update(artistid, artistname, errors=False):
myDB.upsert("artists", newValueDict, controlValueDict)
def addReleaseById(rid, rgid=None):
myDB = db.DBConnection()
@@ -689,6 +691,7 @@ def addReleaseById(rid, rgid=None):
else:
logger.info('Release ' + str(rid) + " already exists in the database!")
def updateFormat():
myDB = db.DBConnection()
tracks = myDB.select('SELECT * from tracks WHERE Location IS NOT NULL and Format IS NULL')
@@ -718,6 +721,7 @@ def updateFormat():
myDB.upsert("have", newValueDict, controlValueDict)
logger.info('Finished finding media format for %s files' % len(havetracks))
def getHybridRelease(fullreleaselist):
"""
Returns a dictionary of best group of tracks from the list of releases and

View File

@@ -30,6 +30,7 @@ API_KEY = "395e6ec6bb557382fc41fde867bce66f"
# Required for API request limit
lock = threading.Lock()
def request_lastfm(method, **kwargs):
"""
Call a Last.FM API method. Automatically sets the method and API key. Method
@@ -62,6 +63,7 @@ def request_lastfm(method, **kwargs):
return data
def getSimilar():
myDB = db.DBConnection()
results = myDB.select("SELECT ArtistID from artists ORDER BY HaveTracks DESC")
@@ -107,6 +109,7 @@ def getSimilar():
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")
@@ -136,6 +139,7 @@ def getArtists():
logger.info("Imported %d new artists from Last.FM", len(artistlist))
def getTagTopArtists(tag, limit=50):
myDB = db.DBConnection()
results = myDB.select("SELECT ArtistID from artists")

View File

@@ -22,9 +22,10 @@ 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
def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=False):
if cron and not headphones.CONFIG.LIBRARYSCAN:
return
@@ -180,7 +181,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
file_count+=1
# Now we start track matching
logger.info("%s new/modified songs found and added to the database" % new_song_count)
song_list = myDB.action("SELECT * FROM have WHERE Matched IS NULL AND LOCATION LIKE ?", [dir.decode(headphones.SYS_ENCODING, 'replace')+"%"])
@@ -293,7 +293,6 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
logger.info('Completed matching tracks from directory: %s' % dir.decode(headphones.SYS_ENCODING, 'replace'))
if not append:
logger.info('Updating scanned artist track counts')
@@ -343,6 +342,8 @@ def libraryScan(dir=None, append=False, ArtistID=None, ArtistName=None, cron=Fal
logger.info('Library scan complete')
#ADDED THIS SECTION TO MARK ALBUMS AS DOWNLOADED IF ARTISTS ARE ADDED EN MASSE BEFORE LIBRARY IS SCANNED
def update_album_status(AlbumID=None):
myDB = db.DBConnection()
logger.info('Counting matched tracks to mark albums as skipped/downloaded')

View File

@@ -39,6 +39,7 @@ logger = logging.getLogger("headphones")
# Global queue for multiprocessing logging
queue = None
class LogListHandler(logging.Handler):
"""
Log handler for Web UI.
@@ -50,6 +51,7 @@ class LogListHandler(logging.Handler):
headphones.LOG_LIST.insert(0, (helpers.now(), message, record.levelname, record.threadName))
@contextlib.contextmanager
def listener():
"""
@@ -85,6 +87,7 @@ def listener():
finally:
queue_listener.stop()
def initMultiprocessing():
"""
Remove all handlers and add QueueHandler on top. This should only be called
@@ -108,6 +111,7 @@ def initMultiprocessing():
# 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
@@ -163,6 +167,7 @@ def initLogger(console=False, verbose=False):
# Install exception hooks
initHooks()
def initHooks(global_exceptions=True, thread_exceptions=True, pass_original=True):
"""
This method installs exception catching mechanisms. Any exception caught

View File

@@ -18,6 +18,7 @@ import htmlentitydefs
from headphones import logger, request
def getLyrics(artist, song):
params = { "artist": artist.encode('utf-8'),
@@ -60,6 +61,7 @@ def getLyrics(artist, song):
return lyrics
def convert_html_entities(s):
matches = re.findall("&#\d+;", s)
if len(matches) > 0:

View File

@@ -32,6 +32,8 @@ 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():
mbuser = None
@@ -73,6 +75,7 @@ def startmb():
return True
def findArtist(name, limit=1):
with mb_lock:
@@ -123,6 +126,7 @@ def findArtist(name, limit=1):
})
return artistlist
def findRelease(name, limit=1, artist=None):
with mb_lock:
@@ -201,6 +205,7 @@ def findRelease(name, limit=1, artist=None):
})
return releaselist
def getArtist(artistid, extrasonly=False):
with mb_lock:
@@ -247,7 +252,6 @@ def getArtist(artistid, extrasonly=False):
# if 'end' in artist['life-span']:
# artist_dict['artist_enddate'] = unicode(artist['life-span']['end'])
releasegroups = []
if not extrasonly:
@@ -321,6 +325,7 @@ def getArtist(artistid, extrasonly=False):
return artist_dict
def getReleaseGroup(rgid):
"""
Returns a list of releases in a release group
@@ -342,6 +347,7 @@ def getReleaseGroup(rgid):
else:
return releaseGroup['release-list']
def getRelease(releaseid, include_artist_info=True):
"""
Deep release search to get track info
@@ -377,7 +383,6 @@ def getRelease(releaseid, include_artist_info=True):
except:
release['country'] = u'Unknown'
if include_artist_info:
if 'release-group' in results:
@@ -404,6 +409,7 @@ def getRelease(releaseid, include_artist_info=True):
return release
def get_new_releases(rgid, includeExtras=False, forcefull=False):
myDB = db.DBConnection()
@@ -486,7 +492,6 @@ def get_new_releases(rgid, includeExtras=False, forcefull=False):
logger.warn('Release ' + releasedata['id'] + ' has no Artists associated.')
return False
release['ReleaseCountry'] = unicode(releasedata['country']) if 'country' in releasedata else u'Unknown'
#assuming that the list will contain media and that the format will be consistent
try:
@@ -570,6 +575,7 @@ def get_new_releases(rgid, includeExtras=False, forcefull=False):
return num_new_releases
def getTracksFromRelease(release):
totalTracks = 1
tracks = []
@@ -590,6 +596,8 @@ def getTracksFromRelease(release):
return tracks
# Used when there is a disambiguation
def findArtistbyAlbum(name):
myDB = db.DBConnection()
@@ -613,7 +621,6 @@ def findArtistbyAlbum(name):
logger.warn('Attempt to query MusicBrainz for %s failed (%s)' % (name, str(e)))
time.sleep(5)
if not results:
return False
@@ -631,10 +638,9 @@ def findArtistbyAlbum(name):
#artist_dict['url'] = u'http://musicbrainz.org/artist/' + newArtist['id']
#artist_dict['score'] = int(releaseGroup['ext:score'])
return artist_dict
def findAlbumID(artist=None, album=None):
results = None

View File

@@ -30,6 +30,7 @@ if headphones.CONFIG.ENCODER == 'xld':
else:
XLD = False
def encode(albumPath):
# Return if xld details not found
@@ -226,6 +227,7 @@ def encode(albumPath):
return musicFinalFiles
def command_map(args):
"""
Wrapper for the '[multiprocessing.]map()' method, to unpack the arguments
@@ -243,6 +245,7 @@ def command_map(args):
logger.exception("Encoder raised an exception.")
return False
def command(encoder, musicSource, musicDest, albumPath):
"""
Encode a given music file with a certain encoder. Returns True on success,
@@ -357,6 +360,7 @@ def command(encoder, musicSource, musicDest, albumPath):
return encoded
def getTimeEncode(start):
seconds =int(time.time()-start)
hours = seconds / 3600

View File

@@ -39,6 +39,7 @@ try:
except ImportError:
from cgi import parse_qsl
class GROWL(object):
"""
Growl notifications, for OS X.
@@ -124,6 +125,7 @@ class GROWL(object):
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
class PROWL(object):
"""
Prowl notifications.
@@ -177,6 +179,7 @@ class PROWL(object):
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
class MPC(object):
"""
MPC library update
@@ -264,6 +267,7 @@ class XBMC(object):
except Exception:
logger.error('Error sending notification request to XBMC')
class LMS(object):
"""
Class for updating a Logitech Media Server
@@ -305,6 +309,7 @@ class LMS(object):
if not request:
logger.warn('Error sending rescan request to LMS')
class Plex(object):
def __init__(self):
@@ -391,6 +396,7 @@ class Plex(object):
except:
logger.warn('Error sending notification request to Plex Media Server')
class NMA(object):
def notify(self, artist=None, album=None, snatched=None):
title = 'Headphones'
@@ -427,6 +433,7 @@ class NMA(object):
else:
return True
class PUSHBULLET(object):
def __init__(self):
@@ -480,6 +487,7 @@ class PUSHBULLET(object):
self.notify('Main Screen Activate', 'Test Message')
class PUSHALOT(object):
def notify(self, message, event):
@@ -519,6 +527,7 @@ class PUSHALOT(object):
logger.info(u"Pushalot notification failed.")
return False
class Synoindex(object):
def __init__(self, util_loc='/usr/syno/bin/synoindex'):
self.util_loc = util_loc
@@ -555,6 +564,7 @@ class Synoindex(object):
for path in path_list:
self.notify(path)
class PUSHOVER(object):
def __init__(self):
@@ -613,6 +623,7 @@ class PUSHOVER(object):
self.notify('Main Screen Activate', 'Test Message')
class TwitterNotifier(object):
REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token'
@@ -689,7 +700,6 @@ class TwitterNotifier(object):
headphones.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
return True
def _send_tweet(self, message=None):
username=self.consumer_key
@@ -717,6 +727,7 @@ class TwitterNotifier(object):
return self._send_tweet(prefix+": "+message)
class OSX_NOTIFY(object):
def __init__(self):
@@ -727,6 +738,7 @@ class OSX_NOTIFY(object):
def swizzle(self, cls, SEL, func):
old_IMP = cls.instanceMethodForSelector_(SEL)
def wrapper(self, *args, **kwargs):
return func(self, old_IMP, *args, **kwargs)
new_IMP = self.objc.selector(wrapper, selector=old_IMP.selector,
@@ -772,6 +784,7 @@ class OSX_NOTIFY(object):
def swizzled_bundleIdentifier(self, original, swizzled):
return 'ade.headphones.osxnotify'
class BOXCAR(object):
def __init__(self):
@@ -798,6 +811,7 @@ class BOXCAR(object):
logger.warn('Error sending Boxcar2 Notification: %s' % e)
return False
class SubSonicNotifier(object):
def __init__(self):

View File

@@ -19,7 +19,6 @@
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.
import httplib
import datetime
@@ -32,6 +31,7 @@ import xmlrpclib
from headphones import logger
def sendNZB(nzb):
addToTop = False
@@ -48,7 +48,6 @@ def sendNZB(nzb):
nzbgetXMLrpc = 'http://' + nzbgetXMLrpc
headphones.CONFIG.NZBGET_HOST.replace('http://', '', 1)
url = nzbgetXMLrpc % {"host": headphones.CONFIG.NZBGET_HOST, "username": headphones.CONFIG.NZBGET_USERNAME, "password": headphones.CONFIG.NZBGET_PASSWORD}
nzbGetRPC = xmlrpclib.ServerProxy(url)

View File

@@ -32,6 +32,7 @@ from headphones import logger, helpers, request, mb, music_encoder
postprocessor_lock = threading.Lock()
def checkFolder():
with postprocessor_lock:
@@ -57,6 +58,7 @@ def checkFolder():
else:
logger.info("No folder name found for " + album['Title'])
def verify(albumid, albumpath, Kind=None, forced=False):
myDB = db.DBConnection()
@@ -276,6 +278,7 @@ def verify(albumid, albumpath, Kind=None, forced=False):
else:
logger.info(u"Already marked as unprocessed: " + albumpath.decode(headphones.SYS_ENCODING, 'replace'))
def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, Kind=None):
logger.info('Starting post-processing for: %s - %s' % (release['ArtistName'], release['AlbumTitle']))
@@ -500,6 +503,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list,
mpc = notifiers.MPC()
mpc.notify()
def embedAlbumArt(artwork, downloaded_track_list):
logger.info('Embedding album art')
@@ -519,6 +523,7 @@ def embedAlbumArt(artwork, downloaded_track_list):
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):
logger.info('Adding album art to folder')
@@ -552,6 +557,7 @@ def addAlbumArt(artwork, albumpath, release):
logger.error('Error saving album art: %s', e)
return
def cleanupFiles(albumpath):
logger.info('Cleaning up files')
@@ -564,6 +570,7 @@ def cleanupFiles(albumpath):
except Exception as e:
logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e))
def renameNFO(albumpath):
logger.info('Renaming NFO')
@@ -577,6 +584,7 @@ def renameNFO(albumpath):
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):
logger.info("Moving files: %s" % albumpath)
try:
@@ -809,6 +817,7 @@ def moveFiles(albumpath, release, tracks):
return destination_paths
def correctMetadata(albumid, release, downloaded_track_list):
logger.info('Preparing to write metadata to tracks....')
@@ -862,6 +871,7 @@ def correctMetadata(albumid, release, downloaded_track_list):
except Exception, e:
logger.warn("Error writing metadata to '%s': %s", item.path.decode(headphones.SYS_ENCODING, 'replace'), str(e))
def embedLyrics(downloaded_track_list):
logger.info('Adding lyrics')
@@ -909,6 +919,7 @@ def embedLyrics(downloaded_track_list):
else:
logger.debug('No lyrics found for track: %s', item.title)
def renameFiles(albumpath, downloaded_track_list, release):
logger.info('Renaming files')
try:
@@ -975,7 +986,6 @@ def renameFiles(albumpath, downloaded_track_list, release):
new_file_name = helpers.replace_all(headphones.CONFIG.FILE_FORMAT.strip(), values).replace('/', '_') + ext
new_file_name = helpers.replace_illegal_chars(new_file_name).encode(headphones.SYS_ENCODING, 'replace')
if headphones.CONFIG.FILE_UNDERSCORES:
@@ -997,6 +1007,7 @@ def renameFiles(albumpath, downloaded_track_list, release):
logger.error('Error renaming file: %s. Error: %s', downloaded_track.decode(headphones.SYS_ENCODING, 'replace'), e)
continue
def updateFilePermissions(albumpaths):
for folder in albumpaths:
@@ -1010,6 +1021,7 @@ def updateFilePermissions(albumpaths):
logger.error("Could not change permissions for file: %s", full_path)
continue
def renameUnprocessedFolder(albumpath):
i = 0
@@ -1026,6 +1038,7 @@ def renameUnprocessedFolder(albumpath):
os.rename(albumpath, new_folder_name)
return
def forcePostProcess(dir=None, expand_subfolders=True, album_dir=None):
if album_dir:

View File

@@ -27,6 +27,7 @@ import collections
# Dictionary with last request times, for rate limiting.
last_requests = collections.defaultdict(int)
def request_response(url, method="get", auto_raise=True,
whitelist_status_code=None, rate_limit=None, **kwargs):
"""
@@ -125,6 +126,7 @@ def request_response(url, method="get", auto_raise=True,
except requests.RequestException as e:
logger.error("Request raised exception: %s", e)
def request_soup(url, **kwargs):
"""
Wrapper for `request_response', which will return a BeatifulSoup object if
@@ -137,6 +139,7 @@ def request_soup(url, **kwargs):
if response is not None:
return BeautifulSoup(response.content, parser)
def request_minidom(url, **kwargs):
"""
Wrapper for `request_response', which will return a Minidom object if no
@@ -148,6 +151,7 @@ def request_minidom(url, **kwargs):
if response is not None:
return minidom.parseString(response.content)
def request_json(url, **kwargs):
"""
Wrapper for `request_response', which will decode the response as JSON
@@ -175,6 +179,7 @@ def request_json(url, **kwargs):
if headphones.VERBOSE:
server_message(response)
def request_content(url, **kwargs):
"""
Wrapper for `request_response', which will return the raw content.
@@ -185,6 +190,7 @@ def request_content(url, **kwargs):
if response is not None:
return response.content
def request_feed(url, **kwargs):
"""
Wrapper for `request_response', which will return a feed object.
@@ -195,6 +201,7 @@ 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.

View File

@@ -30,6 +30,7 @@ from headphones.common import USER_AGENT
from headphones import logger
from headphones import notifiers, helpers
def sendNZB(nzb):
params = {}
@@ -127,6 +128,7 @@ def sendNZB(nzb):
logger.info(u"Unknown failure sending NZB to sab. Return text is: " + sabText)
return False
def checkConfig():
params = { 'mode': 'get_config',

View File

@@ -54,6 +54,7 @@ gazelle = None
# RUtracker search object
rutracker = rutrackersearch.Rutracker()
def fix_url(s, charset="utf-8"):
"""
Fix the URL so it is proper formatted and encoded.
@@ -68,6 +69,7 @@ def fix_url(s, charset="utf-8"):
return urlparse.urlunsplit((scheme, netloc, path, qs, anchor))
def torrent_to_file(target_file, data):
"""
Write torrent data to file, and change permissions accordingly. Will return
@@ -94,6 +96,7 @@ def torrent_to_file(target_file, data):
# Done
return True
def read_torrent_name(torrent_file, default_name=None):
"""
Read the torrent file and return the torrent name. If the torrent name
@@ -123,6 +126,7 @@ def read_torrent_name(torrent_file, default_name=None):
# Return default
return default_name
def calculate_torrent_hash(link, data=None):
"""
Calculate the torrent hash from a magnet link or data.
@@ -141,6 +145,7 @@ def calculate_torrent_hash(link, data=None):
return torrent_hash
def get_seed_ratio(provider):
"""
Return the seed ratio for the specified provider, if applicable. Defaults to
@@ -170,6 +175,7 @@ def get_seed_ratio(provider):
return seed_ratio
def searchforalbum(albumid=None, new=False, losslessOnly=False, choose_specific_download=False):
myDB = db.DBConnection()
@@ -204,6 +210,7 @@ def searchforalbum(albumid=None, new=False, losslessOnly=False, choose_specific_
logger.info('Search for Wanted albums complete')
def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
NZB_PROVIDERS = (headphones.CONFIG.HEADPHONES_INDEXER or headphones.CONFIG.NEWZNAB or headphones.CONFIG.NZBSORG or headphones.CONFIG.OMGWTFNZBS)
@@ -249,7 +256,6 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
results = nzb_results + torrent_results
if choose_specific_download:
return results
@@ -264,11 +270,13 @@ def do_sorted_search(album, new, losslessOnly, choose_specific_download=False):
if data and bestqual:
send_to_downloader(data, bestqual, album)
def removeDisallowedFilenameChars(filename):
validFilenameChars = "-_.() %s%s" % (string.ascii_letters, string.digits)
cleanedFilename = unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore').lower()
return ''.join(c for c in cleanedFilename if c in validFilenameChars)
def more_filtering(results, album, albumlength, new):
low_size_limit = None
@@ -332,6 +340,7 @@ def more_filtering(results, album, albumlength, new):
return results
def sort_search_results(resultlist, album, new, albumlength):
if new and not len(resultlist):
@@ -401,6 +410,7 @@ def sort_search_results(resultlist, album, new, albumlength):
return finallist
def get_year_from_release_date(release_date):
try:
@@ -410,6 +420,7 @@ def get_year_from_release_date(release_date):
return year
def searchNZB(album, new=False, losslessOnly=False, albumlength=None):
albumid = album['AlbumID']
@@ -678,6 +689,7 @@ def searchNZB(album, new=False, losslessOnly=False, albumlength=None):
return results
def send_to_downloader(data, bestqual, album):
logger.info(u'Found best result from %s: <a href="%s">%s</a> - %s', bestqual[3], bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1]))
@@ -920,6 +932,7 @@ def send_to_downloader(data, bestqual, album):
boxcar = notifiers.BOXCAR()
boxcar.notify('Headphones snatched: ' + title, b2msg, rgid)
def verifyresult(title, artistterm, term, lossless):
title = re.sub('[\.\-\/\_]', ' ', title)
@@ -985,6 +998,7 @@ def verifyresult(title, artistterm, term, lossless):
return True
def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
global gazelle # persistent what.cd api object to reduce number of login attempts
@@ -1057,7 +1071,6 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
return proxy_url
if headphones.CONFIG.KAT:
provider = "Kick Ass Torrents"
ka_term = term.replace("!", "")
@@ -1446,6 +1459,8 @@ def searchTorrent(album, new=False, losslessOnly=False, albumlength=None):
return results
# THIS IS KIND OF A MESS AND PROBABLY NEEDS TO BE CLEANED UP
def preprocess(resultlist):
for result in resultlist:

View File

@@ -20,6 +20,7 @@ import urllib
import re
import os
class Rutracker():
logged_in = False

View File

@@ -20,6 +20,8 @@ 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")

View File

@@ -27,6 +27,7 @@ import headphones
# TODO: Store the session id so we don't need to make 2 calls
# Store torrent id so we can check up on it
def addTorrent(link):
method = 'torrent-add'
@@ -60,6 +61,7 @@ def addTorrent(link):
logger.info('Transmission returned status %s' % response['result'])
return False
def getTorrentFolder(torrentid):
method = 'torrent-get'
arguments = { 'ids': torrentid, 'fields': ['name', 'percentDone']}
@@ -80,6 +82,7 @@ def getTorrentFolder(torrentid):
return torrent_folder_name
def setSeedRatio(torrentid, ratio):
method = 'torrent-set'
if ratio != 0:
@@ -91,6 +94,7 @@ def setSeedRatio(torrentid, ratio):
if not response:
return False
def removeTorrent(torrentid, remove_data = False):
method = 'torrent-get'
@@ -120,6 +124,7 @@ def removeTorrent(torrentid, remove_data = False):
return False
def torrentAction(method, arguments):
host = headphones.CONFIG.TRANSMISSION_HOST

View File

@@ -17,6 +17,7 @@ import headphones
from headphones import logger, db, importer
def dbUpdate(forcefull=False):
myDB = db.DBConnection()

View File

@@ -21,6 +21,7 @@ import headphones
from headphones import logger
from collections import namedtuple
class utorrentclient(object):
TOKEN_REGEX = "<div id='token' style='display:none;'>([^<>]+)</div>"
@@ -156,12 +157,14 @@ class utorrentclient(object):
logger.debug('URL: ' + str(url))
logger.debug('uTorrent webUI raised the following error: ' + str(err))
def labelTorrent(hash):
label = headphones.CONFIG.UTORRENT_LABEL
uTorrentClient = utorrentclient()
if label:
uTorrentClient.setprops(hash, 'label', label)
def removeTorrent(hash, remove_data = False):
uTorrentClient = utorrentclient()
status, torrentList = uTorrentClient.list()
@@ -177,6 +180,7 @@ def removeTorrent(hash, remove_data = False):
return False
return False
def setSeedRatio(hash, ratio):
uTorrentClient = utorrentclient()
uTorrentClient.setprops(hash, 'seed_override', '1')
@@ -186,6 +190,7 @@ def setSeedRatio(hash, ratio):
# TODO passing -1 should be unlimited
uTorrentClient.setprops(hash, 'seed_ratio', -10)
def dirTorrent(hash, cacheid=None, return_name=None):
uTorrentClient = utorrentclient()
@@ -212,6 +217,7 @@ def dirTorrent(hash, cacheid=None, return_name=None):
return None, None
def addTorrent(link, hash):
uTorrentClient = utorrentclient()
@@ -243,6 +249,7 @@ def addTorrent(link, hash):
labelTorrent(hash)
return os.path.basename(os.path.normpath(torrent_folder))
def getSettingsDirectories():
uTorrentClient = utorrentclient()
settings = uTorrentClient.get_settings()

View File

@@ -22,6 +22,7 @@ import subprocess
from headphones import logger, version, request
def runGit(args):
if headphones.CONFIG.GIT_PATH:
@@ -59,6 +60,7 @@ def runGit(args):
return (output, err)
def getVersion():
if version.HEADPHONES_VERSION.startswith('win32build'):
@@ -115,6 +117,7 @@ def getVersion():
else:
return None, 'master'
def checkGithub():
headphones.COMMITS_BEHIND = 0
@@ -161,6 +164,7 @@ def checkGithub():
return headphones.LATEST_VERSION
def update():
if headphones.INSTALL_TYPE == 'win':
logger.info('Windows .exe updating not supported yet.')

View File

@@ -37,6 +37,7 @@ except ImportError:
# Python 2.6.x fallback, from libs
from ordereddict import OrderedDict
def serve_template(templatename, **kwargs):
interface_dir = os.path.join(str(headphones.PROG_DIR), 'data/interfaces/')
@@ -50,6 +51,7 @@ def serve_template(templatename, **kwargs):
except:
return exceptions.html_error_template().render()
class WebInterface(object):
def index(self):
@@ -102,7 +104,6 @@ class WebInterface(object):
return serve_template(templatename="artist.html", title=artist['ArtistName'], artist=artist, albums=albums, extras=extras_dict)
artistPage.exposed = True
def albumPage(self, AlbumID):
myDB = db.DBConnection()
album = myDB.action('SELECT * from albums WHERE AlbumID=?', [AlbumID]).fetchone()
@@ -132,7 +133,6 @@ class WebInterface(object):
return serve_template(templatename="album.html", title=title, album=album, tracks=tracks, description=description)
albumPage.exposed = True
def search(self, name, type):
if len(name) == 0:
raise cherrypy.HTTPRedirect("home")
@@ -472,7 +472,6 @@ class WebInterface(object):
check = set([(cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) for d in headphones_album_dictionary])
unmatchedalbums = [d for d in have_album_dictionary if (cleanName(d['ArtistName']).lower(), cleanName(d['AlbumTitle']).lower()) not in check]
return serve_template(templatename="manageunmatched.html", title="Manage Unmatched Items", unmatchedalbums=unmatchedalbums)
manageUnmatched.exposed = True
@@ -782,7 +781,6 @@ class WebInterface(object):
totalcount = 0
myDB = db.DBConnection()
sortcolumn = 'ArtistSortName'
sortbyhavepercent = False
if iSortCol_0 == '2':
@@ -809,7 +807,6 @@ class WebInterface(object):
if sortcolumn == 'ReleaseDate':
filtered.reverse()
artists = filtered[iDisplayStart:(iDisplayStart+iDisplayLength)]
rows = []
for artist in artists:
@@ -840,7 +837,6 @@ class WebInterface(object):
rows.append(row)
dict = {'iTotalDisplayRecords': len(filtered),
'iTotalRecords': totalcount,
'aaData': rows,
@@ -1362,6 +1358,7 @@ class WebInterface(object):
return msg
osxnotifyregister.exposed = True
class Artwork(object):
def index(self):
return "Artwork"
@@ -1400,6 +1397,7 @@ class Artwork(object):
def index(self):
return "Here be thumbs"
index.exposed = True
def default(self, ArtistOrAlbum="", ID=None):
from headphones import cache
ArtistID = None

View File

@@ -22,6 +22,7 @@ from headphones import logger
from headphones.webserve import WebInterface
from headphones.helpers import create_https_certificates
def initialize(options=None):
if options is None:
options = {}
@@ -111,7 +112,6 @@ def initialize(options=None):
})
conf['/api'] = { 'tools.auth_basic.on': False }
# Prevent time-outs
cherrypy.engine.timeout_monitor.unsubscribe()
cherrypy.tree.mount(WebInterface(), str(options['http_root']), config=conf)