Skip to main content

tano_database/actor/
mod.rs

1use std::str::FromStr;
2
3use color_eyre::eyre::Result;
4use sqlx::{SqlitePool, sqlite::SqliteConnectOptions};
5use tano_providers::local::parse_song::ParsedSong;
6use tano_shared::get_data_dir::get_data_dir;
7use tokio::{fs, sync::mpsc};
8
9use crate::{
10    actor::cmd::DatabaseCmd, album::Album, artist::Artist, db, local_song::SyncLocalSong,
11    song::Song,
12};
13
14pub mod cmd;
15pub mod handle;
16
17pub struct DatabaseActor {
18    receiver: mpsc::Receiver<DatabaseCmd>,
19    pool: Option<SqlitePool>,
20}
21
22impl DatabaseActor {
23    pub fn new(receiver: mpsc::Receiver<DatabaseCmd>) -> Self {
24        Self {
25            receiver,
26            pool: None,
27        }
28    }
29
30    async fn handle_command(&mut self, cmd: DatabaseCmd) {
31        match cmd {
32            DatabaseCmd::LoadDatabase { respond_to } => {
33                let _ = respond_to.send(self.load_database().await);
34            }
35            DatabaseCmd::GetSongs { respond_to } => {
36                let _ = respond_to.send(self.get_songs().await);
37            }
38            DatabaseCmd::GetAlbums { respond_to } => {
39                let _ = respond_to.send(self.get_albums().await);
40            }
41            DatabaseCmd::GetAlbumArtists {
42                album_id,
43                respond_to,
44            } => {
45                let _ = respond_to.send(self.get_album_artists(album_id).await);
46            }
47            DatabaseCmd::GetAlbum { id, respond_to } => {
48                let _ = respond_to.send(self.get_album(id).await);
49            }
50            DatabaseCmd::GetAlbumSongs {
51                album_id,
52                respond_to,
53            } => {
54                let _ = respond_to.send(self.get_album_songs(album_id).await);
55            }
56            DatabaseCmd::GetArtists { respond_to } => {
57                let _ = respond_to.send(self.get_artists().await);
58            }
59            DatabaseCmd::GetSongIds { respond_to } => {
60                let _ = respond_to.send(self.get_song_ids().await);
61            }
62            DatabaseCmd::GetSyncLocalSong {
63                respond_to,
64                provider_id,
65            } => {
66                let _ = respond_to.send(self.get_sync_local_songs(provider_id).await);
67            }
68            DatabaseCmd::SyncLocalSongs {
69                provider_id,
70                new_songs,
71                updated_songs,
72                to_update_path,
73                to_delete_ids,
74                respond_to,
75            } => {
76                let _ = respond_to.send(
77                    self.sync_local_songs(
78                        provider_id,
79                        new_songs,
80                        updated_songs,
81                        to_update_path,
82                        to_delete_ids,
83                    )
84                    .await,
85                );
86            }
87            DatabaseCmd::GetLocalSongByPath {
88                provider_id,
89                path,
90                respond_to,
91            } => {
92                let _ = respond_to.send(
93                    db::get_local_song_by_path(self.pool.as_ref().unwrap(), provider_id, &path)
94                        .await,
95                );
96            }
97            DatabaseCmd::GetLocalSongByInode {
98                provider_id,
99                inode,
100                respond_to,
101            } => {
102                let _ = respond_to.send(
103                    db::get_local_song_by_inode(self.pool.as_ref().unwrap(), provider_id, inode)
104                        .await,
105                );
106            }
107            DatabaseCmd::UpdateLocalSongPath {
108                id,
109                path,
110                respond_to,
111            } => {
112                let _ = respond_to
113                    .send(db::update_local_song_path(self.pool.as_ref().unwrap(), id, &path).await);
114            }
115            DatabaseCmd::InsertLocalSong {
116                provider_id,
117                parsed_song,
118                respond_to,
119            } => {
120                let _ = respond_to.send(
121                    db::insert_parsed_song(self.pool.as_ref().unwrap(), provider_id, &parsed_song)
122                        .await,
123                );
124            }
125            DatabaseCmd::UpdateLocalSong {
126                provider_id,
127                id,
128                parsed_song,
129                respond_to,
130            } => {
131                let _ = respond_to.send(
132                    db::update_parsed_song(
133                        self.pool.as_ref().unwrap(),
134                        provider_id,
135                        id,
136                        &parsed_song,
137                    )
138                    .await,
139                );
140            }
141            DatabaseCmd::DeleteLocalSong { id, respond_to } => {
142                let _ =
143                    respond_to.send(db::delete_parsed_song(self.pool.as_ref().unwrap(), id).await);
144            }
145        }
146    }
147
148    async fn get_songs(&self) -> Result<Vec<Song>> {
149        db::get_songs(self.pool.as_ref().unwrap()).await
150    }
151
152    async fn get_albums(&self) -> Result<Vec<Album>> {
153        db::get_albums(self.pool.as_ref().unwrap()).await
154    }
155
156    async fn get_album(&self, id: i64) -> Result<Option<Album>> {
157        db::get_album(self.pool.as_ref().unwrap(), id).await
158    }
159
160    async fn get_album_artists(&self, album_id: i64) -> Result<Vec<Artist>> {
161        db::get_album_artists(self.pool.as_ref().unwrap(), album_id).await
162    }
163
164    async fn get_album_songs(&self, album_id: i64) -> Result<Vec<Song>> {
165        db::get_album_songs(self.pool.as_ref().unwrap(), album_id).await
166    }
167
168    async fn get_artists(&self) -> Result<Vec<Artist>> {
169        db::get_artists(self.pool.as_ref().unwrap()).await
170    }
171
172    async fn get_song_ids(&self) -> Result<Vec<i64>> {
173        db::get_song_ids(self.pool.as_ref().unwrap()).await
174    }
175
176    async fn get_sync_local_songs(&self, provider_id: u64) -> Result<Vec<SyncLocalSong>> {
177        db::get_sync_local_songs(self.pool.as_ref().unwrap(), provider_id).await
178    }
179
180    async fn sync_local_songs(
181        &self,
182        provider_id: u64,
183        new_songs: Vec<ParsedSong>,
184        updated_songs: Vec<(i64, ParsedSong)>,
185        to_update_path: Vec<(i64, String)>,
186        to_delete_ids: Vec<i64>,
187    ) -> Result<()> {
188        db::sync_local_songs(
189            self.pool.as_ref().unwrap(),
190            provider_id,
191            new_songs,
192            updated_songs,
193            to_update_path,
194            to_delete_ids,
195        )
196        .await
197    }
198
199    async fn load_database(&mut self) -> Result<()> {
200        let data_dir = get_data_dir()?;
201        fs::create_dir_all(&data_dir).await?;
202
203        let database_path = &data_dir.join("database.db").to_string_lossy().to_string();
204
205        let connection_options =
206            SqliteConnectOptions::from_str(database_path)?.create_if_missing(true);
207
208        let pool = SqlitePool::connect_with(connection_options).await?;
209
210        sqlx::migrate!().run(&pool).await?;
211
212        self.pool = Some(pool);
213
214        Ok(())
215    }
216}
217
218pub async fn run_database_actor(mut actor: DatabaseActor) {
219    while let Some(cmd) = actor.receiver.recv().await {
220        actor.handle_command(cmd).await;
221    }
222}