diff --git a/src/api.rs b/src/api.rs index 1d84b18..899c8c9 100644 --- a/src/api.rs +++ b/src/api.rs @@ -4,6 +4,7 @@ use chrono::format::ParseError; use chrono::NaiveDateTime; use minreq::Response; use serde::Deserialize; +use std::fmt::{Display, Formatter}; use std::sync::mpsc; use std::sync::mpsc::Receiver; use std::thread; @@ -31,6 +32,50 @@ pub struct TagModel { pub nfc_id: Option, } +impl TagModel { + pub fn get_nick(&self, config: &Config) -> Option { + if let Some(id) = &self.id { + if let Some(n) = config.nicknames.get(id) { + Some(n.clone()) + } else { + None + } + } else { + None + } + } +} + +impl Display for TagModel { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + let mut text = String::new(); + text += &format!( + " id = {}", + self.id.as_ref().unwrap_or(&"unknown".to_owned()) + ); + text += &format!( + "\n imei = {}", + self.imei.as_ref().unwrap_or(&"unknown".to_owned()) + ); + text += &format!( + "\n battery value = {}%", + &self + .battery_value + .map(|v| v.to_string()) + .unwrap_or("unknown".to_owned()) + ); + text += &format!( + "\n bluetooth id = {}", + &self.bt_id.as_ref().unwrap_or(&"unknown".to_owned()) + ); + text += &format!( + "\n NFC id = {}", + &self.nfc_id.as_ref().unwrap_or(&"unknown".to_owned()) + ); + write!(f, "{}", text) + } +} + #[derive(Deserialize, Debug, Clone)] pub struct StateModel { pub id: Option, diff --git a/src/cmd.rs b/src/cmd.rs index 1bb8f85..ef65da5 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -4,24 +4,6 @@ use std::path::PathBuf; #[derive(FromArgs)] #[argh(description = "Tool for gathering location data from Yepzon servers.")] pub struct EnvOpt { - #[argh(subcommand)] - pub subcommand: Subcommand, -} - -#[derive(FromArgs)] -#[argh(subcommand)] -pub enum Subcommand { - Since(SinceOpt), - Init(InitOpt), -} - -#[derive(FromArgs)] -#[argh( - subcommand, - name = "since", - description = "fetch locations for a given section in time.\nsince and until in format dd.mm.yyy, OR hh:mm:ss, OR dd.mm.yyy-hh:mm:ss" -)] -pub struct SinceOpt { #[argh( option, short = 'c', @@ -29,8 +11,29 @@ pub struct SinceOpt { default = "PathBuf::from(\"config.toml\")" )] pub config: PathBuf, + #[argh(subcommand)] + pub subcommand: Subcommand, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +pub enum Subcommand { + Between(BetweenOpt), + Init(InitOpt), + Nick(NickOpt), +} + +#[derive(FromArgs)] +#[argh( + subcommand, + name = "between", + description = "fetch locations for a given section in time.\n nickname, or index from nick list.\nsince and until in format dd.mm.yyy, OR hh:mm:ss, OR dd.mm.yyy-hh:mm:ss" +)] +pub struct BetweenOpt { + #[argh(positional, description = "tag to be fetched")] + pub tag: String, #[argh( - positional, + option, short = 's', description = "the oldest point in time to get locations from." )] @@ -46,3 +49,38 @@ pub struct SinceOpt { #[derive(FromArgs)] #[argh(subcommand, name = "init", description = "initializes a config file")] pub struct InitOpt {} + +#[derive(FromArgs)] +#[argh(subcommand, name = "nick", description = "provides nicknaming to tags")] +pub struct NickOpt { + #[argh(subcommand)] + pub subcommand: NickSub, +} + +#[derive(FromArgs)] +#[argh(subcommand)] +pub enum NickSub { + List(NickListOpt), + Set(NickSetOpt), +} + +#[derive(FromArgs)] +#[argh( + subcommand, + name = "list", + description = "List all tags and their current nicknames" +)] +pub struct NickListOpt {} + +#[derive(FromArgs)] +#[argh( + subcommand, + name = "set", + description = "Set nickname for given tag index. see index with nick list" +)] +pub struct NickSetOpt { + #[argh(positional, description = "tag index, see index with nick list")] + pub index: i32, + #[argh(positional, description = "the new nickname for the tag")] + pub nickname: String, +} diff --git a/src/config.rs b/src/config.rs index 8e7c7dc..0fd9714 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,9 @@ +use super::{GenericError, MessagedError}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fs::File; +use std::io::prelude::*; +use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { @@ -14,6 +19,27 @@ pub struct Config { pub time_format: String, pub throttle: u32, + + pub nicknames: HashMap, +} + +impl Config { + pub fn from_path(path: &PathBuf) -> Result { + let mut file = File::open(&path) + .with_msg(format!("Could not find {}", path.to_str().unwrap_or("")))?; + let mut string = String::new(); + file.read_to_string(&mut string) + .with_msg("config file is not valid UTF-8")?; + + Ok(toml::from_str(&string).with_msg("given config file is not a valid config file")?) + } + + pub fn write_to(&self, path: &PathBuf) -> Result<(), GenericError> { + let mut file = File::create(&path).unwrap(); + file.write_all(&toml::to_vec(self).unwrap()) + .with_msg("Could not write config.toml, make sure you have correct permissions.")?; + Ok(()) + } } impl Default for Config { @@ -32,6 +58,8 @@ impl Default for Config { time_format: "%H:%M:%S".to_owned(), throttle: 9, + + nicknames: HashMap::new(), } } } diff --git a/src/errors.rs b/src/errors.rs index 8a31bdf..8329066 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -10,7 +10,7 @@ pub enum GenericError { ChronoParseError(chrono::ParseError), IOError(io::Error), FromUTF8Error(std::string::FromUtf8Error), - MessagedError(String, Box), + MessagedError(String, Option>), } impl From for GenericError { @@ -61,7 +61,11 @@ impl Display for GenericError { "Yepzon server error: {}", e.message.as_ref().unwrap_or(&String::new()) ), - GenericError::MessagedError(msg, err) => format!("{}\n {}", msg, err), + GenericError::MessagedError(msg, err) => format!( + "{}\n {}", + msg, + err.as_ref().map(|e| e.to_string()).unwrap_or(String::new()) + ), }; write!(f, "{}", err) } @@ -75,7 +79,10 @@ impl> MessagedError for Result { fn with_msg>(self, text: T) -> Result { match self { Ok(ok) => Ok(ok), - Err(e) => Err(GenericError::MessagedError(text.into(), Box::new(e.into()))), + Err(e) => Err(GenericError::MessagedError( + text.into(), + Some(Box::new(e.into())), + )), } } } diff --git a/src/main.rs b/src/main.rs index 0358716..230368f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,12 @@ mod cmd; mod config; mod errors; -use api::API; +use api::{LocationModel, TagModel, API}; use chrono::offset::Local; use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError}; use cmd::*; use config::Config; use errors::{GenericError, MessagedError}; -use std::fs::File; use std::io::prelude::*; use std::sync::mpsc::TryRecvError; use std::time::Duration; @@ -24,55 +23,92 @@ fn main() { fn from_env(env: EnvOpt) -> Result<(), GenericError> { match env.subcommand { - Subcommand::Since(opt) => { - let mut file = File::open(&opt.config).with_msg(format!( - "Could not find {}", - opt.config.to_str().unwrap_or("") - ))?; - let mut string = String::new(); - file.read_to_string(&mut string) - .with_msg("config file is not valid UTF-8")?; + Subcommand::Between(opt) => { + let config = Config::from_path(&env.config)?; - let config: Config = - toml::from_str(&string).with_msg("given config file is not a valid config file")?; - - let since = Some(try_get_datetime(opt.since, &config)?); + let since = + Some(try_get_datetime(opt.since, &config).with_msg("Failed to parse since")?); let until = match opt.until { - Some(until) => Some(try_get_datetime(until, &config)?), + Some(until) => { + Some(try_get_datetime(until, &config).with_msg("Failed to parse until")?) + } None => None, }; - run(&config, since, until)?; + let locs = run(&config, opt.tag, since, until)?; + dbg!( + &locs.iter().map(|loc| loc.0).collect::>(), + locs.len(), + ); Ok(()) } Subcommand::Init(_) => { let config = Config::default(); - - let mut file = File::create("config.toml").unwrap(); - file.write_all(&toml::to_vec(&config).unwrap()) - .with_msg("Could not write config.toml, make sure you have correct permissions.")?; + config.write_to(&env.config)?; Ok(()) } + Subcommand::Nick(opt) => { + let mut config = Config::from_path(&env.config)?; + let mut api = API::new(config.clone()); + + let tags = api.get_tags()?; + + match opt.subcommand { + NickSub::List(_) => { + for (idx, tag) in tags.iter().enumerate() { + println!( + "{}. {}\n{}", + idx + 1, + tag.get_nick(&config) + .unwrap_or("No current nick".to_owned()), + tag + ); + } + Ok(()) + } + NickSub::Set(opt) => { + if let Some(tag) = tags.get((opt.index - 1).max(0) as usize) { + if let Some(id) = &tag.id { + config.nicknames.insert(id.clone(), opt.nickname); + config.write_to(&env.config)?; + Ok(()) + } else { + Err(GenericError::MessagedError( + format!("Tag with index {} does not have an id", opt.index), + None, + )) + } + } else { + Err(GenericError::MessagedError( + format!("Could not find tag with index {}", opt.index), + None, + )) + } + } + } + } } } fn run( config: &Config, + tag_str: String, from: Option, to: Option, -) -> Result<(), GenericError> { +) -> Result, GenericError> { let mut api = API::new(config.clone()); let tags = api.get_tags()?; - let first_tag = tags[0].id.clone().unwrap(); - let state_list = api.get_states(&first_tag)?; + let tag = find_tag(tag_str, &tags, config)?; + + let state_list = api.get_states(&tag)?; let states = API::get_between(&state_list, from, to, true, &config); let mut locations = Vec::new(); for (_, state) in states.iter() { - api.queue_location(&first_tag, state.id.as_ref().unwrap()); + api.queue_location(&tag, state.id.as_ref().unwrap()); } let receiver = api.begin_location_fetch(); let mut counter = 0; @@ -96,7 +132,7 @@ fn run( ), } } - Err(e) => eprintln!("{:?}", e), + Err(e) => eprintln!("{}", e), }, Err(e) => { if let TryRecvError::Disconnected = e { @@ -109,23 +145,7 @@ fn run( locations.sort_by(|loc1, loc2| loc2.timestamp.cmp(&loc1.timestamp)); let locs = API::get_between(&locations, from, to, false, &config); - dbg!( - &locs.iter().map(|loc| loc.0).collect::>(), - locs.len(), - locations.len() - ); - Ok(()) -} - -fn get_opt(option: Option>) -> Result, B> { - if let Some(res) = option { - match res { - Ok(a) => Ok(Some(a)), - Err(b) => Err(b), - } - } else { - Ok(None) - } + Ok(locs) } fn exp_time(api: &API, reqs_left: u64) -> Duration { @@ -145,3 +165,39 @@ fn try_get_datetime(parsed: String, config: &Config) -> Result, + config: &Config, +) -> Result { + let mut tag = None; + if let Ok(num) = tag_str.parse::() { + if let Some(found) = tags.get((num - 1).max(0) as usize) { + tag = Some(&*found); + } + } + for curr in tags.iter() { + if let Some(nick) = curr.get_nick(config) { + if nick == tag_str { + tag = Some(curr); + } + } + } + match tag { + Some(t) => match &t.id { + Some(id) => Ok(id.to_string()), + None => Err(GenericError::MessagedError( + format!( + "Could not find tag {} id. Error probably on Yetzon server side.", + tag_str + ), + None, + )), + }, + None => Err(GenericError::MessagedError( + format!("Could not find tag {}", tag_str), + None, + )), + } +}