Skip to content

Commit ae9eec7

Browse files
committed
demo-app: Extract reusable code for desktop-app
1 parent e0fec2b commit ae9eec7

File tree

4 files changed

+113
-124
lines changed

4 files changed

+113
-124
lines changed

crates/desktop-app/src/collection/mod.rs

+99-4
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33

44
use std::{
55
borrow::Cow,
6+
future::Future,
67
path::{Path, PathBuf},
8+
sync::{Arc, Mutex},
79
time::Instant,
810
};
911

12+
use discro::{Publisher, Subscriber};
1013
use url::Url;
1114

12-
use aoide_backend_embedded::batch::{
13-
self,
14-
synchronize_collection_vfs::{
15-
OrphanedMediaSources, UnsynchronizedTracks, UntrackedFiles, UntrackedMediaSources,
15+
use aoide_backend_embedded::{
16+
batch::{
17+
self,
18+
synchronize_collection_vfs::{
19+
OrphanedMediaSources, Outcome, Progress, UnsynchronizedTracks, UntrackedFiles,
20+
UntrackedMediaSources,
21+
},
1622
},
23+
media::predefined_faceted_tag_mapping_config,
1724
};
1825
use aoide_core::{
1926
collection::{Collection, Entity, EntityUid, MediaSourceConfig},
@@ -845,3 +852,91 @@ where
845852
.await
846853
.map_err(Into::into)
847854
}
855+
856+
#[derive(Debug)]
857+
pub struct SynchronizeVfsTask {
858+
started_at: Instant,
859+
progress: Subscriber<Option<Progress>>,
860+
join_handle: tokio::task::JoinHandle<Option<Outcome>>,
861+
}
862+
863+
impl SynchronizeVfsTask {
864+
#[must_use]
865+
#[allow(clippy::missing_panics_doc)]
866+
pub fn spawn(
867+
rt: &tokio::runtime::Handle,
868+
handle: Handle,
869+
collection: Arc<ObservableState>,
870+
) -> Self {
871+
let started_at = Instant::now();
872+
let progress_pub = Publisher::new(None);
873+
let progress = progress_pub.subscribe();
874+
let report_progress_fn = {
875+
// TODO: How to avoid wrapping the publisher?
876+
let progress_pub = Arc::new(Mutex::new(progress_pub));
877+
move |progress: Option<Progress>| {
878+
progress_pub.lock().unwrap().write(progress);
879+
}
880+
};
881+
let task = synchronize_vfs_task(handle, collection, report_progress_fn);
882+
let join_handle = rt.spawn(task);
883+
Self {
884+
started_at,
885+
progress,
886+
join_handle,
887+
}
888+
}
889+
890+
#[must_use]
891+
pub const fn started_at(&self) -> Instant {
892+
self.started_at
893+
}
894+
895+
#[must_use]
896+
pub const fn progress(&self) -> &Subscriber<Option<Progress>> {
897+
&self.progress
898+
}
899+
900+
pub fn abort(&self) {
901+
self.join_handle.abort();
902+
}
903+
904+
#[must_use]
905+
pub fn is_finished(&self) -> bool {
906+
self.join_handle.is_finished()
907+
}
908+
909+
pub async fn join(self) -> anyhow::Result<Option<Outcome>> {
910+
self.join_handle.await.map_err(Into::into)
911+
}
912+
}
913+
914+
#[allow(clippy::manual_async_fn)] // Required to specify the trait bounds of the returned `Future` explicitly.
915+
fn synchronize_vfs_task(
916+
handle: Handle,
917+
state: Arc<ObservableState>,
918+
mut report_progress_fn: impl FnMut(Option<Progress>) + Clone + Send + 'static,
919+
) -> impl Future<Output = Option<Outcome>> + Send + 'static {
920+
async move {
921+
log::debug!("Synchronizing collection...");
922+
let import_track_config = ImportTrackConfig {
923+
// TODO: Customize faceted tag mapping
924+
faceted_tag_mapping: predefined_faceted_tag_mapping_config(),
925+
..Default::default()
926+
};
927+
let outcome = {
928+
let mut report_progress_fn = report_progress_fn.clone();
929+
let report_progress_fn = move |progress| {
930+
report_progress_fn(Some(progress));
931+
};
932+
state
933+
.synchronize_vfs(&handle, import_track_config, report_progress_fn)
934+
.await
935+
};
936+
report_progress_fn(None);
937+
log::debug!("Synchronizing collection finished: {outcome:?}");
938+
// Implicitly refresh the state from the database to reflect the changes.
939+
state.refresh_from_db(&handle).await;
940+
outcome
941+
}
942+
}

demo-app/src/library/collection.rs

+2-110
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
11
// SPDX-FileCopyrightText: Copyright (C) 2018-2024 Uwe Klotz <uwedotklotzatgmaildotcom> et al.
22
// SPDX-License-Identifier: AGPL-3.0-or-later
33

4-
use std::{
5-
future::Future,
6-
sync::{Arc, Mutex},
7-
time::Instant,
8-
};
4+
use discro::Subscriber;
95

10-
use discro::{Publisher, Subscriber};
11-
12-
use aoide::{
13-
backend_embedded::media::predefined_faceted_tag_mapping_config,
14-
desktop_app::{collection, Handle},
15-
media_file::io::import::ImportTrackConfig,
16-
};
6+
use aoide::desktop_app::collection;
177

188
use crate::NoReceiverForEvent;
199

@@ -43,101 +33,3 @@ where
4333
}
4434
}
4535
}
46-
47-
pub struct RescanTask {
48-
started_at: Instant,
49-
progress:
50-
Subscriber<Option<aoide::backend_embedded::batch::synchronize_collection_vfs::Progress>>,
51-
join_handle: tokio::task::JoinHandle<
52-
Option<aoide::backend_embedded::batch::synchronize_collection_vfs::Outcome>,
53-
>,
54-
}
55-
56-
impl RescanTask {
57-
pub fn spawn(
58-
rt: &tokio::runtime::Handle,
59-
handle: Handle,
60-
collection: Arc<collection::ObservableState>,
61-
) -> Self {
62-
let started_at = Instant::now();
63-
let progress_pub = Publisher::new(None);
64-
let progress = progress_pub.subscribe();
65-
let report_progress_fn = {
66-
// TODO: How to avoid wrapping the publisher?
67-
let progress_pub = Arc::new(Mutex::new(progress_pub));
68-
move |progress: Option<
69-
aoide::backend_embedded::batch::synchronize_collection_vfs::Progress,
70-
>| {
71-
progress_pub.lock().unwrap().write(progress);
72-
}
73-
};
74-
let task = synchronize_music_dir_task(handle, collection, report_progress_fn);
75-
let join_handle = rt.spawn(task);
76-
Self {
77-
started_at,
78-
progress,
79-
join_handle,
80-
}
81-
}
82-
83-
pub const fn started_at(&self) -> Instant {
84-
self.started_at
85-
}
86-
87-
pub const fn progress(
88-
&self,
89-
) -> &Subscriber<Option<aoide::backend_embedded::batch::synchronize_collection_vfs::Progress>>
90-
{
91-
&self.progress
92-
}
93-
94-
pub fn abort(&self) {
95-
self.join_handle.abort();
96-
}
97-
98-
pub fn is_finished(&self) -> bool {
99-
self.join_handle.is_finished()
100-
}
101-
102-
pub async fn join(
103-
self,
104-
) -> anyhow::Result<Option<aoide::backend_embedded::batch::synchronize_collection_vfs::Outcome>>
105-
{
106-
self.join_handle.await.map_err(Into::into)
107-
}
108-
}
109-
110-
#[allow(clippy::manual_async_fn)] // Required to specify the trait bounds of the returned `Future` explicitly.
111-
fn synchronize_music_dir_task(
112-
handle: Handle,
113-
state: Arc<ObservableState>,
114-
mut report_progress_fn: impl FnMut(Option<aoide::backend_embedded::batch::synchronize_collection_vfs::Progress>)
115-
+ Clone
116-
+ Send
117-
+ 'static,
118-
) -> impl Future<Output = Option<aoide::backend_embedded::batch::synchronize_collection_vfs::Outcome>>
119-
+ Send
120-
+ 'static {
121-
async move {
122-
log::debug!("Synchronizing collection with music directory...");
123-
let import_track_config = ImportTrackConfig {
124-
// TODO: Customize faceted tag mapping
125-
faceted_tag_mapping: predefined_faceted_tag_mapping_config(),
126-
..Default::default()
127-
};
128-
let outcome = {
129-
let mut report_progress_fn = report_progress_fn.clone();
130-
let report_progress_fn = move |progress| {
131-
report_progress_fn(Some(progress));
132-
};
133-
state
134-
.synchronize_vfs(&handle, import_track_config, report_progress_fn)
135-
.await
136-
};
137-
report_progress_fn(None);
138-
log::debug!("Synchronizing collection with music directory finished: {outcome:?}");
139-
// Implicitly refresh the state from the database to reflect the changes.
140-
state.refresh_from_db(&handle).await;
141-
outcome
142-
}
143-
}

demo-app/src/library/mod.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use std::{num::NonZeroUsize, path::PathBuf, sync::Arc};
55

66
use aoide::{
77
api::media::source::ResolveUrlFromContentPath,
8-
desktop_app::{fs::DirPath, track::repo_search::FetchMoreSucceeded, Handle, ObservableReader},
8+
desktop_app::{
9+
collection::SynchronizeVfsTask, fs::DirPath, track::repo_search::FetchMoreSucceeded,
10+
Handle, ObservableReader,
11+
},
912
};
1013

1114
use crate::NoReceiverForEvent;
@@ -97,7 +100,7 @@ impl LibraryState {
97100
pub struct Library {
98101
handle: Handle,
99102
state: LibraryState,
100-
pending_rescan_collection_task: Option<collection::RescanTask>,
103+
pending_rescan_collection_task: Option<SynchronizeVfsTask>,
101104
}
102105

103106
impl Library {
@@ -145,7 +148,7 @@ impl Library {
145148
log::info!("Spawning rescan collection task");
146149
let handle = self.handle.clone();
147150
let collection = Arc::clone(&self.state.collection);
148-
let rescan_collection_task = collection::RescanTask::spawn(rt, handle, collection);
151+
let rescan_collection_task = SynchronizeVfsTask::spawn(rt, handle, collection);
149152
self.pending_rescan_collection_task = Some(rescan_collection_task);
150153
true
151154
}
@@ -162,11 +165,11 @@ impl Library {
162165
changed
163166
}
164167

165-
pub const fn pending_rescan_collection_task(&self) -> Option<&collection::RescanTask> {
166-
self.pending_rescan_collection_task.as_ref()
168+
pub const fn has_pending_rescan_collection_task(&self) -> bool {
169+
self.pending_rescan_collection_task.is_some()
167170
}
168171

169-
pub fn abort_pending_rescan_collection_task(&mut self) -> Option<collection::RescanTask> {
172+
pub fn abort_pending_rescan_collection_task(&mut self) -> Option<SynchronizeVfsTask> {
170173
let pending_rescan_collection_task = self.pending_rescan_collection_task.take();
171174
let Some(rescan_collection_task) = pending_rescan_collection_task else {
172175
return None;
@@ -191,9 +194,8 @@ impl Library {
191194
resolve_url_from_content_path,
192195
};
193196
// Argument is consumed when updating succeeds
194-
if self.state.track_search.update_params(&mut params) {
195-
log::debug!("Track search params updated: {params:?}");
196-
} else {
197+
log::debug!("Updating track search params: {params:?}");
198+
if !self.state.track_search.update_params(&mut params) {
197199
log::debug!("Track search params not updated: {params:?}");
198200
}
199201
}

demo-app/src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ impl eframe::App for App {
522522
if ui
523523
.add_enabled(
524524
collection_state.is_ready()
525-
&& self.library.pending_rescan_collection_task().is_none(),
525+
&& !self.library.has_pending_rescan_collection_task(),
526526
Button::new("Rescan collection"),
527527
)
528528
.clicked()

0 commit comments

Comments
 (0)