mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-23 11:13:51 -05:00
[3.2][Audio] Part 6 (Last? maybe?) (#3244)
* Removes `MAX_BALANCE` from bank, user `bank.get_max_balance()` now `[p]bankset maxbal` can be used to set the maximum bank balance Signed-off-by: Guy <guyreis96@gmail.com> * Initial Commit Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * I need to make sure I keep aika on her toes. Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Fixes a few missing kwargs and case consistency Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Fixes a few missing kwargs and case consistency v2 and typos Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Reset cooldowns + add changelogs Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Add 3 extra file formats. Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * IRDUMB - fix capitalization. Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Fix a silent error, and some incorrect messages. Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com> * Remove unnecessary emojis from queue when they are not needed Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com> * Remove duplicated call in `[p]playlist update` Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com> * Remove duplicated call in `[p]playlist update` Signed-off-by: guyre <27962761+drapersniper@users.noreply.github.com> * Resolve conflicts Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Bring all files up to date + Black Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Facepalm Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * *Sigh* Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * *Sigh* 2.0 Signed-off-by: Draper <27962761+Drapersniper@users.noreply.github.com> * Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into audio-misc-pt1 Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> # Resolve Conflicts: # redbot/cogs/audio/audio.py # redbot/cogs/audio/utils.py * Import missing Typecheck Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Fix Broken docstrings Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Sort Local Tracks Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * 🤦 Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Reorder the sorting of local tracks, `alphanumerical lower then alphanumerical upper` `a comes before A, but B comes after A` Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black formatting Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Make the local file sorting case insensitive Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Add global blacklist/whitelist + fix some issues with original server based whitelist/blacklist Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Remove the pre-commit yaml Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Nottin to see Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Further improvement to the blacklists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Further improvement to the blacklists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Fix the __str__ method on LocalTracks Object * Rename LocalTracks.to_string_hidden() to LocalTracks.to_string_user() To keep it inline with the Query object * Remove encoding pragmas + a few typo fixes * Update some typehints + fix some typos * Remove this duplicate call * Black * fix capitalization * Address preda's review Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Remove the API from the audio cog - Is in direct conflict with goals stated in #2804 - Features this was intended to enable can be enabled in other more appropriate ways later on * changelog * Address Aika's review * Black * *sigh* dont use github web ui * Fuck windows Long live linux... *sigh* no lets ensure windows users can still use local tracks * Merge branch 'V3/develop' of https://github.com/Cog-Creators/Red-DiscordBot into refactoring Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> # Conflicts: # redbot/cogs/audio/audio.py * 👀 + chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * facepalm Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * facepalm... again y u h8 me bruh Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fuk this fuk u tube fuck python fuck all Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * awyehfqwajefhnqeffawefqa eqewarfqaesf qwef qaf qwfr Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fuck everything Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * oh lord saviour resus i love you just make this work Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Change logic to be no errors within last 10 seconds... this should be a valid work around discord ratelimits caused by the spam * Remove auto deletion Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * See I did a ting Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * irdumb Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Add an is_url attribute to Query objects * chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black * Address Aikas review Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Hyperlink Playlist names Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Make shit bold Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * why was this here Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * why was this here Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Initial commit * Workinnng Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Improve SQL Statements + migrate from SQL Alchemy + Databases to APSW Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * apsw tested and working Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * chose Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * 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> * Revert "Migrate Playlist to DB 3 TODO 1 Migrate Config to Schema 3 without playlists and update get_playlist methods" This reverts commit4af33cffSigned-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Implement schema migration * Lets not touch the deps since #3192 is already adding them * chore * *sigh* Black * Follow the existing logic and always default Playlist to guild scope * wghqjegqf black * Update usage of last_fetched and last_updated to be Ints... However column migration still pending * Some bug fixes * Update usage of last_fetched and last_updated to be Ints... However column migration still pending * working Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * partial match Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * better partial match Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * I thought i done this before * Delete 3195.misc.1.rst Wrong PR * Thanks Sinbad Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Thanks Sinbad Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Log Errors in init ... Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Update error logs. Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Create index Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * :Drapersweat: Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Revert "Chore" This reverts commitedcc9a9fUGHHHH Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Allow removing tracks from queue by URL Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Words matter Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * arghhh CONFLICTS Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Review sinbads latest comment .. ToDo.. Nuke existing playlist - check version and set version Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * migrate the DB schema to v3 (to keep in line with the schema visioning of Config * Add a Todo * *sigh* conflicts and black * *sigh* black * Passively delete playlist deletion mechanism * Delete Old entries on startup * Since we are dropping the table mightaware make these into JSON for future proofing * Don't Dump strings in JSON field ? :think: * Move some things around to make easier to use 1 connection to the Audio DB * Move some things around to make easier to use 1 connection to the Audio DB * *sigh* * Clean up api * *sigh* black * Red + reorder some variables * 🤦 * how could i forget this ....... * Black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * #automagically Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * FINAFUCKINGLY Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * FINAFUCKINGLY Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Remove unused config default Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Remove the API from the audio Cog (Properly) Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Missed these changes Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * ARGHHH Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Some fixes I've noticed while running through the code line by line Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Translation + UX (show playlist author ID if can't find user) Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* missed this one Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * this is no longer needed .... Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * 🤦 Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fix new lines in error messages Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black * Sinbads Review Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Sinbads Review Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* copy paste Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * imrpove backups Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Im a fucking idiot Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Fix #3238 Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * chore Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * humans Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * humans Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * add play alias to playlists Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Im dumb ... Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Im dumb ... Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fix new line Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fix new line Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * show playlist count on playlist picker Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * DJ/Vote system fixes Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * DJ/Vote system fixes Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* fix currency check Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * show playlist count on playlist picker Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * DJ/Vote system fixes Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * DJ/Vote system fixes Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* fix currency check Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Fix duplicate messages on timeout Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fix SQL Statement logic Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * fix SQL Statement logic Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Markdown escape Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Markdown escape Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Markdown escape fix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Markdown escape fix Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * clean up local cache more frequently Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * clean up db more frequently Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Await in hell Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* im dumb Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* im dumb Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black cuz I hate red Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Black cuz I hate red Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * StringIO to ByteIO Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * StringIO to ByteIO Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* im dumb Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * :Facepalm: the whole purpose of this is so its offline so this can be backed up without being blocking Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Run write queries on ThreadPoolExecutor Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * Backup Audio.db Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* im dumb Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * blaaaack Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * *sigh* Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * formatting Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * remove duplicated string of code Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> * ffs awaits Signed-off-by: Drapersniper <27962761+drapersniper@users.noreply.github.com> Co-authored-by: Michael H <michael@michaelhall.tech>
This commit is contained in:
372
redbot/cogs/audio/databases.py
Normal file
372
redbot/cogs/audio/databases.py
Normal file
@@ -0,0 +1,372 @@
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import contextlib
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING, Tuple, Union, MutableMapping, Mapping
|
||||
|
||||
import apsw
|
||||
|
||||
from redbot.core import Config
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.data_manager import cog_data_path
|
||||
|
||||
from .errors import InvalidTableError
|
||||
from .sql_statements import *
|
||||
from .utils import PlaylistScope
|
||||
|
||||
log = logging.getLogger("red.audio.database")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
database_connection: apsw.Connection
|
||||
_bot: Red
|
||||
_config: Config
|
||||
else:
|
||||
_config = None
|
||||
_bot = None
|
||||
database_connection = None
|
||||
|
||||
|
||||
SCHEMA_VERSION = 3
|
||||
SQLError = apsw.ExecutionCompleteError
|
||||
|
||||
|
||||
_PARSER: Mapping = {
|
||||
"youtube": {
|
||||
"insert": YOUTUBE_UPSERT,
|
||||
"youtube_url": {"query": YOUTUBE_QUERY},
|
||||
"update": YOUTUBE_UPDATE,
|
||||
},
|
||||
"spotify": {
|
||||
"insert": SPOTIFY_UPSERT,
|
||||
"track_info": {"query": SPOTIFY_QUERY},
|
||||
"update": SPOTIFY_UPDATE,
|
||||
},
|
||||
"lavalink": {
|
||||
"insert": LAVALINK_UPSERT,
|
||||
"data": {"query": LAVALINK_QUERY, "played": LAVALINK_QUERY_LAST_FETCHED_RANDOM},
|
||||
"update": LAVALINK_UPDATE,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _pass_config_to_databases(config: Config, bot: Red):
|
||||
global _config, _bot, database_connection
|
||||
if _config is None:
|
||||
_config = config
|
||||
if _bot is None:
|
||||
_bot = bot
|
||||
if database_connection is None:
|
||||
database_connection = apsw.Connection(
|
||||
str(cog_data_path(_bot.get_cog("Audio")) / "Audio.db")
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlaylistFetchResult:
|
||||
playlist_id: int
|
||||
playlist_name: str
|
||||
scope_id: int
|
||||
author_id: int
|
||||
playlist_url: Optional[str] = None
|
||||
tracks: List[MutableMapping] = field(default_factory=lambda: [])
|
||||
|
||||
def __post_init__(self):
|
||||
if isinstance(self.tracks, str):
|
||||
self.tracks = json.loads(self.tracks)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CacheFetchResult:
|
||||
query: Optional[Union[str, MutableMapping]]
|
||||
last_updated: int
|
||||
|
||||
def __post_init__(self):
|
||||
if isinstance(self.last_updated, int):
|
||||
self.updated_on: datetime.datetime = datetime.datetime.fromtimestamp(self.last_updated)
|
||||
if isinstance(self.query, str) and all(
|
||||
k in self.query for k in ["loadType", "playlistInfo", "isSeekable", "isStream"]
|
||||
):
|
||||
self.query = json.loads(self.query)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CacheLastFetchResult:
|
||||
tracks: List[MutableMapping] = field(default_factory=lambda: [])
|
||||
|
||||
def __post_init__(self):
|
||||
if isinstance(self.tracks, str):
|
||||
self.tracks = json.loads(self.tracks)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CacheGetAllLavalink:
|
||||
query: str
|
||||
data: List[MutableMapping] = field(default_factory=lambda: [])
|
||||
|
||||
def __post_init__(self):
|
||||
if isinstance(self.data, str):
|
||||
self.data = json.loads(self.data)
|
||||
|
||||
|
||||
class CacheInterface:
|
||||
def __init__(self):
|
||||
self.database = database_connection.cursor()
|
||||
|
||||
@staticmethod
|
||||
def close():
|
||||
with contextlib.suppress(Exception):
|
||||
database_connection.close()
|
||||
|
||||
async def init(self):
|
||||
self.database.execute(PRAGMA_SET_temp_store)
|
||||
self.database.execute(PRAGMA_SET_journal_mode)
|
||||
self.database.execute(PRAGMA_SET_read_uncommitted)
|
||||
self.maybe_migrate()
|
||||
|
||||
self.database.execute(LAVALINK_CREATE_TABLE)
|
||||
self.database.execute(LAVALINK_CREATE_INDEX)
|
||||
self.database.execute(YOUTUBE_CREATE_TABLE)
|
||||
self.database.execute(YOUTUBE_CREATE_INDEX)
|
||||
self.database.execute(SPOTIFY_CREATE_TABLE)
|
||||
self.database.execute(SPOTIFY_CREATE_INDEX)
|
||||
|
||||
await self.clean_up_old_entries()
|
||||
|
||||
async def clean_up_old_entries(self):
|
||||
max_age = await _config.cache_age()
|
||||
maxage = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(days=max_age)
|
||||
maxage_int = int(time.mktime(maxage.timetuple()))
|
||||
values = {"maxage": maxage_int}
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
|
||||
executor.submit(self.database.execute, LAVALINK_DELETE_OLD_ENTRIES, values)
|
||||
executor.submit(self.database.execute, YOUTUBE_DELETE_OLD_ENTRIES, values)
|
||||
executor.submit(self.database.execute, SPOTIFY_DELETE_OLD_ENTRIES, values)
|
||||
|
||||
def maybe_migrate(self):
|
||||
current_version = self.database.execute(PRAGMA_FETCH_user_version).fetchone()
|
||||
if isinstance(current_version, tuple):
|
||||
current_version = current_version[0]
|
||||
if current_version == SCHEMA_VERSION:
|
||||
return
|
||||
self.database.execute(PRAGMA_SET_user_version, {"version": SCHEMA_VERSION})
|
||||
|
||||
async def insert(self, table: str, values: List[MutableMapping]):
|
||||
try:
|
||||
query = _PARSER.get(table, {}).get("insert")
|
||||
if query is None:
|
||||
raise InvalidTableError(f"{table} is not a valid table in the database.")
|
||||
self.database.execute("BEGIN;")
|
||||
self.database.executemany(query, values)
|
||||
self.database.execute("COMMIT;")
|
||||
except Exception as err:
|
||||
log.debug("Error during audio db insert", exc_info=err)
|
||||
|
||||
async def update(self, table: str, values: Dict[str, Union[str, int]]):
|
||||
try:
|
||||
table = _PARSER.get(table, {})
|
||||
sql_query = table.get("update")
|
||||
time_now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
|
||||
values["last_fetched"] = time_now
|
||||
if not table:
|
||||
raise InvalidTableError(f"{table} is not a valid table in the database.")
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
executor.submit(self.database.execute, sql_query, values)
|
||||
except Exception as err:
|
||||
log.debug("Error during audio db update", exc_info=err)
|
||||
|
||||
async def fetch_one(
|
||||
self, table: str, query: str, values: Dict[str, Union[str, int]]
|
||||
) -> Tuple[Optional[str], bool]:
|
||||
table = _PARSER.get(table, {})
|
||||
sql_query = table.get(query, {}).get("query")
|
||||
if not table:
|
||||
raise InvalidTableError(f"{table} is not a valid table in the database.")
|
||||
max_age = await _config.cache_age()
|
||||
maxage = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(days=max_age)
|
||||
maxage_int = int(time.mktime(maxage.timetuple()))
|
||||
values.update({"maxage": maxage_int})
|
||||
output = self.database.execute(sql_query, values).fetchone() or (None, 0)
|
||||
result = CacheFetchResult(*output)
|
||||
return result.query, False
|
||||
|
||||
async def fetch_all(
|
||||
self, table: str, query: str, values: Dict[str, Union[str, int]]
|
||||
) -> List[CacheLastFetchResult]:
|
||||
table = _PARSER.get(table, {})
|
||||
sql_query = table.get(query, {}).get("played")
|
||||
if not table:
|
||||
raise InvalidTableError(f"{table} is not a valid table in the database.")
|
||||
|
||||
output = []
|
||||
for index, row in enumerate(self.database.execute(sql_query, values), start=1):
|
||||
if index % 50 == 0:
|
||||
await asyncio.sleep(0.01)
|
||||
output.append(CacheLastFetchResult(*row))
|
||||
return output
|
||||
|
||||
async def fetch_random(
|
||||
self, table: str, query: str, values: Dict[str, Union[str, int]]
|
||||
) -> CacheLastFetchResult:
|
||||
table = _PARSER.get(table, {})
|
||||
sql_query = table.get(query, {}).get("played")
|
||||
if not table:
|
||||
raise InvalidTableError(f"{table} is not a valid table in the database.")
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
for future in concurrent.futures.as_completed(
|
||||
[executor.submit(self.database.execute, sql_query, values)]
|
||||
):
|
||||
try:
|
||||
row = future.result()
|
||||
row = row.fetchone()
|
||||
except Exception as exc:
|
||||
log.debug(f"Failed to completed random fetch from database", exc_info=exc)
|
||||
return CacheLastFetchResult(*row)
|
||||
|
||||
|
||||
class PlaylistInterface:
|
||||
def __init__(self):
|
||||
self.cursor = database_connection.cursor()
|
||||
self.cursor.execute(PRAGMA_SET_temp_store)
|
||||
self.cursor.execute(PRAGMA_SET_journal_mode)
|
||||
self.cursor.execute(PRAGMA_SET_read_uncommitted)
|
||||
self.cursor.execute(PLAYLIST_CREATE_TABLE)
|
||||
self.cursor.execute(PLAYLIST_CREATE_INDEX)
|
||||
|
||||
@staticmethod
|
||||
def close():
|
||||
with contextlib.suppress(Exception):
|
||||
database_connection.close()
|
||||
|
||||
@staticmethod
|
||||
def get_scope_type(scope: str) -> int:
|
||||
if scope == PlaylistScope.GLOBAL.value:
|
||||
table = 1
|
||||
elif scope == PlaylistScope.USER.value:
|
||||
table = 3
|
||||
else:
|
||||
table = 2
|
||||
return table
|
||||
|
||||
def fetch(self, scope: str, playlist_id: int, scope_id: int) -> PlaylistFetchResult:
|
||||
scope_type = self.get_scope_type(scope)
|
||||
row = (
|
||||
self.cursor.execute(
|
||||
PLAYLIST_FETCH,
|
||||
({"playlist_id": playlist_id, "scope_id": scope_id, "scope_type": scope_type}),
|
||||
).fetchone()
|
||||
or []
|
||||
)
|
||||
|
||||
return PlaylistFetchResult(*row) if row else None
|
||||
|
||||
async def fetch_all(
|
||||
self, scope: str, scope_id: int, author_id=None
|
||||
) -> List[PlaylistFetchResult]:
|
||||
scope_type = self.get_scope_type(scope)
|
||||
if author_id is not None:
|
||||
output = []
|
||||
for index, row in enumerate(
|
||||
self.cursor.execute(
|
||||
PLAYLIST_FETCH_ALL_WITH_FILTER,
|
||||
({"scope_type": scope_type, "scope_id": scope_id, "author_id": author_id}),
|
||||
),
|
||||
start=1,
|
||||
):
|
||||
if index % 50 == 0:
|
||||
await asyncio.sleep(0.01)
|
||||
output.append(row)
|
||||
else:
|
||||
output = []
|
||||
for index, row in enumerate(
|
||||
self.cursor.execute(
|
||||
PLAYLIST_FETCH_ALL, ({"scope_type": scope_type, "scope_id": scope_id})
|
||||
),
|
||||
start=1,
|
||||
):
|
||||
if index % 50 == 0:
|
||||
await asyncio.sleep(0.01)
|
||||
output.append(row)
|
||||
return [PlaylistFetchResult(*row) for row in output] if output else []
|
||||
|
||||
async def fetch_all_converter(
|
||||
self, scope: str, playlist_name, playlist_id
|
||||
) -> List[PlaylistFetchResult]:
|
||||
scope_type = self.get_scope_type(scope)
|
||||
try:
|
||||
playlist_id = int(playlist_id)
|
||||
except Exception:
|
||||
playlist_id = -1
|
||||
|
||||
output = []
|
||||
for index, row in enumerate(
|
||||
self.cursor.execute(
|
||||
PLAYLIST_FETCH_ALL_CONVERTER,
|
||||
(
|
||||
{
|
||||
"scope_type": scope_type,
|
||||
"playlist_name": playlist_name,
|
||||
"playlist_id": playlist_id,
|
||||
}
|
||||
),
|
||||
),
|
||||
start=1,
|
||||
):
|
||||
if index % 50 == 0:
|
||||
await asyncio.sleep(0.01)
|
||||
output.append(row)
|
||||
return [PlaylistFetchResult(*row) for row in output] if output else []
|
||||
|
||||
def delete(self, scope: str, playlist_id: int, scope_id: int):
|
||||
scope_type = self.get_scope_type(scope)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
executor.submit(
|
||||
self.cursor.execute,
|
||||
PLAYLIST_DELETE,
|
||||
({"playlist_id": playlist_id, "scope_id": scope_id, "scope_type": scope_type}),
|
||||
)
|
||||
|
||||
def delete_scheduled(self):
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
executor.submit(self.cursor.execute, PLAYLIST_DELETE_SCHEDULED)
|
||||
|
||||
def drop(self, scope: str):
|
||||
scope_type = self.get_scope_type(scope)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
executor.submit(
|
||||
self.cursor.execute, PLAYLIST_DELETE_SCOPE, ({"scope_type": scope_type})
|
||||
)
|
||||
|
||||
def create_table(self, scope: str):
|
||||
scope_type = self.get_scope_type(scope)
|
||||
return self.cursor.execute(PLAYLIST_CREATE_TABLE, ({"scope_type": scope_type}))
|
||||
|
||||
def upsert(
|
||||
self,
|
||||
scope: str,
|
||||
playlist_id: int,
|
||||
playlist_name: str,
|
||||
scope_id: int,
|
||||
author_id: int,
|
||||
playlist_url: Optional[str],
|
||||
tracks: List[MutableMapping],
|
||||
):
|
||||
scope_type = self.get_scope_type(scope)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
executor.submit(
|
||||
self.cursor.execute,
|
||||
PLAYLIST_UPSERT,
|
||||
{
|
||||
"scope_type": str(scope_type),
|
||||
"playlist_id": int(playlist_id),
|
||||
"playlist_name": str(playlist_name),
|
||||
"scope_id": int(scope_id),
|
||||
"author_id": int(author_id),
|
||||
"playlist_url": playlist_url,
|
||||
"tracks": json.dumps(tracks),
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user