From d4db8f903e9a0543f3b5c0264635abc502e4430b Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Sun, 1 Jul 2012 01:51:09 +0200 Subject: [PATCH 01/16] Add gitignore for compiled files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bd4bca0a..e134ddb6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -/_source/ +*.pyc /data/ +/_source/ \ No newline at end of file From 9befc5afdd74d584e818f95ceeaefce7c835e612 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Sun, 1 Jul 2012 01:51:36 +0200 Subject: [PATCH 02/16] PublicHD torrent site --- .../providers/torrent/publichd/__init__.py | 23 ++++ .../core/providers/torrent/publichd/main.py | 129 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 couchpotato/core/providers/torrent/publichd/__init__.py create mode 100644 couchpotato/core/providers/torrent/publichd/main.py diff --git a/couchpotato/core/providers/torrent/publichd/__init__.py b/couchpotato/core/providers/torrent/publichd/__init__.py new file mode 100644 index 00000000..c28781e3 --- /dev/null +++ b/couchpotato/core/providers/torrent/publichd/__init__.py @@ -0,0 +1,23 @@ +from .main import PublicHD + +def start(): + return PublicHD() + +config = [{ + 'name': 'publichd', + 'groups': [ + { + 'tab': 'searcher', + 'subtab': 'providers', + 'name': 'PublicHD', + 'description': 'Public Torrent site with only HD content.', + 'options': [ + { + 'name': 'enabled', + 'type': 'enabler', + 'default': False, + }, + ], + }, + ], +}] \ No newline at end of file diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py new file mode 100644 index 00000000..04b1fb8f --- /dev/null +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from bs4 import BeautifulSoup +from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.variable import getTitle +from couchpotato.core.logger import CPLog +from couchpotato.core.providers.torrent.base import TorrentProvider +import re +from urlparse import parse_qs +from urllib import quote_plus +import urllib2 +log = CPLog(__name__) + + +class PublicHD(TorrentProvider): + + urls = { + 'test': 'http://publichd.eu', + 'download': 'http://publichd.eu/%s', + 'detail': 'http://publichd.eu/index.php?page=torrent-details&id=%s', + 'search': 'http://publichd.eu/index.php?page=torrents&search=%s&active=1&category=%d', + } + + cat_ids = [([2], ['720p']), ([5], ['1080p']), ([15], ['bdrip']), + ([16], ['brrip']), ([16], ['blue-ray'])] + + def search(self, movie, quality): + + results = [] + if self.isDisabled(): + return results + + movie_name = re.sub("\W", " ", getTitle(movie['library'])) + movie_name = re.sub(" ", " ", movie_name) + log.info('Cleaned Name: %s', movie_name) + cache_key = 'publichd.%s.%s' % (movie['library']['identifier'], quality.get('identifier')) + searchUrl = self.urls['search'] \ + % (quote_plus(movie_name + ' ' + + quality['identifier']), + self.getCatId(quality['identifier'])[0]) + + data = self.getCache(cache_key, searchUrl) + + try: + soup = BeautifulSoup(data) + + resultsTable = soup.find('table', attrs={'id': 'bgtorrlist2' + }) + entries = resultsTable.findAll('tr') + for result in entries[1:]: + info_url = result.find(href=re.compile('torrent-details' + )) + download = result.find(href=re.compile('\.torrent')) + date_uploaded = result.findAll('td')[3].string + if info_url and download and date_uploaded: + new = { + 'type': 'torrent', + 'check_nzb': False, + 'description': '', + 'provider': self.getName(), + } + log.info('Name: %s', result.findAll('td')[1].string) + log.info('Date uploaded: %s', date_uploaded) + log.info('Seeds: %s', result.findAll('td' + )[4].string) + log.info('Leaches: %s', result.findAll('td' + )[5].string) + log.info('Size: %s', result.findAll('td')[7].string) + + url = parse_qs(info_url['href']) + + new['name'] = info_url.string + new['id'] = url['id'][0] + new['url'] = self.urls['download'] % download['href' + ] + new['size'] = self.parseSize(result.findAll('td' + )[7].string) + new['seeders'] = int(result.findAll('td')[4].string) + new['leechers'] = int(result.findAll('td' + )[5].string) + new['imdbid'] = movie['library']['identifier'] + + new['extra_score'] = self.extra_score + new['score'] = fireEvent('score.calculate', new, + movie, single=True) + is_correct_movie = fireEvent( + 'searcher.correct_movie', + nzb=new, + movie=movie, + quality=quality, + imdb_results=True, + single_category=False, + single=True, + ) + + if is_correct_movie: + new['download'] = self.download + results.append(new) + self.found(new) + return results + except: + + log.info('No results found at PublicHD') + return [] + + def extra_score(self, req): + url = self.urls['detail'] % req['id'] + imdbId = req['imdbid'] + return self.imdbMatch(url, imdbId) + + def imdbMatch(self, url, imdbId): + try: + data = urllib2.urlopen(url).read() + pass + except IOError: + log.error('Failed to open %s.' % url) + return '' + + imdbIdAlt = re.sub('tt[0]*', 'tt', imdbId) + data = unicode(data, errors='ignore') + if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ + + imdbIdAlt in data: + return 50 + return 0 + + def download(self, url='', nzb_id=''): + torrent = self.urlopen(url) + return torrent From c6c754f17959f488abfd523bc2dc1568e0c3a13c Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Sun, 1 Jul 2012 02:38:54 +0200 Subject: [PATCH 03/16] Cache for detail page, more logging exception handling and cleaning the search string --- .../core/providers/torrent/publichd/main.py | 114 ++++++++++-------- 1 file changed, 61 insertions(+), 53 deletions(-) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index 04b1fb8f..ea3a1b65 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -25,6 +25,8 @@ class PublicHD(TorrentProvider): cat_ids = [([2], ['720p']), ([5], ['1080p']), ([15], ['bdrip']), ([16], ['brrip']), ([16], ['blue-ray'])] + cat_backup_id = 0 + def search(self, movie, quality): results = [] @@ -39,79 +41,84 @@ class PublicHD(TorrentProvider): % (quote_plus(movie_name + ' ' + quality['identifier']), self.getCatId(quality['identifier'])[0]) - + log.info('searchUrl: %s', searchUrl) data = self.getCache(cache_key, searchUrl) - try: + if data: soup = BeautifulSoup(data) resultsTable = soup.find('table', attrs={'id': 'bgtorrlist2' }) entries = resultsTable.findAll('tr') - for result in entries[1:]: - info_url = result.find(href=re.compile('torrent-details' - )) - download = result.find(href=re.compile('\.torrent')) - date_uploaded = result.findAll('td')[3].string - if info_url and download and date_uploaded: - new = { - 'type': 'torrent', - 'check_nzb': False, - 'description': '', - 'provider': self.getName(), - } - log.info('Name: %s', result.findAll('td')[1].string) - log.info('Date uploaded: %s', date_uploaded) - log.info('Seeds: %s', result.findAll('td' - )[4].string) - log.info('Leaches: %s', result.findAll('td' - )[5].string) - log.info('Size: %s', result.findAll('td')[7].string) + for result in entries[2:len(entries) - 1]: + try: - url = parse_qs(info_url['href']) + info_url = result.find(href=re.compile('torrent-details' + )) + download = result.find(href=re.compile('\.torrent')) - new['name'] = info_url.string - new['id'] = url['id'][0] - new['url'] = self.urls['download'] % download['href' - ] - new['size'] = self.parseSize(result.findAll('td' - )[7].string) - new['seeders'] = int(result.findAll('td')[4].string) - new['leechers'] = int(result.findAll('td' - )[5].string) - new['imdbid'] = movie['library']['identifier'] + if info_url and download: + new = { + 'type': 'torrent', + 'check_nzb': False, + 'description': '', + 'provider': self.getName(), + } + log.info('Name: %s', result.findAll('td')[1].string) + log.info('Seeds: %s', result.findAll('td' + )[4].string) + log.info('Leaches: %s', result.findAll('td' + )[5].string) + log.info('Size: %s', result.findAll('td')[7].string) - new['extra_score'] = self.extra_score - new['score'] = fireEvent('score.calculate', new, - movie, single=True) - is_correct_movie = fireEvent( - 'searcher.correct_movie', - nzb=new, - movie=movie, - quality=quality, - imdb_results=True, - single_category=False, - single=True, - ) + url = parse_qs(info_url['href']) + + new['name'] = info_url.string + new['id'] = url['id'][0] + new['url'] = self.urls['download'] % download['href' + ] + new['size'] = self.parseSize(result.findAll('td' + )[7].string) + new['seeders'] = int(result.findAll('td')[4].string) + new['leechers'] = int(result.findAll('td' + )[5].string) + new['imdbid'] = movie['library']['identifier'] + + new['extra_score'] = self.extra_score + new['score'] = fireEvent('score.calculate', new, + movie, single=True) + is_correct_movie = fireEvent( + 'searcher.correct_movie', + nzb=new, + movie=movie, + quality=quality, + imdb_results=True, + single_category=False, + single=True, + ) + + if is_correct_movie: + new['download'] = self.download + results.append(new) + self.found(new) + except Exception, e: + log.debug(e) + log.info("Eroro occured during parsing! Passing only processed entries") + return results - if is_correct_movie: - new['download'] = self.download - results.append(new) - self.found(new) return results - except: - log.info('No results found at PublicHD') - return [] def extra_score(self, req): url = self.urls['detail'] % req['id'] + log.info('extra_score: %s', url) imdbId = req['imdbid'] return self.imdbMatch(url, imdbId) def imdbMatch(self, url, imdbId): + log.info('imdbMatch: %s', url) try: - data = urllib2.urlopen(url).read() + data = self.getCache(url, url) pass except IOError: log.error('Failed to open %s.' % url) @@ -121,9 +128,10 @@ class PublicHD(TorrentProvider): data = unicode(data, errors='ignore') if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ + imdbIdAlt in data: - return 50 + return 500 return 0 def download(self, url='', nzb_id=''): + log.info('Downloading: %s', url) torrent = self.urlopen(url) return torrent From 7b53c4cde116778a5e24d47adaa7a89915653a34 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Mon, 2 Jul 2012 00:25:22 +0200 Subject: [PATCH 04/16] Using urllib2 for details --- .../core/providers/torrent/publichd/main.py | 128 +++++++++--------- 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index ea3a1b65..129832b0 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -33,92 +33,94 @@ class PublicHD(TorrentProvider): if self.isDisabled(): return results - movie_name = re.sub("\W", " ", getTitle(movie['library'])) - movie_name = re.sub(" ", " ", movie_name) + movie_name = re.sub("\W", ' ', getTitle(movie['library'])) + movie_name = re.sub(' ', ' ', movie_name) log.info('Cleaned Name: %s', movie_name) - cache_key = 'publichd.%s.%s' % (movie['library']['identifier'], quality.get('identifier')) - searchUrl = self.urls['search'] \ - % (quote_plus(movie_name + ' ' - + quality['identifier']), - self.getCatId(quality['identifier'])[0]) + cache_key = 'publichd.%s.%s' % (movie['library']['identifier'], + quality.get('identifier')) + searchUrl = self.urls['search'] % (quote_plus(movie_name + ' ' + + quality['identifier']), + self.getCatId(quality['identifier'])[0]) log.info('searchUrl: %s', searchUrl) data = self.getCache(cache_key, searchUrl) + if not data: + log.error('Failed to get data from %s.', searchUrl) + return results - if data: + print data + try: soup = BeautifulSoup(data) resultsTable = soup.find('table', attrs={'id': 'bgtorrlist2' }) entries = resultsTable.findAll('tr') for result in entries[2:len(entries) - 1]: - try: + info_url = result.find(href=re.compile('torrent-details' + )) + download = result.find(href=re.compile('\.torrent')) - info_url = result.find(href=re.compile('torrent-details' - )) - download = result.find(href=re.compile('\.torrent')) + if info_url and download: + new = { + 'type': 'torrent', + 'check_nzb': False, + 'description': '', + 'provider': self.getName(), + } + log.info('Name: %s', result.findAll('td')[1].string) + log.info('Seeders: %s', result.findAll('td' + )[4].string) + log.info('Leaches: %s', result.findAll('td' + )[5].string) + log.info('Size: %s', result.findAll('td')[7].string) - if info_url and download: - new = { - 'type': 'torrent', - 'check_nzb': False, - 'description': '', - 'provider': self.getName(), - } - log.info('Name: %s', result.findAll('td')[1].string) - log.info('Seeds: %s', result.findAll('td' - )[4].string) - log.info('Leaches: %s', result.findAll('td' - )[5].string) - log.info('Size: %s', result.findAll('td')[7].string) + url = parse_qs(info_url['href']) - url = parse_qs(info_url['href']) + new['name'] = info_url.string + new['id'] = url['id'][0] + new['url'] = self.urls['download'] % download['href' + ] + new['size'] = self.parseSize(result.findAll('td' + )[7].string) + new['seeders'] = int(result.findAll('td')[4].string) + new['Leechers'] = int(result.findAll('td' + )[5].string) + new['imdbid'] = movie['library']['identifier'] - new['name'] = info_url.string - new['id'] = url['id'][0] - new['url'] = self.urls['download'] % download['href' - ] - new['size'] = self.parseSize(result.findAll('td' - )[7].string) - new['seeders'] = int(result.findAll('td')[4].string) - new['leechers'] = int(result.findAll('td' - )[5].string) - new['imdbid'] = movie['library']['identifier'] + new['extra_score'] = self.extra_score + new['score'] = fireEvent('score.calculate', new, + movie, single=True) + is_correct_movie = fireEvent( + 'searcher.correct_movie', + nzb=new, + movie=movie, + quality=quality, + imdb_results=True, + single_category=False, + single=True, + ) - new['extra_score'] = self.extra_score - new['score'] = fireEvent('score.calculate', new, - movie, single=True) - is_correct_movie = fireEvent( - 'searcher.correct_movie', - nzb=new, - movie=movie, - quality=quality, - imdb_results=True, - single_category=False, - single=True, - ) - - if is_correct_movie: - new['download'] = self.download - results.append(new) - self.found(new) - except Exception, e: - log.debug(e) - log.info("Eroro occured during parsing! Passing only processed entries") - return results + if is_correct_movie: + new['download'] = self.download + results.append(new) + self.found(new) return results + except Exception, e: + log.debug(e) + log.info('Error occured during parsing! Passing only processed entries' + ) + return results - - def extra_score(self, req): - url = self.urls['detail'] % req['id'] + def extra_score(self, torrent): + url = self.urls['detail'] % torrent['id'] log.info('extra_score: %s', url) - imdbId = req['imdbid'] + imdbId = torrent['imdbid'] return self.imdbMatch(url, imdbId) def imdbMatch(self, url, imdbId): log.info('imdbMatch: %s', url) try: - data = self.getCache(url, url) + data = urllib2.urlopen(url).read() pass except IOError: log.error('Failed to open %s.' % url) @@ -128,7 +130,7 @@ class PublicHD(TorrentProvider): data = unicode(data, errors='ignore') if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ + imdbIdAlt in data: - return 500 + return 50 return 0 def download(self, url='', nzb_id=''): From 7e7f319609dc2cda54279484401df6dab24d5028 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Mon, 2 Jul 2012 00:44:53 +0200 Subject: [PATCH 05/16] Piratebay with multiple domains --- .../torrent/thepiratebay/__init__.py | 36 ++- .../providers/torrent/thepiratebay/main.py | 235 +++++++++--------- 2 files changed, 150 insertions(+), 121 deletions(-) diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py index 810e713a..9c132dd7 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py +++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py @@ -1,6 +1,38 @@ -from .main import ThePirateBay +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from main import ThePirateBay + def start(): return ThePirateBay() -config = [] + +config = [{'name': 'ThePirateBay', 'groups': [{ + 'tab': 'searcher', + 'subtab': 'providers', + 'name': 'ThePirateBay', + 'description': 'The world\'s largest bittorrent tracker.', + 'options': [{'name': 'enabled', 'type': 'enabler', + 'default': False}, { + 'name': 'domain_for_tpb', + 'label': 'Proxy server', + 'default': 'http://thepiratebay.se', + 'description': 'Which domain do you preferr(or it\'s not blocked)', + 'type': 'dropdown', + 'values': [ + ('(Sweden) thepiratebay.se', 'http://thepiratebay.se'), + ('(Sweden) tpb.ipredator.se (ssl)', + 'https://tpb.ipredator.se'), + ('(Germany) depiraatbaai.be', 'http://depiraatbaai.be'), + ('(UK) piratereverse.info (ssl)', + 'https://piratereverse.info'), + ('(UK) tpb.pirateparty.org.uk (ssl)', + 'https://tpb.pirateparty.org.uk'), + ('(Netherlands) thepiratebay.se.coevoet.nl', + 'http://thepiratebay.se.coevoet.nl'), + ('(direct) 194.71.107.80', 'http://194.71.107.80'), + ('(direct) 194.71.107.83', 'http://194.71.107.81'), + ], + }], + }]}] diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index 3664ffcd..626e1210 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -1,146 +1,143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from bs4 import BeautifulSoup +from couchpotato.core.event import fireEvent +from couchpotato.core.helpers.variable import getTitle from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider - +import re +from urllib import quote_plus +import urllib2 log = CPLog(__name__) class ThePirateBay(TorrentProvider): - urls = { - 'download': 'http://torrents.depiraatbaai.be/%s/%s.torrent', - 'nfo': 'https://depiraatbaai.be/torrent/%s', - 'detail': 'https://depiraatbaai.be/torrent/%s', - 'search': 'https://depiraatbaai.be/search/%s/0/7/%d', - } + urls = {'detail': '%s/torrent/%s', 'search': '%s/search/%s/0/7/%d'} - cat_ids = [ - ([207], ['720p', '1080p']), - ([200], ['cam', 'ts', 'dvdrip', 'tc', 'r5', 'scr', 'brrip']), - ([202], ['dvdr']) - ] + cat_ids = [([207], ['720p', '1080p']), ([201], [ + 'cam', + 'ts', + 'dvdrip', + 'tc', + 'r5', + 'scr', + 'brrip', + ]), ([202], ['dvdr'])] cat_backup_id = 200 - ignore_string = { - '720p': ' -brrip -bdrip', - '1080p': ' -brrip -bdrip' - } + def getAPIurl(self): + return ("http://thepiratebay.se", self.conf('domain_for_tpb'))[self.conf('domain_for_tpb') != None] - def __init__(self): - pass - - def find(self, movie, quality, type): + def search(self, movie, quality): results = [] - if not self.enabled(): + if self.isDisabled(): return results - url = self.apiUrl % (quote_plus(self.toSearchString(movie.name + ' ' + quality) + self.makeIgnoreString(type)), self.getCatId(type)) - - log.info('Searching: %s', url) - - data = self.urlopen(url) + movie_name = re.sub("\W", ' ', getTitle(movie['library'])) + movie_name = re.sub(' ', ' ', movie_name) + log.info('API url: %s', self.getAPIurl()) + log.info('Cleaned Name: %s', movie_name) + cache_key = 'thepiratebay.%s.%s' % (movie['library' + ]['identifier'], quality.get('identifier')) + searchUrl = self.urls['search'] % (self.getAPIurl(), + quote_plus(movie_name + ' ' + quality['identifier']), + self.getCatId(quality['identifier'])[0]) + log.info('searchUrl: %s', searchUrl) + data = self.getCache(cache_key, searchUrl) + #print data if not data: - log.error('Failed to get data from %s.', url) + log.error('Failed to get data from %s.', searchUrl) return results try: - tables = SoupStrainer('table') - html = BeautifulSoup(data, parseOnlyThese = tables) - resultTable = html.find('table', attrs = {'id':'searchResult'}) - for result in resultTable.findAll('tr'): - details = result.find('a', attrs = {'class':'detLink'}) - if details: - href = re.search('/(?P\d+)/', details['href']) - id = href.group('id') - name = self.toSaveString(details.contents[0]) - desc = result.find('font', attrs = {'class':'detDesc'}).contents[0].split(',') - date = '' - size = 0 - for item in desc: - # Weird date stuff - if 'uploaded' in item.lower(): - date = item.replace('Uploaded', '') - date = date.replace('Today', '') + soup = BeautifulSoup(data) + resultsTable = soup.find('table', + attrs={'id': 'searchResult'}) + entries = resultsTable.findAll('tr') + for result in entries[1:]: + link = result.find(href=re.compile('torrent\/\d+\/')) + download = result.find(href=re.compile('magnet:')) + #Uploaded 06-28 02:27, Size 1.37 GiB, + size = re.search('Size (?P.+),', unicode(result.select("font.detDesc")[0])).group("size") + if link and download: + new = { + 'type': 'torrent', + 'check_nzb': False, + 'description': '', + 'provider': self.getName(), + } + trusted = (0, 10)[result.find('img', + alt=re.compile('Trusted')) != None] + vip = (0, 20)[result.find('img', + alt=re.compile('VIP')) != None] + moderated = (0, 50)[result.find('img', + alt=re.compile('Moderator')) != None] + log.info('Name: %s', link.string) - # Do something with yesterday - yesterdayMinus = 0 - if 'Y-day' in date: - date = date.replace('Y-day', '') - yesterdayMinus = 86400 + log.info('Seeders: %s', result.findAll('td' + )[2].string) + log.info('Leechers: %s', result.findAll('td' + )[3].string) + log.info('Size: %s', size) + log.info('Score(trusted + vip + moderated): %d', + trusted + vip + moderated) - datestring = date.replace(' ', ' ').strip() - date = int(time.mktime(parse(datestring).timetuple())) - yesterdayMinus - # size - elif 'size' in item.lower(): - size = item.replace('Size', '') + new['name'] = link.string + new['id'] = re.search('/(?P\d+)/', link['href' + ]).group('id') + new['url'] = link['href'] + new['download'] = self.getAPIurl() + download['href'] + new['size'] = self.parseSize(size) + new['seeders'] = int(result.findAll('td')[2].string) + new['leechers'] = int(result.findAll('td' + )[3].string) + new['imdbid'] = movie['library']['identifier'] + new['prtbscore'] = trusted + vip + moderated - seedleech = [] - for td in result.findAll('td'): - try: - seedleech.append(int(td.contents[0])) - except ValueError: - pass + new['extra_score'] = self.extra_score + new['score'] = fireEvent('score.calculate', new, + movie, single=True) + is_correct_movie = fireEvent( + 'searcher.correct_movie', + nzb=new, + movie=movie, + quality=quality, + imdb_results=True, + single_category=False, + single=True, + ) - seeders = 0 - leechers = 0 - if len(seedleech) == 2 and seedleech[0] > 0 and seedleech[1] > 0: - seeders = seedleech[0] - leechers = seedleech[1] - - # to item - new = self.feedItem() - new.id = id - new.type = 'torrent' - new.name = name - new.date = date - new.size = self.parseSize(size) - new.seeders = seeders - new.leechers = leechers - new.url = self.downloadLink(id, name) - new.score = self.calcScore(new, movie) + self.uploader(result) + (seeders / 10) - - if seeders > 0 and (new.date + (int(self.conf('wait')) * 60 * 60) < time.time()) and Qualities.types.get(type).get('minSize') <= new.size: - new.detailUrl = self.detailLink(id) - new.content = self.getInfo(new.detailUrl) - if self.isCorrectMovie(new, movie, type): - results.append(new) - log.info('Found: %s', new.name) + if is_correct_movie: + results.append(new) + self.found(new) return results + except Exception, e: + log.debug(e) + log.info('Error occured during parsing! Passing only processed entries' + ) + return results - except AttributeError: - log.debug('No search results found.') + def extra_score(self, torrent): + url = self.getAPIurl() + torrent['url'] + log.info('extra_score: %s', url) + imdbId = torrent['imdbid'] + return self.imdbMatch(url, imdbId) + torrent['prtbscore'] - return [] - - def makeIgnoreString(self, type): - ignore = self.ignoreString.get(type) - return ignore if ignore else '' - - def uploader(self, html): - score = 0 - if html.find('img', attr = {'alt':'VIP'}): - score += 3 - if html.find('img', attr = {'alt':'Trusted'}): - score += 1 - return score - - - def getInfo(self, url): - log.debug('Getting info: %s', url) - - data = self.urlopen(url) - if not data: - log.error('Failed to get data from %s.', url) - return '' - - div = SoupStrainer('div') - html = BeautifulSoup(data, parseOnlyThese = div) - html = html.find('div', attrs = {'class':'nfo'}) - return str(html).decode("utf-8", "replace") - - def downloadLink(self, id, name): - return self.downloadUrl % (id, quote_plus(name)) - - def isEnabled(self): - return self.conf('enabled') and TorrentProvider.isEnabled(self) + def imdbMatch(self, url, imdbId): + log.info('imdbMatch: %s', url) + try: + data = urllib2.urlopen(url).read() + pass + except: + log.error('Failed to open %s.' % url) + return 0 + imdbIdAlt = re.sub('tt[0]*', 'tt', imdbId) + data = unicode(data, errors='ignore') + if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ + + imdbIdAlt in data: + return 50 + return 0 From e19af816b04679f82a104434fcafb3f031d2eb3e Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Mon, 2 Jul 2012 13:22:53 +0200 Subject: [PATCH 06/16] Removed transmissionrpc and replaced with custom lightweight lib --- .../core/downloaders/transmission/main.py | 175 +++- libs/transmissionrpc/__init__.py | 16 - libs/transmissionrpc/client.py | 826 ------------------ libs/transmissionrpc/constants.py | 291 ------ libs/transmissionrpc/error.py | 52 -- libs/transmissionrpc/httphandler.py | 72 -- libs/transmissionrpc/session.py | 108 --- libs/transmissionrpc/torrent.py | 468 ---------- libs/transmissionrpc/utils.py | 191 ---- 9 files changed, 150 insertions(+), 2049 deletions(-) delete mode 100755 libs/transmissionrpc/__init__.py delete mode 100755 libs/transmissionrpc/client.py delete mode 100755 libs/transmissionrpc/constants.py delete mode 100755 libs/transmissionrpc/error.py delete mode 100755 libs/transmissionrpc/httphandler.py delete mode 100755 libs/transmissionrpc/session.py delete mode 100755 libs/transmissionrpc/torrent.py delete mode 100755 libs/transmissionrpc/utils.py diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index d8fb6f6c..f8cac7b2 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -1,51 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + from base64 import b64encode from couchpotato.core.downloaders.base import Downloader from couchpotato.core.helpers.encoding import isInt from couchpotato.core.logger import CPLog -from libs import transmissionrpc - +import urllib2 +import httplib +import json +import re log = CPLog(__name__) +class TransmissionRPC(object): + + """TransmissionRPC lite library""" + + def __init__( + self, + host='localhost', + port=9091, + username=None, + password=None, + ): + + super(TransmissionRPC, self).__init__() + self.url = 'http://' + host + ':' + str(port) \ + + '/transmission/rpc' + self.tag = 0 + self.session_id = 0 + self.session = {} + if username and password: + password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + password_manager.add_password(realm=None, uri=self.url, + user=username, passwd=password) + opener = \ + urllib2.build_opener(urllib2.HTTPBasicAuthHandler(password_manager), + urllib2.HTTPDigestAuthHandler(password_manager)) + opener.addheaders = [('User-agent', + 'couchpotato-transmission-client/1.0')] + urllib2.install_opener(opener) + elif username or password: + log.debug('User or password missing, not using authentication.' + ) + self.session = self.get_session() + + def _request(self, ojson): + self.tag += 1 + headers = {'x-transmission-session-id': str(self.session_id)} + request = urllib2.Request(self.url, + json.dumps(ojson).encode('utf-8'), + headers) + try: + open_request = urllib2.urlopen(request) + response = json.loads(open_request.read()) + log.debug('response: ' + + str(json.dumps(response).encode('utf-8'))) + if response['result'] == 'success': + log.debug(u'Transmission action successfull') + return response['arguments'] + else: + log.debug('Unknown failure sending command to Transmission. Return text is: ' + + response['result']) + return False + except httplib.InvalidURL, e: + log.error(u'Invalid Transmission host, check your config %s' + % e) + return False + except urllib2.HTTPError, e: + if e.code == 401: + log.error(u'Invalid Transmission Username or Password, check your config' + ) + return False + elif e.code == 409: + msg = str(e.read()) + try: + self.session_id = \ + re.search('X-Transmission-Session-Id:\s*(\w+)', + msg).group(1) + log.debug('X-Transmission-Session-Id: ' + + self.session_id) + + # #resend request with the updated header + + return self._request(ojson) + except: + log.error(u'Unable to get Transmission Session-Id %s' + % e) + else: + log.error(u'TransmissionRPC HTTPError: %s' % e) + except urllib2.URLError, e: + log.error(u'Unable to connect to Transmission %s' % e) + + def get_session(self): + post_data = {'method': 'session-get', 'tag': self.tag} + return self._request(post_data) + + def add_torrent_uri(self, torrent, arguments={}): + arguments['filename'] = torrent + post_data = {'arguments': arguments, 'method': 'torrent-add', + 'tag': self.tag} + return self._request(post_data) + + def add_torrent_file(self, torrent, arguments={}): + arguments['metainfo'] = torrent + post_data = {'arguments': arguments, 'method': 'torrent-add', + 'tag': self.tag} + return self._request(post_data) + + def set_torrent(self, id, arguments={}): + arguments['ids'] = id + post_data = {'arguments': arguments, 'method': 'torrent-set', + 'tag': self.tag} + return self._request(post_data) + + class Transmission(Downloader): - type = ['torrent'] + type = ['torrent', 'magnet'] - def download(self, data = {}, movie = {}, manual = False, filedata = None): + def download( + self, + data={}, + movie={}, + manual=False, + filedata=None, + ): - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): + print data + if self.isDisabled(manual) \ + or not self.isCorrectType(data.get('type')): return - log.info('Sending "%s" to Transmission.', data.get('name')) + log.debug('Sending "%s" to Transmission.', data.get('name')) + log.debug('Type "%s" to Transmission.', data.get('type')) # 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.") + log.error('Config properties are not filled in correctly, port is missing.' + ) return False # Set parameters for Transmission - params = { - 'paused': self.conf('paused', default = 0), - 'download_dir': self.conf('directory', default = None) - } + + params = {'paused': self.conf('paused', default=0), + 'download-dir': self.conf('directory', default=None)} + + torrent_params = {'seedRatioLimit': self.conf('ratio'), + 'seedRatioMode': (0 if self.conf('ratio' + ) else 1)} + + if not filedata or data.get('type') != 'magnet': + log.error('Failed sending torrent, no data') + + # Send request to Transmission try: - if not filedata: - log.error('Failed sending torrent to transmission, no data') - - tc = transmissionrpc.Client(host[0], port = host[1], user = self.conf('username'), password = self.conf('password')) - torrent = tc.add_torrent(b64encode(filedata), **params) + tc = TransmissionRPC(host[0], port=host[1], + username=self.conf('username'), + password=self.conf('password')) + if data.get('type') == 'magnet' or data.get('magnet') != None: + remote_torrent = tc.add_torrent_uri(data.get('magnet'), arguments=params) + else: + remote_torrent = tc.add_torrent_file(b64encode(filedata), arguments=params) # Change settings of added torrents - try: - torrent.seed_ratio_limit = self.conf('ratio') - torrent.seed_ratio_mode = 'single' if self.conf('ratio') else 'global' - except transmissionrpc.TransmissionError, e: - log.error('Failed to change settings for transfer in transmission: %s', e) - + print remote_torrent + tc.set_torrent(remote_torrent['torrent-added']['hashString' + ], torrent_params) return True - - except transmissionrpc.TransmissionError, e: - log.error('Failed to send link to transmission: %s', e) + except Exception, e: + log.error('Failed to change settings for transfer: %s', e) return False diff --git a/libs/transmissionrpc/__init__.py b/libs/transmissionrpc/__init__.py deleted file mode 100755 index 057d6784..00000000 --- a/libs/transmissionrpc/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER -from transmissionrpc.error import TransmissionError, HTTPHandlerError -from transmissionrpc.httphandler import HTTPHandler, DefaultHTTPHandler -from transmissionrpc.torrent import Torrent -from transmissionrpc.session import Session -from transmissionrpc.client import Client -from transmissionrpc.utils import add_stdout_logger - -__author__ = u'Erik Svensson ' -__version__ = u'0.9' -__copyright__ = u'Copyright (c) 2008-2011 Erik Svensson' -__license__ = u'MIT' diff --git a/libs/transmissionrpc/client.py b/libs/transmissionrpc/client.py deleted file mode 100755 index 420083b6..00000000 --- a/libs/transmissionrpc/client.py +++ /dev/null @@ -1,826 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -import re, time, operator, warnings -import urllib2, urlparse, base64 - -try: - import json -except ImportError: - import simplejson as json - -from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT -from transmissionrpc.error import TransmissionError, HTTPHandlerError -from transmissionrpc.utils import LOGGER, get_arguments, make_rpc_name, argument_value_convert, rpc_bool -from transmissionrpc.httphandler import DefaultHTTPHandler -from transmissionrpc.torrent import Torrent -from transmissionrpc.session import Session - -def debug_httperror(error): - """ - Log the Transmission RPC HTTP error. - """ - try: - data = json.loads(error.data) - except ValueError: - data = error.data - LOGGER.debug( - json.dumps( - { - 'response': { - 'url': error.url, - 'code': error.code, - 'msg': error.message, - 'headers': error.headers, - 'data': data, - } - }, - indent = 2 - ) - ) - -""" -Torrent ids - -Many functions in Client takes torrent id. A torrent id can either be id or -hashString. When supplying multiple id's it is possible to use a list mixed -with both id and hashString. - -Timeouts - -Since most methods results in HTTP requests against Transmission, it is -possible to provide a argument called ``timeout``. Timeout is only effective -when using Python 2.6 or later and the default timeout is 30 seconds. -""" - -class Client(object): - """ - Client is the class handling the Transmission JSON-RPC client protocol. - """ - - def __init__(self, address = 'localhost', port = DEFAULT_PORT, user = None, password = None, http_handler = None, timeout = None): - if isinstance(timeout, (int, long, float)): - self._query_timeout = float(timeout) - else: - self._query_timeout = DEFAULT_TIMEOUT - urlo = urlparse.urlparse(address) - if urlo.scheme == '': - base_url = 'http://' + address + ':' + str(port) - self.url = base_url + '/transmission/rpc' - else: - if urlo.port: - self.url = urlo.scheme + '://' + urlo.hostname + ':' + str(urlo.port) + urlo.path - else: - self.url = urlo.scheme + '://' + urlo.hostname + urlo.path - LOGGER.info('Using custom URL "' + self.url + '".') - if urlo.username and urlo.password: - user = urlo.username - password = urlo.password - elif urlo.username or urlo.password: - LOGGER.warning('Either user or password missing, not using authentication.') - if http_handler is None: - self.http_handler = DefaultHTTPHandler() - else: - if hasattr(http_handler, 'set_authentication') and hasattr(http_handler, 'request'): - self.http_handler = http_handler - else: - raise ValueError('Invalid HTTP handler.') - if user and password: - self.http_handler.set_authentication(self.url, user, password) - elif user or password: - LOGGER.warning('Either user or password missing, not using authentication.') - self._sequence = 0 - self.session = None - self.session_id = 0 - self.server_version = None - self.protocol_version = None - self.get_session() - self.torrent_get_arguments = get_arguments('torrent-get' - , self.rpc_version) - - def _get_timeout(self): - """ - Get current timeout for HTTP queries. - """ - return self._query_timeout - - def _set_timeout(self, value): - """ - Set timeout for HTTP queries. - """ - self._query_timeout = float(value) - - def _del_timeout(self): - """ - Reset the HTTP query timeout to the default. - """ - self._query_timeout = DEFAULT_TIMEOUT - - timeout = property(_get_timeout, _set_timeout, _del_timeout, doc = "HTTP query timeout.") - - def _http_query(self, query, timeout = None): - """ - Query Transmission through HTTP. - """ - headers = {'x-transmission-session-id': str(self.session_id)} - result = {} - request_count = 0 - if timeout is None: - timeout = self._query_timeout - while True: - LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent = 2)) - try: - result = self.http_handler.request(self.url, query, headers, timeout) - break - except HTTPHandlerError, error: - if error.code == 409: - LOGGER.info('Server responded with 409, trying to set session-id.') - if request_count > 1: - raise TransmissionError('Session ID negotiation failed.', error) - if 'x-transmission-session-id' in error.headers: - self.session_id = error.headers['x-transmission-session-id'] - headers = {'x-transmission-session-id': str(self.session_id)} - else: - debug_httperror(error) - raise TransmissionError('Unknown conflict.', error) - else: - debug_httperror(error) - raise TransmissionError('Request failed.', error) - request_count += 1 - return result - - def _request(self, method, arguments = None, ids = None, require_ids = False, timeout = None): - """ - Send json-rpc request to Transmission using http POST - """ - if not isinstance(method, (str, unicode)): - raise ValueError('request takes method as string') - if arguments is None: - arguments = {} - if not isinstance(arguments, dict): - raise ValueError('request takes arguments as dict') - ids = self._format_ids(ids) - if len(ids) > 0: - arguments['ids'] = ids - elif require_ids: - raise ValueError('request require ids') - - query = json.dumps({'tag': self._sequence, 'method': method - , 'arguments': arguments}) - self._sequence += 1 - start = time.time() - http_data = self._http_query(query, timeout) - elapsed = time.time() - start - LOGGER.info('http request took %.3f s' % (elapsed)) - - try: - data = json.loads(http_data) - except ValueError, error: - LOGGER.error('Error: ' + str(error)) - LOGGER.error('Request: \"%s\"' % (query)) - LOGGER.error('HTTP data: \"%s\"' % (http_data)) - raise - - LOGGER.debug(json.dumps(data, indent = 2)) - if 'result' in data: - if data['result'] != 'success': - raise TransmissionError('Query failed with result \"%s\".' % (data['result'])) - else: - raise TransmissionError('Query failed without result.') - - results = {} - if method == 'torrent-get': - for item in data['arguments']['torrents']: - results[item['id']] = Torrent(self, item) - if self.protocol_version == 2 and 'peers' not in item: - self.protocol_version = 1 - elif method == 'torrent-add': - item = data['arguments']['torrent-added'] - results[item['id']] = Torrent(self, item) - elif method == 'session-get': - self._update_session(data['arguments']) - elif method == 'session-stats': - # older versions of T has the return data in "session-stats" - if 'session-stats' in data['arguments']: - self._update_session(data['arguments']['session-stats']) - else: - self._update_session(data['arguments']) - elif method in ('port-test', 'blocklist-update'): - results = data['arguments'] - else: - return None - - return results - - def _format_ids(self, args): - """ - Take things and make them valid torrent identifiers - """ - ids = [] - - if args is None: - pass - elif isinstance(args, (int, long)): - ids.append(args) - elif isinstance(args, (str, unicode)): - for item in re.split(u'[ ,]+', args): - if len(item) == 0: - continue - addition = None - try: - # handle index - addition = [int(item)] - except ValueError: - pass - if not addition: - # handle hashes - try: - int(item, 16) - addition = [item] - except ValueError: - pass - if not addition: - # handle index ranges i.e. 5:10 - match = re.match(u'^(\d+):(\d+)$', item) - if match: - try: - idx_from = int(match.group(1)) - idx_to = int(match.group(2)) - addition = range(idx_from, idx_to + 1) - except ValueError: - pass - if not addition: - raise ValueError(u'Invalid torrent id, \"%s\"' % item) - ids.extend(addition) - elif isinstance(args, list): - for item in args: - ids.extend(self._format_ids(item)) - else: - raise ValueError(u'Invalid torrent id') - return ids - - def _update_session(self, data): - """ - Update session data. - """ - if self.session: - self.session.from_request(data) - else: - self.session = Session(self, data) - - def _update_server_version(self): - if self.server_version is None: - version_major = 1 - version_minor = 30 - version_changeset = 0 - version_parser = re.compile('(\d).(\d+) \((\d+)\)') - if hasattr(self.session, 'version'): - match = version_parser.match(self.session.version) - if match: - version_major = int(match.group(1)) - version_minor = int(match.group(2)) - version_changeset = match.group(3) - self.server_version = (version_major, version_minor, version_changeset) - - @property - def rpc_version(self): - """ - Get the Transmission RPC version. Trying to deduct if the server don't have a version value. - """ - if self.protocol_version is None: - # Ugly fix for 2.20 - 2.22 reporting rpc-version 11, but having new arguments - if self.server_version and (self.server_version[0] == 2 and self.server_version[1] in [20, 21, 22]): - self.protocol_version = 12 - # Ugly fix for 2.12 reporting rpc-version 10, but having new arguments - elif self.server_version and (self.server_version[0] == 2 and self.server_version[1] == 12): - self.protocol_version = 11 - elif hasattr(self.session, 'rpc_version'): - self.protocol_version = self.session.rpc_version - elif hasattr(self.session, 'version'): - self.protocol_version = 3 - else: - self.protocol_version = 2 - return self.protocol_version - - def _rpc_version_warning(self, version): - """ - Add a warning to the log if the Transmission RPC version is lower then the provided version. - """ - if self.rpc_version < version: - LOGGER.warning('Using feature not supported by server. RPC version for server %d, feature introduced in %d.' - % (self.rpc_version, version)) - - def add_torrent(self, torrent, timeout = None, **kwargs): - """ - Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data. - Additional arguments are: - - ===================== ===== =========== ============================================================= - Argument RPC Replaced by Description - ===================== ===== =========== ============================================================= - ``bandwidthPriority`` 8 - Priority for this transfer. - ``cookies`` 13 - One or more HTTP cookie(s). - ``download_dir`` 1 - The directory where the downloaded contents will be saved in. - ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. - ``files_wanted`` 1 - A list of file id's that should be downloaded. - ``paused`` 1 - If True, does not start the transfer when added. - ``peer_limit`` 1 - Maximum number of peers allowed. - ``priority_high`` 1 - A list of file id's that should have high priority. - ``priority_low`` 1 - A list of file id's that should have low priority. - ``priority_normal`` 1 - A list of file id's that should have normal priority. - ===================== ===== =========== ============================================================= - - Returns a Torrent object with limited fields. - """ - if torrent is None: - raise ValueError('add_torrent requires data or a URI.') - torrent_data = None - try: - # check if this is base64 data - base64.b64decode(torrent) - torrent_data = torrent - except Exception: - torrent_data = None - if not torrent_data: - parsed_uri = urlparse.urlparse(torrent) - if parsed_uri.scheme in ['file', 'ftp', 'ftps', 'http', 'https']: - # there has been some problem with T's built in torrent fetcher, - # use a python one instead - torrent_file = urllib2.urlopen(torrent) - torrent_data = torrent_file.read() - torrent_data = base64.b64encode(torrent_data) - args = {} - if torrent_data: - args = {'metainfo': torrent_data} - else: - args = {'filename': torrent} - for key, value in kwargs.iteritems(): - argument = make_rpc_name(key) - (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version) - args[arg] = val - return self._request('torrent-add', args, timeout = timeout).values()[0] - - def add(self, data, timeout = None, **kwargs): - """ - - .. WARNING:: - Deprecated, please use add_torrent. - """ - args = {} - if data: - args = {'metainfo': data} - elif 'metainfo' not in kwargs and 'filename' not in kwargs: - raise ValueError('No torrent data or torrent uri.') - for key, value in kwargs.iteritems(): - argument = make_rpc_name(key) - (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version) - args[arg] = val - warnings.warn('add has been deprecated, please use add_torrent instead.', DeprecationWarning) - return self._request('torrent-add', args, timeout = timeout) - - def add_uri(self, uri, **kwargs): - """ - - .. WARNING:: - Deprecated, please use add_torrent. - """ - if uri is None: - raise ValueError('add_uri requires a URI.') - # there has been some problem with T's built in torrent fetcher, - # use a python one instead - parsed_uri = urlparse.urlparse(uri) - torrent_data = None - if parsed_uri.scheme in ['file', 'ftp', 'ftps', 'http', 'https']: - torrent_file = urllib2.urlopen(uri) - torrent_data = base64.b64encode(torrent_file.read()) - warnings.warn('add_uri has been deprecated, please use add_torrent instead.', DeprecationWarning) - if torrent_data: - return self.add(torrent_data, **kwargs) - else: - return self.add(None, filename = uri, **kwargs) - - def remove_torrent(self, ids, delete_data = False, timeout = None): - """ - remove torrent(s) with provided id(s). Local data is removed if - delete_data is True, otherwise not. - """ - self._rpc_version_warning(3) - self._request('torrent-remove', - {'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout = timeout) - - def remove(self, ids, delete_data = False, timeout = None): - """ - - .. WARNING:: - Deprecated, please use remove_torrent. - """ - warnings.warn('remove has been deprecated, please use remove_torrent instead.', DeprecationWarning) - self.remove_torrent(ids, delete_data, timeout) - - def start_torrent(self, ids, bypass_queue = False, timeout = None): - """Start torrent(s) with provided id(s)""" - method = 'torrent-start' - if bypass_queue and self.rpc_version >= 14: - method = 'torrent-start-now' - self._request(method, {}, ids, True, timeout = timeout) - - def start(self, ids, bypass_queue = False, timeout = None): - """ - - .. WARNING:: - Deprecated, please use start_torrent. - """ - warnings.warn('start has been deprecated, please use start_torrent instead.', DeprecationWarning) - self.start_torrent(ids, bypass_queue, timeout) - - def start_all(self, bypass_queue = False, timeout = None): - """Start all torrents respecting the queue order""" - torrent_list = self.get_torrents() - method = 'torrent-start' - if self.rpc_version >= 14: - if bypass_queue: - method = 'torrent-start-now' - torrent_list = sorted(torrent_list, key = operator.attrgetter('queuePosition')) - ids = [x.id for x in torrent_list] - self._request(method, {}, ids, True, timeout = timeout) - - def stop_torrent(self, ids, timeout = None): - """stop torrent(s) with provided id(s)""" - self._request('torrent-stop', {}, ids, True, timeout = timeout) - - def stop(self, ids, timeout = None): - """ - - .. WARNING:: - Deprecated, please use stop_torrent. - """ - warnings.warn('stop has been deprecated, please use stop_torrent instead.', DeprecationWarning) - self.stop_torrent(ids, timeout) - - def verify_torrent(self, ids, timeout = None): - """verify torrent(s) with provided id(s)""" - self._request('torrent-verify', {}, ids, True, timeout = timeout) - - def verify(self, ids, timeout = None): - """ - - .. WARNING:: - Deprecated, please use verify_torrent. - """ - warnings.warn('verify has been deprecated, please use verify_torrent instead.', DeprecationWarning) - self.verify_torrent(ids, timeout) - - def reannounce_torrent(self, ids, timeout = None): - """Reannounce torrent(s) with provided id(s)""" - self._rpc_version_warning(5) - self._request('torrent-reannounce', {}, ids, True, timeout = timeout) - - def reannounce(self, ids, timeout = None): - """ - - .. WARNING:: - Deprecated, please use reannounce_torrent. - """ - warnings.warn('reannounce has been deprecated, please use reannounce_torrent instead.', DeprecationWarning) - self.reannounce_torrent(ids, timeout) - - def get_torrent(self, id, arguments = None, timeout = None): - """ - Get information for torrent with provided id. - - Returns a Torrent object. - """ - if not arguments: - arguments = self.torrent_get_arguments - if not isinstance(id, (int, long, str, unicode)): - raise ValueError("Invalid id") - return self._request('torrent-get', {'fields': arguments}, id, require_ids = True, timeout = timeout)[id] - - def get_torrents(self, ids = None, arguments = None, timeout = None): - """ - Get information for torrents with provided ids. - - Returns a list of Torrent object. - """ - if not arguments: - arguments = self.torrent_get_arguments - return self._request('torrent-get', {'fields': arguments}, ids, timeout = timeout).values() - - def info(self, ids = None, arguments = None, timeout = None): - """ - - .. WARNING:: - Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in - the new methods. info returns a dictionary indexed by torrent id. - """ - warnings.warn('info has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning) - if not arguments: - arguments = self.torrent_get_arguments - return self._request('torrent-get', {'fields': arguments}, ids, timeout = timeout) - - def list(self, timeout = None): - """ - - .. WARNING:: - Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in - the new methods. list returns a dictionary indexed by torrent id. - """ - warnings.warn('list has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning) - fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone' - , 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver' - , 'downloadedEver', 'uploadRatio', 'queuePosition'] - return self._request('torrent-get', {'fields': fields}, timeout = timeout) - - def get_files(self, ids = None, timeout = None): - """ - Get list of files for provided torrent id(s). If ids is empty, - information for all torrents are fetched. This function returns a dictionary - for each requested torrent id holding the information about the files. - - :: - - { - : { - : { - 'name': , - 'size': , - 'completed': , - 'priority': , - 'selected': - } - - ... - } - - ... - } - """ - fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted'] - request_result = self._request('torrent-get', {'fields': fields}, ids, timeout = timeout) - result = {} - for tid, torrent in request_result.iteritems(): - result[tid] = torrent.files() - return result - - def set_files(self, items, timeout = None): - """ - Set file properties. Takes a dictionary with similar contents as the result - of `get_files`. - - :: - - { - : { - : { - 'priority': , - 'selected': - } - - ... - } - - ... - } - """ - if not isinstance(items, dict): - raise ValueError('Invalid file description') - for tid, files in items.iteritems(): - if not isinstance(files, dict): - continue - wanted = [] - unwanted = [] - high = [] - normal = [] - low = [] - for fid, file_desc in files.iteritems(): - if not isinstance(file_desc, dict): - continue - if 'selected' in file_desc and file_desc['selected']: - wanted.append(fid) - else: - unwanted.append(fid) - if 'priority' in file_desc: - if file_desc['priority'] == 'high': - high.append(fid) - elif file_desc['priority'] == 'normal': - normal.append(fid) - elif file_desc['priority'] == 'low': - low.append(fid) - args = { - 'timeout': timeout, - 'files_wanted': wanted, - 'files_unwanted': unwanted, - } - if len(high) > 0: - args['priority_high'] = high - if len(normal) > 0: - args['priority_normal'] = normal - if len(low) > 0: - args['priority_low'] = low - self.change([tid], **args) - - def change_torrent(self, ids, timeout = None, **kwargs): - """ - Change torrent parameters for the torrent(s) with the supplied id's. The - parameters are: - - ============================ ===== =============== ======================================================================================= - Argument RPC Replaced by Description - ============================ ===== =============== ======================================================================================= - ``bandwidthPriority`` 5 - Priority for this transfer. - ``downloadLimit`` 5 - Set the speed limit for download in Kib/s. - ``downloadLimited`` 5 - Enable download speed limiter. - ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. - ``files_wanted`` 1 - A list of file id's that should be downloaded. - ``honorsSessionLimits`` 5 - Enables or disables the transfer to honour the upload limit set in the session. - ``location`` 1 - Local download location. - ``peer_limit`` 1 - The peer limit for the torrents. - ``priority_high`` 1 - A list of file id's that should have high priority. - ``priority_low`` 1 - A list of file id's that should have normal priority. - ``priority_normal`` 1 - A list of file id's that should have low priority. - ``queuePosition`` 14 - Position of this transfer in its queue. - ``seedIdleLimit`` 10 - Seed inactivity limit in minutes. - ``seedIdleMode`` 10 - Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. - ``seedRatioLimit`` 5 - Seeding ratio. - ``seedRatioMode`` 5 - Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. - ``speed_limit_down`` 1 - 5 downloadLimit Set the speed limit for download in Kib/s. - ``speed_limit_down_enabled`` 1 - 5 downloadLimited Enable download speed limiter. - ``speed_limit_up`` 1 - 5 uploadLimit Set the speed limit for upload in Kib/s. - ``speed_limit_up_enabled`` 1 - 5 uploadLimited Enable upload speed limiter. - ``trackerAdd`` 10 - Array of string with announce URLs to add. - ``trackerRemove`` 10 - Array of ids of trackers to remove. - ``trackerReplace`` 10 - Array of (id, url) tuples where the announce URL should be replaced. - ``uploadLimit`` 5 - Set the speed limit for upload in Kib/s. - ``uploadLimited`` 5 - Enable upload speed limiter. - ============================ ===== =============== ======================================================================================= - - .. NOTE:: - transmissionrpc will try to automatically fix argument errors. - """ - args = {} - for key, value in kwargs.iteritems(): - argument = make_rpc_name(key) - (arg, val) = argument_value_convert('torrent-set' , argument, value, self.rpc_version) - args[arg] = val - - if len(args) > 0: - self._request('torrent-set', args, ids, True, timeout = timeout) - else: - ValueError("No arguments to set") - - def change(self, ids, timeout = None, **kwargs): - """ - - .. WARNING:: - Deprecated, please use change_torrent. - """ - warnings.warn('change has been deprecated, please use change_torrent instead.', DeprecationWarning) - self.change_torrent(ids, timeout, **kwargs) - - def move_torrent_data(self, ids, location, timeout = None): - """Move torrent data to the new location.""" - self._rpc_version_warning(6) - args = {'location': location, 'move': True} - self._request('torrent-set-location', args, ids, True, timeout = timeout) - - def move(self, ids, location, timeout = None): - """ - - .. WARNING:: - Deprecated, please use move_torrent_data. - """ - warnings.warn('move has been deprecated, please use move_torrent_data instead.', DeprecationWarning) - self.move_torrent_data(ids, location, timeout) - - def locate_torrent_data(self, ids, location, timeout = None): - """Locate torrent data at the provided location.""" - self._rpc_version_warning(6) - args = {'location': location, 'move': False} - self._request('torrent-set-location', args, ids, True, timeout = timeout) - - def locate(self, ids, location, timeout = None): - """ - - .. WARNING:: - Deprecated, please use locate_torrent_data. - """ - warnings.warn('locate has been deprecated, please use locate_torrent_data instead.', DeprecationWarning) - self.locate_torrent_data(ids, location, timeout) - - def queue_top(self, ids, timeout = None): - """Move transfer to the top of the queue.""" - self._rpc_version_warning(14) - self._request('queue-move-top', ids = ids, require_ids = True, timeout = timeout) - - def queue_bottom(self, ids, timeout = None): - """Move transfer to the bottom of the queue.""" - self._rpc_version_warning(14) - self._request('queue-move-bottom', ids = ids, require_ids = True, timeout = timeout) - - def queue_up(self, ids, timeout = None): - """Move transfer up in the queue.""" - self._rpc_version_warning(14) - self._request('queue-move-up', ids = ids, require_ids = True, timeout = timeout) - - def queue_down(self, ids, timeout = None): - """Move transfer down in the queue.""" - self._rpc_version_warning(14) - self._request('queue-move-down', ids = ids, require_ids = True, timeout = timeout) - - def get_session(self, timeout = None): - """Get session parameters""" - self._request('session-get', timeout = timeout) - self._update_server_version() - return self.session - - def set_session(self, timeout = None, **kwargs): - """ - Set session parameters. The parameters are: - - ================================ ===== ================= ========================================================================================================================== - Argument RPC Replaced by Description - ================================ ===== ================= ========================================================================================================================== - ``alt_speed_down`` 5 - Alternate session download speed limit (in Kib/s). - ``alt_speed_enabled`` 5 - Enables alternate global download speed limiter. - ``alt_speed_time_begin`` 5 - Time when alternate speeds should be enabled. Minutes after midnight. - ``alt_speed_time_day`` 5 - Enables alternate speeds scheduling these days. - ``alt_speed_time_enabled`` 5 - Enables alternate speeds scheduling. - ``alt_speed_time_end`` 5 - Time when alternate speeds should be disabled. Minutes after midnight. - ``alt_speed_up`` 5 - Alternate session upload speed limit (in Kib/s). - ``blocklist_enabled`` 5 - Enables the block list - ``blocklist_url`` 11 - Location of the block list. Updated with blocklist-update. - ``cache_size_mb`` 10 - The maximum size of the disk cache in MB - ``dht_enabled`` 6 - Enables DHT. - ``download_dir`` 1 - Set the session download directory. - ``download_queue_enabled`` 14 - Enable parallel download restriction. - ``download_queue_size`` 14 - Number of parallel downloads. - ``encryption`` 1 - Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``. - ``idle_seeding_limit`` 10 - The default seed inactivity limit in minutes. - ``idle_seeding_limit_enabled`` 10 - Enables the default seed inactivity limit - ``incomplete_dir`` 7 - The path to the directory of incomplete transfer data. - ``incomplete_dir_enabled`` 7 - Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target. - ``lpd_enabled`` 9 - Enables local peer discovery for public torrents. - ``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers - ``peer_limit_global`` 5 - Maximum number of peers - ``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer - ``peer_port`` 5 - Peer port. - ``peer_port_random_on_start`` 5 - Enables randomized peer port on start of Transmission. - ``pex_allowed`` 1 - 5 pex-enabled Allowing PEX in public torrents. - ``pex_enabled`` 5 - Allowing PEX in public torrents. - ``port`` 1 - 5 peer-port Peer port. - ``port_forwarding_enabled`` 1 - Enables port forwarding. - ``queue_stalled_enabled`` 14 - Enable tracking of stalled transfers. - ``queue_stalled_minutes`` 14 - Number of minutes of idle that marks a transfer as stalled. - ``rename_partial_files`` 8 - Appends ".part" to incomplete files - ``script_torrent_done_enabled`` 9 - Whether or not to call the "done" script. - ``script_torrent_done_filename`` 9 - Filename of the script to run when the transfer is done. - ``seed_queue_enabled`` 14 - Enable parallel upload restriction. - ``seed_queue_size`` 14 - Number of parallel uploads. - ``seedRatioLimit`` 5 - Seed ratio limit. 1.0 means 1:1 download and upload ratio. - ``seedRatioLimited`` 5 - Enables seed ration limit. - ``speed_limit_down`` 1 - Download speed limit (in Kib/s). - ``speed_limit_down_enabled`` 1 - Enables download speed limiting. - ``speed_limit_up`` 1 - Upload speed limit (in Kib/s). - ``speed_limit_up_enabled`` 1 - Enables upload speed limiting. - ``start_added_torrents`` 9 - Added torrents will be started right away. - ``trash_original_torrent_files`` 9 - The .torrent file of added torrents will be deleted. - ``utp_enabled`` 13 - Enables Micro Transport Protocol (UTP). - ================================ ===== ================= ========================================================================================================================== - - .. NOTE:: - transmissionrpc will try to automatically fix argument errors. - """ - args = {} - for key, value in kwargs.iteritems(): - if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']: - raise ValueError('Invalid encryption value') - argument = make_rpc_name(key) - (arg, val) = argument_value_convert('session-set' , argument, value, self.rpc_version) - args[arg] = val - if len(args) > 0: - self._request('session-set', args, timeout = timeout) - - def blocklist_update(self, timeout = None): - """Update block list. Returns the size of the block list.""" - self._rpc_version_warning(5) - result = self._request('blocklist-update', timeout = timeout) - if 'blocklist-size' in result: - return result['blocklist-size'] - return None - - def port_test(self, timeout = None): - """ - Tests to see if your incoming peer port is accessible from the - outside world. - """ - self._rpc_version_warning(5) - result = self._request('port-test', timeout = timeout) - if 'port-is-open' in result: - return result['port-is-open'] - return None - - def session_stats(self, timeout = None): - """Get session statistics""" - self._request('session-stats', timeout = timeout) - return self.session diff --git a/libs/transmissionrpc/constants.py b/libs/transmissionrpc/constants.py deleted file mode 100755 index 8661da32..00000000 --- a/libs/transmissionrpc/constants.py +++ /dev/null @@ -1,291 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -import logging - -LOGGER = logging.getLogger('transmissionrpc') -LOGGER.setLevel(logging.ERROR) - -def mirror_dict(source): - """ - Creates a dictionary with all values as keys and all keys as values. - """ - source.update(dict((value, key) for key, value in source.iteritems())) - return source - -DEFAULT_PORT = 9091 - -DEFAULT_TIMEOUT = 30.0 - -TR_PRI_LOW = -1 -TR_PRI_NORMAL = 0 -TR_PRI_HIGH = 1 - -PRIORITY = mirror_dict({ - 'low' : TR_PRI_LOW, - 'normal' : TR_PRI_NORMAL, - 'high' : TR_PRI_HIGH -}) - -TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings -TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio -TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio - -RATIO_LIMIT = mirror_dict({ - 'global' : TR_RATIOLIMIT_GLOBAL, - 'single' : TR_RATIOLIMIT_SINGLE, - 'unlimited' : TR_RATIOLIMIT_UNLIMITED -}) - -TR_IDLELIMIT_GLOBAL = 0 # follow the global settings -TR_IDLELIMIT_SINGLE = 1 # override the global settings, seeding until a certain idle time -TR_IDLELIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of activity - -IDLE_LIMIT = mirror_dict({ - 'global' : TR_RATIOLIMIT_GLOBAL, - 'single' : TR_RATIOLIMIT_SINGLE, - 'unlimited' : TR_RATIOLIMIT_UNLIMITED -}) - -# A note on argument maps -# These maps are used to verify *-set methods. The information is structured in -# a tree. -# set +- - [, , , , , ] -# | +- - [, , , , , ] -# | -# get +- - [, , , , , ] -# +- - [, , , , , ] - -# Arguments for torrent methods -TORRENT_ARGS = { - 'get' : { - 'activityDate': ('number', 1, None, None, None, ''), - 'addedDate': ('number', 1, None, None, None, ''), - 'announceResponse': ('string', 1, 7, None, None, ''), - 'announceURL': ('string', 1, 7, None, None, ''), - 'bandwidthPriority': ('number', 5, None, None, None, ''), - 'comment': ('string', 1, None, None, None, ''), - 'corruptEver': ('number', 1, None, None, None, ''), - 'creator': ('string', 1, None, None, None, ''), - 'dateCreated': ('number', 1, None, None, None, ''), - 'desiredAvailable': ('number', 1, None, None, None, ''), - 'doneDate': ('number', 1, None, None, None, ''), - 'downloadDir': ('string', 4, None, None, None, ''), - 'downloadedEver': ('number', 1, None, None, None, ''), - 'downloaders': ('number', 4, 7, None, None, ''), - 'downloadLimit': ('number', 1, None, None, None, ''), - 'downloadLimited': ('boolean', 5, None, None, None, ''), - 'downloadLimitMode': ('number', 1, 5, None, None, ''), - 'error': ('number', 1, None, None, None, ''), - 'errorString': ('number', 1, None, None, None, ''), - 'eta': ('number', 1, None, None, None, ''), - 'files': ('array', 1, None, None, None, ''), - 'fileStats': ('array', 5, None, None, None, ''), - 'hashString': ('string', 1, None, None, None, ''), - 'haveUnchecked': ('number', 1, None, None, None, ''), - 'haveValid': ('number', 1, None, None, None, ''), - 'honorsSessionLimits': ('boolean', 5, None, None, None, ''), - 'id': ('number', 1, None, None, None, ''), - 'isFinished': ('boolean', 9, None, None, None, ''), - 'isPrivate': ('boolean', 1, None, None, None, ''), - 'isStalled': ('boolean', 14, None, None, None, ''), - 'lastAnnounceTime': ('number', 1, 7, None, None, ''), - 'lastScrapeTime': ('number', 1, 7, None, None, ''), - 'leechers': ('number', 1, 7, None, None, ''), - 'leftUntilDone': ('number', 1, None, None, None, ''), - 'magnetLink': ('string', 7, None, None, None, ''), - 'manualAnnounceTime': ('number', 1, None, None, None, ''), - 'maxConnectedPeers': ('number', 1, None, None, None, ''), - 'metadataPercentComplete': ('number', 7, None, None, None, ''), - 'name': ('string', 1, None, None, None, ''), - 'nextAnnounceTime': ('number', 1, 7, None, None, ''), - 'nextScrapeTime': ('number', 1, 7, None, None, ''), - 'peer-limit': ('number', 5, None, None, None, ''), - 'peers': ('array', 2, None, None, None, ''), - 'peersConnected': ('number', 1, None, None, None, ''), - 'peersFrom': ('object', 1, None, None, None, ''), - 'peersGettingFromUs': ('number', 1, None, None, None, ''), - 'peersKnown': ('number', 1, 13, None, None, ''), - 'peersSendingToUs': ('number', 1, None, None, None, ''), - 'percentDone': ('double', 5, None, None, None, ''), - 'pieces': ('string', 5, None, None, None, ''), - 'pieceCount': ('number', 1, None, None, None, ''), - 'pieceSize': ('number', 1, None, None, None, ''), - 'priorities': ('array', 1, None, None, None, ''), - 'queuePosition': ('number', 14, None, None, None, ''), - 'rateDownload': ('number', 1, None, None, None, ''), - 'rateUpload': ('number', 1, None, None, None, ''), - 'recheckProgress': ('double', 1, None, None, None, ''), - 'scrapeResponse': ('string', 1, 7, None, None, ''), - 'scrapeURL': ('string', 1, 7, None, None, ''), - 'seeders': ('number', 1, 7, None, None, ''), - 'seedIdleLimit': ('number', 10, None, None, None, ''), - 'seedIdleMode': ('number', 10, None, None, None, ''), - 'seedRatioLimit': ('double', 5, None, None, None, ''), - 'seedRatioMode': ('number', 5, None, None, None, ''), - 'sizeWhenDone': ('number', 1, None, None, None, ''), - 'startDate': ('number', 1, None, None, None, ''), - 'status': ('number', 1, None, None, None, ''), - 'swarmSpeed': ('number', 1, 7, None, None, ''), - 'timesCompleted': ('number', 1, 7, None, None, ''), - 'trackers': ('array', 1, None, None, None, ''), - 'trackerStats': ('object', 7, None, None, None, ''), - 'totalSize': ('number', 1, None, None, None, ''), - 'torrentFile': ('string', 5, None, None, None, ''), - 'uploadedEver': ('number', 1, None, None, None, ''), - 'uploadLimit': ('number', 1, None, None, None, ''), - 'uploadLimitMode': ('number', 1, 5, None, None, ''), - 'uploadLimited': ('boolean', 5, None, None, None, ''), - 'uploadRatio': ('double', 1, None, None, None, ''), - 'wanted': ('array', 1, None, None, None, ''), - 'webseeds': ('array', 1, None, None, None, ''), - 'webseedsSendingToUs': ('number', 1, None, None, None, ''), - }, - 'set': { - 'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'), - 'downloadLimit': ('number', 5, None, 'speed-limit-down', None, 'Set the speed limit for download in Kib/s.'), - 'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None, 'Enable download speed limiter.'), - 'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."), - 'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."), - 'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."), - 'location': ('array', 1, None, None, None, 'Local download location.'), - 'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'), - 'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."), - 'priority-low': ('array', 1, None, None, None, "A list of file id's that should have normal priority."), - 'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."), - 'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'), - 'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'), - 'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'), - 'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'), - 'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'), - 'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'), - 'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'), - 'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'), - 'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited', 'Enable upload speed limiter.'), - 'trackerAdd': ('array', 10, None, None, None, 'Array of string with announce URLs to add.'), - 'trackerRemove': ('array', 10, None, None, None, 'Array of ids of trackers to remove.'), - 'trackerReplace': ('array', 10, None, None, None, 'Array of (id, url) tuples where the announce URL should be replaced.'), - 'uploadLimit': ('number', 5, None, 'speed-limit-up', None, 'Set the speed limit for upload in Kib/s.'), - 'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None, 'Enable upload speed limiter.'), - }, - 'add': { - 'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'), - 'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'), - 'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'), - 'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."), - 'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."), - 'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."), - 'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'), - 'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'), - 'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'), - 'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."), - 'priority-low': ('array', 1, None, None, None, "A list of file id's that should have low priority."), - 'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have normal priority."), - } -} - -# Arguments for session methods -SESSION_ARGS = { - 'get': { - "alt-speed-down": ('number', 5, None, None, None, ''), - "alt-speed-enabled": ('boolean', 5, None, None, None, ''), - "alt-speed-time-begin": ('number', 5, None, None, None, ''), - "alt-speed-time-enabled": ('boolean', 5, None, None, None, ''), - "alt-speed-time-end": ('number', 5, None, None, None, ''), - "alt-speed-time-day": ('number', 5, None, None, None, ''), - "alt-speed-up": ('number', 5, None, None, None, ''), - "blocklist-enabled": ('boolean', 5, None, None, None, ''), - "blocklist-size": ('number', 5, None, None, None, ''), - "blocklist-url": ('string', 11, None, None, None, ''), - "cache-size-mb": ('number', 10, None, None, None, ''), - "config-dir": ('string', 8, None, None, None, ''), - "dht-enabled": ('boolean', 6, None, None, None, ''), - "download-dir": ('string', 1, None, None, None, ''), - "download-dir-free-space": ('number', 12, None, None, None, ''), - "download-queue-size": ('number', 14, None, None, None, ''), - "download-queue-enabled": ('boolean', 14, None, None, None, ''), - "encryption": ('string', 1, None, None, None, ''), - "idle-seeding-limit": ('number', 10, None, None, None, ''), - "idle-seeding-limit-enabled": ('boolean', 10, None, None, None, ''), - "incomplete-dir": ('string', 7, None, None, None, ''), - "incomplete-dir-enabled": ('boolean', 7, None, None, None, ''), - "lpd-enabled": ('boolean', 9, None, None, None, ''), - "peer-limit": ('number', 1, 5, None, None, ''), - "peer-limit-global": ('number', 5, None, None, None, ''), - "peer-limit-per-torrent": ('number', 5, None, None, None, ''), - "pex-allowed": ('boolean', 1, 5, None, None, ''), - "pex-enabled": ('boolean', 5, None, None, None, ''), - "port": ('number', 1, 5, None, None, ''), - "peer-port": ('number', 5, None, None, None, ''), - "peer-port-random-on-start": ('boolean', 5, None, None, None, ''), - "port-forwarding-enabled": ('boolean', 1, None, None, None, ''), - "queue-stalled-minutes": ('number', 14, None, None, None, ''), - "queue-stalled-enabled": ('boolean', 14, None, None, None, ''), - "rename-partial-files": ('boolean', 8, None, None, None, ''), - "rpc-version": ('number', 4, None, None, None, ''), - "rpc-version-minimum": ('number', 4, None, None, None, ''), - "script-torrent-done-enabled": ('boolean', 9, None, None, None, ''), - "script-torrent-done-filename": ('string', 9, None, None, None, ''), - "seedRatioLimit": ('double', 5, None, None, None, ''), - "seedRatioLimited": ('boolean', 5, None, None, None, ''), - "seed-queue-size": ('number', 14, None, None, None, ''), - "seed-queue-enabled": ('boolean', 14, None, None, None, ''), - "speed-limit-down": ('number', 1, None, None, None, ''), - "speed-limit-down-enabled": ('boolean', 1, None, None, None, ''), - "speed-limit-up": ('number', 1, None, None, None, ''), - "speed-limit-up-enabled": ('boolean', 1, None, None, None, ''), - "start-added-torrents": ('boolean', 9, None, None, None, ''), - "trash-original-torrent-files": ('boolean', 9, None, None, None, ''), - 'units': ('object', 10, None, None, None, ''), - 'utp-enabled': ('boolean', 13, None, None, None, ''), - "version": ('string', 3, None, None, None, ''), - }, - 'set': { - "alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'), - "alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'), - "alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'), - "alt-speed-time-enabled": ('boolean', 5, None, None, None, 'Enables alternate speeds scheduling.'), - "alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'), - "alt-speed-time-day": ('number', 5, None, None, None, 'Enables alternate speeds scheduling these days.'), - "alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'), - "blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'), - "blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'), - "cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'), - "dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'), - "download-dir": ('string', 1, None, None, None, 'Set the session download directory.'), - "download-queue-size": ('number', 14, None, None, None, 'Number of parallel downloads.'), - "download-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel download restriction.'), - "encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'), - "idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'), - "idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'), - "incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'), - "incomplete-dir-enabled": ('boolean', 7, None, None, None, 'Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.'), - "lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'), - "peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers'), - "peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers'), - "peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer'), - "pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'), - "pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'), - "port": ('number', 1, 5, None, 'peer-port', 'Peer port.'), - "peer-port": ('number', 5, None, 'port', None, 'Peer port.'), - "peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'), - "port-forwarding-enabled": ('boolean', 1, None, None, None, 'Enables port forwarding.'), - "rename-partial-files": ('boolean', 8, None, None, None, 'Appends ".part" to incomplete files'), - "queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'), - "queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'), - "script-torrent-done-enabled": ('boolean', 9, None, None, None, 'Whether or not to call the "done" script.'), - "script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'), - "seed-queue-size": ('number', 14, None, None, None, 'Number of parallel uploads.'), - "seed-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel upload restriction.'), - "seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'), - "seedRatioLimited": ('boolean', 5, None, None, None, 'Enables seed ration limit.'), - "speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'), - "speed-limit-down-enabled": ('boolean', 1, None, None, None, 'Enables download speed limiting.'), - "speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'), - "speed-limit-up-enabled": ('boolean', 1, None, None, None, 'Enables upload speed limiting.'), - "start-added-torrents": ('boolean', 9, None, None, None, 'Added torrents will be started right away.'), - "trash-original-torrent-files": ('boolean', 9, None, None, None, 'The .torrent file of added torrents will be deleted.'), - 'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'), - }, -} diff --git a/libs/transmissionrpc/error.py b/libs/transmissionrpc/error.py deleted file mode 100755 index 5e2c1e28..00000000 --- a/libs/transmissionrpc/error.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -class TransmissionError(Exception): - """ - This exception is raised when there has occurred an error related to - communication with Transmission. It is a subclass of Exception. - """ - def __init__(self, message='', original=None): - Exception.__init__(self) - self._message = message - self.original = original - - def __str__(self): - if self.original: - original_name = type(self.original).__name__ - return '%s Original exception: %s, "%s"' % (self._message, original_name, str(self.original)) - else: - return self._message - -class HTTPHandlerError(Exception): - """ - This exception is raised when there has occurred an error related to - the HTTP handler. It is a subclass of Exception. - """ - def __init__(self, httpurl=None, httpcode=None, httpmsg=None, httpheaders=None, httpdata=None): - Exception.__init__(self) - self.url = '' - self.code = 600 - self._message = '' - self.headers = {} - self.data = '' - if isinstance(httpurl, (str, unicode)): - self.url = httpurl - if isinstance(httpcode, (int, long)): - self.code = httpcode - if isinstance(httpmsg, (str, unicode)): - self._message = httpmsg - if isinstance(httpheaders, dict): - self.headers = httpheaders - if isinstance(httpdata, (str, unicode)): - self.data = httpdata - - def __repr__(self): - return '' % (self.code, self._message) - - def __str__(self): - return 'HTTPHandlerError %d: %s' % (self.code, self._message) - - def __unicode__(self): - return u'HTTPHandlerError %d: %s' % (self.code, self._message) diff --git a/libs/transmissionrpc/httphandler.py b/libs/transmissionrpc/httphandler.py deleted file mode 100755 index d34b7815..00000000 --- a/libs/transmissionrpc/httphandler.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2011 Erik Svensson -# Licensed under the MIT license. - -import sys, httplib, urllib2 - -from transmissionrpc.error import HTTPHandlerError - -class HTTPHandler(object): - """ - Prototype for HTTP handling. - """ - def set_authentication(self, uri, login, password): - """ - Transmission use basic authentication in earlier versions and digest - authentication in later versions. - - * uri, the authentication realm URI. - * login, the authentication login. - * password, the authentication password. - """ - raise NotImplementedError("Bad HTTPHandler, failed to implement set_authentication.") - - def request(self, url, query, headers, timeout): - """ - Implement a HTTP POST request here. - - * url, The URL to request. - * query, The query data to send. This is a JSON data string. - * headers, a dictionary of headers to send. - * timeout, requested request timeout in seconds. - """ - raise NotImplementedError("Bad HTTPHandler, failed to implement request.") - -class DefaultHTTPHandler(HTTPHandler): - """ - The default HTTP handler provided with transmissionrpc. - """ - def __init__(self): - HTTPHandler.__init__(self) - - def set_authentication(self, uri, login, password): - password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() - password_manager.add_password(realm=None, uri=uri, user=login, passwd=password) - opener = urllib2.build_opener( - urllib2.HTTPBasicAuthHandler(password_manager) - , urllib2.HTTPDigestAuthHandler(password_manager) - ) - urllib2.install_opener(opener) - - def request(self, url, query, headers, timeout): - request = urllib2.Request(url, query, headers) - try: - if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2: - response = urllib2.urlopen(request, timeout=timeout) - else: - response = urllib2.urlopen(request) - except urllib2.HTTPError, error: - if error.fp is None: - raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs)) - else: - raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs), error.read()) - except urllib2.URLError, error: - # urllib2.URLError documentation is horrendous! - # Try to get the tuple arguments of URLError - if hasattr(error.reason, 'args') and isinstance(error.reason.args, tuple) and len(error.reason.args) == 2: - raise HTTPHandlerError(httpcode=error.reason.args[0], httpmsg=error.reason.args[1]) - else: - raise HTTPHandlerError(httpmsg='urllib2.URLError: %s' % (error.reason)) - except httplib.BadStatusLine, error: - raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line)) - return response.read() diff --git a/libs/transmissionrpc/session.py b/libs/transmissionrpc/session.py deleted file mode 100755 index 532553cf..00000000 --- a/libs/transmissionrpc/session.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -from transmissionrpc.utils import Field - -class Session(object): - """ - Session is a class holding the session data for a Transmission daemon. - - Access the session field can be done through attributes. - The attributes available are the same as the session arguments in the - Transmission RPC specification, but with underscore instead of hyphen. - ``download-dir`` -> ``download_dir``. - """ - - def __init__(self, client=None, fields=None): - self._client = client - self._fields = {} - if fields is not None: - self._update_fields(fields) - - def __getattr__(self, name): - try: - return self._fields[name].value - except KeyError: - raise AttributeError('No attribute %s' % name) - - def __str__(self): - text = '' - for key in sorted(self._fields.keys()): - text += "% 32s: %s\n" % (key[-32:], self._fields[key].value) - return text - - def _update_fields(self, other): - """ - Update the session data from a Transmission JSON-RPC arguments dictionary - """ - fields = None - if isinstance(other, dict): - for key, value in other.iteritems(): - self._fields[key.replace('-', '_')] = Field(value, False) - elif isinstance(other, Session): - for key in other._fields.keys(): - self._fields[key] = Field(other._fields[key].value, False) - else: - raise ValueError('Cannot update with supplied data') - - def _dirty_fields(self): - """Enumerate changed fields""" - outgoing_keys = ['peer_port', 'pex_enabled'] - fields = [] - for key in outgoing_keys: - if key in self._fields and self._fields[key].dirty: - fields.append(key) - return fields - - def _push(self): - """Push changed fields to the server""" - dirty = self._dirty_fields() - args = {} - for key in dirty: - args[key] = self._fields[key].value - self._fields[key] = self._fields[key]._replace(dirty=False) - if len(args) > 0: - self._client.set_session(**args) - - def update(self, timeout=None): - """Update the session information.""" - self._push() - session = self._client.get_session(timeout=timeout) - self._update_fields(session) - session = self._client.session_stats(timeout=timeout) - self._update_fields(session) - - def from_request(self, data): - """Update the session information.""" - self._update_fields(data) - - def _get_peer_port(self): - """ - Get the peer port. - """ - return self._fields['peer_port'].value - - def _set_peer_port(self, port): - """ - Set the peer port. - """ - if isinstance(port, (int, long)): - self._fields['peer_port'] = Field(port, True) - self._push() - else: - raise ValueError("Not a valid limit") - - peer_port = property(_get_peer_port, _set_peer_port, None, "Peer port. This is a mutator.") - - def _get_pex_enabled(self): - return self._fields['pex_enabled'].value - - def _set_pex_enabled(self, enabled): - if isinstance(enabled, bool): - self._fields['pex_enabled'] = Field(enabled, True) - self._push() - else: - raise TypeError("Not a valid type") - - pex_enabled = property(_get_pex_enabled, _set_pex_enabled, None, "Enable PEX. This is a mutator.") diff --git a/libs/transmissionrpc/torrent.py b/libs/transmissionrpc/torrent.py deleted file mode 100755 index 05008784..00000000 --- a/libs/transmissionrpc/torrent.py +++ /dev/null @@ -1,468 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -import sys, datetime - -from transmissionrpc.constants import PRIORITY, RATIO_LIMIT, IDLE_LIMIT -from transmissionrpc.utils import Field, format_timedelta - -class Torrent(object): - """ - Torrent is a class holding the data received from Transmission regarding a bittorrent transfer. - All fetched torrent fields are accessible through this class using attributes. - This class has a few convenience properties using the torrent data. - """ - - def __init__(self, client, fields): - if 'id' not in fields: - raise ValueError('Torrent requires an id') - self._fields = {} - self._update_fields(fields) - self._incoming_pending= False - self._outgoing_pending= False - self._client = client - - def _getNameString(self, codec=None): - if codec is None: - codec = sys.getdefaultencoding() - name = None - # try to find name - if 'name' in self._fields: - name = self._fields['name'].value - # if name is unicode, try to decode - if isinstance(name, unicode): - try: - name = name.encode(codec) - except UnicodeError: - name = None - return name - - def __repr__(self): - tid = self._fields['id'].value - name = self._getNameString() - if isinstance(name, str): - return '' % (tid, name) - else: - return '' % (tid) - - def __str__(self): - name = self._getNameString() - if isinstance(name, str): - return 'Torrent \"%s\"' % (name) - else: - return 'Torrent' - - def __copy__(self): - return Torrent(self._client, self._fields) - - def __getattr__(self, name): - try: - return self._fields[name].value - except KeyError: - raise AttributeError('No attribute %s' % name) - - def _rpc_version(self): - if self._client: - return self._client.rpc_version - return 2 - - def _dirty_fields(self): - """Enumerate changed fields""" - outgoing_keys = ['bandwidthPriority', 'downloadLimit', 'downloadLimited', 'peer_limit', 'queuePosition' - , 'seedIdleLimit', 'seedIdleMode', 'seedRatioLimit', 'seedRatioMode', 'uploadLimit', 'uploadLimited'] - fields = [] - for key in outgoing_keys: - if key in self._fields and self._fields[key].dirty: - fields.append(key) - return fields - - def _push(self): - """Push changed fields to the server""" - dirty = self._dirty_fields() - args = {} - for key in dirty: - args[key] = self._fields[key].value - self._fields[key] = self._fields[key]._replace(dirty=False) - if len(args) > 0: - self._client.change_torrent(self.id, **args) - - def _update_fields(self, other): - """ - Update the torrent data from a Transmission JSON-RPC arguments dictionary - """ - fields = None - if isinstance(other, dict): - for key, value in other.iteritems(): - self._fields[key.replace('-', '_')] = Field(value, False) - elif isinstance(other, Torrent): - for key in other._fields.keys(): - self._fields[key] = Field(other._fields[key].value, False) - else: - raise ValueError('Cannot update with supplied data') - self._incoming_pending = False - - def _status_old(self, code): - mapping = { - (1<<0): 'check pending', - (1<<1): 'checking', - (1<<2): 'downloading', - (1<<3): 'seeding', - (1<<4): 'stopped', - } - return mapping[code] - - def _status_new(self, code): - mapping = { - 0: 'stopped', - 1: 'check pending', - 2: 'checking', - 3: 'download pending', - 4: 'downloading', - 5: 'seed pending', - 6: 'seeding', - } - return mapping[code] - - def _status(self): - code = self._fields['status'].value - if self._rpc_version() >= 14: - return self._status_new(code) - else: - return self._status_old(code) - - def files(self): - """ - Get list of files for this torrent. - - This function returns a dictionary with file information for each file. - The file information is has following fields: - :: - - { - : { - 'name': , - 'size': , - 'completed': , - 'priority': , - 'selected': - } - ... - } - """ - result = {} - if 'files' in self._fields: - files = self._fields['files'].value - indices = xrange(len(files)) - priorities = self._fields['priorities'].value - wanted = self._fields['wanted'].value - for item in zip(indices, files, priorities, wanted): - selected = True if item[3] else False - priority = PRIORITY[item[2]] - result[item[0]] = { - 'selected': selected, - 'priority': priority, - 'size': item[1]['length'], - 'name': item[1]['name'], - 'completed': item[1]['bytesCompleted']} - return result - - @property - def status(self): - """ - Returns the torrent status. Is either one of 'check pending', 'checking', - 'downloading', 'seeding' or 'stopped'. The first two is related to - verification. - """ - return self._status() - - @property - def progress(self): - """Get the download progress in percent.""" - try: - size = self._fields['sizeWhenDone'].value - left = self._fields['leftUntilDone'].value - return 100.0 * (size - left) / float(size) - except ZeroDivisionError: - return 0.0 - - @property - def ratio(self): - """Get the upload/download ratio.""" - return float(self._fields['uploadRatio'].value) - - @property - def eta(self): - """Get the "eta" as datetime.timedelta.""" - eta = self._fields['eta'].value - if eta >= 0: - return datetime.timedelta(seconds=eta) - else: - ValueError('eta not valid') - - @property - def date_active(self): - """Get the attribute "activityDate" as datetime.datetime.""" - return datetime.datetime.fromtimestamp(self._fields['activityDate'].value) - - @property - def date_added(self): - """Get the attribute "addedDate" as datetime.datetime.""" - return datetime.datetime.fromtimestamp(self._fields['addedDate'].value) - - @property - def date_started(self): - """Get the attribute "startDate" as datetime.datetime.""" - return datetime.datetime.fromtimestamp(self._fields['startDate'].value) - - @property - def date_done(self): - """Get the attribute "doneDate" as datetime.datetime.""" - return datetime.datetime.fromtimestamp(self._fields['doneDate'].value) - - def format_eta(self): - """ - Returns the attribute *eta* formatted as a string. - - * If eta is -1 the result is 'not available' - * If eta is -2 the result is 'unknown' - * Otherwise eta is formatted as ::. - """ - eta = self._fields['eta'].value - if eta == -1: - return 'not available' - elif eta == -2: - return 'unknown' - else: - return format_timedelta(self.eta) - - def _get_download_limit(self): - """ - Get the download limit. - Can be a number or None. - """ - if self._fields['downloadLimited'].value: - return self._fields['downloadLimit'].value - else: - return None - - def _set_download_limit(self, limit): - """ - Get the download limit. - Can be a number, 'session' or None. - """ - if isinstance(limit, (int, long)): - self._fields['downloadLimited'] = Field(True, True) - self._fields['downloadLimit'] = Field(limit, True) - self._push() - elif limit == None: - self._fields['downloadLimited'] = Field(False, True) - self._push() - else: - raise ValueError("Not a valid limit") - - download_limit = property(_get_download_limit, _set_download_limit, None, "Download limit in Kbps or None. This is a mutator.") - - def _get_peer_limit(self): - """ - Get the peer limit. - """ - return self._fields['peer_limit'].value - - def _set_peer_limit(self, limit): - """ - Set the peer limit. - """ - if isinstance(limit, (int, long)): - self._fields['peer_limit'] = Field(limit, True) - self._push() - else: - raise ValueError("Not a valid limit") - - peer_limit = property(_get_peer_limit, _set_peer_limit, None, "Peer limit. This is a mutator.") - - def _get_priority(self): - """ - Get the priority as string. - Can be one of 'low', 'normal', 'high'. - """ - return PRIORITY[self._fields['bandwidthPriority'].value] - - def _set_priority(self, priority): - """ - Set the priority as string. - Can be one of 'low', 'normal', 'high'. - """ - if isinstance(priority, (str, unicode)): - self._fields['bandwidthPriority'] = Field(PRIORITY[priority], True) - self._push() - - priority = property(_get_priority, _set_priority, None - , "Bandwidth priority as string. Can be one of 'low', 'normal', 'high'. This is a mutator.") - - def _get_seed_idle_limit(self): - """ - Get the seed idle limit in minutes. - """ - return self._fields['seedIdleLimit'].value - - def _set_seed_idle_limit(self, limit): - """ - Set the seed idle limit in minutes. - """ - if isinstance(limit, (int, long)): - self._fields['seedIdleLimit'] = Field(limit, True) - self._push() - else: - raise ValueError("Not a valid limit") - - seed_idle_limit = property(_get_seed_idle_limit, _set_seed_idle_limit, None - , "Torrent seed idle limit in minutes. Also see seed_idle_mode. This is a mutator.") - - def _get_seed_idle_mode(self): - """ - Get the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'. - """ - return IDLE_LIMIT[self._fields['seedIdleMode'].value] - - def _set_seed_idle_mode(self, mode): - """ - Set the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'. - """ - if isinstance(mode, str): - self._fields['seedIdleMode'] = Field(IDLE_LIMIT[mode], True) - self._push() - else: - raise ValueError("Not a valid limit") - - seed_idle_mode = property(_get_seed_idle_mode, _set_seed_idle_mode, None, - """ - Seed idle mode as string. Can be one of 'global', 'single' or 'unlimited'. - - * global, use session seed idle limit. - * single, use torrent seed idle limit. See seed_idle_limit. - * unlimited, no seed idle limit. - - This is a mutator. - """ - ) - - def _get_seed_ratio_limit(self): - """ - Get the seed ratio limit as float. - """ - return float(self._fields['seedRatioLimit'].value) - - def _set_seed_ratio_limit(self, limit): - """ - Set the seed ratio limit as float. - """ - if isinstance(limit, (int, long, float)) and limit >= 0.0: - self._fields['seedRatioLimit'] = Field(float(limit), True) - self._push() - else: - raise ValueError("Not a valid limit") - - seed_ratio_limit = property(_get_seed_ratio_limit, _set_seed_ratio_limit, None - , "Torrent seed ratio limit as float. Also see seed_ratio_mode. This is a mutator.") - - def _get_seed_ratio_mode(self): - """ - Get the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'. - """ - return RATIO_LIMIT[self._fields['seedRatioMode'].value] - - def _set_seed_ratio_mode(self, mode): - """ - Set the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'. - """ - if isinstance(mode, str): - self._fields['seedRatioMode'] = Field(RATIO_LIMIT[mode], True) - self._push() - else: - raise ValueError("Not a valid limit") - - seed_ratio_mode = property(_get_seed_ratio_mode, _set_seed_ratio_mode, None, - """ - Seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'. - - * global, use session seed ratio limit. - * single, use torrent seed ratio limit. See seed_ratio_limit. - * unlimited, no seed ratio limit. - - This is a mutator. - """ - ) - - def _get_upload_limit(self): - """ - Get the upload limit. - Can be a number or None. - """ - if self._fields['uploadLimited'].value: - return self._fields['uploadLimit'].value - else: - return None - - def _set_upload_limit(self, limit): - """ - Set the upload limit. - Can be a number, 'session' or None. - """ - if isinstance(limit, (int, long)): - self._fields['uploadLimited'] = Field(True, True) - self._fields['uploadLimit'] = Field(limit, True) - self._push() - elif limit == None: - self._fields['uploadLimited'] = Field(False, True) - self._push() - else: - raise ValueError("Not a valid limit") - - upload_limit = property(_get_upload_limit, _set_upload_limit, None, "Upload limit in Kbps or None. This is a mutator.") - - def _get_queue_position(self): - if self._rpc_version() >= 14: - return self._fields['queuePosition'].value - else: - return 0 - - def _set_queue_position(self, position): - if self._rpc_version() >= 14: - if isinstance(position, (int, long)): - self._fields['queuePosition'] = Field(position, True) - self._push() - else: - raise ValueError("Not a valid position") - else: - pass - - queue_position = property(_get_queue_position, _set_queue_position, None, "Queue position") - - def update(self, timeout=None): - """Update the torrent information.""" - self._push() - torrent = self._client.get_torrent(self.id, timeout=timeout) - self._update_fields(torrent) - - def start(self, bypass_queue=False, timeout=None): - """ - Start the torrent. - """ - self._incoming_pending = True - self._client.start_torrent(self.id, bypass_queue=bypass_queue, timeout=timeout) - - def stop(self, timeout=None): - """Stop the torrent.""" - self._incoming_pending = True - self._client.stop_torrent(self.id, timeout=timeout) - - def move_data(self, location, timeout=None): - """Move torrent data to location.""" - self._incoming_pending = True - self._client.move_torrent_data(self.id, location, timeout=timeout) - - def locate_data(self, location, timeout=None): - """Locate torrent data at location.""" - self._incoming_pending = True - self._client.locate_torrent_data(self.id, location, timeout=timeout) diff --git a/libs/transmissionrpc/utils.py b/libs/transmissionrpc/utils.py deleted file mode 100755 index 59f207a6..00000000 --- a/libs/transmissionrpc/utils.py +++ /dev/null @@ -1,191 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2008-2011 Erik Svensson -# Licensed under the MIT license. - -import socket, datetime, logging -from collections import namedtuple -import transmissionrpc.constants as constants -from transmissionrpc.constants import LOGGER - -UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'] - -def format_size(size): - """ - Format byte size into IEC prefixes, B, KiB, MiB ... - """ - size = float(size) - i = 0 - while size >= 1024.0 and i < len(UNITS): - i += 1 - size /= 1024.0 - return (size, UNITS[i]) - -def format_speed(size): - """ - Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ... - """ - (size, unit) = format_size(size) - return (size, unit + '/s') - -def format_timedelta(delta): - """ - Format datetime.timedelta into ::. - """ - minutes, seconds = divmod(delta.seconds, 60) - hours, minutes = divmod(minutes, 60) - return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds) - -def format_timestamp(timestamp, utc=False): - """ - Format unix timestamp into ISO date format. - """ - if timestamp > 0: - if utc: - dt_timestamp = datetime.datetime.utcfromtimestamp(timestamp) - else: - dt_timestamp = datetime.datetime.fromtimestamp(timestamp) - return dt_timestamp.isoformat(' ') - else: - return '-' - -class INetAddressError(Exception): - """ - Error parsing / generating a internet address. - """ - pass - -def inet_address(address, default_port, default_address='localhost'): - """ - Parse internet address. - """ - addr = address.split(':') - if len(addr) == 1: - try: - port = int(addr[0]) - addr = default_address - except ValueError: - addr = addr[0] - port = default_port - elif len(addr) == 2: - try: - port = int(addr[1]) - except ValueError: - raise INetAddressError('Invalid address "%s".' % address) - if len(addr[0]) == 0: - addr = default_address - else: - addr = addr[0] - else: - raise INetAddressError('Invalid address "%s".' % address) - try: - socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM) - except socket.gaierror: - raise INetAddressError('Cannot look up address "%s".' % address) - return (addr, port) - -def rpc_bool(arg): - """ - Convert between Python boolean and Transmission RPC boolean. - """ - if isinstance(arg, (str, unicode)): - try: - arg = bool(int(arg)) - except ValueError: - arg = arg.lower() in [u'true', u'yes'] - return 1 if bool(arg) else 0 - -TR_TYPE_MAP = { - 'number' : int, - 'string' : str, - 'double': float, - 'boolean' : rpc_bool, - 'array': list, - 'object': dict -} - -def make_python_name(name): - """ - Convert Transmission RPC name to python compatible name. - """ - return name.replace('-', '_') - -def make_rpc_name(name): - """ - Convert python compatible name to Transmission RPC name. - """ - return name.replace('_', '-') - -def argument_value_convert(method, argument, value, rpc_version): - """ - Check and fix Transmission RPC issues with regards to methods, arguments and values. - """ - if method in ('torrent-add', 'torrent-get', 'torrent-set'): - args = constants.TORRENT_ARGS[method[-3:]] - elif method in ('session-get', 'session-set'): - args = constants.SESSION_ARGS[method[-3:]] - else: - return ValueError('Method "%s" not supported' % (method)) - if argument in args: - info = args[argument] - invalid_version = True - while invalid_version: - invalid_version = False - replacement = None - if rpc_version < info[1]: - invalid_version = True - replacement = info[3] - if info[2] and info[2] <= rpc_version: - invalid_version = True - replacement = info[4] - if invalid_version: - if replacement: - LOGGER.warning( - 'Replacing requested argument "%s" with "%s".' - % (argument, replacement)) - argument = replacement - info = args[argument] - else: - raise ValueError( - 'Method "%s" Argument "%s" does not exist in version %d.' - % (method, argument, rpc_version)) - return (argument, TR_TYPE_MAP[info[0]](value)) - else: - raise ValueError('Argument "%s" does not exists for method "%s".', - (argument, method)) - -def get_arguments(method, rpc_version): - """ - Get arguments for method in specified Transmission RPC version. - """ - if method in ('torrent-add', 'torrent-get', 'torrent-set'): - args = constants.TORRENT_ARGS[method[-3:]] - elif method in ('session-get', 'session-set'): - args = constants.SESSION_ARGS[method[-3:]] - else: - return ValueError('Method "%s" not supported' % (method)) - accessible = [] - for argument, info in args.iteritems(): - valid_version = True - if rpc_version < info[1]: - valid_version = False - if info[2] and info[2] <= rpc_version: - valid_version = False - if valid_version: - accessible.append(argument) - return accessible - -def add_stdout_logger(level='debug'): - """ - Add a stdout target for the transmissionrpc logging. - """ - levels = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR} - - trpc_logger = logging.getLogger('transmissionrpc') - loghandler = logging.StreamHandler() - if level in levels.keys(): - loglevel = levels[level] - trpc_logger.setLevel(loglevel) - loghandler.setLevel(loglevel) - trpc_logger.addHandler(loghandler) - -Field = namedtuple('Field', ['value', 'dirty']) \ No newline at end of file From 99a4763c4b2d855922303eb35df43fa451b67fe5 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Mon, 2 Jul 2012 13:23:28 +0200 Subject: [PATCH 07/16] fix typo in publichd --- couchpotato/core/providers/torrent/publichd/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index 129832b0..ca5c980b 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -82,7 +82,7 @@ class PublicHD(TorrentProvider): new['size'] = self.parseSize(result.findAll('td' )[7].string) new['seeders'] = int(result.findAll('td')[4].string) - new['Leechers'] = int(result.findAll('td' + new['leechers'] = int(result.findAll('td' )[5].string) new['imdbid'] = movie['library']['identifier'] From d8eabf635a6a81c32cd231e4e37e4a6631f1587d Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Mon, 2 Jul 2012 13:30:35 +0200 Subject: [PATCH 08/16] Magnet type for TPB and transmssion support --- .../core/downloaders/transmission/main.py | 1 - .../providers/torrent/thepiratebay/main.py | 44 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index f8cac7b2..3ac8c21d 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -167,7 +167,6 @@ class Transmission(Downloader): remote_torrent = tc.add_torrent_file(b64encode(filedata), arguments=params) # Change settings of added torrents - print remote_torrent tc.set_torrent(remote_torrent['torrent-added']['hashString' ], torrent_params) return True diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index 626e1210..885b4c60 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -14,8 +14,6 @@ log = CPLog(__name__) class ThePirateBay(TorrentProvider): - urls = {'detail': '%s/torrent/%s', 'search': '%s/search/%s/0/7/%d'} - cat_ids = [([207], ['720p', '1080p']), ([201], [ 'cam', 'ts', @@ -25,10 +23,15 @@ class ThePirateBay(TorrentProvider): 'scr', 'brrip', ]), ([202], ['dvdr'])] + cat_backup_id = 200 - def getAPIurl(self): - return ("http://thepiratebay.se", self.conf('domain_for_tpb'))[self.conf('domain_for_tpb') != None] + def __init__(self): + super(ThePirateBay, self).__init__() + self.urls = {"test": self.getAPIurl(), 'detail': '%s/torrent/%s', 'search': '%s/search/%s/0/7/%d'} + + def getAPIurl(self, url=""): + return (("http://thepiratebay.se", self.conf('domain_for_tpb'))[self.conf('domain_for_tpb') != None]) + url def search(self, movie, quality): @@ -38,8 +41,10 @@ class ThePirateBay(TorrentProvider): movie_name = re.sub("\W", ' ', getTitle(movie['library'])) movie_name = re.sub(' ', ' ', movie_name) + log.info('API url: %s', self.getAPIurl()) log.info('Cleaned Name: %s', movie_name) + cache_key = 'thepiratebay.%s.%s' % (movie['library' ]['identifier'], quality.get('identifier')) searchUrl = self.urls['search'] % (self.getAPIurl(), @@ -64,19 +69,20 @@ class ThePirateBay(TorrentProvider): size = re.search('Size (?P.+),', unicode(result.select("font.detDesc")[0])).group("size") if link and download: new = { - 'type': 'torrent', + 'type': 'magnet', 'check_nzb': False, 'description': '', 'provider': self.getName(), } + trusted = (0, 10)[result.find('img', alt=re.compile('Trusted')) != None] vip = (0, 20)[result.find('img', alt=re.compile('VIP')) != None] moderated = (0, 50)[result.find('img', alt=re.compile('Moderator')) != None] - log.info('Name: %s', link.string) + log.info('Name: %s', link.string) log.info('Seeders: %s', result.findAll('td' )[2].string) log.info('Leechers: %s', result.findAll('td' @@ -88,24 +94,26 @@ class ThePirateBay(TorrentProvider): new['name'] = link.string new['id'] = re.search('/(?P\d+)/', link['href' ]).group('id') - new['url'] = link['href'] - new['download'] = self.getAPIurl() + download['href'] + new['url'] = self.getAPIurl(link['href']) + new['magnet'] = unicode(download['href']) # forcing of storing full magnet data new['size'] = self.parseSize(size) new['seeders'] = int(result.findAll('td')[2].string) new['leechers'] = int(result.findAll('td' )[3].string) - new['imdbid'] = movie['library']['identifier'] - new['prtbscore'] = trusted + vip + moderated + new['TPB_score'] = trusted + vip + moderated new['extra_score'] = self.extra_score new['score'] = fireEvent('score.calculate', new, movie, single=True) + + isImdb = self.imdbMatch(self.getAPIurl(link['href']), movie['library']['identifier']) + is_correct_movie = fireEvent( 'searcher.correct_movie', nzb=new, movie=movie, quality=quality, - imdb_results=True, + imdb_results=isImdb, single_category=False, single=True, ) @@ -122,10 +130,7 @@ class ThePirateBay(TorrentProvider): return results def extra_score(self, torrent): - url = self.getAPIurl() + torrent['url'] - log.info('extra_score: %s', url) - imdbId = torrent['imdbid'] - return self.imdbMatch(url, imdbId) + torrent['prtbscore'] + return torrent["TPB_score"] def imdbMatch(self, url, imdbId): log.info('imdbMatch: %s', url) @@ -134,10 +139,13 @@ class ThePirateBay(TorrentProvider): pass except: log.error('Failed to open %s.' % url) - return 0 + return False imdbIdAlt = re.sub('tt[0]*', 'tt', imdbId) data = unicode(data, errors='ignore') if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ + imdbIdAlt in data: - return 50 - return 0 + return True + return False + + def download(self, url='', nzb_id=''): + return url From 42641c520a0fa244529941f77064f068a2d310d1 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Mon, 2 Jul 2012 13:36:37 +0200 Subject: [PATCH 09/16] removing cachedump from publichd --- couchpotato/core/providers/torrent/publichd/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index ca5c980b..cbd7efa4 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -47,7 +47,6 @@ class PublicHD(TorrentProvider): log.error('Failed to get data from %s.', searchUrl) return results - print data try: soup = BeautifulSoup(data) From 95383bf74d26898f6e5deef0a03f5e34f1693f89 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Tue, 3 Jul 2012 12:17:26 +0200 Subject: [PATCH 10/16] pylint rules file, disabling # C0111 Missing docstring # I0011 Warning locally suppressed using disable-msg # I0012 Warning locally suppressed using disable-msg # W0704 Except doesn't do anything Used when an except clause does nothing but "pass" and there is no "else" clause # W0142 Used * or * magic* Used when a function or method is called using *args or **kwargs to dispatch arguments. # W0212 Access to a protected member %s of a client class # W0232 Class has no __init__ method Used when a class has no __init__ method, neither its parent classes. # W0613 Unused argument %r Used when a function or method argument is not used. # W0702 No exception's type specified Used when an except clause doesn't specify exceptions type to catch. # R0201 Method could be a function # W0614 Unused import XYZ from wildcard import # R0914 Too many local variables # R0912 Too many branches # R0915 Too many statements # R0913 Too many arguments # R0904 Too many public methods --- pylint.rc | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 pylint.rc diff --git a/pylint.rc b/pylint.rc new file mode 100644 index 00000000..6ec0628c --- /dev/null +++ b/pylint.rc @@ -0,0 +1,265 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +# C0111 Missing docstring +# I0011 Warning locally suppressed using disable-msg +# I0012 Warning locally suppressed using disable-msg +# W0704 Except doesn't do anything Used when an except clause does nothing but "pass" and there is no "else" clause +# W0142 Used * or * magic* Used when a function or method is called using *args or **kwargs to dispatch arguments. +# W0212 Access to a protected member %s of a client class +# W0232 Class has no __init__ method Used when a class has no __init__ method, neither its parent classes. +# W0613 Unused argument %r Used when a function or method argument is not used. +# W0702 No exception's type specified Used when an except clause doesn't specify exceptions type to catch. +# R0201 Method could be a function +# W0614 Unused import XYZ from wildcard import +# R0914 Too many local variables +# R0912 Too many branches +# R0915 Too many statements +# R0913 Too many arguments +# R0904 Too many public methods +disable=C0111,I0011,I0012,W0704,W0142,W0212,W0232,W0613,W0702,R0201,W0614,R0914,R0912,R0915,R0913,R0904,R0801,W0703 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=no + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. Python regular +# expressions are accepted. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=yes + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=25 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=17 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=0 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=40 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception From dfd522d7b903350ab57c3a3676d6abb9a22db50c Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Tue, 3 Jul 2012 12:17:59 +0200 Subject: [PATCH 11/16] fixing some lint errors --- .../core/downloaders/transmission/main.py | 79 ++++++++++--------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index 3ac8c21d..3864e1d6 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -9,13 +9,14 @@ import urllib2 import httplib import json import re -log = CPLog(__name__) class TransmissionRPC(object): """TransmissionRPC lite library""" + log = CPLog(__name__) + def __init__( self, host='localhost', @@ -41,7 +42,7 @@ class TransmissionRPC(object): 'couchpotato-transmission-client/1.0')] urllib2.install_opener(opener) elif username or password: - log.debug('User or password missing, not using authentication.' + self.log.debug('User or password missing, not using authentication.' ) self.session = self.get_session() @@ -54,62 +55,62 @@ class TransmissionRPC(object): try: open_request = urllib2.urlopen(request) response = json.loads(open_request.read()) - log.debug('response: ' + self.log.debug('response: ' + str(json.dumps(response).encode('utf-8'))) if response['result'] == 'success': - log.debug(u'Transmission action successfull') + self.log.debug(u'Transmission action successfull') return response['arguments'] else: - log.debug('Unknown failure sending command to Transmission. Return text is: ' + self.log.debug('Unknown failure sending command to Transmission. Return text is: ' + response['result']) return False - except httplib.InvalidURL, e: - log.error(u'Invalid Transmission host, check your config %s' - % e) + except httplib.InvalidURL, err: + self.log.error(u'Invalid Transmission host, check your config %s' + % err) return False - except urllib2.HTTPError, e: - if e.code == 401: - log.error(u'Invalid Transmission Username or Password, check your config' + except urllib2.HTTPError, err: + if err.code == 401: + self.log.error(u'Invalid Transmission Username or Password, check your config' ) return False - elif e.code == 409: - msg = str(e.read()) + elif err.code == 409: + msg = str(err.read()) try: self.session_id = \ re.search('X-Transmission-Session-Id:\s*(\w+)', msg).group(1) - log.debug('X-Transmission-Session-Id: ' + self.log.debug('X-Transmission-Session-Id: ' + self.session_id) # #resend request with the updated header return self._request(ojson) except: - log.error(u'Unable to get Transmission Session-Id %s' - % e) + self.log.error(u'Unable to get Transmission Session-Id %s' + % err) else: - log.error(u'TransmissionRPC HTTPError: %s' % e) - except urllib2.URLError, e: - log.error(u'Unable to connect to Transmission %s' % e) + self.log.error(u'TransmissionRPC HTTPError: %s' % err) + except urllib2.URLError, err: + self.log.error(u'Unable to connect to Transmission %s' % err) def get_session(self): post_data = {'method': 'session-get', 'tag': self.tag} return self._request(post_data) - def add_torrent_uri(self, torrent, arguments={}): + def add_torrent_uri(self, torrent, arguments): arguments['filename'] = torrent post_data = {'arguments': arguments, 'method': 'torrent-add', 'tag': self.tag} return self._request(post_data) - def add_torrent_file(self, torrent, arguments={}): + def add_torrent_file(self, torrent, arguments): arguments['metainfo'] = torrent post_data = {'arguments': arguments, 'method': 'torrent-add', 'tag': self.tag} return self._request(post_data) - def set_torrent(self, id, arguments={}): - arguments['ids'] = id + def set_torrent(self, torrent_id, arguments): + arguments['ids'] = torrent_id post_data = {'arguments': arguments, 'method': 'torrent-set', 'tag': self.tag} return self._request(post_data) @@ -118,11 +119,12 @@ class TransmissionRPC(object): class Transmission(Downloader): type = ['torrent', 'magnet'] + log = CPLog(__name__) def download( self, - data={}, - movie={}, + data, + movie, manual=False, filedata=None, ): @@ -132,14 +134,14 @@ class Transmission(Downloader): or not self.isCorrectType(data.get('type')): return - log.debug('Sending "%s" to Transmission.', data.get('name')) - log.debug('Type "%s" to Transmission.', data.get('type')) + self.log.debug('Sending "%s" to Transmission.', data.get('name')) + self.log.debug('Type "%s" to Transmission.', data.get('type')) # 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.' + self.log.error('Config properties are not filled in correctly, port is missing.' ) return False @@ -153,23 +155,28 @@ class Transmission(Downloader): ) else 1)} if not filedata or data.get('type') != 'magnet': - log.error('Failed sending torrent, no data') + self.log.error('Failed sending torrent, no data') # Send request to Transmission try: - tc = TransmissionRPC(host[0], port=host[1], + trpc = TransmissionRPC(host[0], port=host[1], username=self.conf('username'), password=self.conf('password')) - if data.get('type') == 'magnet' or data.get('magnet') != None: - remote_torrent = tc.add_torrent_uri(data.get('magnet'), arguments=params) + if data.get('type') == 'magnet' or data.get('magnet') \ + != None: + remote_torrent = trpc.add_torrent_uri(data.get('magnet'), + arguments=params) else: - remote_torrent = tc.add_torrent_file(b64encode(filedata), arguments=params) + remote_torrent = \ + trpc.add_torrent_file(b64encode(filedata), + arguments=params) # Change settings of added torrents - tc.set_torrent(remote_torrent['torrent-added']['hashString' + + trpc.set_torrent(remote_torrent['torrent-added']['hashString' ], torrent_params) return True - except Exception, e: - log.error('Failed to change settings for transfer: %s', e) + except Exception, err: + self.log.error('Failed to change settings for transfer: %s', err) return False From a60b227e819dea93110032dee743ef679d46ce05 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Tue, 3 Jul 2012 12:32:12 +0200 Subject: [PATCH 12/16] for_search, imdb_match added to class --- couchpotato/core/providers/base.py | 22 ++++ .../core/providers/torrent/publichd/main.py | 94 ++++++--------- .../providers/torrent/thepiratebay/main.py | 107 ++++++++---------- 3 files changed, 103 insertions(+), 120 deletions(-) diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py index 0397784c..d4a35442 100644 --- a/couchpotato/core/providers/base.py +++ b/couchpotato/core/providers/base.py @@ -3,6 +3,9 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env from urlparse import urlparse +from urllib import quote_plus +from couchpotato.core.helpers.encoding import simplifyString + import re import time @@ -105,5 +108,24 @@ class YarrProvider(Provider): return [self.cat_backup_id] + def imdb_match(self, url, imdb_id): + """ Searches for imdb_id in url of webpage """ + log.info('Finding if imbd_id(%s) is found in url: %s' % (imdb_id, url)) + try: + data = self.urlopen(url) + except: + log.error('Failed to open %s.' % url) + return False + imdb_id_alt = re.sub('tt[0]*', 'tt', imdb_id) + data = unicode(data, errors='ignore') + if 'imdb.com/title/' + imdb_id in data or 'imdb.com/title/' \ + + imdb_id_alt in data: + return True + return False + + def for_search(self, string): + """ Prepare string for search, removing all characters that might confuse search engine""" + return quote_plus(simplifyString(string)) + def found(self, new): log.info('Found: score(%(score)s) on %(provider)s: %(name)s', new) diff --git a/couchpotato/core/providers/torrent/publichd/main.py b/couchpotato/core/providers/torrent/publichd/main.py index cbd7efa4..673ca303 100644 --- a/couchpotato/core/providers/torrent/publichd/main.py +++ b/couchpotato/core/providers/torrent/publichd/main.py @@ -6,15 +6,14 @@ from couchpotato.core.event import fireEvent from couchpotato.core.helpers.variable import getTitle from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider -import re from urlparse import parse_qs -from urllib import quote_plus -import urllib2 -log = CPLog(__name__) + +import re class PublicHD(TorrentProvider): + log = CPLog(__name__) urls = { 'test': 'http://publichd.eu', 'download': 'http://publichd.eu/%s', @@ -22,37 +21,35 @@ class PublicHD(TorrentProvider): 'search': 'http://publichd.eu/index.php?page=torrents&search=%s&active=1&category=%d', } - cat_ids = [([2], ['720p']), ([5], ['1080p']), ([15], ['bdrip']), - ([16], ['brrip']), ([16], ['blue-ray'])] + cat_ids = [([2], ['720p']), ([5], ['1080p']), ([16], ['brrip']), + ([16], ['bd50'])] cat_backup_id = 0 def search(self, movie, quality): results = [] - if self.isDisabled(): + if self.isDisabled() and quality['hd'] != True: return results - movie_name = re.sub("\W", ' ', getTitle(movie['library'])) - movie_name = re.sub(' ', ' ', movie_name) - log.info('Cleaned Name: %s', movie_name) cache_key = 'publichd.%s.%s' % (movie['library']['identifier'], quality.get('identifier')) - searchUrl = self.urls['search'] % (quote_plus(movie_name + ' ' - + quality['identifier']), - self.getCatId(quality['identifier'])[0]) - log.info('searchUrl: %s', searchUrl) - data = self.getCache(cache_key, searchUrl) + search_url = self.urls['search'] \ + % (self.for_search(getTitle(movie['library']) + + ' ' + quality['identifier']), + self.getCatId(quality['identifier'])[0]) + self.log.info('searchUrl: %s', search_url) + data = self.getCache(cache_key, search_url) if not data: - log.error('Failed to get data from %s.', searchUrl) + self.log.error('Failed to get data from %s.', search_url) return results try: soup = BeautifulSoup(data) - resultsTable = soup.find('table', attrs={'id': 'bgtorrlist2' - }) - entries = resultsTable.findAll('tr') + results_table = soup.find('table', + attrs={'id': 'bgtorrlist2'}) + entries = results_table.find_all('tr') for result in entries[2:len(entries) - 1]: info_url = result.find(href=re.compile('torrent-details' )) @@ -65,12 +62,14 @@ class PublicHD(TorrentProvider): 'description': '', 'provider': self.getName(), } - log.info('Name: %s', result.findAll('td')[1].string) - log.info('Seeders: %s', result.findAll('td' - )[4].string) - log.info('Leaches: %s', result.findAll('td' - )[5].string) - log.info('Size: %s', result.findAll('td')[7].string) + self.log.debug('Name: %s', result.find_all('td' + )[1].string) + self.log.debug('Seeders: %s', result.find_all('td' + )[4].string) + self.log.debug('Leaches: %s', result.find_all('td' + )[5].string) + self.log.debug('Size: %s', result.find_all('td' + )[7].string) url = parse_qs(info_url['href']) @@ -78,22 +77,23 @@ class PublicHD(TorrentProvider): new['id'] = url['id'][0] new['url'] = self.urls['download'] % download['href' ] - new['size'] = self.parseSize(result.findAll('td' + new['size'] = self.parseSize(result.find_all('td' )[7].string) - new['seeders'] = int(result.findAll('td')[4].string) - new['leechers'] = int(result.findAll('td' + new['seeders'] = int(result.find_all('td' + )[4].string) + new['leechers'] = int(result.find_all('td' )[5].string) - new['imdbid'] = movie['library']['identifier'] - new['extra_score'] = self.extra_score new['score'] = fireEvent('score.calculate', new, movie, single=True) + is_imdb = self.imdb_match(self.urls['detail'] + % new['id'], movie['library']['identifier']) is_correct_movie = fireEvent( 'searcher.correct_movie', nzb=new, movie=movie, quality=quality, - imdb_results=True, + imdb_results=is_imdb, single_category=False, single=True, ) @@ -104,35 +104,13 @@ class PublicHD(TorrentProvider): self.found(new) return results - except Exception, e: - log.debug(e) - log.info('Error occured during parsing! Passing only processed entries' - ) + except Exception, err: + self.log.debug(err) + self.log.info('Error occured during parsing! Passing only processed entries' + ) return results - def extra_score(self, torrent): - url = self.urls['detail'] % torrent['id'] - log.info('extra_score: %s', url) - imdbId = torrent['imdbid'] - return self.imdbMatch(url, imdbId) - - def imdbMatch(self, url, imdbId): - log.info('imdbMatch: %s', url) - try: - data = urllib2.urlopen(url).read() - pass - except IOError: - log.error('Failed to open %s.' % url) - return '' - - imdbIdAlt = re.sub('tt[0]*', 'tt', imdbId) - data = unicode(data, errors='ignore') - if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ - + imdbIdAlt in data: - return 50 - return 0 - def download(self, url='', nzb_id=''): - log.info('Downloading: %s', url) + self.log.info('Downloading: %s', url) torrent = self.urlopen(url) return torrent diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index 885b4c60..bae79551 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -6,14 +6,13 @@ from couchpotato.core.event import fireEvent from couchpotato.core.helpers.variable import getTitle from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider + import re -from urllib import quote_plus -import urllib2 -log = CPLog(__name__) class ThePirateBay(TorrentProvider): + log = CPLog(__name__) cat_ids = [([207], ['720p', '1080p']), ([201], [ 'cam', 'ts', @@ -28,10 +27,13 @@ class ThePirateBay(TorrentProvider): def __init__(self): super(ThePirateBay, self).__init__() - self.urls = {"test": self.getAPIurl(), 'detail': '%s/torrent/%s', 'search': '%s/search/%s/0/7/%d'} + self.urls = {'test': self.getapiurl(), + 'detail': '%s/torrent/%s', + 'search': '%s/search/%s/0/7/%d'} - def getAPIurl(self, url=""): - return (("http://thepiratebay.se", self.conf('domain_for_tpb'))[self.conf('domain_for_tpb') != None]) + url + def getapiurl(self, url=''): + return ('http://thepiratebay.se', self.conf('domain_for_tpb' + ))[self.conf('domain_for_tpb') != None] + url def search(self, movie, quality): @@ -39,34 +41,35 @@ class ThePirateBay(TorrentProvider): if self.isDisabled(): return results - movie_name = re.sub("\W", ' ', getTitle(movie['library'])) - movie_name = re.sub(' ', ' ', movie_name) - - log.info('API url: %s', self.getAPIurl()) - log.info('Cleaned Name: %s', movie_name) - cache_key = 'thepiratebay.%s.%s' % (movie['library' ]['identifier'], quality.get('identifier')) - searchUrl = self.urls['search'] % (self.getAPIurl(), - quote_plus(movie_name + ' ' + quality['identifier']), + search_url = self.urls['search'] % (self.getapiurl(), + self.for_search(getTitle(movie['library']) + + ' ' + quality['identifier']), self.getCatId(quality['identifier'])[0]) - log.info('searchUrl: %s', searchUrl) - data = self.getCache(cache_key, searchUrl) - #print data + self.log.info('searchUrl: %s', search_url) + data = self.getCache(cache_key, search_url) + + # print data + if not data: - log.error('Failed to get data from %s.', searchUrl) + self.log.error('Failed to get data from %s.', search_url) return results try: soup = BeautifulSoup(data) - resultsTable = soup.find('table', + results_table = soup.find('table', attrs={'id': 'searchResult'}) - entries = resultsTable.findAll('tr') + entries = results_table.find_all('tr') for result in entries[1:]: link = result.find(href=re.compile('torrent\/\d+\/')) download = result.find(href=re.compile('magnet:')) - #Uploaded 06-28 02:27, Size 1.37 GiB, - size = re.search('Size (?P.+),', unicode(result.select("font.detDesc")[0])).group("size") + + # Uploaded 06-28 02:27, Size 1.37 GiB, + + size = re.search('Size (?P.+),', + unicode(result.select('font.detDesc' + )[0])).group('size') if link and download: new = { 'type': 'magnet', @@ -81,39 +84,39 @@ class ThePirateBay(TorrentProvider): alt=re.compile('VIP')) != None] moderated = (0, 50)[result.find('img', alt=re.compile('Moderator')) != None] + is_imdb = self.imdb_match(self.getapiurl(link['href' + ]), movie['library']['identifier']) - log.info('Name: %s', link.string) - log.info('Seeders: %s', result.findAll('td' - )[2].string) - log.info('Leechers: %s', result.findAll('td' - )[3].string) - log.info('Size: %s', size) - log.info('Score(trusted + vip + moderated): %d', - trusted + vip + moderated) + self.log.info('Name: %s', link.string) + self.log.info('Seeders: %s', result.find_all('td' + )[2].string) + self.log.info('Leechers: %s', result.find_all('td' + )[3].string) + self.log.info('Size: %s', size) + self.log.info('Score(trusted + vip + moderated): %d' + , trusted + vip + moderated) new['name'] = link.string new['id'] = re.search('/(?P\d+)/', link['href' ]).group('id') - new['url'] = self.getAPIurl(link['href']) - new['magnet'] = unicode(download['href']) # forcing of storing full magnet data + new['url'] = self.getapiurl(link['href']) + new['magnet'] = download['href'] new['size'] = self.parseSize(size) - new['seeders'] = int(result.findAll('td')[2].string) - new['leechers'] = int(result.findAll('td' + new['seeders'] = int(result.find_all('td' + )[2].string) + new['leechers'] = int(result.find_all('td' )[3].string) - - new['TPB_score'] = trusted + vip + moderated - new['extra_score'] = self.extra_score + new['extra_score'] = lambda : trusted + vip \ + + moderated new['score'] = fireEvent('score.calculate', new, movie, single=True) - isImdb = self.imdbMatch(self.getAPIurl(link['href']), movie['library']['identifier']) - is_correct_movie = fireEvent( 'searcher.correct_movie', nzb=new, movie=movie, quality=quality, - imdb_results=isImdb, + imdb_results=is_imdb, single_category=False, single=True, ) @@ -123,29 +126,9 @@ class ThePirateBay(TorrentProvider): self.found(new) return results - except Exception, e: - log.debug(e) - log.info('Error occured during parsing! Passing only processed entries' - ) + except Exception, error: + self.log.debug(error) return results - def extra_score(self, torrent): - return torrent["TPB_score"] - - def imdbMatch(self, url, imdbId): - log.info('imdbMatch: %s', url) - try: - data = urllib2.urlopen(url).read() - pass - except: - log.error('Failed to open %s.' % url) - return False - imdbIdAlt = re.sub('tt[0]*', 'tt', imdbId) - data = unicode(data, errors='ignore') - if 'imdb.com/title/' + imdbId in data or 'imdb.com/title/' \ - + imdbIdAlt in data: - return True - return False - def download(self, url='', nzb_id=''): return url From 99809348f72d4a91eff7e532f578bba4814ac0c3 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Tue, 3 Jul 2012 12:35:20 +0200 Subject: [PATCH 13/16] lamba fix for tpb --- couchpotato/core/providers/torrent/thepiratebay/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index bae79551..a66cc812 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -106,7 +106,7 @@ class ThePirateBay(TorrentProvider): )[2].string) new['leechers'] = int(result.find_all('td' )[3].string) - new['extra_score'] = lambda : trusted + vip \ + new['extra_score'] = lambda (x): trusted + vip \ + moderated new['score'] = fireEvent('score.calculate', new, movie, single=True) From 9d607bce98a1385051919cf6c1ee1be3744f9525 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Tue, 3 Jul 2012 14:37:30 +0200 Subject: [PATCH 14/16] Extra score for tpb --- .../core/providers/torrent/thepiratebay/main.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index a66cc812..a08f581e 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -44,8 +44,8 @@ class ThePirateBay(TorrentProvider): cache_key = 'thepiratebay.%s.%s' % (movie['library' ]['identifier'], quality.get('identifier')) search_url = self.urls['search'] % (self.getapiurl(), - self.for_search(getTitle(movie['library']) - + ' ' + quality['identifier']), + self.for_search(getTitle(movie['library']) + ' ' + + quality['identifier']), self.getCatId(quality['identifier'])[0]) self.log.info('searchUrl: %s', search_url) data = self.getCache(cache_key, search_url) @@ -82,6 +82,8 @@ class ThePirateBay(TorrentProvider): alt=re.compile('Trusted')) != None] vip = (0, 20)[result.find('img', alt=re.compile('VIP')) != None] + confirmed = (0, 30)[result.find('img', + alt=re.compile('Helpers')) != None] moderated = (0, 50)[result.find('img', alt=re.compile('Moderator')) != None] is_imdb = self.imdb_match(self.getapiurl(link['href' @@ -94,7 +96,8 @@ class ThePirateBay(TorrentProvider): )[3].string) self.log.info('Size: %s', size) self.log.info('Score(trusted + vip + moderated): %d' - , trusted + vip + moderated) + , confirmed + trusted + vip + + moderated) new['name'] = link.string new['id'] = re.search('/(?P\d+)/', link['href' @@ -106,8 +109,8 @@ class ThePirateBay(TorrentProvider): )[2].string) new['leechers'] = int(result.find_all('td' )[3].string) - new['extra_score'] = lambda (x): trusted + vip \ - + moderated + new['extra_score'] = lambda x: confirmed + trusted \ + + vip + moderated new['score'] = fireEvent('score.calculate', new, movie, single=True) From f43a3725ddc3254f4c888d25c7fdd450efb1d5a5 Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Tue, 3 Jul 2012 23:27:04 +0200 Subject: [PATCH 15/16] TPB proxies round-robin balancer with default settings --- .../core/downloaders/transmission/main.py | 1 + .../torrent/thepiratebay/__init__.py | 18 +---- .../providers/torrent/thepiratebay/main.py | 81 +++++++++++++++++-- 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index 3864e1d6..2ab24d14 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -5,6 +5,7 @@ from base64 import b64encode from couchpotato.core.downloaders.base import Downloader from couchpotato.core.helpers.encoding import isInt from couchpotato.core.logger import CPLog + import urllib2 import httplib import json diff --git a/couchpotato/core/providers/torrent/thepiratebay/__init__.py b/couchpotato/core/providers/torrent/thepiratebay/__init__.py index 9c132dd7..d8e05600 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/__init__.py +++ b/couchpotato/core/providers/torrent/thepiratebay/__init__.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- from main import ThePirateBay +from main import TPBProxy def start(): @@ -18,21 +19,8 @@ config = [{'name': 'ThePirateBay', 'groups': [{ 'name': 'domain_for_tpb', 'label': 'Proxy server', 'default': 'http://thepiratebay.se', - 'description': 'Which domain do you preferr(or it\'s not blocked)', + 'description': 'Default domain for requests', 'type': 'dropdown', - 'values': [ - ('(Sweden) thepiratebay.se', 'http://thepiratebay.se'), - ('(Sweden) tpb.ipredator.se (ssl)', - 'https://tpb.ipredator.se'), - ('(Germany) depiraatbaai.be', 'http://depiraatbaai.be'), - ('(UK) piratereverse.info (ssl)', - 'https://piratereverse.info'), - ('(UK) tpb.pirateparty.org.uk (ssl)', - 'https://tpb.pirateparty.org.uk'), - ('(Netherlands) thepiratebay.se.coevoet.nl', - 'http://thepiratebay.se.coevoet.nl'), - ('(direct) 194.71.107.80', 'http://194.71.107.80'), - ('(direct) 194.71.107.83', 'http://194.71.107.81'), - ], + 'values': TPBProxy.list, }], }]}] diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index a08f581e..6dd779f3 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -6,8 +6,55 @@ from couchpotato.core.event import fireEvent from couchpotato.core.helpers.variable import getTitle from couchpotato.core.logger import CPLog from couchpotato.core.providers.torrent.base import TorrentProvider +from random import sample as random +from urlparse import urlparse import re +import time + + +class TPBProxy(object): + + """ TPBProxy deals with failed or blocked TPB proxys + it works as Round-robin balancer if user seleced + or default domain becomes unavaliable. + """ + + list = [ + ('(Sweden) thepiratebay.se', 'http://thepiratebay.se'), + ('(Sweden) tpb.ipredator.se (ssl)', 'https://tpb.ipredator.se' + ), + ('(Germany) depiraatbaai.be', 'http://depiraatbaai.be'), + ('(UK) piratereverse.info (ssl)', 'https://piratereverse.info' + ), + ('(UK) tpb.pirateparty.org.uk (ssl)', + 'https://tpb.pirateparty.org.uk'), + ('(Netherlands) argumentomteemigreren.nl', + 'http://argumentomteemigreren.nl'), + ('(direct) 194.71.107.80', 'http://194.71.107.80'), + ('(direct) 194.71.107.81', 'http://194.71.107.81'), + ('(direct) 194.71.107.82', 'http://194.71.107.82'), + ('(direct) 194.71.107.83', 'http://194.71.107.83'), + ] + + @staticmethod + def get_proxy(http_failed_disabled=None, current=None): + + # compare lists and user/default value, exclude filter + + unused = [item for item in TPBProxy.list if item + not in http_failed_disabled and current not in item] + + if len(unused) > 0: + + # only return uri + + return random(unused, 1)[0][1] + else: + + # this should disable provider for some time + + raise Exception('All ThePirateBay proxies are exhausted') class ThePirateBay(TorrentProvider): @@ -27,13 +74,30 @@ class ThePirateBay(TorrentProvider): def __init__(self): super(ThePirateBay, self).__init__() - self.urls = {'test': self.getapiurl(), + self.urls = {'test': self.api_domain(), 'detail': '%s/torrent/%s', 'search': '%s/search/%s/0/7/%d'} - def getapiurl(self, url=''): - return ('http://thepiratebay.se', self.conf('domain_for_tpb' - ))[self.conf('domain_for_tpb') != None] + url + def api_domain(self, url=''): + + # default domain + + domain = ('http://thepiratebay.se', self.conf('domain_for_tpb' + ))[self.conf('domain_for_tpb') != None] + + host = urlparse(domain).hostname + + # Clear disabled list for default or user selected host if time expired + + if self.http_failed_disabled.get(host, 0) > 0: + if self.http_failed_disabled[host] > time.time() - 900: + # get new random domain + domain = TPBProxy.get_proxy(self.http_failed_disabled, domain) + else: + del self.http_failed_request[host] + del self.http_failed_disabled[host] + + return domain + url def search(self, movie, quality): @@ -43,7 +107,7 @@ class ThePirateBay(TorrentProvider): cache_key = 'thepiratebay.%s.%s' % (movie['library' ]['identifier'], quality.get('identifier')) - search_url = self.urls['search'] % (self.getapiurl(), + search_url = self.urls['search'] % (self.api_domain(), self.for_search(getTitle(movie['library']) + ' ' + quality['identifier']), self.getCatId(quality['identifier'])[0]) @@ -86,8 +150,9 @@ class ThePirateBay(TorrentProvider): alt=re.compile('Helpers')) != None] moderated = (0, 50)[result.find('img', alt=re.compile('Moderator')) != None] - is_imdb = self.imdb_match(self.getapiurl(link['href' - ]), movie['library']['identifier']) + is_imdb = \ + self.imdb_match(self.api_domain(link['href']), + movie['library']['identifier']) self.log.info('Name: %s', link.string) self.log.info('Seeders: %s', result.find_all('td' @@ -102,7 +167,7 @@ class ThePirateBay(TorrentProvider): new['name'] = link.string new['id'] = re.search('/(?P\d+)/', link['href' ]).group('id') - new['url'] = self.getapiurl(link['href']) + new['url'] = self.api_domain(link['href']) new['magnet'] = download['href'] new['size'] = self.parseSize(size) new['seeders'] = int(result.find_all('td' From b7de52229ea5fb14575b820d08146a6dafa544fe Mon Sep 17 00:00:00 2001 From: Janez Troha Date: Wed, 4 Jul 2012 21:01:39 +0200 Subject: [PATCH 16/16] Add info for selectred TPB domain on start of request --- .../providers/torrent/thepiratebay/main.py | 97 ++++++++----------- 1 file changed, 42 insertions(+), 55 deletions(-) diff --git a/couchpotato/core/providers/torrent/thepiratebay/main.py b/couchpotato/core/providers/torrent/thepiratebay/main.py index 6dd779f3..0fbc25d8 100644 --- a/couchpotato/core/providers/torrent/thepiratebay/main.py +++ b/couchpotato/core/providers/torrent/thepiratebay/main.py @@ -15,22 +15,18 @@ import time class TPBProxy(object): - """ TPBProxy deals with failed or blocked TPB proxys - it works as Round-robin balancer if user seleced + """ TPBProxy deals with failed or blocked TPB proxys. + It works as round-robin balancer, if user seleced or default domain becomes unavaliable. """ list = [ ('(Sweden) thepiratebay.se', 'http://thepiratebay.se'), - ('(Sweden) tpb.ipredator.se (ssl)', 'https://tpb.ipredator.se' - ), + ('(Sweden) tpb.ipredator.se (ssl)', 'https://tpb.ipredator.se'), ('(Germany) depiraatbaai.be', 'http://depiraatbaai.be'), - ('(UK) piratereverse.info (ssl)', 'https://piratereverse.info' - ), - ('(UK) tpb.pirateparty.org.uk (ssl)', - 'https://tpb.pirateparty.org.uk'), - ('(Netherlands) argumentomteemigreren.nl', - 'http://argumentomteemigreren.nl'), + ('(UK) piratereverse.info (ssl)', 'https://piratereverse.info'), + ('(UK) tpb.pirateparty.org.uk (ssl)', 'https://tpb.pirateparty.org.uk'), + ('(Netherlands) argumentomteemigreren.nl', 'http://argumentomteemigreren.nl'), ('(direct) 194.71.107.80', 'http://194.71.107.80'), ('(direct) 194.71.107.81', 'http://194.71.107.81'), ('(direct) 194.71.107.82', 'http://194.71.107.82'), @@ -42,8 +38,8 @@ class TPBProxy(object): # compare lists and user/default value, exclude filter - unused = [item for item in TPBProxy.list if item - not in http_failed_disabled and current not in item] + unused = [item for item in TPBProxy.list if item not in http_failed_disabled and current + not in item] if len(unused) > 0: @@ -71,29 +67,35 @@ class ThePirateBay(TorrentProvider): ]), ([202], ['dvdr'])] cat_backup_id = 200 + disable_provider = False def __init__(self): super(ThePirateBay, self).__init__() - self.urls = {'test': self.api_domain(), - 'detail': '%s/torrent/%s', + self.urls = {'test': self.api_domain(), 'detail': '%s/torrent/%s', 'search': '%s/search/%s/0/7/%d'} def api_domain(self, url=''): # default domain - domain = ('http://thepiratebay.se', self.conf('domain_for_tpb' - ))[self.conf('domain_for_tpb') != None] - + domain = self.conf('domain_for_tpb', default='http://thepiratebay.se') + self.log.info('Selected domain for this request: %s', domain) host = urlparse(domain).hostname # Clear disabled list for default or user selected host if time expired if self.http_failed_disabled.get(host, 0) > 0: if self.http_failed_disabled[host] > time.time() - 900: + # get new random domain - domain = TPBProxy.get_proxy(self.http_failed_disabled, domain) + + try: + domain = TPBProxy.get_proxy(self.http_failed_disabled, domain) + except Exception, err: + self.disable_provider = True + self.log.error(err) else: + del self.http_failed_request[host] del self.http_failed_disabled[host] @@ -102,15 +104,15 @@ class ThePirateBay(TorrentProvider): def search(self, movie, quality): results = [] - if self.isDisabled(): + if self.isDisabled() or self.disable_provider: return results - cache_key = 'thepiratebay.%s.%s' % (movie['library' - ]['identifier'], quality.get('identifier')) + cache_key = 'thepiratebay.%s.%s' % (movie['library']['identifier'], quality.get('identifier' + )) search_url = self.urls['search'] % (self.api_domain(), - self.for_search(getTitle(movie['library']) + ' ' - + quality['identifier']), - self.getCatId(quality['identifier'])[0]) + self.for_search(getTitle(movie['library']) + ' ' + + quality['identifier']), + self.getCatId(quality['identifier'])[0]) self.log.info('searchUrl: %s', search_url) data = self.getCache(cache_key, search_url) @@ -122,8 +124,7 @@ class ThePirateBay(TorrentProvider): try: soup = BeautifulSoup(data) - results_table = soup.find('table', - attrs={'id': 'searchResult'}) + results_table = soup.find('table', attrs={'id': 'searchResult'}) entries = results_table.find_all('tr') for result in entries[1:]: link = result.find(href=re.compile('torrent\/\d+\/')) @@ -131,8 +132,7 @@ class ThePirateBay(TorrentProvider): # Uploaded 06-28 02:27, Size 1.37 GiB, - size = re.search('Size (?P.+),', - unicode(result.select('font.detDesc' + size = re.search('Size (?P.+),', unicode(result.select('font.detDesc' )[0])).group('size') if link and download: new = { @@ -142,42 +142,29 @@ class ThePirateBay(TorrentProvider): 'provider': self.getName(), } - trusted = (0, 10)[result.find('img', - alt=re.compile('Trusted')) != None] - vip = (0, 20)[result.find('img', - alt=re.compile('VIP')) != None] - confirmed = (0, 30)[result.find('img', - alt=re.compile('Helpers')) != None] - moderated = (0, 50)[result.find('img', - alt=re.compile('Moderator')) != None] - is_imdb = \ - self.imdb_match(self.api_domain(link['href']), - movie['library']['identifier']) + trusted = (0, 10)[result.find('img', alt=re.compile('Trusted')) != None] + vip = (0, 20)[result.find('img', alt=re.compile('VIP')) != None] + confirmed = (0, 30)[result.find('img', alt=re.compile('Helpers')) != None] + moderated = (0, 50)[result.find('img', alt=re.compile('Moderator')) != None] + is_imdb = self.imdb_match(self.api_domain(link['href']), movie['library' + ]['identifier']) self.log.info('Name: %s', link.string) - self.log.info('Seeders: %s', result.find_all('td' - )[2].string) - self.log.info('Leechers: %s', result.find_all('td' - )[3].string) + self.log.info('Seeders: %s', result.find_all('td')[2].string) + self.log.info('Leechers: %s', result.find_all('td')[3].string) self.log.info('Size: %s', size) - self.log.info('Score(trusted + vip + moderated): %d' - , confirmed + trusted + vip + self.log.info('Score(trusted + vip + moderated): %d', confirmed + trusted + vip + moderated) new['name'] = link.string - new['id'] = re.search('/(?P\d+)/', link['href' - ]).group('id') + new['id'] = re.search('/(?P\d+)/', link['href']).group('id') new['url'] = self.api_domain(link['href']) new['magnet'] = download['href'] new['size'] = self.parseSize(size) - new['seeders'] = int(result.find_all('td' - )[2].string) - new['leechers'] = int(result.find_all('td' - )[3].string) - new['extra_score'] = lambda x: confirmed + trusted \ - + vip + moderated - new['score'] = fireEvent('score.calculate', new, - movie, single=True) + new['seeders'] = int(result.find_all('td')[2].string) + new['leechers'] = int(result.find_all('td')[3].string) + new['extra_score'] = lambda x: confirmed + trusted + vip + moderated + new['score'] = fireEvent('score.calculate', new, movie, single=True) is_correct_movie = fireEvent( 'searcher.correct_movie',