Update folder rename to use deferreds.

This commit is contained in:
Chase Sterling 2012-12-07 17:17:25 -05:00
commit 25efa5437b
2 changed files with 66 additions and 62 deletions

View file

@ -37,9 +37,12 @@
import os import os
import time import time
import logging import logging
import re
from urllib import unquote from urllib import unquote
from urlparse import urlparse from urlparse import urlparse
from twisted.internet.defer import Deferred, DeferredList
from twisted.internet.task import LoopingCall
from deluge._libtorrent import lt from deluge._libtorrent import lt
import deluge.common import deluge.common
@ -116,7 +119,6 @@ class Torrent(object):
# We use this to return dicts that only contain changes from the previous # We use this to return dicts that only contain changes from the previous
# {session_id: status_dict, ...} # {session_id: status_dict, ...}
self.prev_status = {} self.prev_status = {}
from twisted.internet.task import LoopingCall
self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status) self.prev_status_cleanup_loop = LoopingCall(self.cleanup_prev_status)
self.prev_status_cleanup_loop.start(10) self.prev_status_cleanup_loop.start(10)
@ -125,11 +127,10 @@ class Torrent(object):
# Set the torrent_id for this torrent # Set the torrent_id for this torrent
self.torrent_id = str(handle.info_hash()) self.torrent_id = str(handle.info_hash())
# Keep a list of file indexes we're waiting for file_rename alerts on # Keep a list of Deferreds for file indexes we're waiting for file_rename alerts on
# This also includes the old_folder and new_folder to know what signal to send
# This is so we can send one folder_renamed signal instead of multiple # This is so we can send one folder_renamed signal instead of multiple
# file_renamed signals. # file_renamed signals.
# [(old_folder, new_folder, [*indexes]), ...] # [{index: Deferred, ...}, ...]
self.waiting_on_folder_rename = [] self.waiting_on_folder_rename = []
# We store the filename just in case we need to make a copy of the torrentfile # We store the filename just in case we need to make a copy of the torrentfile
@ -989,8 +990,13 @@ class Torrent(object):
self.handle.rename_file(index, filename.encode("utf-8")) self.handle.rename_file(index, filename.encode("utf-8"))
def rename_folder(self, folder, new_folder): def rename_folder(self, folder, new_folder):
"""Renames a folder within a torrent. This basically does a file rename """
on all of the folders children.""" Renames a folder within a torrent. This basically does a file rename
on all of the folders children.
:returns: A deferred which fires when the rename is complete
:rtype: twisted.internet.defer.Deferred
"""
log.debug("attempting to rename folder: %s to %s", folder, new_folder) log.debug("attempting to rename folder: %s to %s", folder, new_folder)
if len(new_folder) < 1: if len(new_folder) < 1:
log.error("Attempting to rename a folder with an invalid folder name: %s", new_folder) log.error("Attempting to rename a folder with an invalid folder name: %s", new_folder)
@ -998,13 +1004,57 @@ class Torrent(object):
new_folder = sanitize_filepath(new_folder, folder=True) new_folder = sanitize_filepath(new_folder, folder=True)
wait_on_folder = (folder, new_folder, []) def on_file_rename_complete(result, wait_dict, index):
wait_dict.pop(index, None)
wait_on_folder = {}
self.waiting_on_folder_rename.append(wait_on_folder)
for f in self.get_files(): for f in self.get_files():
if f["path"].startswith(folder): if f["path"].startswith(folder):
# Keep a list of filerenames we're waiting on # Keep track of filerenames we're waiting on
wait_on_folder[2].append(f["index"]) wait_on_folder[f["index"]] = Deferred().addBoth(on_file_rename_complete, wait_on_folder, f["index"])
self.handle.rename_file(f["index"], f["path"].replace(folder, new_folder, 1).encode("utf-8")) self.handle.rename_file(f["index"], f["path"].replace(folder, new_folder, 1).encode("utf-8"))
self.waiting_on_folder_rename.append(wait_on_folder)
def on_folder_rename_complete(result, torrent, folder, new_folder):
component.get("EventManager").emit(TorrentFolderRenamedEvent(torrent.torrent_id, folder, new_folder))
# Empty folders are removed after libtorrent folder renames
self.remove_empty_folders(folder)
torrent.waiting_on_folder_rename = filter(None, torrent.waiting_on_folder_rename)
component.get("TorrentManager").save_resume_data((self.torrent_id,))
d = DeferredList(wait_on_folder.values())
d.addBoth(on_folder_rename_complete, self, folder, new_folder)
return d
def remove_empty_folders(self, folder):
"""
Recursively removes folders but only if they are empty.
Cleans up after libtorrent folder renames.
"""
info = self.get_status(['save_path'])
# Regex removes leading slashes that causes join function to ignore save_path
folder_full_path = os.path.join(info['save_path'], re.sub("^/*", "", folder))
folder_full_path = os.path.normpath(folder_full_path)
try:
if not os.listdir(folder_full_path):
os.removedirs(folder_full_path)
log.debug("Removed Empty Folder %s", folder_full_path)
else:
for root, dirs, files in os.walk(folder_full_path, topdown=False):
for name in dirs:
try:
os.removedirs(os.path.join(root, name))
log.debug("Removed Empty Folder %s", os.path.join(root, name))
except OSError as (errno, strerror):
from errno import ENOTEMPTY
if errno == ENOTEMPTY:
# Error raised if folder is not empty
log.debug("%s", strerror)
except OSError as (errno, strerror):
log.debug("Cannot Remove Folder: %s (ErrNo %s)", strerror, errno)
def cleanup_prev_status(self): def cleanup_prev_status(self):
""" """

View file

@ -41,7 +41,6 @@ import os
import shutil import shutil
import operator import operator
import logging import logging
import re
from collections import defaultdict from collections import defaultdict
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
@ -812,39 +811,6 @@ class TorrentManager(component.Component):
except IOError: except IOError:
log.warning("Error trying to save fastresume file") log.warning("Error trying to save fastresume file")
def remove_empty_folders(self, torrent_id, folder):
"""
Recursively removes folders but only if they are empty.
Cleans up after libtorrent folder renames.
"""
if torrent_id not in self.torrents:
raise InvalidTorrentError("torrent_id is not in session")
info = self.torrents[torrent_id].get_status(['save_path'])
# Regex removes leading slashes that causes join function to ignore save_path
folder_full_path = os.path.join(info['save_path'], re.sub("^/*", "", folder))
folder_full_path = os.path.normpath(folder_full_path)
try:
if not os.listdir(folder_full_path):
os.removedirs(folder_full_path)
log.debug("Removed Empty Folder %s", folder_full_path)
else:
for root, dirs, files in os.walk(folder_full_path, topdown=False):
for name in dirs:
try:
os.removedirs(os.path.join(root, name))
log.debug("Removed Empty Folder %s", os.path.join(root, name))
except OSError as (errno, strerror):
from errno import ENOTEMPTY
if errno == ENOTEMPTY:
# Error raised if folder is not empty
log.debug("%s", strerror)
except OSError as (errno, strerror):
log.debug("Cannot Remove Folder: %s (ErrNo %s)", strerror, errno)
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()
@ -1097,24 +1063,12 @@ class TorrentManager(component.Component):
except: except:
return return
# We need to see if this file index is in a waiting_on_folder list # We need to see if this file index is in a waiting_on_folder dict
folder_rename = False for wait_on_folder in torrent.waiting_on_folder_rename:
for i, wait_on_folder in enumerate(torrent.waiting_on_folder_rename): if alert.index in wait_on_folder:
if alert.index in wait_on_folder[2]: wait_on_folder[alert.index].callback(None)
folder_rename = True break
if len(wait_on_folder[2]) == 1: else:
# This is the last alert we were waiting for, time to send signal
component.get("EventManager").emit(TorrentFolderRenamedEvent(torrent_id, wait_on_folder[0], wait_on_folder[1]))
# Empty folders are removed after libtorrent folder renames
self.remove_empty_folders(torrent_id, wait_on_folder[0])
del torrent.waiting_on_folder_rename[i]
self.save_resume_data((torrent_id,))
break
# This isn't the last file to be renamed in this folder, so just
# remove the index and continue
torrent.waiting_on_folder_rename[i][2].remove(alert.index)
if not folder_rename:
# This is just a regular file rename so send the signal # This is just a regular file rename so send the signal
component.get("EventManager").emit(TorrentFileRenamedEvent(torrent_id, alert.index, alert.name)) component.get("EventManager").emit(TorrentFileRenamedEvent(torrent_id, alert.index, alert.name))
self.save_resume_data((torrent_id,)) self.save_resume_data((torrent_id,))