mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-08 09:28:41 +00:00
[Core] Backport atomic fastresume and state file saving fixes
* On Windows using shutil.move is not atomic and could account for corruption on power loss.
* Using file saving code from develop branch including latest changes:
7414737cbf
This commit is contained in:
parent
8e3d737adc
commit
41ac46c7fe
1 changed files with 65 additions and 39 deletions
|
@ -628,16 +628,19 @@ class TorrentManager(component.Component):
|
||||||
|
|
||||||
def load_state(self):
|
def load_state(self):
|
||||||
"""Load the state of the TorrentManager from the torrents.state file"""
|
"""Load the state of the TorrentManager from the torrents.state file"""
|
||||||
state = TorrentManagerState()
|
filepath = os.path.join(get_config_dir(), "state", "torrents.state")
|
||||||
|
log.debug("Opening torrent state file for load.")
|
||||||
try:
|
for _filepath in (filepath, filepath + ".bak"):
|
||||||
log.debug("Opening torrent state file for load.")
|
try:
|
||||||
state_file = open(
|
state_file = open(_filepath, "rb")
|
||||||
os.path.join(get_config_dir(), "state", "torrents.state"), "rb")
|
state = cPickle.load(state_file)
|
||||||
state = cPickle.load(state_file)
|
state_file.close()
|
||||||
state_file.close()
|
except (EOFError, IOError, Exception, cPickle.UnpicklingError), e:
|
||||||
except (EOFError, IOError, Exception, cPickle.UnpicklingError), e:
|
log.warning("Unable to load state file: %s", e)
|
||||||
log.warning("Unable to load state file: %s", e)
|
state = TorrentManagerState()
|
||||||
|
else:
|
||||||
|
log.info("Successfully loaded state file: %s", _filepath)
|
||||||
|
break
|
||||||
|
|
||||||
# Try to use an old state
|
# Try to use an old state
|
||||||
try:
|
try:
|
||||||
|
@ -703,26 +706,32 @@ class TorrentManager(component.Component):
|
||||||
state.torrents.append(torrent_state)
|
state.torrents.append(torrent_state)
|
||||||
|
|
||||||
# Pickle the TorrentManagerState object
|
# Pickle the TorrentManagerState object
|
||||||
|
filepath = os.path.join(get_config_dir(), "state", "torrents.state")
|
||||||
|
filepath_tmp = filepath + ".tmp"
|
||||||
|
filepath_bak = filepath + ".bak"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.debug("Saving torrent state file.")
|
os.remove(filepath_bak)
|
||||||
state_file = open(os.path.join(get_config_dir(),
|
except OSError:
|
||||||
"state", "torrents.state.new"), "wb")
|
pass
|
||||||
|
try:
|
||||||
|
log.debug("Creating backup of state at: %s", filepath_bak)
|
||||||
|
os.rename(filepath, filepath_bak)
|
||||||
|
except OSError, ex:
|
||||||
|
log.error("Unable to backup %s to %s: %s", filepath, filepath_bak, ex)
|
||||||
|
try:
|
||||||
|
log.info("Saving the state at: %s", filepath)
|
||||||
|
state_file = open(filepath_tmp, "wb", 0)
|
||||||
cPickle.dump(state, state_file)
|
cPickle.dump(state, state_file)
|
||||||
state_file.flush()
|
state_file.flush()
|
||||||
os.fsync(state_file.fileno())
|
os.fsync(state_file.fileno())
|
||||||
state_file.close()
|
state_file.close()
|
||||||
except IOError, e:
|
os.rename(filepath_tmp, filepath)
|
||||||
log.warning("Unable to save state file: %s", e)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# We have to move the 'torrents.state.new' file to 'torrents.state'
|
|
||||||
try:
|
|
||||||
shutil.move(
|
|
||||||
os.path.join(get_config_dir(), "state", "torrents.state.new"),
|
|
||||||
os.path.join(get_config_dir(), "state", "torrents.state"))
|
|
||||||
except IOError:
|
except IOError:
|
||||||
log.warning("Unable to save state file.")
|
log.error("Unable to save %s: %s", filepath, ex)
|
||||||
return True
|
if os.path.isfile(filepath_bak):
|
||||||
|
log.info("Restoring backup of state from: %s", filepath_bak)
|
||||||
|
os.rename(filepath_bak, filepath)
|
||||||
|
|
||||||
# We return True so that the timer thread will continue
|
# We return True so that the timer thread will continue
|
||||||
return True
|
return True
|
||||||
|
@ -742,15 +751,19 @@ class TorrentManager(component.Component):
|
||||||
self.num_resume_data = len(torrent_ids)
|
self.num_resume_data = len(torrent_ids)
|
||||||
|
|
||||||
def load_resume_data_file(self):
|
def load_resume_data_file(self):
|
||||||
resume_data = {}
|
filepath = os.path.join(get_config_dir(), "state", "torrents.fastresume")
|
||||||
try:
|
log.debug("Opening torrents fastresume file for load.")
|
||||||
log.debug("Opening torrents fastresume file for load.")
|
for _filepath in (filepath, filepath + ".bak"):
|
||||||
fastresume_file = open(os.path.join(get_config_dir(), "state",
|
try:
|
||||||
"torrents.fastresume"), "rb")
|
fastresume_file = open(_filepath, "rb")
|
||||||
resume_data = lt.bdecode(fastresume_file.read())
|
resume_data = lt.bdecode(fastresume_file.read())
|
||||||
fastresume_file.close()
|
fastresume_file.close()
|
||||||
except (EOFError, IOError, Exception), e:
|
except (EOFError, IOError, Exception), e:
|
||||||
log.warning("Unable to load fastresume file: %s", e)
|
log.warning("Unable to load fastresume file: %s", e)
|
||||||
|
resume_data = None
|
||||||
|
else:
|
||||||
|
log.info("Successfully loaded fastresume file: %s", _filepath)
|
||||||
|
break
|
||||||
|
|
||||||
# If the libtorrent bdecode doesn't happen properly, it will return None
|
# If the libtorrent bdecode doesn't happen properly, it will return None
|
||||||
# so we need to make sure we return a {}
|
# so we need to make sure we return a {}
|
||||||
|
@ -774,8 +787,9 @@ class TorrentManager(component.Component):
|
||||||
if self.num_resume_data or not self.resume_data:
|
if self.num_resume_data or not self.resume_data:
|
||||||
return
|
return
|
||||||
|
|
||||||
path = os.path.join(get_config_dir(), "state", "torrents.fastresume")
|
filepath = os.path.join(get_config_dir(), "state", "torrents.fastresume")
|
||||||
path_tmp = path + ".tmp"
|
filepath_tmp = filepath + ".tmp"
|
||||||
|
filepath_bak = filepath + ".bak"
|
||||||
|
|
||||||
# First step is to load the existing file and update the dictionary
|
# First step is to load the existing file and update the dictionary
|
||||||
if resume_data is None:
|
if resume_data is None:
|
||||||
|
@ -785,15 +799,27 @@ class TorrentManager(component.Component):
|
||||||
self.resume_data = {}
|
self.resume_data = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.debug("Saving fastresume file: %s", path)
|
os.remove(filepath_bak)
|
||||||
fastresume_file = open(path_tmp, "wb")
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
log.debug("Creating backup of fastresume at: %s", filepath_bak)
|
||||||
|
os.rename(filepath, filepath_bak)
|
||||||
|
except OSError, ex:
|
||||||
|
log.error("Unable to backup %s to %s: %s", filepath, filepath_bak, ex)
|
||||||
|
try:
|
||||||
|
log.info("Saving the fastresume at: %s", filepath)
|
||||||
|
fastresume_file = open(filepath_tmp, "wb", 0)
|
||||||
fastresume_file.write(lt.bencode(resume_data))
|
fastresume_file.write(lt.bencode(resume_data))
|
||||||
fastresume_file.flush()
|
fastresume_file.flush()
|
||||||
os.fsync(fastresume_file.fileno())
|
os.fsync(fastresume_file.fileno())
|
||||||
fastresume_file.close()
|
fastresume_file.close()
|
||||||
shutil.move(path_tmp, path)
|
os.rename(filepath_tmp, filepath)
|
||||||
except IOError:
|
except IOError:
|
||||||
log.warning("Error trying to save fastresume file")
|
log.error("Unable to save %s: %s", filepath, ex)
|
||||||
|
if os.path.isfile(filepath_bak):
|
||||||
|
log.info("Restoring backup of fastresume from: %s", filepath_bak)
|
||||||
|
os.rename(filepath_bak, filepath)
|
||||||
|
|
||||||
def remove_empty_folders(self, torrent_id, folder):
|
def remove_empty_folders(self, torrent_id, folder):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue