aboutsummaryrefslogtreecommitdiffstats
path: root/src/database.rs
diff options
context:
space:
mode:
authorsalmonik <raisersoft@proton.me>2026-06-04 00:21:46 +0200
committersalmonik <raisersoft@proton.me>2026-06-04 00:21:46 +0200
commit1e48765ab39a679abcc4eac6927ad3bb58f7b438 (patch)
tree5554f2a06bde693d4d9c39ed26f80677d61da991 /src/database.rs
parentfix no metadata songs handling (diff)
downloadrsplayer-1e48765ab39a679abcc4eac6927ad3bb58f7b438.tar.gz
rsplayer-1e48765ab39a679abcc4eac6927ad3bb58f7b438.zip
dirty, broken commit
Diffstat (limited to '')
-rw-r--r--src/database.rs136
1 files changed, 136 insertions, 0 deletions
diff --git a/src/database.rs b/src/database.rs
index b80ad4a..00d970a 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -45,6 +45,10 @@ pub struct Database {
45 45
46#[allow(dead_code)] 46#[allow(dead_code)]
47impl Database { 47impl Database {
48 pub fn conn(&self) -> &Connection {
49 &self.conn
50 }
51
48 pub fn open(_config: &crate::config::Config) -> SqlResult<Self> { 52 pub fn open(_config: &crate::config::Config) -> SqlResult<Self> {
49 let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer").ok_or_else(|| { 53 let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer").ok_or_else(|| {
50 rusqlite::Error::InvalidPath("Failed to get project directories".into()) 54 rusqlite::Error::InvalidPath("Failed to get project directories".into())
@@ -269,6 +273,138 @@ impl Database {
269 self.get_album_tracks_after(album_id, track_number, &path) 273 self.get_album_tracks_after(album_id, track_number, &path)
270 } 274 }
271 275
276 pub fn complete_artists(&self, prefix: &str) -> SqlResult<Vec<String>> {
277 let pattern = format!("%{}%", prefix);
278 let mut stmt = self
279 .conn
280 .prepare("SELECT name FROM artists WHERE name LIKE ?1 ORDER BY name LIMIT 25")?;
281 let rows = stmt.query_map([&pattern], |row| row.get::<_, String>(0))?;
282 rows.collect()
283 }
284
285 pub fn complete_albums(&self, prefix: &str) -> SqlResult<Vec<String>> {
286 let (artist, album_prefix) = match prefix.split_once('/') {
287 Some((a, ap)) => (a, ap),
288 None => return Ok(Vec::new()),
289 };
290 let pattern = format!("{}%", album_prefix);
291 let mut stmt = self.conn.prepare(
292 "SELECT a.title FROM albums a
293 JOIN artists ar ON a.artist_id = ar.id
294 WHERE ar.name = ?1 AND a.title LIKE ?2
295 ORDER BY a.title LIMIT 25",
296 )?;
297 let rows = stmt.query_map(rusqlite::params![artist, pattern], |row| {
298 row.get::<_, String>(0)
299 })?;
300 rows.collect()
301 }
302
303 pub fn complete_tracks(&self, prefix: &str) -> SqlResult<Vec<String>> {
304 let parts: Vec<&str> = prefix.splitn(3, '/').collect();
305 if parts.len() < 3 {
306 return Ok(Vec::new());
307 }
308 let (artist, album, track_prefix) = (parts[0], parts[1], parts[2]);
309 let pattern = format!("{}%", track_prefix);
310 let mut stmt = self.conn.prepare(
311 "SELECT t.title FROM tracks t
312 JOIN albums a ON t.album_id = a.id
313 JOIN artists ar ON a.artist_id = ar.id
314 WHERE ar.name = ?1 AND a.title = ?2 AND t.title LIKE ?3
315 ORDER BY t.track_number, t.title LIMIT 25",
316 )?;
317 let rows = stmt.query_map(rusqlite::params![artist, album, pattern], |row| {
318 row.get::<_, String>(0)
319 })?;
320 rows.collect()
321 }
322
323 #[allow(dead_code)]
324 pub fn open_in_memory() -> SqlResult<Database> {
325 let conn = Connection::open_in_memory()?;
326 let db = Database { conn };
327 db.init_schema()?;
328 Ok(db)
329 }
330
331 /// Find all tracks in an album by exact artist name and album title.
332 pub fn find_tracks_by_album(&self, artist: &str, album: &str) -> SqlResult<Vec<Track>> {
333 let mut stmt = self.conn.prepare(
334 "SELECT t.id, t.album_id, t.title, t.path, t.duration, t.track_number, ar.name, a.title
335 FROM tracks t
336 JOIN albums a ON t.album_id = a.id
337 JOIN artists ar ON a.artist_id = ar.id
338 WHERE ar.name = ?1 AND a.title = ?2
339 ORDER BY t.track_number",
340 )?;
341 let rows = stmt.query_map(rusqlite::params![artist, album], |row| {
342 Ok(Track {
343 id: row.get(0)?,
344 album_id: row.get(1)?,
345 title: row.get(2)?,
346 path: PathBuf::from(row.get::<_, String>(3)?),
347 duration: row.get(4)?,
348 track_number: row.get(5)?,
349 artist_name: Some(row.get(6)?),
350 album_title: Some(row.get(7)?),
351 })
352 })?;
353 rows.collect()
354 }
355
356 /// Find a single track by exact artist, exact album, and track title prefix.
357 /// Returns None if no match found.
358 pub fn find_track_by_parts(
359 &self,
360 artist: &str,
361 album: &str,
362 track_prefix: &str,
363 ) -> SqlResult<Option<Track>> {
364 let pattern = format!("{}%", track_prefix);
365 let mut stmt = self.conn.prepare(
366 "SELECT t.id, t.album_id, t.title, t.path, t.duration, t.track_number, ar.name, a.title
367 FROM tracks t
368 JOIN albums a ON t.album_id = a.id
369 JOIN artists ar ON a.artist_id = ar.id
370 WHERE ar.name = ?1 AND a.title = ?2 AND t.title LIKE ?3
371 ORDER BY t.track_number LIMIT 1",
372 )?;
373 let mut rows = stmt.query_map(rusqlite::params![artist, album, pattern], |row| {
374 Ok(Track {
375 id: row.get(0)?,
376 album_id: row.get(1)?,
377 title: row.get(2)?,
378 path: PathBuf::from(row.get::<_, String>(3)?),
379 duration: row.get(4)?,
380 track_number: row.get(5)?,
381 artist_name: Some(row.get(6)?),
382 album_title: Some(row.get(7)?),
383 })
384 })?;
385 match rows.next() {
386 Some(Ok(t)) => Ok(Some(t)),
387 Some(Err(e)) => Err(e),
388 None => Ok(None),
389 }
390 }
391
392 /// Resolve a hierarchical path like "Artist/Album" or "Artist/Album/Track"
393 /// to DB tracks. Returns None if the path isn't hierarchical (single part
394 /// or 4+ parts) — the caller should fall back to search_tracks().
395 pub fn resolve_path(&self, path: &str) -> SqlResult<Option<Vec<Track>>> {
396 let path = path.strip_suffix('/').unwrap_or(path);
397 let parts: Vec<&str> = path.splitn(3, '/').collect();
398 match parts.len() {
399 2 => Ok(Some(self.find_tracks_by_album(parts[0], parts[1])?)),
400 3 => match self.find_track_by_parts(parts[0], parts[1], parts[2])? {
401 Some(t) => Ok(Some(vec![t])),
402 None => Ok(Some(vec![])),
403 },
404 _ => Ok(None),
405 }
406 }
407
272 pub fn search_tracks(&self, query: &str) -> SqlResult<Vec<Track>> { 408 pub fn search_tracks(&self, query: &str) -> SqlResult<Vec<Track>> {
273 let mut stmt = self.conn.prepare( 409 let mut stmt = self.conn.prepare(
274 "SELECT t.id, t.album_id, t.title, t.path, t.duration, t.track_number, ar.name, a.title 410 "SELECT t.id, t.album_id, t.title, t.path, t.duration, t.track_number, ar.name, a.title