improved logging - added save / load command for playlist

This commit is contained in:
hendrik 2025-05-18 15:01:10 +02:00
parent 9968a4e74a
commit 361a5df7cd
13 changed files with 165 additions and 31 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
/target
.env
/ressources
/resources

View File

@ -19,6 +19,7 @@ dotenv = "0.15.0"
serde = "1.0.219"
symphonia = { version = "0.5.4", features = ["mp3"] }
once_cell = "1.21.3"
tracing = "0.1.41"
[dependencies.songbird]

View File

@ -15,6 +15,7 @@ async fn ignore_stop_event() -> bool {
let mut last = LAST_STOP_EVENT.lock().await;
if let Some(last) = *last {
if last.elapsed() < EVENT_SILNCER {
tracing::warn!("Received Stop event but, ignoring it...");
return true;
}
}
@ -85,6 +86,7 @@ impl PlayHandle {
}
pub async fn leave(&mut self) -> Result<(), PlayerError> {
tracing::info!("Leaving channel");
self.player.leave().await
}
@ -116,7 +118,6 @@ impl PlayHandle {
pub async fn stop(&mut self) -> Result<(), PlayerError> {
ignore_stop_event().await;
println!("Stopping player from PlayHandle");
self.player.stop().await
}

View File

@ -1,6 +1,7 @@
use std::collections::{HashMap, VecDeque};
use rand::seq::SliceRandom;
use tracing::debug;
use crate::{
play::{
@ -215,7 +216,7 @@ impl PlayHandle {
self.qs_back(to_enqueue.into_iter().map(Into::into).collect())
.await;
}
println!("Handled Mix mode");
tracing::debug!("Handled Jukebox / Mix Mode");
Ok(())
}
}

View File

@ -7,6 +7,7 @@ use tokio::sync::Mutex;
pub mod handle;
pub mod jukebox;
pub mod persistence;
pub mod playlist;
pub struct PlayHandleWrapper(Arc<Mutex<handle::PlayHandle>>);
@ -36,10 +37,10 @@ impl SongEndHandler {
}
pub async fn on_stop_event(&self) {
println!("SongEndHandler: received stop event");
tracing::info!("SongEndHandler: received stop event");
let mut player = self.player.lock().await;
if let Err(e) = player.on_stop_event().await {
println!("Error playing next track: {e:?}");
tracing::error!("Error playing next track: {e:?}");
}
}
}

View File

@ -0,0 +1,70 @@
use core::num;
use std::{
fs::File,
io::{BufReader, BufWriter},
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use crate::play::{
QueueElement,
queue::{PlaylistModification, QueueStructure},
};
use super::handle::PlayHandle;
const PLAYLIST: &str = "playlist.json";
fn save_default<T: Serialize>(list: &T) -> Result<(), std::io::Error> {
save(list, PLAYLIST)
}
fn save<T: Serialize>(list: &T, path: &str) -> Result<(), std::io::Error> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
serde_json::to_writer(writer, list)?;
Ok(())
}
fn load<T: DeserializeOwned>(path: &str) -> Result<T, std::io::Error> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let list = serde_json::from_reader(reader)?;
Ok(list)
}
fn load_default<T: DeserializeOwned>() -> Result<T, std::io::Error> {
load(PLAYLIST)
}
impl PlayHandle {
pub fn save_playlist(&self) -> Result<usize, std::io::Error> {
let playlist = self.lists().q();
save_default(playlist)?;
Ok(playlist.len())
}
pub fn load_playlist(&mut self) -> Result<usize, std::io::Error> {
let playlist: Vec<QueueElement> = load_default()?;
let num = playlist.len();
self.lists_mut().clear();
self.lists_mut().qs_front(playlist);
Ok(num)
}
}
#[derive(Serialize, Deserialize, Debug)]
struct Test {
a: i32,
b: String,
}
#[test]
fn f() {
let test = Test {
a: 1,
b: "test".to_string(),
};
save_default(&test).unwrap();
let loaded: Test = load_default().unwrap();
assert_eq!(test.a, loaded.a);
assert_eq!(test.b, loaded.b);
}

View File

@ -147,6 +147,7 @@ impl QueueStructure for PlayHandle {
}
}
// Misc
impl PlayHandle {
pub fn q(&self) -> &Queue<QueueElement> {
self.lists().get_queue()

View File

@ -62,6 +62,29 @@ fn str_err(e: impl Display) -> Error {
format!("{e}").into()
}
trait ResLog {
fn log_mer(self, msg: &str) -> Self;
fn log_er(self) -> Self;
}
impl<T, E> ResLog for Result<T, E>
where
E: Display,
{
fn log_mer(self, msg: &str) -> Self {
if let Err(e) = &self {
tracing::error!("Error in {msg}: {e}");
}
self
}
fn log_er(self) -> Self {
if let Err(e) = &self {
tracing::error!("Error: {e}");
}
self
}
}
trait DiscordResultOutput<T> {
async fn say_err(self, ctx: &CmdContext<'_>) -> Result<T, Error>;
async fn say_msg_or_err<S: Into<String>>(
@ -132,19 +155,27 @@ async fn on_error(error: poise::FrameworkError<'_, ContextData, Error>) {
match error {
poise::FrameworkError::Setup { error, .. } => panic!("Failed to start bot: {error:?}"),
poise::FrameworkError::Command { error, ctx, .. } => {
println!("Error in command `{}`: {:?}", ctx.command().name, error,);
tracing::error!("Error in command `{}`: {:?}", ctx.command().name, error,);
}
error => {
if let Err(e) = poise::builtins::on_error(error).await {
println!("Error while handling error: {e}");
tracing::error!("Error while handling error: {e}");
}
}
}
}
fn setup() {
tracing_subscriber::fmt::init();
match dotenv::dotenv() {
Ok(_) => tracing::info!("Loaded env vars from .env"),
Err(e) => tracing::warn!("Failed to load env vars from .env: {e}"),
}
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
setup();
let options = poise::FrameworkOptions {
commands: vec![
help(),
@ -170,6 +201,8 @@ async fn main() {
playing(),
list(),
list_genre(),
save_list(),
load_list(),
],
prefix_options: poise::PrefixFrameworkOptions {
prefix: Some("~".into()),
@ -210,7 +243,7 @@ async fn main() {
skip_checks_for_owners: false,
event_handler: |_ctx, event, _framework, _data| {
Box::pin(async move {
println!(
tracing::info!(
"Got an event in event handler: {:?}",
event.snake_case_name()
);
@ -223,7 +256,7 @@ async fn main() {
let framework = poise::Framework::builder()
.setup(move |ctx, _ready, framework| {
Box::pin(async move {
println!("Logged in as {}", _ready.user.name);
tracing::info!("Logged in as {}", _ready.user.name);
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(ContextData {
last_request: Mutex::new(None),
@ -233,7 +266,6 @@ async fn main() {
})
.options(options)
.build();
dotenv::dotenv().ok();
let token = std::env::var("TOKEN")
.expect("Missing `DISCORD_TOKEN` env var, see README for more information.");
let intents = serenity::GatewayIntents::non_privileged()
@ -256,11 +288,14 @@ pub async fn fuckoff(ctx: CmdContext<'_>) -> Result<(), Error> {
.await
.leave()
.await
.say_msg_or_err("Bye, bye", &ctx)
.say_msg_or_err(
"Today is not all days. I will be back, no doubt about it.",
&ctx,
)
.await
}
// Clears playlist
/// Clears playlist
#[poise::command(prefix_command, track_edits, slash_command, category = "Player")]
pub async fn clear(ctx: CmdContext<'_>) -> Result<(), Error> {
let player = ctx.data().handle.inner();
@ -309,6 +344,7 @@ pub async fn search2(
query: String,
) -> Result<(), Error> {
let param = parse_cmd_solo_args(&query, &Cmd::Search2.allowed_param());
// todo fix type
if param.is_empty() {
ctx.say("No search parameter found").await?;
return Ok(());
@ -753,5 +789,26 @@ pub async fn shutdown(ctx: CmdContext<'_>) -> Result<(), Error> {
ctx.say("Alright then, bye.").await?;
let player = ctx.data().handle.inner();
_ = player.lock().await.leave().await;
tokio::time::sleep(Duration::from_secs(2)).await;
std::process::exit(0);
}
/// Save Playlist to file
#[poise::command(prefix_command, slash_command, category = "System")]
pub async fn save_list(ctx: CmdContext<'_>) -> Result<(), Error> {
let player = ctx.data().handle.inner();
let cnt = player.lock().await.save_playlist().log_er()?;
ctx.say(format!("Saved {cnt} entries from playlist"))
.await?;
Ok(())
}
/// Load Playlist to file
#[poise::command(prefix_command, slash_command, category = "System")]
pub async fn load_list(ctx: CmdContext<'_>) -> Result<(), Error> {
let player = ctx.data().handle.inner();
let cnt = player.lock().await.load_playlist().log_er()?;
ctx.say(format!("Added {cnt} entries from file to playlist"))
.await?;
Ok(())
}

View File

@ -1,3 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::sonic::response::TrackInfo;
pub mod cache;
@ -6,7 +8,7 @@ pub mod player;
pub mod queue;
pub mod state;
#[derive(Debug, Clone, std::hash::Hash, PartialEq, Eq, Default)]
#[derive(Debug, Clone, std::hash::Hash, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct QueueElement {
pub id: String,
pub title: String,

View File

@ -22,10 +22,9 @@ impl Player {
}
pub async fn stop(&mut self) -> Result<(), PlayerError> {
self.err_if_disconnect()?;
println!("Before stopping - state {:?}", self.state);
self.set_state(PlayerState::Stopped);
if let Some(call) = &self.call_handle {
println!("Stopping player");
tracing::info!("Stopping player");
let mut call = call.lock().await;
call.stop();
}
@ -110,7 +109,6 @@ impl Player {
let mut call = call.lock().await;
if self.state == PlayerState::Playing {
println!("Currently playing stopping .... ");
call.stop();
}
@ -146,5 +144,5 @@ impl Player {
}
fn state_update_notification(from: PlayerState, to: PlayerState) {
println!("Player state change from {:?} to {:?}", from, to);
tracing::info!("Player state change from {:?} to {:?}", from, to);
}

View File

@ -1,5 +1,7 @@
use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use super::err::PlayerError;
pub trait QueueStructure {
@ -7,7 +9,7 @@ pub trait QueueStructure {
fn is_empty(&self) -> bool;
}
#[derive(Default, Debug)]
#[derive(Default, Debug, Deserialize, Serialize)]
pub struct Queue<T>(VecDeque<T>);
impl<T> Queue<T> {
@ -171,15 +173,18 @@ impl<T> PlaylistModification<T> for SongLists<T> {
fn qs_front(&mut self, items: Vec<T>) {
let mut new_queue = items.into_iter().collect::<VecDeque<T>>();
tracing::info!("Adding {} items to front of the playlist", new_queue.len());
new_queue.append(&mut self.queue.0);
std::mem::swap(&mut self.queue.0, &mut new_queue);
}
fn qs_back(&mut self, items: Vec<T>) {
tracing::info!("Adding {} items to back of the playlist", items.len());
self.queue.0.extend(items);
}
fn clear(&mut self) {
tracing::info!("Clearing the play- and playedlist");
self.queue.0.clear();
self.played.0.clear();
}

View File

@ -62,16 +62,13 @@ impl Default for PlayerConfig {
impl PlayerConfig {
pub fn apply(&mut self, vol: VolChange) {
match vol {
VolChange::Increase => {
self.volume = (self.volume + 0.1).min(1.0);
}
VolChange::Decrease => {
self.volume = (self.volume - 0.1).max(0.0);
}
VolChange::Set(v) => {
self.volume = v;
}
}
let new_vol = match vol {
VolChange::Increase => (self.volume + 0.1).min(1.0),
VolChange::Decrease => (self.volume - 0.1).max(0.0),
VolChange::Set(v) => v,
};
tracing::info!("Changinging volume from {} to {}", self.volume, new_vol);
self.volume = new_vol;
}
}

View File

@ -62,7 +62,7 @@ async fn get_all_artist_track_as_q(
match c.await {
Ok(tracks) => results.extend(tracks),
Err(e) => {
println!("Error fetching album: {:?}", e);
tracing::warn!("Error fetching album: {:?}", e);
}
}
}