Initial torrent support
Transmission downloader Code cleanup
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
from couchpotato.core.event import addEvent
|
||||
from couchpotato.core.helpers.encoding import toSafeString
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
from couchpotato.environment import Env
|
||||
from couchpotato.core.helpers.encoding import toSafeString
|
||||
import os
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -42,6 +42,6 @@ class Blackhole(Downloader):
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
@@ -29,6 +29,7 @@ config = [{
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'type': 'password',
|
||||
},
|
||||
{
|
||||
'name': 'paused',
|
||||
@@ -43,6 +44,7 @@ config = [{
|
||||
{
|
||||
'name': 'ratio',
|
||||
'default': 10,
|
||||
'type': 'int',
|
||||
'advanced': True,
|
||||
'description': 'Stop transfer when reaching ratio',
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from base64 import b64encode
|
||||
from couchpotato.core.downloaders.base import Downloader
|
||||
from couchpotato.core.helpers.encoding import isInt
|
||||
from couchpotato.core.logger import CPLog
|
||||
@@ -17,7 +18,6 @@ class Transmission(Downloader):
|
||||
|
||||
log.info('Sending "%s" to Transmission.' % data.get('name'))
|
||||
|
||||
|
||||
# Load host from config and split out port.
|
||||
host = self.conf('host').split(':')
|
||||
if not isInt(host[1]):
|
||||
@@ -26,24 +26,21 @@ class Transmission(Downloader):
|
||||
|
||||
# Set parameters for Transmission
|
||||
params = {
|
||||
'paused': self.conf('paused', 0),
|
||||
'download_dir': self.conf('directory', None)
|
||||
}
|
||||
change_params = {
|
||||
'seedRatioLimit': self.conf('ratio'),
|
||||
'seedRatioMode': 1 if self.conf('ratio') else 0
|
||||
'paused': self.conf('paused', default = 0),
|
||||
'download_dir': self.conf('directory', default = None)
|
||||
}
|
||||
|
||||
try:
|
||||
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
|
||||
for item in tr_id:
|
||||
try:
|
||||
tc.change(item, timeout = None, **change_params)
|
||||
except transmissionrpc.TransmissionError, e:
|
||||
log.error('Failed to change settings for transfer in transmission: %s' % e)
|
||||
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)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -23,4 +23,12 @@ class Score(Plugin):
|
||||
|
||||
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
|
||||
|
||||
@@ -28,7 +28,7 @@ config = [{
|
||||
{
|
||||
'name': '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',
|
||||
},
|
||||
],
|
||||
}, {
|
||||
|
||||
@@ -161,7 +161,7 @@ class Searcher(Plugin):
|
||||
single_category = kwargs.get('single_category', False)
|
||||
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']))
|
||||
return False
|
||||
|
||||
|
||||
@@ -48,6 +48,20 @@ class YarrProvider(Provider):
|
||||
def __init__(self):
|
||||
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):
|
||||
try:
|
||||
hostname = urlparse(url).hostname
|
||||
|
||||
@@ -3,7 +3,7 @@ from couchpotato.core.helpers.variable import mergeDicts
|
||||
from couchpotato.core.logger import CPLog
|
||||
from couchpotato.core.plugins.base import Plugin
|
||||
import json
|
||||
import os.path
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
|
||||
|
||||
@@ -6,22 +6,5 @@ import time
|
||||
class NZBProvider(YarrProvider):
|
||||
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):
|
||||
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.helpers.encoding import toUnicode
|
||||
from couchpotato.core.helpers.variable import tryInt
|
||||
|
||||
@@ -3,4 +3,19 @@ from .main import KickAssTorrents
|
||||
def start():
|
||||
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.providers.torrent.base import TorrentProvider
|
||||
import StringIO
|
||||
import gzip
|
||||
import re
|
||||
import traceback
|
||||
|
||||
log = CPLog(__name__)
|
||||
|
||||
@@ -7,8 +14,124 @@ log = CPLog(__name__)
|
||||
class KickAssTorrents(TorrentProvider):
|
||||
|
||||
urls = {
|
||||
'download': 'http://torrents.thepiratebay.org/%s/%s.torrent',
|
||||
'test': 'http://www.kat.ph/',
|
||||
'detail': 'http://www.kat.ph/%s-t%s.html',
|
||||
'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(' ', ' ')
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@ from transmissionrpc.client import Client
|
||||
from transmissionrpc.utils import add_stdout_logger
|
||||
|
||||
__author__ = u'Erik Svensson <erik.public@gmail.com>'
|
||||
__version__ = u'0.8'
|
||||
__version__ = u'0.9'
|
||||
__copyright__ = u'Copyright (c) 2008-2011 Erik Svensson'
|
||||
__license__ = u'MIT'
|
||||
|
||||
+228
-88
@@ -2,7 +2,7 @@
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import re, time
|
||||
import re, time, operator, warnings
|
||||
import urllib2, urlparse, base64
|
||||
|
||||
try:
|
||||
@@ -36,7 +36,7 @@ def debug_httperror(error):
|
||||
'data': data,
|
||||
}
|
||||
},
|
||||
indent=2
|
||||
indent = 2
|
||||
)
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ 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):
|
||||
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:
|
||||
@@ -91,7 +91,7 @@ class Client(object):
|
||||
elif user or password:
|
||||
LOGGER.warning('Either user or password missing, not using authentication.')
|
||||
self._sequence = 0
|
||||
self.session = Session()
|
||||
self.session = None
|
||||
self.session_id = 0
|
||||
self.server_version = None
|
||||
self.protocol_version = None
|
||||
@@ -117,9 +117,9 @@ class Client(object):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@@ -129,7 +129,7 @@ class Client(object):
|
||||
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))
|
||||
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
|
||||
@@ -150,7 +150,7 @@ class Client(object):
|
||||
request_count += 1
|
||||
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
|
||||
"""
|
||||
@@ -182,7 +182,7 @@ class Client(object):
|
||||
LOGGER.error('HTTP data: \"%s\"' % (http_data))
|
||||
raise
|
||||
|
||||
LOGGER.debug(json.dumps(data, indent=2))
|
||||
LOGGER.debug(json.dumps(data, indent = 2))
|
||||
if 'result' in data:
|
||||
if data['result'] != 'success':
|
||||
raise TransmissionError('Query failed with result \"%s\".' % (data['result']))
|
||||
@@ -264,7 +264,10 @@ class Client(object):
|
||||
"""
|
||||
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):
|
||||
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.
|
||||
"""
|
||||
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:
|
||||
|
||||
===================== ===== =========== =============================================================
|
||||
@@ -327,6 +331,41 @@ class Client(object):
|
||||
``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:
|
||||
@@ -335,31 +374,16 @@ class Client(object):
|
||||
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)
|
||||
(arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version)
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
|
||||
===================== ===== =========== =============================================================
|
||||
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.
|
||||
===================== ===== =========== =============================================================
|
||||
.. WARNING::
|
||||
Deprecated, please use add_torrent.
|
||||
"""
|
||||
if uri is None:
|
||||
raise ValueError('add_uri requires a URI.')
|
||||
@@ -370,47 +394,145 @@ class Client(object):
|
||||
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)
|
||||
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
|
||||
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)
|
||||
{'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout = timeout)
|
||||
|
||||
def start(self, ids, bypass_queue=False, timeout=None):
|
||||
"""start torrent(s) with provided id(s)"""
|
||||
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)
|
||||
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)"""
|
||||
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)"""
|
||||
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)"""
|
||||
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):
|
||||
"""Get detailed information for torrent(s) with provided id(s)."""
|
||||
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
|
||||
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,
|
||||
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']
|
||||
request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout)
|
||||
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):
|
||||
def set_files(self, items, timeout = None):
|
||||
"""
|
||||
Set file properties. Takes a dictionary with similar contents as the result
|
||||
of `get_files`.
|
||||
@@ -498,14 +620,7 @@ class Client(object):
|
||||
args['priority_low'] = low
|
||||
self.change([tid], **args)
|
||||
|
||||
def list(self, timeout=None):
|
||||
"""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):
|
||||
def change_torrent(self, ids, timeout = None, **kwargs):
|
||||
"""
|
||||
Change torrent parameters for the torrent(s) with the supplied id's. The
|
||||
parameters are:
|
||||
@@ -546,54 +661,80 @@ class Client(object):
|
||||
args = {}
|
||||
for key, value in kwargs.iteritems():
|
||||
argument = make_rpc_name(key)
|
||||
(arg, val) = argument_value_convert('torrent-set'
|
||||
, argument, value, self.rpc_version)
|
||||
(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)
|
||||
self._request('torrent-set', args, ids, True, timeout = timeout)
|
||||
else:
|
||||
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."""
|
||||
self._rpc_version_warning(6)
|
||||
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):
|
||||
"""Locate torrent data at the location."""
|
||||
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)
|
||||
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."""
|
||||
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."""
|
||||
self._rpc_version_warning(14)
|
||||
self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout)
|
||||
|
||||
def queue_up(self, ids, timeout=None):
|
||||
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)
|
||||
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."""
|
||||
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"""
|
||||
self._request('session-get', timeout=timeout)
|
||||
self._request('session-get', timeout = timeout)
|
||||
self._update_server_version()
|
||||
return self.session
|
||||
|
||||
def set_session(self, timeout=None, **kwargs):
|
||||
def set_session(self, timeout = None, **kwargs):
|
||||
"""
|
||||
Set session parameters. The parameters are:
|
||||
|
||||
@@ -655,32 +796,31 @@ class Client(object):
|
||||
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)
|
||||
(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)
|
||||
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."""
|
||||
self._rpc_version_warning(5)
|
||||
result = self._request('blocklist-update', timeout=timeout)
|
||||
result = self._request('blocklist-update', timeout = timeout)
|
||||
if 'blocklist-size' in result:
|
||||
return result['blocklist-size']
|
||||
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
|
||||
outside world.
|
||||
"""
|
||||
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:
|
||||
return result['port-is-open']
|
||||
return None
|
||||
|
||||
def session_stats(self, timeout=None):
|
||||
def session_stats(self, timeout = None):
|
||||
"""Get session statistics"""
|
||||
self._request('session-stats', timeout=timeout)
|
||||
self._request('session-stats', timeout = timeout)
|
||||
return self.session
|
||||
|
||||
@@ -15,9 +15,9 @@ class TransmissionError(Exception):
|
||||
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))
|
||||
return '%s Original exception: %s, "%s"' % (self._message, original_name, str(self.original))
|
||||
else:
|
||||
return self.message
|
||||
return self._message
|
||||
|
||||
class HTTPHandlerError(Exception):
|
||||
"""
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# 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.
|
||||
@@ -12,33 +14,95 @@ class Session(object):
|
||||
``download-dir`` -> ``download_dir``.
|
||||
"""
|
||||
|
||||
def __init__(self, fields=None):
|
||||
self.fields = {}
|
||||
def __init__(self, client=None, fields=None):
|
||||
self._client = client
|
||||
self._fields = {}
|
||||
if fields is not None:
|
||||
self.update(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
|
||||
self._update_fields(fields)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.fields[name]
|
||||
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])
|
||||
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.")
|
||||
|
||||
+296
-48
@@ -4,8 +4,8 @@
|
||||
|
||||
import sys, datetime
|
||||
|
||||
from transmissionrpc.constants import PRIORITY
|
||||
from transmissionrpc.utils import format_timedelta
|
||||
from transmissionrpc.constants import PRIORITY, RATIO_LIMIT, IDLE_LIMIT
|
||||
from transmissionrpc.utils import Field, format_timedelta
|
||||
|
||||
class Torrent(object):
|
||||
"""
|
||||
@@ -17,17 +17,19 @@ class Torrent(object):
|
||||
def __init__(self, client, fields):
|
||||
if 'id' not in fields:
|
||||
raise ValueError('Torrent requires an id')
|
||||
self.fields = {}
|
||||
self.update(fields)
|
||||
self.client = client
|
||||
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']
|
||||
if 'name' in self._fields:
|
||||
name = self._fields['name'].value
|
||||
# if name is unicode, try to decode
|
||||
if isinstance(name, unicode):
|
||||
try:
|
||||
@@ -37,7 +39,7 @@ class Torrent(object):
|
||||
return name
|
||||
|
||||
def __repr__(self):
|
||||
tid = self.fields['id']
|
||||
tid = self._fields['id'].value
|
||||
name = self._getNameString()
|
||||
if isinstance(name, str):
|
||||
return '<Torrent %d \"%s\">' % (tid, name)
|
||||
@@ -52,12 +54,53 @@ class Torrent(object):
|
||||
return 'Torrent'
|
||||
|
||||
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):
|
||||
if self.client:
|
||||
return self.client.rpc_version
|
||||
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 = {
|
||||
@@ -82,26 +125,12 @@ class Torrent(object):
|
||||
return mapping[code]
|
||||
|
||||
def _status(self):
|
||||
code = self.fields['status']
|
||||
code = self._fields['status'].value
|
||||
if self._rpc_version() >= 14:
|
||||
return self._status_new(code)
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
Get list of files for this torrent.
|
||||
@@ -122,11 +151,11 @@ class Torrent(object):
|
||||
}
|
||||
"""
|
||||
result = {}
|
||||
if 'files' in self.fields:
|
||||
indices = xrange(len(self.fields['files']))
|
||||
files = self.fields['files']
|
||||
priorities = self.fields['priorities']
|
||||
wanted = self.fields['wanted']
|
||||
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]]
|
||||
@@ -138,12 +167,6 @@ class Torrent(object):
|
||||
'completed': item[1]['bytesCompleted']}
|
||||
return result
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.fields[name]
|
||||
except KeyError:
|
||||
raise AttributeError('No attribute %s' % name)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
@@ -157,19 +180,21 @@ class Torrent(object):
|
||||
def progress(self):
|
||||
"""Get the download progress in percent."""
|
||||
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:
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def ratio(self):
|
||||
"""Get the upload/download ratio."""
|
||||
return float(self.fields['uploadRatio'])
|
||||
return float(self._fields['uploadRatio'].value)
|
||||
|
||||
@property
|
||||
def eta(self):
|
||||
"""Get the "eta" as datetime.timedelta."""
|
||||
eta = self.fields['eta']
|
||||
eta = self._fields['eta'].value
|
||||
if eta >= 0:
|
||||
return datetime.timedelta(seconds=eta)
|
||||
else:
|
||||
@@ -178,22 +203,22 @@ class Torrent(object):
|
||||
@property
|
||||
def date_active(self):
|
||||
"""Get the attribute "activityDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self.fields['activityDate'])
|
||||
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'])
|
||||
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'])
|
||||
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'])
|
||||
return datetime.datetime.fromtimestamp(self._fields['doneDate'].value)
|
||||
|
||||
def format_eta(self):
|
||||
"""
|
||||
@@ -203,7 +228,7 @@ class Torrent(object):
|
||||
* If eta is -2 the result is 'unknown'
|
||||
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
|
||||
"""
|
||||
eta = self.fields['eta']
|
||||
eta = self._fields['eta'].value
|
||||
if eta == -1:
|
||||
return 'not available'
|
||||
elif eta == -2:
|
||||
@@ -211,10 +236,233 @@ class Torrent(object):
|
||||
else:
|
||||
return format_timedelta(self.eta)
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
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']]
|
||||
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,6 +3,7 @@
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import socket, datetime, logging
|
||||
from collections import namedtuple
|
||||
import transmissionrpc.constants as constants
|
||||
from transmissionrpc.constants import LOGGER
|
||||
|
||||
@@ -186,3 +187,5 @@ def add_stdout_logger(level='debug'):
|
||||
trpc_logger.setLevel(loglevel)
|
||||
loghandler.setLevel(loglevel)
|
||||
trpc_logger.addHandler(loghandler)
|
||||
|
||||
Field = namedtuple('Field', ['value', 'dirty'])
|
||||
Reference in New Issue
Block a user