from base64 import b16encode, b32decode from bencode import bencode as benc, bdecode from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList from couchpotato.core.helpers.encoding import isInt, ss from couchpotato.core.helpers.variable import tryInt, tryFloat from couchpotato.core.logger import CPLog from datetime import timedelta from hashlib import sha1 from multipartpost import MultipartPostHandler import cookielib import httplib import json import os import re import stat import time import urllib import urllib2 log = CPLog(__name__) class uTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] utorrent_api = None def connect(self): # Load host from config and split out port. host = self.conf('host').split(':') if not isInt(host[1]): log.error('Config properties are not filled in correctly, port is missing.') return False self.utorrent_api = uTorrentAPI(host[0], port = host[1], username = self.conf('username'), password = self.conf('password')) return self.utorrent_api def download(self, data = None, movie = None, filedata = None): if not movie: movie = {} if not data: data = {} log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('protocol'))) if not self.connect(): return False settings = self.utorrent_api.get_settings() if not settings: return False #Fix settings in case they are not set for CPS compatibility new_settings = {} if not (settings.get('seed_prio_limitul') == 0 and settings['seed_prio_limitul_flag']): new_settings['seed_prio_limitul'] = 0 new_settings['seed_prio_limitul_flag'] = True log.info('Updated uTorrent settings to set a torrent to complete after it the seeding requirements are met.') if settings.get('bt.read_only_on_complete'): #This doesn't work as this option seems to be not available through the api. Mitigated with removeReadOnly function new_settings['bt.read_only_on_complete'] = False log.info('Updated uTorrent settings to not set the files to read only after completing.') if new_settings: self.utorrent_api.set_settings(new_settings) torrent_params = {} if self.conf('label'): torrent_params['label'] = self.conf('label') if not filedata and data.get('protocol') == 'torrent': log.error('Failed sending torrent, no data') return False if data.get('protocol') == 'torrent_magnet': torrent_hash = re.findall('urn:btih:([\w]{32,40})', data.get('url'))[0].upper() torrent_params['trackers'] = '%0D%0A%0D%0A'.join(self.torrent_trackers) else: info = bdecode(filedata)["info"] torrent_hash = sha1(benc(info)).hexdigest().upper() torrent_filename = self.createFileName(data, filedata, movie) if data.get('seed_ratio'): torrent_params['seed_override'] = 1 torrent_params['seed_ratio'] = tryInt(tryFloat(data['seed_ratio']) * 1000) if data.get('seed_time'): torrent_params['seed_override'] = 1 torrent_params['seed_time'] = tryInt(data['seed_time']) * 3600 # Convert base 32 to hex if len(torrent_hash) == 32: torrent_hash = b16encode(b32decode(torrent_hash)) # Send request to uTorrent if data.get('protocol') == 'torrent_magnet': self.utorrent_api.add_torrent_uri(torrent_filename, data.get('url')) else: self.utorrent_api.add_torrent_file(torrent_filename, filedata) # Change settings of added torrent self.utorrent_api.set_torrent(torrent_hash, torrent_params) if self.conf('paused', default = 0): self.utorrent_api.pause_torrent(torrent_hash) return self.downloadReturnId(torrent_hash) def getAllDownloadStatus(self): log.debug('Checking uTorrent download status.') if not self.connect(): return False release_downloads = ReleaseDownloadList(self) data = self.utorrent_api.get_status() if not data: log.error('Error getting data from uTorrent') return False queue = json.loads(data) if queue.get('error'): log.error('Error getting data from uTorrent: %s', queue.get('error')) return False if not queue.get('torrents'): log.debug('Nothing in queue') return False # Get torrents for torrent in queue['torrents']: #Get files of the torrent torrent_files = [] try: torrent_files = json.loads(self.utorrent_api.get_files(torrent[0])) torrent_files = [os.path.join(torrent[26], torrent_file[0]) for torrent_file in torrent_files['files'][1]] except: log.debug('Failed getting files from torrent: %s', torrent[2]) # torrent[21] = Paused | Downloading | Seeding | Finished status = 'busy' if 'Finished' in torrent[21]: status = 'completed' self.removeReadOnly(torrent_files) elif 'Seeding' in torrent[21]: status = 'seeding' self.removeReadOnly(torrent_files) release_downloads.append({ 'id': torrent[0], 'name': torrent[2], 'status': status, 'seed_ratio': float(torrent[7]) / 1000, 'original_status': torrent[1], 'timeleft': str(timedelta(seconds = torrent[10])), 'folder': ss(torrent[26]), 'files': ss('|'.join(torrent_files)) }) return release_downloads def pause(self, release_download, pause = True): if not self.connect(): return False return self.utorrent_api.pause_torrent(release_download['id'], pause) def removeFailed(self, release_download): log.info('%s failed downloading, deleting...', release_download['name']) if not self.connect(): return False return self.utorrent_api.remove_torrent(release_download['id'], remove_data = True) def processComplete(self, release_download, delete_files = False): log.debug('Requesting uTorrent to remove the torrent %s%s.', (release_download['name'], ' and cleanup the downloaded files' if delete_files else '')) if not self.connect(): return False return self.utorrent_api.remove_torrent(release_download['id'], remove_data = delete_files) def removeReadOnly(self, files): #Removes all read-on ly flags in a for all files for filepath in files: if os.path.isfile(filepath): #Windows only needs S_IWRITE, but we bitwise-or with current perms to preserve other permission bits on Linux os.chmod(filepath, stat.S_IWRITE | os.stat(filepath).st_mode) class uTorrentAPI(object): def __init__(self, host = 'localhost', port = 8000, username = None, password = None): super(uTorrentAPI, self).__init__() self.url = 'http://' + str(host) + ':' + str(port) + '/gui/' self.token = '' self.last_time = time.time() cookies = cookielib.CookieJar() self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies), MultipartPostHandler) self.opener.addheaders = [('User-agent', 'couchpotato-utorrent-client/1.0')] if username and password: password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() password_manager.add_password(realm = None, uri = self.url, user = username, passwd = password) self.opener.add_handler(urllib2.HTTPBasicAuthHandler(password_manager)) self.opener.add_handler(urllib2.HTTPDigestAuthHandler(password_manager)) elif username or password: log.debug('User or password missing, not using authentication.') self.token = self.get_token() def _request(self, action, data = None): if time.time() > self.last_time + 1800: self.last_time = time.time() self.token = self.get_token() request = urllib2.Request(self.url + "?token=" + self.token + "&" + action, data) try: open_request = self.opener.open(request) response = open_request.read() if response: return response else: log.debug('Unknown failure sending command to uTorrent. Return text is: %s', response) except httplib.InvalidURL, err: log.error('Invalid uTorrent host, check your config %s', err) except urllib2.HTTPError, err: if err.code == 401: log.error('Invalid uTorrent Username or Password, check your config') else: log.error('uTorrent HTTPError: %s', err) except urllib2.URLError, err: log.error('Unable to connect to uTorrent %s', err) return False def get_token(self): request = self.opener.open(self.url + "token.html") token = re.findall("(.*?)