From 23e3dd91b3f3337c424fffdf538d5a7bb578b6ea Mon Sep 17 00:00:00 2001 From: Begall Date: Mon, 2 Jun 2014 21:22:31 +0100 Subject: [PATCH 01/12] Add ability to rename .nfo file to .nfo-orig --- data/interfaces/default/config.html | 1 + headphones/__init__.py | 5 ++++- headphones/postprocessor.py | 23 +++++++++++++++++++---- headphones/webserve.py | 4 +++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 5ee7a95b..f832b35d 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -525,6 +525,7 @@ +
diff --git a/headphones/__init__.py b/headphones/__init__.py index 82e2a5d6..a84dc54a 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 @@ -347,7 +348,7 @@ 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, ISOHUNT, KAT, PIRATEBAY, PIRATEBAY_PROXY_URL, MININOVA, WAFFLES, WAFFLES_UID, WAFFLES_PASSKEY, \ RUTRACKER, RUTRACKER_USER, RUTRACKER_PASSWORD, WHATCD, WHATCD_USERNAME, WHATCD_PASSWORD, DOWNLOAD_TORRENT_DIR, \ @@ -453,6 +454,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)) @@ -882,6 +884,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) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index e8f18d28..5ca5509d 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -388,6 +388,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) @@ -557,11 +560,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): diff --git a/headphones/webserve.py b/headphones/webserve.py index 3a7c6f16..f923fe88 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -1014,6 +1014,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), @@ -1138,7 +1139,7 @@ class WebInterface(object): use_headphones_indexer=0, newznab=0, newznab_host=None, newznab_apikey=None, newznab_enabled=0, nzbsorg=0, nzbsorg_uid=None, nzbsorg_hash=None, nzbsrus=0, nzbsrus_uid=None, nzbsrus_apikey=None, omgwtfnzbs=0, omgwtfnzbs_uid=None, omgwtfnzbs_apikey=None, preferred_words=None, required_words=None, ignored_words=None, preferred_quality=0, preferred_bitrate=None, detect_bitrate=0, move_files=0, torrentblackhole_dir=None, download_torrent_dir=None, numberofseeders=None, use_piratebay=0, piratebay_proxy_url=None, use_isohunt=0, use_kat=0, use_mininova=0, waffles=0, waffles_uid=None, waffles_passkey=None, whatcd=0, whatcd_username=None, whatcd_password=None, - rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, replace_existing_folders=False, + rutracker=0, rutracker_user=None, rutracker_password=None, rename_files=0, correct_metadata=0, cleanup_files=0, keep_nfo=0, add_album_art=0, album_art_format=None, embed_album_art=0, embed_lyrics=0, replace_existing_folders=False, destination_dir=None, lossless_destination_dir=None, folder_format=None, file_format=None, file_underscores=0, include_extras=0, single=0, ep=0, compilation=0, soundtrack=0, live=0, remix=0, spokenword=0, audiobook=0, other=0, 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, @@ -1232,6 +1233,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 From ba552bd1f86c43dbbc6f92342278aa0d55d7799a Mon Sep 17 00:00:00 2001 From: Ade Date: Fri, 8 Aug 2014 22:23:06 +1200 Subject: [PATCH 02/12] Torrent seed ratio --- data/interfaces/default/config.html | 156 ++++++++++++++++++---------- headphones/__init__.py | 55 +++++++--- headphones/searcher.py | 34 ++++++ headphones/transmission.py | 11 ++ headphones/utorrent.py | 34 ++++-- headphones/webserve.py | 16 ++- 6 files changed, 234 insertions(+), 72 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index a05578f0..1b25722a 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -382,66 +382,117 @@
Torrents -
- -
-
-
- - + +
+ The Pirate Bay +
+
-
-
- -
-
- -
-
-
- - +
+
+ + +
+
+ + +
-
-
- -
-
-
- - +
+ +
+ Kick Ass Torrents +
+
-
- - +
+
+ + +
+
+ + +
-
-
- -
-
-
- - +
+ +
+ Waffles.fm +
+
-
- - +
+
+ + +
+
+ + +
+
+ + +
-
-
- -
-
-
- - +
+ +
+ rutracker.org +
+
-
- - +
+
+ + +
+
+ + +
+
+ + +
-
+
+ +
+ What.cd +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ Mininova +
+ +
+
+
+ + +
+
+
+ @@ -1745,6 +1796,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 e80ffb7b..b73df6a6 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -190,18 +190,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 @@ -346,8 +352,8 @@ def initialize(): 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, 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, \ UTORRENT_HOST, UTORRENT_USERNAME, UTORRENT_PASSWORD, UTORRENT_LABEL, NEWZNAB, NEWZNAB_HOST, NEWZNAB_APIKEY, NEWZNAB_ENABLED, EXTRA_NEWZNABS, \ @@ -378,6 +384,9 @@ def initialize(): CheckSection('Newznab') CheckSection('NZBsorg') CheckSection('omgwtfnzbs') + CheckSection('Piratebay') + CheckSection('Kat') + CheckSection('Mininova') CheckSection('Waffles') CheckSection('Rutracker') CheckSection('What.cd') @@ -478,24 +487,34 @@ 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', '') + WHATCD_PASSWORD = check_setting_str(CFG, 'What.cd', 'whatcd_password', '') SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '') SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '') @@ -895,27 +914,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) diff --git a/headphones/searcher.py b/headphones/searcher.py index ef889d5c..06293f08 100644 --- a/headphones/searcher.py +++ b/headphones/searcher.py @@ -698,6 +698,11 @@ 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") @@ -725,6 +730,11 @@ 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(_hash, 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]) @@ -1351,3 +1361,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/transmission.py b/headphones/transmission.py index 17ad434e..13a24de4 100644 --- a/headphones/transmission.py +++ b/headphones/transmission.py @@ -82,6 +82,17 @@ 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 torrentAction(method, arguments): host = headphones.TRANSMISSION_HOST diff --git a/headphones/utorrent.py b/headphones/utorrent.py index 914409f7..b98456c0 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -155,7 +155,16 @@ def labelTorrent(hash): if label: uTorrentClient.setprops(hash,'label',label) -def dirTorrent(hash, cacheid=None): +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 +183,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 +196,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 +213,9 @@ 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) + return torrent_folder else: labelTorrent(hash) return os.path.basename(os.path.normpath(torrent_folder)) @@ -206,6 +223,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 af8198b6..fa1a9c54 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -1008,18 +1008,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), @@ -1159,8 +1165,8 @@ class WebInterface(object): 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, keep_nfo=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, @@ -1227,18 +1233,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 From e224ad1a6a010a90ede84f24f9c2217d95c71988 Mon Sep 17 00:00:00 2001 From: Ade Date: Mon, 11 Aug 2014 20:32:37 +1200 Subject: [PATCH 03/12] Torrent seed ratio part 2 Set scheduled job to remove torrent when post processed and finished seeding --- data/interfaces/default/config.html | 12 ++++----- headphones/__init__.py | 6 +++-- headphones/api.py | 2 +- headphones/postprocessor.py | 23 ++++++++++++++--- headphones/searcher.py | 20 ++++++++++----- headphones/torrentfinished.py | 40 +++++++++++++++++++++++++++++ headphones/transmission.py | 26 +++++++++++++++++-- headphones/utorrent.py | 19 ++++++++++++++ headphones/webserve.py | 6 ++--- 9 files changed, 130 insertions(+), 24 deletions(-) create mode 100644 headphones/torrentfinished.py diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 1b25722a..81a584da 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -395,7 +395,7 @@
- +
@@ -412,7 +412,7 @@
- +
@@ -433,7 +433,7 @@
- +
@@ -454,7 +454,7 @@
- +
@@ -475,7 +475,7 @@
- +
@@ -488,7 +488,7 @@
- +
diff --git a/headphones/__init__.py b/headphones/__init__.py index b73df6a6..0560a9fa 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -514,7 +514,6 @@ def initialize(): 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', '') - WHATCD_PASSWORD = check_setting_str(CFG, 'What.cd', 'whatcd_password', '') SAB_HOST = check_setting_str(CFG, 'SABnzbd', 'sab_host', '') SAB_USERNAME = check_setting_str(CFG, 'SABnzbd', 'sab_username', '') @@ -1135,7 +1134,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) @@ -1147,6 +1146,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/postprocessor.py b/headphones/postprocessor.py index a89eee68..8e378551 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) @@ -416,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 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: diff --git a/headphones/searcher.py b/headphones/searcher.py index 06293f08..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] @@ -708,14 +710,14 @@ 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]) - 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) @@ -733,12 +735,16 @@ def send_to_downloader(data, bestqual, album): # Set Seed Ratio seed_ratio = getSeedRatio(bestqual[3]) if seed_ratio != None: - utorrent.setSeedRatio(_hash, seed_ratio) + 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] 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 13a24de4..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 @@ -93,6 +93,28 @@ def setSeedRatio(torrentid, ratio): 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 b98456c0..1c1e9bb1 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,6 +162,17 @@ def labelTorrent(hash): if label: uTorrentClient.setprops(hash,'label',label) +def removeTorrent(hash): + 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, True) + return True + return False + def setSeedRatio(hash, ratio): uTorrentClient = utorrentclient() uTorrentClient.setprops(hash, 'seed_override', '1') @@ -215,6 +233,7 @@ def addTorrent(link, hash): 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) diff --git a/headphones/webserve.py b/headphones/webserve.py index fa1a9c54..401e5921 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 From 49179170fae1f85e7456f194b02b4e18927616c0 Mon Sep 17 00:00:00 2001 From: piejanssens Date: Mon, 11 Aug 2014 20:09:19 +0200 Subject: [PATCH 04/12] Add NZBget priority option (including force) This feature allows the user to select a priority for the nzb that is sent to nzbget. Force will download the task even though the queue is paused. --- data/interfaces/default/config.html | 62 ++++++++++++++++++++++++++ headphones/__init__.py | 5 ++- headphones/nzbget.py | 67 ++++++++++++++++++++++------- headphones/webserve.py | 4 +- 4 files changed, 120 insertions(+), 18 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 81a584da..37c6a4ef 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 = '' + %> +
+ + +
diff --git a/headphones/__init__.py b/headphones/__init__.py index 0560a9fa..8248799e 100644 --- a/headphones/__init__.py +++ b/headphones/__init__.py @@ -148,6 +148,7 @@ NZBGET_USERNAME = None NZBGET_PASSWORD = None NZBGET_CATEGORY = None NZBGET_HOST = None +NZBGET_PRIORITY = 0 HEADPHONES_INDEXER = False @@ -355,7 +356,7 @@ def initialize(): 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, \ @@ -525,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)) @@ -966,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) 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/webserve.py b/headphones/webserve.py index 401e5921..7a8314c7 100644 --- a/headphones/webserve.py +++ b/headphones/webserve.py @@ -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, @@ -1161,7 +1162,7 @@ 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, @@ -1202,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 From e04245cff5e24ef05274d197bf80707a419e8254 Mon Sep 17 00:00:00 2001 From: piejanssens Date: Mon, 11 Aug 2014 20:23:00 +0200 Subject: [PATCH 05/12] Add (re)search to wanted album on artist page If the snatch got lost, you can click search instead of skip and then search. Replaces #1261 --- data/interfaces/default/artist.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b36178d6d40a72c87f437174880c01367d17d9f0 Mon Sep 17 00:00:00 2001 From: piejanssens Date: Mon, 11 Aug 2014 20:37:09 +0200 Subject: [PATCH 06/12] Add $OriginalFolder flag to renamer (thx Begall) Add $OriginalFolder flag to renamer #1699 --- data/interfaces/default/config.html | 2 +- headphones/postprocessor.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index 81a584da..960a19f8 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -886,7 +886,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]
diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index 8e378551..f9be079f 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -622,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) From e7ddb2d7b287b5db6f0391bcf9fc4e59a6f67a5f Mon Sep 17 00:00:00 2001 From: piejanssens Date: Mon, 11 Aug 2014 20:47:57 +0200 Subject: [PATCH 07/12] Resolved conflicts for Begall's db fixes --- headphones/db.py | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) 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 From 6891252cf631f8d87f276e117f090894aaa36fa1 Mon Sep 17 00:00:00 2001 From: Ade Date: Wed, 13 Aug 2014 18:14:02 +1200 Subject: [PATCH 08/12] utorrent fix up --- headphones/postprocessor.py | 2 +- headphones/utorrent.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/headphones/postprocessor.py b/headphones/postprocessor.py index f9be079f..c80fc267 100644 --- a/headphones/postprocessor.py +++ b/headphones/postprocessor.py @@ -419,7 +419,7 @@ def doPostProcessing(albumid, albumpath, release, tracks, downloaded_track_list, 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 2): + 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'] diff --git a/headphones/utorrent.py b/headphones/utorrent.py index 1c1e9bb1..0856df33 100644 --- a/headphones/utorrent.py +++ b/headphones/utorrent.py @@ -162,14 +162,14 @@ def labelTorrent(hash): if label: uTorrentClient.setprops(hash,'label',label) -def removeTorrent(hash): +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, True) + uTorrentClient.remove(hash, remove_data) return True return False From e2f0354deb8542835f33df0ac4a08b7b7249843d Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Wed, 13 Aug 2014 18:20:29 +0000 Subject: [PATCH 09/12] Raised Last.FM timeout to 60 seconds --- headphones/importer.py | 3 ++- headphones/lastfm.py | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/headphones/importer.py b/headphones/importer.py index a94bf35c..f8008b1a 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): 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) From 922cca96526e5cc90b649422a8825f0962675c04 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Wed, 13 Aug 2014 18:21:51 +0000 Subject: [PATCH 10/12] Constants should be in caps --- headphones/cache.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 From 9178740c265f4692448c0680ad4c2842ab51ae84 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Wed, 13 Aug 2014 18:23:55 +0000 Subject: [PATCH 11/12] Added license info to requests.py --- headphones/request.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 From 8c9b834bc4f34737620c8d3303b69b4d6955d836 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Wed, 13 Aug 2014 18:31:08 +0000 Subject: [PATCH 12/12] Check for total number of tracks before continuing. Prevents ZeroDivisonError's --- headphones/importer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/headphones/importer.py b/headphones/importer.py index f8008b1a..4bb3f9a7 100644 --- a/headphones/importer.py +++ b/headphones/importer.py @@ -432,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'],