Command execution and storage
This commit is contained in:
commit
8350dca696
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "obs-commands"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
either = "1.6.1"
|
||||
obws = "0.8.0"
|
||||
path-gen = { version = "0.1.0", git = "https://git.asonix.dog/asonix/path-gen", branch = "main" }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde_json = "1.0.68"
|
||||
sled = "0.34.7"
|
||||
tokio = { version = "1.12.0", features = ["time"] }
|
||||
thiserror = "1.0.29"
|
||||
tracing-error = "0.1.2"
|
||||
|
||||
[dev-dependencies]
|
||||
path-gen = { version = "0.1.0", git = "https://git.asonix.dog/asonix/path-gen", branch = "main", features = ["test"] }
|
407
src/command.rs
Normal file
407
src/command.rs
Normal file
|
@ -0,0 +1,407 @@
|
|||
use crate::{
|
||||
error::Error,
|
||||
response::{Response, ResponseItem, SceneItem},
|
||||
};
|
||||
use obws::Client;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CommandList {
|
||||
first: Command,
|
||||
rest: Vec<CommandNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
struct CommandNode {
|
||||
delay: Option<Delay>,
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
struct Delay {
|
||||
millis: u64,
|
||||
seconds: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Command {
|
||||
GetSceneList(GetSceneList),
|
||||
SwitchScene(SwitchScene),
|
||||
SetSceneItemVisibility(SetSceneItemVisibility),
|
||||
SetStreaming(SetStreaming),
|
||||
SetRecording(SetRecording),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetSceneList;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SwitchScene {
|
||||
to: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SetSceneItemVisibility {
|
||||
scene_name: String,
|
||||
item_id: i64,
|
||||
operation: VisibilityOperation,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SetStreaming {
|
||||
operation: StateOperation,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SetRecording {
|
||||
operation: StateOperation,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub enum VisibilityOperation {
|
||||
On,
|
||||
Off,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub enum StateOperation {
|
||||
Start,
|
||||
Stop,
|
||||
Pause,
|
||||
Resume,
|
||||
Toggle,
|
||||
}
|
||||
|
||||
impl CommandList {
|
||||
pub fn new<C>(command: C) -> Self
|
||||
where
|
||||
Command: From<C>,
|
||||
{
|
||||
CommandList {
|
||||
first: command.into(),
|
||||
rest: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_command<C>(&mut self, command: C, delay: Option<Duration>) -> &mut Self
|
||||
where
|
||||
Command: From<C>,
|
||||
{
|
||||
self.rest.push(CommandNode {
|
||||
delay: delay.map(From::from),
|
||||
command: command.into(),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn execute(&self, client: &Client) -> Result<Response, Error> {
|
||||
let mut output = Response::default();
|
||||
|
||||
self.first.execute(&mut output, client).await?;
|
||||
|
||||
for node in &self.rest {
|
||||
node.execute(&mut output, client).await?;
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandNode {
|
||||
async fn execute(&self, output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
if let Some(delay) = &self.delay {
|
||||
let duration = Duration::from_secs(delay.seconds) + Duration::from_millis(delay.millis);
|
||||
tokio::time::sleep(duration).await;
|
||||
}
|
||||
|
||||
self.command.execute(output, client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Command {
|
||||
async fn execute(&self, output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
match self {
|
||||
Command::GetSceneList(get_scene_list) => get_scene_list.execute(output, client).await,
|
||||
Command::SwitchScene(switch_scene) => switch_scene.execute(output, client).await,
|
||||
Command::SetSceneItemVisibility(set_scene_item_visibility) => {
|
||||
set_scene_item_visibility.execute(output, client).await
|
||||
}
|
||||
Command::SetRecording(set_recording) => set_recording.execute(output, client).await,
|
||||
Command::SetStreaming(set_streaming) => set_streaming.execute(output, client).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GetSceneList {
|
||||
pub fn new() -> Self {
|
||||
GetSceneList
|
||||
}
|
||||
|
||||
pub fn into_command(self) -> Command {
|
||||
Command::GetSceneList(self)
|
||||
}
|
||||
|
||||
async fn execute(&self, output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
let scene_list = client.scenes().get_scene_list().await?;
|
||||
|
||||
output.insert(ResponseItem::CurrentScene {
|
||||
name: scene_list.current_scene,
|
||||
});
|
||||
output.insert(ResponseItem::SceneList {
|
||||
scenes: scene_list
|
||||
.scenes
|
||||
.iter()
|
||||
.map(|scene| scene.name.clone())
|
||||
.collect(),
|
||||
});
|
||||
for scene in scene_list.scenes {
|
||||
output.insert(ResponseItem::SceneItems {
|
||||
scene: scene.name,
|
||||
items: scene
|
||||
.sources
|
||||
.into_iter()
|
||||
.map(SceneItem::translate)
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SwitchScene {
|
||||
pub fn to(to: String) -> Self {
|
||||
SwitchScene { to }
|
||||
}
|
||||
|
||||
pub fn into_command(self) -> Command {
|
||||
Command::SwitchScene(self)
|
||||
}
|
||||
|
||||
async fn execute(&self, output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
client.scenes().set_current_scene(&self.to).await?;
|
||||
|
||||
output.insert(ResponseItem::CurrentScene {
|
||||
name: self.to.clone(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SetSceneItemVisibility {
|
||||
pub fn show(scene_name: String, item_id: i64) -> Self {
|
||||
SetSceneItemVisibility {
|
||||
scene_name,
|
||||
item_id,
|
||||
operation: VisibilityOperation::On,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hide(scene_name: String, item_id: i64) -> Self {
|
||||
SetSceneItemVisibility {
|
||||
scene_name,
|
||||
item_id,
|
||||
operation: VisibilityOperation::Off,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(scene_name: String, item_id: i64) -> Self {
|
||||
SetSceneItemVisibility {
|
||||
scene_name,
|
||||
item_id,
|
||||
operation: VisibilityOperation::Toggle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_command(self) -> Command {
|
||||
Command::SetSceneItemVisibility(self)
|
||||
}
|
||||
|
||||
async fn execute(&self, _output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
fn to_specification(
|
||||
id: i64,
|
||||
) -> either::Either<&'static str, obws::requests::SceneItemSpecification<'static>> {
|
||||
either::Either::Right(obws::requests::SceneItemSpecification {
|
||||
name: None,
|
||||
id: Some(id),
|
||||
})
|
||||
}
|
||||
|
||||
let visible = match self.operation {
|
||||
VisibilityOperation::On => true,
|
||||
VisibilityOperation::Off => false,
|
||||
VisibilityOperation::Toggle => {
|
||||
let properties = client
|
||||
.scene_items()
|
||||
.get_scene_item_properties(
|
||||
Some(&self.scene_name),
|
||||
to_specification(self.item_id),
|
||||
)
|
||||
.await?;
|
||||
|
||||
!properties.visible
|
||||
}
|
||||
};
|
||||
|
||||
let properties = obws::requests::SceneItemProperties {
|
||||
scene_name: Some(&self.scene_name),
|
||||
item: to_specification(self.item_id),
|
||||
visible: Some(visible),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
client
|
||||
.scene_items()
|
||||
.set_scene_item_properties(properties)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SetRecording {
|
||||
pub fn start() -> Self {
|
||||
SetRecording {
|
||||
operation: StateOperation::Start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop() -> Self {
|
||||
SetRecording {
|
||||
operation: StateOperation::Stop,
|
||||
}
|
||||
}
|
||||
pub fn pause() -> Self {
|
||||
SetRecording {
|
||||
operation: StateOperation::Pause,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resume() -> Self {
|
||||
SetRecording {
|
||||
operation: StateOperation::Resume,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle() -> Self {
|
||||
SetRecording {
|
||||
operation: StateOperation::Toggle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_command(self) -> Command {
|
||||
Command::SetRecording(self)
|
||||
}
|
||||
|
||||
async fn execute(&self, _output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
let status = client.recording().get_recording_status().await?;
|
||||
|
||||
match self.operation {
|
||||
StateOperation::Start if !status.is_recording => {
|
||||
client.recording().start_recording().await?;
|
||||
}
|
||||
StateOperation::Stop if status.is_recording => {
|
||||
client.recording().stop_recording().await?;
|
||||
}
|
||||
StateOperation::Toggle => {
|
||||
client.recording().start_stop_recording().await?;
|
||||
}
|
||||
StateOperation::Pause if !status.is_recording_paused => {
|
||||
client.recording().pause_recording().await?;
|
||||
}
|
||||
StateOperation::Resume if status.is_recording_paused => {
|
||||
client.recording().resume_recording().await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SetStreaming {
|
||||
pub fn start() -> Self {
|
||||
SetStreaming {
|
||||
operation: StateOperation::Start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop() -> Self {
|
||||
SetStreaming {
|
||||
operation: StateOperation::Stop,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle() -> Self {
|
||||
SetStreaming {
|
||||
operation: StateOperation::Toggle,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_command(self) -> Command {
|
||||
Command::SetStreaming(self)
|
||||
}
|
||||
|
||||
async fn execute(&self, _output: &mut Response, client: &Client) -> Result<(), Error> {
|
||||
let status = client.streaming().get_streaming_status().await?;
|
||||
|
||||
match self.operation {
|
||||
StateOperation::Start if !status.streaming => {
|
||||
client.streaming().start_streaming(None).await?;
|
||||
}
|
||||
StateOperation::Stop if status.streaming => {
|
||||
client.streaming().stop_streaming().await?;
|
||||
}
|
||||
StateOperation::Toggle => {
|
||||
client.streaming().start_stop_streaming().await?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Duration> for Delay {
|
||||
fn from(duration: Duration) -> Self {
|
||||
Delay {
|
||||
millis: duration.subsec_millis().into(),
|
||||
seconds: duration.as_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetSceneList> for Command {
|
||||
fn from(gsl: GetSceneList) -> Self {
|
||||
gsl.into_command()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SwitchScene> for Command {
|
||||
fn from(ss: SwitchScene) -> Self {
|
||||
ss.into_command()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetSceneItemVisibility> for Command {
|
||||
fn from(ssiv: SetSceneItemVisibility) -> Self {
|
||||
ssiv.into_command()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetRecording> for Command {
|
||||
fn from(sr: SetRecording) -> Self {
|
||||
sr.into_command()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SetStreaming> for Command {
|
||||
fn from(ss: SetStreaming) -> Self {
|
||||
ss.into_command()
|
||||
}
|
||||
}
|
73
src/error.rs
Normal file
73
src/error.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use crate::persist::ParseError;
|
||||
use sled::transaction::TransactionError;
|
||||
use tracing_error::SpanTrace;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
kind: ErrorKind,
|
||||
context: SpanTrace,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ErrorKind {
|
||||
#[error(transparent)]
|
||||
Obws(#[from] obws::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Sled(#[from] sled::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
PathBuilding(#[from] ParseError),
|
||||
|
||||
#[error("Authentication required to connect")]
|
||||
AuthRequired,
|
||||
|
||||
#[error("Command not found")]
|
||||
Missing,
|
||||
|
||||
#[error("Cannot remove command currently use")]
|
||||
CommandInUse,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn kind(&self) -> &ErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.kind)?;
|
||||
std::fmt::Display::fmt(&self.context, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
self.kind.source()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransactionError<Error>> for Error {
|
||||
fn from(txerr: TransactionError<Error>) -> Self {
|
||||
match txerr {
|
||||
TransactionError::Abort(e) => e,
|
||||
TransactionError::Storage(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Error
|
||||
where
|
||||
ErrorKind: From<T>,
|
||||
{
|
||||
fn from(t: T) -> Self {
|
||||
Error {
|
||||
kind: ErrorKind::from(t),
|
||||
context: SpanTrace::capture(),
|
||||
}
|
||||
}
|
||||
}
|
30
src/lib.rs
Normal file
30
src/lib.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
mod command;
|
||||
mod error;
|
||||
mod persist;
|
||||
mod response;
|
||||
|
||||
pub use command::{
|
||||
Command, CommandList, GetSceneList, SetRecording, SetSceneItemVisibility, SetStreaming,
|
||||
SwitchScene,
|
||||
};
|
||||
pub use error::{Error, ErrorKind};
|
||||
pub use persist::Store;
|
||||
pub use response::{Response, ResponseItem, SceneItem};
|
||||
|
||||
use obws::Client;
|
||||
|
||||
pub async fn connect(host: &str, port: u16, password: Option<&str>) -> Result<Client, Error> {
|
||||
let mut client = Client::connect(host, port).await?;
|
||||
|
||||
if let Some(password) = password {
|
||||
client.login(Some(password)).await?;
|
||||
} else {
|
||||
let auth_required = client.general().get_auth_required().await?;
|
||||
if auth_required.auth_required {
|
||||
client.disconnect().await;
|
||||
return Err(ErrorKind::AuthRequired.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(client)
|
||||
}
|
637
src/persist.rs
Normal file
637
src/persist.rs
Normal file
|
@ -0,0 +1,637 @@
|
|||
use crate::{
|
||||
command::CommandList,
|
||||
error::{Error, ErrorKind},
|
||||
};
|
||||
use path_gen::PathGen;
|
||||
use sled::{transaction::ConflictableTransactionError, Db, Tree};
|
||||
|
||||
mod path;
|
||||
|
||||
use path::{CommandField, CommandName, DeckId, Key, NameField, UsedBy, UsedByField, PATH_ROOT};
|
||||
|
||||
pub use path::{
|
||||
CommandFieldParseError, NameFieldParseError, ParseError, UsedByFieldParseError,
|
||||
UsedByParseError,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Store {
|
||||
commands: Tree,
|
||||
_db: Db,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn build(db: Db) -> Result<Self, Error> {
|
||||
let commands = db.open_tree("obs-commands.commands-store")?;
|
||||
|
||||
// read everything to memory
|
||||
for res in commands.iter() {
|
||||
let _ = res?;
|
||||
}
|
||||
|
||||
Ok(Store { commands, _db: db })
|
||||
}
|
||||
|
||||
pub fn save_command(
|
||||
&self,
|
||||
command_name: &str,
|
||||
command_list: &CommandList,
|
||||
) -> Result<(), Error> {
|
||||
let v = serde_json::to_vec(command_list)?;
|
||||
|
||||
let command_name_path = PATH_ROOT
|
||||
.push(CommandName::new(command_name))
|
||||
.field(CommandField)
|
||||
.to_bytes();
|
||||
|
||||
self.commands.insert(command_name_path, v)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command(&self, command_name: &str) -> Result<Option<CommandList>, Error> {
|
||||
let command_name_path = PATH_ROOT
|
||||
.push(CommandName::new(command_name))
|
||||
.field(CommandField)
|
||||
.to_bytes();
|
||||
|
||||
if let Some(ivec) = self.commands.get(command_name_path)? {
|
||||
return Ok(Some(serde_json::from_slice(&ivec)?));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn map_command(&self, deck_id: &str, key: u8, command_name: &str) -> Result<(), Error> {
|
||||
let command_key = PATH_ROOT
|
||||
.push(CommandName::new(command_name))
|
||||
.field(CommandField)
|
||||
.to_bytes();
|
||||
|
||||
let key_command_path = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.push(Key::new(key))
|
||||
.field(CommandField)
|
||||
.to_bytes();
|
||||
|
||||
let command_use_path = PATH_ROOT
|
||||
.push(CommandName::new(command_name))
|
||||
.push(UsedBy)
|
||||
.push(DeckId::new(deck_id))
|
||||
.push(Key::new(key))
|
||||
.field(UsedByField)
|
||||
.to_bytes();
|
||||
|
||||
#[cfg(not(release))]
|
||||
let key_command_parser = PathGen::parser()
|
||||
.push::<DeckId>()
|
||||
.push::<Key>()
|
||||
.field::<CommandField>();
|
||||
|
||||
#[cfg(not(release))]
|
||||
let command_use_parser = PathGen::parser()
|
||||
.push::<CommandName>()
|
||||
.push::<UsedBy>()
|
||||
.push::<DeckId>()
|
||||
.push::<Key>()
|
||||
.field::<UsedByField>();
|
||||
|
||||
self.commands.transaction(|commands| {
|
||||
if commands.get(&command_key)?.is_none() {
|
||||
return Err(trans_err(ErrorKind::Missing));
|
||||
}
|
||||
|
||||
if let Some(previous_command_use_path) = commands.get(&key_command_path)? {
|
||||
#[cfg(not(release))]
|
||||
let previous_command_use_path = command_use_parser
|
||||
.parse(&previous_command_use_path)
|
||||
.map_err(trans_parse_err)?
|
||||
.to_bytes();
|
||||
|
||||
if let Some(previous_key_command_path) =
|
||||
commands.remove(previous_command_use_path)?
|
||||
{
|
||||
#[cfg(not(release))]
|
||||
let previous_key_command_path = key_command_parser
|
||||
.parse(&previous_key_command_path)
|
||||
.map_err(trans_parse_err)?
|
||||
.to_bytes();
|
||||
|
||||
commands.remove(previous_key_command_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
commands.insert(key_command_path.as_slice(), command_use_path.as_slice())?;
|
||||
commands.insert(command_use_path.as_slice(), key_command_path.as_slice())?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Figure out transactions for this
|
||||
pub fn remove_command(&self, command_name: &str) -> Result<(), Error> {
|
||||
if !self.get_command_uses(command_name).is_empty() {
|
||||
return Err(ErrorKind::CommandInUse.into());
|
||||
}
|
||||
|
||||
let command_key = PATH_ROOT
|
||||
.push(CommandName::new(command_name))
|
||||
.field(CommandField)
|
||||
.to_bytes();
|
||||
|
||||
self.commands.remove(command_key)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command_uses(&self, command_name: &str) -> Vec<(String, u8)> {
|
||||
let command_use_prefix = PATH_ROOT
|
||||
.push(CommandName::new(command_name))
|
||||
.prefix::<UsedBy>()
|
||||
.to_bytes();
|
||||
|
||||
let key_command_parser = PathGen::parser()
|
||||
.push::<DeckId>()
|
||||
.push::<Key>()
|
||||
.field::<CommandField>();
|
||||
|
||||
self.commands
|
||||
.scan_prefix(&command_use_prefix)
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(|key_command_map| {
|
||||
let key_command_path = key_command_parser
|
||||
.parse::<ParseError>(&key_command_map)
|
||||
.ok()?;
|
||||
|
||||
let key = key_command_path.parent().key();
|
||||
let deck_id = key_command_path.parent().parent().deck_id().to_string();
|
||||
|
||||
Some((deck_id, key))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_mapped_command(&self, deck_id: &str, key: u8) -> Result<Option<String>, Error> {
|
||||
let key_command_path = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.push(Key::new(key))
|
||||
.field(CommandField)
|
||||
.to_bytes();
|
||||
|
||||
let command_use_parser = PathGen::parser()
|
||||
.push::<CommandName>()
|
||||
.push::<UsedBy>()
|
||||
.push::<DeckId>()
|
||||
.push::<Key>()
|
||||
.field::<UsedByField>();
|
||||
|
||||
if let Some(ivec) = self.commands.get(key_command_path)? {
|
||||
let command_use_path = command_use_parser.parse::<ParseError>(&ivec)?;
|
||||
|
||||
let command_name = command_use_path
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.command_name()
|
||||
.to_string();
|
||||
|
||||
return Ok(Some(command_name));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn mappings_for_deck(&self, deck_id: &str) -> Vec<(u8, String)> {
|
||||
let key_prefix_for_deck = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.prefix::<Key>()
|
||||
.to_bytes();
|
||||
|
||||
let command_use_parser = PathGen::parser()
|
||||
.push::<CommandName>()
|
||||
.push::<UsedBy>()
|
||||
.push::<DeckId>()
|
||||
.push::<Key>()
|
||||
.field::<UsedByField>();
|
||||
|
||||
self.commands
|
||||
.scan_prefix(key_prefix_for_deck)
|
||||
.values()
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(|v| {
|
||||
let command_use_path = command_use_parser.parse::<ParseError>(&v).ok()?;
|
||||
|
||||
let key = command_use_path.parent().key();
|
||||
let command_name = command_use_path
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.parent()
|
||||
.command_name()
|
||||
.to_string();
|
||||
|
||||
Some((key, command_name))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn save_deck_name(&self, deck_id: &str, name: &str) -> Result<(), Error> {
|
||||
let deck_name_path = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.field(NameField)
|
||||
.to_bytes();
|
||||
|
||||
self.commands.insert(deck_name_path, name.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_deck_name(&self, deck_id: &str) -> Result<Option<String>, Error> {
|
||||
let deck_name_path = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.field(NameField)
|
||||
.to_bytes();
|
||||
|
||||
if let Some(ivec) = self.commands.get(deck_name_path)? {
|
||||
let string = String::from_utf8(ivec.to_vec()).map_err(ParseError::from)?;
|
||||
|
||||
return Ok(Some(string));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn save_key_name(&self, deck_id: &str, key: u8, name: &str) -> Result<(), Error> {
|
||||
let key_name_path = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.push(Key::new(key))
|
||||
.field(NameField)
|
||||
.to_bytes();
|
||||
|
||||
self.commands.insert(key_name_path, name.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_key_name(&self, deck_id: &str, key: u8) -> Result<Option<String>, Error> {
|
||||
let key_name_path = PATH_ROOT
|
||||
.push(DeckId::new(deck_id))
|
||||
.push(Key::new(key))
|
||||
.field(NameField)
|
||||
.to_bytes();
|
||||
|
||||
if let Some(ivec) = self.commands.get(key_name_path)? {
|
||||
let string = String::from_utf8(ivec.to_vec()).map_err(ParseError::from)?;
|
||||
|
||||
return Ok(Some(string));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn all_commands(&self) -> Vec<(String, CommandList)> {
|
||||
let command_prefix = PATH_ROOT.prefix::<CommandName>().to_bytes();
|
||||
let command_parser = PathGen::parser()
|
||||
.push::<CommandName>()
|
||||
.field::<CommandField>();
|
||||
|
||||
self.commands
|
||||
.scan_prefix(command_prefix)
|
||||
.filter_map(|res| res.ok())
|
||||
.filter_map(|(k, v)| {
|
||||
let command_name_path = command_parser.parse::<ParseError>(&k).ok()?;
|
||||
let command_list = serde_json::from_slice(&v).ok()?;
|
||||
|
||||
let command_name = command_name_path.parent().command_name().to_string();
|
||||
|
||||
Some((command_name, command_list))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Store {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Store").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(release))]
|
||||
fn trans_parse_err(e: ParseError) -> ConflictableTransactionError<Error> {
|
||||
trans_err(e.into())
|
||||
}
|
||||
|
||||
fn trans_err(kind: ErrorKind) -> ConflictableTransactionError<Error> {
|
||||
ConflictableTransactionError::Abort(ErrorKind::from(kind).into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
command::{CommandList, GetSceneList},
|
||||
persist::Store,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
fn with_store<O>(f: impl Fn(Store) -> O) -> O {
|
||||
let db = sled::Config::new().temporary(true).open().unwrap();
|
||||
let store = Store::build(db).unwrap();
|
||||
|
||||
(f)(store)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_and_retrieve_command() {
|
||||
let cmd = CommandList::new(GetSceneList::new());
|
||||
let name = "store_and_retrieve_command";
|
||||
|
||||
let new_cmd = with_store(|store| {
|
||||
store.save_command(name, &cmd).unwrap();
|
||||
store.get_command(name).unwrap().unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(cmd, new_cmd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map_key_to_command() {
|
||||
let cmd = CommandList::new(GetSceneList::new());
|
||||
let name = "map_key_to_command";
|
||||
let key = 1;
|
||||
let deck_id = "012345";
|
||||
|
||||
let new_name = with_store(|store| {
|
||||
store.save_command(name, &cmd).unwrap();
|
||||
store.map_command(deck_id, key, name).unwrap();
|
||||
store.get_mapped_command(deck_id, key).unwrap().unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(new_name, name)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_map_missing_command() {
|
||||
let name = "doesnt_map_missing_command";
|
||||
let key = 1;
|
||||
let deck_id = "012345";
|
||||
|
||||
let result = with_store(|store| store.map_command(deck_id, key, name));
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adds_use_for_mapped_command() {
|
||||
let cmd = CommandList::new(GetSceneList::new());
|
||||
let name = "adds_use_for_mapped_command";
|
||||
let key = 1;
|
||||
let deck_id = "012345";
|
||||
|
||||
let uses = with_store(|store| {
|
||||
store.save_command(name, &cmd).unwrap();
|
||||
store.map_command(deck_id, key, name).unwrap();
|
||||
store.get_command_uses(name)
|
||||
});
|
||||
|
||||
assert_eq!(uses.len(), 1);
|
||||
assert_eq!(uses[0].0, deck_id);
|
||||
assert_eq!(uses[0].1, key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_unused_command() {
|
||||
let cmd = CommandList::new(GetSceneList::new());
|
||||
let name = "remove_unused_command";
|
||||
|
||||
let opt = with_store(|store| {
|
||||
store.save_command(name, &cmd).unwrap();
|
||||
store.remove_command(name).unwrap();
|
||||
store.get_command(name).unwrap()
|
||||
});
|
||||
|
||||
assert!(opt.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dont_remove_used_command() {
|
||||
let cmd = CommandList::new(GetSceneList::new());
|
||||
let name = "dont_remove_used_command";
|
||||
let deck_id = "012345";
|
||||
let key = 1;
|
||||
|
||||
let res = with_store(|store| {
|
||||
store.save_command(name, &cmd).unwrap();
|
||||
store.map_command(deck_id, key, name).unwrap();
|
||||
store.remove_command(name)
|
||||
});
|
||||
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_mappings_for_deck() {
|
||||
let names = [
|
||||
"get_mappings_for_deck_1",
|
||||
"get_mappings_for_deck_2",
|
||||
"get_mappings_for_deck_3",
|
||||
];
|
||||
let cmds = names
|
||||
.iter()
|
||||
.map(|name| (*name, CommandList::new(GetSceneList::new())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mappings = names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, name)| {
|
||||
let index: u8 = index.try_into().unwrap();
|
||||
let key: u8 = index + 1;
|
||||
|
||||
(key, *name)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let deck_id = "012345";
|
||||
|
||||
let new_mappings = with_store(|store| {
|
||||
for (name, cmd) in &cmds {
|
||||
store.save_command(name, cmd).unwrap();
|
||||
}
|
||||
|
||||
for (key, name) in &mappings {
|
||||
store.map_command(deck_id, *key, name).unwrap();
|
||||
}
|
||||
|
||||
store.mappings_for_deck(deck_id)
|
||||
});
|
||||
|
||||
assert_eq!(new_mappings.len(), mappings.len());
|
||||
|
||||
for (key, name) in new_mappings {
|
||||
mappings
|
||||
.iter()
|
||||
.find(|(left_key, left_name)| *left_key == key && *left_name == name)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_get_mappings_for_wrong_deck() {
|
||||
let names = [
|
||||
"get_mappings_for_deck_1",
|
||||
"get_mappings_for_deck_2",
|
||||
"get_mappings_for_deck_3",
|
||||
];
|
||||
let cmds = names
|
||||
.iter()
|
||||
.map(|name| (*name, CommandList::new(GetSceneList::new())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mappings = names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, name)| {
|
||||
let index: u8 = index.try_into().unwrap();
|
||||
let key: u8 = index + 1;
|
||||
|
||||
(key, *name)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let deck_id = "012345";
|
||||
let wrong_deck_id = "543210";
|
||||
|
||||
let new_mappings = with_store(|store| {
|
||||
for (name, cmd) in &cmds {
|
||||
store.save_command(name, cmd).unwrap();
|
||||
}
|
||||
|
||||
for (key, name) in &mappings {
|
||||
store.map_command(deck_id, *key, name).unwrap();
|
||||
}
|
||||
|
||||
store.mappings_for_deck(wrong_deck_id)
|
||||
});
|
||||
|
||||
assert_eq!(new_mappings.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_and_retrieve_deck_name() {
|
||||
let deck_name = "Some Name";
|
||||
let deck_id = "012345";
|
||||
|
||||
let new_deck_name = with_store(|store| {
|
||||
store.save_deck_name(deck_id, deck_name).unwrap();
|
||||
store.get_deck_name(deck_id).unwrap().unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(new_deck_name, deck_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_and_retrieve_key_name() {
|
||||
let key_name = "Top Left";
|
||||
let deck_id = "012345";
|
||||
let key = 1;
|
||||
|
||||
let new_key_name = with_store(|store| {
|
||||
store.save_key_name(deck_id, key, key_name).unwrap();
|
||||
store.get_key_name(deck_id, key).unwrap().unwrap()
|
||||
});
|
||||
|
||||
assert_eq!(new_key_name, key_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_name_doesnt_interfere_with_mappings() {
|
||||
let names = [
|
||||
"get_mappings_for_deck_1",
|
||||
"get_mappings_for_deck_2",
|
||||
"get_mappings_for_deck_3",
|
||||
];
|
||||
let cmds = names
|
||||
.iter()
|
||||
.map(|name| (*name, CommandList::new(GetSceneList::new())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mappings = names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, name)| {
|
||||
let index: u8 = index.try_into().unwrap();
|
||||
let key: u8 = index + 1;
|
||||
|
||||
(key, *name)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let key_names = names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| {
|
||||
let index: u8 = index.try_into().unwrap();
|
||||
let key: u8 = index + 1;
|
||||
|
||||
(key, format!("Key {}", key))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let deck_id = "012345";
|
||||
|
||||
let new_mappings = with_store(|store| {
|
||||
for (name, cmd) in &cmds {
|
||||
store.save_command(name, cmd).unwrap();
|
||||
}
|
||||
|
||||
for (key, name) in &mappings {
|
||||
store.map_command(deck_id, *key, name).unwrap();
|
||||
}
|
||||
|
||||
for (key, name) in &key_names {
|
||||
store.save_key_name(deck_id, *key, name).unwrap();
|
||||
}
|
||||
|
||||
store.mappings_for_deck(deck_id)
|
||||
});
|
||||
|
||||
assert_eq!(new_mappings.len(), mappings.len());
|
||||
|
||||
for (key, name) in new_mappings {
|
||||
mappings
|
||||
.iter()
|
||||
.find(|(left_key, left_name)| *left_key == key && *left_name == name)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_and_retrieve_many_commands() {
|
||||
let names = [
|
||||
"get_mappings_for_deck_1",
|
||||
"get_mappings_for_deck_2",
|
||||
"get_mappings_for_deck_3",
|
||||
];
|
||||
let cmds = names
|
||||
.iter()
|
||||
.map(|name| (*name, CommandList::new(GetSceneList::new())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let new_commands = with_store(|store| {
|
||||
for (name, cmd) in &cmds {
|
||||
store.save_command(name, cmd).unwrap();
|
||||
}
|
||||
|
||||
store.all_commands()
|
||||
});
|
||||
|
||||
assert_eq!(new_commands.len(), 3);
|
||||
|
||||
for (name, command) in new_commands {
|
||||
cmds.iter()
|
||||
.find(|(left_name, left_cmd)| *left_name == name && *left_cmd == command)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
304
src/persist/path.rs
Normal file
304
src/persist/path.rs
Normal file
|
@ -0,0 +1,304 @@
|
|||
use path_gen::{DeconstructError, PathGen};
|
||||
use std::{borrow::Cow, str::Utf8Error, string::FromUtf8Error};
|
||||
|
||||
pub(super) const PATH_ROOT: PathGen<'static> = PathGen::new("obs-commands");
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct CommandField;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||
#[error("Invalid command field")]
|
||||
pub struct CommandFieldParseError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct CommandName<'a> {
|
||||
inner: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> CommandName<'a> {
|
||||
pub(super) const fn new(s: &'a str) -> Self {
|
||||
CommandName {
|
||||
inner: Cow::Borrowed(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_owned(s: String) -> Self {
|
||||
CommandName {
|
||||
inner: Cow::Owned(s),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn command_name(&self) -> &str {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct DeckId<'a> {
|
||||
inner: Cow<'a, str>,
|
||||
}
|
||||
|
||||
impl<'a> DeckId<'a> {
|
||||
pub(super) const fn new(s: &'a str) -> Self {
|
||||
DeckId {
|
||||
inner: Cow::Borrowed(s),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_owned(s: String) -> Self {
|
||||
DeckId {
|
||||
inner: Cow::Owned(s),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn deck_id(&self) -> &str {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct Key {
|
||||
inner: u8,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub(super) fn new(key: u8) -> Self {
|
||||
Key { inner: key }
|
||||
}
|
||||
|
||||
pub(super) fn key(&self) -> u8 {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||
#[error("Invalid key")]
|
||||
pub struct ParseKeyError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct NameField;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||
#[error("Invalid name field")]
|
||||
pub struct NameFieldParseError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct UsedBy;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||
#[error("Invalid UseBy node")]
|
||||
pub struct UsedByParseError;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(super) struct UsedByField;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, thiserror::Error)]
|
||||
#[error("Invalid use-by field")]
|
||||
pub struct UsedByFieldParseError;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
#[error(transparent)]
|
||||
Command(#[from] CommandFieldParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
Name(#[from] NameFieldParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
UseBy(#[from] UsedByParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
UseByField(#[from] UsedByFieldParseError),
|
||||
|
||||
#[error(transparent)]
|
||||
Key(#[from] ParseKeyError),
|
||||
|
||||
#[error(transparent)]
|
||||
Utf8(#[from] Utf8Error),
|
||||
|
||||
#[error(transparent)]
|
||||
FromUtf8(#[from] FromUtf8Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Path(#[from] DeconstructError),
|
||||
}
|
||||
|
||||
mod implementation {
|
||||
use super::{
|
||||
CommandField, CommandFieldParseError, CommandName, DeckId, Key, NameField,
|
||||
NameFieldParseError, ParseError, ParseKeyError, UsedBy, UsedByField, UsedByFieldParseError,
|
||||
UsedByParseError,
|
||||
};
|
||||
use path_gen::{PathField, PathItem, PathNode};
|
||||
use std::{borrow::Cow, str::from_utf8};
|
||||
|
||||
impl<'a> PathItem<'a> for CommandField {
|
||||
type Error = CommandFieldParseError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if bytes.as_ref() == b"command" {
|
||||
return Ok(CommandField);
|
||||
}
|
||||
|
||||
Err(CommandFieldParseError)
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
Cow::Borrowed(b"command")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathItem<'a> for CommandName<'a> {
|
||||
type Error = ParseError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
match bytes {
|
||||
Cow::Borrowed(bytes) => Ok(CommandName::new(from_utf8(bytes)?)),
|
||||
Cow::Owned(bytes) => Ok(CommandName::new_owned(String::from_utf8(bytes)?)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
self.inner.as_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathItem<'a> for DeckId<'a> {
|
||||
type Error = ParseError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
match bytes {
|
||||
Cow::Borrowed(bytes) => Ok(DeckId::new(from_utf8(bytes)?)),
|
||||
Cow::Owned(bytes) => Ok(DeckId::new_owned(String::from_utf8(bytes)?)),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
self.inner.as_bytes().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathItem<'a> for Key {
|
||||
type Error = ParseKeyError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if bytes.len() != 1 {
|
||||
return Err(ParseKeyError);
|
||||
}
|
||||
|
||||
Ok(Key::new(bytes[0]))
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
Cow::Owned(vec![self.key()])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathItem<'a> for NameField {
|
||||
type Error = NameFieldParseError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if bytes.as_ref() == b"name" {
|
||||
return Ok(NameField);
|
||||
}
|
||||
|
||||
Err(NameFieldParseError)
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
Cow::Borrowed(b"name")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathItem<'a> for UsedBy {
|
||||
type Error = UsedByParseError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if bytes.as_ref() == b"by" {
|
||||
return Ok(UsedBy);
|
||||
}
|
||||
|
||||
Err(UsedByParseError)
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
Cow::Borrowed(b"by")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathItem<'a> for UsedByField {
|
||||
type Error = UsedByFieldParseError;
|
||||
|
||||
fn parse(bytes: Cow<'a, [u8]>) -> Result<Self, Self::Error>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if bytes.as_ref() == b"used-by" {
|
||||
return Ok(UsedByField);
|
||||
}
|
||||
|
||||
Err(UsedByFieldParseError)
|
||||
}
|
||||
|
||||
fn to_bytes<'b>(&'b self) -> Cow<'b, [u8]> {
|
||||
Cow::Borrowed(b"used-by")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathNode<'a> for CommandName<'a> {
|
||||
const NAME: &'static [u8] = b"command";
|
||||
}
|
||||
|
||||
impl<'a> PathNode<'a> for DeckId<'a> {
|
||||
const NAME: &'static [u8] = b"deck-id";
|
||||
}
|
||||
|
||||
impl<'a> PathNode<'a> for Key {
|
||||
const NAME: &'static [u8] = b"key";
|
||||
}
|
||||
|
||||
impl<'a> PathNode<'a> for UsedBy {
|
||||
const NAME: &'static [u8] = b"used";
|
||||
}
|
||||
|
||||
impl<'a> PathField<'a> for CommandField {}
|
||||
impl<'a> PathField<'a> for NameField {}
|
||||
impl<'a> PathField<'a> for UsedByField {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use path_gen::test::{test_field, test_pathnode};
|
||||
|
||||
use super::{CommandField, CommandName, DeckId, Key, NameField, UsedBy, UsedByField};
|
||||
|
||||
#[test]
|
||||
fn test_path_components() {
|
||||
test_pathnode::<_, DeckId, _>(DeckId::new("test"));
|
||||
test_pathnode::<_, CommandName, _>(CommandName::new("test"));
|
||||
test_pathnode::<_, Key, _>(Key::new(5));
|
||||
test_pathnode::<_, UsedBy, _>(UsedBy);
|
||||
|
||||
test_field::<_, CommandField, _>(CommandField);
|
||||
test_field::<_, NameField, _>(NameField);
|
||||
test_field::<_, UsedByField, _>(UsedByField);
|
||||
}
|
||||
}
|
||||
}
|
50
src/response.rs
Normal file
50
src/response.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
items: Vec<ResponseItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ResponseItem {
|
||||
CurrentScene {
|
||||
name: String,
|
||||
},
|
||||
SceneList {
|
||||
scenes: Vec<String>,
|
||||
},
|
||||
SceneItems {
|
||||
scene: String,
|
||||
items: Vec<SceneItem>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Response {
|
||||
pub(crate) fn insert(&mut self, item: ResponseItem) {
|
||||
self.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct SceneItem {
|
||||
name: String,
|
||||
id: i64,
|
||||
ty: String,
|
||||
visible: bool,
|
||||
children: Vec<SceneItem>,
|
||||
}
|
||||
|
||||
impl SceneItem {
|
||||
pub(crate) fn translate(scene_item: obws::common::SceneItem) -> Self {
|
||||
SceneItem {
|
||||
name: scene_item.name,
|
||||
id: scene_item.id,
|
||||
ty: scene_item.ty,
|
||||
visible: scene_item.render,
|
||||
children: scene_item
|
||||
.group_children
|
||||
.into_iter()
|
||||
.map(Self::translate)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue