#[cfg(feature = "daemon")]
use std::sync::atomic::Ordering;
#[cfg(feature = "daemon")]
use lunar_lib::database::{
DatabaseEntry, Db, DbIdExt, Entry, TransactionError, caching::Cacheable,
};
use lunar_lib::{id::Id, iterator_ext::IteratorExtensions, runners::DispatchError};
#[cfg(feature = "daemon")]
use selene_core::database::{Library, Searchable};
use selene_core::library::{album::Album, artist::Artist, collectable::Collectable, track::Track};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use thiserror::Error;
#[cfg(feature = "local-session")]
use crate::local_session::{LocalSessionEventExt, local_session};
use crate::{
ConnectionType, LoopMode, LoudnormMode, SessionToken, ShuffleMode,
dto::{self, IntoDto},
ipc_common::{PlayerQueryFlags, QueryResult},
};
#[cfg(feature = "daemon")]
use crate::{
library_db,
session::{SessionHandle, SessionRunnerExt, session_registry},
};
pub(crate) mod resolve;
#[derive(Debug, Error, Serialize, Deserialize)]
pub enum MethodError {
#[error("Failed to connect to the server: {0}")]
FailedToConnect(String),
#[error("An internal server error occured during your request. Try again later")]
InternalServerError,
#[error("The client is on the wrong version. Expected v{expected}. Found v{connected}")]
WrongVersion { expected: u32, connected: u32 },
#[error("The method call was invalid")]
InvalidMethodCall,
#[error("A valid method call was provided, but with invalid data")]
InvalidMethodInput,
#[error("The server responded with bad data")]
InvalidMethodResponse,
#[error("Invalid authorization to complete action")]
NotAuthorized,
}
#[cfg(feature = "daemon")]
impl From<TransactionError> for MethodError {
fn from(_value: TransactionError) -> Self {
MethodError::InternalServerError
}
}
impl From<DispatchError> for MethodError {
fn from(_value: DispatchError) -> Self {
MethodError::InternalServerError
}
}
impl From<anyhow::Error> for MethodError {
fn from(_value: anyhow::Error) -> Self {
MethodError::InternalServerError
}
}
impl Method {
#[must_use]
pub fn new(connection_type: ConnectionType) -> Self {
Self {
call_data: Vec::new(),
connection_type,
}
}
#[cfg(feature = "daemon")]
pub(crate) fn resolve(data: Vec<u8>) -> Vec<u8> {
let Some((header, buf)) = data.split_first_chunk::<4>() else {
return postcard::to_stdvec::<Result<(), MethodError>>(&Err(
MethodError::InvalidMethodCall,
))
.expect("MethodError should never fail to serialize");
};
match *header {
Method::RESCAN => Method::resolve_rescan(),
Method::TRACK => TrackMethod::resolve(buf),
Method::ALBUM => AlbumMethod::resolve(buf),
Method::ARTIST => ArtistMethod::resolve(buf),
Method::SESSION => SessionMethod::resolve(buf),
Method::LOCAL_SESSION => LocalSessionMethod::resolve(buf),
_ => {
postcard::to_stdvec::<Result<(), MethodError>>(&Err(MethodError::InvalidMethodCall))
.expect("MethodError should never fail to serialize")
}
}
}
pub const MAX_RESULT_COUNT: u32 = 1000;
}
pub struct Method {
call_data: Vec<u8>,
connection_type: ConnectionType,
}
pub struct MethodCall<T> {
call_data: Vec<u8>,
connection_type: ConnectionType,
_marker: std::marker::PhantomData<T>,
}
impl<T: Serialize + DeserializeOwned> MethodCall<T> {
fn new(call_data: Vec<u8>, connection_type: ConnectionType) -> Self {
Self {
call_data,
connection_type,
_marker: std::marker::PhantomData,
}
}
#[cfg(feature = "client")]
pub fn call(mut self) -> Result<T, MethodError> {
use crate::{HandshakeType, IPC_PROTOCOL_VERSION};
let mut stream = self
.connection_type
.connect()
.map_err(|err| MethodError::FailedToConnect(err.to_string()))?;
let mut header = Vec::with_capacity(8);
header.extend_from_slice(&IPC_PROTOCOL_VERSION);
header.extend_from_slice(&HandshakeType::METHOD);
stream
.write_all(&header)
.map_err(|err| MethodError::FailedToConnect(err.to_string()))?;
let mut version = [0u8; 4];
stream
.read_exact(&mut version)
.map_err(|err| MethodError::FailedToConnect(err.to_string()))?;
if version != IPC_PROTOCOL_VERSION {
return Err(MethodError::WrongVersion {
expected: u32::from_be_bytes(IPC_PROTOCOL_VERSION),
connected: u32::from_be_bytes(version),
});
}
self.call_data
.extend((self.call_data.len() as u32).to_be_bytes());
self.call_data.rotate_right(4);
stream
.write_all(&self.call_data)
.map_err(|err| MethodError::FailedToConnect(err.to_string()))?;
let data = stream
.read_data()
.map_err(|_| MethodError::InvalidMethodResponse)?;
postcard::from_bytes::<Result<T, MethodError>>(&data)
.map_err(|_| MethodError::InvalidMethodResponse)
.flatten()
}
}
macro_rules! impl_sub_method {
($parent:ident, $fn:ident::<$header:literal>()$([$($ctx_field:ident : $ctx_ty:ty),*])? => $header_val:ident, $rest:ident, $body:block) => {
paste::paste!(
pub struct [<$fn:camel Method>] {
call_data: Vec<u8>,
connection_type: ConnectionType,
}
#[cfg(feature = "daemon")]
impl [<$fn:camel Method>] {
pub(crate) fn resolve(buf: &[u8], $($($ctx_field: $ctx_ty)*)?) -> Vec<u8> {
fn inner($header_val: [u8; 4], $rest: &[u8], $($($ctx_field: $ctx_ty),*)?) -> Result<Vec<u8>, MethodError> {
$body
}
let Some(($header_val, $rest)) = buf.split_first_chunk::<4>() else {
return postcard::to_stdvec::<Result<(), MethodError>>(&Err(MethodError::InvalidMethodCall))
.expect("MethodError should never fail to serialize");
};
inner(*$header_val, $rest, $($($ctx_field),*)?)
.unwrap_or_else(|err| {
postcard::to_stdvec::<Result<(), MethodError>>(&Err(err)).expect("MethodError should never fail to serialize")
})
}
}
impl $parent {
pub(crate) const [<$fn:snake:upper>]: [u8; 4] = *$header;
pub fn $fn(mut self) -> [<$fn:camel Method>] {
self.call_data.extend_from_slice(&$parent::[<$fn:snake:upper>]);
[<$fn:camel Method>] {
call_data: self.call_data,
connection_type: self.connection_type,
}
}
}
);
};
($parent:ident, $fn:ident::<$header:literal>($($field:ident : $ty:ty),*)$([$($ctx_field:ident : $ctx_ty:ty),*])? => $header_val:ident, $rest:ident, $body:block) => {
paste::paste!(
pub struct [<$fn:camel Method>] {
call_data: Vec<u8>,
connection_type: ConnectionType,
}
#[cfg(feature = "daemon")]
impl [<$fn:camel Method>] {
pub(crate) fn resolve(buf: &[u8], $($($ctx_field: $ctx_ty)*)?) -> Vec<u8> {
fn inner($header_val: [u8; 4], $rest: &[u8], $($field: $ty,)* $($($ctx_field: $ctx_ty),*)?) -> Result<Vec<u8>, MethodError> {
$body
}
postcard::take_from_bytes::<($($ty,)*)>(buf)
.map_err(|_| MethodError::InvalidMethodInput)
.and_then(|(($($field,)*), remaining)| {
let Some(($header_val, $rest)) = remaining.split_first_chunk::<4>() else {
return Err(MethodError::InvalidMethodCall);
};
inner(*$header_val, $rest, $($field,)* $($($ctx_field),*)?)
})
.unwrap_or_else(|err| {
postcard::to_stdvec::<Result<(), MethodError>>(&Err(err))
.expect("MethodError should never fail to serialize")
})
}
}
impl $parent {
pub(crate) const [<$fn:snake:upper>]: [u8; 4] = *$header;
pub fn $fn(mut self $(, $field: $ty)*) -> [<$fn:camel Method>] {
self.call_data.extend_from_slice(&$parent::[<$fn:snake:upper>]);
[<$fn:camel Method>] {
call_data: postcard::to_extend(&($($field,)*), self.call_data).unwrap(),
connection_type: self.connection_type,
}
}
}
);
};
}
macro_rules! impl_call {
($parent:ident, $fn:ident::<$header:literal>()$([$($ctx_field:ident : $ctx_ty:ty),*])? -> $ret:ty $body:block ) => {
paste::paste!(
impl $parent {
pub(crate) const [<$fn:snake:upper>]: [u8; 4] = *$header;
#[cfg(feature = "daemon")]
pub(crate) fn [<resolve_ $fn>]($($($ctx_field: $ctx_ty)*)?) -> Vec<u8> {
fn inner($($($ctx_field: $ctx_ty),*)?) -> Result<$ret, MethodError> {
$body
}
let result = inner($($($ctx_field),*)?);
postcard::to_stdvec(&result)
.expect("Values sent from the server should never fail to serialize")
}
pub fn $fn(mut self) -> MethodCall<$ret> {
self.call_data.extend_from_slice(&$parent::[<$fn:snake:upper>]);
MethodCall::new(self.call_data, self.connection_type)
}
}
);
};
($parent:ident, $fn:ident::<$header:literal>($($field:ident : $ty:ty),*)$([$($ctx_field:ident : $ctx_ty:ty),*])? -> $ret:ty $body:block ) => {
paste::paste!(
impl $parent {
pub(crate) const [<$fn:snake:upper>]: [u8; 4] = *$header;
#[cfg(feature = "daemon")]
pub(crate) fn [<resolve_ $fn>](buf: &[u8], $($($ctx_field: $ctx_ty)*)?) -> Vec<u8> {
fn inner($($field: $ty,)* $($($ctx_field: $ctx_ty),*)?) -> Result<$ret, MethodError> {
$body
}
let result: Result<$ret, MethodError> = postcard::from_bytes::<($($ty,)*)>(buf)
.map_err(|_| MethodError::InvalidMethodInput)
.and_then(|($($field,)*)| inner($($field,)* $($($ctx_field),*)?));
postcard::to_stdvec(&result)
.expect("Values sent from the server should never fail to serialize")
}
pub fn $fn(mut self $(, $field: $ty)*) -> MethodCall<$ret> {
self.call_data.extend_from_slice(&$parent::[<$fn:snake:upper>]);
MethodCall::new(postcard::to_extend(&($($field,)*), self.call_data).unwrap(), self.connection_type)
}
}
);
};
}
impl_call!(Method, rescan::<b"SCAN">() -> bool {
let (callback, rx) = oneshot::channel();
crate::process::send_process_request(crate::process::ProcessRequest::Extract { callback });
rx.recv().map_err(|_| MethodError::InternalServerError)
});
// #[cfg(debug_assertions)]
// impl_sub_method!(Method, debug::<b"DEBG">() => _header, _buf, {
// todo!()
// });
// TRACK METHODS
impl_sub_method!(Method, track::<b"TRCK">() => header, buf, {
let ok = match header {
TrackMethod::GET => TrackMethod::resolve_get(buf),
TrackMethod::SEARCH => TrackMethod::resolve_search(buf),
TrackMethod::COUNT => TrackMethod::resolve_count(),
TrackMethod::LIST => TrackMethod::resolve_list(buf),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(TrackMethod, get::<b"GET ">(id: Id<Track>) -> Option<dto::Track> {
id.db_get(library_db())
.map_err(|_| MethodError::InternalServerError)?
.map(IntoDto::into_dto)
.transpose()
.map_err(|_| MethodError::InternalServerError)
});
impl_call!(TrackMethod, search::<b"SRCH">(query: String, limit: u32) -> Vec<dto::Track> {
Ok(search::<Track>(library_db(), &query, limit)?)
});
impl_call!(TrackMethod, list::<b"LIST">(limit: u32, offset: u32) -> Vec<dto::Track> { Ok(list::<Track>(library_db(), limit, offset)?) });
impl_call!(TrackMethod, count::<b"COUN">() -> u32 { Ok(library_db().entry_count::<Track>() as u32) });
// ALBUM METHODS
impl_sub_method!(Method, album::<b"ALBM">() => header, buf, {
let ok = match header {
AlbumMethod::GET => AlbumMethod::resolve_get(buf),
AlbumMethod::SEARCH => AlbumMethod::resolve_search(buf),
AlbumMethod::COUNT => AlbumMethod::resolve_count(),
AlbumMethod::LIST => AlbumMethod::resolve_list(buf),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(AlbumMethod, get::<b"GET ">(id: Id<Album>) -> Option<dto::Album> {
id.db_get(library_db())
.map_err(|_| MethodError::InternalServerError)?
.map(IntoDto::into_dto)
.transpose()
.map_err(|_| MethodError::InternalServerError)
});
impl_call!(AlbumMethod, search::<b"SRCH">(query: String, limit: u32) -> Vec<dto::Album> { Ok(search::<Album>(library_db(), &query, limit)?) });
impl_call!(AlbumMethod, list::<b"LIST">(limit: u32, offset: u32) -> Vec<dto::Album> { Ok(list::<Album>(library_db(), limit, offset)?) });
impl_call!(AlbumMethod, count::<b"COUN">() -> u32 { Ok(library_db().entry_count::<Album>() as u32) });
// ARTIST METHODS
impl_sub_method!(Method, artist::<b"ARTS">() => header, buf, {
let ok = match header {
ArtistMethod::GET => ArtistMethod::resolve_get(buf),
ArtistMethod::SEARCH => ArtistMethod::resolve_search(buf),
ArtistMethod::COUNT => ArtistMethod::resolve_count(),
ArtistMethod::LIST => ArtistMethod::resolve_list(buf),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(ArtistMethod, get::<b"GET ">(id: Id<Artist>) -> Option<dto::Artist> {
id.db_get(library_db())
.map_err(|_| MethodError::InternalServerError)?
.map(IntoDto::into_dto)
.transpose()
.map_err(|_| MethodError::InternalServerError)
});
impl_call!(ArtistMethod, search::<b"SRCH">(query: String, limit: u32) -> Vec<dto::Artist> { Ok(search::<Artist>(library_db(), &query, limit)?) });
impl_call!(ArtistMethod, list::<b"LIST">(limit: u32, offset: u32) -> Vec<dto::Artist> { Ok(list::<Artist>(library_db(), limit, offset)?) });
impl_call!(ArtistMethod, count::<b"COUN">() -> u32 { Ok(library_db().entry_count::<Artist>() as u32) });
// SESSION METHODS
impl_sub_method!(Method, session::<b"SESS">(token: SessionToken) => header, buf, {
let session_registry = session_registry();
#[cfg(feature = "local-session")]
let session = if token == SessionToken::LOCAL_SESSION {
local_session()
} else {
session_registry.get(&token).ok_or(MethodError::InvalidMethodInput)?
};
#[cfg(not(feature = "local-session"))]
let session = session_registry.get(&token).ok_or(MethodError::InvalidMethodInput)?;
let ok = match header {
SessionMethod::PLAY => SessionMethod::resolve_play(buf, session),
SessionMethod::STOP => SessionMethod::resolve_stop(session),
SessionMethod::SET_IS_PLAYING => SessionMethod::resolve_set_is_playing(buf, session),
SessionMethod::TOGGLE_IS_PLAYING => SessionMethod::resolve_toggle_is_playing(session),
SessionMethod::PREVIOUS => SessionMethod::resolve_previous(session),
SessionMethod::NEXT => SessionMethod::resolve_next(session),
SessionMethod::SEEK => SessionMethod::resolve_seek(buf, session),
SessionMethod::PLAYLIST => PlaylistMethod::resolve(buf, session),
SessionMethod::QUEUE => QueueMethod::resolve(buf, session),
SessionMethod::TRACKLIST => TracklistMethod::resolve(buf, session),
SessionMethod::QUERY_STATE => SessionMethod::resolve_query_state(buf, session),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(SessionMethod, play::<b"PLAY">(collectables: Vec<Collectable>)[session: &SessionHandle] -> () {
let db = library_db();
let playables = collectables.into_iter().map(|c| crate::playlist::Playable::from_collectable(c, db)).try_to_vec()?;
Ok(session.session_runner.play(playables)??)
});
impl_call!(SessionMethod, stop::<b"STOP">()[session: &SessionHandle] -> () { Ok(session.session_runner.stop()??) });
impl_call!(SessionMethod, set_is_playing::<b"STPL">(is_playing: bool)[session: &SessionHandle] -> bool { Ok(session.session_runner.set_is_playing(is_playing)??) });
impl_call!(SessionMethod, toggle_is_playing::<b"TGPL">()[session: &SessionHandle] -> bool { Ok(session.session_runner.toggle_playing()??) });
impl_call!(SessionMethod, previous::<b"PREV">()[session: &SessionHandle] -> Option<u32> { Ok(session.session_runner.previous()??) });
impl_call!(SessionMethod, next::<b"NEXT">()[session: &SessionHandle] -> Option<u32> { Ok(session.session_runner.next()??) });
impl_call!(SessionMethod, seek::<b"SEEK">(seconds: f64, increment: bool)[session: &SessionHandle] -> Option<f64> { Ok(session.session_runner.seek(seconds, increment)??) });
impl_call!(SessionMethod, query_state::<b"STAT">(query: PlayerQueryFlags)[session: &SessionHandle] -> QueryResult { Ok(session.session_runner.get_state(query)??) });
impl_sub_method!(SessionMethod, playlist::<b"PLST">()[session: &SessionHandle] => header, buf, {
let ok = match header {
PlaylistMethod::SET => PlaylistMethod::resolve_set(buf, session),
PlaylistMethod::REMOVE => PlaylistMethod::resolve_remove(buf, session),
PlaylistMethod::LIST => PlaylistMethod::resolve_list(buf, session),
PlaylistMethod::COUNT => PlaylistMethod::resolve_count(session),
PlaylistMethod::SHUFFLE_MODE => ShuffleModeMethod::resolve(buf, session),
PlaylistMethod::LOOP_MODE => LoopModeMethod::resolve(buf, session),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(PlaylistMethod, set::<b"SET ">(to: Vec<Collectable>)[session: &SessionHandle] -> () {
let db = library_db();
let playables = to.into_iter().map(|c| crate::playlist::Playable::from_collectable(c, db)).try_to_vec()?;
Ok(session.session_runner.playlist_set(playables)??)
});
impl_call!(PlaylistMethod, remove::<b"RMVE">(targets: Vec<(u32, Collectable)>)[session: &SessionHandle] -> bool { Ok(session.session_runner.playlist_remove(targets)?) });
impl_call!(PlaylistMethod, list::<b"LIST">(limit: u32, offset: u32)[session: &SessionHandle] -> Vec<dto::Playable> { Ok(session.session_runner.playlist_list(limit, offset)?.into_iter().map(IntoDto::into_dto).try_to_vec()?) });
impl_call!(PlaylistMethod, count::<b"COUN">()[session: &SessionHandle] -> u32 { Ok(session.session_runner.playlist_count()?) });
impl_sub_method!(PlaylistMethod, shuffle_mode::<b"SHMD">()[session: &SessionHandle] => header, buf, {
let ok = match header {
ShuffleModeMethod::SET => ShuffleModeMethod::resolve_set(buf, session),
ShuffleModeMethod::GET => ShuffleModeMethod::resolve_get(session),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(ShuffleModeMethod, get::<b"GET ">()[session: &SessionHandle] -> ShuffleMode { Ok(session.session_runner.get_shuffle_mode()?) });
impl_call!(ShuffleModeMethod, set::<b"SET ">(shuffle_mode: ShuffleMode)[session: &SessionHandle] -> () { Ok(session.session_runner.set_shuffle_mode(shuffle_mode)??) });
impl_sub_method!(PlaylistMethod, loop_mode::<b"LPMD">()[session: &SessionHandle] => header, buf, {
let ok = match header {
LoopModeMethod::SET => LoopModeMethod::resolve_set(buf, session),
LoopModeMethod::GET => LoopModeMethod::resolve_get(session),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(LoopModeMethod, get::<b"GET ">()[session: &SessionHandle] -> LoopMode { Ok(session.session_runner.get_loop_mode()?) });
impl_call!(LoopModeMethod, set::<b"SET ">(loop_mode: LoopMode)[session: &SessionHandle] -> () { Ok(session.session_runner.set_loop_mode(loop_mode)?) });
impl_sub_method!(SessionMethod, queue::<b"QUEU">()[session: &SessionHandle] => header, buf, {
let ok = match header {
QueueMethod::SET => QueueMethod::resolve_set(buf, session),
QueueMethod::REMOVE => QueueMethod::resolve_remove(buf, session),
QueueMethod::LIST => QueueMethod::resolve_list(buf, session),
QueueMethod::COUNT => QueueMethod::resolve_count(session),
QueueMethod::SHUFFLE => QueueMethod::resolve_shuffle(session),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(QueueMethod, set::<b"SET ">(to: Vec<Collectable>)[session: &SessionHandle] -> () {
let db = library_db();
let playables = to.into_iter().map(|c| crate::playlist::Playable::from_collectable(c, db)).try_to_vec()?;
Ok(session.session_runner.queue_set(playables)??)
});
impl_call!(QueueMethod, remove::<b"RMVE">(targets: Vec<(u32, Collectable)>)[session: &SessionHandle] -> bool { Ok(session.session_runner.queue_remove(targets)?) });
impl_call!(QueueMethod, list::<b"LIST">(limit: u32, offset: u32)[session: &SessionHandle] -> Vec<dto::Track> { Ok(session.session_runner.queue_list(limit, offset)?.into_iter().map(|t| (*t).clone().into_dto()).try_to_vec()?) });
impl_call!(QueueMethod, shuffle::<b"SHFL">()[session: &SessionHandle] -> () { Ok(session.session_runner.queue_shuffle()??) });
impl_call!(QueueMethod, count::<b"COUN">()[session: &SessionHandle] -> u32 { Ok(session.session_runner.queue_count()?) });
impl_sub_method!(SessionMethod, tracklist::<b"TRKL">()[session: &SessionHandle] => header, buf, {
let ok = match header {
TracklistMethod::LIST => TracklistMethod::resolve_list(buf, session),
TracklistMethod::COUNT => TracklistMethod::resolve_count(session),
TracklistMethod::RESHUFFLE => TracklistMethod::resolve_reshuffle(session),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(TracklistMethod, list::<b"LIST">(limit: u32, offset: u32)[session: &SessionHandle] -> Vec<dto::Track> { Ok(session.session_runner.tracklist_list(limit, offset)?.into_iter().map(|t| (*t).clone().into_dto()).try_to_vec()?) });
impl_call!(TracklistMethod, reshuffle::<b"SHFL">()[session: &SessionHandle] -> () { Ok(session.session_runner.tracklist_reshuffle()?) });
impl_call!(TracklistMethod, count::<b"COUN">()[session: &SessionHandle] -> u32 { Ok(session.session_runner.tracklist_count()?) });
// LOCAL SESSION
impl_sub_method!(Method, local_session::<b"LSES">() => header, buf, {
let ok = match header {
LocalSessionMethod::SET_VOLUME => LocalSessionMethod::resolve_set_volume(buf),
LocalSessionMethod::SET_LOUDNORM_MODE => LocalSessionMethod::resolve_set_loudnorm_mode(buf),
_ => return Err(MethodError::InvalidMethodCall),
};
Ok(ok)
});
impl_call!(LocalSessionMethod, set_volume::<b"SVOL">(volume: f32) -> f32 {
#[cfg(feature = "local-session")]
{
let volume = volume.clamp(0.0, 1.0);
let local_session = local_session();
local_session.volume.store(volume.to_bits(), Ordering::Relaxed);
local_session.session.event_runner.volume_changed(volume)?;
Ok(volume)
}
#[cfg(not(feature = "local-session"))]
{
unreachable!()
}
});
impl_call!(LocalSessionMethod, set_loudnorm_mode::<b"SLNM">(mode: LoudnormMode) -> () {
#[cfg(feature = "local-session")]
{
local_session().loudnorm_mode.store(mode, Ordering::Relaxed);
Ok(())
}
#[cfg(not(feature = "local-session"))]
{
unreachable!()
}
});
#[cfg(feature = "daemon")]
fn search<T>(
db: &Db<Library>,
query: &str,
limit: u32,
) -> Result<Vec<<Entry<T> as IntoDto>::Dto>, TransactionError>
where
T: Searchable + Cacheable + DatabaseEntry<DbInner = Library>,
Entry<T>: IntoDto<Err = TransactionError>,
{
let dtos = T::search(db, query, limit.min(Method::MAX_RESULT_COUNT) as usize, 0)?
.into_iter()
.map(IntoDto::into_dto)
.try_to_vec()?;
Ok(dtos)
}
#[cfg(feature = "daemon")]
fn list<T>(
db: &Db<Library>,
limit: u32,
offset: u32,
) -> Result<Vec<<Entry<T> as IntoDto>::Dto>, TransactionError>
where
T: Searchable + DatabaseEntry<DbInner = Library>,
Entry<T>: IntoDto<Err = TransactionError>,
{
let Some(cursor) = db
.entry_tree::<T>()
.iter()
.keys()
.nth(offset as usize)
.transpose()?
else {
return Ok(Vec::new());
};
let cursor = Id::<T>::try_from(cursor).expect("All keys should fit inside ids");
let dtos = Entry::db_get_list(
Some(cursor),
limit.min(Method::MAX_RESULT_COUNT) as usize,
db,
)?
.into_iter()
.map(IntoDto::into_dto)
.try_to_vec()?;
Ok(dtos)
}