Add nicknames

This commit is contained in:
Sofia 2020-08-24 19:50:29 +03:00
parent 36b2051d4f
commit 0ba80a0578
5 changed files with 238 additions and 64 deletions

View File

@ -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<String>,
}
impl TagModel {
pub fn get_nick(&self, config: &Config) -> Option<String> {
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<String>,

View File

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

View File

@ -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<String, String>,
}
impl Config {
pub fn from_path(path: &PathBuf) -> Result<Config, GenericError> {
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(),
}
}
}

View File

@ -10,7 +10,7 @@ pub enum GenericError {
ChronoParseError(chrono::ParseError),
IOError(io::Error),
FromUTF8Error(std::string::FromUtf8Error),
MessagedError(String, Box<GenericError>),
MessagedError(String, Option<Box<GenericError>>),
}
impl From<toml::de::Error> 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<A, B: Into<GenericError>> MessagedError<A> for Result<A, B> {
fn with_msg<T: Into<String>>(self, text: T) -> Result<A, GenericError> {
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())),
)),
}
}
}

View File

@ -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::<Vec<NaiveDateTime>>(),
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<NaiveDateTime>,
to: Option<NaiveDateTime>,
) -> Result<(), GenericError> {
) -> Result<Vec<(NaiveDateTime, LocationModel)>, 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::<Vec<NaiveDateTime>>(),
locs.len(),
locations.len()
);
Ok(())
}
fn get_opt<A, B>(option: Option<Result<A, B>>) -> Result<Option<A>, 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<NaiveDateTime, Pa
},
}
}
fn find_tag(
tag_str: String,
tags: &Vec<TagModel>,
config: &Config,
) -> Result<String, GenericError> {
let mut tag = None;
if let Ok(num) = tag_str.parse::<i32>() {
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,
)),
}
}