From 6a247310a687b636161c49214fd90618da2d5271 Mon Sep 17 00:00:00 2001
From: Kubat <maelle.martin@proton.me>
Date: Tue, 3 Dec 2024 19:42:21 +0100
Subject: [PATCH] NETWORK: Handle playlists messages

- Query things by id and not names
- Modify LKT
- Handle things in amadeus
---
 amadeus/src/app.rs                    | 81 ++++++++++++++++++---------
 amadeus/src/playlist.rs               | 12 ++--
 amadeus/src/store.rs                  | 23 ++++++--
 lektor_lib/src/requests.rs            | 14 +++--
 lektor_nkdb/src/playlists/playlist.rs | 25 +++++----
 lektor_payloads/src/action.rs         |  3 +
 lektord/src/app/routes.rs             | 21 ++++---
 lkt/src/lib.rs                        | 18 ++++--
 8 files changed, 133 insertions(+), 64 deletions(-)

diff --git a/amadeus/src/app.rs b/amadeus/src/app.rs
index ba976dc9..e40eda13 100644
--- a/amadeus/src/app.rs
+++ b/amadeus/src/app.rs
@@ -33,7 +33,9 @@ use futures::{
     stream::{self, FuturesUnordered},
 };
 use lektor_lib::*;
-use lektor_payloads::{Epochs, KId, Kara, Priority, SearchFrom, PRIORITY_LENGTH, PRIORITY_VALUES};
+use lektor_payloads::{
+    Epochs, KId, Kara, PlaylistInfo, Priority, SearchFrom, PRIORITY_LENGTH, PRIORITY_VALUES,
+};
 use lektor_utils::{config::SocketScheme, open};
 use std::{
     borrow::Cow,
@@ -167,6 +169,7 @@ pub enum LektordMessage {
     ChangedQueueLevel(Priority, Vec<KId>),
     ChangedHistory(Vec<KId>),
     ChangedAvailablePlaylists(Vec<KId>),
+    ChangedAvailablePlaylistInfos(PlaylistInfo),
     ChangedPlaylistContent(KId, Vec<KId>),
     ChangedPlaylistsContent(Vec<(KId, Vec<KId>)>),
 }
@@ -612,26 +615,18 @@ impl AppModel {
 
             // Down here, got updates from lektord.
             LektordMessage::ChangedAvailablePlaylists(names) => {
-                let config = self.connect_config.clone();
-                let playlists = self.store.keep_playlists(names);
-                Task::future(async move {
-                    let updated_playlists = stream::iter(playlists)
-                        .zip(stream::repeat_with(move || config.clone()))
-                        .filter_map(|(id, config)| async move {
-                            let config = config.read().await;
-                            requests::get_playlist_content(config.as_ref(), id)
-                                .await
-                                .map(|content| (id, content))
-                                .ok()
-                        })
-                        .collect::<FuturesUnordered<_>>()
-                        .await;
-                    cosmic::app::Message::App(Message::LektordUpdate(
-                        LektordMessage::ChangedPlaylistsContent(Vec::from_iter(updated_playlists)),
-                    ))
-                })
+                let playlists = self.store.keep_playlists(&names);
+                self.update_playlists_content(playlists)
             }
 
+            LektordMessage::ChangedAvailablePlaylistInfos(infos) => (self.store)
+                .set_playlist_infos(infos)
+                .map_or_else(Task::none, |plt_id| {
+                    Task::done(cosmic::app::Message::App(Message::SendCommand(
+                        LektordCommand::PlaylistGetContent(plt_id),
+                    )))
+                }),
+
             LektordMessage::ChangedHistory(kids) => {
                 self.store.set_history(kids);
                 Task::none()
@@ -662,6 +657,28 @@ impl AppModel {
         }
     }
 
+    /// Update playlists' content.
+    fn update_playlists_content(&mut self, playlists: Vec<KId>) -> Task<Message> {
+        let config = self.connect_config.clone();
+
+        Task::future(async move {
+            let updated_playlists = stream::iter(playlists)
+                .zip(stream::repeat_with(move || config.clone()))
+                .filter_map(|(id, config)| async move {
+                    requests::get_playlist_content(config.read().await.as_ref(), id)
+                        .await
+                        .map(|content| (id, content))
+                        .ok()
+                })
+                .collect::<FuturesUnordered<_>>()
+                .await;
+
+            cosmic::app::Message::App(Message::LektordUpdate(
+                LektordMessage::ChangedPlaylistsContent(Vec::from_iter(updated_playlists)),
+            ))
+        })
+    }
+
     /// Send commands to lektord.
     fn send_command(&mut self, cmd: LektordCommand) -> Task<Message> {
         let config = self.connect_config.clone();
@@ -686,7 +703,7 @@ impl AppModel {
                 Ok($res) => $handle,
                 Err(err) => {
                     log::error!("failed '{}': {err}", stringify!($txt));
-                    cosmic::app::message::none()
+                    return cosmic::app::message::none()
                 }
             }};
         }
@@ -703,10 +720,9 @@ impl AppModel {
             LektordCommand::QueueClear => cmd!(remove_range_from_queue(..)),
             LektordCommand::QueueCrop => cmd!(remove_range_from_queue(1..)),
             LektordCommand::QueueGet => cmd!(get_queue_range(..), queue => {
-                msg!(ChangedQueue(queue.into_iter().fold(<[Vec<KId>; PRIORITY_LENGTH]>::default(), |mut ret, (level, kid)| {
-                    ret[level.index()].push(kid);
-                    ret
-                })))
+                let mut ret = <[Vec<KId>; PRIORITY_LENGTH]>::default();
+                queue.into_iter().for_each(|(level, kid)| ret[level.index()].push(kid));
+                msg!(ChangedQueue(ret))
             }),
             LektordCommand::QueueLevelShuffle(lvl) => cmd!(shuffle_level_queue(lvl)),
             LektordCommand::QueueLevelClear(lvl) => cmd!(remove_level_from_queue(lvl)),
@@ -735,10 +751,19 @@ impl AppModel {
             LektordCommand::PlaylistGetContent(id) => cmd!(get_playlist_content(id), content => {
                 msg!(ChangedPlaylistContent(id, content))
             }),
-            LektordCommand::PlaylistsGet => todo!(),
-            LektordCommand::PlaylistShuffleContent(_) => todo!(),
-            LektordCommand::PlaylistDelete(_) => todo!(),
-            LektordCommand::PlaylistRemoveKara(..) => todo!(),
+            LektordCommand::PlaylistDelete(plt_id) => cmd!(delete_playlist(plt_id)),
+            LektordCommand::PlaylistRemoveKara(plt_id, kid) => {
+                cmd!(remove_from_playlist(plt_id, KaraFilter::KId(kid)))
+            }
+            LektordCommand::PlaylistsGet => {
+                cmd!(get_playlists(), playlists => {
+                    let playlists = playlists.into_iter().map(|(id, _)| id).collect();
+                    msg!(ChangedAvailablePlaylists(playlists))
+                })
+            }
+            LektordCommand::PlaylistShuffleContent(id) => cmd!(shuffle_playlist(id), _ => {
+                cosmic::app::message::app(Message::SendCommand(LektordCommand::PlaylistGetContent(id)))
+            }),
         }
     }
 }
diff --git a/amadeus/src/playlist.rs b/amadeus/src/playlist.rs
index dcff1f07..17e1a53b 100644
--- a/amadeus/src/playlist.rs
+++ b/amadeus/src/playlist.rs
@@ -17,11 +17,15 @@ impl Playlist {
         self.content.iter().copied()
     }
 
-    pub fn set_content(&mut self, new: Vec<KId>) {
-        _ = mem::replace(&mut self.content, new);
-    }
-
     pub fn infos(&self) -> Option<&PlaylistInfo> {
         self.infos.as_ref()
     }
+
+    pub fn set_infos(&mut self, infos: PlaylistInfo) {
+        _ = self.infos.replace(infos);
+    }
+
+    pub fn set_content(&mut self, new: Vec<KId>) {
+        _ = mem::replace(&mut self.content, new);
+    }
 }
diff --git a/amadeus/src/store.rs b/amadeus/src/store.rs
index 85a5bd1c..ff6f2297 100644
--- a/amadeus/src/store.rs
+++ b/amadeus/src/store.rs
@@ -8,7 +8,7 @@ mod queue_level;
 
 use crate::playlist::Playlist;
 use hashbrown::HashMap;
-use lektor_payloads::{KId, Kara, Priority, PRIORITY_LENGTH};
+use lektor_payloads::{KId, Kara, PlaylistInfo, Priority, PRIORITY_LENGTH};
 use std::mem;
 
 /// Stores the kara or its id if the [Kara] struct was not already cached in the [Store].
@@ -67,16 +67,31 @@ impl Store {
 
     /// Keep playlists only if their name is specified in the passed [Vec]. Returns the new
     /// playlists in a vector.
-    pub fn keep_playlists(&mut self, ids: Vec<KId>) -> Vec<KId> {
+    pub fn keep_playlists(&mut self, ids: &[KId]) -> Vec<KId> {
+        log::error!("do better for the code…");
         self.playlists.retain(|key, _| ids.contains(key));
-        Vec::from_iter(ids.into_iter().flat_map(|id| {
+        Vec::from_iter(ids.iter().flat_map(|&id| {
             (!self.playlists.contains_key(&id)).then(|| {
-                self.playlists.insert(id, Default::default());
+                self.playlists.insert(id, Playlist::default());
                 id
             })
         }))
     }
 
+    /// Set informtions for a playlist. The playlist will be created if needed. If the playlist is
+    /// created, returns the [KId] of the created playlist.
+    pub fn set_playlist_infos(&mut self, infos: PlaylistInfo) -> Option<KId> {
+        let returns = self
+            .playlists
+            .contains_key(&infos.local_id())
+            .then_some(infos.local_id());
+        self.playlists
+            .entry(infos.local_id())
+            .or_default()
+            .set_infos(infos);
+        returns
+    }
+
     /// Set the metadata informations about a kar in the [Store]. Any previous information is
     /// overwritten.
     pub fn set(&mut self, kara: Kara) {
diff --git a/lektor_lib/src/requests.rs b/lektor_lib/src/requests.rs
index 8db36c8a..47e48c43 100644
--- a/lektor_lib/src/requests.rs
+++ b/lektor_lib/src/requests.rs
@@ -1,8 +1,8 @@
 use crate::ConnectConfig;
 use anyhow::{bail, Context, Result};
 use futures::{
+    prelude::*,
     stream::{self, FuturesUnordered},
-    StreamExt,
 };
 use lektor_payloads::*;
 use lektor_utils::{encode_base64, encode_base64_value};
@@ -210,18 +210,22 @@ pub async fn add_to_playlist(
 
 pub async fn remove_from_playlist(
     config: impl AsRef<ConnectConfig>,
-    name: Arc<str>,
+    id: KId,
     what: KaraFilter,
 ) -> Result<()> {
-    request!(config; PATCH(PlaylistUpdateAction::Remove(what)) @ "/playlist/{}", encode_base64(name)?)
+    request!(config; PATCH(PlaylistUpdateAction::Remove(what)) @ "/playlist/{id}")
 }
 
 pub async fn create_playlist(config: impl AsRef<ConnectConfig>, name: Arc<str>) -> Result<()> {
     request!(config; PUT @ "/playlist/{}", encode_base64(name)?)
 }
 
-pub async fn delete_playlist(config: impl AsRef<ConnectConfig>, name: Arc<str>) -> Result<()> {
-    request!(config; DELETE @ "/playlist/{}", encode_base64(name)?)
+pub async fn delete_playlist(config: impl AsRef<ConnectConfig>, id: KId) -> Result<()> {
+    request!(config; DELETE @ "/playlist/{id}")
+}
+
+pub async fn shuffle_playlist(config: impl AsRef<ConnectConfig>, id: KId) -> Result<()> {
+    request!(config; PATCH(PlaylistUpdateAction::Shuffle) @ "/playlist/{id}")
 }
 
 // ================================================== //
diff --git a/lektor_nkdb/src/playlists/playlist.rs b/lektor_nkdb/src/playlists/playlist.rs
index e8ae512d..17dd78e6 100644
--- a/lektor_nkdb/src/playlists/playlist.rs
+++ b/lektor_nkdb/src/playlists/playlist.rs
@@ -125,28 +125,33 @@ impl Playlist {
     }
 
     /// Add a [crate::Kara] by its [KId] to the playlist.
-    pub fn push(&mut self, id: KId) {
-        self.content.push(id)
+    pub fn push(&mut self, id: KId) -> &mut Self {
+        self.content.push(id);
+        self
     }
 
     /// Remove a [crate::Kara] by its [KId] from the playlist.
-    pub fn remove(&mut self, id: KId) {
-        self.retain(|other| *other != id)
+    pub fn remove(&mut self, id: KId) -> &mut Self {
+        self.retain(|other| *other != id);
+        self
     }
 
     /// Select which [crate::Kara] are kept in the playlist by runing a callback on its [KId].
-    pub fn retain(&mut self, cb: impl FnMut(&KId) -> bool) {
-        self.content.retain(cb)
+    pub fn retain(&mut self, cb: impl FnMut(&KId) -> bool) -> &mut Self {
+        self.content.retain(cb);
+        self
     }
 
     /// Add a [Vec] of [crate::Kara] by their [KId] to the playlist.
-    pub fn append(&mut self, ids: &mut Vec<KId>) {
-        self.content.append(ids)
+    pub fn append(&mut self, ids: &mut Vec<KId>) -> &mut Self {
+        self.content.append(ids);
+        self
     }
 
     /// Shuffle the content of the playlist.
-    pub fn shuffle(&mut self) {
-        self.content.shuffle(&mut rand::thread_rng())
+    pub fn shuffle(&mut self) -> &mut Self {
+        self.content.shuffle(&mut rand::thread_rng());
+        self
     }
 }
 
diff --git a/lektor_payloads/src/action.rs b/lektor_payloads/src/action.rs
index d5c871f1..823d668b 100644
--- a/lektor_payloads/src/action.rs
+++ b/lektor_payloads/src/action.rs
@@ -44,6 +44,9 @@ pub enum PlaylistUpdateAction {
     /// Delete the playlist.
     Delete,
 
+    /// Shuffle the content of the playlist.
+    Shuffle,
+
     /// Give the playlist to another user.
     GiveTo(Box<str>),
 
diff --git a/lektord/src/app/routes.rs b/lektord/src/app/routes.rs
index 17e538b8..976bd54a 100644
--- a/lektord/src/app/routes.rs
+++ b/lektord/src/app/routes.rs
@@ -181,23 +181,28 @@ pub(crate) async fn update_playlist(
             db.write(id, uin, uia, |plt| _ = plt.set_name(new_name))
                 .await?
         }
+        PlaylistUpdateAction::Shuffle => db.write(id, uin, uia, |plt| _ = plt.shuffle()).await?,
         PlaylistUpdateAction::Remove(filter) => match filter {
-            KaraFilter::KId(kid) => db.write(id, uin, uia, |plt| plt.remove(kid)).await?,
+            KaraFilter::KId(kid) => db.write(id, uin, uia, |plt| _ = plt.remove(kid)).await?,
             KaraFilter::List(_, kids) => {
-                db.write(id, uin, uia, move |plt| plt.retain(|id| !kids.contains(id)))
-                    .await?
+                db.write(id, uin, uia, move |plt| {
+                    _ = plt.retain(|id| !kids.contains(id))
+                })
+                .await?
             }
             KaraFilter::Playlist(_, from) => {
                 let kids = db
                     .read(from, |plt| plt.iter_seq_content().collect::<Vec<_>>())
                     .await?;
-                db.write(id, uin, uia, move |plt| plt.retain(|id| !kids.contains(id)))
-                    .await?
+                db.write(id, uin, uia, move |plt| {
+                    _ = plt.retain(|id| !kids.contains(id))
+                })
+                .await?
             }
         },
 
         PlaylistUpdateAction::Add(filter) => match filter {
-            KaraFilter::KId(kid) => db.write(id, uin, uia, |plt| plt.push(kid)).await?,
+            KaraFilter::KId(kid) => db.write(id, uin, uia, |plt| _ = plt.push(kid)).await?,
             KaraFilter::Playlist(rand, from) => {
                 let mut kids = db
                     .read(from, |plt| plt.iter_seq_content().collect::<Vec<_>>())
@@ -205,14 +210,14 @@ pub(crate) async fn update_playlist(
                 if rand {
                     kids[..].shuffle(&mut thread_rng());
                 }
-                db.write(id, uin, uia, move |plt| plt.append(&mut kids))
+                db.write(id, uin, uia, move |plt| _ = plt.append(&mut kids))
                     .await?
             }
             KaraFilter::List(rand, mut kids) => {
                 if rand {
                     kids[..].shuffle(&mut thread_rng());
                 }
-                db.write(id, uin, uia, move |plt| plt.append(&mut kids))
+                db.write(id, uin, uia, move |plt| _ = plt.append(&mut kids))
                     .await?
             }
         },
diff --git a/lkt/src/lib.rs b/lkt/src/lib.rs
index 051637ef..45446f9d 100644
--- a/lkt/src/lib.rs
+++ b/lkt/src/lib.rs
@@ -16,7 +16,7 @@ use lektor_lib::*;
 use lektor_payloads::*;
 use std::{borrow::Cow, ops::RangeBounds};
 
-pub async fn collect_karas(config: &ConnectConfig, ids: Vec<KId>) -> Result<Vec<Kara>> {
+async fn collect_karas(config: &ConnectConfig, ids: Vec<KId>) -> Result<Vec<Kara>> {
     let count = ids.len();
     let res: Vec<_> = stream::iter(ids.into_iter())
         .then(|id| requests::get_kara_by_kid(config, id))
@@ -29,7 +29,7 @@ pub async fn collect_karas(config: &ConnectConfig, ids: Vec<KId>) -> Result<Vec<
         .ok_or(anyhow!("got errors, didn't received all the karas' data"))
 }
 
-pub async fn simple_karas_print(config: &ConnectConfig, ids: Vec<KId>) -> Result<()> {
+async fn simple_karas_print(config: &ConnectConfig, ids: Vec<KId>) -> Result<()> {
     let res = collect_karas(config, ids).await?;
     let count = res.len().to_string().len();
     res.into_iter()
@@ -38,6 +38,14 @@ pub async fn simple_karas_print(config: &ConnectConfig, ids: Vec<KId>) -> Result
     Ok(())
 }
 
+async fn get_playlist_id_by_name(config: &ConnectConfig, name: &str) -> Result<KId> {
+    requests::get_playlists(config)
+        .await?
+        .into_iter()
+        .find_map(|(id, plt_name)| (plt_name.as_str() == name).then_some(id))
+        .with_context(|| format!("failed to find playlist with name `{name}`"))
+}
+
 pub async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> {
     // Write to apply changes...
     lektor_utils::config::write_config_async("lkt", config.clone()).await?;
@@ -255,7 +263,7 @@ pub async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> {
         // Delete a playlist and all its content.
         Playlist {
             destroy: Some(n), ..
-        } => requests::delete_playlist(config, n.into()).await,
+        } => requests::delete_playlist(config, get_playlist_id_by_name(config, &n).await?).await,
 
         // List all the playlists
         Playlist {
@@ -324,11 +332,11 @@ pub async fn exec_lkt(config: LktConfig, cmd: SubCommand) -> Result<()> {
             remove: Some(mut args),
             ..
         } => {
-            let name = args.remove(0).into();
+            let id = get_playlist_id_by_name(config, &args.remove(0)).await?;
             let rgx = args.join(" ").parse()?;
             let ids = requests::search_karas(config, SearchFrom::Database, [rgx]).await?;
             let remove = KaraFilter::List(true, ids);
-            requests::remove_from_playlist(config, name, remove).await
+            requests::remove_from_playlist(config, id, remove).await
         }
 
         // ============= //
-- 
GitLab