Skip to content

Commit

Permalink
new, server: player playlist refactoring (move, add, remove, onDBChan…
Browse files Browse the repository at this point in the history
…ge) WIP
  • Loading branch information
dxstiny committed Sep 13, 2023
1 parent 8be1103 commit 891d796
Show file tree
Hide file tree
Showing 29 changed files with 804 additions and 963 deletions.
19 changes: 19 additions & 0 deletions src/server/db/table/iPlaylistModel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# -*- coding: utf-8 -*-
"""reAudioPlayer ONE"""
__copyright__ = "Copyright (c) 2023 https://github.com/reAudioPlayer"

from abc import ABC, abstractmethod

class IPlaylistModel(ABC):
Expand All @@ -6,16 +10,31 @@ class IPlaylistModel(ABC):
def name(self) -> str:
"""playlist name"""

@name.setter
@abstractmethod
def name(self, value: str) -> None:
"""set playlist name"""

@property
@abstractmethod
def description(self) -> str:
"""playlist description"""

@description.setter
@abstractmethod
def description(self, value: str) -> None:
"""set playlist description"""

@property
@abstractmethod
def cover(self) -> str:
"""playlist cover"""

@cover.setter
@abstractmethod
def cover(self, value: str) -> None:
"""set playlist cover"""

@property
@abstractmethod
def plays(self) -> int:
Expand Down
6 changes: 5 additions & 1 deletion src/server/db/table/playlists.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def songsList(self) -> List[int]:

@songsList.setter
def songsList(self, songs: List[int]) -> None:
self._songs = json.dumps(songs)
self.songs = json.dumps(songs)

@property
def plays(self) -> int:
Expand All @@ -135,6 +135,10 @@ def id(self) -> int:
assert self._id is not None
return self._id

@id.setter
def id(self, value: int) -> None:
self._id = value

def toDict(self) -> Dict[str, Any]:
"""return dict"""
return {
Expand Down
14 changes: 6 additions & 8 deletions src/server/db/table/smartPlaylists.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,9 @@ def _model(self) -> Type[SmartPlaylistModel]:
return SmartPlaylistModel

async def byId(self, id_: int) -> Optional[SmartPlaylistModel]:
"""get song by id"""
where = f"id = {id_}"
return await self.selectOne(append=f"WHERE {where}")

async def allByIds(self, ids: List[int]) -> List[SmartPlaylistModel]:
"""get songs by ids"""
where = f"id IN ({', '.join([ str(x) for x in ids ])})"
return await self.select(append=f"WHERE {where}")
"""get playlist by id"""
return await self.selectOne(append=f"WHERE id = {id_}")

async def deleteById(self, id_: int) -> None:
"""delete playlist by id"""
await self._db.execute(f"DELETE FROM {self.NAME} WHERE id={id_}")
4 changes: 4 additions & 0 deletions src/server/db/table/songs.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ def id(self) -> int:
assert self._id is not None
return self._id

@id.setter
def id(self, value: int) -> None:
self._id = value

@property
def title(self) -> str:
"""return title"""
Expand Down
6 changes: 4 additions & 2 deletions src/server/db/table/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,12 @@ async def all(self) -> List[_T]:
async with self._db.execute(f"SELECT * FROM {self.NAME}") as cursor:
return [self.cast(row) for row in await cursor.fetchall()]

async def insert(self, item: _T) -> None:
async def insert(self, item: _T) -> Optional[int]:
"""insert item"""
await self._db.execute(f"INSERT INTO {self.NAME} {item.insertStatement}", item.toTuple())
item.onChanged.add(self.update)
async with self._db.execute(f"INSERT INTO {self.NAME} {item.insertStatement}",
item.toTuple()) as cursor:
return cursor.lastrowid

async def delete(self, item: _T) -> None:
"""delete item"""
Expand Down
18 changes: 0 additions & 18 deletions src/server/handler/collection.py

This file was deleted.

84 changes: 28 additions & 56 deletions src/server/handler/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
__copyright__ = "Copyright (c) 2022 https://github.com/reAudioPlayer"

import asyncio
from typing import Dict, Any, Optional
from typing import Dict, Any, Union, Optional
import json

from aiohttp import web
from pyaddict.schema import Object, String, Integer
from pyaddict.schema import Object, String, Integer, OneOf
from pyaddict import JDict

from db.database import Database
from helper.payloadParser import withObjectPayload
from player.player import Player
from player.playerPlaylist import PlayerPlaylist
from player.playlistManager import PlaylistManager
from dataModel.song import Song
from player.smartPlayerPlaylist import SpecialPlayerPlaylist


MIN_PLAYLIST_ID = -2
Expand All @@ -41,48 +40,29 @@ async def getPrevious(self, _: web.Request) -> web.Response:
return web.Response(status=200, text="success!")

@withObjectPayload(
Object(
{
"type": String().enum(
"playlist", "collection", "collection/breaking", "track", "smart"
),
"id": String().coerce().optional(),
"smart": String().optional(),
}
OneOf(
Object({"type": String().enum("playlist"), "id": String()}),
Object({"type": String().enum("track"), "id": Integer()}),
),
inBody=True,
)
async def loadPlaylist(self, payload: Dict[str, Any]) -> web.Response:
async def loadPlaylist(self, payload: Dict[str, str]) -> web.Response:
"""post(/api/player/load)"""
type_: str = payload["type"]
id_: Optional[str] = payload.get("id")

if type_ in ("playlist", "track") and not id_:
return web.HTTPBadRequest(text="id is required for types playlist and track")
id_: Union[str, int] = payload["id"]

if type_ == "playlist":
if id_ is None:
return web.HTTPBadRequest(text="id is required for type playlist")
assert isinstance(id_, str)
if playlist := self._playlistManager.get(id_):
asyncio.create_task(self._player.loadPlaylist(playlist))
return web.Response()
return web.HTTPNotFound(text="playlist not found")

# if type_ == "collection":
# asyncio.create_task(self._player.loadPlaylist(await PlayerPlaylist.liked()))
# return web.Response()

# if type_ == "collection/breaking":
# asyncio.create_task(self._player.loadPlaylist(await PlayerPlaylist.breaking()))
# return web.Response()

if type_ == "track":
assert id_ is not None
if not (song := await self._dbManager.songs.byId(int(id_))):
return web.HTTPNotFound(text="song not found")
# asyncio.create_task(
# self._player.loadPlaylist(PlayerPlaylist(songs=Song.list([song]), name=str(id_)))
# )
assert isinstance(id_, int)
playlist = SpecialPlayerPlaylist.track(int(id_))
await playlist.waitForLoad()
asyncio.create_task(self._player.loadPlaylist(playlist))
return web.Response()

return web.HTTPBadRequest(text="invalid type")
Expand All @@ -91,35 +71,27 @@ async def loadPlaylist(self, payload: Dict[str, Any]) -> web.Response:
Object(
{
"index": Integer().min(0), # index of song in playlist
"playlistIndex": Integer().min(MIN_PLAYLIST_ID).optional(), # playlist id
"type": String().enum("collection", "collection/breaking").optional(),
"playlist": String().optional(), # playlist id
}
),
inBody=True,
)
async def loadSongAt(self, payload: web.Request) -> web.Response:
async def loadSongAt(self, payload: Dict[str, Any]) -> web.Response:
"""post(/api/player/at)"""
songId: int = payload.get("index", -1)
type_: Optional[str] = payload.get("type", None)
found = False

if payload.get("playlistIndex", 0) in SPECIAL_PLAYLISTS:
type_ = SPECIAL_PLAYLISTS[payload.get("playlistIndex", 0)]
del payload["playlistIndex"]

if "playlistIndex" in payload: # other playlist
if await self._player.loadPlaylist(
self._playlistManager.get(payload["playlistIndex"]), songId
):
found = True
else:
found = await self._player.at(songId)

else:
found = await self._player.at(songId)

if not found:
index: int = payload["index"]
playlistId: Optional[str] = payload.get("playlist")

if not playlistId:
await self._player.at(index)
return web.Response()

playlist = self._playlistManager.get(playlistId)
if not playlist:
return web.HTTPNotFound()

success = await self._player.loadPlaylist(playlist, index)
if not success:
return web.HTTPInternalServerError()
return web.Response()

async def updateSong(self, request: web.Request) -> web.Response:
Expand Down
66 changes: 48 additions & 18 deletions src/server/handler/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from helper.payloadParser import withObjectPayload
from dataModel.song import Song
from player.player import Player
from player.playerPlaylist import PlayerPlaylist
from player.playlistManager import PlaylistManager
from player.smartPlayerPlaylist import SmartPlayerPlaylist, SpecialPlayerPlaylist


SMART_DEFINITION = Object(
Expand All @@ -32,6 +32,15 @@
}
)

SMART_PLAYLIST = Object(
{
"name": String().min(1),
"description": String().coerce(),
"cover": String().min(1),
"definition": SMART_DEFINITION,
}
)


class PlaylistHandler:
"""playlist handler"""
Expand All @@ -42,26 +51,34 @@ def __init__(self, player: Player, playlistManager: PlaylistManager) -> None:

async def addSong(self, request: web.Request) -> web.Response:
"""post(/api/playlists/{id}/tracks)"""
id_ = int(request.match_info["id"])
id_ = str(request.match_info["id"])
jdata = await request.json()
await self._playlistManager.addToPlaylist(id_, Song.fromDict(jdata))
return web.Response()
success = await self._playlistManager.addToPlaylist(id_, Song.fromDict(jdata))
if success:
return web.Response()
return web.HTTPBadRequest

async def moveSong(self, request: web.Request) -> web.Response:
"""put(/api/playlists/{id}/tracks)"""
id_ = int(request.match_info["id"])
id_ = str(request.match_info["id"])
jdata = await request.json()
self._playlistManager.moveInPlaylist(id_, jdata["songOldIndex"], jdata["songNewIndex"])
return web.Response()
success = await self._playlistManager.moveInPlaylist(
id_, jdata["songOldIndex"], jdata["songNewIndex"]
)
if success:
return web.Response()
return web.HTTPBadRequest

async def removeSong(self, request: web.Request) -> web.Response:
"""/api/playlists/{id}/tracks"""
id_ = int(request.match_info["id"])
id_ = str(request.match_info["id"])
jdata = await request.json()
if not "songId" in jdata:
return web.Response(status=400, text="no songId")
await self._playlistManager.removefromPlaylist(id_, jdata["songId"])
return web.Response(status=200, text="success!")
success = await self._playlistManager.removefromPlaylist(id_, jdata["songId"])
if success:
return web.Response()
return web.HTTPBadRequest

@withObjectPayload(Object({"id": String().coerce()}), inPath=True)
async def getPlaylist(self, payload: Dict[str, Any]) -> web.Response:
Expand Down Expand Up @@ -105,26 +122,39 @@ async def updatePlaylist(self, request: web.Request) -> web.Response:
)
return web.Response()

@withObjectPayload(SMART_DEFINITION, inBody=True)
@withObjectPayload(SMART_PLAYLIST, inBody=True)
async def addSmartPlaylist(self, payload: Dict[str, Any]) -> web.Response:
"""post(/api/playlists/smart)"""
return web.Response()

@withObjectPayload(SMART_PLAYLIST, inBody=True)
async def updateSmartPlaylist(self, payload: Dict[str, Any]) -> web.Response:
"""post(/api/playlists/smart/{id})"""
"""put(/api/playlists/smart/{id})"""
playlist = self._playlistManager.get(payload["id"])
if not isinstance(playlist, SmartPlayerPlaylist):
return web.HTTPNotFound()
playlist.updateMeta(payload["name"], payload["description"], payload["cover"])
playlist.definition = payload["definition"]
return web.Response()

@withObjectPayload(SMART_DEFINITION, inBody=True)
async def peekSmartPlaylist(self, payload: Dict[str, Any]) -> web.Response:
"""post(/api/playlists/smart/preview)"""
playlist = await PlayerPlaylist.smart(payload)
return web.json_response(playlist.toDict())
playlist = SpecialPlayerPlaylist("Peek", "", payload, "peek", "")
await playlist.waitForLoad()
return web.json_response({})

@withObjectPayload(
Object(
{
"id": Integer().min(0).coerce(),
"id": String().coerce(),
}
),
inPath=True,
)
async def getSmartPlaylist(self, payload: Dict[str, int]) -> web.Response:
async def getSmartPlaylist(self, payload: Dict[str, str]) -> web.Response:
"""get(/api/playlists/smart/{id})"""
id_: int = payload["id"]
return web.Response()
playlist = self._playlistManager.get(payload["id"])
if not isinstance(playlist, SmartPlayerPlaylist):
return web.HTTPNotFound()
return web.json_response(playlist.definition)
2 changes: 1 addition & 1 deletion src/server/helper/logged.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, name: str) -> None:
self._logger = logging.getLogger(name)

@staticmethod
def init() -> None:
def initLogger() -> None:
"""Initialise the logger"""
pipeHandler = logging.StreamHandler(sys.stdout)
logging.config.fileConfig('logging.ini')
Expand Down
Loading

0 comments on commit 891d796

Please sign in to comment.