From a7c404703592abcb063500da9788ba2bb92c983c Mon Sep 17 00:00:00 2001 From: Trim21 Date: Fri, 13 Dec 2024 20:41:41 +0800 Subject: [PATCH] feat(torrent)!: return `BitMap` as `.pieces` instead of base64 str (#485) --- transmission_rpc/torrent.py | 16 ++++++---------- transmission_rpc/types.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/transmission_rpc/torrent.py b/transmission_rpc/torrent.py index a5905d8f..204b6c46 100644 --- a/transmission_rpc/torrent.py +++ b/transmission_rpc/torrent.py @@ -1,13 +1,15 @@ from __future__ import annotations +import base64 import enum from datetime import datetime, timedelta, timezone +from functools import cached_property from typing import Any from typing_extensions import deprecated from transmission_rpc.constants import IdleMode, Priority, RatioLimitMode -from transmission_rpc.types import Container, File +from transmission_rpc.types import BitMap, Container, File from transmission_rpc.utils import format_timedelta _STATUS_NEW_MAPPING = { @@ -567,15 +569,9 @@ def percent_done(self) -> float: """ return float(self.fields["percentDone"]) - @property - def pieces(self) -> str: - """ - A bitfield holding pieceCount flags which are set to 'true' - if we have the piece matching that position. - - JSON doesn't allow raw binary data, so this is a base64-encoded string. (Source: tr_torrent) - """ - return self.fields["pieces"] + @cached_property + def pieces(self) -> BitMap: + return BitMap(base64.b64decode(self.fields["pieces"].encode())) @property def piece_count(self) -> int: diff --git a/transmission_rpc/types.py b/transmission_rpc/types.py index 0637b9ad..9dcc0d4e 100644 --- a/transmission_rpc/types.py +++ b/transmission_rpc/types.py @@ -74,3 +74,24 @@ def speed_limit_up_enabled(self) -> bool: def speed_limit_up(self) -> int: """max global upload speed (KBps)""" return self.fields["speed-limit-up"] + + +class BitMap: + __value: bytes + __slots__ = ("__value",) + + def __init__(self, b: bytes): + self.__value = b + + def get(self, index: int) -> bool: + """ + Args: + index: piece index + Returns: + this method always return a bool, even index overflow piece count of torrent. + This is because there is no reliable way to know piece count only based on `torrent.pieces`. + """ + try: + return bool(self.__value[index // 8] & (1 << (7 - (index % 8)))) + except IndexError: + return False