diff --git a/setup.cfg b/setup.cfg index f1efa5f9..92ed1539 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,7 @@ long_description_content_type = text/x-rst packages = trashcli trashcli.empty + trashcli.guard trashcli.lib trashcli.list trashcli.list.minor_actions diff --git a/tests/test_empty/components/test_guard.py b/tests/test_empty/components/test_guard.py index 824f27d1..a5d44ab2 100644 --- a/tests/test_empty/components/test_guard.py +++ b/tests/test_empty/components/test_guard.py @@ -1,16 +1,16 @@ import unittest from tests.support.py2mock import Mock, call -from trashcli.empty.guard import Guard, UserIntention +from trashcli.guard.guard import Guard, UserIntention class TestGuard(unittest.TestCase): def setUp(self): - self.user = Mock(spec=['do_you_wanna_empty_trash_dirs']) + self.user = Mock(spec=['confirm']) self.guard = Guard(self.user) def test_user_says_yes(self): - self.user.do_you_wanna_empty_trash_dirs.return_value = True + self.user.confirm.return_value = True result = self.guard.ask_the_user(True, ['trash_dirs']) @@ -18,7 +18,7 @@ def test_user_says_yes(self): trash_dirs=['trash_dirs']) == result def test_user_says_no(self): - self.user.do_you_wanna_empty_trash_dirs.return_value = False + self.user.confirm.return_value = False result = self.guard.ask_the_user(True, ['trash_dirs']) diff --git a/tests/test_empty/components/test_parse_reply.py b/tests/test_empty/components/test_parse_reply.py index 6c0a8653..8b3d970d 100644 --- a/tests/test_empty/components/test_parse_reply.py +++ b/tests/test_empty/components/test_parse_reply.py @@ -1,6 +1,6 @@ import unittest -from trashcli.empty.parse_reply import parse_reply +from trashcli.guard.parse_reply import parse_reply class TestParseReply(unittest.TestCase): diff --git a/tests/test_empty/components/test_user.py b/tests/test_empty/components/test_user.py index 4558ff36..4df81e58 100644 --- a/tests/test_empty/components/test_user.py +++ b/tests/test_empty/components/test_user.py @@ -2,7 +2,7 @@ from tests.support.py2mock import Mock, call -from trashcli.empty.user import User +from trashcli.guard.user import User from trashcli.lib.my_input import HardCodedInput @@ -18,7 +18,7 @@ def test(self): self.parse_reply.return_value = 'result' self.input.set_reply('reply') - result = self.user.do_you_wanna_empty_trash_dirs(['trash_dirs']) + result = self.user.confirm(['trash_dirs']) assert [ result, diff --git a/trashcli/empty/empty_action.py b/trashcli/empty/empty_action.py index c852f70b..5d533e84 100644 --- a/trashcli/empty/empty_action.py +++ b/trashcli/empty/empty_action.py @@ -7,10 +7,10 @@ ) from trashcli.empty.emptier import Emptier from trashcli.empty.existing_file_remover import ExistingFileRemover -from trashcli.empty.guard import Guard -from trashcli.empty.parse_reply import parse_reply +from trashcli.guard.guard import Guard +from trashcli.guard.parse_reply import parse_reply from trashcli.empty.prepare_output_message import prepare_output_message -from trashcli.empty.user import User +from trashcli.guard.user import User from trashcli.fs import ContentsOf from trashcli.fstab.volume_listing import VolumesListing from trashcli.fstab.volume_of import VolumeOf @@ -65,8 +65,12 @@ def run_action(self, args.user_specified_trash_dirs, args.environ, args.uid) - delete_pass = self.guard.ask_the_user(args.interactive, - trash_dirs) - if delete_pass.ok_to_empty: - self.emptier.do_empty(delete_pass.trash_dirs, args.environ, - args.days, args.dry_run, args.verbose) + trash_dirs = list(trash_dirs) + if trash_dirs: # skip asking the user if there is nothing to delete; avoids being stuck + delete_pass = self.guard.ask_the_user(args.interactive, + trash_dirs) + if delete_pass.ok_to_empty: + self.emptier.do_empty(delete_pass.trash_dirs, args.environ, + args.days, args.dry_run, args.verbose) + else: + print('No trash directories to empty.') diff --git a/trashcli/empty/empty_cmd.py b/trashcli/empty/empty_cmd.py index 483a3a25..d9803155 100644 --- a/trashcli/empty/empty_cmd.py +++ b/trashcli/empty/empty_cmd.py @@ -7,7 +7,7 @@ from trashcli.empty.empty_action import EmptyAction, EmptyActionArgs from trashcli.empty.errors import Errors from trashcli.empty.existing_file_remover import ExistingFileRemover -from trashcli.empty.is_input_interactive import is_input_interactive +from trashcli.guard.is_input_interactive import is_input_interactive from trashcli.empty.parser import Parser from trashcli.empty.print_time_action import PrintTimeAction, PrintTimeArgs from trashcli.fs import ContentsOf diff --git a/trashcli/guard/__init__.py b/trashcli/guard/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/trashcli/guard/__init__.py @@ -0,0 +1 @@ + diff --git a/trashcli/empty/guard.py b/trashcli/guard/guard.py similarity index 92% rename from trashcli/empty/guard.py rename to trashcli/guard/guard.py index baa0534f..162a8cc8 100644 --- a/trashcli/empty/guard.py +++ b/trashcli/guard/guard.py @@ -1,6 +1,6 @@ from typing import Iterable, NamedTuple -from trashcli.empty.user import User +from trashcli.guard.user import User from trashcli.trash_dirs_scanner import TrashDir UserIntention = NamedTuple('UserIntention', @@ -26,7 +26,7 @@ def _interactive(self, trash_dirs, # type: Iterable[TrashDir] ): # type: (...) -> UserIntention trash_dirs_list = list(trash_dirs) # type: Iterable[TrashDir] ok_to_empty = \ - self.user.do_you_wanna_empty_trash_dirs(trash_dirs_list) + self.user.confirm(trash_dirs_list) list_result = trash_dirs_list if ok_to_empty else [] return UserIntention(ok_to_empty=ok_to_empty, trash_dirs=list_result) diff --git a/trashcli/empty/is_input_interactive.py b/trashcli/guard/is_input_interactive.py similarity index 100% rename from trashcli/empty/is_input_interactive.py rename to trashcli/guard/is_input_interactive.py diff --git a/trashcli/empty/parse_reply.py b/trashcli/guard/parse_reply.py similarity index 100% rename from trashcli/empty/parse_reply.py rename to trashcli/guard/parse_reply.py diff --git a/trashcli/empty/user.py b/trashcli/guard/user.py similarity index 85% rename from trashcli/empty/user.py rename to trashcli/guard/user.py index c76c3ccb..bf8c8ec9 100644 --- a/trashcli/empty/user.py +++ b/trashcli/guard/user.py @@ -10,6 +10,6 @@ def __init__(self, self.input = input self.parse_reply = parse_reply - def do_you_wanna_empty_trash_dirs(self, trash_dirs): - reply = self.input.read_input(self.prepare_output_message(trash_dirs)) + def confirm(self, items_to_confirm): + reply = self.input.read_input(self.prepare_output_message(items_to_confirm)) return self.parse_reply(reply) diff --git a/trashcli/rm/prepare_output_message.py b/trashcli/rm/prepare_output_message.py new file mode 100644 index 00000000..dba9bbef --- /dev/null +++ b/trashcli/rm/prepare_output_message.py @@ -0,0 +1,6 @@ +def prepare_output_message(files_to_remove): + if not files_to_remove: + return 'No files to be removed.' + else: + return 'The following files / directories will be removed:\n' + '\n'.join( + ' - ' + file[0] for file in files_to_remove) + '\nProceed? (y/N) ' diff --git a/trashcli/rm/rm_cmd.py b/trashcli/rm/rm_cmd.py index 8c4f9ca2..a55997a6 100644 --- a/trashcli/rm/rm_cmd.py +++ b/trashcli/rm/rm_cmd.py @@ -2,13 +2,19 @@ from trashcli.compat import Protocol from trashcli.fs import ContentsOf +from trashcli.guard.guard import Guard +from trashcli.guard.is_input_interactive import is_input_interactive +from trashcli.guard.parse_reply import parse_reply +from trashcli.guard.user import User from trashcli.lib.dir_checker import DirChecker from trashcli.lib.dir_reader import DirReader +from trashcli.lib.my_input import RealInput from trashcli.lib.user_info import SingleUserInfoProvider from trashcli.rm.cleanable_trashcan import CleanableTrashcan from trashcli.rm.file_remover import FileRemover from trashcli.rm.filter import Filter from trashcli.rm.list_trashinfo import ListTrashinfos +from trashcli.rm.prepare_output_message import prepare_output_message from trashcli.trash_dirs_scanner import TrashDirsScanner, TopTrashDirRules, \ trash_dir_found @@ -60,6 +66,7 @@ def run(self, argv, uid): for event, args in scanner.scan_trash_dirs(self.environ, uid): if event == trash_dir_found: + files_to_remove = [] path, volume = args for type, arg in listing.list_from_volume_trashdir(path, volume): @@ -68,8 +75,15 @@ def run(self, argv, uid): elif type == 'trashed_file': original_location, info_file = arg if cmd.matches(original_location): - trashcan.delete_trash_info_and_backup_copy( - info_file) + files_to_remove.append(arg) + if files_to_remove: # skip asking the user if there is nothing to delete; avoids being stuck + user = User(prepare_output_message, RealInput(), parse_reply) + guard = Guard(user) + if guard.ask_the_user(is_input_interactive(), files_to_remove).ok_to_empty: + for file in files_to_remove: + trashcan.delete_trash_info_and_backup_copy(file[1]) + else: + print('No files / directories to be removed in {}'.format(path)) def unable_to_parse_path(self, trashinfo): self.report_error('{}: unable to parse \'Path\''.format(trashinfo))