moosicbox_session/
lib.rs

1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3#![allow(clippy::multiple_crate_versions)]
4
5use moosicbox_audio_zone::{
6    db::audio_zone_try_from_db,
7    models::{AudioZone, Player},
8};
9use moosicbox_json_utils::database::DatabaseFetchError;
10use moosicbox_music_models::api::ApiTrack;
11use moosicbox_session_models::{
12    CreateSession, PlaybackTarget, Session, SessionPlaylist, SetSessionAudioZone, UpdateSession,
13};
14use switchy_database::{config::ConfigDatabase, profiles::LibraryDatabase};
15
16mod db;
17pub use moosicbox_session_models as models;
18use thiserror::Error;
19
20#[cfg(feature = "api")]
21pub mod api;
22
23#[cfg(feature = "events")]
24pub mod events;
25
26/// # Errors
27///
28/// * If a database error occurs
29pub async fn get_session_playlist_tracks(
30    db: &LibraryDatabase,
31    session_playlist_id: u64,
32) -> Result<Vec<ApiTrack>, DatabaseFetchError> {
33    crate::db::get_session_playlist_tracks(db, session_playlist_id).await
34}
35
36/// # Errors
37///
38/// * If a database error occurs
39pub async fn get_session_playlist(
40    db: &LibraryDatabase,
41    session_id: u64,
42) -> Result<Option<SessionPlaylist>, DatabaseFetchError> {
43    crate::db::get_session_playlist(db, session_id).await
44}
45
46/// # Errors
47///
48/// * If a database error occurs
49pub async fn get_session_audio_zone(
50    db: &LibraryDatabase,
51    session_id: u64,
52) -> Result<Option<AudioZone>, DatabaseFetchError> {
53    Ok(
54        if let Some(zone) = crate::db::get_session_audio_zone(db, session_id).await? {
55            Some(audio_zone_try_from_db(zone, db.into()).await?)
56        } else {
57            None
58        },
59    )
60}
61
62/// # Errors
63///
64/// * If a database error occurs
65pub async fn set_session_audio_zone(
66    db: &LibraryDatabase,
67    set_session_audio_zone: &SetSessionAudioZone,
68) -> Result<(), DatabaseFetchError> {
69    crate::db::set_session_audio_zone(db, set_session_audio_zone).await
70}
71
72/// # Errors
73///
74/// * If a database error occurs
75pub async fn get_session_playing(
76    db: &LibraryDatabase,
77    id: u64,
78) -> Result<Option<bool>, DatabaseFetchError> {
79    crate::db::get_session_playing(db, id).await
80}
81
82/// # Errors
83///
84/// * If a database error occurs
85pub async fn get_session(
86    db: &LibraryDatabase,
87    id: u64,
88) -> Result<Option<Session>, DatabaseFetchError> {
89    crate::db::get_session(db, id).await
90}
91
92/// # Errors
93///
94/// * If a database error occurs
95pub async fn get_sessions(db: &LibraryDatabase) -> Result<Vec<Session>, DatabaseFetchError> {
96    crate::db::get_sessions(db).await
97}
98
99/// # Errors
100///
101/// * If a database error occurs
102pub async fn create_session(
103    db: &LibraryDatabase,
104    session: &CreateSession,
105) -> Result<Session, DatabaseFetchError> {
106    crate::db::create_session(db, session).await
107}
108
109/// # Errors
110///
111/// * If a database error occurs
112pub async fn update_session(
113    db: &LibraryDatabase,
114    session: &UpdateSession,
115) -> Result<(), DatabaseFetchError> {
116    crate::db::update_session(db, session).await
117}
118
119/// # Errors
120///
121/// * If a database error occurs
122pub async fn delete_session(
123    db: &LibraryDatabase,
124    session_id: u64,
125) -> Result<(), DatabaseFetchError> {
126    crate::db::delete_session(db, session_id).await
127}
128
129/// # Errors
130///
131/// * If a database error occurs
132pub async fn get_connections(
133    db: &ConfigDatabase,
134) -> Result<Vec<models::Connection>, DatabaseFetchError> {
135    crate::db::get_connections(db).await
136}
137
138/// # Errors
139///
140/// * If a database error occurs
141pub async fn register_connection(
142    db: &ConfigDatabase,
143    connection: &models::RegisterConnection,
144) -> Result<models::Connection, DatabaseFetchError> {
145    let result = crate::db::register_connection(db, connection).await?;
146
147    for player in &connection.players {
148        create_player(db, &connection.connection_id, player).await?;
149    }
150
151    let players = get_players(db, &result.id).await?;
152
153    Ok(models::Connection {
154        id: result.id,
155        name: result.name,
156        created: result.created,
157        updated: result.updated,
158        players,
159    })
160}
161
162/// # Errors
163///
164/// * If a database error occurs
165pub async fn delete_connection(
166    db: &ConfigDatabase,
167    connection_id: &str,
168) -> Result<(), DatabaseFetchError> {
169    crate::db::delete_connection(db, connection_id).await
170}
171
172/// # Errors
173///
174/// * If a database error occurs
175pub async fn get_players(
176    db: &ConfigDatabase,
177    connection_id: &str,
178) -> Result<Vec<Player>, DatabaseFetchError> {
179    crate::db::get_players(db, connection_id).await
180}
181
182/// # Errors
183///
184/// * If a database error occurs
185pub async fn create_player(
186    db: &ConfigDatabase,
187    connection_id: &str,
188    player: &models::RegisterPlayer,
189) -> Result<Player, DatabaseFetchError> {
190    let result = crate::db::create_player(db, connection_id, player).await?;
191
192    #[cfg(feature = "events")]
193    {
194        moosicbox_task::spawn("create_player updated_events", async move {
195            if let Err(e) = crate::events::trigger_players_updated_event().await {
196                moosicbox_assert::die_or_error!("Failed to trigger event: {e:?}");
197            }
198        });
199    }
200
201    Ok(result)
202}
203
204#[derive(Debug, Error)]
205pub enum CreatePlayersError {
206    #[error(transparent)]
207    Db(#[from] DatabaseFetchError),
208    #[error("Invalid connection")]
209    InvalidConnection,
210}
211
212/// # Errors
213///
214/// * If a database error occurs
215pub async fn create_players(
216    db: &ConfigDatabase,
217    connection_id: &str,
218    players: &[models::RegisterPlayer],
219) -> Result<Vec<Player>, CreatePlayersError> {
220    let connections = crate::db::get_connections(db).await?;
221    if !connections.iter().any(|x| x.id == connection_id) {
222        return Err(CreatePlayersError::InvalidConnection);
223    }
224
225    let mut results = vec![];
226
227    for player in players {
228        results.push(crate::db::create_player(db, connection_id, player).await?);
229    }
230
231    #[cfg(feature = "events")]
232    {
233        moosicbox_task::spawn("create_players updated_events", async move {
234            if let Err(e) = crate::events::trigger_players_updated_event().await {
235                moosicbox_assert::die_or_error!("Failed to trigger event: {e:?}");
236            }
237        });
238    }
239
240    Ok(results)
241}
242
243/// # Errors
244///
245/// * If a database error occurs
246pub async fn delete_player(db: &ConfigDatabase, player_id: u64) -> Result<(), DatabaseFetchError> {
247    crate::db::delete_player(db, player_id).await?;
248
249    #[cfg(feature = "events")]
250    {
251        moosicbox_task::spawn("delete_player updated_events", async move {
252            if let Err(e) = crate::events::trigger_players_updated_event().await {
253                moosicbox_assert::die_or_error!("Failed to trigger event: {e:?}");
254            }
255        });
256    }
257
258    Ok(())
259}
260
261/// # Errors
262///
263/// * If a database error occurs
264pub async fn delete_session_playlist_track_by_track_id(
265    db: &LibraryDatabase,
266    id: u64,
267) -> Result<Option<ApiTrack>, DatabaseFetchError> {
268    crate::db::delete_session_playlist_track_by_track_id(db, id).await
269}
270
271/// # Errors
272///
273/// * If a database error occurs
274pub async fn delete_session_playlist_tracks_by_track_id(
275    db: &LibraryDatabase,
276    ids: Option<&Vec<u64>>,
277) -> Result<Vec<ApiTrack>, DatabaseFetchError> {
278    crate::db::delete_session_playlist_tracks_by_track_id(db, ids).await
279}
280
281/// # Errors
282///
283/// * If the audio zone fails to be fetched
284pub async fn update_session_audio_output_ids(
285    session: &UpdateSession,
286    db: &ConfigDatabase,
287) -> Result<Vec<String>, DatabaseFetchError> {
288    Ok(match &session.playback_target {
289        PlaybackTarget::AudioZone { audio_zone_id } => {
290            let Some(output) = moosicbox_audio_zone::get_zone(db, *audio_zone_id).await? else {
291                return Ok(vec![]);
292            };
293
294            output
295                .players
296                .into_iter()
297                .map(|x| x.audio_output_id)
298                .collect::<Vec<_>>()
299        }
300        PlaybackTarget::ConnectionOutput { output_id, .. } => vec![output_id.to_owned()],
301    })
302}