예제 #1
0
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"
    )
예제 #2
0
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")
        )
    }
예제 #3
0
 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))
예제 #4
0
 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}
     )
예제 #5
0
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"
예제 #6
0
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"))
예제 #7
0
 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}
     )
예제 #8
0
 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)
예제 #9
0
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"
예제 #10
0
    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)
            }
        )
예제 #11
0
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"
    )
예제 #12
0
    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)
예제 #13
0
 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)
예제 #14
0
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"
    )
예제 #15
0
 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
         }
     )
예제 #16
0
 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}
     )
예제 #17
0
    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}
        )
예제 #18
0
 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
         }
     )
예제 #19
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))
예제 #20
0
 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
         }
     )