Initial torrent support

Transmission downloader
Code cleanup
This commit is contained in:
Ruud
2012-02-17 11:15:51 +01:00
parent 02fab2d04d
commit c7507e14ed
19 changed files with 794 additions and 197 deletions
+1 -1
View File
@@ -1,8 +1,8 @@
from couchpotato.core.event import addEvent from couchpotato.core.event import addEvent
from couchpotato.core.helpers.encoding import toSafeString
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env from couchpotato.environment import Env
from couchpotato.core.helpers.encoding import toSafeString
import os import os
log = CPLog(__name__) log = CPLog(__name__)
@@ -42,6 +42,6 @@ class Blackhole(Downloader):
pass pass
except: except:
log.debug('Failed to download file: %s' % data.get('name')) log.debug('Failed to download file %s: %s' % (data.get('name'), traceback.format_exc()))
return False return False
return False return False
@@ -29,6 +29,7 @@ config = [{
}, },
{ {
'name': 'password', 'name': 'password',
'type': 'password',
}, },
{ {
'name': 'paused', 'name': 'paused',
@@ -43,6 +44,7 @@ config = [{
{ {
'name': 'ratio', 'name': 'ratio',
'default': 10, 'default': 10,
'type': 'int',
'advanced': True, 'advanced': True,
'description': 'Stop transfer when reaching ratio', 'description': 'Stop transfer when reaching ratio',
}, },
@@ -1,3 +1,4 @@
from base64 import b64encode
from couchpotato.core.downloaders.base import Downloader from couchpotato.core.downloaders.base import Downloader
from couchpotato.core.helpers.encoding import isInt from couchpotato.core.helpers.encoding import isInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
@@ -17,7 +18,6 @@ class Transmission(Downloader):
log.info('Sending "%s" to Transmission.' % data.get('name')) log.info('Sending "%s" to Transmission.' % data.get('name'))
# Load host from config and split out port. # Load host from config and split out port.
host = self.conf('host').split(':') host = self.conf('host').split(':')
if not isInt(host[1]): if not isInt(host[1]):
@@ -26,24 +26,21 @@ class Transmission(Downloader):
# Set parameters for Transmission # Set parameters for Transmission
params = { params = {
'paused': self.conf('paused', 0), 'paused': self.conf('paused', default = 0),
'download_dir': self.conf('directory', None) 'download_dir': self.conf('directory', default = None)
}
change_params = {
'seedRatioLimit': self.conf('ratio'),
'seedRatioMode': 1 if self.conf('ratio') else 0
} }
try: try:
tc = transmissionrpc.Client(host[0], port = host[1], user = self.conf('username'), password = self.conf('password')) tc = transmissionrpc.Client(host[0], port = host[1], user = self.conf('username'), password = self.conf('password'))
tr_id = tc.add_uri(data.get('url'), **params) filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id'))
torrent = tc.add_torrent(b64encode(filedata), **params)
# Change settings of added torrents # Change settings of added torrents
for item in tr_id: try:
try: torrent.seed_ratio_limit = self.conf('ratio')
tc.change(item, timeout = None, **change_params) torrent.seed_ratio_mode = 'single' if self.conf('ratio') else 'global'
except transmissionrpc.TransmissionError, e: except transmissionrpc.TransmissionError, e:
log.error('Failed to change settings for transfer in transmission: %s' % e) log.error('Failed to change settings for transfer in transmission: %s' % e)
return True return True
+8
View File
@@ -23,4 +23,12 @@ class Score(Plugin):
score += sizeScore(nzb['size']) score += sizeScore(nzb['size'])
# Torrents only
if nzb.get('seeds'):
try:
score += nzb.get('seeds') / 5
score += nzb.get('leechers') / 10
except:
pass
return score return score
@@ -28,7 +28,7 @@ config = [{
{ {
'name': 'ignored_words', 'name': 'ignored_words',
'label': 'Ignored words', 'label': 'Ignored words',
'default': 'german, dutch, french, danish, swedish, dubbed, swesub, korsub', 'default': 'german, dutch, french, danish, swedish, spanish, italian, korean, dubbed, swesub, korsub',
}, },
], ],
}, { }, {
+1 -1
View File
@@ -161,7 +161,7 @@ class Searcher(Plugin):
single_category = kwargs.get('single_category', False) single_category = kwargs.get('single_category', False)
retention = Env.setting('retention', section = 'nzb') retention = Env.setting('retention', section = 'nzb')
if retention < nzb.get('age', 0): if nzb.get('seeds') is None and retention < nzb.get('age', 0):
log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s' % (nzb['age'], retention, nzb['name'])) log.info('Wrong: Outside retention, age is %s, needs %s or lower: %s' % (nzb['age'], retention, nzb['name']))
return False return False
+14
View File
@@ -48,6 +48,20 @@ class YarrProvider(Provider):
def __init__(self): def __init__(self):
addEvent('provider.belongs_to', self.belongsTo) addEvent('provider.belongs_to', self.belongsTo)
addEvent('%s.search' % self.type, self.search)
addEvent('yarr.search', self.search)
addEvent('nzb.feed', self.feed)
def download(self, url = '', nzb_id = ''):
return self.urlopen(url)
def feed(self):
return []
def search(self, movie, quality):
return []
def belongsTo(self, url, host = None): def belongsTo(self, url, host = None):
try: try:
hostname = urlparse(url).hostname hostname = urlparse(url).hostname
+1 -1
View File
@@ -3,7 +3,7 @@ from couchpotato.core.helpers.variable import mergeDicts
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin from couchpotato.core.plugins.base import Plugin
import json import json
import os.path import os
import shutil import shutil
import traceback import traceback
-17
View File
@@ -6,22 +6,5 @@ import time
class NZBProvider(YarrProvider): class NZBProvider(YarrProvider):
type = 'nzb' type = 'nzb'
def __init__(self):
super(NZBProvider, self).__init__()
addEvent('nzb.search', self.search)
addEvent('yarr.search', self.search)
addEvent('nzb.feed', self.feed)
def download(self, url = '', nzb_id = ''):
return self.urlopen(url)
def feed(self):
return []
def search(self, movie, quality):
return []
def calculateAge(self, unix): def calculateAge(self, unix):
return int(time.time() - unix) / 24 / 60 / 60 return int(time.time() - unix) / 24 / 60 / 60
@@ -1,4 +1,4 @@
from BeautifulSoup import BeautifulSoup, SoupStrainer from BeautifulSoup import BeautifulSoup
from couchpotato.core.event import fireEvent from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.encoding import toUnicode from couchpotato.core.helpers.encoding import toUnicode
from couchpotato.core.helpers.variable import tryInt from couchpotato.core.helpers.variable import tryInt
@@ -3,4 +3,19 @@ from .main import KickAssTorrents
def start(): def start():
return KickAssTorrents() return KickAssTorrents()
config = [] config = [{
'name': 'kickasstorrents',
'groups': [
{
'tab': 'providers',
'name': 'KickAssTorrents',
'options': [
{
'name': 'enabled',
'type': 'enabler',
'default': False,
},
],
},
],
}]
@@ -1,5 +1,12 @@
from BeautifulSoup import BeautifulSoup
from couchpotato.core.event import fireEvent
from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog from couchpotato.core.logger import CPLog
from couchpotato.core.providers.torrent.base import TorrentProvider from couchpotato.core.providers.torrent.base import TorrentProvider
import StringIO
import gzip
import re
import traceback
log = CPLog(__name__) log = CPLog(__name__)
@@ -7,8 +14,124 @@ log = CPLog(__name__)
class KickAssTorrents(TorrentProvider): class KickAssTorrents(TorrentProvider):
urls = { urls = {
'download': 'http://torrents.thepiratebay.org/%s/%s.torrent', 'test': 'http://www.kat.ph/',
'detail': 'http://www.kat.ph/%s-t%s.html', 'detail': 'http://www.kat.ph/%s-t%s.html',
'search': 'http://www.kat.ph/%s-i%s/', 'search': 'http://www.kat.ph/%s-i%s/',
} }
cat_ids = [
(['cam'], ['cam']),
(['telesync'], ['ts', 'tc']),
(['screener', 'tvrip'], ['screener']),
(['x264', '720p', '1080p', 'blu-ray', 'hdrip'], ['bd50', '1080p', '720p', 'brrip']),
(['dvdrip'], ['dvdrip']),
(['dvd'], ['dvdr']),
]
http_time_between_calls = 1 #seconds
def search(self, movie, quality):
results = []
if self.isDisabled() or not self.isAvailable(self.urls['test']):
return results
cache_key = 'kickasstorrents.%s' % movie['library']['identifier']
data = self.getCache(cache_key, self.urls['search'] % (movie['library']['titles'][0]['title'], movie['library']['identifier'].replace('tt', '')))
if data:
cat_ids = self.getCatId(quality['identifier'])
table_order = ['name', 'size', None, 'age', 'seeds', 'leechers']
try:
html = BeautifulSoup(data)
resultdiv = html.find('div', attrs = {'class':'tabs'})
for result in resultdiv.findAll('div', recursive = False):
if result.get('id').lower() not in cat_ids:
continue
try:
try:
for temp in result.findAll('tr'):
if temp['class'] is 'firstr' or not temp.get('id'):
continue
new = {
'type': 'torrent',
'check_nzb': False,
'description': '',
'provider': self.getName(),
'download': self.download,
'score': 0,
}
nr = 0
for td in temp.findAll('td'):
column_name = table_order[nr]
if column_name:
if column_name is 'name':
link = td.find('div', {'class': 'torrentname'}).findAll('a')[1]
new['id'] = temp.get('id')[-8:]
new['name'] = link.text
new['url'] = td.findAll('a', 'idownload')[1]['href']
new['score'] = 20 if td.find('a', 'iverif') else 0
elif column_name is 'size':
new['size'] = self.parseSize(td.text)
elif column_name is 'age':
new['age'] = self.ageToDays(td.text)
elif column_name is 'seeds':
new['seeds'] = tryInt(td.text)
elif column_name is 'leechers':
new['leechers'] = tryInt(td.text)
nr += 1
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:
results.append(new)
self.found(new)
except:
log.error('Failed parsing KickAssTorrents: %s' % traceback.format_exc())
except:
pass
return results
except AttributeError:
log.debug('No search results found.')
return results
def ageToDays(self, age_str):
age = 0
age_str = age_str.replace('&nbsp;', ' ')
regex = '(\d*.?\d+).(sec|hour|day|week|month|year)+'
matches = re.findall(regex, age_str)
for match in matches:
nr, size = match
mult = 1
if size == 'week':
mult = 7
elif size == 'month':
mult = 30.5
elif size == 'year':
mult = 365
age += tryInt(nr) * mult
return tryInt(age)
def download(self, url = '', nzb_id = ''):
compressed_data = super(KickAssTorrents, self).download(url = url, nzb_id = nzb_id)
compressedstream = StringIO.StringIO(compressed_data)
gzipper = gzip.GzipFile(fileobj = compressedstream)
data = gzipper.read()
return data
+1 -1
View File
@@ -11,6 +11,6 @@ from transmissionrpc.client import Client
from transmissionrpc.utils import add_stdout_logger from transmissionrpc.utils import add_stdout_logger
__author__ = u'Erik Svensson <erik.public@gmail.com>' __author__ = u'Erik Svensson <erik.public@gmail.com>'
__version__ = u'0.8' __version__ = u'0.9'
__copyright__ = u'Copyright (c) 2008-2011 Erik Svensson' __copyright__ = u'Copyright (c) 2008-2011 Erik Svensson'
__license__ = u'MIT' __license__ = u'MIT'
+228 -88
View File
@@ -2,7 +2,7 @@
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com> # Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
# Licensed under the MIT license. # Licensed under the MIT license.
import re, time import re, time, operator, warnings
import urllib2, urlparse, base64 import urllib2, urlparse, base64
try: try:
@@ -36,7 +36,7 @@ def debug_httperror(error):
'data': data, 'data': data,
} }
}, },
indent=2 indent = 2
) )
) )
@@ -59,7 +59,7 @@ class Client(object):
Client is the class handling the Transmission JSON-RPC client protocol. 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): def __init__(self, address = 'localhost', port = DEFAULT_PORT, user = None, password = None, http_handler = None, timeout = None):
if isinstance(timeout, (int, long, float)): if isinstance(timeout, (int, long, float)):
self._query_timeout = float(timeout) self._query_timeout = float(timeout)
else: else:
@@ -91,7 +91,7 @@ class Client(object):
elif user or password: elif user or password:
LOGGER.warning('Either user or password missing, not using authentication.') LOGGER.warning('Either user or password missing, not using authentication.')
self._sequence = 0 self._sequence = 0
self.session = Session() self.session = None
self.session_id = 0 self.session_id = 0
self.server_version = None self.server_version = None
self.protocol_version = None self.protocol_version = None
@@ -117,9 +117,9 @@ class Client(object):
""" """
self._query_timeout = DEFAULT_TIMEOUT self._query_timeout = DEFAULT_TIMEOUT
timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.") timeout = property(_get_timeout, _set_timeout, _del_timeout, doc = "HTTP query timeout.")
def _http_query(self, query, timeout=None): def _http_query(self, query, timeout = None):
""" """
Query Transmission through HTTP. Query Transmission through HTTP.
""" """
@@ -129,7 +129,7 @@ class Client(object):
if timeout is None: if timeout is None:
timeout = self._query_timeout timeout = self._query_timeout
while True: while True:
LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent=2)) LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent = 2))
try: try:
result = self.http_handler.request(self.url, query, headers, timeout) result = self.http_handler.request(self.url, query, headers, timeout)
break break
@@ -150,7 +150,7 @@ class Client(object):
request_count += 1 request_count += 1
return result return result
def _request(self, method, arguments=None, ids=None, require_ids=False, timeout=None): def _request(self, method, arguments = None, ids = None, require_ids = False, timeout = None):
""" """
Send json-rpc request to Transmission using http POST Send json-rpc request to Transmission using http POST
""" """
@@ -182,7 +182,7 @@ class Client(object):
LOGGER.error('HTTP data: \"%s\"' % (http_data)) LOGGER.error('HTTP data: \"%s\"' % (http_data))
raise raise
LOGGER.debug(json.dumps(data, indent=2)) LOGGER.debug(json.dumps(data, indent = 2))
if 'result' in data: if 'result' in data:
if data['result'] != 'success': if data['result'] != 'success':
raise TransmissionError('Query failed with result \"%s\".' % (data['result'])) raise TransmissionError('Query failed with result \"%s\".' % (data['result']))
@@ -264,7 +264,10 @@ class Client(object):
""" """
Update session data. Update session data.
""" """
self.session.update(data) if self.session:
self.session.from_request(data)
else:
self.session = Session(self, data)
def _update_server_version(self): def _update_server_version(self):
if self.server_version is None: if self.server_version is None:
@@ -305,11 +308,12 @@ class Client(object):
Add a warning to the log if the Transmission RPC version is lower then the provided version. Add a warning to the log if the Transmission RPC version is lower then the provided version.
""" """
if self.rpc_version < 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)) LOGGER.warning('Using feature not supported by server. RPC version for server %d, feature introduced in %d.'
% (self.rpc_version, version))
def add(self, data, timeout=None, **kwargs): def add_torrent(self, torrent, timeout = None, **kwargs):
""" """
Add torrent to transfers list. Takes a base64 encoded .torrent file in data. Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data.
Additional arguments are: Additional arguments are:
===================== ===== =========== ============================================================= ===================== ===== =========== =============================================================
@@ -327,6 +331,41 @@ class Client(object):
``priority_normal`` 1 - A list of file id's that should have normal 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 = {} args = {}
if data: if data:
@@ -335,31 +374,16 @@ class Client(object):
raise ValueError('No torrent data or torrent uri.') raise ValueError('No torrent data or torrent uri.')
for key, value in kwargs.iteritems(): for key, value in kwargs.iteritems():
argument = make_rpc_name(key) argument = make_rpc_name(key)
(arg, val) = argument_value_convert('torrent-add', (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version)
argument, value, self.rpc_version)
args[arg] = val args[arg] = val
return self._request('torrent-add', args, timeout=timeout) 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): def add_uri(self, uri, **kwargs):
""" """
Add torrent to transfers list. Takes a uri to a torrent, supporting
all uri's supported by Transmissions torrent-add 'filename'
argument. Additional arguments are:
===================== ===== =========== ============================================================= .. WARNING::
Argument RPC Replaced by Description Deprecated, please use add_torrent.
===================== ===== =========== =============================================================
``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.
===================== ===== =========== =============================================================
""" """
if uri is None: if uri is None:
raise ValueError('add_uri requires a URI.') raise ValueError('add_uri requires a URI.')
@@ -370,47 +394,145 @@ class Client(object):
if parsed_uri.scheme in ['file', 'ftp', 'ftps', 'http', 'https']: if parsed_uri.scheme in ['file', 'ftp', 'ftps', 'http', 'https']:
torrent_file = urllib2.urlopen(uri) torrent_file = urllib2.urlopen(uri)
torrent_data = base64.b64encode(torrent_file.read()) torrent_data = base64.b64encode(torrent_file.read())
warnings.warn('add_uri has been deprecated, please use add_torrent instead.', DeprecationWarning)
if torrent_data: if torrent_data:
return self.add(torrent_data, **kwargs) return self.add(torrent_data, **kwargs)
else: else:
return self.add(None, filename=uri, **kwargs) return self.add(None, filename = uri, **kwargs)
def remove(self, ids, delete_data=False, timeout=None): def remove_torrent(self, ids, delete_data = False, timeout = None):
""" """
remove torrent(s) with provided id(s). Local data is removed if remove torrent(s) with provided id(s). Local data is removed if
delete_data is True, otherwise not. delete_data is True, otherwise not.
""" """
self._rpc_version_warning(3) self._rpc_version_warning(3)
self._request('torrent-remove', self._request('torrent-remove',
{'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout) {'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout = timeout)
def start(self, ids, bypass_queue=False, timeout=None): def remove(self, ids, delete_data = False, timeout = None):
"""start torrent(s) with provided id(s)""" """
.. 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' method = 'torrent-start'
if bypass_queue and self.rpc_version >= 14: if bypass_queue and self.rpc_version >= 14:
method = 'torrent-start-now' method = 'torrent-start-now'
self._request(method, {}, ids, True, timeout=timeout) self._request(method, {}, ids, True, timeout = timeout)
def stop(self, ids, timeout=None): 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)""" """stop torrent(s) with provided id(s)"""
self._request('torrent-stop', {}, ids, True, timeout=timeout) self._request('torrent-stop', {}, ids, True, timeout = timeout)
def verify(self, ids, timeout=None): 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)""" """verify torrent(s) with provided id(s)"""
self._request('torrent-verify', {}, ids, True, timeout=timeout) self._request('torrent-verify', {}, ids, True, timeout = timeout)
def reannounce(self, ids, timeout=None): 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)""" """Reannounce torrent(s) with provided id(s)"""
self._rpc_version_warning(5) self._rpc_version_warning(5)
self._request('torrent-reannounce', {}, ids, True, timeout=timeout) self._request('torrent-reannounce', {}, ids, True, timeout = timeout)
def info(self, ids=None, arguments=None, timeout=None): def reannounce(self, ids, timeout = None):
"""Get detailed information for torrent(s) with provided id(s).""" """
.. 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: if not arguments:
arguments = self.torrent_get_arguments arguments = self.torrent_get_arguments
return self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout) 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_files(self, ids=None, timeout=None): 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, Get list of files for provided torrent id(s). If ids is empty,
information for all torrents are fetched. This function returns a dictionary information for all torrents are fetched. This function returns a dictionary
@@ -435,13 +557,13 @@ class Client(object):
} }
""" """
fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted'] fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout) request_result = self._request('torrent-get', {'fields': fields}, ids, timeout = timeout)
result = {} result = {}
for tid, torrent in request_result.iteritems(): for tid, torrent in request_result.iteritems():
result[tid] = torrent.files() result[tid] = torrent.files()
return result return result
def set_files(self, items, timeout=None): def set_files(self, items, timeout = None):
""" """
Set file properties. Takes a dictionary with similar contents as the result Set file properties. Takes a dictionary with similar contents as the result
of `get_files`. of `get_files`.
@@ -498,14 +620,7 @@ class Client(object):
args['priority_low'] = low args['priority_low'] = low
self.change([tid], **args) self.change([tid], **args)
def list(self, timeout=None): def change_torrent(self, ids, timeout = None, **kwargs):
"""list all torrents"""
fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone'
, 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver'
, 'downloadedEver', 'uploadRatio']
return self._request('torrent-get', {'fields': fields}, timeout=timeout)
def change(self, ids, timeout=None, **kwargs):
""" """
Change torrent parameters for the torrent(s) with the supplied id's. The Change torrent parameters for the torrent(s) with the supplied id's. The
parameters are: parameters are:
@@ -546,54 +661,80 @@ class Client(object):
args = {} args = {}
for key, value in kwargs.iteritems(): for key, value in kwargs.iteritems():
argument = make_rpc_name(key) argument = make_rpc_name(key)
(arg, val) = argument_value_convert('torrent-set' (arg, val) = argument_value_convert('torrent-set' , argument, value, self.rpc_version)
, argument, value, self.rpc_version)
args[arg] = val args[arg] = val
if len(args) > 0: if len(args) > 0:
self._request('torrent-set', args, ids, True, timeout=timeout) self._request('torrent-set', args, ids, True, timeout = timeout)
else: else:
ValueError("No arguments to set") ValueError("No arguments to set")
def move(self, ids, location, timeout=None): 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.""" """Move torrent data to the new location."""
self._rpc_version_warning(6) self._rpc_version_warning(6)
args = {'location': location, 'move': True} args = {'location': location, 'move': True}
self._request('torrent-set-location', args, ids, True, timeout=timeout) self._request('torrent-set-location', args, ids, True, timeout = timeout)
def locate(self, ids, location, timeout=None): def move(self, ids, location, timeout = None):
"""Locate torrent data at the location.""" """
.. 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) self._rpc_version_warning(6)
args = {'location': location, 'move': False} args = {'location': location, 'move': False}
self._request('torrent-set-location', args, ids, True, timeout=timeout) self._request('torrent-set-location', args, ids, True, timeout = timeout)
def queue_top(self, ids, timeout=None): 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.""" """Move transfer to the top of the queue."""
self._rpc_version_warning(14) self._rpc_version_warning(14)
self._request('queue-move-top', ids=ids, require_ids=True, timeout=timeout) self._request('queue-move-top', ids = ids, require_ids = True, timeout = timeout)
def queue_bottom(self, ids, timeout=None): def queue_bottom(self, ids, timeout = None):
"""Move transfer to the bottom of the queue.""" """Move transfer to the bottom of the queue."""
self._rpc_version_warning(14) self._rpc_version_warning(14)
self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout) self._request('queue-move-bottom', ids = ids, require_ids = True, timeout = timeout)
def queue_up(self, ids, timeout=None): def queue_up(self, ids, timeout = None):
"""Move transfer up in the queue.""" """Move transfer up in the queue."""
self._rpc_version_warning(14) self._rpc_version_warning(14)
self._request('queue-move-up', ids=ids, require_ids=True, timeout=timeout) self._request('queue-move-up', ids = ids, require_ids = True, timeout = timeout)
def queue_down(self, ids, timeout=None): def queue_down(self, ids, timeout = None):
"""Move transfer down in the queue.""" """Move transfer down in the queue."""
self._rpc_version_warning(14) self._rpc_version_warning(14)
self._request('queue-move-down', ids=ids, require_ids=True, timeout=timeout) self._request('queue-move-down', ids = ids, require_ids = True, timeout = timeout)
def get_session(self, timeout=None): def get_session(self, timeout = None):
"""Get session parameters""" """Get session parameters"""
self._request('session-get', timeout=timeout) self._request('session-get', timeout = timeout)
self._update_server_version() self._update_server_version()
return self.session return self.session
def set_session(self, timeout=None, **kwargs): def set_session(self, timeout = None, **kwargs):
""" """
Set session parameters. The parameters are: Set session parameters. The parameters are:
@@ -655,32 +796,31 @@ class Client(object):
if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']: if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']:
raise ValueError('Invalid encryption value') raise ValueError('Invalid encryption value')
argument = make_rpc_name(key) argument = make_rpc_name(key)
(arg, val) = argument_value_convert('session-set' (arg, val) = argument_value_convert('session-set' , argument, value, self.rpc_version)
, argument, value, self.rpc_version)
args[arg] = val args[arg] = val
if len(args) > 0: if len(args) > 0:
self._request('session-set', args, timeout=timeout) self._request('session-set', args, timeout = timeout)
def blocklist_update(self, timeout=None): def blocklist_update(self, timeout = None):
"""Update block list. Returns the size of the block list.""" """Update block list. Returns the size of the block list."""
self._rpc_version_warning(5) self._rpc_version_warning(5)
result = self._request('blocklist-update', timeout=timeout) result = self._request('blocklist-update', timeout = timeout)
if 'blocklist-size' in result: if 'blocklist-size' in result:
return result['blocklist-size'] return result['blocklist-size']
return None return None
def port_test(self, timeout=None): def port_test(self, timeout = None):
""" """
Tests to see if your incoming peer port is accessible from the Tests to see if your incoming peer port is accessible from the
outside world. outside world.
""" """
self._rpc_version_warning(5) self._rpc_version_warning(5)
result = self._request('port-test', timeout=timeout) result = self._request('port-test', timeout = timeout)
if 'port-is-open' in result: if 'port-is-open' in result:
return result['port-is-open'] return result['port-is-open']
return None return None
def session_stats(self, timeout=None): def session_stats(self, timeout = None):
"""Get session statistics""" """Get session statistics"""
self._request('session-stats', timeout=timeout) self._request('session-stats', timeout = timeout)
return self.session return self.session
+2 -2
View File
@@ -15,9 +15,9 @@ class TransmissionError(Exception):
def __str__(self): def __str__(self):
if self.original: if self.original:
original_name = type(self.original).__name__ original_name = type(self.original).__name__
return '%s Original exception: %s, "%s"' % (self.message, original_name, str(self.original)) return '%s Original exception: %s, "%s"' % (self._message, original_name, str(self.original))
else: else:
return self.message return self._message
class HTTPHandlerError(Exception): class HTTPHandlerError(Exception):
""" """
+84 -20
View File
@@ -2,6 +2,8 @@
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com> # Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
# Licensed under the MIT license. # Licensed under the MIT license.
from transmissionrpc.utils import Field
class Session(object): class Session(object):
""" """
Session is a class holding the session data for a Transmission daemon. Session is a class holding the session data for a Transmission daemon.
@@ -12,33 +14,95 @@ class Session(object):
``download-dir`` -> ``download_dir``. ``download-dir`` -> ``download_dir``.
""" """
def __init__(self, fields=None): def __init__(self, client=None, fields=None):
self.fields = {} self._client = client
self._fields = {}
if fields is not None: if fields is not None:
self.update(fields) self._update_fields(fields)
def update(self, other):
"""Update the session data from a session arguments dictionary"""
fields = None
if isinstance(other, dict):
fields = other
elif isinstance(other, Session):
fields = other.fields
else:
raise ValueError('Cannot update with supplied data')
for key, value in fields.iteritems():
self.fields[key.replace('-', '_')] = value
def __getattr__(self, name): def __getattr__(self, name):
try: try:
return self.fields[name] return self._fields[name].value
except KeyError: except KeyError:
raise AttributeError('No attribute %s' % name) raise AttributeError('No attribute %s' % name)
def __str__(self): def __str__(self):
text = '' text = ''
for key in sorted(self.fields.keys()): for key in sorted(self._fields.keys()):
text += "% 32s: %s\n" % (key[-32:], self.fields[key]) text += "% 32s: %s\n" % (key[-32:], self._fields[key].value)
return text 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.")
+296 -48
View File
@@ -4,8 +4,8 @@
import sys, datetime import sys, datetime
from transmissionrpc.constants import PRIORITY from transmissionrpc.constants import PRIORITY, RATIO_LIMIT, IDLE_LIMIT
from transmissionrpc.utils import format_timedelta from transmissionrpc.utils import Field, format_timedelta
class Torrent(object): class Torrent(object):
""" """
@@ -17,17 +17,19 @@ class Torrent(object):
def __init__(self, client, fields): def __init__(self, client, fields):
if 'id' not in fields: if 'id' not in fields:
raise ValueError('Torrent requires an id') raise ValueError('Torrent requires an id')
self.fields = {} self._fields = {}
self.update(fields) self._update_fields(fields)
self.client = client self._incoming_pending= False
self._outgoing_pending= False
self._client = client
def _getNameString(self, codec=None): def _getNameString(self, codec=None):
if codec is None: if codec is None:
codec = sys.getdefaultencoding() codec = sys.getdefaultencoding()
name = None name = None
# try to find name # try to find name
if 'name' in self.fields: if 'name' in self._fields:
name = self.fields['name'] name = self._fields['name'].value
# if name is unicode, try to decode # if name is unicode, try to decode
if isinstance(name, unicode): if isinstance(name, unicode):
try: try:
@@ -37,7 +39,7 @@ class Torrent(object):
return name return name
def __repr__(self): def __repr__(self):
tid = self.fields['id'] tid = self._fields['id'].value
name = self._getNameString() name = self._getNameString()
if isinstance(name, str): if isinstance(name, str):
return '<Torrent %d \"%s\">' % (tid, name) return '<Torrent %d \"%s\">' % (tid, name)
@@ -52,12 +54,53 @@ class Torrent(object):
return 'Torrent' return 'Torrent'
def __copy__(self): def __copy__(self):
return Torrent(self.client, self.fields) 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): def _rpc_version(self):
if self.client: if self._client:
return self.client.rpc_version return self._client.rpc_version
return 2 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): def _status_old(self, code):
mapping = { mapping = {
@@ -82,26 +125,12 @@ class Torrent(object):
return mapping[code] return mapping[code]
def _status(self): def _status(self):
code = self.fields['status'] code = self._fields['status'].value
if self._rpc_version() >= 14: if self._rpc_version() >= 14:
return self._status_new(code) return self._status_new(code)
else: else:
return self._status_old(code) return self._status_old(code)
def update(self, other):
"""
Update the torrent data from a Transmission JSON-RPC arguments dictionary
"""
fields = None
if isinstance(other, dict):
fields = other
elif isinstance(other, Torrent):
fields = other.fields
else:
raise ValueError('Cannot update with supplied data')
for key, value in fields.iteritems():
self.fields[key.replace('-', '_')] = value
def files(self): def files(self):
""" """
Get list of files for this torrent. Get list of files for this torrent.
@@ -122,11 +151,11 @@ class Torrent(object):
} }
""" """
result = {} result = {}
if 'files' in self.fields: if 'files' in self._fields:
indices = xrange(len(self.fields['files'])) files = self._fields['files'].value
files = self.fields['files'] indices = xrange(len(files))
priorities = self.fields['priorities'] priorities = self._fields['priorities'].value
wanted = self.fields['wanted'] wanted = self._fields['wanted'].value
for item in zip(indices, files, priorities, wanted): for item in zip(indices, files, priorities, wanted):
selected = True if item[3] else False selected = True if item[3] else False
priority = PRIORITY[item[2]] priority = PRIORITY[item[2]]
@@ -138,12 +167,6 @@ class Torrent(object):
'completed': item[1]['bytesCompleted']} 'completed': item[1]['bytesCompleted']}
return result return result
def __getattr__(self, name):
try:
return self.fields[name]
except KeyError:
raise AttributeError('No attribute %s' % name)
@property @property
def status(self): def status(self):
""" """
@@ -157,19 +180,21 @@ class Torrent(object):
def progress(self): def progress(self):
"""Get the download progress in percent.""" """Get the download progress in percent."""
try: try:
return 100.0 * (self.fields['sizeWhenDone'] - self.fields['leftUntilDone']) / float(self.fields['sizeWhenDone']) size = self._fields['sizeWhenDone'].value
left = self._fields['leftUntilDone'].value
return 100.0 * (size - left) / float(size)
except ZeroDivisionError: except ZeroDivisionError:
return 0.0 return 0.0
@property @property
def ratio(self): def ratio(self):
"""Get the upload/download ratio.""" """Get the upload/download ratio."""
return float(self.fields['uploadRatio']) return float(self._fields['uploadRatio'].value)
@property @property
def eta(self): def eta(self):
"""Get the "eta" as datetime.timedelta.""" """Get the "eta" as datetime.timedelta."""
eta = self.fields['eta'] eta = self._fields['eta'].value
if eta >= 0: if eta >= 0:
return datetime.timedelta(seconds=eta) return datetime.timedelta(seconds=eta)
else: else:
@@ -178,22 +203,22 @@ class Torrent(object):
@property @property
def date_active(self): def date_active(self):
"""Get the attribute "activityDate" as datetime.datetime.""" """Get the attribute "activityDate" as datetime.datetime."""
return datetime.datetime.fromtimestamp(self.fields['activityDate']) return datetime.datetime.fromtimestamp(self._fields['activityDate'].value)
@property @property
def date_added(self): def date_added(self):
"""Get the attribute "addedDate" as datetime.datetime.""" """Get the attribute "addedDate" as datetime.datetime."""
return datetime.datetime.fromtimestamp(self.fields['addedDate']) return datetime.datetime.fromtimestamp(self._fields['addedDate'].value)
@property @property
def date_started(self): def date_started(self):
"""Get the attribute "startDate" as datetime.datetime.""" """Get the attribute "startDate" as datetime.datetime."""
return datetime.datetime.fromtimestamp(self.fields['startDate']) return datetime.datetime.fromtimestamp(self._fields['startDate'].value)
@property @property
def date_done(self): def date_done(self):
"""Get the attribute "doneDate" as datetime.datetime.""" """Get the attribute "doneDate" as datetime.datetime."""
return datetime.datetime.fromtimestamp(self.fields['doneDate']) return datetime.datetime.fromtimestamp(self._fields['doneDate'].value)
def format_eta(self): def format_eta(self):
""" """
@@ -203,7 +228,7 @@ class Torrent(object):
* If eta is -2 the result is 'unknown' * If eta is -2 the result is 'unknown'
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>. * Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
""" """
eta = self.fields['eta'] eta = self._fields['eta'].value
if eta == -1: if eta == -1:
return 'not available' return 'not available'
elif eta == -2: elif eta == -2:
@@ -211,10 +236,233 @@ class Torrent(object):
else: else:
return format_timedelta(self.eta) return format_timedelta(self.eta)
@property def _get_download_limit(self):
def priority(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. Get the priority as string.
Can be one of 'low', 'normal', 'high'. Can be one of 'low', 'normal', 'high'.
""" """
return PRIORITY[self.fields['bandwidthPriority']] 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)
+3
View File
@@ -3,6 +3,7 @@
# Licensed under the MIT license. # Licensed under the MIT license.
import socket, datetime, logging import socket, datetime, logging
from collections import namedtuple
import transmissionrpc.constants as constants import transmissionrpc.constants as constants
from transmissionrpc.constants import LOGGER from transmissionrpc.constants import LOGGER
@@ -186,3 +187,5 @@ def add_stdout_logger(level='debug'):
trpc_logger.setLevel(loglevel) trpc_logger.setLevel(loglevel)
loghandler.setLevel(loglevel) loghandler.setLevel(loglevel)
trpc_logger.addHandler(loghandler) trpc_logger.addHandler(loghandler)
Field = namedtuple('Field', ['value', 'dirty'])