Skip to content

Commit

Permalink
Fixing 'Plugin gets stuck in the "updates in progress" state' issue
Browse files Browse the repository at this point in the history
  • Loading branch information
gsturov committed Mar 16, 2013
1 parent 64cb19b commit a286d22
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 85 deletions.
147 changes: 76 additions & 71 deletions updatorr/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
# Line below is required to import tracker handler on fly.
import updatorr.tracker_handlers
from updatorr.utils import *
import sys
import traceback


log = logging.getLogger(__name__)
Expand Down Expand Up @@ -181,78 +183,81 @@ def walk(self, force=False):

# To prevent possible concurent runs.
self.walking = True

log.info('Updatorr walking...')
component.get('EventManager').emit(UpdatorrUpdatesCheckStartedEvent())

allow_last_walk_update = False

if isinstance(force, list):
torrents_list = force
else:
torrents_list = self.torrents_to_update

for torrent_id in torrents_list:
try:
torrent_data = self.core.get_torrent_status(torrent_id, [])
except KeyError:
log.debug('Updatorr \tSKIPPED No torrent with id %s listed [yet]' % torrent_id)
continue
log.info('Updatorr Processing %s ...' % torrent_data['name'])
# Remove not url data from comment
torrent_data['comment'] = RE_LINK.search(torrent_data['comment']).group('url')
if not is_url(torrent_data['comment']):
log.info('Updatorr \tSKIPPED No URL found in torrent comment')
continue
# From now on we consider that update took its place.
# If only this update is not forced.
if not force:
allow_last_walk_update = True
tracker_handler = get_tracker_handler(torrent_data, log)
if tracker_handler is None:
self.dump_error(torrent_id, 'Unable to find tracker handler for %s' % torrent_data['comment'])
continue
tracker_handler.set_settings(self.trackers_settings.get(tracker_handler.tracker_host))
new_torrent_filepath = tracker_handler.get_torrent_file()
if new_torrent_filepath is None:
self.dump_error(torrent_id, 'Error in tracker handling: %s' % tracker_handler.get_error_text())
continue

# Let's store cookies form that tracker to enter without logins in future sessions.
self.trackers_settings[tracker_handler.tracker_host]['cookies'] = tracker_handler.get_cookies(as_dict=True)

new_torrent_contents = read_torrent_file(new_torrent_filepath)
new_torrent_info = read_torrent_info(new_torrent_contents)
if torrent_data['hash'] == new_torrent_info['hash']:
log.info('Updatorr \tSKIPPED Torrent is up-to-date')
continue
log.info('Updatorr \tTorrent update is available')

new_torrent_prefs = get_new_prefs(torrent_data, new_torrent_info)
added_torrent_id = self.core.add_torrent_file(None, base64.encodestring(new_torrent_contents), new_torrent_prefs)

if added_torrent_id is not None:
self.core.remove_torrent(torrent_id, False)
log.info('Updatorr \tTorrent is updated')
# Fire up update finished event.
component.get('EventManager').emit(UpdatorrUpdateDoneEvent(new_torrent_info['hash']))
# Add new torrent hash to continue autoupdates.
self.set_items_to_update(new_torrent_info['hash'], True)
# Remove old torrent from autoupdates list.
self.set_items_to_update(torrent_id, False)
try:
log.info('Updatorr walking...')
component.get('EventManager').emit(UpdatorrUpdatesCheckStartedEvent())

allow_last_walk_update = False

if isinstance(force, list):
torrents_list = force
else:
self.dump_error(torrent_id, 'Unable to replace current torrent with a new one')

# No littering, remove temporary .torrent file.
os.remove(new_torrent_filepath)

if allow_last_walk_update:
# Remember lastrun time.
self.last_walk = time.time()

log.info('Updatorr walk is finished')
component.get('EventManager').emit(UpdatorrUpdatesCheckFinishedEvent())
self.walking = False
torrents_list = self.torrents_to_update

for torrent_id in torrents_list:
try:
torrent_data = self.core.get_torrent_status(torrent_id, [])
log.info('Updatorr Processing %s ...' % torrent_data['name'])
except KeyError:
log.debug('Updatorr \tSKIPPED No torrent with id %s listed [yet]' % torrent_id)
continue
# Remove not url data from comment
torrent_data['comment'] = RE_LINK.search(torrent_data['comment']).group('url')
if not is_url(torrent_data['comment']):
log.info('Updatorr \tSKIPPED No URL found in torrent comment')
continue
# From now on we consider that update took its place.
# If only this update is not forced.
if not force:
allow_last_walk_update = True
tracker_handler = get_tracker_handler(torrent_data, log)
if tracker_handler is None:
self.dump_error(torrent_id, 'Unable to find tracker handler for %s' % torrent_data['comment'])
continue
tracker_handler.set_settings(self.trackers_settings.get(tracker_handler.tracker_host))
new_torrent_filepath = tracker_handler.get_torrent_file()
if new_torrent_filepath is None:
self.dump_error(torrent_id, 'Error in tracker handling: %s' % tracker_handler.get_error_text())
continue

# Let's store cookies form that tracker to enter without logins in future sessions.
self.trackers_settings[tracker_handler.tracker_host]['cookies'] = tracker_handler.get_cookies(as_dict=True)

new_torrent_contents = read_torrent_file(new_torrent_filepath)
new_torrent_info = read_torrent_info(new_torrent_contents)
if torrent_data['hash'] == new_torrent_info['hash']:
log.info('Updatorr \tSKIPPED Torrent is up-to-date')
continue
log.info('Updatorr \tTorrent update is available')

new_torrent_prefs = get_new_prefs(torrent_data, new_torrent_info)
added_torrent_id = self.core.add_torrent_file(None, base64.encodestring(new_torrent_contents), new_torrent_prefs)

if added_torrent_id is not None:
self.core.remove_torrent(torrent_id, False)
log.info('Updatorr \tTorrent is updated')
# Fire up update finished event.
component.get('EventManager').emit(UpdatorrUpdateDoneEvent(new_torrent_info['hash']))
# Add new torrent hash to continue autoupdates.
self.set_items_to_update(new_torrent_info['hash'], True)
# Remove old torrent from autoupdates list.
self.set_items_to_update(torrent_id, False)
else:
self.dump_error(torrent_id, 'Unable to replace current torrent with a new one')

# No littering, remove temporary .torrent file.
os.remove(new_torrent_filepath)

if allow_last_walk_update:
# Remember lastrun time.
self.last_walk = time.time()

log.info('Updatorr walk is finished')
component.get('EventManager').emit(UpdatorrUpdatesCheckFinishedEvent())
except:

This comment has been minimized.

Copy link
@idlesign

idlesign Mar 17, 2013

Any particular reason to use such a broad exception?

This comment has been minimized.

Copy link
@gsturov

gsturov Mar 17, 2013

Author Owner

The only reason is that the function is called on a separate thread and any exception will just force the thread to die (silently). All we can do is report the cause of the death.
In other words since this is a separated thread we can't propagate the exception outside of the function scope anyway.

This comment has been minimized.

Copy link
@idlesign

idlesign Mar 17, 2013

The question was about the broadness rather than exceptions in general or exceptions in threads ;)
Anyway I got your intension to have a post mortem. Need to think the approach over though.

This comment has been minimized.

Copy link
@gsturov

gsturov Mar 17, 2013

Author Owner

Not sure I understand the question then.
If we want to report problems/exceptions why don't we catch them all?
Not sure I understand your concern...

log.error(traceback.format_exc())
finally:
self.walking = False

def dump_error(self, torrent_id, text):
"""Logs error and fires error event."""
Expand Down
47 changes: 33 additions & 14 deletions updatorr/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

from deluge._libtorrent import lt

import logging
log = logging.getLogger(__name__)

# Torrent tracker handler classes registry.
TRACKER_HANDLERS = {}

Expand Down Expand Up @@ -51,7 +54,7 @@ def read_torrent_info(file_contents):
"""
info_contents = lt.torrent_info(lt.bdecode(file_contents))
files_from_torrent = [a_file.path for a_file in info_contents.files()]
files_from_torrent = [a_file.path.decode('utf-8') for a_file in info_contents.files()]
info = {'hash': str(info_contents.info_hash()), 'files': files_from_torrent}
return info

Expand All @@ -66,6 +69,7 @@ def get_new_prefs(full_prefs, new_torrent_info):
are copied from the previous session.
"""

new_prefs = deepcopy(full_prefs)

# These are some items we definitely do not want in a new session.
Expand All @@ -88,28 +92,43 @@ def get_new_prefs(full_prefs, new_torrent_info):
new_prefs['mapped_files'] = {}
new_prefs['file_priorities'] = []

# Check for root folder rename
new_files = new_torrent_info['files']
old_root = _find_root([a_file['path'] for a_file in full_prefs['files']])
if old_root is not None:
new_root = _find_root([os.path.dirname(a_file) for a_file in new_torrent_info['files']])
if new_root is not None and new_root != old_root:
new_files = new_prefs['mapped_files']
i = 0
for a_file in new_torrent_info['files']:
if len(new_root) == 0:
new_prefs['mapped_files'][i] = os.path.join(old_root, a_file)
elif len(old_root) == 0:
new_prefs['mapped_files'][i] = a_file.replace(new_root, '', 1)[1:] # remove path separator as well
else:
new_prefs['mapped_files'][i] = a_file.replace(new_root, old_root, 1)
i += 1

# Copying files priorities.
old_priorities = get_files_priorities(full_prefs)
for a_file in new_torrent_info['files']:
for a_file in new_files:
priority = 1
if a_file in old_priorities:
priority = old_priorities[a_file]
new_prefs['file_priorities'].append(priority)

# Check for root folder rename
old_splited = os.path.split(full_prefs['files'][0]['path'])
if len (old_splited) > 1:
old_folder = old_splited[0]
new_folder = os.path.split(new_torrent_info['files'][0])[0]
if new_folder != old_folder:
i = 0
for a_file in new_torrent_info['files']:
new_prefs['mapped_files'][i] = a_file.replace(new_folder, old_folder)
i += 1


return new_prefs

def _find_root(folders):
"""Returns common root folder (which can be empty string) or None if given folders don't have a common root folder"""
root = None
for folder in folders:
while not os.path.basename(folder) == folder:
folder = os.path.dirname(folder)
if root is not None:
if folder != root: return None
else: root = folder
return root

def get_files_priorities(torrent_data):
"""Returns a dictionary with files priorities, where
Expand Down

0 comments on commit a286d22

Please sign in to comment.