2
from netrc import netrc, NetrcParseError
5
from flexget.plugin import register_plugin, priority, get_plugin_by_name, PluginError
6
from flexget import validator
7
from flexget.utils.tools import replace_from_entry
9
log = logging.getLogger('transmission')
14
Transmissionrpc sets a new default opener for urllib2
15
We use this as a decorator to capture and restore it when needed
18
def new_f(self, *args, **kwargs):
20
prev_opener = urllib2._opener
21
urllib2.install_opener(self.opener)
23
f(self, *args, **kwargs)
24
self.opener = urllib2._opener
26
urllib2.install_opener(prev_opener)
30
class PluginTransmissionrpc:
32
Add url from entry url to transmission
39
netrc: /home/flexget/.tmnetrc
42
path: the download location
45
Default values for the config elements:
59
"""Return config validator"""
60
root = validator.factory()
61
root.accept('boolean')
62
advanced = root.accept('dict')
63
advanced.accept('text', key='host')
64
advanced.accept('number', key='port')
65
# note that password is optional in transmission
66
advanced.accept('file', key='netrc', required=False)
67
advanced.accept('text', key='username', required=False)
68
advanced.accept('text', key='password', required=False)
69
advanced.accept('path', key='path', required=False, allow_replacement=True)
70
advanced.accept('boolean', key='addpaused', required=False)
71
advanced.accept('number', key='maxconnections', required=False)
72
advanced.accept('number', key='maxupspeed', required=False)
73
advanced.accept('number', key='maxdownspeed', required=False)
74
advanced.accept('decimal', key='ratio', required=False)
75
advanced.accept('boolean', key='enabled')
76
advanced.accept('boolean', key='removewhendone')
79
def get_config(self, feed):
80
config = feed.config['transmissionrpc']
81
if isinstance(config, bool):
82
config = {'enabled': config}
83
config.setdefault('enabled', True)
84
config.setdefault('host', 'localhost')
85
config.setdefault('port', 9091)
86
config.setdefault('removewhendone', False)
90
def on_process_start(self, feed):
93
import transmissionrpc
94
from transmissionrpc import TransmissionError
95
from transmissionrpc import HTTPHandlerError
97
raise PluginError('Transmissionrpc module version 0.5 or higher required.', log)
98
set_plugin = get_plugin_by_name('set')
99
set_plugin.instance.register_keys({'path': 'text', \
100
'addpaused': 'boolean', \
101
'maxconnections': 'number', \
102
'maxupspeed': 'number', \
103
'maxdownspeed': 'number', \
105
config = self.get_config(feed)
106
if config['enabled']:
107
if feed.manager.options.test:
108
log.info('Trying to connect to transmission...')
109
self.client = self.create_rpc_client(feed)
111
log.info('Successfully connected to transmission.')
113
log.error('It looks like there was a problem connecting to transmission.')
114
elif config['removewhendone']:
115
self.client = self.create_rpc_client(feed)
116
self.remove_finished(self.client)
119
def on_feed_download(self, feed):
121
Call download plugin to generate the temp files we will load
122
into deluge then verify they are valid torrents
124
config = self.get_config(feed)
125
if not config['enabled']:
127
# If the download plugin is not enabled, we need to call it to get
128
# our temp .torrent files
129
if not 'download' in feed.config:
130
download = get_plugin_by_name('download')
131
download.instance.get_temp_files(feed, handle_magnets=True)
135
def on_feed_output(self, feed):
137
config = self.get_config(feed)
138
# don't add when learning
139
if feed.manager.options.learn:
141
if not config['enabled']:
143
# Do not run if there is nothing to do
144
if len(feed.accepted) == 0:
146
if self.client is None:
147
self.client = self.create_rpc_client(feed)
149
log.debug('Successfully connected to transmission.')
151
raise PluginError("Couldn't connect to transmission.")
152
self.add_to_transmission(self.client, feed)
154
def _make_torrent_options_dict(self, feed, entry):
157
config = self.get_config(feed)
159
for opt_key in ['path', 'addpaused', 'maxconnections', 'maxupspeed', 'maxdownspeed', 'ratio']:
161
opt_dic[opt_key] = entry[opt_key]
162
elif opt_key in config:
163
opt_dic[opt_key] = config[opt_key]
165
options = {'add': {}, 'change': {}}
167
if 'path' in opt_dic:
168
opt_dic['path'] = replace_from_entry(opt_dic['path'], entry, 'path', log.error)
170
options['add']['download_dir'] = os.path.expanduser(opt_dic['path'])
171
if 'addpaused' in opt_dic and opt_dic['addpaused']:
172
options['add']['paused'] = True
173
if 'maxconnections' in opt_dic:
174
options['add']['peer_limit'] = opt_dic['maxconnections']
176
if 'maxupspeed' in opt_dic:
177
options['change']['uploadLimit'] = opt_dic['maxupspeed']
178
options['change']['uploadLimited'] = True
179
if 'maxdownspeed' in opt_dic:
180
options['change']['downloadLimit'] = opt_dic['maxdownspeed']
181
options['change']['downloadLimited'] = True
183
if 'ratio' in opt_dic:
184
options['change']['seedRatioLimit'] = opt_dic['ratio']
185
if opt_dic['ratio'] == -1:
187
# 0 follow the global settings
188
# 1 override the global settings, seeding until a certain ratio
189
# 2 override the global settings, seeding regardless of ratio
190
options['change']['seedRatioMode'] = 2
192
options['change']['seedRatioMode'] = 1
196
def create_rpc_client(self, feed):
197
import transmissionrpc
198
from transmissionrpc import TransmissionError
199
from transmissionrpc import HTTPHandlerError
201
config = self.get_config(feed)
202
user, password = None, None
204
if 'netrc' in config:
206
user, account, password = netrc(config['netrc']).authenticators(config['host'])
208
log.error('netrc: unable to open: %s' % e.filename)
209
except NetrcParseError, e:
210
log.error('netrc: %s, file: %s, line: %s' % (e.msg, e.filename, e.lineno))
212
if 'username' in config:
213
user = config['username']
214
if 'password' in config:
215
password = config['password']
218
cli = transmissionrpc.Client(config['host'], config['port'], user, password)
219
except TransmissionError, e:
220
if isinstance(e.original, HTTPHandlerError):
221
if e.original.code == 111:
222
raise PluginError("Cannot connect to transmission. Is it running?")
223
elif e.original.code == 401:
224
raise PluginError("Username/password for transmission is incorrect. Cannot connect.")
225
elif e.original.code == 110:
226
raise PluginError("Cannot connect to transmission: Connection timed out.")
228
raise PluginError("Error connecting to transmission: %s" % e.original.message)
230
raise PluginError("Error connecting to transmission: %s" % e.message)
233
def add_to_transmission(self, cli, feed):
234
"""Adds accepted entries to transmission """
235
from transmissionrpc import TransmissionError
236
for entry in feed.accepted:
237
if feed.manager.options.test:
238
log.info('Would add %s to transmission' % entry['url'])
240
options = self._make_torrent_options_dict(feed, entry)
242
download = not(entry['url'].startswith('magnet:'))
244
# Check that file is downloaded
245
if download and not 'file' in entry:
246
feed.fail(entry, 'file missing?')
249
# Verify the temp file exists
250
if download and not os.path.exists(entry['file']):
251
tmp_path = os.path.join(feed.manager.config_base, 'temp')
252
log.debug('entry: %s' % entry)
253
log.debug('temp: %s' % ', '.join(os.listdir(tmp_path)))
254
feed.fail(entry, "Downloaded temp file '%s' doesn't exist!?" % entry['file'])
259
f = open(entry['file'], 'rb')
261
filedump = base64.encodestring(f.read())
264
r = cli.add(filedump, 30, **options['add'])
266
r = cli.add(None, filename=entry['url'],
267
timeout=30, **options['add'])
268
log.info('"%s" torrent added to transmission' % (entry['title']))
269
if options['change'].keys():
271
cli.change(id, 30, **options['change'])
272
except TransmissionError, e:
276
# Clean up temp file if download plugin is not configured for
278
if download and not 'download' in feed.config:
279
os.remove(entry['file'])
282
def remove_finished(self, cli):
283
# Get a list of active transfers
284
transfers = cli.info(arguments=['id', 'hashString', 'name', 'status', 'uploadRatio', 'seedRatioLimit'])
286
# Go through the list of active transfers and add finished transfers to remove_ids.
287
for transfer in transfers.itervalues():
288
log.debug('Transfer "%s": status: "%s" upload ratio: %.2f seed ratio: %.2f' % \
289
(transfer.name, transfer.status, transfer.uploadRatio, transfer.seedRatioLimit))
290
if transfer.status == 'stopped' and transfer.uploadRatio >= transfer.seedRatioLimit:
291
log.info('Remove torrent "%s" from transmission' % (transfer.name))
292
remove_ids.append(transfer.id)
293
# Remove finished transfers
294
if len(remove_ids) > 0:
295
cli.remove(remove_ids)
297
def on_feed_exit(self, feed):
298
"""Make sure all temp files are cleaned up when feed exits"""
299
# If download plugin is enabled, it will handle cleanup.
300
if not 'download' in feed.config:
301
download = get_plugin_by_name('download')
302
download.instance.cleanup_temp_files(feed)
304
def on_feed_abort(self, feed):
305
"""Make sure all temp files are cleaned up when feed is aborted."""
306
# If download plugin is enabled, it will handle cleanup.
307
if not 'download' in feed.config:
308
download = get_plugin_by_name('download')
309
download.instance.cleanup_temp_files(feed)
311
register_plugin(PluginTransmissionrpc, 'transmissionrpc')