7
from flexget.event import event
8
from flexget.entry import Entry
9
from flexget.utils.tools import make_valid_path
10
from flexget.plugin import register_plugin, PluginError, priority, get_plugin_by_name, DependencyError
11
from flexget.utils.template import RenderError
13
log = logging.getLogger('deluge')
15
# Deluge does not install to python system on Windows, add the install directory to sys.path if it is found
16
if sys.platform.startswith('win') and os.environ.get('ProgramFiles'):
17
deluge_dir = os.path.join(os.environ['ProgramFiles'], 'Deluge')
18
log.debug('Looking for deluge install in %s' % deluge_dir)
19
if os.path.isdir(deluge_dir):
20
log.debug('Found deluge install in %s adding to sys.path' % deluge_dir)
21
sys.path.append(deluge_dir)
22
for item in os.listdir(deluge_dir):
23
if item.endswith(('.egg', '.zip')):
24
sys.path.append(os.path.join(deluge_dir, item))
27
from twisted.python import log as twisted_log
28
from twisted.internet.main import installReactor
29
from twisted.internet.selectreactor import SelectReactor
31
class PausingReactor(SelectReactor):
32
"""A SelectReactor that can be paused and resumed."""
35
SelectReactor.__init__(self)
37
self._return_value = None
38
self._release_requested = False
39
self._mainLoopGen = None
41
# Older versions of twisted do not have the _started attribute, make it a synonym for running in that case
42
if not hasattr(self, '_started'):
43
PausingReactor._started = property(lambda self: self.running)
45
def _mainLoopGenerator(self):
46
"""Generator that acts as mainLoop, but yields when requested."""
50
if self._release_requested:
51
self._release_requested = False
53
yield self._return_value
56
except KeyboardInterrupt:
57
# Keyboard interrupt pauses the reactor
60
# GeneratorExit means stop the generator; Do it cleanly by stopping the whole reactor.
61
log.debug('Got GeneratorExit, stopping reactor.', exc_info=True)
65
twisted_log.msg("Unexpected error in main loop.")
68
twisted_log.msg('Main loop terminated.')
70
def run(self, installSignalHandlers=False):
71
"""Starts or resumes the reactor."""
73
self.startRunning(installSignalHandlers)
74
self._mainLoopGen = self._mainLoopGenerator()
76
return self._mainLoopGen.next()
80
def pause(self, return_value=None):
81
"""Causes reactor to pause after this iteration.
82
If :return_value: is specified, it will be returned by the reactor.run call."""
83
self._return_value = return_value
84
self._release_requested = True
87
"""Stops the reactor."""
88
SelectReactor.stop(self)
89
# If this was called while the reactor was paused we have to resume in order for it to complete
93
# These need to be re-registered so that the PausingReactor can be safely restarted after a stop
94
self.addSystemEventTrigger('during', 'shutdown', self.crash)
95
self.addSystemEventTrigger('during', 'shutdown', self.disconnectAll)
97
# Configure twisted to use the PausingReactor.
98
installReactor(PausingReactor())
101
# If twisted is not found, errors will be shown later
105
# Define a base class with some methods that are used for all deluge versions
106
class DelugePlugin(object):
107
"""Base class for deluge plugins, contains settings and methods for connecting to a deluge daemon."""
109
def validate_connection_info(self, dict_validator):
110
dict_validator.accept('text', key='host')
111
dict_validator.accept('integer', key='port')
112
dict_validator.accept('text', key='user')
113
dict_validator.accept('text', key='pass')
115
def prepare_connection_info(self, config):
116
config.setdefault('host', 'localhost')
117
config.setdefault('port', 58846)
118
config.setdefault('user', '')
119
config.setdefault('pass', '')
121
def on_process_start(self, feed, config):
122
"""Raise a DependencyError if our dependencies aren't available"""
123
# This is overridden by OutputDeluge to add deluge 1.1 support
125
from deluge.ui.client import client
126
except ImportError, e:
127
log.debug('Error importing deluge: %s' % e)
128
raise DependencyError('output_deluge', 'deluge', 'Deluge module and it\'s dependencies required. ImportError: %s' % e, log)
130
from twisted.internet import reactor
132
raise DependencyError('output_deluge', 'twisted.internet', 'Twisted.internet package required', log)
133
log.debug('Using deluge 1.2 api')
135
def on_feed_abort(self, feed, config):
138
# Add some more methods to the base class if we are using deluge 1.2+
140
from twisted.internet import reactor
141
from deluge.ui.client import client
142
from deluge.ui.common import get_localhost_auth
144
class DelugePlugin(DelugePlugin):
146
def on_disconnect(self):
147
"""Pauses the reactor. Gets called when we disconnect from the daemon."""
148
# pause the reactor, so flexget can continue
149
reactor.callLater(0, reactor.pause)
151
def on_connect_fail(self, result):
152
"""Pauses the reactor, returns PluginError. Gets called when connection to deluge daemon fails."""
153
log.debug('Connect to deluge daemon failed, result: %s' % result)
154
reactor.callLater(0, reactor.pause, PluginError('Could not connect to deluge daemon', log))
156
def on_connect_success(self, result, feed, config):
157
"""Gets called when successfully connected to the daemon. Should do the work then call client.disconnect"""
158
raise NotImplementedError
160
def connect(self, feed, config):
161
"""Connects to the deluge daemon and runs on_connect_success """
163
if config['host'] in ['localhost', '127.0.0.1'] and not config.get('user'):
164
# If an user is not specified, we have to do a lookup for the localclient username/password
165
auth = get_localhost_auth()
167
config['user'], config['pass'] = auth
169
raise PluginError('Unable to get local authentication info for Deluge. '
170
'You may need to specify an username and password from your Deluge auth file.')
172
client.set_disconnect_callback(self.on_disconnect)
177
username=config['user'],
178
password=config['pass'])
180
d.addCallback(self.on_connect_success, feed, config).addErrback(self.on_connect_fail)
181
result = reactor.run()
182
if isinstance(result, Exception):
186
@event('manager.shutdown')
187
def stop_reactor(manager):
188
"""Shut down the twisted reactor after all feeds have run."""
189
if not reactor._stopped:
190
log.debug('Stopping twisted reactor.')
197
class InputDeluge(DelugePlugin):
198
"""Create entries for torrents in the deluge session."""
202
'hash': 'torrent_info_hash',
203
'num_peers': 'torrent_peers',
204
'num_seeds': 'torrent_seeds',
205
'progress': 'deluge_progress',
206
'private': 'deluge_private',
207
'state': 'deluge_state',
209
'ratio': 'deluge_ratio',
210
'move_on_completed_path': 'deluge_movedone',
211
'save_path': 'deluge_path',
212
'label': 'deluge_label',
213
'total_size': ('content_size', lambda size: size / 1024 / 1024),
214
'files': ('content_files', lambda file_dicts: [os.path.basename(f['path']) for f in file_dicts])}
220
from flexget import validator
221
root = validator.factory()
222
root.accept('boolean')
223
advanced = root.accept('dict')
224
advanced.accept('path', key='config_path')
225
self.validate_connection_info(advanced)
226
filter = advanced.accept('dict', key='filter')
227
filter.accept('text', key='label')
228
filter.accept('choice', key='state').accept_choices(
229
['active', 'downloading', 'seeding', 'queued', 'paused'], ignore_case=True)
232
def prepare_config(self, config):
233
if isinstance(config, bool):
235
if 'filter' in config:
236
filter = config['filter']
237
if 'label' in filter:
238
filter['label'] = filter['label'].lower()
239
if 'state' in filter:
240
filter['state'] = filter['state'].capitalize()
241
self.prepare_connection_info(config)
244
def on_feed_input(self, feed, config):
245
"""Generates and returns a list of entries from the deluge daemon."""
246
# Reset the entries list
248
# Call connect, entries get generated if everything is successful
249
self.connect(feed, self.prepare_config(config))
252
def on_connect_success(self, result, feed, config):
253
"""Creates a list of FlexGet entries from items loaded in deluge and stores them to self.entries"""
254
from deluge.ui.client import client
256
def on_get_torrents_status(torrents):
257
config_path = os.path.expanduser(config.get('config_path', ''))
258
for hash, torrent_dict in torrents.iteritems():
259
# Make sure it has a url so no plugins crash
260
entry = Entry(deluge_id=hash, url='')
262
torrent_path = os.path.join(config_path, 'state', hash + '.torrent')
263
if os.path.isfile(torrent_path):
264
entry['location'] = torrent_path
265
if not torrent_path.startswith('/'):
266
torrent_path = '/' + torrent_path
267
entry['url'] = 'file://' + torrent_path
269
log.warning('Did not find torrent file at %s' % torrent_path)
270
for key, value in torrent_dict.iteritems():
271
flexget_key = self.settings_map[key]
272
if isinstance(flexget_key, tuple):
273
flexget_key, format_func = flexget_key
274
value = format_func(value)
275
entry[flexget_key] = value
276
self.entries.append(entry)
278
filter = config.get('filter', {})
279
client.core.get_torrents_status(filter, self.settings_map.keys()).addCallback(on_get_torrents_status)
282
class OutputDeluge(DelugePlugin):
283
"""Add the torrents directly to deluge, supporting custom save paths."""
286
from flexget import validator
287
root = validator.factory()
288
root.accept('boolean')
289
deluge = root.accept('dict')
290
self.validate_connection_info(deluge)
291
deluge.accept('path', key='path', allow_replacement=True)
292
deluge.accept('path', key='movedone', allow_replacement=True)
293
deluge.accept('text', key='label')
294
deluge.accept('boolean', key='queuetotop')
295
deluge.accept('boolean', key='automanaged')
296
deluge.accept('number', key='maxupspeed')
297
deluge.accept('number', key='maxdownspeed')
298
deluge.accept('integer', key='maxconnections')
299
deluge.accept('integer', key='maxupslots')
300
deluge.accept('number', key='ratio')
301
deluge.accept('boolean', key='removeatratio')
302
deluge.accept('boolean', key='addpaused')
303
deluge.accept('boolean', key='compact')
304
deluge.accept('text', key='content_filename')
305
deluge.accept('boolean', key='main_file_only')
306
deluge.accept('boolean', key='enabled')
309
def prepare_config(self, config):
310
if isinstance(config, bool):
311
config = {'enabled': config}
312
self.prepare_connection_info(config)
313
config.setdefault('enabled', True)
314
config.setdefault('path', '')
315
config.setdefault('movedone', '')
316
config.setdefault('label', '')
321
self.deluge_version = None
322
self.options = {'maxupspeed': 'max_upload_speed', 'maxdownspeed': 'max_download_speed', \
323
'maxconnections': 'max_connections', 'maxupslots': 'max_upload_slots', \
324
'automanaged': 'auto_managed', 'ratio': 'stop_ratio', 'removeatratio': 'remove_at_ratio', \
325
'addpaused': 'add_paused', 'compact': 'compact_allocation'}
328
def on_process_start(self, feed, config):
329
"""Register the usable set: keywords. Detect what version of deluge is loaded."""
330
set_plugin = get_plugin_by_name('set')
331
set_plugin.instance.register_keys({'path': 'text', 'movedone': 'text', \
332
'queuetotop': 'boolean', 'label': 'text', 'automanaged': 'boolean', \
333
'maxupspeed': 'number', 'maxdownspeed': 'number', 'maxupslots': 'integer', \
334
'maxconnections': 'integer', 'ratio': 'number', 'removeatratio': 'boolean', \
335
'addpaused': 'boolean', 'compact': 'boolean', 'content_filename': 'text', 'main_file_only': 'boolean'})
336
if self.deluge12 is None:
337
logger = log.info if feed.manager.options.test else log.debug
339
log.debug('Looking for deluge 1.1 API')
340
from deluge.ui.client import sclient
341
log.debug('1.1 API found')
343
log.debug('Looking for deluge 1.2 API')
344
DelugePlugin.on_process_start(self, feed, config)
345
logger('Using deluge 1.2 api')
348
logger('Using deluge 1.1 api')
349
self.deluge12 = False
352
def on_feed_download(self, feed, config):
354
call download plugin to generate the temp files we will load into deluge
355
then verify they are valid torrents
357
import deluge.ui.common
358
config = self.prepare_config(config)
359
if not config['enabled']:
361
# If the download plugin is not enabled, we need to call it to get our temp .torrent files
362
if not 'download' in feed.config:
363
download = get_plugin_by_name('download')
364
for entry in feed.accepted:
365
if not entry.get('deluge_id'):
366
download.instance.get_temp_file(feed, entry, handle_magnets=True)
368
# Check torrent files are valid
369
for entry in feed.accepted:
370
if os.path.exists(entry.get('file', '')):
371
# Check if downloaded file is a valid torrent file
373
deluge.ui.common.TorrentInfo(entry['file'])
375
feed.fail(entry, 'Invalid torrent file')
376
log.error('Torrent file appears invalid for: %s', entry['title'])
379
def on_feed_output(self, feed, config):
380
"""Add torrents to deluge at exit."""
381
config = self.prepare_config(config)
382
# don't add when learning
383
if feed.manager.options.learn:
385
if not config['enabled'] or not (feed.accepted or feed.manager.options.test):
388
add_to_deluge = self.connect if self.deluge12 else self.add_to_deluge11
389
add_to_deluge(feed, config)
390
# Clean up temp file if download plugin is not configured for this feed
391
if not 'download' in feed.config:
392
for entry in feed.accepted + feed.failed:
393
if os.path.exists(entry.get('file', '')):
394
os.remove(entry['file'])
397
def add_to_deluge11(self, feed, config):
398
"""Add torrents to deluge using deluge 1.1.x api."""
400
from deluge.ui.client import sclient
402
raise PluginError('Deluge module required', log)
404
sclient.set_core_uri()
405
for entry in feed.accepted:
407
before = sclient.get_session_state()
408
except Exception, (errno, msg):
409
raise PluginError('Could not communicate with deluge core. %s' % msg, log)
410
if feed.manager.options.test:
413
path = entry.get('path', config['path'])
416
opts['download_location'] = os.path.expanduser(entry.render(path))
417
except RenderError, e:
418
log.error('Could not set path for %s: %s' % (entry['title'], e))
419
for fopt, dopt in self.options.iteritems():
420
value = entry.get(fopt, config.get(fopt))
421
if value is not None:
424
opts['stop_at_ratio'] = True
426
# check that file is downloaded
427
if not 'file' in entry:
428
feed.fail(entry, 'file missing?')
431
# see that temp file is present
432
if not os.path.exists(entry['file']):
433
tmp_path = os.path.join(feed.manager.config_base, 'temp')
434
log.debug('entry: %s' % entry)
435
log.debug('temp: %s' % ', '.join(os.listdir(tmp_path)))
436
feed.fail(entry, 'Downloaded temp file \'%s\' doesn\'t exist!?' % entry['file'])
439
sclient.add_torrent_file([entry['file']], [opts])
440
log.info('%s torrent added to deluge with options %s' % (entry['title'], opts))
442
movedone = entry.get('movedone', config['movedone'])
443
label = entry.get('label', config['label']).lower()
444
queuetotop = entry.get('queuetotop', config.get('queuetotop'))
446
# Sometimes deluge takes a moment to add the torrent, wait a second.
448
after = sclient.get_session_state()
450
# find torrentid of just added torrent
451
if not item in before:
453
movedone = entry.render(movedone)
454
except RenderError, e:
455
log.error('Could not set movedone for %s: %s' % (entry['title'], e))
458
movedone = os.path.expanduser(movedone)
459
if not os.path.isdir(movedone):
460
log.debug('movedone path %s doesn\'t exist, creating' % movedone)
461
os.makedirs(movedone)
462
log.debug('%s move on complete set to %s' % (entry['title'], movedone))
463
sclient.set_torrent_move_on_completed(item, True)
464
sclient.set_torrent_move_on_completed_path(item, movedone)
466
if not 'label' in sclient.get_enabled_plugins():
467
sclient.enable_plugin('label')
468
if not label in sclient.label_get_labels():
469
sclient.label_add(label)
470
log.debug('%s label set to \'%s\'' % (entry['title'], label))
471
sclient.label_set_torrent(item, label)
473
log.debug('%s moved to top of queue' % entry['title'])
474
sclient.queue_top([item])
477
log.info('%s is already loaded in deluge. Cannot change label, movedone, or queuetotop' % entry['title'])
479
def on_connect_success(self, result, feed, config):
480
"""Gets called when successfully connected to a daemon."""
481
from deluge.ui.client import client
482
from twisted.internet import reactor, defer
486
log.debug('on_connect_success returned a failed result. BUG?')
488
if feed.manager.options.test:
489
log.debug('Test connection to deluge daemon successful.')
493
def format_label(label):
494
"""Makes a string compliant with deluge label naming rules"""
495
return re.sub('[^\w-]+', '_', label.lower())
497
def set_torrent_options(torrent_id, entry, opts):
498
"""Gets called when a torrent was added to the daemon."""
501
log.error('There was an error adding %s to deluge.' % entry['title'])
502
# TODO: Fail entry? How can this happen still now?
504
log.info('%s successfully added to deluge.' % entry['title'])
505
entry['deluge_id'] = torrent_id
507
def create_path(result, path):
508
"""Creates the specified path if deluge is older than 1.3"""
509
from deluge.common import VersionSplit
510
# Before 1.3, deluge would not create a non-existent move directory, so we need to.
511
if VersionSplit('1.3.0') > VersionSplit(self.deluge_version):
512
if client.is_localhost():
513
if not os.path.isdir(path):
514
log.debug('path %s doesn\'t exist, creating' % path)
517
log.warning('If path does not exist on the machine running the daemon, move will fail.')
519
if opts.get('movedone'):
520
dlist.append(version_deferred.addCallback(create_path, opts['movedone']))
521
dlist.append(client.core.set_torrent_move_completed(torrent_id, True))
522
dlist.append(client.core.set_torrent_move_completed_path(torrent_id, opts['movedone']))
523
log.debug('%s move on complete set to %s' % (entry['title'], opts['movedone']))
524
if opts.get('label'):
526
def apply_label(result, torrent_id, label):
527
"""Gets called after labels and torrent were added to deluge."""
528
return client.label.set_torrent(torrent_id, label)
530
dlist.append(label_deferred.addCallback(apply_label, torrent_id, opts['label']))
531
if opts.get('queuetotop') is not None:
532
if opts['queuetotop']:
533
dlist.append(client.core.queue_top([torrent_id]))
534
log.debug('%s moved to top of queue' % entry['title'])
536
dlist.append(client.core.queue_bottom([torrent_id]))
537
log.debug('%s moved to bottom of queue' % entry['title'])
539
def on_get_torrent_status(status):
540
"""Gets called with torrent status, including file info.
541
Sets the torrent options which require knowledge of the current status of the torrent."""
545
# Determine where the file should be
547
if opts.get('movedone'):
548
if status['progress'] == 100:
549
move_now_path = opts['movedone']
551
# Deluge will unset the move completed option if we move the storage, forgo setting proper
552
# path, in favor of leaving proper final location.
553
log.debug('Not moving storage for %s, as this will prevent movedone.' % entry['title'])
554
elif opts.get('path'):
555
move_now_path = opts['path']
557
if move_now_path and os.path.normpath(move_now_path) != os.path.normpath(status['save_path']):
558
main_file_dlist.append(version_deferred.addCallback(create_path, move_now_path))
559
log.debug('Moving storage for %s to %s' % (entry['title'], move_now_path))
560
main_file_dlist.append(client.core.move_storage([torrent_id], move_now_path))
562
if opts.get('content_filename') or opts.get('main_file_only'):
565
# Checks the download path as well as the move completed path for existence of the file
566
if os.path.exists(os.path.join(status['save_path'], filename)):
568
elif status.get('move_on_completed') and status.get('move_on_completed_path'):
569
if os.path.exists(os.path.join(status['move_on_completed_path'], filename)):
574
for file in status['files']:
575
# Only rename file if it is > 90% of the content
576
if file['size'] > (status['total_size'] * 0.9):
577
if opts.get('content_filename'):
578
filename = opts['content_filename'] + os.path.splitext(file['path'])[1]
580
if client.is_localhost():
582
# Try appending a (#) suffix till a unique filename is found
583
filename = ''.join([opts['content_filename'], '(', str(counter), ')', os.path.splitext(file['path'])[1]])
586
log.debug('Cannot ensure content_filename is unique when adding to a remote deluge daemon.')
587
log.debug('File %s in %s renamed to %s' % (file['path'], entry['title'], filename))
588
main_file_dlist.append(client.core.rename_files(torrent_id, [(file['index'], filename)]))
589
if opts.get('main_file_only'):
590
file_priorities = [1 if f['index'] == file['index'] else 0 for f in status['files']]
591
main_file_dlist.append(client.core.set_torrent_file_priorities(torrent_id, file_priorities))
594
log.warning('No files in %s are > 90%% of content size, no files renamed.' % entry['title'])
596
return defer.DeferredList(main_file_dlist)
598
status_keys = ['files', 'total_size', 'save_path', 'move_on_completed_path', 'move_on_completed', 'progress']
599
dlist.append(client.core.get_torrent_status(torrent_id, status_keys).addCallback(on_get_torrent_status))
601
return defer.DeferredList(dlist)
603
def on_fail(result, feed, entry):
604
"""Gets called when daemon reports a failure adding the torrent."""
605
log.info('%s was not added to deluge! %s' % (entry['title'], result))
606
feed.fail(entry, 'Could not be added to deluge')
608
# dlist is a list of deferreds that must complete before we exit
610
# loop through entries to get a list of labels to add
611
labels = set([format_label(entry['label']) for entry in feed.accepted if entry.get('label')])
612
if config.get('label'):
613
labels.add(format_label(config['label']))
614
label_deferred = defer.succeed(True)
616
# Make sure the label plugin is available and enabled, then add appropriate labels
618
def on_get_enabled_plugins(plugins):
619
"""Gets called with the list of enabled deluge plugins."""
621
def on_label_enabled(result):
622
""" This runs when we verify the label plugin is enabled. """
624
def on_get_labels(d_labels):
625
"""Gets available labels from deluge, and adds any new labels we need."""
628
if not label in d_labels:
629
log.debug('Adding the label %s to deluge' % label)
630
dlist.append(client.label.add(label))
631
return defer.DeferredList(dlist)
633
return client.label.get_labels().addCallback(on_get_labels)
635
if 'Label' in plugins:
636
return on_label_enabled(True)
638
# Label plugin isn't enabled, so we check if it's available and enable it.
640
def on_get_available_plugins(plugins):
641
"""Gets plugins available to deluge, enables Label plugin if available."""
642
if 'Label' in plugins:
643
log.debug('Enabling label plugin in deluge')
644
return client.core.enable_plugin('Label').addCallback(on_label_enabled)
646
log.error('Label plugin is not installed in deluge')
648
return client.core.get_available_plugins().addCallback(on_get_available_plugins)
650
label_deferred = client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
651
dlist.append(label_deferred)
653
def on_get_daemon_info(ver):
654
"""Gets called with the daemon version info, stores it in self."""
655
log.debug('deluge version %s' % ver)
656
self.deluge_version = ver
658
version_deferred = client.daemon.info().addCallback(on_get_daemon_info)
659
dlist.append(version_deferred)
661
def on_get_session_state(torrent_ids):
662
"""Gets called with a list of torrent_ids loaded in the deluge session.
663
Adds new torrents and modifies the settings for ones already in the session."""
666
for entry in feed.accepted:
668
def add_entry(entry, opts):
669
"""Adds an entry to the deluge session"""
670
magnet, filedump = None, None
671
if entry.get('url', '').startswith('magnet:'):
672
magnet = entry['url']
674
if not os.path.exists(entry['file']):
675
feed.fail(entry, 'Downloaded temp file \'%s\' doesn\'t exist!' % entry['file'])
679
f = open(entry['file'], 'rb')
680
filedump = base64.encodestring(f.read())
684
log.verbose('Adding %s to deluge.' % entry['title'])
686
return client.core.add_torrent_magnet(magnet, opts)
688
return client.core.add_torrent_file(entry['title'], filedump, opts)
690
# Generate deluge options dict for torrent add
693
path = entry.render(entry.get('path', config['path']))
695
add_opts['download_location'] = make_valid_path(os.path.expanduser(path))
696
except RenderError, e:
697
log.error('Could not set path for %s: %s' % (entry['title'], e))
698
for fopt, dopt in self.options.iteritems():
699
value = entry.get(fopt, config.get(fopt))
700
if value is not None:
701
add_opts[dopt] = value
703
add_opts['stop_at_ratio'] = True
704
# Make another set of options, that get set after the torrent has been added
705
modify_opts = {'label': format_label(entry.get('label', config['label'])),
706
'queuetotop': entry.get('queuetotop', config.get('queuetotop')),
707
'main_file_only': entry.get('main_file_only', config.get('main_file_only', False))}
709
movedone = entry.render(entry.get('movedone', config['movedone']))
710
modify_opts['movedone'] = make_valid_path(os.path.expanduser(movedone))
711
except RenderError, e:
712
log.error('Error setting movedone for %s: %s' % (entry['title'], e))
714
modify_opts['content_filename'] = entry.render(entry.get('content_filename', config.get('content_filename', '')))
715
except RenderError, e:
716
log.error('Error setting content_filename for %s: %s' % (entry['title'], e))
718
torrent_id = entry.get('deluge_id') or entry.get('torrent_info_hash')
719
torrent_id = torrent_id and torrent_id.lower()
720
if torrent_id in torrent_ids:
721
log.info('%s is already loaded in deluge, setting options' % entry['title'])
722
# Entry has a deluge id, verify the torrent is still in the deluge session and apply options
723
# Since this is already loaded in deluge, we may also need to change the path
724
modify_opts['path'] = add_opts.pop('download_location', None)
725
dlist.extend([set_torrent_options(torrent_id, entry, modify_opts),
726
client.core.set_torrent_options([torrent_id], add_opts)])
728
dlist.append(add_entry(entry, add_opts).addCallbacks(set_torrent_options, on_fail,
729
callbackArgs=(entry, modify_opts), errbackArgs=(feed, entry)))
730
return defer.DeferredList(dlist)
731
dlist.append(client.core.get_session_state().addCallback(on_get_session_state))
733
def on_complete(result):
734
"""Gets called when all of our tasks for deluge daemon are complete."""
736
tasks = defer.DeferredList(dlist).addBoth(on_complete)
738
def on_timeout(result):
739
"""Gets called if tasks have not completed in 30 seconds.
740
Should only happen when something goes wrong."""
741
log.error('Timed out while adding torrents to deluge.')
742
log.debug('dlist: %s' % result.resultList)
745
# Schedule a disconnect to happen in 30 seconds if FlexGet hangs while connected to Deluge
746
reactor.callLater(30, lambda: tasks.called or on_timeout(tasks))
748
def on_feed_exit(self, feed, config):
749
"""Make sure all temp files are cleaned up when feed exits"""
750
# If download plugin is enabled, it will handle cleanup.
751
if not 'download' in feed.config:
752
download = get_plugin_by_name('download')
753
download.instance.cleanup_temp_files(feed)
755
def on_feed_abort(self, feed, config):
756
"""Make sure normal cleanup tasks still happen on abort."""
757
DelugePlugin.on_feed_abort(self, feed, config)
758
self.on_feed_exit(feed, config)
761
register_plugin(InputDeluge, 'from_deluge', api_ver=2)
762
register_plugin(OutputDeluge, 'deluge', api_ver=2)