A tool to sync music with your favorite devices
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

*: introduce support for podcasts

Gee Sawra 6a32b969 26851643

+160 -62
+6
.sqlx/query-4269cff6b3f024043865e48148cf4ca56860bb09032850e0cf18e9dcb306c9aa.json
··· 7 7 "name": "directory", 8 8 "ordinal": 0, 9 9 "type_info": "Text" 10 + }, 11 + { 12 + "name": "kind", 13 + "ordinal": 1, 14 + "type_info": "Int64" 10 15 } 11 16 ], 12 17 "parameters": { 13 18 "Right": 0 14 19 }, 15 20 "nullable": [ 21 + false, 16 22 false 17 23 ] 18 24 },
-12
.sqlx/query-5052a7d020ddc527b28aa9def3c13610b870d16a85484d3ca49fa61de30ff979.json
··· 1 - { 2 - "db_name": "SQLite", 3 - "query": "\n INSERT OR REPLACE INTO tracks (\n track_id,\n title,\n artist,\n album,\n genre,\n track_len,\n year,\n number,\n file_path,\n disc_number,\n disc_total,\n file_state,\n extension,\n artwork_path\n ) VALUES (\n ?1,\n ?2,\n ?3,\n ?4,\n ?5,\n ?6,\n ?7,\n ?8,\n ?9,\n ?10,\n ?11,\n ?12,\n ?13,\n ?14\n );\n ", 4 - "describe": { 5 - "columns": [], 6 - "parameters": { 7 - "Right": 14 8 - }, 9 - "nullable": [] 10 - }, 11 - "hash": "5052a7d020ddc527b28aa9def3c13610b870d16a85484d3ca49fa61de30ff979" 12 - }
+12
.sqlx/query-7df5b4b79676a84928fe29acafd880afa71213b84515916aae4e3eb373bf134a.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n INSERT OR REPLACE INTO tracks (\n kind,\n track_id,\n title,\n artist,\n album,\n genre,\n track_len,\n year,\n number,\n file_path,\n disc_number,\n disc_total,\n file_state,\n extension,\n artwork_path\n ) VALUES (\n ?1,\n ?2,\n ?3,\n ?4,\n ?5,\n ?6,\n ?7,\n ?8,\n ?9,\n ?10,\n ?11,\n ?12,\n ?13,\n ?14,\n ?15\n );\n ", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 15 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "7df5b4b79676a84928fe29acafd880afa71213b84515916aae4e3eb373bf134a" 12 + }
+7 -1
.sqlx/query-86245ea0c3119d94e08fe0404482b4f8de3241bc067136c0181ff4e0aa2bcc67.json
··· 77 77 "name": "artwork_path", 78 78 "ordinal": 14, 79 79 "type_info": "Text" 80 + }, 81 + { 82 + "name": "kind", 83 + "ordinal": 15, 84 + "type_info": "Int64" 80 85 } 81 86 ], 82 87 "parameters": { ··· 97 102 false, 98 103 false, 99 104 false, 100 - true 105 + true, 106 + false 101 107 ] 102 108 }, 103 109 "hash": "86245ea0c3119d94e08fe0404482b4f8de3241bc067136c0181ff4e0aa2bcc67"
+7 -1
.sqlx/query-90d659e4ebb83559ac94ba4618a065c5d9966e99d5c318c5b8f9dcaa670ae5b2.json
··· 77 77 "name": "artwork_path", 78 78 "ordinal": 14, 79 79 "type_info": "Text" 80 + }, 81 + { 82 + "name": "kind", 83 + "ordinal": 15, 84 + "type_info": "Int64" 80 85 } 81 86 ], 82 87 "parameters": { ··· 97 102 false, 98 103 false, 99 104 false, 100 - true 105 + true, 106 + false 101 107 ] 102 108 }, 103 109 "hash": "90d659e4ebb83559ac94ba4618a065c5d9966e99d5c318c5b8f9dcaa670ae5b2"
-12
.sqlx/query-9bf2fda97d829ad09e463504c964619c055032255c4b8c1919c83cba24cd2739.json
··· 1 - { 2 - "db_name": "SQLite", 3 - "query": "INSERT OR REPLACE INTO directories (directory) VALUES (?1);", 4 - "describe": { 5 - "columns": [], 6 - "parameters": { 7 - "Right": 1 8 - }, 9 - "nullable": [] 10 - }, 11 - "hash": "9bf2fda97d829ad09e463504c964619c055032255c4b8c1919c83cba24cd2739" 12 - }
+12
.sqlx/query-e71d9a4f66fae68be7c0ebc4432d82bfa2c56e9c8a5101391004c8d1b576b4c0.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "INSERT OR REPLACE INTO directories (directory, kind) VALUES (?1, ?2);", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 2 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "e71d9a4f66fae68be7c0ebc4432d82bfa2c56e9c8a5101391004c8d1b576b4c0" 12 + }
-20
.sqlx/query-f026bae33f7999c12b01bae1175d9cff43be9124660d427830305999c5c7ee2c.json
··· 1 - { 2 - "db_name": "SQLite", 3 - "query": "\n SELECT track_id FROM tracks WHERE file_state = ?1;\n ", 4 - "describe": { 5 - "columns": [ 6 - { 7 - "name": "track_id", 8 - "ordinal": 0, 9 - "type_info": "Text" 10 - } 11 - ], 12 - "parameters": { 13 - "Right": 1 14 - }, 15 - "nullable": [ 16 - false 17 - ] 18 - }, 19 - "hash": "f026bae33f7999c12b01bae1175d9cff43be9124660d427830305999c5c7ee2c" 20 - }
+7 -1
.sqlx/query-f1349b72ca08058d2503c7348ee66ef638dfc1c3ce09cfd2167c01237ed25840.json
··· 77 77 "name": "artwork_path", 78 78 "ordinal": 14, 79 79 "type_info": "Text" 80 + }, 81 + { 82 + "name": "kind", 83 + "ordinal": 15, 84 + "type_info": "Int64" 80 85 } 81 86 ], 82 87 "parameters": { ··· 97 102 false, 98 103 false, 99 104 false, 100 - true 105 + true, 106 + false 101 107 ] 102 108 }, 103 109 "hash": "f1349b72ca08058d2503c7348ee66ef638dfc1c3ce09cfd2167c01237ed25840"
+6 -2
src/cmd/add.rs
··· 24 24 default_value_t = false 25 25 )] 26 26 pub is_destination: bool, 27 + 28 + /// Specifies the kind of data found in the sources. 29 + #[arg(long = "kind")] 30 + pub kind: model::TrackKind, 27 31 } 28 32 29 33 impl Args { ··· 58 62 let res = try_join_all( 59 63 sources 60 64 .into_iter() 61 - .map(|source| crate::library::scan_source(&db, source)) 65 + .map(|source| crate::library::scan_source(&db, source, args.kind)) 62 66 .collect::<Vec<_>>(), 63 67 ) 64 68 .await?; ··· 79 83 }; 80 84 81 85 Ok(()) 82 - } 86 + }
+16 -5
src/db/instance.rs
··· 221 221 sqlx::query!( 222 222 r#" 223 223 INSERT OR REPLACE INTO tracks ( 224 + kind, 224 225 track_id, 225 226 title, 226 227 artist, ··· 249 250 ?11, 250 251 ?12, 251 252 ?13, 252 - ?14 253 + ?14, 254 + ?15 253 255 ); 254 256 "#, 257 + track.kind, 255 258 track.track_id, 256 259 track.title, 257 260 track.artist, ··· 311 314 track_len: track_len, 312 315 year: year as usize, 313 316 artwork_path: r.get("artwork_path"), 317 + kind: r.get("kind"), 314 318 } 315 319 }) 316 320 .collect()) ··· 333 337 .into_iter() 334 338 .map(|r| model::Track { 335 339 id: r.id, 340 + kind: r.kind.into(), 336 341 track_id: r.track_id, 337 342 title: r.title, 338 343 artist: r.artist, ··· 366 371 Ok(()) 367 372 } 368 373 369 - pub async fn directories(&self) -> Result<Vec<String>, Error> { 374 + pub async fn directories(&self) -> Result<Vec<(String, model::TrackKind)>, Error> { 370 375 let mut conn = self.pool.acquire().await?; 371 376 372 377 Ok(sqlx::query!(r#"SELECT * FROM directories;"#) 373 378 .fetch_all(&mut *conn) 374 379 .await? 375 380 .into_iter() 376 - .map(|e| e.directory) 381 + .map(|e| (e.directory, e.kind.into())) 377 382 .collect()) 378 383 } 379 384 380 - pub async fn insert_directory(&self, directory: String) -> Result<(), Error> { 385 + pub async fn insert_directory( 386 + &self, 387 + directory: String, 388 + kind: model::TrackKind, 389 + ) -> Result<(), Error> { 381 390 let mut conn = self.pool.acquire().await?; 382 391 383 392 sqlx::query!( 384 - r#"INSERT OR REPLACE INTO directories (directory) VALUES (?1);"#, 393 + r#"INSERT OR REPLACE INTO directories (directory, kind) VALUES (?1, ?2);"#, 385 394 directory, 395 + kind 386 396 ) 387 397 .execute(&mut *conn) 388 398 .await?; ··· 518 528 Ok(track) => tx 519 529 .send(Ok(model::Track { 520 530 id: track.id, 531 + kind: track.kind.into(), 521 532 track_id: track.track_id, 522 533 title: track.title, 523 534 artist: track.artist,
+2
src/db/migrations/0006_track_kind.sql
··· 1 + ALTER TABLE tracks ADD kind INTEGER NOT NULL; 2 + ALTER TABLE directories ADD kind INTEGER NOT NULL;
+38 -1
src/gpod/gpod.rs
··· 189 189 190 190 pub fn tracks(&self) -> Result<impl Iterator<Item = Track>> { 191 191 unsafe { 192 + let ti = ITunesDBTrack { 193 + tl: (*itdb_playlist_mpl(self.db_ptr)).members as *mut GList, 194 + kind: model::TrackKind::Music, 195 + }; 196 + 197 + let pi = self.podcasts()?; 198 + 199 + Ok(ti.chain(pi)) 200 + } 201 + } 202 + 203 + fn podcasts(&self) -> Result<impl Iterator<Item = Track>> { 204 + unsafe { 205 + let pp = 'plgen: { 206 + let pl = itdb_playlist_podcasts(self.db_ptr); 207 + if pl.is_null() { 208 + let np = itdb_playlist_new(c_strdup("Podcasts"), 0); 209 + if np.is_null() { 210 + break 'plgen Err(anyhow!("could not create new Podcasts playlist")); 211 + } 212 + 213 + itdb_playlist_set_podcasts(np); 214 + break 'plgen Ok(np); 215 + } 216 + 217 + Ok(pl) 218 + }?; 219 + 192 220 Ok(ITunesDBTrack { 193 - tl: (*itdb_playlist_mpl(self.db_ptr)).members as *mut GList, 221 + tl: (*pp).members as *mut GList, 222 + kind: model::TrackKind::Podcast, 194 223 }) 195 224 } 196 225 } ··· 198 227 199 228 struct ITunesDBTrack { 200 229 tl: *mut GList, 230 + kind: model::TrackKind, 201 231 } 202 232 203 233 impl Iterator for ITunesDBTrack { ··· 235 265 file_state: FileState::Copied, 236 266 extension: file_type, 237 267 artwork_path: None, 268 + kind: self.kind, 238 269 }; 239 270 240 271 track.track_id = model::track_hash(&track); ··· 395 426 for track in m.tracks().unwrap() { 396 427 println!("yolooo {}", track); 397 428 } 429 + } 430 + 431 + #[test] 432 + fn podcasts() { 433 + let m = Manager::new("/run/media/geesawra/IPOD/".to_owned()).unwrap(); 434 + m.podcasts().unwrap(); 398 435 } 399 436 }
+6 -3
src/gtkgui/gtkgui.rs
··· 13 13 14 14 use crate::db::{self, Instance}; 15 15 use crate::ffmpeg::{Codec, SamplingRate}; 16 - use crate::model::Track; 16 + use crate::model::{self, Track}; 17 17 use crate::sync; 18 18 19 19 pub async fn run() -> Result<()> { ··· 975 975 match instance.directories().await { 976 976 Ok(dirs) => { 977 977 for dir in dirs { 978 - let row = adw::ActionRow::builder().title(&dir).build(); 978 + let row = adw::ActionRow::builder().title(&dir.0).build(); 979 979 group.add(&row); 980 980 } 981 981 } ··· 1026 1026 // Add to DB 1027 1027 let instance = { db_state_clone.borrow().clone() }; 1028 1028 if let Some(instance) = instance { 1029 - if let Err(e) = instance.insert_directory(path_str).await { 1029 + if let Err(e) = instance 1030 + .insert_directory(path_str, model::TrackKind::Music) 1031 + .await 1032 + { 1030 1033 show_error_dialog( 1031 1034 &dialog, 1032 1035 &format!("Failed to insert directory: {}", e),
+12 -4
src/library.rs
··· 14 14 15 15 let mut tracks = vec![]; 16 16 17 - for source in &sources { 17 + for (source, _) in &sources { 18 18 for i in db 19 19 .track_paths_from_dir(source.clone()) 20 20 .await ··· 29 29 let res = try_join_all( 30 30 sources 31 31 .into_iter() 32 - .map(|source| { 32 + .map(|(source, kind)| { 33 33 let cb = progress_cb.clone(); 34 34 traverse_and_add_param( 35 35 db, ··· 40 40 move |path, _| Ok(tracks_set.contains(path)) 41 41 }, 42 42 Some(cb), 43 + kind, 43 44 ) 44 45 }) 45 46 .collect::<Vec<_>>(), ··· 67 68 Ok(totals) 68 69 } 69 70 70 - pub async fn scan_source(db: &db::Instance, source: String) -> Result<(u64, u64)> { 71 + pub async fn scan_source( 72 + db: &db::Instance, 73 + source: String, 74 + kind: model::TrackKind, 75 + ) -> Result<(u64, u64)> { 71 76 traverse_and_add_param( 72 77 db, 73 78 source.clone(), 74 79 move |path, db| add_dupe_checker(path, db), 75 80 None::<fn(&model::Track)>, 81 + kind, 76 82 ) 77 83 .await 78 84 } ··· 92 98 path: String, 93 99 dupe_checker: F, 94 100 progress_cb: Option<P>, 101 + kind: model::TrackKind, 95 102 ) -> Result<(u64, u64)> 96 103 where 97 104 F: FnOnce(&String, &db::Instance) -> Result<bool> + Clone, ··· 128 135 tags, 129 136 path: p, 130 137 artwork_path: find_cover_image(&p_path), 138 + kind: kind, 131 139 } 132 140 .try_into()?; 133 141 track.file_state = model::FileState::Copied; ··· 145 153 new_tracks += 1; 146 154 } 147 155 148 - db.insert_directory(path).await?; 156 + db.insert_directory(path, kind).await?; 149 157 150 158 Ok((new_tracks, duplicate)) 151 159 }
+29
src/model.rs
··· 31 31 pub tags: TaggedFile, 32 32 pub path: String, 33 33 pub artwork_path: Option<String>, 34 + pub kind: TrackKind, 34 35 } 35 36 36 37 #[derive(Debug, Clone, Default, rhai::CustomType)] 37 38 pub struct BaseTrack { 39 + pub kind: TrackKind, 38 40 pub title: String, 39 41 pub artist: String, 40 42 pub album: String, ··· 48 50 impl From<Track> for BaseTrack { 49 51 fn from(value: Track) -> Self { 50 52 Self { 53 + kind: value.kind, 51 54 title: value.title, 52 55 artist: value.artist, 53 56 album: value.album, ··· 60 63 } 61 64 } 62 65 66 + #[derive(Debug, Clone, Copy, sqlx::Type, clap::ValueEnum)] 67 + pub enum TrackKind { 68 + #[value(skip)] 69 + Unknown, 70 + Music, 71 + Podcast, 72 + } 73 + 74 + impl From<i64> for TrackKind { 75 + fn from(value: i64) -> Self { 76 + match value { 77 + 1 => Self::Music, 78 + 2 => Self::Podcast, 79 + _ => Self::Unknown, 80 + } 81 + } 82 + } 83 + 84 + impl std::default::Default for TrackKind { 85 + fn default() -> Self { 86 + Self::Music 87 + } 88 + } 89 + 63 90 #[derive(Debug, Clone, sqlx::Type, Default)] 64 91 pub struct Track { 65 92 pub id: i64, 93 + pub kind: TrackKind, 66 94 pub track_id: String, 67 95 pub title: String, 68 96 pub artist: String, ··· 132 160 133 161 let mut t = Self { 134 162 id: 0, 163 + kind: track.kind, 135 164 track_id: Default::default(), 136 165 title: title, 137 166 artist: artist,