Add nicknames
This commit is contained in:
parent
36b2051d4f
commit
0ba80a0578
45
src/api.rs
45
src/api.rs
@ -4,6 +4,7 @@ use chrono::format::ParseError;
|
|||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use minreq::Response;
|
use minreq::Response;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@ -31,6 +32,50 @@ pub struct TagModel {
|
|||||||
pub nfc_id: Option<String>,
|
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)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct StateModel {
|
pub struct StateModel {
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
|
76
src/cmd.rs
76
src/cmd.rs
@ -4,24 +4,6 @@ use std::path::PathBuf;
|
|||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
#[argh(description = "Tool for gathering location data from Yepzon servers.")]
|
#[argh(description = "Tool for gathering location data from Yepzon servers.")]
|
||||||
pub struct EnvOpt {
|
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(
|
#[argh(
|
||||||
option,
|
option,
|
||||||
short = 'c',
|
short = 'c',
|
||||||
@ -29,8 +11,29 @@ pub struct SinceOpt {
|
|||||||
default = "PathBuf::from(\"config.toml\")"
|
default = "PathBuf::from(\"config.toml\")"
|
||||||
)]
|
)]
|
||||||
pub config: PathBuf,
|
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(
|
#[argh(
|
||||||
positional,
|
option,
|
||||||
short = 's',
|
short = 's',
|
||||||
description = "the oldest point in time to get locations from."
|
description = "the oldest point in time to get locations from."
|
||||||
)]
|
)]
|
||||||
@ -46,3 +49,38 @@ pub struct SinceOpt {
|
|||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
#[argh(subcommand, name = "init", description = "initializes a config file")]
|
#[argh(subcommand, name = "init", description = "initializes a config file")]
|
||||||
pub struct InitOpt {}
|
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,
|
||||||
|
}
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
|
use super::{GenericError, MessagedError};
|
||||||
use serde::{Deserialize, Serialize};
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@ -14,6 +19,27 @@ pub struct Config {
|
|||||||
pub time_format: String,
|
pub time_format: String,
|
||||||
|
|
||||||
pub throttle: u32,
|
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 {
|
impl Default for Config {
|
||||||
@ -32,6 +58,8 @@ impl Default for Config {
|
|||||||
time_format: "%H:%M:%S".to_owned(),
|
time_format: "%H:%M:%S".to_owned(),
|
||||||
|
|
||||||
throttle: 9,
|
throttle: 9,
|
||||||
|
|
||||||
|
nicknames: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ pub enum GenericError {
|
|||||||
ChronoParseError(chrono::ParseError),
|
ChronoParseError(chrono::ParseError),
|
||||||
IOError(io::Error),
|
IOError(io::Error),
|
||||||
FromUTF8Error(std::string::FromUtf8Error),
|
FromUTF8Error(std::string::FromUtf8Error),
|
||||||
MessagedError(String, Box<GenericError>),
|
MessagedError(String, Option<Box<GenericError>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<toml::de::Error> for GenericError {
|
impl From<toml::de::Error> for GenericError {
|
||||||
@ -61,7 +61,11 @@ impl Display for GenericError {
|
|||||||
"Yepzon server error: {}",
|
"Yepzon server error: {}",
|
||||||
e.message.as_ref().unwrap_or(&String::new())
|
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)
|
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> {
|
fn with_msg<T: Into<String>>(self, text: T) -> Result<A, GenericError> {
|
||||||
match self {
|
match self {
|
||||||
Ok(ok) => Ok(ok),
|
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())),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
140
src/main.rs
140
src/main.rs
@ -3,13 +3,12 @@ mod cmd;
|
|||||||
mod config;
|
mod config;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
|
||||||
use api::API;
|
use api::{LocationModel, TagModel, API};
|
||||||
use chrono::offset::Local;
|
use chrono::offset::Local;
|
||||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
|
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
|
||||||
use cmd::*;
|
use cmd::*;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use errors::{GenericError, MessagedError};
|
use errors::{GenericError, MessagedError};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::sync::mpsc::TryRecvError;
|
use std::sync::mpsc::TryRecvError;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -24,55 +23,92 @@ fn main() {
|
|||||||
|
|
||||||
fn from_env(env: EnvOpt) -> Result<(), GenericError> {
|
fn from_env(env: EnvOpt) -> Result<(), GenericError> {
|
||||||
match env.subcommand {
|
match env.subcommand {
|
||||||
Subcommand::Since(opt) => {
|
Subcommand::Between(opt) => {
|
||||||
let mut file = File::open(&opt.config).with_msg(format!(
|
let config = Config::from_path(&env.config)?;
|
||||||
"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")?;
|
|
||||||
|
|
||||||
let config: Config =
|
let since =
|
||||||
toml::from_str(&string).with_msg("given config file is not a valid config file")?;
|
Some(try_get_datetime(opt.since, &config).with_msg("Failed to parse since")?);
|
||||||
|
|
||||||
let since = Some(try_get_datetime(opt.since, &config)?);
|
|
||||||
|
|
||||||
let until = match opt.until {
|
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,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Subcommand::Init(_) => {
|
Subcommand::Init(_) => {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
config.write_to(&env.config)?;
|
||||||
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.")?;
|
|
||||||
Ok(())
|
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(
|
fn run(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
|
tag_str: String,
|
||||||
from: Option<NaiveDateTime>,
|
from: Option<NaiveDateTime>,
|
||||||
to: Option<NaiveDateTime>,
|
to: Option<NaiveDateTime>,
|
||||||
) -> Result<(), GenericError> {
|
) -> Result<Vec<(NaiveDateTime, LocationModel)>, GenericError> {
|
||||||
let mut api = API::new(config.clone());
|
let mut api = API::new(config.clone());
|
||||||
|
|
||||||
let tags = api.get_tags()?;
|
let tags = api.get_tags()?;
|
||||||
let first_tag = tags[0].id.clone().unwrap();
|
let tag = find_tag(tag_str, &tags, config)?;
|
||||||
let state_list = api.get_states(&first_tag)?;
|
|
||||||
|
let state_list = api.get_states(&tag)?;
|
||||||
|
|
||||||
let states = API::get_between(&state_list, from, to, true, &config);
|
let states = API::get_between(&state_list, from, to, true, &config);
|
||||||
|
|
||||||
let mut locations = Vec::new();
|
let mut locations = Vec::new();
|
||||||
for (_, state) in states.iter() {
|
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 receiver = api.begin_location_fetch();
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
@ -96,7 +132,7 @@ fn run(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("{:?}", e),
|
Err(e) => eprintln!("{}", e),
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let TryRecvError::Disconnected = e {
|
if let TryRecvError::Disconnected = e {
|
||||||
@ -109,23 +145,7 @@ fn run(
|
|||||||
locations.sort_by(|loc1, loc2| loc2.timestamp.cmp(&loc1.timestamp));
|
locations.sort_by(|loc1, loc2| loc2.timestamp.cmp(&loc1.timestamp));
|
||||||
|
|
||||||
let locs = API::get_between(&locations, from, to, false, &config);
|
let locs = API::get_between(&locations, from, to, false, &config);
|
||||||
dbg!(
|
Ok(locs)
|
||||||
&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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exp_time(api: &API, reqs_left: u64) -> Duration {
|
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,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user