Skip to content

Commit db63345

Browse files
committed
some seekable tweaks
1 parent ac9f181 commit db63345

4 files changed

Lines changed: 47 additions & 13 deletions

File tree

prime_backup/action/helpers/chunk_io.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from prime_backup.compressors import Compressor
66
from prime_backup.types.chunk_info import ChunkInfo
77
from prime_backup.utils.bypass_io import BypassReader
8-
from prime_backup.utils.io_types import SupportsReadBytes
8+
from prime_backup.utils.io_types import SupportsReadBytes, SupportsReadAndSeek
99

1010

1111
class ChunkIO:
@@ -18,7 +18,7 @@ def __get_pack_id(self) -> int:
1818
return self.chunk.pack_entry.pack_id
1919

2020
@contextlib.contextmanager
21-
def open_raw(self) -> Generator[SupportsReadBytes, None, None]:
21+
def open_raw(self) -> Generator[SupportsReadAndSeek, None, None]:
2222
with PackReader.open_entry(self.__get_pack_id(), self.chunk.pack_entry.offset, self.chunk.stored_size) as reader:
2323
yield reader
2424

prime_backup/action/helpers/pack_reader.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import contextlib
2+
import os
23
from typing import BinaryIO, Generator
34

5+
from typing_extensions import Final
6+
47
from prime_backup.utils import pack_utils
5-
from prime_backup.utils.io_types import SupportsReadBytes
8+
from prime_backup.utils.io_types import SupportsReadAndSeek
69

710

811
class PackEntryReader:
912
def __init__(self, file_obj: BinaryIO, offset: int, length: int):
10-
self.__file_obj = file_obj
11-
self.__offset = offset
12-
self.__length = length
13+
self.__file_obj: Final[BinaryIO] = file_obj
14+
self.__offset: Final[int] = offset
15+
self.__length: Final[int] = length
1316
self.__position = 0
1417
self.__file_obj.seek(offset)
1518

@@ -23,11 +26,33 @@ def read(self, size: int = -1) -> bytes:
2326
self.__position += len(data)
2427
return data
2528

29+
def seekable(self) -> bool:
30+
return True
31+
32+
def seek(self, offset: int, whence: int = 0):
33+
if whence == os.SEEK_SET:
34+
position = offset
35+
elif whence == os.SEEK_CUR:
36+
position = self.__position + offset
37+
elif whence == os.SEEK_END:
38+
position = self.__length + offset
39+
else:
40+
raise ValueError('invalid whence {}'.format(whence))
41+
42+
if position < 0:
43+
raise ValueError('negative seek position {}'.format(position))
44+
if position > self.__length:
45+
position = self.__length
46+
47+
self.__file_obj.seek(self.__offset + position)
48+
self.__position = position
49+
return self.__position
50+
2651

2752
class PackReader:
2853
@classmethod
2954
@contextlib.contextmanager
30-
def open_entry(cls, pack_id: int, offset: int, length: int) -> Generator[SupportsReadBytes, None, None]:
55+
def open_entry(cls, pack_id: int, offset: int, length: int) -> Generator[SupportsReadAndSeek, None, None]:
3156
pack_path = pack_utils.get_pack_path(pack_id)
3257
with open(pack_path, 'rb') as fh:
3358
yield PackEntryReader(fh, offset, length)

prime_backup/cli/fuse/file.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from contextlib import AbstractContextManager
55
from io import BytesIO
66
from pathlib import Path
7-
from typing import IO, cast, List
7+
from typing import List, cast
88

99
from typing_extensions import Optional, override
1010

@@ -17,6 +17,7 @@
1717
from prime_backup.types.blob_info import BlobInfo
1818
from prime_backup.types.chunk_info import OffsetChunkInfo, ChunkInfo
1919
from prime_backup.utils import blob_utils
20+
from prime_backup.utils.io_types import SupportsReadBytes, SupportsReadAndSeek
2021

2122

2223
class _FileReader(ABC):
@@ -33,17 +34,17 @@ def close(self):
3334

3435

3536
class _SingleFileReader(_FileReader):
36-
def __init__(self, file_ctx: AbstractContextManager[IO[bytes]]):
37+
def __init__(self, file_ctx: AbstractContextManager[SupportsReadBytes]):
3738
self.file_ctx = file_ctx
3839
self.file_obj = file_ctx.__enter__()
39-
self.file_seekable = self.file_obj.seekable()
40+
self.file_seekable = hasattr(self.file_obj, 'seekable') and hasattr(self.file_obj, 'seek') and self.file_obj.seekable()
4041
self.offset = 0
4142

4243
@override
4344
def read(self, size: int, offset: int) -> bytes:
4445
if offset != self.offset:
4546
if self.file_seekable:
46-
self.file_obj.seek(offset)
47+
cast(SupportsReadAndSeek, self.file_obj).seek(offset)
4748
self.offset = offset
4849
else:
4950
raise self.NoSequenceRead()
@@ -62,15 +63,15 @@ def create_from_file(cls, file_path: Path, compress_method: CompressMethod) -> '
6263
return _SingleFileReader(open(file_path, 'rb'))
6364
else:
6465
decompressed_stream = Compressor.create(compress_method).open_decompressed(file_path)
65-
return _SingleFileReader(cast(AbstractContextManager[IO[bytes]], decompressed_stream))
66+
return _SingleFileReader(decompressed_stream)
6667

6768
@classmethod
6869
def create_from_blob(cls, blob: BlobInfo) -> '_SingleFileReader':
6970
return _SingleFileReader.create_from_file(blob_utils.get_blob_path(blob.hash), blob.compress)
7071

7172
@classmethod
7273
def create_from_chunk(cls, chunk: ChunkInfo) -> '_SingleFileReader':
73-
return _SingleFileReader(cast(AbstractContextManager[IO[bytes]], ChunkIO(chunk).open_decompressed()))
74+
return _SingleFileReader(ChunkIO(chunk).open_decompressed())
7475

7576

7677
class _MultiFileReader(_FileReader):

prime_backup/utils/io_types.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ def read(self, length: int = -1) -> bytes:
66
...
77

88

9+
class SupportsReadAndSeek(SupportsReadBytes, Protocol):
10+
def seekable(self) -> bool:
11+
...
12+
13+
def seek(self, offset: int, whence: int = 0):
14+
...
15+
16+
917
class SupportsWriteBytes(Protocol):
1018
def write(self, s: bytes) -> int:
1119
...

0 commit comments

Comments
 (0)