Skip to content

Commit

Permalink
small fix
Browse files Browse the repository at this point in the history
  • Loading branch information
vm86 committed Nov 28, 2024
1 parent 7ba59e0 commit 6709441
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 23 deletions.
16 changes: 2 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@ pip install yandex_fuse-*.tar.gz
#### Запускаем
```shell
systemctl --user start yamusic-fs.service
```
Или
```shell
yamusic-fs ~/Music/Yandex/
```
Expand All @@ -62,12 +56,6 @@ yamusic-fs ~/Music/Yandex/
#### Отмонтировать
```shell
systemctl stop yamusic-fs.service --user
```
Или
```shell
fusermount -u ~/Music/Yandex
```
Expand All @@ -79,14 +67,14 @@ fusermount -u ~/Music/Yandex
```json
{
"token": "",
"best_codec": "aac",
"quality": "hq",
"blacklist": [],
}
```
token = Токен доступа
best_codec = aac или mp3
quality = lossless, hq
blacklist = "Черный список" жанров для "Моя волна"
Expand Down
126 changes: 126 additions & 0 deletions tests/test_music_fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# ruff: noqa: S101
# ruff: noqa: ARG002
# ruff: noqa: ANN001
# ruff: noqa: ANN201
# ruff: noqa: ANN202
# ruff: noqa: ANN204
# mypy: ignore-errors

from typing import ParamSpec, TypeVar
from unittest import mock

import pytest
from pytest_mock import MockerFixture

from yandex_fuse.ya_music_fs import SQLTrack, StreamReader, YaMusicFS

P = ParamSpec("P")
T = TypeVar("T")


class MockResponse:
def __init__(self, text: str, status: int) -> None:
self._text = text
self.status = status

async def text(self) -> str:
return self._text

async def __aexit__(self, exc_type, exc, tb):
pass

async def __aenter__(self):
return self


TRACK_INFO = SQLTrack(
name="TestTrack",
inode=10,
track_id=10,
codec="acc",
bitrate=128,
artist="test",
title="test",
album="test",
year="2024",
genre="test",
duration_ms=100,
playlist_id="NOT",
quality="hq",
size=100,
)


@pytest.fixture(autouse="True")
def client_session_mock():
with mock.patch("yandex_fuse.ya_music_fs.YaMusicFS._client_session") as m:
yield m


@mock.patch("yandex_fuse.ya_music_fs.Buffer.download", mock.AsyncMock())
@mock.patch("yandex_fuse.ya_music_fs.YaMusicFS._ya_player", mock.AsyncMock())
@pytest.mark.asyncio
class TestMusicFS:
@pytest.fixture(scope="session")
def ya_music_fs(self) -> YaMusicFS:
yandex_music = YaMusicFS
yandex_music.FILE_DB = "file::memory:?cache=shared"
return yandex_music()

@mock.patch("yandex_fuse.ya_music_fs.YaMusicFS._get_track_by_inode")
@mock.patch("yandex_fuse.ya_music_fs.YaMusicFS.get_or_update_direct_link")
async def test_open(
self,
mock_get_or_update_direct_link: mock.Mock,
mock_get_track_by_inode: mock.Mock,
ya_music_fs: YaMusicFS,
) -> None:
mock_get_track_by_inode.return_value = TRACK_INFO
file_info = await ya_music_fs.open(519, 0o664, None)
assert file_info.fh == 1

assert mock_get_or_update_direct_link.call_count == 1

file_info = await ya_music_fs.open(519, 0o664, None)
assert file_info.fh == 2 # noqa: PLR2004

async def test_read(
self,
ya_music_fs: YaMusicFS,
mocker: MockerFixture,
) -> None:
buffer = mock.MagicMock()

buffer.read_from = mock.AsyncMock()
buffer.read_from.return_value = b"Test"
buffer.total_second.return_value = 0

stream = StreamReader(
buffer=buffer,
track=TRACK_INFO,
is_send_feedback=False,
)
mocker.patch.object(ya_music_fs, "_fd_map_stream", {10: stream})
chunk = await ya_music_fs.read(10, 100, 100)
assert chunk == b"Test"

@mock.patch("yandex_fuse.virt_fs.VirtFS._get_file_stat_by_inode")
async def test_release(
self,
mock_get_file_stat_by_inode: mock.Mock(),
ya_music_fs: YaMusicFS,
mocker: MockerFixture,
) -> None:
stream = StreamReader(
buffer=mock.MagicMock(),
track=TRACK_INFO,
is_send_feedback=False,
)
mocker.patch.object(ya_music_fs, "_fd_map_inode", {10: 519})
mocker.patch.object(ya_music_fs, "_fd_map_stream", {10: stream})

await ya_music_fs.release(10)

# TODO(vm86): check via mock
assert ya_music_fs._fd_map_inode == {} # noqa: SLF001
assert ya_music_fs._fd_map_stream == {} # noqa: SLF001
17 changes: 10 additions & 7 deletions yandex_fuse/virt_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,10 @@ def _get_list_row(
return [dict(row) for row in cur.fetchall()]

def _get_fd(self) -> int:
for i in range(2048):
if i in self._fd_map_inode:
continue

return i
raise FUSEError(errno.ENOENT)
try:
return max(self._fd_map_inode.keys()) + 1
except ValueError:
return 1

@property
def _inode_map_fd(self) -> dict[InodeT, set[int]]:
Expand Down Expand Up @@ -322,9 +320,10 @@ def queue_later_invalidate_inode(self) -> set[InodeT]:
return self.__later_invalidate_inode

def _invalidate_inode(self, inode: InodeT) -> None:
self.__later_invalidate_inode.discard(inode)
if inode not in self._nlookup:
return
if len(self._fd_map_inode) > 0:
if inode in self._inode_map_fd:
log.warning(
"Invalidate inode %d skip. There are open descriptors.",
inode,
Expand Down Expand Up @@ -435,6 +434,8 @@ async def opendir(self, inode: InodeT, ctx: RequestContext) -> FileHandleT: # n
async def releasedir(self, fd: FileHandleT) -> None:
inode = self._fd_map_inode.pop(fd)
self.__fd_token_read.pop(inode, None)
if inode in self.__later_invalidate_inode:
self._invalidate_inode(inode)

@fail_is_exit
async def statfs(self, ctx: RequestContext) -> StatvfsData: # noqa: ARG002
Expand Down Expand Up @@ -531,6 +532,8 @@ async def release(self, fd: int) -> None:
if self._get_file_stat_by_inode(inode).st_nlink == 0:
with self._db_cursor() as cur:
cur.execute("DELETE FROM inodes WHERE id=?", (inode,))
if inode in self.__later_invalidate_inode:
self._invalidate_inode(inode)

@fail_is_exit
async def unlink(
Expand Down
12 changes: 10 additions & 2 deletions yandex_fuse/ya_music_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,16 @@ class SQLPlaylist(SQLRow):
id: int | None = None


@dataclass
class SQLDirectLink(SQLRow):
__tablename__ = "direct_link"

track_id: str
link: str
expired: int
id: int | None = None


@dataclass
class StreamReader:
buffer: Buffer
Expand Down Expand Up @@ -324,8 +334,6 @@ def __init__(self, *args: P.args, **kwargs: P.kwargs) -> None:
self.__client_session: ClientSession | None = None
self.__ya_player: YandexMusicPlayer | None = None
self._fd_map_stream: dict[int, StreamReader] = {}
self._station_id_map_inode: dict[int, int] = {}
self._tracks = 0

async def start(self) -> None:
self.__client_session = ClientSession(
Expand Down

0 comments on commit 6709441

Please sign in to comment.