Migrate Playlist to DB 3 TODO

1 Migrate Config to Schema 3 without playlists
and update get_playlist methods

Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com>
This commit is contained in:
Drapersniper
2019-12-17 22:16:59 +00:00
parent e32eecd6e7
commit 4af33cff17

View File

@@ -1,18 +1,23 @@
import json
import os
from collections import namedtuple from collections import namedtuple
from enum import Enum, unique from enum import Enum, unique
from typing import List, Optional, Union from typing import List, Optional, Union, Tuple
import apsw
import discord import discord
import lavalink import lavalink
from redbot.core import Config, commands from redbot.core import Config, commands
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_list from redbot.core.utils.chat_formatting import humanize_list
from .errors import InvalidPlaylistScope, MissingAuthor, MissingGuild, NotAllowed from .errors import InvalidPlaylistScope, MissingAuthor, MissingGuild, NotAllowed
_config = None _config: Config = None
_bot = None _bot: Red = None
_database: "Database" = None
__all__ = [ __all__ = [
"Playlist", "Playlist",
@@ -31,6 +36,105 @@ FakePlaylist = namedtuple("Playlist", "author scope")
_ = Translator("Audio", __file__) _ = Translator("Audio", __file__)
_PRAGMA_UPDATE_temp_store = """
PRAGMA temp_store = 2;
"""
_PRAGMA_UPDATE_journal_mode = """
PRAGMA journal_mode = wal;
"""
_PRAGMA_UPDATE_wal_autocheckpoint = """
PRAGMA wal_autocheckpoint;
"""
_PRAGMA_UPDATE_read_uncommitted = """
PRAGMA read_uncommitted = 1;
"""
_PRAGMA_UPDATE_optimize = """
PRAGMA optimize = 1;
"""
_CREATE_TABLE = """
CREATE TABLE IF NOT EXISTS GLOBAL (
playlist_id INTEGER PRIMARY KEY,
playlist_name TEXT NOT NULL,
scope_id INTEGER NOT NULL,
author_id INTEGER NOT NULL,
playlist_url TEXT,
tracks BLOB);
"""
_DROP = """
DROP TABLE {table};
"""
_DELETE = """
DELETE FROM {table}
WHERE
(
playlist_id = :playlist_id
AND
scope_id = :scope_id
)
;
"""
_FETCH_ALL = """
SELECT
playlist_id,
playlist_name,
scope_id,
author_id,
playlist_url,
tracks
FROM {table};
"""
_FETCH = """
SELECT
playlist_id,
playlist_name,
scope_id,
author_id,
playlist_url,
tracks
FROM {table}
WHERE
(
playlist_id = :playlist_id
AND
scope_id = :scope_id
)
"""
_UPSET = """INSERT INTO
{table}
(
playlist_id,
playlist_name,
scope_id,
author_id,
playlist_url,
tracks
)
VALUES
(
:playlist_id,
:playlist_name,
:scope_id,
:author_id,
:playlist_url,
:tracks
)
ON CONFLICT
(
playlist_id,
scope_id
)
DO UPDATE
SET
playlist_name = :playlist_name,
playlist_url = :playlist_url,
tracks = :tracks
;
"""
@unique @unique
class PlaylistScope(Enum): class PlaylistScope(Enum):
@@ -46,12 +150,89 @@ class PlaylistScope(Enum):
return list(map(lambda c: c.value, PlaylistScope)) return list(map(lambda c: c.value, PlaylistScope))
class Database:
def __init__(self):
self._database = apsw.Connection(
str(cog_data_path(_bot.get_cog("Audio")) / "playlists.db")
)
self.cursor = self._database.cursor()
self.cursor.execute(_PRAGMA_UPDATE_temp_store)
self.cursor.execute(_PRAGMA_UPDATE_journal_mode)
self.cursor.execute(_PRAGMA_UPDATE_wal_autocheckpoint)
self.cursor.execute(_PRAGMA_UPDATE_read_uncommitted)
for t in ["GLOBAL", "GUILD", "USER"]:
self.cursor.execute(_CREATE_TABLE.format(table=t))
@staticmethod
def parse_query(scope: PlaylistScope, query: str):
if scope == PlaylistScope.GLOBAL.value:
table = "GLOBAL"
elif scope == PlaylistScope.GUILD.value:
table = "GUILD"
elif scope == PlaylistScope.USER.value:
table = "USER"
else:
raise
return query.format(table=table)
def fetch(
self, scope: PlaylistScope, playlist_id: int, scope_id: int
) -> Tuple[int, str, int, int, str, str]:
query = self.parse_query(scope, _FETCH)
return self.cursor.execute(
query, ({"playlist_id": playlist_id, "scope_id": scope_id})
).fetchone()
def delete(self, scope: PlaylistScope, playlist_id: int, scope_id: int):
query = self.parse_query(scope, _DELETE)
return self.cursor.execute(query, ({"playlist_id": playlist_id, "scope_id": scope_id}))
def fetch_all(self, scope: PlaylistScope) -> List[Tuple[int, str, int, int, str, str]]:
query = self.parse_query(scope, _FETCH_ALL)
return self.cursor.execute(query).fetchall()
def drop(self, scope: PlaylistScope):
query = self.parse_query(scope, _DROP)
return self.cursor.execute(query)
def create_table(self, scope: PlaylistScope):
query = self.parse_query(scope, _CREATE_TABLE)
return self.cursor.execute(query)
def upsert(
self,
scope: PlaylistScope,
playlist_id: int,
playlist_name: str,
scope_id: int,
author_id: int,
playlist_url: str,
tracks: List[dict],
):
query = self.parse_query(scope, _UPSET)
self.cursor.execute(
query,
(
{
"playlist_id": playlist_id,
"playlist_name": playlist_name,
"scope_id": scope_id,
"author_id": author_id,
"playlist_url": playlist_url,
"tracks": json.dumps(tracks),
}
),
)
def _pass_config_to_playlist(config: Config, bot: Red): def _pass_config_to_playlist(config: Config, bot: Red):
global _config, _bot global _config, _bot, _database
if _config is None: if _config is None:
_config = config _config = config
if _bot is None: if _bot is None:
_bot = bot _bot = bot
if _database is None:
_database = Database()
def standardize_scope(scope) -> str: def standardize_scope(scope) -> str:
@@ -92,15 +273,15 @@ def _prepare_config_scope(
scope = standardize_scope(scope) scope = standardize_scope(scope)
if scope == PlaylistScope.GLOBAL.value: if scope == PlaylistScope.GLOBAL.value:
config_scope = [PlaylistScope.GLOBAL.value] config_scope = [PlaylistScope.GLOBAL.value, _bot.user.id]
elif scope == PlaylistScope.USER.value: elif scope == PlaylistScope.USER.value:
if author is None: if author is None:
raise MissingAuthor("Invalid author for user scope.") raise MissingAuthor("Invalid author for user scope.")
config_scope = [PlaylistScope.USER.value, str(getattr(author, "id", author))] config_scope = [PlaylistScope.USER.value, getattr(author, "id", author)]
else: else:
if guild is None: if guild is None:
raise MissingGuild("Invalid guild for guild scope.") raise MissingGuild("Invalid guild for guild scope.")
config_scope = [PlaylistScope.GUILD.value, str(getattr(guild, "id", guild))] config_scope = [PlaylistScope.GUILD.value, getattr(guild, "id", guild)]
return config_scope return config_scope
@@ -132,6 +313,14 @@ class Playlist:
self.tracks = tracks or [] self.tracks = tracks or []
self.tracks_obj = [lavalink.Track(data=track) for track in self.tracks] self.tracks_obj = [lavalink.Track(data=track) for track in self.tracks]
def _get_scope_id(self):
if self.scope == PlaylistScope.GLOBAL.value:
return self.bot.user.id
elif self.scope == PlaylistScope.USER.value:
return self.author
else:
return self.guild.id
async def edit(self, data: dict): async def edit(self, data: dict):
""" """
Edits a Playlist. Edits a Playlist.
@@ -146,8 +335,22 @@ class Playlist:
for item in list(data.keys()): for item in list(data.keys()):
setattr(self, item, data[item]) setattr(self, item, data[item])
await self.save()
await _config.custom(*self.config_scope, str(self.id)).set(self.to_json()) async def save(self):
"""
Saves a Playlist.
"""
scope, scope_id = self.config_scope
_database.upsert(
scope,
playlist_id=int(self.id),
playlist_name=self.name,
scope_id=scope_id,
author_id=self.author,
playlist_url=self.url,
tracks=self.tracks,
)
def to_json(self) -> dict: def to_json(self) -> dict:
"""Transform the object to a dict. """Transform the object to a dict.
@@ -216,7 +419,7 @@ class Playlist:
) )
async def get_playlist( async def get_playlist( # TODO: convert to SQL
playlist_number: int, playlist_number: int,
scope: str, scope: str,
bot: Red, bot: Red,
@@ -262,7 +465,7 @@ async def get_playlist(
) )
async def get_all_playlist( async def get_all_playlist( # TODO: convert to SQL
scope: str, scope: str,
bot: Red, bot: Red,
guild: Union[discord.Guild, int] = None, guild: Union[discord.Guild, int] = None,
@@ -360,10 +563,7 @@ async def create_playlist(
playlist = Playlist( playlist = Playlist(
ctx.bot, scope, author.id, ctx.message.id, playlist_name, playlist_url, tracks, ctx.guild ctx.bot, scope, author.id, ctx.message.id, playlist_name, playlist_url, tracks, ctx.guild
) )
await playlist.save()
await _config.custom(*_prepare_config_scope(scope, author, guild), str(ctx.message.id)).set(
playlist.to_json()
)
return playlist return playlist
@@ -393,7 +593,9 @@ async def reset_playlist(
`MissingAuthor` `MissingAuthor`
Trying to access the User scope without an user id. Trying to access the User scope without an user id.
""" """
await _config.custom(*_prepare_config_scope(scope, author, guild)).clear() scope, scope_id = _prepare_config_scope(scope, author, guild)
_database.drop(scope)
_database.create_table(scope)
async def delete_playlist( async def delete_playlist(
@@ -425,4 +627,5 @@ async def delete_playlist(
`MissingAuthor` `MissingAuthor`
Trying to access the User scope without an user id. Trying to access the User scope without an user id.
""" """
await _config.custom(*_prepare_config_scope(scope, author, guild), str(playlist_id)).clear() scope, scope_id = _prepare_config_scope(scope, author, guild)
_database.delete(scope, int(playlist_id), scope_id)