use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::process::exit; use std::sync::Arc; use anyhow::{anyhow, Result}; use crate::modules::{InitializationContext, ModuleContext, ModuleContextMqtt}; use crate::mqtt::OwnedTopicsService; use crate::util::generate_alphanumeric_id; mod config; mod modules; mod mqtt; mod util; struct Paths { data_directory: Box, config: Box, } async fn get_paths_and_create_directories() -> Result { let project_dirs = directories::ProjectDirs::from("", "", "Hassliebe"); let paths = Paths { config: std::env::var_os("HASS_CONFIG") .map(PathBuf::from) // I don’t like this clone .or(project_dirs.clone().map(|d| d.config_dir().to_path_buf().join("config.toml"))) .ok_or_else(|| anyhow!("Please specify a config file via HASS_CONFIG"))? .into_boxed_path(), data_directory: std::env::var_os("HASS_DATA_DIR") .map(PathBuf::from) .or(project_dirs.map(|d| d.data_dir().to_path_buf())) .ok_or_else(|| anyhow!("Please specify a data directory via HASS_DATA_DIR"))? .into_boxed_path(), }; if let Some(p) = paths.config.parent() { tokio::fs::create_dir_all(p).await?; } tokio::fs::create_dir_all(&paths.data_directory).await?; Ok(paths) } fn initialize_logger() { let mut builder = env_logger::builder(); builder.parse_filters("warn,hassliebe=info"); builder.parse_default_env(); builder.init(); } async fn load_machine_id(paths: &Paths) -> Result { let overwrite_path = paths.data_directory.join("machine_id"); let mut value = tokio::fs::read_to_string(&overwrite_path).await.unwrap_or("".to_owned()).trim().to_owned(); if value.is_empty() { log::debug!("{} does not exist or is empty", overwrite_path.to_string_lossy()); match tokio::fs::read_to_string("/etc/machine-id").await { Ok(id) => { value = id.trim().to_owned(); log::debug!("Found machine ID in /etc/machine-id: {}", value); } Err(_) => { log::debug!("Could not read /etc/machine-id"); value = generate_alphanumeric_id(32); log::info!("Generated new machine ID: {}", value); tokio::fs::write(overwrite_path, &value).await?; } } } else { log::debug!("Found machine ID in {}: {}", overwrite_path.to_string_lossy(), value); } Ok(value) } #[tokio::main] async fn main() -> Result<()> { initialize_logger(); let paths = get_paths_and_create_directories().await?; let machine_id = load_machine_id(&paths).await?; let Some(config) = config::load(&paths.config)? else { exit(exitcode::CONFIG); }; let availability_topic = config.friendly_id.to_owned() + "/availability"; let (mqtt_client, event_loop) = mqtt::create_client(&config, &machine_id, &availability_topic).await?; let discovery_device_object = mqtt::create_discovery_device_object(&config, &machine_id); let owned_topics_service = OwnedTopicsService::new(&paths.data_directory).await?; let mut initialization_context = InitializationContext { owned_mqtt_topics: HashSet::new(), message_handler_by_mqtt_topic: HashMap::new(), full: Arc::new(ModuleContext { config, machine_id, mqtt: ModuleContextMqtt { client: mqtt_client, availability_topic, discovery_device_object, }, // dbus_session_connection: zbus::Connection::session().await.context("while connecting to the D-Bus session bus")? }), }; modules::init_all(&mut initialization_context).await?; mqtt::start_communication(&initialization_context, event_loop, owned_topics_service).await }