refactored msd reader api

This commit is contained in:
Maxim Devaev 2022-08-03 19:44:08 +03:00
parent 0809daa199
commit 9925198762
5 changed files with 51 additions and 48 deletions

View File

@ -87,12 +87,10 @@ class MsdApi:
@exposed_http("GET", "/msd/read") @exposed_http("GET", "/msd/read")
async def __read_handler(self, request: Request) -> StreamResponse: async def __read_handler(self, request: Request) -> StreamResponse:
name = valid_msd_image_name(request.query.get("image")) name = valid_msd_image_name(request.query.get("image"))
async with self.__msd.read_image(name) as size: async with self.__msd.read_image(name) as reader:
size = reader.get_total_size()
response = await start_streaming(request, "application/octet-stream", size, name) response = await start_streaming(request, "application/octet-stream", size, name)
while True: async for chunk in reader.read_chunked():
chunk = await self.__msd.read_image_chunk()
if not chunk:
return response
await response.write(chunk) await response.write(chunk)
return response return response

View File

@ -104,6 +104,19 @@ class MsdRwNotSupported(MsdOperationError):
# ===== # =====
class BaseMsdReader:
def get_state(self) -> Dict:
raise NotImplementedError()
def get_total_size(self) -> int:
raise NotImplementedError()
async def read_chunked(self) -> AsyncGenerator[bytes, None]:
if self is not None: # XXX: Vulture and pylint hack
raise NotImplementedError()
yield
class BaseMsd(BasePlugin): class BaseMsd(BasePlugin):
async def get_state(self) -> Dict: async def get_state(self) -> Dict:
raise NotImplementedError() raise NotImplementedError()
@ -134,14 +147,11 @@ class BaseMsd(BasePlugin):
raise NotImplementedError() raise NotImplementedError()
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def read_image(self, name: str) -> AsyncGenerator[int, None]: async def read_image(self, name: str) -> AsyncGenerator[BaseMsdReader, None]:
_ = name _ = name
if self is not None: # XXX: Vulture and pylint hack if self is not None: # XXX: Vulture and pylint hack
raise NotImplementedError() raise NotImplementedError()
yield 1 yield BaseMsdReader()
async def read_image_chunk(self) -> bytes:
raise NotImplementedError()
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]: async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]:
@ -159,7 +169,7 @@ class BaseMsd(BasePlugin):
raise NotImplementedError() raise NotImplementedError()
class MsdImageReader: # pylint: disable=too-many-instance-attributes class MsdFileReader(BaseMsdReader): # pylint: disable=too-many-instance-attributes
def __init__(self, notifier: aiotools.AioNotifier, path: str, chunk_size: int) -> None: def __init__(self, notifier: aiotools.AioNotifier, path: str, chunk_size: int) -> None:
self.__notifier = notifier self.__notifier = notifier
self.__name = os.path.basename(path) self.__name = os.path.basename(path)
@ -171,10 +181,6 @@ class MsdImageReader: # pylint: disable=too-many-instance-attributes
self.__readed = 0 self.__readed = 0
self.__tick = 0.0 self.__tick = 0.0
def get_size(self) -> int:
assert self.__file is not None
return self.__file_size
def get_state(self) -> Dict: def get_state(self) -> Dict:
return { return {
"name": self.__name, "name": self.__name,
@ -182,25 +188,33 @@ class MsdImageReader: # pylint: disable=too-many-instance-attributes
"readed": self.__readed, "readed": self.__readed,
} }
async def open(self) -> "MsdImageReader": def get_total_size(self) -> int:
assert self.__file is not None
return self.__file_size
async def read_chunked(self) -> AsyncGenerator[bytes, None]:
assert self.__file is not None
while True:
chunk = await self.__file.read(self.__chunk_size) # type: ignore
if not chunk:
break
self.__readed += len(chunk)
now = time.monotonic()
if self.__tick + 1 < now or self.__readed == self.__file_size:
self.__tick = now
await self.__notifier.notify()
yield chunk
async def open(self) -> "MsdFileReader":
assert self.__file is None assert self.__file is None
get_logger(1).info("Reading %r image from MSD ...", self.__name) get_logger(1).info("Reading %r image from MSD ...", self.__name)
self.__file_size = os.stat(self.__path).st_size self.__file_size = os.stat(self.__path).st_size
self.__file = await aiofiles.open(self.__path, mode="rb") # type: ignore self.__file = await aiofiles.open(self.__path, mode="rb") # type: ignore
return self return self
async def read(self) -> bytes:
assert self.__file is not None
chunk = await self.__file.read(self.__chunk_size) # type: ignore
self.__readed += len(chunk)
now = time.monotonic()
if self.__tick + 1 < now or self.__readed == self.__file_size:
self.__tick = now
await self.__notifier.notify()
return chunk
async def close(self) -> None: async def close(self) -> None:
assert self.__file is not None assert self.__file is not None
logger = get_logger() logger = get_logger()

View File

@ -29,6 +29,7 @@ from typing import Optional
from ... import aiotools from ... import aiotools
from . import MsdOperationError from . import MsdOperationError
from . import BaseMsdReader
from . import BaseMsd from . import BaseMsd
@ -77,13 +78,10 @@ class Plugin(BaseMsd):
raise MsdDisabledError() raise MsdDisabledError()
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def read_image(self, name: str) -> AsyncGenerator[int, None]: async def read_image(self, name: str) -> AsyncGenerator[BaseMsdReader, None]:
if self is not None: # XXX: Vulture and pylint hack if self is not None: # XXX: Vulture and pylint hack
raise MsdDisabledError() raise MsdDisabledError()
yield 1 yield BaseMsdReader()
async def read_image_chunk(self) -> bytes:
raise MsdDisabledError()
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]: async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]:

View File

@ -57,7 +57,7 @@ from .. import MsdImageNotSelected
from .. import MsdUnknownImageError from .. import MsdUnknownImageError
from .. import MsdImageExistsError from .. import MsdImageExistsError
from .. import BaseMsd from .. import BaseMsd
from .. import MsdImageReader from .. import MsdFileReader
from .. import MsdImageWriter from .. import MsdImageWriter
from . import fs from . import fs
@ -165,7 +165,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__drive = Drive(gadget, instance=0, lun=0) self.__drive = Drive(gadget, instance=0, lun=0)
self.__reader: Optional[MsdImageReader] = None self.__reader: Optional[MsdFileReader] = None
self.__writer: Optional[MsdImageWriter] = None self.__writer: Optional[MsdImageWriter] = None
self.__notifier = aiotools.AioNotifier() self.__notifier = aiotools.AioNotifier()
@ -329,7 +329,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__state.vd.connected = connected self.__state.vd.connected = connected
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def read_image(self, name: str) -> AsyncGenerator[int, None]: async def read_image(self, name: str) -> AsyncGenerator[MsdFileReader, None]:
try: try:
async with self.__state._region: # pylint: disable=protected-access async with self.__state._region: # pylint: disable=protected-access
try: try:
@ -345,23 +345,19 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
if name not in self.__state.storage.images or not os.path.exists(path): if name not in self.__state.storage.images or not os.path.exists(path):
raise MsdUnknownImageError() raise MsdUnknownImageError()
self.__reader = await MsdImageReader( self.__reader = await MsdFileReader(
notifier=self.__notifier, notifier=self.__notifier,
path=path, path=path,
chunk_size=self.__read_chunk_size, chunk_size=self.__read_chunk_size,
).open() ).open()
yield self.__reader.get_size() yield self.__reader
finally: finally:
await self.__close_reader() await self.__close_reader()
finally: finally:
await self.__notifier.notify() await self.__notifier.notify()
async def read_image_chunk(self) -> bytes:
assert self.__reader
return (await self.__reader.read())
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]: async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]:
try: try:

View File

@ -50,6 +50,7 @@ from .. import MsdDisconnectedError
from .. import MsdMultiNotSupported from .. import MsdMultiNotSupported
from .. import MsdCdromNotSupported from .. import MsdCdromNotSupported
from .. import MsdRwNotSupported from .. import MsdRwNotSupported
from .. import BaseMsdReader
from .. import BaseMsd from .. import BaseMsd
from .. import MsdImageWriter from .. import MsdImageWriter
@ -218,15 +219,11 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__connected = connected self.__connected = connected
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def read_image(self, name: str) -> AsyncGenerator[int, None]: async def read_image(self, name: str) -> AsyncGenerator[BaseMsdReader, None]:
async with self.__working(): async with self.__working():
if self is not None: # XXX: Vulture and pylint hack if self is not None: # XXX: Vulture and pylint hack
raise MsdMultiNotSupported() raise MsdMultiNotSupported()
yield 1 yield BaseMsdReader()
async def read_image_chunk(self) -> bytes:
async with self.__working():
raise MsdMultiNotSupported()
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]: async def write_image(self, name: str, size: int, remove_incomplete: Optional[bool]) -> AsyncGenerator[int, None]: