Add config
This commit is contained in:
parent
a48b5c3f9c
commit
01a9671f2f
10 changed files with 193 additions and 20 deletions
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -114,6 +114,26 @@ version = "0.8.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.31"
|
||||
|
@ -271,6 +291,17 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.13"
|
||||
|
@ -351,6 +382,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dirs",
|
||||
"eyre",
|
||||
"fern",
|
||||
"futures",
|
||||
|
@ -359,6 +391,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -742,6 +775,17 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
|
@ -1030,6 +1074,26 @@ version = "0.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
|
@ -1122,6 +1186,15 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.1"
|
||||
|
|
|
@ -15,4 +15,6 @@ chrono = "0.4"
|
|||
reqwest = { version = "0.11", features = ["json", "rustls-tls-native-roots", "native-tls"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
clap = { version = "3.1.8", features = ["derive"] }
|
||||
clap = { version = "3.1.8", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
dirs = "4.0"
|
|
@ -3,11 +3,12 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::api::models::lights::{Light, On};
|
||||
use crate::api::models::Response;
|
||||
use crate::config::Config;
|
||||
use crate::{api, ToggleState};
|
||||
|
||||
pub async fn all() -> Result<Vec<Light>> {
|
||||
let request_url = format!("https://{}/clip/v2/resource/light", api::HUB_IP);
|
||||
let client = api::get_client()?;
|
||||
pub async fn all(config: Config) -> Result<Vec<Light>> {
|
||||
let request_url = format!("https://{}/clip/v2/resource/light", config.hostname);
|
||||
let client = api::get_client(&config)?;
|
||||
|
||||
match client.get(request_url).send().await {
|
||||
Ok(response) => Ok(response.json::<Response<Light>>().await?.data),
|
||||
|
@ -29,16 +30,16 @@ pub struct LightStatePut {
|
|||
on: On,
|
||||
}
|
||||
|
||||
pub async fn toggle(id: String, state: ToggleState) -> Result<()> {
|
||||
let request_url = format!("https://{}/clip/v2/resource/light/{}", api::HUB_IP, id);
|
||||
let client = api::get_client()?;
|
||||
pub async fn toggle(config: Config, id: String, state: ToggleState) -> Result<()> {
|
||||
let request_url = format!("https://{}/clip/v2/resource/light/{}", config.hostname, id);
|
||||
let client = api::get_client(&config)?;
|
||||
|
||||
let target_state: LightStatePut = LightStatePut {
|
||||
on: On {
|
||||
on: match state {
|
||||
ToggleState::On => true,
|
||||
ToggleState::Off => false,
|
||||
ToggleState::Swap => match all().await?.into_iter().find(|x| x.id == id) {
|
||||
ToggleState::Swap => match all(config).await?.into_iter().find(|x| x.id == id) {
|
||||
Some(light) => !light.on.on,
|
||||
None => true,
|
||||
},
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
pub mod lights;
|
||||
pub mod models;
|
||||
|
||||
use crate::config::Config;
|
||||
use eyre::Result;
|
||||
use reqwest::{header, Client};
|
||||
|
||||
const HUB_IP: &'static str = "10.60.0.163";
|
||||
const HUE_TOKEN: &'static str = "sNLQz7sOVfji2VA42jeLwhx2rnwBCkYu-OVc2ddw";
|
||||
|
||||
pub fn get_client() -> Result<Client> {
|
||||
pub fn get_client(config: &Config) -> Result<Client> {
|
||||
let mut headers = header::HeaderMap::new();
|
||||
headers.insert(
|
||||
"hue-application-key",
|
||||
header::HeaderValue::from_static(HUE_TOKEN),
|
||||
header::HeaderValue::from_str(config.token.as_str())?,
|
||||
);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.use_native_tls()
|
||||
.danger_accept_invalid_certs(true)
|
||||
.default_headers(headers)
|
||||
.timeout(Duration::from_secs(3))
|
||||
.build()?;
|
||||
|
||||
Ok(client)
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use eyre::Result;
|
||||
|
||||
use crate::api;
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn exec() -> Result<()> {
|
||||
let lights = api::lights::all().await?;
|
||||
pub async fn exec(config: Config) -> Result<()> {
|
||||
let lights = api::lights::all(config).await?;
|
||||
|
||||
for light in lights {
|
||||
println!("{} - {}", light.id, light.metadata.name)
|
||||
let icon = if light.on.on { "💡" } else { "🔌" };
|
||||
println!("{} - {} - {}", icon, light.id, light.metadata.name)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use eyre::Result;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::{api, ToggleState};
|
||||
|
||||
pub async fn exec(id: &String, state: &Option<String>) -> Result<()> {
|
||||
pub async fn exec(config: Config, id: &String, state: &Option<String>) -> Result<()> {
|
||||
let target_state = if let Some(target) = &state {
|
||||
ToggleState::from_str(target.as_str()).unwrap()
|
||||
} else {
|
||||
ToggleState::Swap
|
||||
};
|
||||
|
||||
api::lights::toggle(id.clone(), target_state).await?;
|
||||
api::lights::toggle(config, id.clone(), target_state).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
33
src/config/config_file.rs
Normal file
33
src/config/config_file.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use eyre::Result;
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ConfigFile {
|
||||
pub token: Option<String>,
|
||||
pub host: Option<Host>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Host {
|
||||
pub hostname: String,
|
||||
}
|
||||
|
||||
pub async fn read() -> Result<Option<ConfigFile>> {
|
||||
let home_dir = match dirs::home_dir() {
|
||||
Some(h) => h,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let config_file_path = home_dir.join(Path::new(".hue-control.toml"));
|
||||
|
||||
let content = match fs::read_to_string(config_file_path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
|
||||
let config: ConfigFile = toml::from_str(&content)?;
|
||||
|
||||
Ok(Some(config))
|
||||
}
|
1
src/config/environment.rs
Normal file
1
src/config/environment.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
59
src/config/mod.rs
Normal file
59
src/config/mod.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
mod config_file;
|
||||
mod environment;
|
||||
|
||||
use eyre::{eyre, Result};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub hostname: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
pub async fn resolve() -> Result<Config> {
|
||||
let mut config = Config {
|
||||
hostname: "".to_string(),
|
||||
token: "".to_string(),
|
||||
};
|
||||
|
||||
let config_file = config_file::read().await?;
|
||||
if let Some(c) = config_file {
|
||||
if let Some(token) = c.token {
|
||||
config.token = token;
|
||||
}
|
||||
if let Some(host) = c.host {
|
||||
config.hostname = host.hostname
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(token) = std::env::var("HUE_TOKEN") {
|
||||
config.token = token;
|
||||
}
|
||||
|
||||
if let Ok(host) = std::env::var("HUE_HOSTNAME") {
|
||||
config.hostname = host;
|
||||
}
|
||||
|
||||
println!("{:?}", config);
|
||||
|
||||
if let Err(e) = validate_config(&config) {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn validate_config(config: &Config) -> Result<()> {
|
||||
if config.token.is_empty() {
|
||||
return Err(eyre!("Token config variable is empty"));
|
||||
}
|
||||
|
||||
if config.token.len() != 40 || !config.token.is_ascii() {
|
||||
return Err(eyre!("Token format is invalid"));
|
||||
}
|
||||
|
||||
if config.hostname.is_empty() {
|
||||
return Err(eyre!("hostname is empty"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
mod api;
|
||||
mod args;
|
||||
mod commands;
|
||||
mod config;
|
||||
|
||||
use crate::args::ToggleState;
|
||||
use clap::Parser;
|
||||
|
@ -9,13 +10,14 @@ use eyre::Result;
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let args = args::Args::parse();
|
||||
let config = config::resolve().await?;
|
||||
|
||||
match &args.command {
|
||||
args::Commands::Toggle { id, state } => {
|
||||
commands::toggle::exec(id, state).await?;
|
||||
commands::toggle::exec(config, id, state).await?;
|
||||
}
|
||||
args::Commands::List => {
|
||||
commands::list::exec().await?;
|
||||
commands::list::exec(config).await?;
|
||||
}
|
||||
args::Commands::Inspect => {
|
||||
panic!("Not implemented");
|
||||
|
|
Loading…
Reference in a new issue