use mineral_protocol::{FailureKind, OperationFailure, OperationResult, Request, Response};
use crate::client::ClientHandle;
pub(crate) enum AsyncRequest {
AudioOutputs,
StoreGet {
song: mineral_model::SongId,
key: String,
},
StoreSet {
song: mineral_model::SongId,
key: String,
value: mineral_protocol::StoreValue,
},
ToggleLove(Box<mineral_model::Song>),
QuerySongStats(mineral_model::SongId),
}
pub(crate) fn async_request(request: &Request) -> Option<AsyncRequest> {
match request {
Request::AudioOutputs => Some(AsyncRequest::AudioOutputs),
Request::StoreGet { song, key } => Some(AsyncRequest::StoreGet {
song: song.clone(),
key: key.clone(),
}),
Request::StoreSet { song, key, value } => Some(AsyncRequest::StoreSet {
song: song.clone(),
key: key.clone(),
value: value.clone(),
}),
Request::ToggleLove(song) => Some(AsyncRequest::ToggleLove(song.clone())),
Request::QuerySongStats(id) => Some(AsyncRequest::QuerySongStats(id.clone())),
_ => None,
}
}
pub(crate) fn execute_sync(client: &ClientHandle, request: Request) -> OperationResult {
match request {
Request::Pause => {
client.pause();
OperationResult::Applied
}
Request::Resume => {
client.resume();
OperationResult::Applied
}
Request::Stop => {
client.stop();
OperationResult::Applied
}
Request::Seek(ms) => {
client.seek(ms);
OperationResult::Applied
}
Request::SetVolume(pct) => {
client.set_volume(pct);
OperationResult::Applied
}
Request::PlaySong(song) => {
client.play_song(&song);
OperationResult::Applied
}
Request::PlayQueue {
songs,
target,
context,
} => {
query(Response::PlayQueue(
client.play_queue(songs, target, context),
))
}
Request::QueueInsertNext { songs, context } => {
client.queue_insert_next(songs, context);
OperationResult::Applied
}
Request::QueueAppend { songs, context } => {
client.queue_append(songs, context);
OperationResult::Applied
}
Request::CyclePlayMode => {
client.cycle_play_mode();
OperationResult::Applied
}
Request::SetPlayMode(mode) => {
client.set_play_mode(mode);
OperationResult::Applied
}
Request::PrevOrRestart => {
client.prev_or_restart();
OperationResult::Applied
}
Request::NextSong => {
client.next_song();
OperationResult::Applied
}
Request::SubmitTask(kind, priority) => {
client.submit_task(kind, priority);
OperationResult::Accepted
}
Request::Download(target) => {
client.download(target);
OperationResult::Accepted
}
Request::StopDownload(id) => match client.stop_download(&id) {
Ok(()) => OperationResult::Applied,
Err(error) => failure(&error, FailureKind::NotFound),
},
Request::ChannelCaps => query(Response::ChannelCaps(client.channel_caps())),
Request::ServiceInfo => query(Response::ServiceInfo(client.service_info())),
Request::DaemonInfo => query(Response::DaemonInfo {
pid: std::process::id(),
}),
Request::TerminalState {
rows,
cols,
fullscreen,
focused,
} => {
client.report_terminal_state(rows, cols, fullscreen, focused);
OperationResult::Applied
}
Request::Shutdown => {
mineral_log::info!(target: "ipc", "shutdown requested via IPC");
OperationResult::Applied
}
other => {
mineral_log::error!(target: "ipc", request = ?other, "request reached wrong dispatch lane");
OperationResult::Failed(OperationFailure {
kind: FailureKind::Internal,
detail: "操作失败".to_owned(),
})
}
}
}
pub(crate) async fn execute_async(client: &ClientHandle, request: AsyncRequest) -> OperationResult {
match request {
AsyncRequest::AudioOutputs => match client.audio_outputs().await {
Ok(devices) => query(Response::AudioOutputs(devices)),
Err(error) => failure(&error, audio_failure_kind(&error)),
},
AsyncRequest::StoreGet { song, key } => match client.store_get_async(&song, &key).await {
Ok(value) => query(Response::StoreValue(value)),
Err(error) => failure(&error, store_failure_kind(&error)),
},
AsyncRequest::StoreSet { song, key, value } => {
match client.store_set_async(&song, &key, &value).await {
Ok(()) => OperationResult::Applied,
Err(error) => failure(&error, store_failure_kind(&error)),
}
}
AsyncRequest::ToggleLove(song) => match client.toggle_love_async(&song).await {
Ok(loved) => query(Response::LoveToggled(loved)),
Err(error) => failure(&error, FailureKind::Internal),
},
AsyncRequest::QuerySongStats(id) => match client.query_song_stats_async(&id).await {
Ok(stats) => query(Response::SongStats(stats)),
Err(error) => failure(&error, FailureKind::Internal),
},
}
}
pub(crate) async fn execute_queue_edit(
client: &ClientHandle,
op: mineral_protocol::QueueOp,
) -> OperationResult {
query(Response::QueueEdited(client.queue_edit_async(op).await))
}
pub(crate) fn failure(
error: &(dyn std::error::Error + 'static),
kind: FailureKind,
) -> OperationResult {
mineral_log::warn!(target: "ipc", error = mineral_log::chain(error), failure_kind = ?kind, "RPC request failed");
OperationResult::Failed(OperationFailure {
kind,
detail: match kind {
FailureKind::Invalid => "请求参数不合法",
FailureKind::NotFound => "目标不存在",
FailureKind::Unavailable => "服务暂不可用",
FailureKind::Conflict => "当前状态不允许该操作",
FailureKind::Internal => "操作失败",
}
.to_owned(),
})
}
fn store_failure_kind(error: &crate::persistence::Error) -> FailureKind {
match error {
crate::persistence::Error::ReservedKey { .. }
| crate::persistence::Error::NamespaceMismatch { .. } => FailureKind::Invalid,
_ => FailureKind::Internal,
}
}
pub(crate) fn audio_failure_kind(error: &mineral_audio::Error) -> FailureKind {
match error {
mineral_audio::Error::InvalidDeviceId { .. } => FailureKind::Invalid,
mineral_audio::Error::DeviceUnavailable { .. } => FailureKind::NotFound,
mineral_audio::Error::OutputUnavailable
| mineral_audio::Error::OutputDisabled
| mineral_audio::Error::NoDefaultDevice => FailureKind::Unavailable,
_ => FailureKind::Internal,
}
}
pub(crate) fn query(response: Response) -> OperationResult {
OperationResult::Query(Box::new(response))
}