From 61f634a21ed0c9429965d072258ae4a52a113817 Mon Sep 17 00:00:00 2001 From: Viktor Elofsson Date: Tue, 21 Oct 2014 16:52:28 +0200 Subject: [PATCH] Refactored Hadouken downloader. --- couchpotato/core/downloaders/hadouken.py | 121 +++++++++++++++++------ 1 file changed, 93 insertions(+), 28 deletions(-) diff --git a/couchpotato/core/downloaders/hadouken.py b/couchpotato/core/downloaders/hadouken.py index ac6eae12..b27a645a 100644 --- a/couchpotato/core/downloaders/hadouken.py +++ b/couchpotato/core/downloaders/hadouken.py @@ -4,7 +4,9 @@ from couchpotato.core.helpers.variable import tryInt, tryFloat, cleanHost from couchpotato.core.logger import CPLog from base64 import b16encode, b32decode, b64encode +from bencode import bencode as benc, bdecode from distutils.version import LooseVersion +from hashlib import sha1 import httplib import json import os @@ -32,7 +34,7 @@ class Hadouken(DownloaderBase): log.error('Config properties are not filled in correctly, API key is missing.') return False - self.hadouken_api = HadoukenAPI(host[0], port = host[1], apiKey = self.conf('apikey')) + self.hadouken_api = HadoukenAPI(host[0], port = host[1], api_key = self.conf('api_key')) return True @@ -57,6 +59,7 @@ class Hadouken(DownloaderBase): torrent_params['trackers'] = self.torrent_trackers torrent_params['name'] = torrent_filename else: + info = bdecode(filedata)['info'] torrent_hash = sha1(benc(info)).hexdigest().upper() # Convert base 32 to hex @@ -83,10 +86,11 @@ class Hadouken(DownloaderBase): log.error('Could not get Hadouken version.') return False - if LooseVersion(version) >= LooseVersion('4.4.1'): + # The minimum required version of Hadouken is 4.5.6. + if LooseVersion(version) >= LooseVersion('4.5.6'): return True - log.error('Hadouken v4.1.1 (or newer) required. Found v%s', version) + log.error('Hadouken v4.5.6 (or newer) required. Found v%s', version) return False def getAllDownloadStatus(self, ids): @@ -105,15 +109,27 @@ class Hadouken(DownloaderBase): if torrent is None: continue - torrent_dir = os.path.join(torrent['SavePath'], torrent['Name']) + torrent_filelist = self.hadouken_api.get_files_by_hash(torrent['InfoHash']) torrent_files = [] - status = 'busy' - for torrent_file in torrent['Files']: - torrent_files.append(sp(os.path.join(torrent['SavePath'], torrent_file['Path']))) + save_path = torrent['SavePath'] - if os.path.isdir(torrent_dir): - torrent['SavePath'] = torrent_dir + # The 'Path' key for each file_item contains + # the full path to the single file relative to the + # torrents save path. + + # For a single file torrent the result would be, + # - Save path: "C:\Downloads" + # - file_item['Path'] = "file1.iso" + # Resulting path: "C:\Downloads\file1.iso" + + # For a multi file torrent the result would be, + # - Save path: "C:\Downloads" + # - file_item['Path'] = "dirname/file1.iso" + # Resulting path: "C:\Downloads\dirname/file1.iso" + + for file_item in torrent_filelist: + torrent_files.append(sp(os.path.join(save_path, file_item['Path']))) release_downloads.append({ 'id': torrent['InfoHash'].upper(), @@ -122,7 +138,7 @@ class Hadouken(DownloaderBase): 'seed_ratio': self.get_seed_ratio(torrent), 'original_status': torrent['State'], 'timeleft': -1, - 'folder': sp(torrent['SavePath']), + 'folder': sp(save_path if len(torrent_files == 1) else os.path.join(save_path, torrent['Name'])), 'files': torrent_files }) @@ -203,82 +219,127 @@ class Hadouken(DownloaderBase): return self.hadouken_api.remove(release_download['id'], remove_data = delete_files) class HadoukenAPI(object): - def __init__(self, host = 'localhost', port = 7890, apiKey = None): + def __init__(self, host = 'localhost', port = 7890, api_key = None): self.url = 'http://' + str(host) + ':' + str(port) - self.apiKey = apiKey + self.api_key = api_key self.requestId = 0; self.opener = urllib2.build_opener() self.opener.addheaders = [('User-agent', 'couchpotato-hadouken-client/1.0'), ('Accept', 'application/json')] - if not apiKey: + if not api_key: log.error('API key missing.') def add_file(self, filedata, torrent_params): + """ Add a file to Hadouken with the specified parameters. + + Keyword arguments: + filedata -- The binary torrent data. + torrent_params -- Additional parameters for the file. + """ data = { 'method': 'torrents.addFile', 'params': [ b64encode(filedata), torrent_params ] } - return self._request('/jsonrpc', data) + return self._request(data) def add_magnet_link(self, magnetLink, torrent_params): + """ Add a magnet link to Hadouken with the specified parameters. + + Keyword arguments: + magnetLink -- The magnet link to send. + torrent_params -- Additional parameters for the magnet link. + """ data = { 'method': 'torrents.addUrl', 'params': [ magnetLink, torrent_params ] } - return self._request('/jsonrpc', data) + return self._request(data) def get_by_hash_list(self, infoHashList): + """ Gets a list of torrents filtered by the given info hash list. + + Keyword arguments: + infoHashList -- A list of info hashes. + """ data = { 'method': 'torrents.getByInfoHashList', 'params': [ infoHashList ] } - return self._request('/jsonrpc', data) + return self._request(data) + + def get_files_by_hash(self, infoHash): + """ Gets a list of files for the torrent identified by the + given info hash. + + Keyword arguments: + infoHash -- The info hash of the torrent to return files for. + """ + data = { + 'method': 'torrents.getFiles', + 'params': [ infoHash ] + } + + return self._request(data) def get_version(self): + """ Gets the version, commitish and build date of Hadouken. """ data = { 'method': 'core.getVersion', 'params': None } - result = self._request('/jsonrpc', data) + result = self._request(data) if not result: return False return result['Version'] - def pause(self, id, pause): + def pause(self, infoHash, pause): + """ Pauses/unpauses the torrent identified by the given info hash. + + Keyword arguments: + infoHash -- The info hash of the torrent to operate on. + pause -- If true, pauses the torrent. Otherwise resumes. + """ data = { 'method': 'torrents.pause', - 'params': [ id ] + 'params': [ infoHash ] } if not pause: data['method'] = 'torrents.resume' - return self._request('/jsonrpc', data) + return self._request(data) - def remove(self, id, remove_data = False): + def remove(self, infoHash, remove_data = False): + """ Removes the torrent identified by the given info hash and + optionally removes the data as well. + + Keyword arguments: + infoHash -- The info hash of the torrent to remove. + remove_data -- If true, removes the data associated with the torrent. + """ data = { 'method': 'torrents.remove', - 'params': [ id, remove_data ] + 'params': [ infoHash, remove_data ] } - return self._request('/jsonrpc', data) + return self._request(data) - def _request(self, url, data): + def _request(self, data): self.requestId += 1 data['jsonrpc'] = '2.0' data['id'] = self.requestId - request = urllib2.Request(self.url + url, data = json.dumps(data)) - request.add_header('Authorization', 'Token ' + self.apiKey) + request = urllib2.Request(self.url + '/jsonrpc', data = json.dumps(data)) + request.add_header('Authorization', 'Token ' + self.api_key) request.add_header('Content-Type', 'application/json') try: @@ -313,7 +374,7 @@ config = [{ 'list': 'download_providers', 'name': 'hadouken', 'label': 'Hadouken', - 'description': 'Use Hadouken (>= v4.4.1) to download torrents.', + 'description': 'Use Hadouken (>= v4.5.6) to download torrents.', 'wizard': True, 'options': [ { @@ -327,9 +388,13 @@ config = [{ 'default': 'localhost:7890' }, { - 'name': 'apikey', + 'name': 'api_key', 'label': 'API key', 'type': 'password' + }, + { + 'name': 'label', + 'description': 'Label to add torrent as.' } ] }