[#2250] [Core] [GTKUI] Added method remove_torrents to core

Removing multiple torrents with remove_torrents will be faster
than calling remove_torrent for each torrent, as the state file
will be written only once, after all the torrents are deleted.
This commit is contained in:
bendikro 2014-12-01 16:00:26 +01:00 committed by Calum Lind
commit 08363f28dd
5 changed files with 151 additions and 38 deletions

View file

@ -326,22 +326,48 @@ class Core(component.Component):
@export @export
def remove_torrent(self, torrent_id, remove_data): def remove_torrent(self, torrent_id, remove_data):
""" """Removes a single torrent from the session.
Removes a torrent from the session.
:param torrent_id: the torrent_id of the torrent to remove Args:
:type torrent_id: string torrent_id (str): The torrent ID to remove.
:param remove_data: if True, remove the data associated with this torrent remove_data (bool): If True, also remove the downloaded data.
:type remove_data: boolean
:returns: True if removed successfully
:rtype: bool
:raises InvalidTorrentError: if the torrent_id does not exist in the session Returns:
bool: True if removed successfully.
Raises:
InvalidTorrentError: If the torrent ID does not exist in the session.
""" """
log.debug("Removing torrent %s from the core.", torrent_id) log.debug("Removing torrent %s from the core.", torrent_id)
return self.torrentmanager.remove(torrent_id, remove_data) return self.torrentmanager.remove(torrent_id, remove_data)
@export
def remove_torrents(self, torrent_ids, remove_data):
"""Remove multiple torrents from the session.
Args:
torrent_ids (list): The torrent IDs to remove.
remove_data (bool, optional): If True, also remove the downloaded data.
Returns:
list: a list containing all the errors, empty list if no errors occured
"""
log.info("Removing %d torrents from core.", len(torrent_ids))
def do_remove_torrents():
errors = []
for torrent_id in torrent_ids:
try:
self.torrentmanager.remove(torrent_id, remove_data=remove_data, save_state=False)
except InvalidTorrentError, ite:
errors.append((torrent_id, ite))
# Save the session state
self.torrentmanager.save_state()
return errors
return task.deferLater(reactor, 0, do_remove_torrents)
@export @export
def get_session_status(self, keys): def get_session_status(self, keys):
""" """

View file

@ -523,12 +523,13 @@ class TorrentManager(component.Component):
from_state and "loaded" or "added") from_state and "loaded" or "added")
return torrent.torrent_id return torrent.torrent_id
def remove(self, torrent_id, remove_data=False): def remove(self, torrent_id, remove_data=False, save_state=True):
"""Remove specified torrent from the session. """Remove a torrent from the session.
Args: Args:
torrent_id (str): The torrent to remove. torrent_id (str): The torrent ID to remove.
remove_data (bool, optional): If True, remove the downloaded data, defaults to False. remove_data (bool, optional): If True, remove the downloaded data, defaults to False.
save_state (bool, optional): If True, save the session state after removal, defaults to True.
Returns: Returns:
bool: True if removed successfully, False if not. bool: True if removed successfully, False if not.
@ -538,16 +539,16 @@ class TorrentManager(component.Component):
""" """
try: try:
torrent_name = self.torrents[torrent_id].get_status(["name"])["name"] torrent = self.torrents[torrent_id]
except KeyError: except KeyError:
raise InvalidTorrentError("torrent_id not in session") raise InvalidTorrentError("torrent_id '%s' not in session." % torrent_id)
# Emit the signal to the clients # Emit the signal to the clients
component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id)) component.get("EventManager").emit(PreTorrentRemovedEvent(torrent_id))
try: try:
self.session.remove_torrent(self.torrents[torrent_id].handle, 1 if remove_data else 0) self.session.remove_torrent(torrent.handle, 1 if remove_data else 0)
except (RuntimeError, KeyError) as ex: except RuntimeError as ex:
log.warning("Error removing torrent: %s", ex) log.warning("Error removing torrent: %s", ex)
return False return False
@ -555,10 +556,11 @@ class TorrentManager(component.Component):
self.resume_data.pop(torrent_id, None) self.resume_data.pop(torrent_id, None)
# Remove the .torrent file in the state # Remove the .torrent file in the state
self.torrents[torrent_id].delete_torrentfile() torrent.delete_torrentfile()
# Remove the torrent file from the user specified directory # Remove the torrent file from the user specified directory
filename = self.torrents[torrent_id].filename torrent_name = torrent.get_status(["name"])["name"]
filename = torrent.filename
if self.config["copy_torrent_file"] and self.config["del_copy_torrent_file"] and filename: if self.config["copy_torrent_file"] and self.config["del_copy_torrent_file"] and filename:
users_torrent_file = os.path.join(self.config["torrentfiles_location"], filename) users_torrent_file = os.path.join(self.config["torrentfiles_location"], filename)
log.info("Delete user's torrent file: %s", users_torrent_file) log.info("Delete user's torrent file: %s", users_torrent_file)
@ -568,25 +570,22 @@ class TorrentManager(component.Component):
log.warning("Unable to remove copy torrent file: %s", ex) log.warning("Unable to remove copy torrent file: %s", ex)
# Remove from set if it wasn't finished # Remove from set if it wasn't finished
if not self.torrents[torrent_id].is_finished: if not torrent.is_finished:
try: try:
self.queued_torrents.remove(torrent_id) self.queued_torrents.remove(torrent_id)
except KeyError: except KeyError:
log.debug("%s isn't in queued torrents set?", torrent_id) log.debug("%s isn't in queued torrents set?", torrent_id)
raise InvalidTorrentError("%s isn't in queued torrents set?" % torrent_id)
# Remove the torrent from deluge's session # Remove the torrent from deluge's session
try: del self.torrents[torrent_id]
del self.torrents[torrent_id]
except (KeyError, ValueError):
return False
# Save the session state if save_state:
self.save_state() self.save_state()
# Emit the signal to the clients # Emit the signal to the clients
component.get("EventManager").emit(TorrentRemovedEvent(torrent_id)) component.get("EventManager").emit(TorrentRemovedEvent(torrent_id))
log.info("Torrent %s removed by user: %s", torrent_name, log.info("Torrent %s removed by user: %s", torrent_name, component.get("RPCServer").get_session_user())
component.get("RPCServer").get_session_user())
return True return True
def load_state(self): def load_state(self):

View file

@ -1,3 +1,4 @@
import base64
import os import os
from hashlib import sha1 as sha from hashlib import sha1 as sha
@ -11,9 +12,10 @@ from twisted.web.server import Site
from twisted.web.static import File from twisted.web.static import File
import deluge.component as component import deluge.component as component
import deluge.error import deluge.core.torrent
from deluge.core.core import Core from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer from deluge.core.rpcserver import RPCServer
from deluge.error import InvalidTorrentError
from deluge.ui.web.common import compress from deluge.ui.web.common import compress
from . import common from . import common
@ -104,7 +106,6 @@ class CoreTestCase(BaseTestCase):
def test_add_torrent_file(self): def test_add_torrent_file(self):
options = {} options = {}
filename = os.path.join(os.path.dirname(__file__), "test.torrent") filename = os.path.join(os.path.dirname(__file__), "test.torrent")
import base64
torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), options) torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), options)
# Get the info hash from the test.torrent # Get the info hash from the test.torrent
@ -168,16 +169,53 @@ class CoreTestCase(BaseTestCase):
def test_remove_torrent(self): def test_remove_torrent(self):
options = {} options = {}
filename = os.path.join(os.path.dirname(__file__), "test.torrent") filename = os.path.join(os.path.dirname(__file__), "test.torrent")
import base64
torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), options) torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), options)
removed = self.core.remove_torrent(torrent_id, True)
self.assertRaises(deluge.error.InvalidTorrentError, self.core.remove_torrent, "torrentidthatdoesntexist", True) self.assertTrue(removed)
ret = self.core.remove_torrent(torrent_id, True)
self.assertTrue(ret)
self.assertEquals(len(self.core.get_session_state()), 0) self.assertEquals(len(self.core.get_session_state()), 0)
def test_remove_torrent_invalid(self):
d = self.core.remove_torrents(["torrentidthatdoesntexist"], True)
def test_true(val):
self.assertTrue(val[0][0] == "torrentidthatdoesntexist")
self.assertTrue(type(val[0][1]) == InvalidTorrentError)
d.addCallback(test_true)
return d
def test_remove_torrents(self):
options = {}
filename = os.path.join(os.path.dirname(__file__), "test.torrent")
torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), options)
filename2 = os.path.join(os.path.dirname(__file__), "unicode_filenames.torrent")
torrent_id2 = self.core.add_torrent_file(filename2, base64.encodestring(open(filename2).read()), options)
d = self.core.remove_torrents([torrent_id, torrent_id2], True)
def test_ret(val):
self.assertTrue(val == [])
d.addCallback(test_ret)
def test_session_state(val):
self.assertEquals(len(self.core.get_session_state()), 0)
d.addCallback(test_session_state)
return d
def test_remove_torrents_invalid(self):
options = {}
filename = os.path.join(os.path.dirname(__file__), "test.torrent")
torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), options)
d = self.core.remove_torrents(["invalidid1", "invalidid2", torrent_id], False)
def test_ret(val):
self.assertTrue(len(val) == 2)
self.assertTrue(val[0][0] == "invalidid1")
self.assertTrue(type(val[0][1]) == InvalidTorrentError)
self.assertTrue(val[1][0] == "invalidid2")
self.assertTrue(type(val[1][1]) == InvalidTorrentError)
d.addCallback(test_ret)
return d
def test_get_session_status(self): def test_get_session_status(self):
status = self.core.get_session_status(["upload_rate", "download_rate"]) status = self.core.get_session_status(["upload_rate", "download_rate"])
self.assertEquals(type(status), dict) self.assertEquals(type(status), dict)

View file

@ -0,0 +1,44 @@
import base64
import os
import warnings
from twisted.trial import unittest
import common
from deluge import component
from deluge.core.core import Core
from deluge.core.rpcserver import RPCServer
from deluge.error import InvalidTorrentError
warnings.filterwarnings("ignore", category=RuntimeWarning)
warnings.resetwarnings()
class TorrentmanagerTestCase(unittest.TestCase):
def setUp(self): # NOQA
common.set_tmp_config_dir()
self.rpcserver = RPCServer(listen=False)
self.core = Core()
self.torrentManager = self.core.torrentmanager
return component.start()
def tearDown(self): # NOQA
def on_shutdown(result):
component._ComponentRegistry.components = {}
del self.rpcserver
del self.core
del self.torrentManager
return component.shutdown().addCallback(on_shutdown)
def test_remove_torrent(self):
filename = os.path.join(os.path.dirname(__file__), "test.torrent")
torrent_id = self.core.add_torrent_file(filename, base64.encodestring(open(filename).read()), {})
self.assertTrue(self.torrentManager.remove(torrent_id, False))
def test_remove_torrent_false(self):
"""Test when remove_torrent returns False"""
raise unittest.SkipTest("")
def test_remove_invalid_torrent(self):
self.assertRaises(InvalidTorrentError, self.torrentManager.remove, "torrentidthatdoesntexist")

View file

@ -67,8 +67,14 @@ class RemoveTorrentDialog(object):
# Unselect all to avoid issues with the selection changed event # Unselect all to avoid issues with the selection changed event
component.get("TorrentView").treeview.get_selection().unselect_all() component.get("TorrentView").treeview.get_selection().unselect_all()
for torrent_id in self.__torrent_ids: def on_removed_finished(errors):
client.core.remove_torrent(torrent_id, remove_data) if errors:
log.info("Error(s) occured when trying to delete torrent(s).")
for t_id, e_msg in errors:
log.warn("Error removing torrent %s : %s" % (t_id, e_msg))
d = client.core.remove_torrents(self.__torrent_ids, remove_data)
d.addCallback(on_removed_finished)
def run(self): def run(self):
""" """