From 5595b02211a75e5e6e2e401a4747eb127414819f Mon Sep 17 00:00:00 2001 From: Victoria Fischer Date: Mon, 15 Jun 2026 15:27:20 +0200 Subject: [PATCH] artifacts: split out each artifact source into a submodule, move archive.rs into artifacts/beets.rs --- src/artifacts/bandcamp.rs | 28 ++++++++ src/{archive.rs => artifacts/beets.rs} | 0 src/artifacts/mixxx.rs | 63 ++++++++++++++++++ src/{artifacts.rs => artifacts/mod.rs} | 90 ++------------------------ src/main.rs | 1 - src/prediction.rs | 2 +- src/ui.rs | 4 +- 7 files changed, 98 insertions(+), 90 deletions(-) create mode 100644 src/artifacts/bandcamp.rs rename src/{archive.rs => artifacts/beets.rs} (100%) create mode 100644 src/artifacts/mixxx.rs rename src/{artifacts.rs => artifacts/mod.rs} (53%) diff --git a/src/artifacts/bandcamp.rs b/src/artifacts/bandcamp.rs new file mode 100644 index 0000000..f5d635d --- /dev/null +++ b/src/artifacts/bandcamp.rs @@ -0,0 +1,28 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::artifacts::{Album, Artifact, Artist, SourceID}; + +#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] +pub struct BandcampQueryArgs { + pub query: String +} + +impl Into for bandcamp::Artist { + fn into(self) -> Artifact { + Artifact::Artist(Artist { name: self.name, bio: self.bio, location: self.location, sources: vec![SourceID::Bandcamp(self.id)] }) + } +} + +impl Into for bandcamp::Album { + fn into(self) -> Artifact { + Artifact::Album(Album { + about: self.about, + title: self.title, + artist: self.band.name, + credits: self.credits, + release_date: Some(self.release_date), + sources: vec!{SourceID::Bandcamp(self.id)} + }) + } +} \ No newline at end of file diff --git a/src/archive.rs b/src/artifacts/beets.rs similarity index 100% rename from src/archive.rs rename to src/artifacts/beets.rs diff --git a/src/artifacts/mixxx.rs b/src/artifacts/mixxx.rs new file mode 100644 index 0000000..7693178 --- /dev/null +++ b/src/artifacts/mixxx.rs @@ -0,0 +1,63 @@ +use sqlite::OpenFlags; + +use crate::artifacts::{Album, Artifact, Artist, SourceID, Track}; + +#[derive(Debug)] +#[allow(unused)] +pub enum MixxxError { + Sql(sqlite::Error) +} + +impl From for MixxxError { + fn from(value: sqlite::Error) -> Self { + Self::Sql(value) + } +} + +pub struct MixxxDB(()); + +impl MixxxDB { + pub fn load(episode_number: u32) -> Result, MixxxError> { + let mut ret = vec![]; + let playlist_name = format!("BFF.fm - Episode {}", episode_number); + log::info!("Loading Mixxx playlist {}", playlist_name); + let connection = sqlite::Connection::open_thread_safe_with_flags("mixxxdb.sqlite", OpenFlags::new().with_read_only())?; + let query = "SELECT id FROM Playlists WHERE name = ? ORDER BY id DESC LIMIT 1"; + let mut statement = connection.prepare(query)?; + statement.bind((1, playlist_name.as_str()))?; + statement.next()?; + let latest_id = statement.read::("id")?; + + let query = "SELECT title, artist, album, comment, url, bpm FROM library LEFT JOIN PlaylistTracks ON PlaylistTracks.track_id = library.id WHERE PlaylistTracks.playlist_id = ? ORDER BY position"; + for track in connection.prepare(query)?.into_iter().bind((1, latest_id))? { + let track = track?; + let title = track.try_read::<&str, _>("title").unwrap_or("Untitled Track"); + let artist = track.try_read::<&str, _>("artist").unwrap_or("Unknown Artist"); + let album = track.try_read::<&str, _>("album").unwrap_or("Unknown Album"); + let bpm = track.try_read::("bpm").unwrap_or(0.); + ret.push(Artifact::Track(Track { + artist: Some(artist.into()), + album: Some(album.into()), + title: title.into(), + bpm: Some(bpm), + sources: vec![SourceID::Mixxx], + ..Default::default() + })); + + ret.push(Artifact::Album(Album { + artist: artist.into(), + title: album.into(), + sources: vec![SourceID::Mixxx], + ..Default::default() + })); + + ret.push(Artifact::Artist(Artist { + name: artist.into(), + sources: vec![SourceID::Mixxx], + ..Default::default() + })); + } + + Ok(ret) + } +} \ No newline at end of file diff --git a/src/artifacts.rs b/src/artifacts/mod.rs similarity index 53% rename from src/artifacts.rs rename to src/artifacts/mod.rs index e2f1e6c..e1f0dbf 100644 --- a/src/artifacts.rs +++ b/src/artifacts/mod.rs @@ -1,7 +1,9 @@ use chrono::{DateTime, Utc}; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use sqlite::OpenFlags; + +pub mod bandcamp; +pub mod mixxx; +pub mod beets; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum SourceID { @@ -131,88 +133,4 @@ impl Artifact { _ => () } } -} - -impl Into for bandcamp::Artist { - fn into(self) -> Artifact { - Artifact::Artist(Artist { name: self.name, bio: self.bio, location: self.location, sources: vec![SourceID::Bandcamp(self.id)] }) - } -} - -impl Into for bandcamp::Album { - fn into(self) -> Artifact { - Artifact::Album(Album { - about: self.about, - title: self.title, - artist: self.band.name, - credits: self.credits, - release_date: Some(self.release_date), - sources: vec!{SourceID::Bandcamp(self.id)} - }) - } -} - -#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema)] -pub struct BandcampQueryArgs { - pub query: String -} - -#[derive(Debug)] -#[allow(unused)] -pub enum MixxxError { - Sql(sqlite::Error) -} - -impl From for MixxxError { - fn from(value: sqlite::Error) -> Self { - Self::Sql(value) - } -} - -pub struct MixxxDB(()); - -impl MixxxDB { - pub fn load(episode_number: u32) -> Result, MixxxError> { - let mut ret = vec![]; - let playlist_name = format!("BFF.fm - Episode {}", episode_number); - log::info!("Loading Mixxx playlist {}", playlist_name); - let connection = sqlite::Connection::open_thread_safe_with_flags("mixxxdb.sqlite", OpenFlags::new().with_read_only())?; - let query = "SELECT id FROM Playlists WHERE name = ? ORDER BY id DESC LIMIT 1"; - let mut statement = connection.prepare(query)?; - statement.bind((1, playlist_name.as_str()))?; - statement.next()?; - let latest_id = statement.read::("id")?; - - let query = "SELECT title, artist, album, comment, url, bpm FROM library LEFT JOIN PlaylistTracks ON PlaylistTracks.track_id = library.id WHERE PlaylistTracks.playlist_id = ? ORDER BY position"; - for track in connection.prepare(query)?.into_iter().bind((1, latest_id))? { - let track = track?; - let title = track.try_read::<&str, _>("title").unwrap_or("Untitled Track"); - let artist = track.try_read::<&str, _>("artist").unwrap_or("Unknown Artist"); - let album = track.try_read::<&str, _>("album").unwrap_or("Unknown Album"); - let bpm = track.try_read::("bpm").unwrap_or(0.); - ret.push(Artifact::Track(Track { - artist: Some(artist.into()), - album: Some(album.into()), - title: title.into(), - bpm: Some(bpm), - sources: vec![SourceID::Mixxx], - ..Default::default() - })); - - ret.push(Artifact::Album(Album { - artist: artist.into(), - title: album.into(), - sources: vec![SourceID::Mixxx], - ..Default::default() - })); - - ret.push(Artifact::Artist(Artist { - name: artist.into(), - sources: vec![SourceID::Mixxx], - ..Default::default() - })); - } - - Ok(ret) - } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 32b3837..a17583f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,6 @@ mod tts; mod prediction; mod audio; mod artifacts; -mod archive; mod ui; mod widgets; diff --git a/src/prediction.rs b/src/prediction.rs index 3107097..2d7f2e2 100644 --- a/src/prediction.rs +++ b/src/prediction.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Serializer, ser::CompactFormatter}; use tokio::sync::{RwLock, mpsc, watch}; -use crate::{SaveData, archive::BeatsQueryArgs, artifacts::{Album, Artifact, Artist, BandcampQueryArgs, MixxxDB, Track}, scene::{Scene, Scenery, StageDirection, conversation::ConversationEntry}}; +use crate::{SaveData, artifacts::{Album, Artifact, Artist, Track, beets::BeatsQueryArgs, bandcamp::BandcampQueryArgs, mixxx::MixxxDB}, scene::{Scene, Scenery, StageDirection, conversation::ConversationEntry}}; const SYSTEM_PROMPT: &str = include_str!("system-prompt.txt"); diff --git a/src/ui.rs b/src/ui.rs index 46e7a82..3375c9c 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,6 +1,6 @@ use chrono::{Duration, Utc}; -use crossterm::event::{self, KeyCode, KeyModifiers, MediaKeyCode::Record}; -use ratatui::{Frame, layout::{Direction, Layout, Rect}, style::{self, Style, Stylize}, text::{Line, Span, Text}, widgets::{BorderType, Clear, Gauge, List, ListDirection, ListState, Paragraph, StatefulWidget, Widget, Wrap}}; +use crossterm::event::{self, KeyCode, KeyModifiers}; +use ratatui::{Frame, layout::{Direction, Layout, Rect}, style::{self, }, text::Span, widgets::{BorderType, Clear, ListState, Paragraph}}; use throbber_widgets_tui::{Throbber, ThrobberState}; use tokio::time::Instant; use tui_input::{Input, backend::crossterm::EventHandler};