diff --git a/data/interfaces/default/artist.html b/data/interfaces/default/artist.html index c5f21ccf..4617f7eb 100644 --- a/data/interfaces/default/artist.html +++ b/data/interfaces/default/artist.html @@ -128,7 +128,7 @@ %if album['Status'] == 'Skipped' or album['Status'] == 'Ignored': [want] %elif (album['Status'] == 'Wanted' or album['Status'] == 'Wanted Lossless'): - [skip] + [skip] [search] %else: [retry][new] %endif diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 9ac3a0f6..d5df9e61 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -168,6 +168,68 @@ + <% + if config['nzbget_priority'] == -100: + prio_verylow = 'selected="selected"' + prio_low = '' + prio_normal = '' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == -50: + prio_verylow = '' + prio_low = 'selected="selected"' + prio_normal = '' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == 0: + prio_verylow = '' + prio_low = '' + prio_normal = 'selected="selected"' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == 50: + prio_verylow = '' + prio_low = '' + prio_normal = '' + prio_high = 'selected="selected"' + prio_veryhigh = '' + prio_force = '' + elif config['nzbget_priority'] == 100: + prio_verylow = '' + prio_low = '' + prio_normal = '' + prio_high = '' + prio_veryhigh = 'selected="selected"' + prio_force = '' + elif config['nzbget_priority'] == 900: + prio_verylow = '' + prio_low = '' + prio_normal = '' + prio_high = '' + prio_veryhigh = '' + prio_force = 'selected="selected"' + else: + prio_verylow = '' + prio_low = '' + prio_normal = 'selected="selected"' + prio_high = '' + prio_veryhigh = '' + prio_force = '' + %> +
+ + +
@@ -382,66 +444,117 @@
Torrents -
- -
-
-
- - + +
+ The Pirate Bay +
+
-
-
- -
-
- -
-
-
- - +
+
+ + +
+
+ + +
-
-
- -
-
-
- - +
+ +
+ Kick Ass Torrents +
+
-
- - +
+
+ + +
+
+ + +
-
-
- -
-
-
- - +
+ +
+ Waffles.fm +
+
-
- - +
+
+ + +
+
+ + +
+
+ + +
-
-
- -
-
-
- - +
+ +
+ rutracker.org +
+
-
- - +
+
+ + +
+
+ + +
+
+ + +
-
+
+ +
+ What.cd +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ Mininova +
+ +
+
+
+ + +
+
+
+
@@ -514,6 +627,7 @@ +
@@ -834,7 +948,7 @@
- Use: $Artist/$artist, $Album/$album, $Year/$year, $Type/$type (release type) and $First/$first (first letter in artist name) + Use: $Artist/$artist, $Album/$album, $Year/$year, $Type/$type (release type) and $First/$first (first letter in artist name), $OriginalFolder/$originalfolder (downloaded directory name) E.g.: $Type/$First/$artist/$album [$year] = Album/G/girl talk/all day [2010]
@@ -1744,6 +1858,7 @@ initConfigCheckbox("#useomgwtfnzbs"); initConfigCheckbox("#usekat"); initConfigCheckbox("#usepiratebay"); + initConfigCheckbox("#usemininova"); initConfigCheckbox("#usewaffles"); initConfigCheckbox("#userutracker"); initConfigCheckbox("#usewhatcd"); diff --git a/headphones/__init__.py b/headphones/__init__.py index c29dcb72..8248799e 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -111,6 +111,7 @@ CORRECT_METADATA = 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 @@ -147,6 +148,7 @@ NZBGET_USERNAME = None NZBGET_PASSWORD = None NZBGET_CATEGORY = None NZBGET_HOST = None +NZBGET_PRIORITY = 0 HEADPHONES_INDEXER = False @@ -189,18 +191,24 @@ TORRENTBLACKHOLE_DIR = None NUMBEROFSEEDERS = 10 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 @@ -343,12 +351,12 @@ def initialize(): 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, \ + 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, KAT, KAT_PROXY_URL, PIRATEBAY, PIRATEBAY_PROXY_URL, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \ - RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \ + 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, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, \ - NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \ + NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_HOST, HEADPHONES_INDEXER, NZBMATRIX, TRANSMISSION_HOST, TRANSMISSION_USERNAME, TRANSMISSION_PASSWORD, \ UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, UTORRENT_LABEL, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \ NZBSORG, NZBSORG_UID, NZBSORG_HASH, OMGWTFNZBS, OMGWTFNZBS_UID, OMGWTFNZBS_APIKEY, \ NZB_DOWNLOADER, TORRENT_DOWNLOADER, PREFERRED_WORDS, REQUIRED_WORDS, IGNORED_WORDS, LASTFM_USERNAME, \ @@ -377,6 +385,9 @@ def initialize(): CheckSection('Newznab') CheckSection('NZBsorg') CheckSection('omgwtfnzbs') + CheckSection('Piratebay') + CheckSection('Kat') + CheckSection('Mininova') CheckSection('Waffles') CheckSection('Rutracker') CheckSection('What.cd') @@ -448,6 +459,7 @@ def initialize(): 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)) @@ -476,24 +488,33 @@ def initialize(): TORRENTBLACKHOLE_DIR = check_setting_str(CFG, 'General', 'torrentblackhole_dir', '') NUMBEROFSEEDERS = check_setting_str(CFG, 'General', 'numberofseeders', '10') - KAT = bool(check_setting_int(CFG, 'General', 'kat', 0)) - KAT_PROXY_URL = check_setting_str(CFG, 'General', 'kat_proxy_url', '') - 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', '') @@ -505,6 +526,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)) @@ -872,6 +894,7 @@ def config_write(): 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) @@ -892,27 +915,39 @@ def config_write(): new_config['General']['numberofseeders'] = NUMBEROFSEEDERS new_config['General']['torrentblackhole_dir'] = TORRENTBLACKHOLE_DIR - new_config['General']['kat'] = int(KAT) - new_config['General']['kat_proxy_url'] = KAT_PROXY_URL - 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) @@ -933,6 +968,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) @@ -1101,7 +1137,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) @@ -1113,6 +1149,9 @@ 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 + SCHED.add_interval_job(torrentfinished.checkTorrentFinished, hours=12) + SCHED.start() started = True diff --git a/headphones/api.py b/headphones/api.py index 87f20ae0..1fcce419 100644 --- a/headphones/api.py +++ b/headphones/api.py @@ -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): diff --git a/headphones/cache.py b/headphones/cache.py index 4eba6034..0194600e 100644 --- a/headphones/cache.py +++ b/headphones/cache.py @@ -20,7 +20,7 @@ import headphones from headphones import db, helpers, logger, lastfm, request -lastfm_apikey = "690e1ed3bc00bc91804cd8f7fe5ed6d4" +LASTFM_API_KEY = "690e1ed3bc00bc91804cd8f7fe5ed6d4" class Cache(object): """ @@ -59,7 +59,6 @@ class Cache(object): info_content = None def __init__(self): - pass def _findfilesstartingwith(self,pattern,folder): @@ -209,7 +208,7 @@ 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 @@ -227,7 +226,7 @@ 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 @@ -254,7 +253,7 @@ 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 @@ -282,7 +281,7 @@ 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 diff --git a/headphones/db.py b/headphones/db.py index 576d2533..060b91b1 100644 --- a/headphones/db.py +++ b/headphones/db.py @@ -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 diff --git a/headphones/importer.py b/headphones/importer.py index a94bf35c..4bb3f9a7 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -98,9 +98,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): @@ -431,9 +432,16 @@ 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) + 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'], diff --git a/headphones/lastfm.py b/headphones/lastfm.py index 9f12601c..fdb456b3 100644 --- a/headphones/lastfm.py +++ b/headphones/lastfm.py @@ -21,8 +21,9 @@ 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): """ @@ -40,7 +41,7 @@ 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) + data = request.request_json(ENTRY_POINT, timeout=TIMEOUT, params=kwargs) # Parse response and check for errors. if not data: @@ -55,13 +56,13 @@ def request_lastfm(method, **kwargs): 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 +95,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 +130,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) diff --git a/headphones/nzbget.py b/headphones/nzbget.py index 8c977c57..0d9eca19 100644 --- a/headphones/nzbget.py +++ b/headphones/nzbget.py @@ -69,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 diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index b824fcff..c80fc267 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -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 @@ -305,7 +305,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) @@ -389,6 +389,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) @@ -413,7 +416,24 @@ 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 + 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: @@ -560,11 +580,23 @@ def cleanupFiles(albumpath): 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)) + if not (headphones.KEEP_NFO and files.lower().endswith('.nfo')): + logger.debug('Removing: %s' % files) + try: + os.remove(os.path.join(r, files)) + except Exception, e: + logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), e)) + +def renameNFO(albumpath): + 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, e: - logger.error(u'Could not remove file: %s. Error: %s' % (files.decode(headphones.SYS_ENCODING, 'replace'), 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): @@ -590,20 +622,27 @@ def moveFiles(albumpath, release, tracks): firstchar = '0-9' else: firstchar = sortname[0] - + + for r,d,f in os.walk(albumpath): + try: + origfolder = os.path.basename(os.path.normpath(r)) + except: + origfolder = '' 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) diff --git a/headphones/request.py b/headphones/request.py index 4e6b827f..756584ce 100644 --- a/headphones/request.py +++ b/headphones/request.py @@ -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 . + from headphones import logger from xml.dom import minidom diff --git a/headphones/searcher.py b/headphones/searcher.py index ef889d5c..3efc3cad 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -569,6 +569,8 @@ def send_to_downloader(data, bestqual, album): logger.info(u'Found best result from %s: %s - %s', bestqual[3], bestqual[2], bestqual[0], helpers.bytes_to_mb(bestqual[1])) # Get rid of any dodgy chars here so we can prevent sab from renaming our downloads kind = bestqual[4] + seed_ratio = None + torrentid = None if kind == 'nzb': folder_name = helpers.sab_sanitize_foldername(bestqual[0]) @@ -674,7 +676,7 @@ def send_to_downloader(data, bestqual, album): # rutracker needs cookies to be set, pass the .torrent file instead of url if bestqual[3] == 'rutracker.org': - file_or_url, _hash = rutracker.get_torrent(bestqual[2]) + file_or_url, torrentid = rutracker.get_torrent(bestqual[2]) else: file_or_url = bestqual[2] @@ -698,19 +700,24 @@ def send_to_downloader(data, bestqual, album): except Exception as e: logger.exception("Unhandled exception") + # Set Seed Ratio + seed_ratio = getSeedRatio(bestqual[3]) + if seed_ratio != None: + transmission.setSeedRatio(torrentid, seed_ratio) + 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 if bestqual[3] == 'rutracker.org': - file_or_url, _hash = rutracker.get_torrent(bestqual[2]) - folder_name, cacheid = utorrent.dirTorrent(_hash) + file_or_url, torrentid = rutracker.get_torrent(bestqual[2]) + folder_name, cacheid = utorrent.dirTorrent(torrentid) folder_name = os.path.basename(os.path.normpath(folder_name)) - utorrent.labelTorrent(_hash) + utorrent.labelTorrent(torrentid) else: file_or_url = bestqual[2] - _hash = CalculateTorrentHash(file_or_url, data) - folder_name = utorrent.addTorrent(file_or_url, _hash) + torrentid = CalculateTorrentHash(file_or_url, data) + folder_name = utorrent.addTorrent(file_or_url, torrentid) if folder_name: logger.info('Torrent folder name: %s' % folder_name) @@ -725,10 +732,19 @@ def send_to_downloader(data, bestqual, album): except Exception as e: logger.exception("Unhandled exception") + # Set Seed Ratio + seed_ratio = getSeedRatio(bestqual[3]) + if seed_ratio != None: + utorrent.setSeedRatio(torrentid, seed_ratio) + myDB = db.DBConnection() myDB.action('UPDATE albums SET status = "Snatched" WHERE AlbumID=?', [album['AlbumID']]) myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Snatched", folder_name, kind]) + # Store the torrent id so we can check later if it's finished seeding and can be removed + if seed_ratio != None and seed_ratio != 0 and torrentid: + myDB.action('INSERT INTO snatched VALUES( ?, ?, ?, ?, DATETIME("NOW", "localtime"), ?, ?, ?)', [album['AlbumID'], bestqual[0], bestqual[1], bestqual[2], "Seed_Snatched", torrentid, kind]) + # notify artist = album[1] albumname = album[2] @@ -1351,3 +1367,27 @@ def CalculateTorrentHash(link, data): logger.debug('Torrent Hash: ' + str(tor_hash)) return tor_hash + +def getSeedRatio(provider): + seed_ratio = '' + if provider == 'rutracker.org': + seed_ratio = headphones.RUTRACKER_RATIO + elif provider == 'Kick Ass Torrents': + seed_ratio = headphones.KAT_RATIO + elif provider == 'What.cd': + seed_ratio = headphones.WHATCD_RATIO + elif provider == 'The Pirate Bay': + seed_ratio = headphones.PIRATEBAY_RATIO + elif provider == 'Waffles.fm': + seed_ratio = headphones.WAFFLES_RATIO + elif provider == 'Mininova': + seed_ratio = headphones.MININOVA_RATIO + if seed_ratio != '': + try: + seed_ratio_float = float(seed_ratio) + except: + seed_ratio_float = None + logger.warn('Could not get Seed Ratio for %s' % provider) + return seed_ratio_float + else: + return None \ No newline at end of file diff --git a/headphones/torrentfinished.py b/headphones/torrentfinished.py new file mode 100644 index 00000000..a86614e2 --- /dev/null +++ b/headphones/torrentfinished.py @@ -0,0 +1,40 @@ +# 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 . + +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(): + + 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]) diff --git a/headphones/transmission.py b/headphones/transmission.py index 17ad434e..427115ea 100644 --- a/headphones/transmission.py +++ b/headphones/transmission.py @@ -47,10 +47,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 +82,39 @@ 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) + + 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 + + return False + def torrentAction(method, arguments): host = headphones.TRANSMISSION_HOST diff --git a/headphones/utorrent.py b/headphones/utorrent.py index 914409f7..0856df33 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -132,6 +132,13 @@ class utorrentclient(object): 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) @@ -155,7 +162,27 @@ def labelTorrent(hash): if label: uTorrentClient.setprops(hash,'label',label) -def dirTorrent(hash, cacheid=None): +def removeTorrent(hash, remove_data = False): + uTorrentClient = utorrentclient() + status, torrentList = uTorrentClient.list() + torrents = torrentList['torrents'] + for torrent in torrents: + if torrent[0].lower() == hash and torrent[21] == 'Finished': + logger.info('%s has finished seeding, removing torrent and data' % torrent[2]) + uTorrentClient.remove(hash, remove_data) + return True + 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', -1.00) + +def dirTorrent(hash, cacheid=None, return_name=None): uTorrentClient = utorrentclient() @@ -174,7 +201,10 @@ def dirTorrent(hash, cacheid=None): for torrent in torrents: if (torrent[0].lower() == hash): - return torrent[26], cacheid + if not return_name: + return torrent[26], cacheid + else: + return torrent[2], cacheid return None, None @@ -184,6 +214,10 @@ def addTorrent(link, hash): # Get Active Directory from settings active_dir, completed_dir = getSettingsDirectories() + if not active_dir or not completed_dir: + logger.error('Could not get "Put new downloads in:" or "Move completed downloads to:" directories from uTorrent settings, please ensure they are set') + return None + uTorrentClient.add_url(link) # Get Torrent Folder Name @@ -197,8 +231,10 @@ def addTorrent(link, hash): time.sleep(6) torrent_folder, cacheid = dirTorrent(hash, cacheid) - if torrent_folder == active_dir: - return None + 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)) @@ -206,6 +242,11 @@ def addTorrent(link, hash): def getSettingsDirectories(): uTorrentClient = utorrentclient() settings = uTorrentClient.get_settings() - active = settings['dir_active_download'][2] - completed = settings['dir_completed_download'][2] + 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 + diff --git a/headphones/webserve.py b/headphones/webserve.py index 832016f3..7a8314c7 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -735,7 +735,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 @@ -901,13 +901,13 @@ class WebInterface(object): 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 @@ -971,6 +971,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, @@ -1008,18 +1009,24 @@ class WebInterface(object): "numberofseeders" : headphones.NUMBEROFSEEDERS, "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), @@ -1035,6 +1042,7 @@ class WebInterface(object): "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), @@ -1154,12 +1162,12 @@ class WebInterface(object): 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, + 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, 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_kat=0, kat_proxy_url=None, 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, + 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, djmix=0, mixtape_street=0, broadcast=0, interview=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, @@ -1195,6 +1203,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 @@ -1226,18 +1235,24 @@ class WebInterface(object): headphones.DOWNLOAD_TORRENT_DIR = download_torrent_dir 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 @@ -1250,6 +1265,7 @@ class WebInterface(object): 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