mecomp_tui/state/
library.rs1use mecomp_prost::{
6 DynamicPlaylistCreateRequest, DynamicPlaylistUpdateRequest, LibraryAnalyzeRequest,
7 LibraryBriefResponse as LibraryBrief, MusicPlayerClient, PlaylistAddListRequest, PlaylistName,
8 PlaylistRemoveSongsRequest, PlaylistRenameRequest,
9};
10use tokio::sync::{
11 broadcast,
12 mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
13};
14
15use crate::termination::Interrupted;
16
17use super::action::LibraryAction;
18
19#[derive(Debug, Clone)]
21#[allow(clippy::module_name_repetitions)]
22pub struct LibraryState {
23 state_tx: UnboundedSender<LibraryBrief>,
24}
25
26impl LibraryState {
27 #[must_use]
29 pub fn new() -> (Self, UnboundedReceiver<LibraryBrief>) {
30 let (state_tx, state_rx) = unbounded_channel::<LibraryBrief>();
31
32 (Self { state_tx }, state_rx)
33 }
34
35 pub async fn main_loop(
43 &self,
44 mut daemon: MusicPlayerClient,
45 mut action_rx: UnboundedReceiver<LibraryAction>,
46 mut interrupt_rx: broadcast::Receiver<Interrupted>,
47 ) -> anyhow::Result<Interrupted> {
48 let state = get_library(&mut daemon).await?;
50 self.state_tx.send(state)?;
51
52 loop {
53 tokio::select! {
54 Some(action) = action_rx.recv() => {
57 handle_action(&self.state_tx, &mut daemon, action).await?;
58 },
59 Ok(interrupted) = interrupt_rx.recv() => {
61 break Ok(interrupted);
62 }
63 }
64 }
65 }
66}
67async fn handle_action(
68 state_tx: &UnboundedSender<LibraryBrief>,
69 daemon: &mut MusicPlayerClient,
70 action: LibraryAction,
71) -> anyhow::Result<()> {
72 let mut update = false;
73 let mut flag_update = || update = true;
74
75 match action {
76 LibraryAction::Rescan => rescan_library(daemon).await?,
77 LibraryAction::Update => flag_update(),
78 LibraryAction::Analyze => analyze_library(daemon).await?,
79 LibraryAction::Recluster => recluster_library(daemon).await?,
80 LibraryAction::CreatePlaylist(name) => daemon
81 .playlist_get_or_create(PlaylistName::new(name))
82 .await?
83 .map(|_| flag_update())
84 .into_inner(),
85 LibraryAction::RemovePlaylist(id) => daemon
86 .playlist_remove(id)
87 .await?
88 .map(|()| flag_update())
89 .into_inner(),
90 LibraryAction::RenamePlaylist(id, name) => daemon
91 .playlist_rename(PlaylistRenameRequest::new(id, name))
92 .await?
93 .map(|_| flag_update())
94 .into_inner(),
95 LibraryAction::RemoveSongsFromPlaylist(playlist, songs) => daemon
96 .playlist_remove_songs(PlaylistRemoveSongsRequest::new(playlist, songs))
97 .await?
98 .map(|()| flag_update())
99 .into_inner(),
100 LibraryAction::AddThingsToPlaylist(playlist, things) => daemon
101 .playlist_add_list(PlaylistAddListRequest::new(playlist, things))
102 .await?
103 .map(|()| flag_update())
104 .into_inner(),
105 LibraryAction::CreatePlaylistAndAddThings(name, things) => {
106 let playlist = daemon
107 .playlist_get_or_create(PlaylistName::new(name))
108 .await?
109 .into_inner();
110 daemon
111 .playlist_add_list(PlaylistAddListRequest::new(playlist, things))
112 .await?
113 .map(|()| flag_update())
114 .into_inner();
115 }
116 LibraryAction::CreateDynamicPlaylist(name, query) => daemon
117 .dynamic_playlist_create(DynamicPlaylistCreateRequest::new(name, query))
118 .await?
119 .map(|_| flag_update())
120 .into_inner(),
121 LibraryAction::RemoveDynamicPlaylist(id) => daemon
122 .dynamic_playlist_remove(id)
123 .await?
124 .map(|()| flag_update())
125 .into_inner(),
126 LibraryAction::UpdateDynamicPlaylist(id, changes) => daemon
127 .dynamic_playlist_update(DynamicPlaylistUpdateRequest::new(id, changes))
128 .await?
129 .map(|_| flag_update())
130 .into_inner(),
131 }
132
133 if update {
134 let state = get_library(daemon).await?;
135 state_tx.send(state)?;
136 }
137
138 Ok(())
139}
140
141async fn get_library(daemon: &mut MusicPlayerClient) -> anyhow::Result<LibraryBrief> {
142 Ok(daemon.library_brief(()).await?.into_inner())
143}
144
145async fn rescan_library(daemon: &mut MusicPlayerClient) -> anyhow::Result<()> {
147 match daemon.library_rescan(()).await {
149 Ok(_) => Ok(()),
150 Err(e) if e.code() == tonic::Code::Aborted => Ok(()),
151 Err(e) => Err(e.into()),
152 }
153}
154
155async fn analyze_library(daemon: &mut MusicPlayerClient) -> anyhow::Result<()> {
157 match daemon
159 .library_analyze(LibraryAnalyzeRequest::new(false))
160 .await
161 {
162 Ok(_) => Ok(()),
163 Err(e) if e.code() == tonic::Code::Aborted => Ok(()),
164 Err(e) => Err(e.into()),
165 }
166}
167
168async fn recluster_library(daemon: &mut MusicPlayerClient) -> anyhow::Result<()> {
170 match daemon.library_recluster(()).await {
171 Ok(_) => Ok(()),
172 Err(e) if e.code() == tonic::Code::Aborted => Ok(()),
173 Err(e) => Err(e.into()),
174 }
175}