mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-03 06:58:42 +00:00
[Core] Archive corrupt torrent.state on load
If the torrent.state was corrupted then loading would create a new state with no backup to examine. The solution is to use the archive function to save a copy of the torrent.state. Added a message argument to archive_files so that the error message with a reason for archiving can be included in the tarball.
This commit is contained in:
parent
d70abd2986
commit
f47089ae7d
3 changed files with 60 additions and 16 deletions
|
@ -25,6 +25,8 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
|
from contextlib import closing
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
|
@ -160,7 +162,7 @@ def get_default_download_dir():
|
||||||
return download_dir
|
return download_dir
|
||||||
|
|
||||||
|
|
||||||
def archive_files(arc_name, filepaths):
|
def archive_files(arc_name, filepaths, message=None):
|
||||||
"""Compress a list of filepaths into timestamped tarball in config dir.
|
"""Compress a list of filepaths into timestamped tarball in config dir.
|
||||||
|
|
||||||
The archiving config directory is 'archive'.
|
The archiving config directory is 'archive'.
|
||||||
|
@ -197,9 +199,17 @@ def archive_files(arc_name, filepaths):
|
||||||
log.warning('More than %s tarballs in config archive', max_num_arcs)
|
log.warning('More than %s tarballs in config archive', max_num_arcs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with tarfile.open(arc_filepath, 'w:' + arc_comp) as tf:
|
with tarfile.open(arc_filepath, 'w:' + arc_comp) as tar:
|
||||||
for filepath in filepaths:
|
for filepath in filepaths:
|
||||||
tf.add(filepath, arcname=os.path.basename(filepath))
|
if not os.path.isfile(filepath):
|
||||||
|
continue
|
||||||
|
tar.add(filepath, arcname=os.path.basename(filepath))
|
||||||
|
if message:
|
||||||
|
with closing(BytesIO(message.encode('utf8'))) as fobj:
|
||||||
|
tarinfo = tarfile.TarInfo('archive_message.txt')
|
||||||
|
tarinfo.size = len(fobj.getvalue())
|
||||||
|
tarinfo.mtime = time.time()
|
||||||
|
tar.addfile(tarinfo, fileobj=fobj)
|
||||||
except OSError:
|
except OSError:
|
||||||
log.error('Problem occurred archiving filepaths: %s', filepaths)
|
log.error('Problem occurred archiving filepaths: %s', filepaths)
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -228,14 +228,7 @@ class TorrentManager(component.Component):
|
||||||
def start(self):
|
def start(self):
|
||||||
# Check for old temp file to verify safe shutdown
|
# Check for old temp file to verify safe shutdown
|
||||||
if os.path.isfile(self.temp_file):
|
if os.path.isfile(self.temp_file):
|
||||||
log.warning(
|
self.archive_state('Bad shutdown detected so archiving state files')
|
||||||
'Potential bad shutdown of Deluge detected, archiving torrent state files...'
|
|
||||||
)
|
|
||||||
arc_filepaths = []
|
|
||||||
for filename in ('torrents.fastresume', 'torrents.state'):
|
|
||||||
filepath = os.path.join(self.state_dir, filename)
|
|
||||||
arc_filepaths.extend([filepath, filepath + '.bak'])
|
|
||||||
archive_files('torrents_state', arc_filepaths)
|
|
||||||
os.remove(self.temp_file)
|
os.remove(self.temp_file)
|
||||||
|
|
||||||
with open(self.temp_file, 'a'):
|
with open(self.temp_file, 'a'):
|
||||||
|
@ -789,6 +782,7 @@ class TorrentManager(component.Component):
|
||||||
if state.torrents:
|
if state.torrents:
|
||||||
t_state_tmp = TorrentState()
|
t_state_tmp = TorrentState()
|
||||||
if dir(state.torrents[0]) != dir(t_state_tmp):
|
if dir(state.torrents[0]) != dir(t_state_tmp):
|
||||||
|
self.archive_state('Migration of TorrentState required.')
|
||||||
try:
|
try:
|
||||||
for attr in set(dir(t_state_tmp)) - set(dir(state.torrents[0])):
|
for attr in set(dir(t_state_tmp)) - set(dir(state.torrents[0])):
|
||||||
for t_state in state.torrents:
|
for t_state in state.torrents:
|
||||||
|
@ -807,21 +801,25 @@ class TorrentManager(component.Component):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
torrents_state = os.path.join(self.state_dir, 'torrents.state')
|
torrents_state = os.path.join(self.state_dir, 'torrents.state')
|
||||||
|
state = None
|
||||||
for filepath in (torrents_state, torrents_state + '.bak'):
|
for filepath in (torrents_state, torrents_state + '.bak'):
|
||||||
log.info('Loading torrent state: %s', filepath)
|
log.info('Loading torrent state: %s', filepath)
|
||||||
|
if not os.path.isfile(filepath):
|
||||||
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(filepath, 'rb') as _file:
|
with open(filepath, 'rb') as _file:
|
||||||
state = pickle.load(_file)
|
state = pickle.load(_file)
|
||||||
except (IOError, EOFError, pickle.UnpicklingError) as ex:
|
except (IOError, EOFError, pickle.UnpicklingError) as ex:
|
||||||
log.warning('Unable to load %s: %s', filepath, ex)
|
message = 'Unable to load {}: {}'.format(filepath, ex)
|
||||||
state = None
|
log.error(message)
|
||||||
|
if not filepath.endswith('.bak'):
|
||||||
|
self.archive_state(message)
|
||||||
else:
|
else:
|
||||||
log.info('Successfully loaded %s', filepath)
|
log.info('Successfully loaded %s', filepath)
|
||||||
break
|
break
|
||||||
|
|
||||||
if state is None:
|
return state if state else TorrentManagerState()
|
||||||
state = TorrentManagerState()
|
|
||||||
return state
|
|
||||||
|
|
||||||
def load_state(self):
|
def load_state(self):
|
||||||
"""Load all the torrents from TorrentManager state into session.
|
"""Load all the torrents from TorrentManager state into session.
|
||||||
|
@ -1157,6 +1155,15 @@ class TorrentManager(component.Component):
|
||||||
os.close(dirfd)
|
os.close(dirfd)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def archive_state(self, message):
|
||||||
|
log.warning(message)
|
||||||
|
arc_filepaths = []
|
||||||
|
for filename in ('torrents.fastresume', 'torrents.state'):
|
||||||
|
filepath = os.path.join(self.state_dir, filename)
|
||||||
|
arc_filepaths.extend([filepath, filepath + '.bak'])
|
||||||
|
|
||||||
|
archive_files('state', arc_filepaths, message=message)
|
||||||
|
|
||||||
def get_queue_position(self, torrent_id):
|
def get_queue_position(self, torrent_id):
|
||||||
"""Get queue position of torrent"""
|
"""Get queue position of torrent"""
|
||||||
return self.torrents[torrent_id].get_queue_position()
|
return self.torrents[torrent_id].get_queue_position()
|
||||||
|
|
|
@ -173,3 +173,30 @@ class CommonTestCase(unittest.TestCase):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
tar_info.name in [os.path.basename(arcf) for arcf in arc_filelist]
|
tar_info.name in [os.path.basename(arcf) for arcf in arc_filelist]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_archive_files_missing(self):
|
||||||
|
"""Archive exists even with file not found."""
|
||||||
|
filelist = ['test.torrent', 'deluge.png', 'missing.file']
|
||||||
|
arc_filepath = archive_files(
|
||||||
|
'test-arc', [get_test_data_file(f) for f in filelist]
|
||||||
|
)
|
||||||
|
filelist.remove('missing.file')
|
||||||
|
|
||||||
|
with tarfile.open(arc_filepath, 'r') as tar:
|
||||||
|
self.assertEqual(tar.getnames(), filelist)
|
||||||
|
self.assertTrue(all(tarinfo.isfile() for tarinfo in tar))
|
||||||
|
|
||||||
|
def test_archive_files_message(self):
|
||||||
|
filelist = ['test.torrent', 'deluge.png']
|
||||||
|
arc_filepath = archive_files(
|
||||||
|
'test-arc', [get_test_data_file(f) for f in filelist], message='test'
|
||||||
|
)
|
||||||
|
|
||||||
|
result_files = filelist + ['archive_message.txt']
|
||||||
|
with tarfile.open(arc_filepath, 'r') as tar:
|
||||||
|
self.assertEqual(tar.getnames(), result_files)
|
||||||
|
for tar_info in tar:
|
||||||
|
self.assertTrue(tar_info.isfile())
|
||||||
|
if tar_info.name == 'archive_message.txt':
|
||||||
|
result = tar.extractfile(tar_info).read().decode()
|
||||||
|
self.assertEqual(result, 'test')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue