chore(deps): rusqlite 0.40 (u64 casts; a latent two-statement execute fixed)

rusqlite 0.40 removed ToSql/FromSql for u64 (SQLite integers are i64):
every u64 column now reads/writes through an explicit i64 cast. It also
rejects a multi-statement execute() instead of silently running only the
first statement — which is what catalog.rs's editions/logical_albums
prune had been doing all along; it is an execute_batch now, so the
logical_albums prune finally runs.

cc
This commit is contained in:
vicrodh committed 2026-08-29 08:36:21 -06:00
1 parent d89397415b
commit 6d8c951a38
26 files changed
+97 -93

No files matched your search

+30 -28
View File
@@ -43,18 +43,6 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.5" version = "1.1.5"
@@ -2062,15 +2050,6 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
@@ -2104,11 +2083,11 @@ dependencies = [
[[package]] [[package]]
name = "hashlink" name = "hashlink"
version = "0.9.1" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248"
dependencies = [ dependencies = [
"hashbrown 0.14.5", "hashbrown 0.17.1",
] ]
[[package]] [[package]]
@@ -2847,9 +2826,9 @@ dependencies = [
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.28.0" version = "0.38.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",
@@ -5226,6 +5205,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "rsqlite-vfs"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
dependencies = [
"hashbrown 0.16.1",
"thiserror 2.0.20",
]
[[package]] [[package]]
name = "rtrb" name = "rtrb"
version = "0.3.5" version = "0.3.5"
@@ -5254,9 +5243,9 @@ dependencies = [
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.31.0" version = "0.40.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
dependencies = [ dependencies = [
"bitflags 2.13.1", "bitflags 2.13.1",
"fallible-iterator", "fallible-iterator",
@@ -5264,6 +5253,7 @@ dependencies = [
"hashlink", "hashlink",
"libsqlite3-sys", "libsqlite3-sys",
"smallvec", "smallvec",
"sqlite-wasm-rs",
] ]
[[package]] [[package]]
@@ -5735,6 +5725,18 @@ dependencies = [
"lock_api", "lock_api",
] ]
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
dependencies = [
"cc",
"js-sys",
"rsqlite-vfs",
"wasm-bindgen",
]
[[package]] [[package]]
name = "ssdp-client" name = "ssdp-client"
version = "2.1.0" version = "2.1.0"
+1 -1
View File
@@ -17,7 +17,7 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
log = { workspace = true } log = { workspace = true }
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
dirs = "6" dirs = "6"
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
chrono = { workspace = true } chrono = { workspace = true }
@@ -179,7 +179,7 @@ impl BlacklistService {
.map_err(|e| format!("Failed to prepare blacklist query: {}", e))?; .map_err(|e| format!("Failed to prepare blacklist query: {}", e))?;
let ids: Vec<u64> = stmt let ids: Vec<u64> = stmt
.query_map([], |row| row.get(0)) .query_map([], |row| row.get::<_, i64>(0).map(|v| v as u64))
.map_err(|e| format!("Failed to query blacklist: {}", e))? .map_err(|e| format!("Failed to query blacklist: {}", e))?
.filter_map(|r| r.ok()) .filter_map(|r| r.ok())
.collect(); .collect();
+19 -19
View File
@@ -335,11 +335,11 @@ impl RecoStore {
params![ params![
event.event_type.as_str(), event.event_type.as_str(),
event.item_type.as_str(), event.item_type.as_str(),
event.track_id, event.track_id.map(|v| v as i64),
event.album_id.as_deref(), event.album_id.as_deref(),
event.artist_id, event.artist_id.map(|v| v as i64),
event.playlist_id, event.playlist_id.map(|v| v as i64),
event.genre_id, event.genre_id.map(|v| v as i64),
now_ts(), now_ts(),
], ],
) )
@@ -405,7 +405,7 @@ impl RecoStore {
.map_err(|e| format!("Failed to prepare recent tracks query: {}", e))?; .map_err(|e| format!("Failed to prepare recent tracks query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![limit], |row| row.get::<_, u64>(0)) .query_map(params![limit], |row| row.get::<_, i64>(0).map(|v| v as u64))
.map_err(|e| format!("Failed to query recent tracks: {}", e))?; .map_err(|e| format!("Failed to query recent tracks: {}", e))?;
let mut tracks = Vec::new(); let mut tracks = Vec::new();
@@ -439,7 +439,7 @@ impl RecoStore {
.map_err(|e| format!("Failed to prepare windowed recent tracks query: {}", e))?; .map_err(|e| format!("Failed to prepare windowed recent tracks query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![since_ts, limit], |row| row.get::<_, u64>(0)) .query_map(params![since_ts, limit], |row| row.get::<_, i64>(0).map(|v| v as u64))
.map_err(|e| format!("Failed to query windowed recent tracks: {}", e))?; .map_err(|e| format!("Failed to query windowed recent tracks: {}", e))?;
let mut tracks = Vec::new(); let mut tracks = Vec::new();
@@ -467,7 +467,7 @@ impl RecoStore {
.map_err(|e| format!("Failed to prepare favorite tracks query: {}", e))?; .map_err(|e| format!("Failed to prepare favorite tracks query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![limit], |row| row.get::<_, u64>(0)) .query_map(params![limit], |row| row.get::<_, i64>(0).map(|v| v as u64))
.map_err(|e| format!("Failed to query favorite tracks: {}", e))?; .map_err(|e| format!("Failed to query favorite tracks: {}", e))?;
let mut tracks = Vec::new(); let mut tracks = Vec::new();
@@ -542,7 +542,7 @@ impl RecoStore {
let rows = stmt let rows = stmt
.query_map(params![limit], |row| { .query_map(params![limit], |row| {
Ok(TopArtistSeed { Ok(TopArtistSeed {
artist_id: row.get::<_, u64>(0)?, artist_id: row.get::<_, i64>(0).map(|v| v as u64)?,
play_count: row.get::<_, u32>(1)?, play_count: row.get::<_, u32>(1)?,
}) })
}) })
@@ -576,7 +576,7 @@ impl RecoStore {
) )
.map_err(|e| format!("Failed to prepare known artists query: {}", e))?; .map_err(|e| format!("Failed to prepare known artists query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![play_threshold], |row| row.get::<_, u64>(0)) .query_map(params![play_threshold], |row| row.get::<_, i64>(0).map(|v| v as u64))
.map_err(|e| format!("Failed to query known artists: {}", e))?; .map_err(|e| format!("Failed to query known artists: {}", e))?;
let mut ids = std::collections::HashSet::new(); let mut ids = std::collections::HashSet::new();
for row in rows { for row in rows {
@@ -604,7 +604,7 @@ impl RecoStore {
.map_err(|e| format!("Failed to prepare top genres query: {}", e))?; .map_err(|e| format!("Failed to prepare top genres query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![limit], |row| { .query_map(params![limit], |row| {
Ok((row.get::<_, u64>(0)?, row.get::<_, String>(1)?)) Ok((row.get::<_, i64>(0).map(|v| v as u64)?, row.get::<_, String>(1)?))
}) })
.map_err(|e| format!("Failed to query top genres: {}", e))?; .map_err(|e| format!("Failed to query top genres: {}", e))?;
let mut genres = Vec::new(); let mut genres = Vec::new();
@@ -703,7 +703,7 @@ impl RecoStore {
) )
.map_err(|e| format!("Failed to prepare scored tracks query: {}", e))?; .map_err(|e| format!("Failed to prepare scored tracks query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![score_type, limit], |row| row.get::<_, u64>(0)) .query_map(params![score_type, limit], |row| row.get::<_, i64>(0).map(|v| v as u64))
.map_err(|e| format!("Failed to query scored tracks: {}", e))?; .map_err(|e| format!("Failed to query scored tracks: {}", e))?;
let mut out = Vec::new(); let mut out = Vec::new();
for row in rows { for row in rows {
@@ -729,7 +729,7 @@ impl RecoStore {
.map_err(|e| format!("Failed to prepare scored artists query: {}", e))?; .map_err(|e| format!("Failed to prepare scored artists query: {}", e))?;
let rows = stmt let rows = stmt
.query_map(params![score_type, limit], |row| { .query_map(params![score_type, limit], |row| {
Ok((row.get::<_, u64>(0)?, row.get::<_, f64>(1)?)) Ok((row.get::<_, i64>(0).map(|v| v as u64)?, row.get::<_, f64>(1)?))
}) })
.map_err(|e| format!("Failed to query scored artists: {}", e))?; .map_err(|e| format!("Failed to query scored artists: {}", e))?;
let mut out = Vec::new(); let mut out = Vec::new();
@@ -761,10 +761,10 @@ impl RecoStore {
Ok(RecoEventRecord { Ok(RecoEventRecord {
event_type: row.get(0)?, event_type: row.get(0)?,
item_type: row.get(1)?, item_type: row.get(1)?,
track_id: row.get(2)?, track_id: row.get::<_, Option<i64>>(2)?.map(|v| v as u64),
album_id: row.get(3)?, album_id: row.get(3)?,
artist_id: row.get(4)?, artist_id: row.get::<_, Option<i64>>(4)?.map(|v| v as u64),
genre_id: row.get(5)?, genre_id: row.get::<_, Option<i64>>(5)?.map(|v| v as u64),
created_at: row.get(6)?, created_at: row.get(6)?,
}) })
}) })
@@ -808,9 +808,9 @@ impl RecoStore {
stmt.execute(params![ stmt.execute(params![
score_type, score_type,
item_type, item_type,
entry.track_id, entry.track_id.map(|v| v as i64),
entry.album_id.as_deref(), entry.album_id.as_deref(),
entry.artist_id, entry.artist_id.map(|v| v as i64),
entry.score, entry.score,
updated_at, updated_at,
]) ])
@@ -1071,7 +1071,7 @@ impl RecoStore {
.conn .conn
.execute( .execute(
"UPDATE reco_events SET genre_id = ? WHERE album_id = ? AND genre_id IS NULL", "UPDATE reco_events SET genre_id = ? WHERE album_id = ? AND genre_id IS NULL",
params![genre_id, album_id], params![genre_id as i64, album_id],
) )
.map_err(|e| format!("Failed to update genre for album: {}", e))?; .map_err(|e| format!("Failed to update genre for album: {}", e))?;
Ok(affected as u64) Ok(affected as u64)
@@ -1147,7 +1147,7 @@ mod tests {
r#"INSERT INTO reco_events r#"INSERT INTO reco_events
(event_type, item_type, track_id, album_id, artist_id, playlist_id, genre_id, created_at) (event_type, item_type, track_id, album_id, artist_id, playlist_id, genre_id, created_at)
VALUES (?, ?, ?, ?, ?, NULL, ?, ?)"#, VALUES (?, ?, ?, ?, ?, NULL, ?, ?)"#,
params![event_type, item_type, track_id, album_id, artist_id, genre_id, created_at], params![event_type, item_type, track_id.map(|v| v as i64), album_id, artist_id.map(|v| v as i64), genre_id.map(|v| v as i64), created_at],
) )
.expect("insert event at ts"); .expect("insert event at ts");
} }
+1 -1
View File
@@ -34,7 +34,7 @@ rustfft = "6.4"
spectrum-analyzer = "1.5" spectrum-analyzer = "1.5"
# Database for loudness cache # Database for loudness cache
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# System directories # System directories
dirs = "6" dirs = "6"
+1 -1
View File
@@ -17,5 +17,5 @@ dirs = "6"
filetime = "0.2" filetime = "0.2"
# Image cache index + URL hashing # Image cache index + URL hashing
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
md-5 = "0.10" md-5 = "0.10"
+1 -1
View File
@@ -11,7 +11,7 @@ base64 = { workspace = true }
# The disc MEMORY (src/store.rs). SQLite rather than a JSON file because the # The disc MEMORY (src/store.rs). SQLite rather than a JSON file because the
# app already speaks it everywhere and a row-at-a-time upsert is what the # app already speaks it everywhere and a row-at-a-time upsert is what the
# "never overwrite a human" rule needs. # "never overwrite a human" rule needs.
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
dirs = "6" dirs = "6"
+1 -1
View File
@@ -19,7 +19,7 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
# Cache (per-user SQLite, WAL per ADR-002) # Cache (per-user SQLite, WAL per ADR-002)
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# Logging # Logging
log = { workspace = true } log = { workspace = true }
+1 -1
View File
@@ -30,7 +30,7 @@ urlencoding = "2"
discord-rich-presence = "1.1.0" discord-rich-presence = "1.1.0"
# SQLite for caching (optional, enabled by default) # SQLite for caching (optional, enabled by default)
rusqlite = { version = "0.31", features = ["bundled"], optional = true } rusqlite = { version = "0.40", features = ["bundled"], optional = true }
# Time handling for cache TTL # Time handling for cache TTL
chrono = { version = "0.4", optional = true } chrono = { version = "0.4", optional = true }
+1 -1
View File
@@ -17,7 +17,7 @@ serde_json = "1"
thiserror = "2" thiserror = "2"
# Database # Database
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# Filesystem # Filesystem
walkdir = "2" walkdir = "2"
+15 -15
View File
@@ -1369,12 +1369,12 @@ impl LibraryDatabase {
track.genre, track.genre,
genres_json, genres_json,
track.catalog_number, track.catalog_number,
track.duration_secs, track.duration_secs as i64,
track.format.to_string(), track.format.to_string(),
track.bit_depth, track.bit_depth,
track.sample_rate, track.sample_rate,
track.channels, track.channels,
track.file_size_bytes, track.file_size_bytes as i64,
track.cue_file_path, track.cue_file_path,
track.cue_start_secs, track.cue_start_secs,
track.cue_end_secs, track.cue_end_secs,
@@ -1416,12 +1416,12 @@ impl LibraryDatabase {
track.genre, track.genre,
genres_json, genres_json,
track.catalog_number, track.catalog_number,
track.duration_secs, track.duration_secs as i64,
track.format.to_string(), track.format.to_string(),
track.bit_depth, track.bit_depth,
track.sample_rate, track.sample_rate,
track.channels, track.channels,
track.file_size_bytes, track.file_size_bytes as i64,
track.cue_file_path, track.cue_file_path,
track.cue_start_secs, track.cue_start_secs,
track.cue_end_secs, track.cue_end_secs,
@@ -1804,7 +1804,7 @@ impl LibraryDatabase {
artwork_path, artwork_path,
artwork_source: None, artwork_source: None,
track_count: row.get(7)?, track_count: row.get(7)?,
total_duration_secs: row.get(8)?, total_duration_secs: row.get::<_, i64>(8)? as u64,
format: Self::parse_format( format: Self::parse_format(
&row.get::<_, Option<String>>(9)?.unwrap_or_default(), &row.get::<_, Option<String>>(9)?.unwrap_or_default(),
), ),
@@ -2302,7 +2302,7 @@ impl LibraryDatabase {
artwork_path, artwork_path,
artwork_source: None, artwork_source: None,
track_count: row.get(7)?, track_count: row.get(7)?,
total_duration_secs: row.get(8)?, total_duration_secs: row.get::<_, i64>(8)? as u64,
format: Self::parse_format( format: Self::parse_format(
&row.get::<_, Option<String>>(9)?.unwrap_or_default(), &row.get::<_, Option<String>>(9)?.unwrap_or_default(),
), ),
@@ -2855,7 +2855,7 @@ impl LibraryDatabase {
artwork_path, artwork_path,
artwork_source: None, artwork_source: None,
track_count: row.get(7)?, track_count: row.get(7)?,
total_duration_secs: row.get(8)?, total_duration_secs: row.get::<_, i64>(8)? as u64,
format: Self::parse_format( format: Self::parse_format(
&row.get::<_, Option<String>>(9)?.unwrap_or_default(), &row.get::<_, Option<String>>(9)?.unwrap_or_default(),
), ),
@@ -3778,8 +3778,8 @@ impl LibraryDatabase {
track_count: row.get(0)?, track_count: row.get(0)?,
album_count: row.get(1)?, album_count: row.get(1)?,
artist_count: row.get(2)?, artist_count: row.get(2)?,
total_duration_secs: row.get(3)?, total_duration_secs: row.get::<_, i64>(3)? as u64,
total_size_bytes: row.get(4)?, total_size_bytes: row.get::<_, i64>(4)? as u64,
}) })
}) })
.map_err(|e| LibraryError::Database(e.to_string())) .map_err(|e| LibraryError::Database(e.to_string()))
@@ -3862,12 +3862,12 @@ impl LibraryDatabase {
year: row.get(8)?, // year year: row.get(8)?, // year
genre, genre,
genres, genres,
duration_secs: row.get(11)?, // duration_secs duration_secs: row.get::<_, i64>(11)? as u64, // duration_secs
format: Self::parse_format(&row.get::<_, String>(12)?), // format format: Self::parse_format(&row.get::<_, String>(12)?), // format
bit_depth: row.get(13)?, // bit_depth bit_depth: row.get(13)?, // bit_depth
sample_rate: row.get::<_, f64>(14)?, // sample_rate sample_rate: row.get::<_, f64>(14)?, // sample_rate
channels: row.get(15)?, // channels channels: row.get(15)?, // channels
file_size_bytes: row.get(16)?, // file_size_bytes file_size_bytes: row.get::<_, i64>(16)? as u64, // file_size_bytes
cue_file_path: row.get(17)?, // cue_file_path cue_file_path: row.get(17)?, // cue_file_path
cue_start_secs: row.get(18)?, // cue_start_secs cue_start_secs: row.get(18)?, // cue_start_secs
cue_end_secs: row.get(19)?, // cue_end_secs cue_end_secs: row.get(19)?, // cue_end_secs
@@ -5104,12 +5104,12 @@ impl LibraryDatabase {
row.get::<_, Option<String>>(11)?.as_deref(), row.get::<_, Option<String>>(11)?.as_deref(),
), ),
catalog_number: None, catalog_number: None,
duration_secs: row.get(13)?, duration_secs: row.get::<_, i64>(13)? as u64,
format: Self::parse_format(&row.get::<_, String>(14)?), format: Self::parse_format(&row.get::<_, String>(14)?),
bit_depth: row.get(15)?, bit_depth: row.get(15)?,
sample_rate: row.get::<_, f64>(16)?, sample_rate: row.get::<_, f64>(16)?,
channels: row.get(17)?, channels: row.get(17)?,
file_size_bytes: row.get(18)?, file_size_bytes: row.get::<_, i64>(18)? as u64,
cue_file_path: row.get(19)?, cue_file_path: row.get(19)?,
cue_start_secs: row.get(20)?, cue_start_secs: row.get(20)?,
cue_end_secs: row.get(21)?, cue_end_secs: row.get(21)?,
@@ -5179,12 +5179,12 @@ impl LibraryDatabase {
row.get::<_, Option<String>>(11)?.as_deref(), row.get::<_, Option<String>>(11)?.as_deref(),
), ),
catalog_number: None, catalog_number: None,
duration_secs: row.get(13)?, duration_secs: row.get::<_, i64>(13)? as u64,
format: Self::parse_format(&row.get::<_, String>(14)?), format: Self::parse_format(&row.get::<_, String>(14)?),
bit_depth: row.get(15)?, bit_depth: row.get(15)?,
sample_rate: row.get::<_, f64>(16)?, sample_rate: row.get::<_, f64>(16)?,
channels: row.get(17)?, channels: row.get(17)?,
file_size_bytes: row.get(18)?, file_size_bytes: row.get::<_, i64>(18)? as u64,
cue_file_path: row.get(19)?, cue_file_path: row.get(19)?,
cue_start_secs: row.get(20)?, cue_start_secs: row.get(20)?,
cue_end_secs: row.get(21)?, cue_end_secs: row.get(21)?,
+4 -4
View File
@@ -344,12 +344,12 @@ fn update_track(
track.year, track.year,
track.genre, track.genre,
track.catalog_number, track.catalog_number,
track.duration_secs, track.duration_secs as i64,
format, format,
track.bit_depth, track.bit_depth,
track.sample_rate, track.sample_rate,
track.channels, track.channels,
track.file_size_bytes, track.file_size_bytes as i64,
track.artwork_path, track.artwork_path,
track.last_modified, track.last_modified,
track.indexed_at, track.indexed_at,
@@ -397,12 +397,12 @@ fn insert_track(
track.year, track.year,
track.genre, track.genre,
track.catalog_number, track.catalog_number,
track.duration_secs, track.duration_secs as i64,
format, format,
track.bit_depth, track.bit_depth,
track.sample_rate, track.sample_rate,
track.channels, track.channels,
track.file_size_bytes, track.file_size_bytes as i64,
track.artwork_path, track.artwork_path,
track.last_modified, track.last_modified,
track.indexed_at, track.indexed_at,
+1 -1
View File
@@ -6,7 +6,7 @@ license.workspace = true
repository.workspace = true repository.workspace = true
[dependencies] [dependencies]
rusqlite = { version = "0.31", features = ["backup", "bundled"] } rusqlite = { version = "0.40", features = ["backup", "bundled"] }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
thiserror.workspace = true thiserror.workspace = true
+6 -4
View File
@@ -4,7 +4,7 @@ use std::time::{Duration, Instant};
use rusqlite::types::Value; use rusqlite::types::Value;
use rusqlite::{ use rusqlite::{
params, params_from_iter, Connection, DatabaseName, OpenFlags, OptionalExtension, Row, params, params_from_iter, Connection, OpenFlags, OptionalExtension, Row,
Statement, Statement,
}; };
@@ -96,7 +96,7 @@ impl Catalog {
} }
pub(crate) fn backup_to(&self, path: &Path) -> Result<()> { pub(crate) fn backup_to(&self, path: &Path) -> Result<()> {
self.conn.backup(DatabaseName::Main, path, None)?; self.conn.backup(rusqlite::MAIN_DB, path, None)?;
Ok(()) Ok(())
} }
@@ -495,7 +495,10 @@ impl Catalog {
)", )",
params![batch.source.source.as_str(), batch.source.source_instance], params![batch.source.source.as_str(), batch.source.source_instance],
)?; )?;
tx.execute( // Two statements, two calls: rusqlite < 0.32 silently ran only the
// FIRST statement of a multi-statement `execute` (the logical_albums
// prune below never happened); 0.40 refuses it outright.
tx.execute_batch(
"DELETE FROM editions "DELETE FROM editions
WHERE NOT EXISTS ( WHERE NOT EXISTS (
SELECT 1 FROM source_copies sc SELECT 1 FROM source_copies sc
@@ -506,7 +509,6 @@ impl Catalog {
SELECT 1 FROM editions e SELECT 1 FROM editions e
WHERE e.logical_album_id=logical_albums.logical_album_id WHERE e.logical_album_id=logical_albums.logical_album_id
);", );",
[],
)?; )?;
} }
tx.execute( tx.execute(
+1 -1
View File
@@ -10,7 +10,7 @@ authors = ["blitzkriegfc"]
qbz-qobuz = { path = "../qbz-qobuz" } qbz-qobuz = { path = "../qbz-qobuz" }
# Database (same pin as every other QBZ crate) # Database (same pin as every other QBZ crate)
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# HTTP (external fallback providers: LRCLIB, lyrics.ovh) # HTTP (external fallback providers: LRCLIB, lyrics.ovh)
reqwest = { workspace = true } reqwest = { workspace = true }
+1 -1
View File
@@ -6,7 +6,7 @@ license.workspace = true
description = "One SQLite cache for every remote media server QBZ reads (Jellyfin, Subsonic, and Plex when it migrates)" description = "One SQLite cache for every remote media server QBZ reads (Jellyfin, Subsonic, and Plex when it migrates)"
[dependencies] [dependencies]
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
serde_json.workspace = true serde_json.workspace = true
[dev-dependencies] [dev-dependencies]
+1 -1
View File
@@ -11,7 +11,7 @@ qbz-models = { path = "../qbz-models" }
qbz-qobuz = { path = "../qbz-qobuz" } qbz-qobuz = { path = "../qbz-qobuz" }
# Database # Database
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# UUID generation # UUID generation
uuid = { version = "1", features = ["v4"] } uuid = { version = "1", features = ["v4"] }
+1 -1
View File
@@ -14,7 +14,7 @@ qbz-secrets = { path = "../qbz-secrets" }
qbz-library = { path = "../qbz-library" } qbz-library = { path = "../qbz-library" }
# SQLite index (match qbz-library's version/features) # SQLite index (match qbz-library's version/features)
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# Async runtime + HTTP (download pipeline + artwork) # Async runtime + HTTP (download pipeline + artwork)
tokio = { workspace = true } tokio = { workspace = true }
+1 -1
View File
@@ -7,7 +7,7 @@ description = "Plex LAN-only integration for QBZ: HTTP client, XML metadata pars
[dependencies] [dependencies]
# SQLite cache (match the workspace's bundled SQLite usage) # SQLite cache (match the workspace's bundled SQLite usage)
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# HTTP client for the Plex Media Server LAN API # HTTP client for the Plex Media Server LAN API
reqwest = { workspace = true } reqwest = { workspace = true }
+3 -3
View File
@@ -1568,7 +1568,7 @@ pub fn plex_cache_get_tracks(
album: row album: row
.get::<_, Option<String>>(3)? .get::<_, Option<String>>(3)?
.map(|v| decode_xml_entities(v.trim())), .map(|v| decode_xml_entities(v.trim())),
duration_ms: row.get(4)?, duration_ms: row.get::<_, Option<i64>>(4)?.map(|v| v as u64),
artwork_path: row.get(5)?, artwork_path: row.get(5)?,
collection_artwork_path: row.get(19)?, collection_artwork_path: row.get(19)?,
part_key: row.get(6)?, part_key: row.get(6)?,
@@ -1616,7 +1616,7 @@ pub fn plex_cache_get_tracks(
album: row album: row
.get::<_, Option<String>>(3)? .get::<_, Option<String>>(3)?
.map(|v| decode_xml_entities(v.trim())), .map(|v| decode_xml_entities(v.trim())),
duration_ms: row.get(4)?, duration_ms: row.get::<_, Option<i64>>(4)?.map(|v| v as u64),
artwork_path: row.get(5)?, artwork_path: row.get(5)?,
collection_artwork_path: row.get(19)?, collection_artwork_path: row.get(19)?,
part_key: row.get(6)?, part_key: row.get(6)?,
@@ -1685,7 +1685,7 @@ pub fn plex_cache_get_tracks_by_keys(rating_keys: &[String]) -> Result<Vec<PlexT
album: row album: row
.get::<_, Option<String>>(3)? .get::<_, Option<String>>(3)?
.map(|v| decode_xml_entities(v.trim())), .map(|v| decode_xml_entities(v.trim())),
duration_ms: row.get(4)?, duration_ms: row.get::<_, Option<i64>>(4)?.map(|v| v as u64),
artwork_path: row.get(5)?, artwork_path: row.get(5)?,
collection_artwork_path: row.get(19)?, collection_artwork_path: row.get(19)?,
part_key: row.get(6)?, part_key: row.get(6)?,
+1 -1
View File
@@ -124,7 +124,7 @@ uuid = { version = "1", features = ["v4"] }
futures-util = { workspace = true } futures-util = { workspace = true }
async-trait = "0.1" async-trait = "0.1"
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
# Native file picker, used by exactly two call sites — the MyQBZ collection # Native file picker, used by exactly two call sites — the MyQBZ collection
+1 -1
View File
@@ -9,6 +9,6 @@ description = "Agnostic smart-radio pool builder for QBZ (ported from the Tauri
qbz-models = { path = "../qbz-models" } qbz-models = { path = "../qbz-models" }
qbz-qobuz = { path = "../qbz-qobuz" } qbz-qobuz = { path = "../qbz-qobuz" }
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
log = { workspace = true } log = { workspace = true }
dirs = "5" dirs = "5"
+1 -1
View File
@@ -7,7 +7,7 @@ description = "Frontend-agnostic artist-vector recommendation engine for QBZ (po
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
log = { workspace = true } log = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
rand = { workspace = true } rand = { workspace = true }
+1 -1
View File
@@ -221,7 +221,7 @@ impl ArtistVectorStore {
.execute( .execute(
"INSERT OR REPLACE INTO vector_metadata (artist_idx, updated_at, nnz) "INSERT OR REPLACE INTO vector_metadata (artist_idx, updated_at, nnz)
VALUES (?1, ?2, ?3)", VALUES (?1, ?2, ?3)",
params![artist_idx, now, vector.nnz()], params![artist_idx, now, vector.nnz() as i64],
) )
.map_err(|e| format!("Failed to update metadata: {}", e))?; .map_err(|e| format!("Failed to update metadata: {}", e))?;
+1 -1
View File
@@ -24,7 +24,7 @@ qbz-subsonic = { path = "../qbz-subsonic" }
qbz-media-cache = { path = "../qbz-media-cache" } qbz-media-cache = { path = "../qbz-media-cache" }
# Only to hold the cache's `Connection`. This crate never writes SQL of its own # Only to hold the cache's `Connection`. This crate never writes SQL of its own
# — every statement lives in `qbz-media-cache` or `qbz-library`. # — every statement lives in `qbz-media-cache` or `qbz-library`.
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.40", features = ["bundled"] }
# `ItemResolver` — this crate IMPLEMENTS it (mixtape_adapter.rs). qbz-mixtape is # `ItemResolver` — this crate IMPLEMENTS it (mixtape_adapter.rs). qbz-mixtape is
# provider-agnostic and the dependency remains one-directional, so the Qt # provider-agnostic and the dependency remains one-directional, so the Qt
# application can consume the adapter without coupling qbz-mixtape to sources. # application can consume the adapter without coupling qbz-mixtape to sources.
+1 -1
View File
@@ -37,7 +37,7 @@ log = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
# QConnect glue copies (T9) — mirror the desktop copies' direct imports # QConnect glue copies (T9) — mirror the desktop copies' direct imports
uuid = { version = "1", features = ["v4"] } # renderer-report action uuids uuid = { version = "1", features = ["v4"] } # renderer-report action uuids
rusqlite = { version = "0.31", features = ["bundled"] } # daemon-root qconnect_settings.db KV rusqlite = { version = "0.40", features = ["bundled"] } # daemon-root qconnect_settings.db KV
futures-util = { workspace = true } # remote_stream.rs feeder (byte-exact) futures-util = { workspace = true } # remote_stream.rs feeder (byte-exact)
# new direct deps (versions already in lock where noted) # new direct deps (versions already in lock where noted)
tiny_http = "0.12" # in lock via qbz-cast tiny_http = "0.12" # in lock via qbz-cast