fix: store playback history per Discord server
This commit is contained in:
5 files changed
+74
-14
No files matched your search
@@ -23,6 +23,8 @@ jest dostępny pod `http://localhost` po wykonaniu `docker compose up --build`.
|
||||
Panel obsługuje wiele serwerów Discorda. Wybór serwera w nagłówku przełącza jego
|
||||
osobną kolejkę, historię, bazę plików i ustawienia odtwarzania. Istniejące dane ze
|
||||
starego, wspólnego formatu są przypisywane do pierwszego użytego serwera.
|
||||
Historia jest zapisywana w osobnych plikach `config/history/<guild_id>.json`, a
|
||||
uploady w katalogach `uploads/<guild_id>/`.
|
||||
|
||||
## Testy
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from zyrarbot.music.history_entry import HistoryEntry
|
||||
from zyrarbot.storage.history_repository import HistoryRepository
|
||||
from zyrarbot.storage.json_file_store import JsonFileStore
|
||||
|
||||
|
||||
class HistoryRepositoryTest(unittest.TestCase):
|
||||
def test_history_is_stored_in_separate_files_and_legacy_data_is_migrated(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
legacy_path = Path(directory) / "history.json"
|
||||
entry = HistoryEntry(
|
||||
id="track-1",
|
||||
title="Track",
|
||||
url="https://example.test/track",
|
||||
duration=10,
|
||||
is_local=False,
|
||||
filepath=None,
|
||||
)
|
||||
legacy_path.write_text(json.dumps({"10": [entry.to_dict()]}), encoding="utf-8")
|
||||
repository = HistoryRepository(JsonFileStore(str(legacy_path)))
|
||||
|
||||
repository.migrate_legacy(10)
|
||||
|
||||
self.assertEqual(repository.list(10)[0].id, "track-1")
|
||||
self.assertEqual(repository.list(20), [])
|
||||
self.assertTrue((Path(directory) / "history" / "10.json").exists())
|
||||
self.assertFalse((Path(directory) / "history" / "20.json").exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -43,7 +43,7 @@ class Application:
|
||||
self.uploads = UploadsRepository(self.settings.uploads_dir)
|
||||
self.tracks = TrackFactory(self.uploads, MediaProbe())
|
||||
|
||||
self.bot = MusicBot(self.settings, self.tracks, self.uploads, self.config)
|
||||
self.bot = MusicBot(self.settings, self.tracks, self.uploads, self.config, self.history)
|
||||
self.guilds = GuildResolver(self.bot)
|
||||
self.player = MusicPlayer(
|
||||
bot=self.bot,
|
||||
|
||||
@@ -1,17 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from zyrarbot.music.history_entry import HistoryEntry
|
||||
from zyrarbot.storage.json_file_store import JsonFileStore
|
||||
|
||||
|
||||
class HistoryRepository:
|
||||
"""Historia odtwarzania per serwer, buforowana i utrwalana w history.json."""
|
||||
"""Historia odtwarzania w osobnym pliku dla każdego serwera."""
|
||||
|
||||
def __init__(self, store: JsonFileStore, limit: int = 50):
|
||||
self._store = store
|
||||
self._limit = limit
|
||||
self._cache: dict[int, list[HistoryEntry]] = {}
|
||||
self._legacy_guild_id: int | None = None
|
||||
self._guild_stores: dict[int, JsonFileStore] = {}
|
||||
self._legacy_migrated = False
|
||||
|
||||
def _guild_store(self, guild_id: int) -> JsonFileStore:
|
||||
if guild_id not in self._guild_stores:
|
||||
directory = os.path.join(os.path.dirname(self._store.path), "history")
|
||||
path = os.path.join(directory, f"{guild_id}.json")
|
||||
self._guild_stores[guild_id] = JsonFileStore(path)
|
||||
return self._guild_stores[guild_id]
|
||||
|
||||
def migrate_legacy(self, first_guild_id: int) -> None:
|
||||
"""Move the old shared history file into one file per guild."""
|
||||
if self._legacy_migrated:
|
||||
return
|
||||
|
||||
legacy_data = self._store.read()
|
||||
for key, entries in legacy_data.items():
|
||||
if key == "history" and isinstance(entries, list):
|
||||
self._write_entries(first_guild_id, entries)
|
||||
elif key.isdigit() and isinstance(entries, list):
|
||||
self._write_entries(int(key), entries)
|
||||
self._legacy_migrated = True
|
||||
|
||||
def list(self, guild_id: int) -> list[HistoryEntry]:
|
||||
if guild_id not in self._cache:
|
||||
@@ -30,16 +53,7 @@ class HistoryRepository:
|
||||
return next((item for item in self.list(guild_id) if item.id == entry_id), None)
|
||||
|
||||
def _load(self, guild_id: int) -> list[HistoryEntry]:
|
||||
raw_entries = self._store.get(str(guild_id))
|
||||
if raw_entries is None and self._legacy_guild_id is None:
|
||||
legacy = self._store.get("history", [])
|
||||
if isinstance(legacy, list):
|
||||
raw_entries = legacy
|
||||
self._legacy_guild_id = guild_id
|
||||
self._store.set(str(guild_id), legacy)
|
||||
data = self._store.read()
|
||||
data.pop("history", None)
|
||||
self._store.write(data)
|
||||
raw_entries = self._guild_store(guild_id).get("entries", [])
|
||||
raw_entries = raw_entries or []
|
||||
entries: list[HistoryEntry] = []
|
||||
seen: set[tuple[str, str | None]] = set()
|
||||
@@ -53,4 +67,9 @@ class HistoryRepository:
|
||||
return entries
|
||||
|
||||
def _persist(self, guild_id: int) -> None:
|
||||
self._store.set(str(guild_id), [entry.to_dict() for entry in self._cache.get(guild_id, [])])
|
||||
self._guild_store(guild_id).set(
|
||||
"entries", [entry.to_dict() for entry in self._cache.get(guild_id, [])]
|
||||
)
|
||||
|
||||
def _write_entries(self, guild_id: int, entries: list[dict]) -> None:
|
||||
self._guild_store(guild_id).set("entries", entries[: self._limit])
|
||||
@@ -9,6 +9,7 @@ from zyrarbot.audio.track_factory import TrackFactory
|
||||
from zyrarbot.music.music_player import MusicPlayer
|
||||
from zyrarbot.settings import Settings
|
||||
from zyrarbot.storage.guild_config_repository import GuildConfigRepository
|
||||
from zyrarbot.storage.history_repository import HistoryRepository
|
||||
from zyrarbot.storage.uploads_repository import UploadsRepository
|
||||
from zyrarbot.ui.bot.cogs.control_cog import ControlCog
|
||||
from zyrarbot.ui.bot.cogs.playback_cog import PlaybackCog
|
||||
@@ -32,6 +33,7 @@ class MusicBot(commands.Bot):
|
||||
tracks: TrackFactory,
|
||||
uploads: UploadsRepository,
|
||||
config: GuildConfigRepository,
|
||||
history: HistoryRepository,
|
||||
):
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
@@ -42,6 +44,7 @@ class MusicBot(commands.Bot):
|
||||
self.tracks = tracks
|
||||
self.uploads = uploads
|
||||
self.config = config
|
||||
self.history = history
|
||||
self.player: MusicPlayer | None = None
|
||||
|
||||
def _create_music_cogs(self) -> tuple[commands.Cog, ...]:
|
||||
@@ -77,5 +80,6 @@ class MusicBot(commands.Bot):
|
||||
first_guild_id = self.guilds[0].id
|
||||
self.uploads.migrate_legacy(first_guild_id)
|
||||
self.config.migrate_legacy(first_guild_id)
|
||||
self.history.migrate_legacy(first_guild_id)
|
||||
if self.user:
|
||||
logger.info("Zalogowano jako %s (%s)", self.user.name, self.user.id)
|
||||
Reference in new issue
Block a user