Commits

Erik Svensson committed 3a70d48

New interfaces.

  • Participants
  • Parent commits 0fdcadf

Comments (0)

Files changed (2)

File transmissionrpc/client.py

 # Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
 # Licensed under the MIT license.
 
-import re, time, operator
+import re, time, operator, warnings
 import urllib2, urlparse, base64
 
 try:
         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):
         """
             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
+        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):
         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:
         self._rpc_version_warning(3)
         self._request('torrent-remove',
                     {'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout)
+    remove_torrent = remove
 
     def start(self, ids, bypass_queue=False, timeout=None):
         """start torrent(s) with provided id(s)"""
         if bypass_queue and self.rpc_version >= 14:
             method = 'torrent-start-now'
         self._request(method, {}, ids, True, timeout=timeout)
+    start_torrent = start
 
     def start_all(self, bypass_queue=False, timeout=None):
         """start all torrents respecting the queue order"""
     def stop(self, ids, timeout=None):
         """stop torrent(s) with provided id(s)"""
         self._request('torrent-stop', {}, ids, True, timeout=timeout)
+    stop_torrent = stop
 
     def verify(self, ids, timeout=None):
         """verify torrent(s) with provided id(s)"""
         self._request('torrent-verify', {}, ids, True, timeout=timeout)
+    verify_torrent = verify
 
     def reannounce(self, ids, timeout=None):
         """Reannounce torrent(s) with provided id(s)"""
         self._rpc_version_warning(5)
         self._request('torrent-reannounce', {}, ids, True, timeout=timeout)
+    reannounce_torrent = reannounce
+
+    def add_torrent(self, torrent, timeout=None, **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.
+        ===================== ===== =========== =============================================================
+        """
+        if torrent is None:
+            raise ValueError('add_torrent requires a data or URI.')
+        torrent_data = None
+        try:
+            # check if this is base64 data
+            base64.b64decode(torrent).decode('ascii')
+            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 get_torrent(self, id, arguments=None, timeout=None):
+        """Get information for torrent with provided id."""
+        if not arguments:
+            arguments = self.torrent_get_arguments
+        if not isinstance(id, (int, long, str, unicode)):
+            raise ValueError("Invalid id")
+        return self._request('torrent-get', {'fields': arguments}, id, require_ids=True, timeout=timeout)[id]
+
+    def get_torrents(self, ids=None, arguments=None, timeout=None):
+        """Get information for torrents with provided ids."""
+        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):
         """Get detailed information for torrent(s) with provided id(s)."""
+        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):
         """list all torrents"""
+        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']
         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)
         else:
             ValueError("No arguments to set")
+    change_torrent = change
 
     def move(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)
+    move_torrent_data = move
 
     def locate(self, ids, location, timeout=None):
         """Locate torrent data at the location."""
         self._rpc_version_warning(6)
         args = {'location': location, 'move': False}
         self._request('torrent-set-location', args, ids, True, timeout=timeout)
+    locate_torrent_data = locate
 
     def queue_top(self, ids, timeout=None):
         """Move transfer to the top of the queue."""
             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)

File transmissionrpc/torrent.py

 # Licensed under the MIT license.
 
 import sys, datetime
+from collections import namedtuple
 
 from transmissionrpc.constants import PRIORITY
 from transmissionrpc.utils import format_timedelta
 
+Field = namedtuple('Field', ['value', 'dirty'])
+
 class Torrent(object):
     """
     Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
     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:
         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)
             return 'Torrent'
 
     def __copy__(self):
-        return Torrent(self.client, self.fields)
+        return Torrent(self._client, self._fields)
 
     def _rpc_version(self):
-        if self.client:
-            return self.client.rpc_version
+        if self._client:
+            return self._client.rpc_version
         return 2
     
     def _status_old(self, code):
         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):
+    def _update_fields(self, other):
         """
         Update the torrent data from a Transmission JSON-RPC arguments dictionary
         """
         fields = None
         if isinstance(other, dict):
-            fields = other
+            for key, value in other.iteritems():
+                self._fields[key.replace('-', '_')] = Field(value, False)
         elif isinstance(other, Torrent):
-            fields = other.fields
+            for key in other._fields.keys():
+                self._fields[key] = Field(other._fields[key].value, False)
         else:
             raise ValueError('Cannot update with supplied data')
-        for key, value in fields.iteritems():
-            self.fields[key.replace('-', '_')] = value
+        self._incoming_pending = False
 
     def files(self):
         """
             }
         """
         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]]
 
     def __getattr__(self, name):
         try:
-            return self.fields[name]
+            return self._fields[name].value
         except KeyError:
             raise AttributeError('No attribute %s' % name)
 
     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:
     @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):
         """
         * 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:
         else:
             return format_timedelta(self.eta)
 
-    @property
-    def priority(self):
+    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
+        , "Priority as string. Can be one of 'low', 'normal', 'high'.")
+
+    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):
+        """
+        Get 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")
+
+    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")
+
+    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 _dirty_fields(self):
+        outgoing_keys = ['bandwidthPriority', 'downloadLimit', 'downloadLimited', 'queuePosition'
+            , '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):
+        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(self, timeout=None):
+        self._push()
+        torrent = self._client.get_torrent(self.id, timeout=timeout)
+        self._update_fields(torrent)
+
+    def start(self, bypass_queue=False, timeout=None):
+        """Move torrent data to location"""
+        self._incoming_pending = True
+        self._client.start_torrent(self.id, bypass_queue=bypass_queue, timeout=timeout)
+
+    def stop(self, timeout=None):
+        """Move torrent data to location"""
+        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)