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