def test_display_errors(mocker: MockerFixture, capsys): archive_path = Path("/", "test_path") fs = mocker.Mock(spec=Filesystem) client = mocker.Mock(spec=TransmissionApi) client.get_errors_by_id.return_value = QueryResult( {1: (1, "some tracker error"), 2: (3, "some local error")} ) client.get_torrent_files_by_id.return_value = QueryResult( {1: Path("/some/path"), 2: Path("/some/path2")} ) client.get_torrent_name_by_id.return_value = QueryResult( {1: "some_name", 2: "another_name"} ) command = ErrorArchiveCommand(archive_path, fs, client) output = command.run() output.display() result = capsys.readouterr().out assert ( result == "\n".join( [ "Found 1 torrent local errors:", 'another_name with error "some local error"', "Found 1 torrent tracker errors:", 'some_name with error "some tracker error"', "Moved 1 metainfo files to /test_path/tracker_error", "\x1b[32m✓ another_name", "Moved 1 metainfo files to /test_path/local_error", "\x1b[32m✓ some_name", ] ) + "\n" )
def test_error_archive_run(mocker: MockerFixture): archive_path = Path("/", "test_path") fs = mocker.Mock(spec=Filesystem) client = mocker.Mock(spec=TransmissionApi) client.get_errors_by_id.return_value = QueryResult( {1: (1, "some tracker error"), 2: (3, "some local error")} ) client.get_torrent_files_by_id.return_value = QueryResult( {1: Path("/some/path"), 2: Path("/some/path2")} ) client.get_torrent_name_by_id.return_value = QueryResult( {1: "some_name", 2: "another_name"} ) command = ErrorArchiveCommand(archive_path, fs, client) result = command.run() assert result.local_errors == { ArchiveAction( 2, "another_name", Path("/some/path2"), client_error=(3, "some local error") ) } assert result.tracker_errors == { ArchiveAction( 1, "some_name", Path("/some/path"), client_error=(1, "some tracker error") ) }
def get_metainfo_file_path(self, torrent_id: int) -> QueryResult[Path]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"torrent_file"} ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) if len(torrents) != 1: return QueryResult(error="expected only one result", success=False) return QueryResult(value=Path(torrents[0].torrent_file))
def get_torrent_files_by_id(self) -> QueryResult[Mapping[int, Path]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"id", "torrent_file"} ) if response.result != "success": QueryResult(success=False, error=response.result) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={torrent.id: Path(torrent.torrent_file) for torrent in torrents} )
def test_archive_second_query_failure(mocker: MockerFixture): archive_path = Path("/", "test_path") fs = mocker.Mock(spec=Filesystem) client = mocker.Mock(spec=TransmissionApi) client.get_torrent_files_by_id.return_value = QueryResult({1: Path("/", "file_1")}) client.get_torrent_name_by_id.return_value = QueryResult( error="some_error", success=False ) command = ArchiveCommand(archive_path, fs, client) result: ArchiveOutput = command.run() assert result.query_failure == "query failed: get_torrent_name_by_id"
def test_archive_success(mocker: MockerFixture): archive_path = Path("/", "test_path") fs = mocker.Mock(spec=Filesystem) client = mocker.Mock(spec=TransmissionApi) client.get_torrent_files_by_id.return_value = QueryResult({1: Path("/", "file_1")}) client.get_torrent_name_by_id.return_value = QueryResult({1: "test_name"}) command = ArchiveCommand(archive_path, fs, client) result: ArchiveOutput = command.run() assert result.copied == {ArchiveAction(1, "test_name", Path("/", "file_1"))} fs.create_dir.assert_called_once_with(Path("/", "test_path")) fs.copy.assert_called_once_with(Path("/", "file_1"), Path("/", "test_path"))
def get_torrent_ids_by_hash(self) -> QueryResult[Mapping[str, int]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"id", "hash_string"} ) if response.result != "success": return QueryResult(success=False, error=response.result) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={torrent.hash_string: torrent.id for torrent in torrents} )
def get_torrent_name_by_id(self, ids: Set[int]) -> QueryResult[Mapping[int, str]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"id", "percent_done", "name"}, ids=ids ) if response.result != "success": return QueryResult(success=False, error=response.result) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) result = {} for torrent in torrents: if torrent.id is not None and torrent.name is not None: result[torrent.id] = torrent.name return QueryResult(value=result)
def test_query_failure_output(mocker: MockerFixture, capsys): archive_path = Path("/", "test_path") fs = MockFilesystem({}) client = mocker.Mock(spec=TransmissionApi) client.get_torrent_files_by_id.return_value = QueryResult( error="some_error", success=False ) client.get_torrent_name_by_id.return_value = QueryResult({1: "test_name"}) command = ArchiveCommand(archive_path, fs, client) output: ArchiveOutput = command.run() output.display() result = capsys.readouterr().out assert result == "Query failed: get_torrent_files_by_id\n"
def get_incomplete_ids(self) -> QueryResult[Set[int]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={ "id", "percent_done", "error", "error_string", "is_finished", "left_until_done", } ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) def is_missing_data_error(error: int, error_string: str): return error == 3 and error_string.startswith("No data found!") return QueryResult( value={ torrent.id for torrent in torrents if torrent.percent_done == 0.0 or is_missing_data_error(torrent.error, torrent.error_string) } )
def test_dry_run_display(mocker: MockerFixture, capsys): archive_path = Path("/", "test_path") fs = mocker.Mock(spec=Filesystem) client = mocker.Mock(spec=TransmissionApi) client.get_torrent_files_by_id.return_value = QueryResult({1: Path("/", "file_1")}) client.get_torrent_name_by_id.return_value = QueryResult({1: "test_name"}) command = ArchiveCommand(archive_path, fs, client) output: ArchiveOutput = command.dry_run() output.dry_run_display() result = capsys.readouterr().out assert ( result == "\n".join(["Found 1 duplicate metainfo files", "No metainfo files to move"]) + "\n" )
def get_partial_torrents(self) -> QueryResult[Mapping[str, PartialTorrent]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"hash_string", "wanted", "files"} ) if response.result != "success": return QueryResult(success=False, error=response.result) partial_torrents: MutableMapping[str, PartialTorrent] = {} arguments = cast(TorrentAccessorResponse, response.arguments) torrents = cast(Sequence[TorrentAccessorObject], arguments.torrents) for torrent in torrents: wanted_files = {file for file in torrent.wanted} file_names = {file.name for file in torrent.files} wanted_file_names = set(itertools.compress(file_names, wanted_files)) partial_torrents[torrent.hash_string] = PartialTorrent( torrent.name, wanted_file_names ) return QueryResult(value=partial_torrents)
def get_torrent_names_by_id_with_missing_data( self, ) -> QueryResult[Mapping[int, str]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"id", "error_string", "error", "name"} ) if response.result != "success": return QueryResult(error=response.result, success=False) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) result: MutableMapping[int, str] = {} for torrent in torrents: # no data found error found in torrent.c in Transmission project if torrent.error == 3 and "No data found!" in torrent.error_string: result[torrent.id] = torrent.name return QueryResult(value=result)
def test_run_display_copy_failure(mocker: MockerFixture, capsys): archive_path = Path("/", "test_path") fs = MockFilesystem({"file_1", "test_path"}) client = mocker.Mock(spec=TransmissionApi) client.get_torrent_files_by_id.return_value = QueryResult({1: Path("/", "file_1")}) client.get_torrent_name_by_id.return_value = QueryResult({1: "test_name"}) command = ArchiveCommand(archive_path, fs, client) output: ArchiveOutput = command.run() output.display() result = capsys.readouterr().out assert ( result == "\n".join( [ "Failed to move 1 metainfo files:", "\x1b[31m✗ failed to move /file_1 because:destination is a file", ] ) + "\n" )
def get_announce_urls(self) -> QueryResult[Set[str]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"trackers"} ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={ tracker.announce for torrent in torrents for tracker in torrent.trackers } )
def get_metainfo_file_paths_by_id( self, ids: Set[int] ) -> QueryResult[Mapping[int, Path]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"id", "torrent_file", "percent_done"}, ids=ids ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={torrent.id: Path(torrent.torrent_file) for torrent in torrents} )
def get_torrent_trackers(self) -> QueryResult[Mapping[int, Set[str]]]: def get_announce_urls(torrent) -> Set[str]: return {tracker.announce for tracker in torrent.trackers} response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"trackers", "id"} ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={torrent.id: get_announce_urls(torrent) for torrent in torrents} )
def get_incomplete_torrent_files(self) -> QueryResult[Set[Path]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"torrent_file", "percent_done"} ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={ Path(torrent.torrent_file) for torrent in torrents if torrent.percent_done == 0.0 } )
def get_torrent_location(self, torrent_id: int) -> QueryResult[Path]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"download_dir"}, ids=torrent_id ) if response.result != "success": raise TransmissionError(f"clutch failure: {response.result}") arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) if len(torrents) != 1: raise TransmissionError( f"torrent with id {torrent_id} not returned in result" ) else: return QueryResult(value=Path(torrents[0].download_dir))
def get_errors_by_id( self, ids: Set[int] ) -> QueryResult[Mapping[int, Tuple[int, str]]]: response: Response[TorrentAccessorResponse] = self.client.torrent.accessor( fields={"id", "error", "error_string"} ) arguments = cast(TorrentAccessorResponse, response.arguments) torrents: Sequence[TorrentAccessorObject] = cast( Sequence[TorrentAccessorObject], arguments.torrents ) return QueryResult( value={ torrent.id: (torrent.error, torrent.error_string) for torrent in torrents if torrent.error != 0 } )