mirror of
https://github.com/correl/Transmission-XBMC.git
synced 2024-11-21 11:08:43 +00:00
Update transmissionrpc to version 0.11
This commit is contained in:
parent
f341a8a125
commit
13dc11f94d
10 changed files with 2179 additions and 1625 deletions
|
@ -7,6 +7,7 @@
|
|||
<import addon="xbmc.python" version="2.14.0"/>
|
||||
<import addon="script.module.simplejson" version="3.3.0"/>
|
||||
<import addon="script.module.beautifulsoup" version="3.0.8" />
|
||||
<import addon="script.module.six" version="1.5.2" />
|
||||
</requires>
|
||||
<extension point="xbmc.python.script"
|
||||
library="default.py" />
|
||||
|
|
|
@ -149,7 +149,7 @@ class TransmissionGUI(xbmcgui.WindowXMLDialog):
|
|||
if selected < 0:
|
||||
return
|
||||
try:
|
||||
self.transmission.add_uri(results[selected]['url'])
|
||||
self.transmission.add_torrent(results[selected]['url'])
|
||||
except:
|
||||
xbmcgui.Dialog().ok(_(0), _(293))
|
||||
return
|
||||
|
|
34
resources/lib/transmissionrpc/__init__.py
Normal file → Executable file
34
resources/lib/transmissionrpc/__init__.py
Normal file → Executable file
|
@ -1,16 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER
|
||||
from transmissionrpc.error import TransmissionError, HTTPHandlerError
|
||||
from transmissionrpc.httphandler import HTTPHandler, DefaultHTTPHandler
|
||||
from transmissionrpc.torrent import Torrent
|
||||
from transmissionrpc.session import Session
|
||||
from transmissionrpc.client import Client
|
||||
from transmissionrpc.utils import add_stdout_logger
|
||||
|
||||
__author__ = u'Erik Svensson <erik.public@gmail.com>'
|
||||
__version__ = u'0.8'
|
||||
__copyright__ = u'Copyright (c) 2008-2011 Erik Svensson'
|
||||
__license__ = u'MIT'
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER
|
||||
from transmissionrpc.error import TransmissionError, HTTPHandlerError
|
||||
from transmissionrpc.httphandler import HTTPHandler, DefaultHTTPHandler
|
||||
from transmissionrpc.torrent import Torrent
|
||||
from transmissionrpc.session import Session
|
||||
from transmissionrpc.client import Client
|
||||
from transmissionrpc.utils import add_stdout_logger, add_file_logger
|
||||
|
||||
__author__ = 'Erik Svensson <erik.public@gmail.com>'
|
||||
__version_major__ = 0
|
||||
__version_minor__ = 11
|
||||
__version__ = '{0}.{1}'.format(__version_major__, __version_minor__)
|
||||
__copyright__ = 'Copyright (c) 2008-2013 Erik Svensson'
|
||||
__license__ = 'MIT'
|
||||
|
|
1667
resources/lib/transmissionrpc/client.py
Normal file → Executable file
1667
resources/lib/transmissionrpc/client.py
Normal file → Executable file
File diff suppressed because it is too large
Load diff
586
resources/lib/transmissionrpc/constants.py
Normal file → Executable file
586
resources/lib/transmissionrpc/constants.py
Normal file → Executable file
|
@ -1,291 +1,295 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import logging
|
||||
|
||||
LOGGER = logging.getLogger('transmissionrpc')
|
||||
LOGGER.setLevel(logging.ERROR)
|
||||
|
||||
def mirror_dict(source):
|
||||
"""
|
||||
Creates a dictionary with all values as keys and all keys as values.
|
||||
"""
|
||||
source.update(dict((value, key) for key, value in source.iteritems()))
|
||||
return source
|
||||
|
||||
DEFAULT_PORT = 9091
|
||||
|
||||
DEFAULT_TIMEOUT = 30.0
|
||||
|
||||
TR_PRI_LOW = -1
|
||||
TR_PRI_NORMAL = 0
|
||||
TR_PRI_HIGH = 1
|
||||
|
||||
PRIORITY = mirror_dict({
|
||||
'low' : TR_PRI_LOW,
|
||||
'normal' : TR_PRI_NORMAL,
|
||||
'high' : TR_PRI_HIGH
|
||||
})
|
||||
|
||||
TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings
|
||||
TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio
|
||||
TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio
|
||||
|
||||
RATIO_LIMIT = mirror_dict({
|
||||
'global' : TR_RATIOLIMIT_GLOBAL,
|
||||
'single' : TR_RATIOLIMIT_SINGLE,
|
||||
'unlimited' : TR_RATIOLIMIT_UNLIMITED
|
||||
})
|
||||
|
||||
TR_IDLELIMIT_GLOBAL = 0 # follow the global settings
|
||||
TR_IDLELIMIT_SINGLE = 1 # override the global settings, seeding until a certain idle time
|
||||
TR_IDLELIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of activity
|
||||
|
||||
IDLE_LIMIT = mirror_dict({
|
||||
'global' : TR_RATIOLIMIT_GLOBAL,
|
||||
'single' : TR_RATIOLIMIT_SINGLE,
|
||||
'unlimited' : TR_RATIOLIMIT_UNLIMITED
|
||||
})
|
||||
|
||||
# A note on argument maps
|
||||
# These maps are used to verify *-set methods. The information is structured in
|
||||
# a tree.
|
||||
# set +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
# | +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
# |
|
||||
# get +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
# +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
|
||||
# Arguments for torrent methods
|
||||
TORRENT_ARGS = {
|
||||
'get' : {
|
||||
'activityDate': ('number', 1, None, None, None, ''),
|
||||
'addedDate': ('number', 1, None, None, None, ''),
|
||||
'announceResponse': ('string', 1, 7, None, None, ''),
|
||||
'announceURL': ('string', 1, 7, None, None, ''),
|
||||
'bandwidthPriority': ('number', 5, None, None, None, ''),
|
||||
'comment': ('string', 1, None, None, None, ''),
|
||||
'corruptEver': ('number', 1, None, None, None, ''),
|
||||
'creator': ('string', 1, None, None, None, ''),
|
||||
'dateCreated': ('number', 1, None, None, None, ''),
|
||||
'desiredAvailable': ('number', 1, None, None, None, ''),
|
||||
'doneDate': ('number', 1, None, None, None, ''),
|
||||
'downloadDir': ('string', 4, None, None, None, ''),
|
||||
'downloadedEver': ('number', 1, None, None, None, ''),
|
||||
'downloaders': ('number', 4, 7, None, None, ''),
|
||||
'downloadLimit': ('number', 1, None, None, None, ''),
|
||||
'downloadLimited': ('boolean', 5, None, None, None, ''),
|
||||
'downloadLimitMode': ('number', 1, 5, None, None, ''),
|
||||
'error': ('number', 1, None, None, None, ''),
|
||||
'errorString': ('number', 1, None, None, None, ''),
|
||||
'eta': ('number', 1, None, None, None, ''),
|
||||
'files': ('array', 1, None, None, None, ''),
|
||||
'fileStats': ('array', 5, None, None, None, ''),
|
||||
'hashString': ('string', 1, None, None, None, ''),
|
||||
'haveUnchecked': ('number', 1, None, None, None, ''),
|
||||
'haveValid': ('number', 1, None, None, None, ''),
|
||||
'honorsSessionLimits': ('boolean', 5, None, None, None, ''),
|
||||
'id': ('number', 1, None, None, None, ''),
|
||||
'isFinished': ('boolean', 9, None, None, None, ''),
|
||||
'isPrivate': ('boolean', 1, None, None, None, ''),
|
||||
'isStalled': ('boolean', 14, None, None, None, ''),
|
||||
'lastAnnounceTime': ('number', 1, 7, None, None, ''),
|
||||
'lastScrapeTime': ('number', 1, 7, None, None, ''),
|
||||
'leechers': ('number', 1, 7, None, None, ''),
|
||||
'leftUntilDone': ('number', 1, None, None, None, ''),
|
||||
'magnetLink': ('string', 7, None, None, None, ''),
|
||||
'manualAnnounceTime': ('number', 1, None, None, None, ''),
|
||||
'maxConnectedPeers': ('number', 1, None, None, None, ''),
|
||||
'metadataPercentComplete': ('number', 7, None, None, None, ''),
|
||||
'name': ('string', 1, None, None, None, ''),
|
||||
'nextAnnounceTime': ('number', 1, 7, None, None, ''),
|
||||
'nextScrapeTime': ('number', 1, 7, None, None, ''),
|
||||
'peer-limit': ('number', 5, None, None, None, ''),
|
||||
'peers': ('array', 2, None, None, None, ''),
|
||||
'peersConnected': ('number', 1, None, None, None, ''),
|
||||
'peersFrom': ('object', 1, None, None, None, ''),
|
||||
'peersGettingFromUs': ('number', 1, None, None, None, ''),
|
||||
'peersKnown': ('number', 1, 13, None, None, ''),
|
||||
'peersSendingToUs': ('number', 1, None, None, None, ''),
|
||||
'percentDone': ('double', 5, None, None, None, ''),
|
||||
'pieces': ('string', 5, None, None, None, ''),
|
||||
'pieceCount': ('number', 1, None, None, None, ''),
|
||||
'pieceSize': ('number', 1, None, None, None, ''),
|
||||
'priorities': ('array', 1, None, None, None, ''),
|
||||
'queuePosition': ('number', 14, None, None, None, ''),
|
||||
'rateDownload': ('number', 1, None, None, None, ''),
|
||||
'rateUpload': ('number', 1, None, None, None, ''),
|
||||
'recheckProgress': ('double', 1, None, None, None, ''),
|
||||
'scrapeResponse': ('string', 1, 7, None, None, ''),
|
||||
'scrapeURL': ('string', 1, 7, None, None, ''),
|
||||
'seeders': ('number', 1, 7, None, None, ''),
|
||||
'seedIdleLimit': ('number', 10, None, None, None, ''),
|
||||
'seedIdleMode': ('number', 10, None, None, None, ''),
|
||||
'seedRatioLimit': ('double', 5, None, None, None, ''),
|
||||
'seedRatioMode': ('number', 5, None, None, None, ''),
|
||||
'sizeWhenDone': ('number', 1, None, None, None, ''),
|
||||
'startDate': ('number', 1, None, None, None, ''),
|
||||
'status': ('number', 1, None, None, None, ''),
|
||||
'swarmSpeed': ('number', 1, 7, None, None, ''),
|
||||
'timesCompleted': ('number', 1, 7, None, None, ''),
|
||||
'trackers': ('array', 1, None, None, None, ''),
|
||||
'trackerStats': ('object', 7, None, None, None, ''),
|
||||
'totalSize': ('number', 1, None, None, None, ''),
|
||||
'torrentFile': ('string', 5, None, None, None, ''),
|
||||
'uploadedEver': ('number', 1, None, None, None, ''),
|
||||
'uploadLimit': ('number', 1, None, None, None, ''),
|
||||
'uploadLimitMode': ('number', 1, 5, None, None, ''),
|
||||
'uploadLimited': ('boolean', 5, None, None, None, ''),
|
||||
'uploadRatio': ('double', 1, None, None, None, ''),
|
||||
'wanted': ('array', 1, None, None, None, ''),
|
||||
'webseeds': ('array', 1, None, None, None, ''),
|
||||
'webseedsSendingToUs': ('number', 1, None, None, None, ''),
|
||||
},
|
||||
'set': {
|
||||
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
|
||||
'downloadLimit': ('number', 5, None, 'speed-limit-down', None, 'Set the speed limit for download in Kib/s.'),
|
||||
'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None, 'Enable download speed limiter.'),
|
||||
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
|
||||
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
|
||||
'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."),
|
||||
'location': ('array', 1, None, None, None, 'Local download location.'),
|
||||
'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
|
||||
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
|
||||
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
|
||||
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
|
||||
'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'),
|
||||
'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
|
||||
'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
|
||||
'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
|
||||
'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
|
||||
'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
|
||||
'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
|
||||
'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
|
||||
'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited', 'Enable upload speed limiter.'),
|
||||
'trackerAdd': ('array', 10, None, None, None, 'Array of string with announce URLs to add.'),
|
||||
'trackerRemove': ('array', 10, None, None, None, 'Array of ids of trackers to remove.'),
|
||||
'trackerReplace': ('array', 10, None, None, None, 'Array of (id, url) tuples where the announce URL should be replaced.'),
|
||||
'uploadLimit': ('number', 5, None, 'speed-limit-up', None, 'Set the speed limit for upload in Kib/s.'),
|
||||
'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None, 'Enable upload speed limiter.'),
|
||||
},
|
||||
'add': {
|
||||
'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
|
||||
'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
|
||||
'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'),
|
||||
'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."),
|
||||
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
|
||||
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
|
||||
'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
|
||||
'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
|
||||
'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
|
||||
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
|
||||
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
|
||||
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
|
||||
}
|
||||
}
|
||||
|
||||
# Arguments for session methods
|
||||
SESSION_ARGS = {
|
||||
'get': {
|
||||
"alt-speed-down": ('number', 5, None, None, None, ''),
|
||||
"alt-speed-enabled": ('boolean', 5, None, None, None, ''),
|
||||
"alt-speed-time-begin": ('number', 5, None, None, None, ''),
|
||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None, ''),
|
||||
"alt-speed-time-end": ('number', 5, None, None, None, ''),
|
||||
"alt-speed-time-day": ('number', 5, None, None, None, ''),
|
||||
"alt-speed-up": ('number', 5, None, None, None, ''),
|
||||
"blocklist-enabled": ('boolean', 5, None, None, None, ''),
|
||||
"blocklist-size": ('number', 5, None, None, None, ''),
|
||||
"blocklist-url": ('string', 11, None, None, None, ''),
|
||||
"cache-size-mb": ('number', 10, None, None, None, ''),
|
||||
"config-dir": ('string', 8, None, None, None, ''),
|
||||
"dht-enabled": ('boolean', 6, None, None, None, ''),
|
||||
"download-dir": ('string', 1, None, None, None, ''),
|
||||
"download-dir-free-space": ('number', 12, None, None, None, ''),
|
||||
"download-queue-size": ('number', 14, None, None, None, ''),
|
||||
"download-queue-enabled": ('boolean', 14, None, None, None, ''),
|
||||
"encryption": ('string', 1, None, None, None, ''),
|
||||
"idle-seeding-limit": ('number', 10, None, None, None, ''),
|
||||
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, ''),
|
||||
"incomplete-dir": ('string', 7, None, None, None, ''),
|
||||
"incomplete-dir-enabled": ('boolean', 7, None, None, None, ''),
|
||||
"lpd-enabled": ('boolean', 9, None, None, None, ''),
|
||||
"peer-limit": ('number', 1, 5, None, None, ''),
|
||||
"peer-limit-global": ('number', 5, None, None, None, ''),
|
||||
"peer-limit-per-torrent": ('number', 5, None, None, None, ''),
|
||||
"pex-allowed": ('boolean', 1, 5, None, None, ''),
|
||||
"pex-enabled": ('boolean', 5, None, None, None, ''),
|
||||
"port": ('number', 1, 5, None, None, ''),
|
||||
"peer-port": ('number', 5, None, None, None, ''),
|
||||
"peer-port-random-on-start": ('boolean', 5, None, None, None, ''),
|
||||
"port-forwarding-enabled": ('boolean', 1, None, None, None, ''),
|
||||
"queue-stalled-minutes": ('number', 14, None, None, None, ''),
|
||||
"queue-stalled-enabled": ('boolean', 14, None, None, None, ''),
|
||||
"rename-partial-files": ('boolean', 8, None, None, None, ''),
|
||||
"rpc-version": ('number', 4, None, None, None, ''),
|
||||
"rpc-version-minimum": ('number', 4, None, None, None, ''),
|
||||
"script-torrent-done-enabled": ('boolean', 9, None, None, None, ''),
|
||||
"script-torrent-done-filename": ('string', 9, None, None, None, ''),
|
||||
"seedRatioLimit": ('double', 5, None, None, None, ''),
|
||||
"seedRatioLimited": ('boolean', 5, None, None, None, ''),
|
||||
"seed-queue-size": ('number', 14, None, None, None, ''),
|
||||
"seed-queue-enabled": ('boolean', 14, None, None, None, ''),
|
||||
"speed-limit-down": ('number', 1, None, None, None, ''),
|
||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None, ''),
|
||||
"speed-limit-up": ('number', 1, None, None, None, ''),
|
||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None, ''),
|
||||
"start-added-torrents": ('boolean', 9, None, None, None, ''),
|
||||
"trash-original-torrent-files": ('boolean', 9, None, None, None, ''),
|
||||
'units': ('object', 10, None, None, None, ''),
|
||||
'utp-enabled': ('boolean', 13, None, None, None, ''),
|
||||
"version": ('string', 3, None, None, None, ''),
|
||||
},
|
||||
'set': {
|
||||
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
||||
"alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
|
||||
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
|
||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'Enables alternate speeds scheduling.'),
|
||||
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
|
||||
"alt-speed-time-day": ('number', 5, None, None, None, 'Enables alternate speeds scheduling these days.'),
|
||||
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
|
||||
"blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
|
||||
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
|
||||
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
|
||||
"dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
|
||||
"download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
|
||||
"download-queue-size": ('number', 14, None, None, None, 'Number of parallel downloads.'),
|
||||
"download-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel download restriction.'),
|
||||
"encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
|
||||
"idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
|
||||
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
|
||||
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
|
||||
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.'),
|
||||
"lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
|
||||
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers'),
|
||||
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers'),
|
||||
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer'),
|
||||
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
|
||||
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
|
||||
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
||||
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
|
||||
"peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
|
||||
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'Enables port forwarding.'),
|
||||
"rename-partial-files": ('boolean', 8, None, None, None, 'Appends ".part" to incomplete files'),
|
||||
"queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
|
||||
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
|
||||
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'Whether or not to call the "done" script.'),
|
||||
"script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
|
||||
"seed-queue-size": ('number', 14, None, None, None, 'Number of parallel uploads.'),
|
||||
"seed-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel upload restriction.'),
|
||||
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
|
||||
"seedRatioLimited": ('boolean', 5, None, None, None, 'Enables seed ration limit.'),
|
||||
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
|
||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'Enables download speed limiting.'),
|
||||
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
|
||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'Enables upload speed limiting.'),
|
||||
"start-added-torrents": ('boolean', 9, None, None, None, 'Added torrents will be started right away.'),
|
||||
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'The .torrent file of added torrents will be deleted.'),
|
||||
'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'),
|
||||
},
|
||||
}
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import logging
|
||||
from six import iteritems
|
||||
|
||||
LOGGER = logging.getLogger('transmissionrpc')
|
||||
LOGGER.setLevel(logging.ERROR)
|
||||
|
||||
def mirror_dict(source):
|
||||
"""
|
||||
Creates a dictionary with all values as keys and all keys as values.
|
||||
"""
|
||||
source.update(dict((value, key) for key, value in iteritems(source)))
|
||||
return source
|
||||
|
||||
DEFAULT_PORT = 9091
|
||||
|
||||
DEFAULT_TIMEOUT = 30.0
|
||||
|
||||
TR_PRI_LOW = -1
|
||||
TR_PRI_NORMAL = 0
|
||||
TR_PRI_HIGH = 1
|
||||
|
||||
PRIORITY = mirror_dict({
|
||||
'low' : TR_PRI_LOW,
|
||||
'normal' : TR_PRI_NORMAL,
|
||||
'high' : TR_PRI_HIGH
|
||||
})
|
||||
|
||||
TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings
|
||||
TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio
|
||||
TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio
|
||||
|
||||
RATIO_LIMIT = mirror_dict({
|
||||
'global' : TR_RATIOLIMIT_GLOBAL,
|
||||
'single' : TR_RATIOLIMIT_SINGLE,
|
||||
'unlimited' : TR_RATIOLIMIT_UNLIMITED
|
||||
})
|
||||
|
||||
TR_IDLELIMIT_GLOBAL = 0 # follow the global settings
|
||||
TR_IDLELIMIT_SINGLE = 1 # override the global settings, seeding until a certain idle time
|
||||
TR_IDLELIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of activity
|
||||
|
||||
IDLE_LIMIT = mirror_dict({
|
||||
'global' : TR_RATIOLIMIT_GLOBAL,
|
||||
'single' : TR_RATIOLIMIT_SINGLE,
|
||||
'unlimited' : TR_RATIOLIMIT_UNLIMITED
|
||||
})
|
||||
|
||||
# A note on argument maps
|
||||
# These maps are used to verify *-set methods. The information is structured in
|
||||
# a tree.
|
||||
# set +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
# | +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
# |
|
||||
# get +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
# +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||
|
||||
# Arguments for torrent methods
|
||||
TORRENT_ARGS = {
|
||||
'get' : {
|
||||
'activityDate': ('number', 1, None, None, None, 'Last time of upload or download activity.'),
|
||||
'addedDate': ('number', 1, None, None, None, 'The date when this torrent was first added.'),
|
||||
'announceResponse': ('string', 1, 7, None, None, 'The announce message from the tracker.'),
|
||||
'announceURL': ('string', 1, 7, None, None, 'Current announce URL.'),
|
||||
'bandwidthPriority': ('number', 5, None, None, None, 'Bandwidth priority. Low (-1), Normal (0) or High (1).'),
|
||||
'comment': ('string', 1, None, None, None, 'Torrent comment.'),
|
||||
'corruptEver': ('number', 1, None, None, None, 'Number of bytes of corrupt data downloaded.'),
|
||||
'creator': ('string', 1, None, None, None, 'Torrent creator.'),
|
||||
'dateCreated': ('number', 1, None, None, None, 'Torrent creation date.'),
|
||||
'desiredAvailable': ('number', 1, None, None, None, 'Number of bytes avalable and left to be downloaded.'),
|
||||
'doneDate': ('number', 1, None, None, None, 'The date when the torrent finished downloading.'),
|
||||
'downloadDir': ('string', 4, None, None, None, 'The directory path where the torrent is downloaded to.'),
|
||||
'downloadedEver': ('number', 1, None, None, None, 'Number of bytes of good data downloaded.'),
|
||||
'downloaders': ('number', 4, 7, None, None, 'Number of downloaders.'),
|
||||
'downloadLimit': ('number', 1, None, None, None, 'Download limit in Kbps.'),
|
||||
'downloadLimited': ('boolean', 5, None, None, None, 'Download limit is enabled'),
|
||||
'downloadLimitMode': ('number', 1, 5, None, None, 'Download limit mode. 0 means global, 1 means signle, 2 unlimited.'),
|
||||
'error': ('number', 1, None, None, None, 'Kind of error. 0 means OK, 1 means tracker warning, 2 means tracker error, 3 means local error.'),
|
||||
'errorString': ('number', 1, None, None, None, 'Error message.'),
|
||||
'eta': ('number', 1, None, None, None, 'Estimated number of seconds left when downloading or seeding. -1 means not available and -2 means unknown.'),
|
||||
'etaIdle': ('number', 15, None, None, None, 'Estimated number of seconds left until the idle time limit is reached. -1 means not available and -2 means unknown.'),
|
||||
'files': ('array', 1, None, None, None, 'Array of file object containing key, bytesCompleted, length and name.'),
|
||||
'fileStats': ('array', 5, None, None, None, 'Aray of file statistics containing bytesCompleted, wanted and priority.'),
|
||||
'hashString': ('string', 1, None, None, None, 'Hashstring unique for the torrent even between sessions.'),
|
||||
'haveUnchecked': ('number', 1, None, None, None, 'Number of bytes of partial pieces.'),
|
||||
'haveValid': ('number', 1, None, None, None, 'Number of bytes of checksum verified data.'),
|
||||
'honorsSessionLimits': ('boolean', 5, None, None, None, 'True if session upload limits are honored'),
|
||||
'id': ('number', 1, None, None, None, 'Session unique torrent id.'),
|
||||
'isFinished': ('boolean', 9, None, None, None, 'True if the torrent is finished. Downloaded and seeded.'),
|
||||
'isPrivate': ('boolean', 1, None, None, None, 'True if the torrent is private.'),
|
||||
'isStalled': ('boolean', 14, None, None, None, 'True if the torrent has stalled (been idle for a long time).'),
|
||||
'lastAnnounceTime': ('number', 1, 7, None, None, 'The time of the last announcement.'),
|
||||
'lastScrapeTime': ('number', 1, 7, None, None, 'The time af the last successful scrape.'),
|
||||
'leechers': ('number', 1, 7, None, None, 'Number of leechers.'),
|
||||
'leftUntilDone': ('number', 1, None, None, None, 'Number of bytes left until the download is done.'),
|
||||
'magnetLink': ('string', 7, None, None, None, 'The magnet link for this torrent.'),
|
||||
'manualAnnounceTime': ('number', 1, None, None, None, 'The time until you manually ask for more peers.'),
|
||||
'maxConnectedPeers': ('number', 1, None, None, None, 'Maximum of connected peers.'),
|
||||
'metadataPercentComplete': ('number', 7, None, None, None, 'Download progress of metadata. 0.0 to 1.0.'),
|
||||
'name': ('string', 1, None, None, None, 'Torrent name.'),
|
||||
'nextAnnounceTime': ('number', 1, 7, None, None, 'Next announce time.'),
|
||||
'nextScrapeTime': ('number', 1, 7, None, None, 'Next scrape time.'),
|
||||
'peer-limit': ('number', 5, None, None, None, 'Maximum number of peers.'),
|
||||
'peers': ('array', 2, None, None, None, 'Array of peer objects.'),
|
||||
'peersConnected': ('number', 1, None, None, None, 'Number of peers we are connected to.'),
|
||||
'peersFrom': ('object', 1, None, None, None, 'Object containing download peers counts for different peer types.'),
|
||||
'peersGettingFromUs': ('number', 1, None, None, None, 'Number of peers we are sending data to.'),
|
||||
'peersKnown': ('number', 1, 13, None, None, 'Number of peers that the tracker knows.'),
|
||||
'peersSendingToUs': ('number', 1, None, None, None, 'Number of peers sending to us'),
|
||||
'percentDone': ('double', 5, None, None, None, 'Download progress of selected files. 0.0 to 1.0.'),
|
||||
'pieces': ('string', 5, None, None, None, 'String with base64 encoded bitfield indicating finished pieces.'),
|
||||
'pieceCount': ('number', 1, None, None, None, 'Number of pieces.'),
|
||||
'pieceSize': ('number', 1, None, None, None, 'Number of bytes in a piece.'),
|
||||
'priorities': ('array', 1, None, None, None, 'Array of file priorities.'),
|
||||
'queuePosition': ('number', 14, None, None, None, 'The queue position.'),
|
||||
'rateDownload': ('number', 1, None, None, None, 'Download rate in bps.'),
|
||||
'rateUpload': ('number', 1, None, None, None, 'Upload rate in bps.'),
|
||||
'recheckProgress': ('double', 1, None, None, None, 'Progress of recheck. 0.0 to 1.0.'),
|
||||
'secondsDownloading': ('number', 15, None, None, None, ''),
|
||||
'secondsSeeding': ('number', 15, None, None, None, ''),
|
||||
'scrapeResponse': ('string', 1, 7, None, None, 'Scrape response message.'),
|
||||
'scrapeURL': ('string', 1, 7, None, None, 'Current scrape URL'),
|
||||
'seeders': ('number', 1, 7, None, None, 'Number of seeders reported by the tracker.'),
|
||||
'seedIdleLimit': ('number', 10, None, None, None, 'Idle limit in minutes.'),
|
||||
'seedIdleMode': ('number', 10, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
|
||||
'seedRatioLimit': ('double', 5, None, None, None, 'Seed ratio limit.'),
|
||||
'seedRatioMode': ('number', 5, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
|
||||
'sizeWhenDone': ('number', 1, None, None, None, 'Size of the torrent download in bytes.'),
|
||||
'startDate': ('number', 1, None, None, None, 'The date when the torrent was last started.'),
|
||||
'status': ('number', 1, None, None, None, 'Current status, see source'),
|
||||
'swarmSpeed': ('number', 1, 7, None, None, 'Estimated speed in Kbps in the swarm.'),
|
||||
'timesCompleted': ('number', 1, 7, None, None, 'Number of successful downloads reported by the tracker.'),
|
||||
'trackers': ('array', 1, None, None, None, 'Array of tracker objects.'),
|
||||
'trackerStats': ('object', 7, None, None, None, 'Array of object containing tracker statistics.'),
|
||||
'totalSize': ('number', 1, None, None, None, 'Total size of the torrent in bytes'),
|
||||
'torrentFile': ('string', 5, None, None, None, 'Path to .torrent file.'),
|
||||
'uploadedEver': ('number', 1, None, None, None, 'Number of bytes uploaded, ever.'),
|
||||
'uploadLimit': ('number', 1, None, None, None, 'Upload limit in Kbps'),
|
||||
'uploadLimitMode': ('number', 1, 5, None, None, 'Upload limit mode. 0 means global, 1 means signle, 2 unlimited.'),
|
||||
'uploadLimited': ('boolean', 5, None, None, None, 'Upload limit enabled.'),
|
||||
'uploadRatio': ('double', 1, None, None, None, 'Seed ratio.'),
|
||||
'wanted': ('array', 1, None, None, None, 'Array of booleans indicated wanted files.'),
|
||||
'webseeds': ('array', 1, None, None, None, 'Array of webseeds objects'),
|
||||
'webseedsSendingToUs': ('number', 1, None, None, None, 'Number of webseeds seeding to us.'),
|
||||
},
|
||||
'set': {
|
||||
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
|
||||
'downloadLimit': ('number', 5, None, 'speed-limit-down', None, 'Set the speed limit for download in Kib/s.'),
|
||||
'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None, 'Enable download speed limiter.'),
|
||||
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
|
||||
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
|
||||
'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."),
|
||||
'location': ('array', 1, None, None, None, 'Local download location.'),
|
||||
'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
|
||||
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
|
||||
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
|
||||
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
|
||||
'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'),
|
||||
'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
|
||||
'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
|
||||
'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
|
||||
'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
|
||||
'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
|
||||
'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
|
||||
'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
|
||||
'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited', 'Enable upload speed limiter.'),
|
||||
'trackerAdd': ('array', 10, None, None, None, 'Array of string with announce URLs to add.'),
|
||||
'trackerRemove': ('array', 10, None, None, None, 'Array of ids of trackers to remove.'),
|
||||
'trackerReplace': ('array', 10, None, None, None, 'Array of (id, url) tuples where the announce URL should be replaced.'),
|
||||
'uploadLimit': ('number', 5, None, 'speed-limit-up', None, 'Set the speed limit for upload in Kib/s.'),
|
||||
'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None, 'Enable upload speed limiter.'),
|
||||
},
|
||||
'add': {
|
||||
'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
|
||||
'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
|
||||
'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'),
|
||||
'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."),
|
||||
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
|
||||
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
|
||||
'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
|
||||
'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
|
||||
'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
|
||||
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
|
||||
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
|
||||
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
|
||||
}
|
||||
}
|
||||
|
||||
# Arguments for session methods
|
||||
SESSION_ARGS = {
|
||||
'get': {
|
||||
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
||||
"alt-speed-enabled": ('boolean', 5, None, None, None, 'True if alternate global download speed limiter is ebabled.'),
|
||||
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
|
||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'True if alternate speeds scheduling is enabled.'),
|
||||
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
|
||||
"alt-speed-time-day": ('number', 5, None, None, None, 'Days alternate speeds scheduling is enabled.'),
|
||||
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s)'),
|
||||
"blocklist-enabled": ('boolean', 5, None, None, None, 'True when blocklist is enabled.'),
|
||||
"blocklist-size": ('number', 5, None, None, None, 'Number of rules in the blocklist'),
|
||||
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
|
||||
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
|
||||
"config-dir": ('string', 8, None, None, None, 'location of transmissions configuration directory'),
|
||||
"dht-enabled": ('boolean', 6, None, None, None, 'True if DHT enabled.'),
|
||||
"download-dir": ('string', 1, None, None, None, 'The download directory.'),
|
||||
"download-dir-free-space": ('number', 12, None, None, None, 'Free space in the download directory, in bytes'),
|
||||
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
|
||||
"download-queue-enabled": ('boolean', 14, None, None, None, 'True if the download queue is enabled.'),
|
||||
"encryption": ('string', 1, None, None, None, 'Encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
|
||||
"idle-seeding-limit": ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
|
||||
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'True if the seed activity limit is enabled.'),
|
||||
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory for incomplete torrent transfer data.'),
|
||||
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'True if the incomplete dir is enabled.'),
|
||||
"lpd-enabled": ('boolean', 9, None, None, None, 'True if local peer discovery is enabled.'),
|
||||
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
|
||||
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
|
||||
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
|
||||
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'True if PEX is allowed.'),
|
||||
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'True if PEX is enabled.'),
|
||||
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
||||
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
|
||||
"peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
|
||||
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'True if port forwarding is enabled.'),
|
||||
"queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
|
||||
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'True if stalled tracking of transfers is enabled.'),
|
||||
"rename-partial-files": ('boolean', 8, None, None, None, 'True if ".part" is appended to incomplete files'),
|
||||
"rpc-version": ('number', 4, None, None, None, 'Transmission RPC API Version.'),
|
||||
"rpc-version-minimum": ('number', 4, None, None, None, 'Minimum accepted RPC API Version.'),
|
||||
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'True if the done script is enabled.'),
|
||||
"script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
|
||||
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
|
||||
"seedRatioLimited": ('boolean', 5, None, None, None, 'True if seed ration limit is enabled.'),
|
||||
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
|
||||
"seed-queue-enabled": ('boolean', 14, None, None, None, 'True if upload queue is enabled.'),
|
||||
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
|
||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'True if the download speed is limited.'),
|
||||
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
|
||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'True if the upload speed is limited.'),
|
||||
"start-added-torrents": ('boolean', 9, None, None, None, 'When true uploaded torrents will start right away.'),
|
||||
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'When true added .torrent files will be deleted.'),
|
||||
'units': ('object', 10, None, None, None, 'An object containing units for size and speed.'),
|
||||
'utp-enabled': ('boolean', 13, None, None, None, 'True if Micro Transport Protocol (UTP) is enabled.'),
|
||||
"version": ('string', 3, None, None, None, 'Transmission version.'),
|
||||
},
|
||||
'set': {
|
||||
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
||||
"alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
|
||||
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
|
||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'Enables alternate speeds scheduling.'),
|
||||
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
|
||||
"alt-speed-time-day": ('number', 5, None, None, None, 'Enables alternate speeds scheduling these days.'),
|
||||
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
|
||||
"blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
|
||||
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
|
||||
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
|
||||
"dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
|
||||
"download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
|
||||
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
|
||||
"download-queue-enabled": ('boolean', 14, None, None, None, 'Enables download queue.'),
|
||||
"encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
|
||||
"idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
|
||||
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
|
||||
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
|
||||
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.'),
|
||||
"lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
|
||||
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
|
||||
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
|
||||
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
|
||||
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
|
||||
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
|
||||
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
||||
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
|
||||
"peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
|
||||
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'Enables port forwarding.'),
|
||||
"rename-partial-files": ('boolean', 8, None, None, None, 'Appends ".part" to incomplete files'),
|
||||
"queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
|
||||
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
|
||||
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'Whether or not to call the "done" script.'),
|
||||
"script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
|
||||
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
|
||||
"seed-queue-enabled": ('boolean', 14, None, None, None, 'Enables upload queue.'),
|
||||
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
|
||||
"seedRatioLimited": ('boolean', 5, None, None, None, 'Enables seed ration limit.'),
|
||||
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
|
||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'Enables download speed limiting.'),
|
||||
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
|
||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'Enables upload speed limiting.'),
|
||||
"start-added-torrents": ('boolean', 9, None, None, None, 'Added torrents will be started right away.'),
|
||||
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'The .torrent file of added torrents will be deleted.'),
|
||||
'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,52 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
class TransmissionError(Exception):
|
||||
"""
|
||||
This exception is raised when there has occurred an error related to
|
||||
communication with Transmission. It is a subclass of Exception.
|
||||
"""
|
||||
def __init__(self, message='', original=None):
|
||||
Exception.__init__(self)
|
||||
self._message = message
|
||||
self.original = original
|
||||
|
||||
def __str__(self):
|
||||
if self.original:
|
||||
original_name = type(self.original).__name__
|
||||
return '%s Original exception: %s, "%s"' % (self.message, original_name, str(self.original))
|
||||
else:
|
||||
return self.message
|
||||
|
||||
class HTTPHandlerError(Exception):
|
||||
"""
|
||||
This exception is raised when there has occurred an error related to
|
||||
the HTTP handler. It is a subclass of Exception.
|
||||
"""
|
||||
def __init__(self, httpurl=None, httpcode=None, httpmsg=None, httpheaders=None, httpdata=None):
|
||||
Exception.__init__(self)
|
||||
self.url = ''
|
||||
self.code = 600
|
||||
self._message = ''
|
||||
self.headers = {}
|
||||
self.data = ''
|
||||
if isinstance(httpurl, (str, unicode)):
|
||||
self.url = httpurl
|
||||
if isinstance(httpcode, (int, long)):
|
||||
self.code = httpcode
|
||||
if isinstance(httpmsg, (str, unicode)):
|
||||
self._message = httpmsg
|
||||
if isinstance(httpheaders, dict):
|
||||
self.headers = httpheaders
|
||||
if isinstance(httpdata, (str, unicode)):
|
||||
self.data = httpdata
|
||||
|
||||
def __repr__(self):
|
||||
return '<HTTPHandlerError %d, %s>' % (self.code, self._message)
|
||||
|
||||
def __str__(self):
|
||||
return 'HTTPHandlerError %d: %s' % (self.code, self._message)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'HTTPHandlerError %d: %s' % (self.code, self._message)
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
from six import string_types, integer_types
|
||||
|
||||
class TransmissionError(Exception):
|
||||
"""
|
||||
This exception is raised when there has occurred an error related to
|
||||
communication with Transmission. It is a subclass of Exception.
|
||||
"""
|
||||
def __init__(self, message='', original=None):
|
||||
Exception.__init__(self)
|
||||
self.message = message
|
||||
self.original = original
|
||||
|
||||
def __str__(self):
|
||||
if self.original:
|
||||
original_name = type(self.original).__name__
|
||||
return '%s Original exception: %s, "%s"' % (self.message, original_name, str(self.original))
|
||||
else:
|
||||
return self.message
|
||||
|
||||
class HTTPHandlerError(Exception):
|
||||
"""
|
||||
This exception is raised when there has occurred an error related to
|
||||
the HTTP handler. It is a subclass of Exception.
|
||||
"""
|
||||
def __init__(self, httpurl=None, httpcode=None, httpmsg=None, httpheaders=None, httpdata=None):
|
||||
Exception.__init__(self)
|
||||
self.url = ''
|
||||
self.code = 600
|
||||
self.message = ''
|
||||
self.headers = {}
|
||||
self.data = ''
|
||||
if isinstance(httpurl, string_types):
|
||||
self.url = httpurl
|
||||
if isinstance(httpcode, integer_types):
|
||||
self.code = httpcode
|
||||
if isinstance(httpmsg, string_types):
|
||||
self.message = httpmsg
|
||||
if isinstance(httpheaders, dict):
|
||||
self.headers = httpheaders
|
||||
if isinstance(httpdata, string_types):
|
||||
self.data = httpdata
|
||||
|
||||
def __repr__(self):
|
||||
return '<HTTPHandlerError %d, %s>' % (self.code, self.message)
|
||||
|
||||
def __str__(self):
|
||||
return 'HTTPHandlerError %d: %s' % (self.code, self.message)
|
||||
|
||||
def __unicode__(self):
|
||||
return 'HTTPHandlerError %d: %s' % (self.code, self.message)
|
||||
|
|
|
@ -1,72 +1,82 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import sys, httplib, urllib2
|
||||
|
||||
from transmissionrpc.error import HTTPHandlerError
|
||||
|
||||
class HTTPHandler(object):
|
||||
"""
|
||||
Prototype for HTTP handling.
|
||||
"""
|
||||
def set_authentication(self, uri, login, password):
|
||||
"""
|
||||
Transmission use basic authentication in earlier versions and digest
|
||||
authentication in later versions.
|
||||
|
||||
* uri, the authentication realm URI.
|
||||
* login, the authentication login.
|
||||
* password, the authentication password.
|
||||
"""
|
||||
raise NotImplementedError("Bad HTTPHandler, failed to implement set_authentication.")
|
||||
|
||||
def request(self, url, query, headers, timeout):
|
||||
"""
|
||||
Implement a HTTP POST request here.
|
||||
|
||||
* url, The URL to request.
|
||||
* query, The query data to send. This is a JSON data string.
|
||||
* headers, a dictionary of headers to send.
|
||||
* timeout, requested request timeout in seconds.
|
||||
"""
|
||||
raise NotImplementedError("Bad HTTPHandler, failed to implement request.")
|
||||
|
||||
class DefaultHTTPHandler(HTTPHandler):
|
||||
"""
|
||||
The default HTTP handler provided with transmissionrpc.
|
||||
"""
|
||||
def __init__(self):
|
||||
HTTPHandler.__init__(self)
|
||||
|
||||
def set_authentication(self, uri, login, password):
|
||||
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||
password_manager.add_password(realm=None, uri=uri, user=login, passwd=password)
|
||||
opener = urllib2.build_opener(
|
||||
urllib2.HTTPBasicAuthHandler(password_manager)
|
||||
, urllib2.HTTPDigestAuthHandler(password_manager)
|
||||
)
|
||||
urllib2.install_opener(opener)
|
||||
|
||||
def request(self, url, query, headers, timeout):
|
||||
request = urllib2.Request(url, query, headers)
|
||||
try:
|
||||
if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
|
||||
response = urllib2.urlopen(request, timeout=timeout)
|
||||
else:
|
||||
response = urllib2.urlopen(request)
|
||||
except urllib2.HTTPError, error:
|
||||
if error.fp is None:
|
||||
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs))
|
||||
else:
|
||||
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs), error.read())
|
||||
except urllib2.URLError, error:
|
||||
# urllib2.URLError documentation is horrendous!
|
||||
# Try to get the tuple arguments of URLError
|
||||
if hasattr(error.reason, 'args') and isinstance(error.reason.args, tuple) and len(error.reason.args) == 2:
|
||||
raise HTTPHandlerError(httpcode=error.reason.args[0], httpmsg=error.reason.args[1])
|
||||
else:
|
||||
raise HTTPHandlerError(httpmsg='urllib2.URLError: %s' % (error.reason))
|
||||
except httplib.BadStatusLine, error:
|
||||
raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line))
|
||||
return response.read()
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2011-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import sys
|
||||
|
||||
from transmissionrpc.error import HTTPHandlerError
|
||||
|
||||
from six import PY3
|
||||
|
||||
if PY3:
|
||||
from urllib.request import Request, build_opener, \
|
||||
HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, HTTPDigestAuthHandler
|
||||
from urllib.error import HTTPError, URLError
|
||||
from http.client import BadStatusLine
|
||||
else:
|
||||
from urllib2 import Request, build_opener, \
|
||||
HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, HTTPDigestAuthHandler
|
||||
from urllib2 import HTTPError, URLError
|
||||
from httplib import BadStatusLine
|
||||
|
||||
class HTTPHandler(object):
|
||||
"""
|
||||
Prototype for HTTP handling.
|
||||
"""
|
||||
def set_authentication(self, uri, login, password):
|
||||
"""
|
||||
Transmission use basic authentication in earlier versions and digest
|
||||
authentication in later versions.
|
||||
|
||||
* uri, the authentication realm URI.
|
||||
* login, the authentication login.
|
||||
* password, the authentication password.
|
||||
"""
|
||||
raise NotImplementedError("Bad HTTPHandler, failed to implement set_authentication.")
|
||||
|
||||
def request(self, url, query, headers, timeout):
|
||||
"""
|
||||
Implement a HTTP POST request here.
|
||||
|
||||
* url, The URL to request.
|
||||
* query, The query data to send. This is a JSON data string.
|
||||
* headers, a dictionary of headers to send.
|
||||
* timeout, requested request timeout in seconds.
|
||||
"""
|
||||
raise NotImplementedError("Bad HTTPHandler, failed to implement request.")
|
||||
|
||||
class DefaultHTTPHandler(HTTPHandler):
|
||||
"""
|
||||
The default HTTP handler provided with transmissionrpc.
|
||||
"""
|
||||
def __init__(self):
|
||||
HTTPHandler.__init__(self)
|
||||
self.http_opener = build_opener()
|
||||
|
||||
def set_authentication(self, uri, login, password):
|
||||
password_manager = HTTPPasswordMgrWithDefaultRealm()
|
||||
password_manager.add_password(realm=None, uri=uri, user=login, passwd=password)
|
||||
self.http_opener = build_opener(HTTPBasicAuthHandler(password_manager), HTTPDigestAuthHandler(password_manager))
|
||||
|
||||
def request(self, url, query, headers, timeout):
|
||||
request = Request(url, query.encode('utf-8'), headers)
|
||||
try:
|
||||
if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
|
||||
response = self.http_opener.open(request, timeout=timeout)
|
||||
else:
|
||||
response = self.http_opener.open(request)
|
||||
except HTTPError as error:
|
||||
if error.fp is None:
|
||||
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs))
|
||||
else:
|
||||
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs), error.read())
|
||||
except URLError as error:
|
||||
# urllib2.URLError documentation is horrendous!
|
||||
# Try to get the tuple arguments of URLError
|
||||
if hasattr(error.reason, 'args') and isinstance(error.reason.args, tuple) and len(error.reason.args) == 2:
|
||||
raise HTTPHandlerError(httpcode=error.reason.args[0], httpmsg=error.reason.args[1])
|
||||
else:
|
||||
raise HTTPHandlerError(httpmsg='urllib2.URLError: %s' % (error.reason))
|
||||
except BadStatusLine as error:
|
||||
raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line))
|
||||
return response.read().decode('utf-8')
|
||||
|
|
|
@ -1,44 +1,111 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
class Session(object):
|
||||
"""
|
||||
Session is a class holding the session data for a Transmission daemon.
|
||||
|
||||
Access the session field can be done through attributes.
|
||||
The attributes available are the same as the session arguments in the
|
||||
Transmission RPC specification, but with underscore instead of hyphen.
|
||||
``download-dir`` -> ``download_dir``.
|
||||
"""
|
||||
|
||||
def __init__(self, fields=None):
|
||||
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
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.fields[name]
|
||||
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])
|
||||
return text
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
from transmissionrpc.utils import Field
|
||||
|
||||
from six import iteritems, integer_types
|
||||
|
||||
class Session(object):
|
||||
"""
|
||||
Session is a class holding the session data for a Transmission daemon.
|
||||
|
||||
Access the session field can be done through attributes.
|
||||
The attributes available are the same as the session arguments in the
|
||||
Transmission RPC specification, but with underscore instead of hyphen.
|
||||
``download-dir`` -> ``download_dir``.
|
||||
"""
|
||||
|
||||
def __init__(self, client=None, fields=None):
|
||||
self._client = client
|
||||
self._fields = {}
|
||||
if fields is not None:
|
||||
self._update_fields(fields)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self._fields[name].value
|
||||
except KeyError:
|
||||
raise AttributeError('No attribute %s' % name)
|
||||
|
||||
def __str__(self):
|
||||
text = ''
|
||||
for key in sorted(self._fields.keys()):
|
||||
text += "% 32s: %s\n" % (key[-32:], self._fields[key].value)
|
||||
return text
|
||||
|
||||
def _update_fields(self, other):
|
||||
"""
|
||||
Update the session data from a Transmission JSON-RPC arguments dictionary
|
||||
"""
|
||||
if isinstance(other, dict):
|
||||
for key, value in iteritems(other):
|
||||
self._fields[key.replace('-', '_')] = Field(value, False)
|
||||
elif isinstance(other, Session):
|
||||
for key in list(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, integer_types):
|
||||
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):
|
||||
"""Is peer exchange enabled?"""
|
||||
return self._fields['pex_enabled'].value
|
||||
|
||||
def _set_pex_enabled(self, enabled):
|
||||
"""Enable/disable peer exchange."""
|
||||
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 peer exchange. This is a mutator.")
|
||||
|
|
701
resources/lib/transmissionrpc/torrent.py
Normal file → Executable file
701
resources/lib/transmissionrpc/torrent.py
Normal file → Executable file
|
@ -1,222 +1,479 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import sys, datetime
|
||||
|
||||
from transmissionrpc.constants import PRIORITY
|
||||
from transmissionrpc.utils import format_timedelta
|
||||
|
||||
class Torrent(object):
|
||||
"""
|
||||
Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
|
||||
All fetched torrent fields are accessible through this class using attributes.
|
||||
This class has a few convenience properties using the torrent data.
|
||||
"""
|
||||
|
||||
def __init__(self, client, fields):
|
||||
if 'id' not in fields:
|
||||
raise ValueError('Torrent requires an id')
|
||||
self.fields = {}
|
||||
self.update(fields)
|
||||
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 is unicode, try to decode
|
||||
if isinstance(name, unicode):
|
||||
try:
|
||||
name = name.encode(codec)
|
||||
except UnicodeError:
|
||||
name = None
|
||||
return name
|
||||
|
||||
def __repr__(self):
|
||||
tid = self.fields['id']
|
||||
name = self._getNameString()
|
||||
if isinstance(name, str):
|
||||
return '<Torrent %d \"%s\">' % (tid, name)
|
||||
else:
|
||||
return '<Torrent %d>' % (tid)
|
||||
|
||||
def __str__(self):
|
||||
name = self._getNameString()
|
||||
if isinstance(name, str):
|
||||
return 'Torrent \"%s\"' % (name)
|
||||
else:
|
||||
return 'Torrent'
|
||||
|
||||
def __copy__(self):
|
||||
return Torrent(self.client, self.fields)
|
||||
|
||||
def _rpc_version(self):
|
||||
if self.client:
|
||||
return self.client.rpc_version
|
||||
return 2
|
||||
|
||||
def _status_old(self, code):
|
||||
mapping = {
|
||||
(1<<0): 'check pending',
|
||||
(1<<1): 'checking',
|
||||
(1<<2): 'downloading',
|
||||
(1<<3): 'seeding',
|
||||
(1<<4): 'stopped',
|
||||
}
|
||||
return mapping[code]
|
||||
|
||||
def _status_new(self, code):
|
||||
mapping = {
|
||||
0: 'stopped',
|
||||
1: 'check pending',
|
||||
2: 'checking',
|
||||
3: 'download pending',
|
||||
4: 'downloading',
|
||||
5: 'seed pending',
|
||||
6: 'seeding',
|
||||
}
|
||||
return mapping[code]
|
||||
|
||||
def _status(self):
|
||||
code = self.fields['status']
|
||||
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.
|
||||
|
||||
This function returns a dictionary with file information for each file.
|
||||
The file information is has following fields:
|
||||
::
|
||||
|
||||
{
|
||||
<file id>: {
|
||||
'name': <file name>,
|
||||
'size': <file size in bytes>,
|
||||
'completed': <bytes completed>,
|
||||
'priority': <priority ('high'|'normal'|'low')>,
|
||||
'selected': <selected for download>
|
||||
}
|
||||
...
|
||||
}
|
||||
"""
|
||||
result = {}
|
||||
if 'files' in self.fields:
|
||||
indices = xrange(len(self.fields['files']))
|
||||
files = self.fields['files']
|
||||
priorities = self.fields['priorities']
|
||||
wanted = self.fields['wanted']
|
||||
for item in zip(indices, files, priorities, wanted):
|
||||
selected = False
|
||||
if item[3]:
|
||||
selected = True
|
||||
priority = PRIORITY[item[2]]
|
||||
result[item[0]] = {
|
||||
'selected': selected,
|
||||
'priority': priority,
|
||||
'size': item[1]['length'],
|
||||
'name': item[1]['name'],
|
||||
'completed': item[1]['bytesCompleted']}
|
||||
return result
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.fields[name]
|
||||
except KeyError:
|
||||
raise AttributeError('No attribute %s' % name)
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
Returns the torrent status. Is either one of 'check pending', 'checking',
|
||||
'downloading', 'seeding' or 'stopped'. The first two is related to
|
||||
verification.
|
||||
"""
|
||||
return self._status()
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
"""Get the download progress in percent."""
|
||||
try:
|
||||
return 100.0 * (self.fields['sizeWhenDone'] - self.fields['leftUntilDone']) / float(self.fields['sizeWhenDone'])
|
||||
except ZeroDivisionError:
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def ratio(self):
|
||||
"""Get the upload/download ratio."""
|
||||
return float(self.fields['uploadRatio'])
|
||||
|
||||
@property
|
||||
def eta(self):
|
||||
"""Get the "eta" as datetime.timedelta."""
|
||||
eta = self.fields['eta']
|
||||
if eta >= 0:
|
||||
return datetime.timedelta(seconds=eta)
|
||||
else:
|
||||
ValueError('eta not valid')
|
||||
|
||||
@property
|
||||
def date_active(self):
|
||||
"""Get the attribute "activityDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self.fields['activityDate'])
|
||||
|
||||
@property
|
||||
def date_added(self):
|
||||
"""Get the attribute "addedDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self.fields['addedDate'])
|
||||
|
||||
@property
|
||||
def date_started(self):
|
||||
"""Get the attribute "startDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self.fields['startDate'])
|
||||
|
||||
@property
|
||||
def date_done(self):
|
||||
"""Get the attribute "doneDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self.fields['doneDate'])
|
||||
|
||||
def format_eta(self):
|
||||
"""
|
||||
Returns the attribute *eta* formatted as a string.
|
||||
|
||||
* If eta is -1 the result is 'not available'
|
||||
* If eta is -2 the result is 'unknown'
|
||||
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
|
||||
"""
|
||||
eta = self.fields['eta']
|
||||
if eta == -1:
|
||||
return 'not available'
|
||||
elif eta == -2:
|
||||
return 'unknown'
|
||||
else:
|
||||
return format_timedelta(self.eta)
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
"""
|
||||
Get the priority as string.
|
||||
Can be one of 'low', 'normal', 'high'.
|
||||
"""
|
||||
return PRIORITY[self.fields['bandwidthPriority']]
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import sys, datetime
|
||||
|
||||
from transmissionrpc.constants import PRIORITY, RATIO_LIMIT, IDLE_LIMIT
|
||||
from transmissionrpc.utils import Field, format_timedelta
|
||||
|
||||
from six import integer_types, string_types, text_type, iteritems
|
||||
|
||||
|
||||
def get_status_old(code):
|
||||
"""Get the torrent status using old status codes"""
|
||||
mapping = {
|
||||
(1<<0): 'check pending',
|
||||
(1<<1): 'checking',
|
||||
(1<<2): 'downloading',
|
||||
(1<<3): 'seeding',
|
||||
(1<<4): 'stopped',
|
||||
}
|
||||
return mapping[code]
|
||||
|
||||
def get_status_new(code):
|
||||
"""Get the torrent status using new status codes"""
|
||||
mapping = {
|
||||
0: 'stopped',
|
||||
1: 'check pending',
|
||||
2: 'checking',
|
||||
3: 'download pending',
|
||||
4: 'downloading',
|
||||
5: 'seed pending',
|
||||
6: 'seeding',
|
||||
}
|
||||
return mapping[code]
|
||||
|
||||
class Torrent(object):
|
||||
"""
|
||||
Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
|
||||
|
||||
All fetched torrent fields are accessible through this class using attributes.
|
||||
This class has a few convenience properties using the torrent data.
|
||||
"""
|
||||
|
||||
def __init__(self, client, fields):
|
||||
if 'id' not in fields:
|
||||
raise ValueError('Torrent requires an id')
|
||||
self._fields = {}
|
||||
self._update_fields(fields)
|
||||
self._incoming_pending = False
|
||||
self._outgoing_pending = False
|
||||
self._client = client
|
||||
|
||||
def _get_name_string(self, codec=None):
|
||||
"""Get the name"""
|
||||
if codec is None:
|
||||
codec = sys.getdefaultencoding()
|
||||
name = None
|
||||
# try to find name
|
||||
if 'name' in self._fields:
|
||||
name = self._fields['name'].value
|
||||
# if name is unicode, try to decode
|
||||
if isinstance(name, text_type):
|
||||
try:
|
||||
name = name.encode(codec)
|
||||
except UnicodeError:
|
||||
name = None
|
||||
return name
|
||||
|
||||
def __repr__(self):
|
||||
tid = self._fields['id'].value
|
||||
name = self._get_name_string()
|
||||
if isinstance(name, str):
|
||||
return '<Torrent %d \"%s\">' % (tid, name)
|
||||
else:
|
||||
return '<Torrent %d>' % (tid)
|
||||
|
||||
def __str__(self):
|
||||
name = self._get_name_string()
|
||||
if isinstance(name, str):
|
||||
return 'Torrent \"%s\"' % (name)
|
||||
else:
|
||||
return 'Torrent'
|
||||
|
||||
def __copy__(self):
|
||||
return Torrent(self._client, self._fields)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self._fields[name].value
|
||||
except KeyError:
|
||||
raise AttributeError('No attribute %s' % name)
|
||||
|
||||
def _rpc_version(self):
|
||||
"""Get the Transmission RPC API 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 iteritems(other):
|
||||
self._fields[key.replace('-', '_')] = Field(value, False)
|
||||
elif isinstance(other, Torrent):
|
||||
for key in list(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(self):
|
||||
"""Get the torrent status"""
|
||||
code = self._fields['status'].value
|
||||
if self._rpc_version() >= 14:
|
||||
return get_status_new(code)
|
||||
else:
|
||||
return get_status_old(code)
|
||||
|
||||
def files(self):
|
||||
"""
|
||||
Get list of files for this torrent.
|
||||
|
||||
This function returns a dictionary with file information for each file.
|
||||
The file information is has following fields:
|
||||
::
|
||||
|
||||
{
|
||||
<file id>: {
|
||||
'name': <file name>,
|
||||
'size': <file size in bytes>,
|
||||
'completed': <bytes completed>,
|
||||
'priority': <priority ('high'|'normal'|'low')>,
|
||||
'selected': <selected for download>
|
||||
}
|
||||
...
|
||||
}
|
||||
"""
|
||||
result = {}
|
||||
if 'files' in self._fields:
|
||||
files = self._fields['files'].value
|
||||
indices = range(len(files))
|
||||
priorities = self._fields['priorities'].value
|
||||
wanted = self._fields['wanted'].value
|
||||
for item in zip(indices, files, priorities, wanted):
|
||||
selected = True if item[3] else False
|
||||
priority = PRIORITY[item[2]]
|
||||
result[item[0]] = {
|
||||
'selected': selected,
|
||||
'priority': priority,
|
||||
'size': item[1]['length'],
|
||||
'name': item[1]['name'],
|
||||
'completed': item[1]['bytesCompleted']}
|
||||
return result
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
Returns the torrent status. Is either one of 'check pending', 'checking',
|
||||
'downloading', 'seeding' or 'stopped'. The first two is related to
|
||||
verification.
|
||||
"""
|
||||
return self._status()
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
"""Get the download progress in percent."""
|
||||
try:
|
||||
size = self._fields['sizeWhenDone'].value
|
||||
left = self._fields['leftUntilDone'].value
|
||||
return 100.0 * (size - left) / float(size)
|
||||
except ZeroDivisionError:
|
||||
return 0.0
|
||||
|
||||
@property
|
||||
def ratio(self):
|
||||
"""Get the upload/download ratio."""
|
||||
return float(self._fields['uploadRatio'].value)
|
||||
|
||||
@property
|
||||
def eta(self):
|
||||
"""Get the "eta" as datetime.timedelta."""
|
||||
eta = self._fields['eta'].value
|
||||
if eta >= 0:
|
||||
return datetime.timedelta(seconds=eta)
|
||||
else:
|
||||
raise ValueError('eta not valid')
|
||||
|
||||
@property
|
||||
def date_active(self):
|
||||
"""Get the attribute "activityDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self._fields['activityDate'].value)
|
||||
|
||||
@property
|
||||
def date_added(self):
|
||||
"""Get the attribute "addedDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self._fields['addedDate'].value)
|
||||
|
||||
@property
|
||||
def date_started(self):
|
||||
"""Get the attribute "startDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self._fields['startDate'].value)
|
||||
|
||||
@property
|
||||
def date_done(self):
|
||||
"""Get the attribute "doneDate" as datetime.datetime."""
|
||||
return datetime.datetime.fromtimestamp(self._fields['doneDate'].value)
|
||||
|
||||
def format_eta(self):
|
||||
"""
|
||||
Returns the attribute *eta* formatted as a string.
|
||||
|
||||
* If eta is -1 the result is 'not available'
|
||||
* If eta is -2 the result is 'unknown'
|
||||
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
|
||||
"""
|
||||
eta = self._fields['eta'].value
|
||||
if eta == -1:
|
||||
return 'not available'
|
||||
elif eta == -2:
|
||||
return 'unknown'
|
||||
else:
|
||||
return format_timedelta(self.eta)
|
||||
|
||||
def _get_download_limit(self):
|
||||
"""
|
||||
Get the download limit.
|
||||
Can be a number or None.
|
||||
"""
|
||||
if self._fields['downloadLimited'].value:
|
||||
return self._fields['downloadLimit'].value
|
||||
else:
|
||||
return None
|
||||
|
||||
def _set_download_limit(self, limit):
|
||||
"""
|
||||
Get the download limit.
|
||||
Can be a number, 'session' or None.
|
||||
"""
|
||||
if isinstance(limit, integer_types):
|
||||
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, integer_types):
|
||||
self._fields['peer_limit'] = Field(limit, True)
|
||||
self._push()
|
||||
else:
|
||||
raise ValueError("Not a valid limit")
|
||||
|
||||
peer_limit = property(_get_peer_limit, _set_peer_limit, None, "Peer limit. This is a mutator.")
|
||||
|
||||
def _get_priority(self):
|
||||
"""
|
||||
Get the priority as string.
|
||||
Can be one of 'low', 'normal', 'high'.
|
||||
"""
|
||||
return PRIORITY[self._fields['bandwidthPriority'].value]
|
||||
|
||||
def _set_priority(self, priority):
|
||||
"""
|
||||
Set the priority as string.
|
||||
Can be one of 'low', 'normal', 'high'.
|
||||
"""
|
||||
if isinstance(priority, string_types):
|
||||
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, integer_types):
|
||||
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, (integer_types, 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, integer_types):
|
||||
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):
|
||||
"""Get the queue position for this torrent."""
|
||||
if self._rpc_version() >= 14:
|
||||
return self._fields['queuePosition'].value
|
||||
else:
|
||||
return 0
|
||||
|
||||
def _set_queue_position(self, position):
|
||||
"""Set the queue position for this torrent."""
|
||||
if self._rpc_version() >= 14:
|
||||
if isinstance(position, integer_types):
|
||||
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)
|
||||
|
|
|
@ -1,191 +1,207 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import socket, datetime, logging
|
||||
import transmissionrpc.constants as constants
|
||||
from transmissionrpc.constants import LOGGER
|
||||
|
||||
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
|
||||
|
||||
def format_size(size):
|
||||
"""
|
||||
Format byte size into IEC prefixes, B, KiB, MiB ...
|
||||
"""
|
||||
size = float(size)
|
||||
i = 0
|
||||
while size >= 1024.0 and i < len(UNITS):
|
||||
i += 1
|
||||
size /= 1024.0
|
||||
return (size, UNITS[i])
|
||||
|
||||
def format_speed(size):
|
||||
"""
|
||||
Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ...
|
||||
"""
|
||||
(size, unit) = format_size(size)
|
||||
return (size, unit + '/s')
|
||||
|
||||
def format_timedelta(delta):
|
||||
"""
|
||||
Format datetime.timedelta into <days> <hours>:<minutes>:<seconds>.
|
||||
"""
|
||||
minutes, seconds = divmod(delta.seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
|
||||
|
||||
def format_timestamp(timestamp, utc=False):
|
||||
"""
|
||||
Format unix timestamp into ISO date format.
|
||||
"""
|
||||
if timestamp > 0:
|
||||
if utc:
|
||||
dt_timestamp = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
else:
|
||||
dt_timestamp = datetime.datetime.fromtimestamp(timestamp)
|
||||
return dt_timestamp.isoformat(' ')
|
||||
else:
|
||||
return '-'
|
||||
|
||||
class INetAddressError(Exception):
|
||||
"""
|
||||
Error parsing / generating a internet address.
|
||||
"""
|
||||
pass
|
||||
|
||||
def inet_address(address, default_port, default_address='localhost'):
|
||||
"""
|
||||
Parse internet address.
|
||||
"""
|
||||
addr = address.split(':')
|
||||
if len(addr) == 1:
|
||||
try:
|
||||
port = int(addr[0])
|
||||
addr = default_address
|
||||
except ValueError:
|
||||
addr = addr[0]
|
||||
port = default_port
|
||||
elif len(addr) == 2:
|
||||
try:
|
||||
port = int(addr[1])
|
||||
except ValueError:
|
||||
raise INetAddressError('Invalid address "%s".' % address)
|
||||
if len(addr[0]) == 0:
|
||||
addr = default_address
|
||||
else:
|
||||
addr = addr[0]
|
||||
else:
|
||||
raise INetAddressError('Invalid address "%s".' % address)
|
||||
try:
|
||||
socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM)
|
||||
except socket.gaierror:
|
||||
raise INetAddressError('Cannot look up address "%s".' % address)
|
||||
return (addr, port)
|
||||
|
||||
def rpc_bool(arg):
|
||||
"""
|
||||
Convert between Python boolean and Transmission RPC boolean.
|
||||
"""
|
||||
if isinstance(arg, (str, unicode)):
|
||||
try:
|
||||
arg = bool(int(arg))
|
||||
except ValueError:
|
||||
arg = arg.lower() in [u'true', u'yes']
|
||||
if bool(arg):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
TR_TYPE_MAP = {
|
||||
'number' : int,
|
||||
'string' : str,
|
||||
'double': float,
|
||||
'boolean' : rpc_bool,
|
||||
'array': list,
|
||||
'object': dict
|
||||
}
|
||||
|
||||
def make_python_name(name):
|
||||
"""
|
||||
Convert Transmission RPC name to python compatible name.
|
||||
"""
|
||||
return name.replace('-', '_')
|
||||
|
||||
def make_rpc_name(name):
|
||||
"""
|
||||
Convert python compatible name to Transmission RPC name.
|
||||
"""
|
||||
return name.replace('_', '-')
|
||||
|
||||
def argument_value_convert(method, argument, value, rpc_version):
|
||||
"""
|
||||
Check and fix Transmission RPC issues with regards to methods, arguments and values.
|
||||
"""
|
||||
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
||||
args = constants.TORRENT_ARGS[method[-3:]]
|
||||
elif method in ('session-get', 'session-set'):
|
||||
args = constants.SESSION_ARGS[method[-3:]]
|
||||
else:
|
||||
return ValueError('Method "%s" not supported' % (method))
|
||||
if argument in args:
|
||||
info = args[argument]
|
||||
invalid_version = True
|
||||
while invalid_version:
|
||||
invalid_version = False
|
||||
replacement = None
|
||||
if rpc_version < info[1]:
|
||||
invalid_version = True
|
||||
replacement = info[3]
|
||||
if info[2] and info[2] <= rpc_version:
|
||||
invalid_version = True
|
||||
replacement = info[4]
|
||||
if invalid_version:
|
||||
if replacement:
|
||||
LOGGER.warning(
|
||||
'Replacing requested argument "%s" with "%s".'
|
||||
% (argument, replacement))
|
||||
argument = replacement
|
||||
info = args[argument]
|
||||
else:
|
||||
raise ValueError(
|
||||
'Method "%s" Argument "%s" does not exist in version %d.'
|
||||
% (method, argument, rpc_version))
|
||||
return (argument, TR_TYPE_MAP[info[0]](value))
|
||||
else:
|
||||
raise ValueError('Argument "%s" does not exists for method "%s".',
|
||||
(argument, method))
|
||||
|
||||
def get_arguments(method, rpc_version):
|
||||
"""
|
||||
Get arguments for method in specified Transmission RPC version.
|
||||
"""
|
||||
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
||||
args = constants.TORRENT_ARGS[method[-3:]]
|
||||
elif method in ('session-get', 'session-set'):
|
||||
args = constants.SESSION_ARGS[method[-3:]]
|
||||
else:
|
||||
return ValueError('Method "%s" not supported' % (method))
|
||||
accessible = []
|
||||
for argument, info in args.iteritems():
|
||||
valid_version = True
|
||||
if rpc_version < info[1]:
|
||||
valid_version = False
|
||||
if info[2] and info[2] <= rpc_version:
|
||||
valid_version = False
|
||||
if valid_version:
|
||||
accessible.append(argument)
|
||||
return accessible
|
||||
|
||||
def add_stdout_logger(level='debug'):
|
||||
"""
|
||||
Add a stdout target for the transmissionrpc logging.
|
||||
"""
|
||||
levels = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR}
|
||||
|
||||
trpc_logger = logging.getLogger('transmissionrpc')
|
||||
loghandler = logging.StreamHandler()
|
||||
if level in levels.keys():
|
||||
loglevel = levels[level]
|
||||
trpc_logger.setLevel(loglevel)
|
||||
loghandler.setLevel(loglevel)
|
||||
trpc_logger.addHandler(loghandler)
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||
# Licensed under the MIT license.
|
||||
|
||||
import socket, datetime, logging
|
||||
from collections import namedtuple
|
||||
import transmissionrpc.constants as constants
|
||||
from transmissionrpc.constants import LOGGER
|
||||
|
||||
from six import string_types, iteritems
|
||||
|
||||
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
|
||||
|
||||
def format_size(size):
|
||||
"""
|
||||
Format byte size into IEC prefixes, B, KiB, MiB ...
|
||||
"""
|
||||
size = float(size)
|
||||
i = 0
|
||||
while size >= 1024.0 and i < len(UNITS):
|
||||
i += 1
|
||||
size /= 1024.0
|
||||
return (size, UNITS[i])
|
||||
|
||||
def format_speed(size):
|
||||
"""
|
||||
Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ...
|
||||
"""
|
||||
(size, unit) = format_size(size)
|
||||
return (size, unit + '/s')
|
||||
|
||||
def format_timedelta(delta):
|
||||
"""
|
||||
Format datetime.timedelta into <days> <hours>:<minutes>:<seconds>.
|
||||
"""
|
||||
minutes, seconds = divmod(delta.seconds, 60)
|
||||
hours, minutes = divmod(minutes, 60)
|
||||
return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
|
||||
|
||||
def format_timestamp(timestamp, utc=False):
|
||||
"""
|
||||
Format unix timestamp into ISO date format.
|
||||
"""
|
||||
if timestamp > 0:
|
||||
if utc:
|
||||
dt_timestamp = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
else:
|
||||
dt_timestamp = datetime.datetime.fromtimestamp(timestamp)
|
||||
return dt_timestamp.isoformat(' ')
|
||||
else:
|
||||
return '-'
|
||||
|
||||
class INetAddressError(Exception):
|
||||
"""
|
||||
Error parsing / generating a internet address.
|
||||
"""
|
||||
pass
|
||||
|
||||
def inet_address(address, default_port, default_address='localhost'):
|
||||
"""
|
||||
Parse internet address.
|
||||
"""
|
||||
addr = address.split(':')
|
||||
if len(addr) == 1:
|
||||
try:
|
||||
port = int(addr[0])
|
||||
addr = default_address
|
||||
except ValueError:
|
||||
addr = addr[0]
|
||||
port = default_port
|
||||
elif len(addr) == 2:
|
||||
try:
|
||||
port = int(addr[1])
|
||||
except ValueError:
|
||||
raise INetAddressError('Invalid address "%s".' % address)
|
||||
if len(addr[0]) == 0:
|
||||
addr = default_address
|
||||
else:
|
||||
addr = addr[0]
|
||||
else:
|
||||
raise INetAddressError('Invalid address "%s".' % address)
|
||||
try:
|
||||
socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM)
|
||||
except socket.gaierror:
|
||||
raise INetAddressError('Cannot look up address "%s".' % address)
|
||||
return (addr, port)
|
||||
|
||||
def rpc_bool(arg):
|
||||
"""
|
||||
Convert between Python boolean and Transmission RPC boolean.
|
||||
"""
|
||||
if isinstance(arg, string_types):
|
||||
try:
|
||||
arg = bool(int(arg))
|
||||
except ValueError:
|
||||
arg = arg.lower() in ['true', 'yes']
|
||||
return 1 if bool(arg) else 0
|
||||
|
||||
TR_TYPE_MAP = {
|
||||
'number' : int,
|
||||
'string' : str,
|
||||
'double': float,
|
||||
'boolean' : rpc_bool,
|
||||
'array': list,
|
||||
'object': dict
|
||||
}
|
||||
|
||||
def make_python_name(name):
|
||||
"""
|
||||
Convert Transmission RPC name to python compatible name.
|
||||
"""
|
||||
return name.replace('-', '_')
|
||||
|
||||
def make_rpc_name(name):
|
||||
"""
|
||||
Convert python compatible name to Transmission RPC name.
|
||||
"""
|
||||
return name.replace('_', '-')
|
||||
|
||||
def argument_value_convert(method, argument, value, rpc_version):
|
||||
"""
|
||||
Check and fix Transmission RPC issues with regards to methods, arguments and values.
|
||||
"""
|
||||
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
||||
args = constants.TORRENT_ARGS[method[-3:]]
|
||||
elif method in ('session-get', 'session-set'):
|
||||
args = constants.SESSION_ARGS[method[-3:]]
|
||||
else:
|
||||
return ValueError('Method "%s" not supported' % (method))
|
||||
if argument in args:
|
||||
info = args[argument]
|
||||
invalid_version = True
|
||||
while invalid_version:
|
||||
invalid_version = False
|
||||
replacement = None
|
||||
if rpc_version < info[1]:
|
||||
invalid_version = True
|
||||
replacement = info[3]
|
||||
if info[2] and info[2] <= rpc_version:
|
||||
invalid_version = True
|
||||
replacement = info[4]
|
||||
if invalid_version:
|
||||
if replacement:
|
||||
LOGGER.warning(
|
||||
'Replacing requested argument "%s" with "%s".'
|
||||
% (argument, replacement))
|
||||
argument = replacement
|
||||
info = args[argument]
|
||||
else:
|
||||
raise ValueError(
|
||||
'Method "%s" Argument "%s" does not exist in version %d.'
|
||||
% (method, argument, rpc_version))
|
||||
return (argument, TR_TYPE_MAP[info[0]](value))
|
||||
else:
|
||||
raise ValueError('Argument "%s" does not exists for method "%s".',
|
||||
(argument, method))
|
||||
|
||||
def get_arguments(method, rpc_version):
|
||||
"""
|
||||
Get arguments for method in specified Transmission RPC version.
|
||||
"""
|
||||
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
||||
args = constants.TORRENT_ARGS[method[-3:]]
|
||||
elif method in ('session-get', 'session-set'):
|
||||
args = constants.SESSION_ARGS[method[-3:]]
|
||||
else:
|
||||
return ValueError('Method "%s" not supported' % (method))
|
||||
accessible = []
|
||||
for argument, info in iteritems(args):
|
||||
valid_version = True
|
||||
if rpc_version < info[1]:
|
||||
valid_version = False
|
||||
if info[2] and info[2] <= rpc_version:
|
||||
valid_version = False
|
||||
if valid_version:
|
||||
accessible.append(argument)
|
||||
return accessible
|
||||
|
||||
def add_stdout_logger(level='debug'):
|
||||
"""
|
||||
Add a stdout target for the transmissionrpc logging.
|
||||
"""
|
||||
levels = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR}
|
||||
|
||||
trpc_logger = logging.getLogger('transmissionrpc')
|
||||
loghandler = logging.StreamHandler()
|
||||
if level in list(levels.keys()):
|
||||
loglevel = levels[level]
|
||||
trpc_logger.setLevel(loglevel)
|
||||
loghandler.setLevel(loglevel)
|
||||
trpc_logger.addHandler(loghandler)
|
||||
|
||||
def add_file_logger(filepath, level='debug'):
|
||||
"""
|
||||
Add a stdout target for the transmissionrpc logging.
|
||||
"""
|
||||
levels = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR}
|
||||
|
||||
trpc_logger = logging.getLogger('transmissionrpc')
|
||||
loghandler = logging.FileHandler(filepath, encoding='utf-8')
|
||||
if level in list(levels.keys()):
|
||||
loglevel = levels[level]
|
||||
trpc_logger.setLevel(loglevel)
|
||||
loghandler.setLevel(loglevel)
|
||||
trpc_logger.addHandler(loghandler)
|
||||
|
||||
Field = namedtuple('Field', ['value', 'dirty'])
|
||||
|
|
Loading…
Reference in a new issue