diff --git a/src/knuckles/bookmarks.py b/src/knuckles/bookmarks.py index 46a03ff..4949a49 100644 --- a/src/knuckles/bookmarks.py +++ b/src/knuckles/bookmarks.py @@ -2,6 +2,7 @@ from .api import Api from .models.bookmark import Bookmark +from .models.play_queue import PlayQueue if TYPE_CHECKING: from .subsonic import Subsonic @@ -65,7 +66,7 @@ def create_bookmark( "createBookmark", {"id": id, "position": position, "comment": comment} ) - # Fake the structure given by the songs in the API. + # Fake the song structure given by in the API. return Bookmark(self.subsonic, {"id": id}, position=position, comment=comment) def update_bookmark( @@ -73,7 +74,7 @@ def update_bookmark( ) -> Bookmark: """Method that internally calls the create_bookmark method as creating and updating a bookmark uses the same endpoint. Useful for having - more self descriptive code. + more self-descriptive code. :param id: The ID of the song of the bookmark. :type id: str @@ -99,3 +100,27 @@ def delete_bookmark(self, id: str) -> "Subsonic": self.api.request("deleteBookmark", {"id": id}) return self.subsonic + + def get_play_queue(self) -> PlayQueue: + response = self.api.request("getPlayQueue")["playQueue"] + + return PlayQueue(self.subsonic, **response) + + def save_play_queue( + self, + song_ids: list[str], + current_song_id: str | None = None, + position: int | None = None, + ) -> PlayQueue: + self.api.request( + "savePlayQueue", + {"id": song_ids, "current": current_song_id, "position": position}, + ) + + # TODO This approach is expensive, a better one is preferred + # Fake the song structure given by in the API. + songs = [] + for song_id in song_ids: + songs.append({"id": song_id}) + + return PlayQueue(self.subsonic, songs, current_song_id, position) diff --git a/src/knuckles/models/play_queue.py b/src/knuckles/models/play_queue.py new file mode 100644 index 0000000..7ae6171 --- /dev/null +++ b/src/knuckles/models/play_queue.py @@ -0,0 +1,58 @@ +from typing import TYPE_CHECKING, Any, Self + +from dateutil import parser + +from .song import Song +from .user import User + +if TYPE_CHECKING: + from ..subsonic import Subsonic + + +class PlayQueue: + """Representation of all the data related to a play queue in Subsonic.""" + + def __init__( + self, + subsonic: "Subsonic", + entry: list[dict[str, Any]], + current: str | None = None, + position: int | None = None, + username: str | None = None, + changed: str | None = None, + changedBy: str | None = None, + ) -> None: + self.__subsonic = subsonic + self.current = Song(self.__subsonic, current) if current else None + self.position = position + self.user = User(username) if username else None + self.changed = parser.parse(changed) if changed else None + self.changed_by = changedBy + self.songs = ( + [Song(self.__subsonic, **song) for song in entry] if entry else None + ) + + def generate(self) -> "PlayQueue": + """Return a new play queue with all the data updated from the API, + using the endpoint that return the most information possible. + + Useful for making copies with updated data or updating the object itself + with immutability, e.g., foo = foo.generate(). + + :return: A new share object with all the data updated. + :rtype: PlayQueue + """ + + get_play_queue = self.__subsonic.bookmarks.get_play_queue() + + return get_play_queue + + def save(self) -> Self: + # TODO This should raise an exception + song_ids: list[str] = [song.id for song in self.songs] if self.songs else [] + + self.__subsonic.bookmarks.save_play_queue( + song_ids, self.current.id if self.current else None, self.position + ) + + return self diff --git a/src/knuckles/models/playlist.py b/src/knuckles/models/playlist.py index 5e8a8da..260f154 100644 --- a/src/knuckles/models/playlist.py +++ b/src/knuckles/models/playlist.py @@ -103,7 +103,7 @@ def create(self) -> "Playlist": new_playlist = self.__subsonic.playlists.create_playlist( # Ignore the None type error as the server - # should return a Error Code 10 in response + # should return an Error Code 10 in response self.name, # type: ignore[arg-type] self.comment, self.public, diff --git a/tests/api/test_bookmarks.py b/tests/api/test_bookmarks.py index 1b4e462..3ed4962 100644 --- a/tests/api/test_bookmarks.py +++ b/tests/api/test_bookmarks.py @@ -97,6 +97,7 @@ def test_get_play_queue( assert response.current.id == song["id"] assert response.changed == parser.parse(play_queue["changed"]) assert response.changed_by == client + assert type(response.songs) is list assert response.songs[0].id == song["id"] @@ -109,4 +110,8 @@ def test_save_play_queue( ) -> None: responses.add(mock_save_play_queue) - subsonic.bookmarks.save_play_queue([song["id"]], song["id"], play_queue["position"]) + response = subsonic.bookmarks.save_play_queue( + [song["id"]], song["id"], play_queue["position"] + ) + + assert response.current.id == song["id"] diff --git a/tests/models/test_song_queue.py b/tests/models/test_song_queue.py index 5089cc2..f103bf3 100644 --- a/tests/models/test_song_queue.py +++ b/tests/models/test_song_queue.py @@ -2,6 +2,7 @@ import responses from knuckles import Subsonic +from knuckles.models.play_queue import PlayQueue from responses import Response @@ -32,5 +33,4 @@ def test_save( requested_queue = subsonic.bookmarks.get_play_queue() requested_queue = requested_queue.save() - assert True is False - # assert type(requested_queue) is PlayQueue + assert type(requested_queue) is PlayQueue diff --git a/tests/models/test_user.py b/tests/models/test_user.py index 6f23081..6aa9128 100644 --- a/tests/models/test_user.py +++ b/tests/models/test_user.py @@ -73,7 +73,7 @@ def test_user_change_password( response = subsonic.user_management.get_user(user["username"]) response = response.change_password(new_password) - assert type(response) == User + assert type(response) is User def test_user_without_api_access(user: dict[str, Any]) -> None: