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}