Skip to main content

koan_core/db/queries/
mod.rs

1mod albums;
2pub mod api_keys;
3mod artists;
4pub mod auth;
5pub mod batch;
6mod favourites;
7pub mod history;
8pub mod lyrics;
9pub mod playback_state;
10pub mod playlists;
11pub mod radio;
12mod scan_cache;
13mod search;
14pub mod shares;
15mod stats;
16pub mod tracks;
17pub mod uids;
18pub mod vectors;
19
20use std::path::PathBuf;
21
22// Re-export all public items so `use queries::*` still works.
23pub use albums::*;
24pub use artists::*;
25pub use auth::LOCAL_USER;
26pub use batch::*;
27pub use favourites::*;
28pub use history::*;
29pub use lyrics::*;
30pub use playback_state::*;
31pub use playlists::*;
32pub use radio::*;
33pub use scan_cache::*;
34pub use search::*;
35pub use stats::*;
36pub use tracks::*;
37pub use uids::*;
38pub use vectors::*;
39
40/// The half-open range of paths under a folder, for `path >= .0 AND path < .1`.
41///
42/// A prefix match on an indexed column, rather than `LIKE 'folder/%'` — which
43/// SQLite answers by reading every row, because a pattern is opaque to an
44/// index until it has been evaluated. It also takes the pattern out of the
45/// path: `LIKE` reads `_` as "any character" and folds ASCII case, so
46/// `/Volumes/My_Music` matched `/Volumes/My Music` and `/volumes/my_music`
47/// alike.
48///
49/// The trailing separator is what keeps `/Volumes/Music` out of
50/// `/Volumes/Music Backup`; the upper bound is the highest code point, so
51/// every path under the folder sorts below it.
52pub fn folder_prefix_range(folder: &std::path::Path) -> (String, String) {
53    let prefix = format!(
54        "{}{}",
55        folder
56            .to_string_lossy()
57            .trim_end_matches(std::path::MAIN_SEPARATOR),
58        std::path::MAIN_SEPARATOR
59    );
60    let upper = format!("{prefix}\u{10FFFF}");
61    (prefix, upper)
62}
63
64// --- Row types ---
65
66#[derive(Debug, Clone)]
67pub struct ArtistRow {
68    pub id: i64,
69    pub name: String,
70    pub sort_name: Option<String>,
71    pub remote_id: Option<String>,
72    /// Albums credited to this artist, and tracks across them. Aggregated in
73    /// the same query as the row itself — a count per artist would be one
74    /// query per row in a list thousands long.
75    pub album_count: i64,
76    pub track_count: i64,
77}
78
79#[derive(Debug, Clone)]
80pub struct AlbumRow {
81    pub id: i64,
82    pub title: String,
83    pub artist_id: i64,
84    pub artist_name: String,
85    pub date: Option<String>,
86    pub total_discs: Option<i32>,
87    pub total_tracks: Option<i32>,
88    pub codec: Option<String>,
89    pub label: Option<String>,
90    pub remote_id: Option<String>,
91    /// When the album entered the library — the server's `created` for remote
92    /// albums, otherwise the time it was first indexed.
93    pub added_at: Option<String>,
94}
95
96#[derive(Debug, Clone)]
97pub struct TrackRow {
98    pub id: i64,
99    pub album_id: Option<i64>,
100    pub artist_id: Option<i64>,
101    pub artist_name: String,
102    pub album_artist_name: String,
103    pub album_title: String,
104    pub disc: Option<i32>,
105    pub track_number: Option<i32>,
106    pub title: String,
107    pub duration_ms: Option<i64>,
108    pub path: Option<String>,
109    pub codec: Option<String>,
110    pub sample_rate: Option<i32>,
111    pub bit_depth: Option<i32>,
112    pub channels: Option<i32>,
113    pub bitrate: Option<i32>,
114    pub genre: Option<String>,
115    pub source: String,
116    pub remote_id: Option<String>,
117    pub cached_path: Option<String>,
118}
119
120/// Where to get audio data for playback. Local always wins.
121#[derive(Debug, Clone)]
122pub enum PlaybackSource {
123    Local(PathBuf),
124    Cached(PathBuf),
125    Remote(String),
126}
127
128#[derive(Debug, Clone, Default)]
129pub struct LibraryStats {
130    pub total_tracks: i64,
131    pub local_tracks: i64,
132    pub remote_tracks: i64,
133    pub cached_tracks: i64,
134    pub total_albums: i64,
135    pub total_artists: i64,
136}
137
138/// Metadata for inserting/updating a track.
139#[derive(Debug, Clone)]
140pub struct TrackMeta {
141    pub title: String,
142    pub artist: String,
143    pub album_artist: Option<String>,
144    pub album: String,
145    pub date: Option<String>,
146    pub disc: Option<i32>,
147    pub track_number: Option<i32>,
148    pub genre: Option<String>,
149    pub label: Option<String>,
150    pub duration_ms: Option<i64>,
151    pub codec: Option<String>,
152    pub sample_rate: Option<i32>,
153    pub bit_depth: Option<i32>,
154    pub channels: Option<i32>,
155    pub bitrate: Option<i32>,
156    pub size_bytes: Option<i64>,
157    pub mtime: Option<i64>,
158    pub path: Option<String>,
159    pub source: String,
160    pub remote_id: Option<String>,
161    pub remote_url: Option<String>,
162    /// The server's ids for the album and its artist.
163    ///
164    /// Carried alongside the track's own, because the server keys stars,
165    /// shares and cover art off them — a library synced without these has
166    /// albums and artists it can name but cannot refer to.
167    pub album_remote_id: Option<String>,
168    pub artist_remote_id: Option<String>,
169    /// MusicBrainz recording and release ids — `MUSICBRAINZ_TRACKID` and
170    /// `MUSICBRAINZ_ALBUMID` in a file's tags, `musicBrainzId` on a server's
171    /// song and album. Together they name one track whatever each source calls
172    /// the album; the recording alone recurs on every compilation it is on.
173    pub mbid: Option<String>,
174    pub album_mbid: Option<String>,
175    /// When the album this track belongs to entered the library. Remote sync
176    /// supplies the server's `created`; anything else leaves it and the album
177    /// is stamped with the time it was first seen.
178    pub album_added_at: Option<String>,
179}
180
181/// Test helper: build a sample TrackMeta for use in tests across sub-modules.
182#[cfg(test)]
183pub fn sample_meta(title: &str, artist: &str, album: &str) -> TrackMeta {
184    TrackMeta {
185        title: title.into(),
186        artist: artist.into(),
187        album_artist: Some(artist.into()),
188        album: album.into(),
189        date: Some("2024".into()),
190        disc: Some(1),
191        track_number: Some(1),
192        genre: Some("Electronic".into()),
193        label: None,
194        duration_ms: Some(240_000),
195        codec: Some("FLAC".into()),
196        sample_rate: Some(44100),
197        bit_depth: Some(16),
198        channels: Some(2),
199        bitrate: Some(1000),
200        size_bytes: Some(30_000_000),
201        mtime: Some(1700000000),
202        path: Some(format!("/music/{}/{}.flac", album, title)),
203        source: "local".into(),
204        remote_id: None,
205        album_remote_id: None,
206        artist_remote_id: None,
207        mbid: None,
208        album_mbid: None,
209        remote_url: None,
210        album_added_at: None,
211    }
212}