Skip to content
Extraits de code Groupes Projets
Valider 10b50430 rédigé par Elliu's avatar Elliu
Parcourir les fichiers

NKDB: make playlists handle use a DKMap

parent 7bedefb9
Branches
Aucune étiquette associée trouvée
1 requête de fusion!207Draft: Add playlist sync
...@@ -2,17 +2,43 @@ ...@@ -2,17 +2,43 @@
pub(crate) mod playlist; pub(crate) mod playlist;
use crate::{playlists::playlist::Playlist, DatabaseStorage, KId}; use crate::{playlists::playlist::Playlist, DatabaseStorage, KId, RemoteKId};
use anyhow::{bail, Context as _, Result}; use anyhow::{bail, Context as _, Result};
use hashbrown::{hash_map, HashMap}; use hashbrown::hash_map;
use lektor_utils::dkmap::{DKMap, IntoDKMapping};
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::RwLock; use tokio::sync::RwLock;
type PlaylistMappingValue = (KId, Option<RemoteKId>, Playlist);
type PlaylistMapping = DKMap<KId, Option<RemoteKId>, Playlist>;
impl IntoDKMapping<KId, Option<RemoteKId>, Playlist> for Playlist {
fn into_keys_value(&self) -> PlaylistMappingValue {
(self.local_id(), self.remote_id(), self.clone())
}
fn into_keys(&self) -> (KId, Option<RemoteKId>) {
(self.local_id(), self.remote_id())
}
fn into_primary_key(&self) -> KId {
self.local_id()
}
fn into_secondary_key(&self) -> Option<RemoteKId> {
self.remote_id()
}
fn into_value(&self) -> Playlist {
self.clone()
}
}
/// This type is just a wrapper around the [PlaylistsContent] with a [RwLock]. For function /// This type is just a wrapper around the [PlaylistsContent] with a [RwLock]. For function
/// documentation see [PlaylistsContent]. /// documentation see [PlaylistsContent].
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Playlists { pub(crate) struct Playlists {
content: RwLock<HashMap<KId, Playlist>>, content: RwLock<PlaylistMapping>,
epoch: AtomicU64, epoch: AtomicU64,
} }
...@@ -48,19 +74,19 @@ impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> { ...@@ -48,19 +74,19 @@ impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> {
/// Read a [Playlist]. /// Read a [Playlist].
pub async fn read<T>(&self, plt: KId, cb: impl FnOnce(&Playlist) -> T) -> Result<T> { pub async fn read<T>(&self, plt: KId, cb: impl FnOnce(&Playlist) -> T) -> Result<T> {
Ok(cb((self.playlists.content.read().await) Ok(cb((self.playlists.content.read().await)
.get(&plt) .get_k1(&plt)
.context("playlist not found")?)) .context("playlist not found")?))
} }
/// Get the names and [KId] of the stored [Playlist]. /// Get the names and [KId] of the stored [Playlist].
pub async fn list(&self) -> Vec<(KId, String)> { pub async fn list(&self) -> Vec<(KId, String)> {
(self.playlists.content.read().await.iter()) (self.playlists.content.read().await.iter())
.map(|(&id, playlist)| (id, playlist.name().to_string())) .map(|playlist| (playlist.local_id(), playlist.name().to_string()))
.collect() .collect()
} }
/// Write a [Playlist]. This will increment the playlists' epoch, even if you do nothing inside /// Write a [Playlist]. This will increment the playlists' epoch, even if you do nothing inside
/// the callback, prompting all the clients to refrech the playlists. /// the callback, prompting all the clients to refresh the playlists.
pub async fn write<T>( pub async fn write<T>(
&self, &self,
plt: KId, plt: KId,
...@@ -68,15 +94,17 @@ impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> { ...@@ -68,15 +94,17 @@ impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> {
admin: bool, admin: bool,
cb: impl FnOnce(&mut Playlist) -> T, cb: impl FnOnce(&mut Playlist) -> T,
) -> Result<T> { ) -> Result<T> {
let mut this = self.playlists.content.write().await; let this = self.playlists.content.write().await;
let plt = this let mut plt = this
.get_mut(&plt) .get_k1(&plt)
.context("playlist not found")? .context("playlist not found")?
.authorize_writes(user, admin) .authorize_writes(user, admin)
.context("user not allowed to modify playlist")?; .context("user not allowed to modify playlist")?
let res = cb(plt); .clone();
self.storage.write_playlist(plt).await?; let res = cb(&mut plt);
self.storage.write_playlist(&plt).await?;
self.playlists.epoch.fetch_add(1, Ordering::AcqRel); self.playlists.epoch.fetch_add(1, Ordering::AcqRel);
// TODO: update playlist.updated_at
Ok(res) Ok(res)
} }
...@@ -88,27 +116,42 @@ impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> { ...@@ -88,27 +116,42 @@ impl<'a, Storage: DatabaseStorage + Sized> PlaylistsHandle<'a, Storage> {
settings: impl AsyncFnOnce(Playlist) -> Playlist, settings: impl AsyncFnOnce(Playlist) -> Playlist,
) -> Result<KId> { ) -> Result<KId> {
let mut this = self.playlists.content.write().await; let mut this = self.playlists.content.write().await;
let next_id = KId(this.keys().map(|KId(id)| id + 1).max().unwrap_or(1)); let next_id = KId(this
.iter()
// TODO: maybe add and use a dkmap.keys1() iter (an keys2?)
.map(|playlist| {
let KId(id) = playlist.local_id();
id + 1
})
.max()
.unwrap_or(1));
let plt = settings(Playlist::new(next_id, name)).await.updated_now(); let plt = settings(Playlist::new(next_id, name)).await.updated_now();
self.playlists.epoch.fetch_add(1, Ordering::AcqRel); self.playlists.epoch.fetch_add(1, Ordering::AcqRel);
self.storage.write_playlist(&plt).await?; self.storage.write_playlist(&plt).await?;
this.insert(next_id, plt); this.insert_only(plt)
.context("cannot create playlist: a playlist with this same KId already exists")?;
Ok(next_id) Ok(next_id)
} }
/// Delete a [Playlist]. /// Delete a [Playlist].
pub async fn delete(&self, plt: KId, user: impl AsRef<str>, admin: bool) -> Result<()> { pub async fn delete(&self, plt: KId, user: impl AsRef<str>, admin: bool) -> Result<()> {
match self.playlists.content.write().await.entry(plt) { let mut playlists = self.playlists.content.write().await;
hash_map::Entry::Vacant(_) => bail!("playlist not found"), match playlists.get_k1(&plt) {
hash_map::Entry::Occupied(mut entry) => { Some(playlist) => {
(entry.get_mut()) playlist
.authorize_writes(user, admin) .authorize_writes(user, admin)
.context("user not allowed to modify the playlist")?; .context("user not allowed to modify the playlist")?;
entry.remove();
if let Err(err) = playlists.delete_k1(&plt) {
log::error!("failed to delete playlist '{plt}' from memory: {err}")
}
if let Err(err) = self.storage.delete_playlist(plt).await { if let Err(err) = self.storage.delete_playlist(plt).await {
log::error!("failed to delete playlist '{plt}': {err}") log::error!("failed to delete playlist '{plt}' from disk: {err}")
} }
} }
None => bail!("playlist not found"),
} }
Ok(()) Ok(())
} }
...@@ -118,7 +161,7 @@ impl Playlists { ...@@ -118,7 +161,7 @@ impl Playlists {
/// Create a new [Playlists] store, initialized by an initial list of [Playlist]. /// Create a new [Playlists] store, initialized by an initial list of [Playlist].
pub(crate) fn new(iter: impl IntoIterator<Item = Playlist>) -> Self { pub(crate) fn new(iter: impl IntoIterator<Item = Playlist>) -> Self {
Self { Self {
content: RwLock::new(iter.into_iter().map(|plt| (plt.local_id(), plt)).collect()), content: RwLock::new(DKMap::from_iter(iter.into_iter())),
epoch: AtomicU64::new(0), epoch: AtomicU64::new(0),
} }
} }
......
...@@ -143,11 +143,7 @@ impl Playlist { ...@@ -143,11 +143,7 @@ impl Playlist {
/// Tells if someone is authorized to modify the playlist. It can be in the [Self::owners] /// Tells if someone is authorized to modify the playlist. It can be in the [Self::owners]
/// list, or be an admin. /// list, or be an admin.
pub(crate) fn authorize_writes( pub(crate) fn authorize_writes(&self, user: impl AsRef<str>, admin: bool) -> Option<&Self> {
&mut self,
user: impl AsRef<str>,
admin: bool,
) -> Option<&mut Self> {
(admin || (self.owners.iter()).any(|owner| owner.as_ref() == user.as_ref())).then_some(self) (admin || (self.owners.iter()).any(|owner| owner.as_ref() == user.as_ref())).then_some(self)
} }
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Veuillez vous inscrire ou vous pour commenter