Skip to content

Commit fe8423a

Browse files
feat(storage): use computed fields in favor of calling repair all the time (#311)
closes #308 I'd like to make better use of SurrealDB's features for things like automatically deleting orphaned items as well, but that's a task for future me. This does cause a major (breaking) change to the DB schema though so v0.4.0 will be coming soon! Somewhere along the line, I also fixed #301
2 parents e55d1ea + 833f46e commit fe8423a

File tree

18 files changed

+257
-273
lines changed

18 files changed

+257
-273
lines changed

cli/src/handlers/snapshots/mecomp_cli__handlers__smoke_tests__handlers_smoke_tests_test_playlist_command_case_02@stdout.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ source: cli/src/handlers/smoke_tests.rs
33
expression: "String::from_utf8(stdout.0.clone()).unwrap()"
44
---
55
Daemon response:
6-
Err(Database("SurrealDB error: Serialization error: missing field `name`"))
6+
Ok("album added to playlist")

core/src/logger.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::time::{Duration, Instant};
1+
use std::time::Instant;
22
use std::{io::Write, sync::LazyLock};
33

44
use env_logger::fmt::style::{RgbColor, Style};
@@ -259,7 +259,7 @@ pub fn init_tracing() -> impl tracing::Subscriber {
259259

260260
#[cfg(feature = "tokio_console")]
261261
let console_layer = console_subscriber::Builder::default()
262-
.retention(Duration::from_secs(60 * 20)) // 20 minutes
262+
.retention(std::time::Duration::from_secs(60 * 20)) // 20 minutes
263263
.enable_self_trace(true)
264264
.spawn();
265265
#[cfg(feature = "tokio_console")]

daemon/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ tracing = { workspace = true }
6363
walkdir = { workspace = true }
6464

6565
# MECOMP dependencies
66-
mecomp-core = { workspace = true, features = ["rpc", "audio", "tokio_console"] }
66+
mecomp-core = { workspace = true, features = ["rpc", "audio"] }
6767
mecomp-storage = { workspace = true, features = ["serde", "db"] }
6868
mecomp-analysis = { workspace = true, optional = true }
6969
one-or-many = { workspace = true }

daemon/src/controller.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,9 @@ impl MusicPlayer for MusicPlayerServer {
11371137
let (parsed_name, song_paths) =
11381138
import_playlist(file).tap_err(|e| warn!("Error in playlist_import: {e}"))?;
11391139

1140+
log::debug!("Parsed playlist name: {parsed_name:?}");
1141+
log::debug!("Parsed song paths: {song_paths:?}");
1142+
11401143
let name = match (name, parsed_name) {
11411144
(Some(name), _) | (None, Some(name)) => name,
11421145
(None, None) => "Imported Playlist".to_owned(),

daemon/src/services/library.rs

Lines changed: 36 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub async fn rescan<C: Connection>(
8585
};
8686
info!(
8787
"{} has conflicting metadata with index, {log_postfix}",
88-
path.to_string_lossy(),
88+
path.display(),
8989
);
9090

9191
match conflict_resolution_mode {
@@ -103,9 +103,8 @@ pub async fn rescan<C: Connection>(
103103
// if we have an error, delete the song from the library
104104
Err(e) => {
105105
warn!(
106-
"Error reading metadata for {}: {}",
107-
path.to_string_lossy(),
108-
e
106+
"Error reading metadata for {}: {e}",
107+
path.display()
109108
);
110109
info!("assuming the file isn't a song or doesn't exist anymore, removing from library");
111110
Song::delete(db, song.id).await?;
@@ -143,6 +142,8 @@ pub async fn rescan<C: Connection>(
143142

144143
visited_paths.insert(path.path().to_owned());
145144

145+
let displayable_path = path.path().display();
146+
146147
// if the file is a song, add it to the library
147148
match SongMetadata::load_from_path(
148149
path.path().to_owned(),
@@ -151,14 +152,10 @@ pub async fn rescan<C: Connection>(
151152
genre_separator,
152153
) {
153154
Ok(metadata) => Song::try_load_into_db(db, metadata).await.map_or_else(
154-
|e| warn!("Error indexing {}: {}", path.path().to_string_lossy(), e),
155-
|_| debug!("Indexed {}", path.path().to_string_lossy()),
156-
),
157-
Err(e) => warn!(
158-
"Error reading metadata for {}: {}",
159-
path.path().to_string_lossy(),
160-
e
155+
|e| warn!("Error indexing {displayable_path}: {e}"),
156+
|_| debug!("Indexed {displayable_path}"),
161157
),
158+
Err(e) => warn!("Error reading metadata for {displayable_path}: {e}"),
162159
}
163160
}
164161

@@ -168,52 +165,34 @@ pub async fn rescan<C: Connection>(
168165
.await?;
169166

170167
// find and delete any remaining orphaned albums and artists
171-
// TODO: create a custom query for this
172168

173-
async {
174-
for album in Album::read_all(db).await? {
175-
if Album::repair(db, album.id.clone()).await? {
176-
info!("Deleted orphaned album {}", album.id.clone());
177-
Album::delete(db, album.id.clone()).await?;
178-
}
179-
}
180-
<Result<(), Error>>::Ok(())
169+
let orphans = Album::delete_orphaned(db)
170+
.instrument(tracing::info_span!("Deleting orphaned albums"))
171+
.await?;
172+
if !orphans.is_empty() {
173+
info!("Deleted orphaned albums: {orphans:?}");
181174
}
182-
.instrument(tracing::info_span!("Repairing albums"))
183-
.await?;
184-
async {
185-
for artist in Artist::read_all(db).await? {
186-
if Artist::repair(db, artist.id.clone()).await? {
187-
info!("Deleted orphaned artist {}", artist.id.clone());
188-
Artist::delete(db, artist.id.clone()).await?;
189-
}
190-
}
191-
<Result<(), Error>>::Ok(())
175+
176+
let orphans = Artist::delete_orphaned(db)
177+
.instrument(tracing::info_span!("Repairing artists"))
178+
.await?;
179+
if !orphans.is_empty() {
180+
info!("Deleted orphaned artists: {orphans:?}");
192181
}
193-
.instrument(tracing::info_span!("Repairing artists"))
194-
.await?;
195-
async {
196-
for collection in Collection::read_all(db).await? {
197-
if Collection::repair(db, collection.id.clone()).await? {
198-
info!("Deleted orphaned collection {}", collection.id.clone());
199-
Collection::delete(db, collection.id.clone()).await?;
200-
}
201-
}
202-
<Result<(), Error>>::Ok(())
182+
183+
let orphans = Collection::delete_orphaned(db)
184+
.instrument(tracing::info_span!("Repairing collections"))
185+
.await?;
186+
if !orphans.is_empty() {
187+
info!("Deleted orphaned collections: {orphans:?}");
203188
}
204-
.instrument(tracing::info_span!("Repairing collections"))
205-
.await?;
206-
async {
207-
for playlist in Playlist::read_all(db).await? {
208-
if Playlist::repair(db, playlist.id.clone()).await? {
209-
info!("Deleted orphaned playlist {}", playlist.id.clone());
210-
Playlist::delete(db, playlist.id.clone()).await?;
211-
}
212-
}
213-
<Result<(), Error>>::Ok(())
189+
190+
let orphans = Playlist::delete_orphaned(db)
191+
.instrument(tracing::info_span!("Repairing playlists"))
192+
.await?;
193+
if !orphans.is_empty() {
194+
info!("Deleted orphaned playlists: {orphans:?}");
214195
}
215-
.instrument(tracing::info_span!("Repairing playlists"))
216-
.await?;
217196

218197
info!("Library rescan complete");
219198
info!("Library brief: {:?}", brief(db).await?);
@@ -747,6 +726,11 @@ mod tests {
747726
// check that the album and artist deleted
748727
assert_eq!(Song::read_all(&db).await.unwrap().len(), 0);
749728
assert_eq!(Album::read_all(&db).await.unwrap().len(), 0);
729+
let artists = Artist::read_all(&db).await.unwrap();
730+
for artist in artists {
731+
assert_eq!(artist.album_count, 0);
732+
assert_eq!(artist.song_count, 0);
733+
}
750734
assert_eq!(Artist::read_all(&db).await.unwrap().len(), 0);
751735
}
752736

storage/src/db/crud/album.rs

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,6 @@ impl Album {
158158
.bind(("songs", song_ids))
159159
.await?;
160160

161-
Self::repair(db, id).await?;
162-
163161
Ok(())
164162
}
165163

@@ -181,12 +179,12 @@ impl Album {
181179
db: &Surreal<C>,
182180
id: AlbumId,
183181
song_ids: Vec<SongId>,
184-
) -> StorageResult<bool> {
182+
) -> StorageResult<()> {
185183
db.query(remove_songs())
186184
.bind(("album", id.clone()))
187185
.bind(("songs", song_ids))
188186
.await?;
189-
Self::repair(db, id).await
187+
Ok(())
190188
}
191189

192190
#[instrument]
@@ -197,32 +195,15 @@ impl Album {
197195
Ok(db.query(read_artist()).bind(("id", id)).await?.take(0)?)
198196
}
199197

200-
/// update counts and runtime
198+
/// Deletes all orphaned albums from the database
201199
///
202-
/// # Arguments
203-
///
204-
/// * `id` - The id of the album to repair
205-
///
206-
/// # Returns
207-
///
208-
/// Returns a boolean indicating if the album should be removed (if it has no songs left in it)
209-
#[instrument()]
210-
pub async fn repair<C: Connection>(db: &Surreal<C>, id: AlbumId) -> StorageResult<bool> {
211-
// remove or update the album and return
212-
let songs = Self::read_songs(db, id.clone()).await?;
213-
214-
Self::update(
215-
db,
216-
id,
217-
AlbumChangeSet {
218-
song_count: Some(songs.len()),
219-
runtime: Some(songs.iter().map(|s| s.runtime).sum()),
220-
..Default::default()
221-
},
222-
)
223-
.await?;
224-
225-
Ok(songs.is_empty())
200+
/// An orphaned album is an album that has no songs in it
201+
#[instrument]
202+
pub async fn delete_orphaned<C: Connection>(db: &Surreal<C>) -> StorageResult<Vec<Self>> {
203+
Ok(db
204+
.query("DELETE FROM album WHERE type::int(song_count) = 0 RETURN BEFORE")
205+
.await?
206+
.take(0)?)
226207
}
227208
}
228209

@@ -551,8 +532,11 @@ mod tests {
551532
.ok_or_else(|| anyhow!("Failed to create song"))?;
552533

553534
Album::add_songs(&db, album.id.clone(), vec![song.id.clone()]).await?;
554-
assert!(Album::remove_songs(&db, album.id.clone(), vec![song.id.clone()]).await?);
535+
let read = Album::read_songs(&db, album.id.clone()).await?;
536+
assert_eq!(read.len(), 1);
537+
assert_eq!(read[0], song);
555538

539+
Album::remove_songs(&db, album.id.clone(), vec![song.id.clone()]).await?;
556540
let read = Album::read_songs(&db, album.id.clone()).await?;
557541
assert_eq!(read.len(), 0);
558542
Ok(())

0 commit comments

Comments
 (0)