Add config

This commit is contained in:
Daan Boerlage 2022-04-09 22:51:17 +02:00
parent a48b5c3f9c
commit 01a9671f2f
Signed by: daan
GPG key ID: FCE070E1E4956606
10 changed files with 193 additions and 20 deletions

73
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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,
},

View file

@ -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)

View file

@ -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(())

View file

@ -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
View 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))
}

View file

@ -0,0 +1 @@

59
src/config/mod.rs Normal file
View 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(())
}

View file

@ -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");