async def upsert(
     self,
     scope: str,
     playlist_id: int,
     playlist_name: str,
     scope_id: int,
     author_id: int,
     playlist_url: Optional[str],
     tracks: List[MutableMapping],
 ):
     """Insert or update a playlist into the database."""
     scope_type = self.get_scope_type(scope)
     with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
         executor.submit(
             self.database.cursor().execute,
             self.statement.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),
             },
         )
 async def enqueued(self, guild_id: int, room_id: int, track: lavalink.Track):
     enqueue_time = track.extras.get("enqueue_time", 0)
     if enqueue_time == 0:
         track.extras["enqueue_time"] = int(time.time())
     track_identifier = track.track_identifier
     track = self.cog.track_to_json(track)
     with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
         executor.submit(
             self.database.cursor().execute,
             PERSIST_QUEUE_UPSERT,
             {
                 "guild_id": int(guild_id),
                 "room_id": int(room_id),
                 "played": False,
                 "time": enqueue_time,
                 "track": json.dumps(track),
                 "track_id": track_identifier,
             },
         )
Exemple #3
0
    async def _load_v3_playlist(
        self,
        ctx: commands.Context,
        scope: str,
        uploaded_playlist_name: str,
        uploaded_playlist_url: str,
        track_list: List,
        author: Union[discord.User, discord.Member],
        guild: Union[discord.Guild],
    ) -> None:
        embed1 = discord.Embed(title=_("Please wait, adding tracks..."))
        playlist_msg = await self.send_embed_msg(ctx, embed=embed1)
        track_count = len(track_list)
        uploaded_track_count = len(track_list)
        await asyncio.sleep(1)
        embed2 = discord.Embed(
            colour=await ctx.embed_colour(),
            title=_("Loading track {num}/{total}...").format(
                num=track_count, total=uploaded_track_count),
        )
        await playlist_msg.edit(embed=embed2)

        playlist = await create_playlist(
            ctx,
            self.playlist_api,
            scope,
            uploaded_playlist_name,
            uploaded_playlist_url,
            track_list,
            author,
            guild,
        )
        scope_name = self.humanize_scope(
            scope, ctx=guild if scope == PlaylistScope.GUILD.value else author)
        if not track_count:
            msg = _("Empty playlist {name} (`{id}`) [**{scope}**] created."
                    ).format(name=playlist.name,
                             id=playlist.id,
                             scope=scope_name)
        elif uploaded_track_count != track_count:
            bad_tracks = uploaded_track_count - track_count
            msg = _(
                "Added {num} tracks from the {playlist_name} playlist. {num_bad} track(s) "
                "could not be loaded.").format(num=track_count,
                                               playlist_name=playlist.name,
                                               num_bad=bad_tracks)
        else:
            msg = _("Added {num} tracks from the {playlist_name} playlist."
                    ).format(num=track_count, playlist_name=playlist.name)
        embed3 = discord.Embed(colour=await ctx.embed_colour(),
                               title=_("Playlist Saved"),
                               description=msg)
        await playlist_msg.edit(embed=embed3)
        database_entries = []
        time_now = int(
            datetime.datetime.now(datetime.timezone.utc).timestamp())
        async for t in AsyncIter(track_list):
            uri = t.get("info", {}).get("uri")
            if uri:
                t = {"loadType": "V2_COMPAT", "tracks": [t], "query": uri}
                data = json.dumps(t)
                if all(k in data for k in
                       ["loadType", "playlistInfo", "isSeekable", "isStream"]):
                    database_entries.append({
                        "query": uri,
                        "data": data,
                        "last_updated": time_now,
                        "last_fetched": time_now,
                    })
        if database_entries:
            await self.api_interface.local_cache_api.lavalink.insert(
                database_entries)
Exemple #4
0
    async def fetch_track(
        self,
        ctx: commands.Context,
        player: lavalink.Player,
        query: Query,
        forced: bool = False,
        lazy: bool = False,
        should_query_global: bool = True,
    ) -> Tuple[LoadResult, bool]:
        """A replacement for :code:`lavalink.Player.load_tracks`. This will try to get a valid
        cached entry first if not found or if in valid it will then call the lavalink API.

        Parameters
        ----------
        ctx: commands.Context
            The context this method is being called under.
        player : lavalink.Player
            The player who's requesting the query.
        query: audio_dataclasses.Query
            The Query object for the query in question.
        forced:bool
            Whether or not to skip cache and call API first.
        lazy:bool
            If set to True, it will not call the api if a track is not found.
        should_query_global:bool
            If the method should query the global database.

        Returns
        -------
        Tuple[lavalink.LoadResult, bool]
            Tuple with the Load result and whether or not the API was called.
        """
        current_cache_level = CacheLevel(await self.config.cache_level())
        cache_enabled = CacheLevel.set_lavalink().is_subset(
            current_cache_level)
        val = None
        query = Query.process_input(query, self.cog.local_folder_current_path)
        query_string = str(query)
        globaldb_toggle = self.cog.global_api_user.get("can_read")
        valid_global_entry = False
        results = None
        called_api = False
        prefer_lyrics = await self.cog.get_lyrics_status(ctx)
        if prefer_lyrics and query.is_youtube and query.is_search:
            query_string = f"{query} - lyrics"
        if cache_enabled and not forced and not query.is_local:
            try:
                (val,
                 last_updated) = await self.local_cache_api.lavalink.fetch_one(
                     {"query": query_string})
            except Exception as exc:
                debug_exc_log(
                    log, exc,
                    f"Failed to fetch '{query_string}' from Lavalink table")

            if val and isinstance(val, dict):
                if IS_DEBUG:
                    log.debug(f"Updating Local Database with {query_string}")
                task = ("update", ("lavalink", {"query": query_string}))
                self.append_task(ctx, *task)
            else:
                val = None

            if val and not forced and isinstance(val, dict):
                valid_global_entry = False
                called_api = False
            else:
                val = None
        if (globaldb_toggle and not val and should_query_global and not forced
                and not query.is_local and not query.is_spotify):
            valid_global_entry = False
            with contextlib.suppress(Exception):
                global_entry = await self.global_cache_api.get_call(query=query
                                                                    )
                if global_entry.get("loadType") == "V2_COMPACT":
                    global_entry["loadType"] = "V2_COMPAT"
                results = LoadResult(global_entry)
                if results.load_type in [
                        LoadType.PLAYLIST_LOADED,
                        LoadType.TRACK_LOADED,
                        LoadType.SEARCH_RESULT,
                        LoadType.V2_COMPAT,
                ]:
                    valid_global_entry = True
                if valid_global_entry:
                    if IS_DEBUG:
                        log.debug(f"Querying Global DB api for {query}")
                    results, called_api = results, False
        if valid_global_entry:
            pass
        elif lazy is True:
            called_api = False
        elif val and not forced and isinstance(val, dict):
            data = val
            data["query"] = query_string
            if data.get("loadType") == "V2_COMPACT":
                data["loadType"] = "V2_COMPAT"
            results = LoadResult(data)
            called_api = False
            if results.has_error:
                # If cached value has an invalid entry make a new call so that it gets updated
                results, called_api = await self.fetch_track(ctx,
                                                             player,
                                                             query,
                                                             forced=True)
            valid_global_entry = False
        else:
            if IS_DEBUG:
                log.debug(f"Querying Lavalink api for {query_string}")
            called_api = True
            try:
                results = await player.load_tracks(query_string)
            except KeyError:
                results = None
            except RuntimeError:
                raise TrackEnqueueError
        if results is None:
            results = LoadResult({
                "loadType": "LOAD_FAILED",
                "playlistInfo": {},
                "tracks": []
            })
            valid_global_entry = False
        update_global = (globaldb_toggle and not valid_global_entry
                         and self.global_cache_api.has_api_key)
        with contextlib.suppress(Exception):
            if (update_global and not query.is_local and not results.has_error
                    and len(results.tracks) >= 1):
                global_task = ("global", dict(llresponse=results, query=query))
                self.append_task(ctx, *global_task)
        if (cache_enabled and results.load_type and not results.has_error
                and not query.is_local and results.tracks):
            try:
                time_now = int(
                    datetime.datetime.now(datetime.timezone.utc).timestamp())
                data = json.dumps(results._raw)
                if all(k in data for k in
                       ["loadType", "playlistInfo", "isSeekable", "isStream"]):
                    task = (
                        "insert",
                        (
                            "lavalink",
                            [{
                                "query": query_string,
                                "data": data,
                                "last_updated": time_now,
                                "last_fetched": time_now,
                            }],
                        ),
                    )
                    self.append_task(ctx, *task)
            except Exception as exc:
                debug_exc_log(
                    log,
                    exc,
                    f"Failed to enqueue write task for '{query_string}' to Lavalink table",
                )
        return results, called_api
Exemple #5
0
    async def data_schema_migration(self, from_version: int,
                                    to_version: int) -> None:
        database_entries = []
        time_now = int(
            datetime.datetime.now(datetime.timezone.utc).timestamp())
        if from_version == to_version:
            return
        if from_version < 2 <= to_version:
            all_guild_data = await self.config.all_guilds()
            all_playlist = {}
            async for guild_id, guild_data in AsyncIter(
                    all_guild_data.items()):
                temp_guild_playlist = guild_data.pop("playlists", None)
                if temp_guild_playlist:
                    guild_playlist = {}
                    async for count, (name, data) in AsyncIter(
                            temp_guild_playlist.items()).enumerate(start=1000):
                        if not data or not name:
                            continue
                        playlist = {
                            "id": count,
                            "name": name,
                            "guild": int(guild_id)
                        }
                        playlist.update(data)
                        guild_playlist[str(count)] = playlist

                        tracks_in_playlist = data.get("tracks", []) or []
                        async for t in AsyncIter(tracks_in_playlist):
                            uri = t.get("info", {}).get("uri")
                            if uri:
                                t = {
                                    "loadType": "V2_COMPAT",
                                    "tracks": [t],
                                    "query": uri,
                                }
                                data = json.dumps(t)
                                if all(k in data for k in [
                                        "loadType",
                                        "playlistInfo",
                                        "isSeekable",
                                        "isStream",
                                ]):
                                    database_entries.append({
                                        "query":
                                        uri,
                                        "data":
                                        data,
                                        "last_updated":
                                        time_now,
                                        "last_fetched":
                                        time_now,
                                    })
                    if guild_playlist:
                        all_playlist[str(guild_id)] = guild_playlist
            await self.config.custom(PlaylistScope.GUILD.value
                                     ).set(all_playlist)
            # new schema is now in place
            await self.config.schema_version.set(2)

            # migration done, now let's delete all the old stuff
            async for guild_id in AsyncIter(all_guild_data):
                await self.config.guild(
                    cast(discord.Guild,
                         discord.Object(id=guild_id))).clear_raw("playlists")
        if from_version < 3 <= to_version:
            for scope in PlaylistScope.list():
                scope_playlist = await get_all_playlist_for_migration23(
                    self.bot, self.playlist_api, self.config, scope)
                async for p in AsyncIter(scope_playlist):
                    await p.save()
                await self.config.custom(scope).clear()
            await self.config.schema_version.set(3)

        if database_entries:
            await self.api_interface.local_cache_api.lavalink.insert(
                database_entries)