Separate lib and ux

This commit is contained in:
Sofia 2020-08-29 02:27:23 +03:00
parent 2b835a72b0
commit b1b8ac417b
12 changed files with 196 additions and 139 deletions

5
.gitignore vendored
View File

@ -1,3 +1,6 @@
/target
secret.toml
*.gpx
*.gpx
*.code-workspace
/thingy_lib/Cargo.lock
/thingy_lib/target/

9
Cargo.lock generated
View File

@ -371,9 +371,16 @@ name = "thingy"
version = "0.1.0"
dependencies = [
"argh",
"chrono",
"geo-types",
"gpx",
"thingy-lib",
]
[[package]]
name = "thingy-lib"
version = "0.1.0"
dependencies = [
"chrono",
"minreq",
"serde",
"toml",

View File

@ -5,10 +5,12 @@ authors = ["Teascade <teascade@gmail.com>"]
edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"
minreq = { version = "2.2.0", features = ["https", "json-using-serde"] }
chrono = "0.4"
argh = "0.1"
gpx = "0.8"
geo-types = "*"
geo-types = "*"
thingy-lib = { path = "./thingy_lib/" }
[target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]
[target.i686-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]

View File

@ -1,52 +1,15 @@
use super::api::ErrorModel;
use std::fmt::{Display, Formatter};
use std::io;
#[derive(Debug)]
pub enum GenericError {
TomlError(toml::de::Error),
YepzonServerError(ErrorModel),
MinreqError(minreq::Error),
ChronoParseError(chrono::ParseError),
IOError(io::Error),
FromUTF8Error(std::string::FromUtf8Error),
ThingyLibError(thingy_lib::Error),
GPXError(gpx::errors::Error),
MessagedError(String, Option<Box<GenericError>>),
}
impl From<toml::de::Error> for GenericError {
fn from(error: toml::de::Error) -> Self {
GenericError::TomlError(error)
}
}
impl From<minreq::Error> for GenericError {
fn from(error: minreq::Error) -> Self {
GenericError::MinreqError(error)
}
}
impl From<chrono::ParseError> for GenericError {
fn from(error: chrono::ParseError) -> Self {
GenericError::ChronoParseError(error)
}
}
impl From<ErrorModel> for GenericError {
fn from(error: ErrorModel) -> Self {
GenericError::YepzonServerError(error)
}
}
impl From<io::Error> for GenericError {
fn from(error: io::Error) -> Self {
GenericError::IOError(error)
}
}
impl From<std::string::FromUtf8Error> for GenericError {
fn from(error: std::string::FromUtf8Error) -> Self {
GenericError::FromUTF8Error(error)
impl From<thingy_lib::Error> for GenericError {
fn from(error: thingy_lib::Error) -> Self {
GenericError::ThingyLibError(error)
}
}
@ -59,21 +22,13 @@ impl From<gpx::errors::Error> for GenericError {
impl Display for GenericError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let err = match self {
GenericError::ChronoParseError(e) => format!("Date-Time value parse error: {}", e),
GenericError::FromUTF8Error(e) => format!("UTF-8 error: {}", e),
GenericError::IOError(e) => format!("IO error: {}", e),
GenericError::MinreqError(e) => format!("Network error: {}", e),
GenericError::TomlError(e) => format!("Toml error: {}", e),
GenericError::YepzonServerError(e) => format!(
"Yepzon server error: {}",
e.message.as_ref().unwrap_or(&String::new())
),
GenericError::ThingyLibError(e) => e.to_string(),
GenericError::GPXError(e) => format!("GPX error: {}", e),
GenericError::MessagedError(msg, err) => format!(
"{}\n {}",
msg,
err.as_ref().map(|e| e.to_string()).unwrap_or(String::new())
),
GenericError::GPXError(e) => format!("GPX error: {}", e),
};
write!(f, "{}", err)
}

View File

@ -1,9 +1,9 @@
use super::{Config, GenericError, LocationModel, TagModel};
use chrono::offset::Utc as UTC_ZONE;
use chrono::{DateTime, NaiveDateTime, Utc};
use geo_types::Point;
use gpx::{Gpx, GpxVersion, Metadata, Person, Route, Track, TrackSegment, Waypoint};
use std::fs::File;
use thingy_lib::chrono::offset::Utc as UTC_ZONE;
use thingy_lib::chrono::{DateTime, NaiveDateTime, Utc};
pub fn generate_gpx(
tag: &TagModel,

View File

@ -1,16 +1,15 @@
mod api;
#![windows_subsystem = "windows"]
mod args;
mod commands;
mod config;
mod errors;
mod gpx;
use api::{LocationModel, TagModel, API};
use args::*;
use chrono::offset::Local;
use chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
use config::Config;
use errors::{GenericError, MessagedError};
use errors::GenericError;
use thingy_lib::api::{LocationModel, TagModel, API};
use thingy_lib::chrono::offset::Local;
use thingy_lib::chrono::{NaiveDate, NaiveDateTime, NaiveTime, ParseError};
use thingy_lib::{Config, MessagedError};
fn main() {
let env: EnvOpt = argh::from_env();
@ -36,7 +35,11 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
None => None,
};
let (_, locs) = commands::between(&mut api, opt.device, since, until)?;
let (tag, locs) = thingy_lib::between(&mut api, opt.device, since, until)?;
let gpx = gpx::generate_gpx(&tag, &locs, &api.config)?;
gpx::write_gpx(&gpx)?;
dbg!(
&locs.iter().map(|loc| loc.0).collect::<Vec<NaiveDateTime>>(),
locs.len(),
@ -44,27 +47,27 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
Ok(())
}
Subcommand::Init(opt) => {
commands::init(&env.config, opt.api_key)?;
thingy_lib::init(&env.config, opt.api_key)?;
Ok(())
}
Subcommand::Api(_) => {
let config = Config::from_path(&env.config)?;
let api = API::new(config.clone());
match commands::check_api(&api) {
match thingy_lib::check_api(&api) {
Ok(_) => {
println!("API verified, no issues");
Ok(())
}
Err(e) => Err(GenericError::MessagedError(
"API integrity failed, or API-key is not valid. ".to_owned(),
Some(Box::new(e)),
Some(Box::new(e.into())),
)),
}
}
Subcommand::ShareToken(opt) => {
let config = Config::from_path(&env.config)?;
let api = API::new(config.clone());
println!("{}", commands::sharetoken(&api, opt.device)?);
println!("{}", thingy_lib::sharetoken(&api, opt.device)?);
Ok(())
}
Subcommand::Nick(opt) => {
@ -73,12 +76,13 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
match opt.subcommand {
NickSub::List(_) => {
for ((idx, nick), tag) in commands::nick_list(&api)? {
for ((idx, nick), tag) in thingy_lib::nick_list(&api)? {
println!("{}. {}\n{}", idx, nick, tag);
}
}
NickSub::Set(opt) => {
commands::nick_set(&mut api, &env.config, opt.device, opt.nickname)?;
thingy_lib::nick_set(&mut api, opt.device, opt.nickname)?;
api.config.write_to(&env.config)?;
}
}
Ok(())
@ -98,10 +102,10 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> {
dbg!(&tags);
}
GetSub::States(opt) => {
API::print_list(commands::get_states(&api, opt.device));
API::print_list(thingy_lib::get_states(&api, opt.device));
}
GetSub::Locations(opt) => {
API::print_list(commands::get_locations(&api, opt.device, opt.state))
API::print_list(thingy_lib::get_locations(&api, opt.device, opt.state))
}
}
Ok(())

11
thingy_lib/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "thingy-lib"
version = "0.1.0"
authors = ["Teascade <teascade@gmail.com>"]
edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"
minreq = { version = "2.2.0", features = ["https", "json-using-serde"] }
chrono = "0.4"

View File

@ -1,7 +1,7 @@
pub mod structs;
use super::Config;
use super::GenericError;
use super::LibError;
use chrono::format::ParseError;
use chrono::NaiveDateTime;
use minreq::Response;
@ -25,15 +25,14 @@ impl API {
}
}
#[allow(dead_code)]
pub fn get_raw_string(&self, url: APIUrl) -> Result<String, GenericError> {
pub fn get_raw_string(&self, url: APIUrl) -> Result<String, LibError> {
Ok(self
.request(API::api_url(url, &self.config))?
.as_str()?
.to_owned())
}
pub fn get_sharetoken(&self, tag_id: &String) -> Result<SharetokenModel, GenericError> {
pub fn get_sharetoken(&self, tag_id: &String) -> Result<SharetokenModel, LibError> {
let response = self.request(API::api_url(
APIUrl::Sharetoken(tag_id.clone()),
&self.config,
@ -41,33 +40,28 @@ impl API {
Ok(response.json()?)
}
#[allow(dead_code)]
pub fn get_tags(&self) -> Result<Vec<TagModel>, GenericError> {
pub fn get_tags(&self) -> Result<Vec<TagModel>, LibError> {
let response = self.request(API::api_url(APIUrl::Tags, &self.config))?;
let tags = response.json();
if let Err(_) = tags {
let err: ErrorModel = response.json()?;
Err(GenericError::from(err))
Err(LibError::from(err))
} else {
tags.map_err(GenericError::from)
tags.map_err(LibError::from)
}
}
#[allow(dead_code)]
pub fn get_tag(&self, tag_id: &String) -> Result<TagModel, GenericError> {
pub fn get_tag(&self, tag_id: &String) -> Result<TagModel, LibError> {
let response = self.request(API::api_url(APIUrl::Tag(tag_id.clone()), &self.config))?;
Ok(response.json()?)
}
pub fn get_states(&self, tag_id: &String) -> Result<Vec<StateModel>, GenericError> {
pub fn get_states(&self, tag_id: &String) -> Result<Vec<StateModel>, LibError> {
let response = self.request(API::api_url(APIUrl::States(tag_id.clone()), &self.config))?;
Ok(response.json()?)
}
pub fn get_current_locations(
&self,
tag_id: &String,
) -> Result<Vec<LocationModel>, GenericError> {
pub fn get_current_locations(&self, tag_id: &String) -> Result<Vec<LocationModel>, LibError> {
let response = self.request(API::api_url(
APIUrl::CurrLocations(tag_id.clone()),
&self.config,
@ -79,7 +73,7 @@ impl API {
&self,
tag_id: &String,
state_id: &String,
) -> Result<Vec<LocationModel>, GenericError> {
) -> Result<Vec<LocationModel>, LibError> {
let response = self.request(API::api_url(
APIUrl::Locations(tag_id.clone(), state_id.clone()),
&self.config,
@ -96,7 +90,7 @@ impl API {
pub fn begin_location_fetch(
&mut self,
) -> Receiver<Result<Result<Vec<LocationModel>, ErrorModel>, GenericError>> {
) -> Receiver<Result<Result<Vec<LocationModel>, ErrorModel>, LibError>> {
let (sender, receiver) = mpsc::channel();
let mut locations = self.location_req_que.clone();
let config = self.config.clone();
@ -139,7 +133,7 @@ impl API {
Ok(loc) => Ok(Ok(loc)),
Err(_) => Ok(Err(r.json()?)),
})
.map_err(GenericError::from);
.map_err(LibError::from);
i_sender.send((location, response)).ok();
});
timer += interval;
@ -169,7 +163,7 @@ impl API {
receiver
}
pub fn get_between<T: Timestamped + Clone>(
pub(crate) fn get_between<T: Timestamped + Clone>(
list: &Vec<T>,
from: Option<NaiveDateTime>,
to: Option<NaiveDateTime>,
@ -210,7 +204,7 @@ impl API {
}
#[allow(dead_code)]
pub fn print_list<T: Display + Timestamped>(list: Result<Vec<T>, GenericError>) {
pub fn print_list<T: Display + Timestamped>(list: Result<Vec<T>, LibError>) {
match list {
Ok(items) => {
for item in items {
@ -243,7 +237,7 @@ impl API {
fn add_error_back(
location: String,
rec: &Result<Result<Vec<LocationModel>, ErrorModel>, GenericError>,
rec: &Result<Result<Vec<LocationModel>, ErrorModel>, LibError>,
locations: &mut Vec<String>,
) -> bool {
if let Ok(inner) = rec {
@ -258,12 +252,12 @@ impl API {
}
}
fn request(&self, url: String) -> Result<Response, GenericError> {
fn request(&self, url: String) -> Result<Response, LibError> {
let response = minreq::get(url)
.with_header("content-type", "application/json")
.with_header("x-api-key", &self.config.api_key)
.send();
response.map_err(GenericError::from)
response.map_err(LibError::from)
}
fn parse_timestamp(

View File

@ -1,4 +1,4 @@
use super::{Config, GenericError};
use super::{Config, LibError};
use serde::Deserialize;
use std::fmt::{Display, Formatter};
@ -77,10 +77,10 @@ impl TagModel {
}
}
pub fn get_id(&self) -> Result<String, GenericError> {
pub fn get_id(&self) -> Result<String, LibError> {
match &self.id {
Some(id) => Ok(id.to_string()),
None => Err(GenericError::MessagedError(
None => Err(LibError::MessagedError(
"Could not find device id. Error probably on Yetzon server side.".to_owned(),
None,
)),
@ -153,10 +153,10 @@ pub struct StateModel {
}
impl StateModel {
pub fn get_id(&self) -> Result<String, GenericError> {
pub fn get_id(&self) -> Result<String, LibError> {
match &self.id {
Some(id) => Ok(id.to_string()),
None => Err(GenericError::MessagedError(
None => Err(LibError::MessagedError(
"Could not find state id. Error probably on Yetzon server side.".to_owned(),
None,
)),

View File

@ -1,4 +1,4 @@
use super::{GenericError, MessagedError};
use super::errors::{LibError, MessagedError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::File;
@ -27,7 +27,7 @@ pub struct Config {
}
impl Config {
pub fn from_path(path: &PathBuf) -> Result<Config, GenericError> {
pub fn from_path(path: &PathBuf) -> Result<Config, LibError> {
let mut file = File::open(&path)
.with_msg(format!("Could not find {}", path.to_str().unwrap_or("")))?;
let mut string = String::new();
@ -37,7 +37,7 @@ impl Config {
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> {
pub fn write_to(&self, path: &PathBuf) -> Result<(), LibError> {
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.")?;

88
thingy_lib/src/errors.rs Normal file
View File

@ -0,0 +1,88 @@
use super::api::ErrorModel;
use std::fmt::{Display, Formatter};
use std::io;
#[derive(Debug)]
pub enum LibError {
TomlError(toml::de::Error),
YepzonServerError(ErrorModel),
MinreqError(minreq::Error),
ChronoParseError(chrono::ParseError),
IOError(io::Error),
FromUTF8Error(std::string::FromUtf8Error),
MessagedError(String, Option<Box<LibError>>),
}
impl From<toml::de::Error> for LibError {
fn from(error: toml::de::Error) -> Self {
LibError::TomlError(error)
}
}
impl From<minreq::Error> for LibError {
fn from(error: minreq::Error) -> Self {
LibError::MinreqError(error)
}
}
impl From<chrono::ParseError> for LibError {
fn from(error: chrono::ParseError) -> Self {
LibError::ChronoParseError(error)
}
}
impl From<ErrorModel> for LibError {
fn from(error: ErrorModel) -> Self {
LibError::YepzonServerError(error)
}
}
impl From<io::Error> for LibError {
fn from(error: io::Error) -> Self {
LibError::IOError(error)
}
}
impl From<std::string::FromUtf8Error> for LibError {
fn from(error: std::string::FromUtf8Error) -> Self {
LibError::FromUTF8Error(error)
}
}
impl Display for LibError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let err = match self {
LibError::ChronoParseError(e) => format!("Date-Time value parse error: {}", e),
LibError::FromUTF8Error(e) => format!("UTF-8 error: {}", e),
LibError::IOError(e) => format!("IO error: {}", e),
LibError::MinreqError(e) => format!("Network error: {}", e),
LibError::TomlError(e) => format!("Toml error: {}", e),
LibError::YepzonServerError(e) => format!(
"Yepzon server error: {}",
e.message.as_ref().unwrap_or(&String::new())
),
LibError::MessagedError(msg, err) => format!(
"{}\n {}",
msg,
err.as_ref().map(|e| e.to_string()).unwrap_or(String::new())
),
};
write!(f, "{}", err)
}
}
pub trait MessagedError<A> {
fn with_msg<T: Into<String>>(self, text: T) -> Result<A, LibError>;
}
impl<A, B: Into<LibError>> MessagedError<A> for Result<A, B> {
fn with_msg<T: Into<String>>(self, text: T) -> Result<A, LibError> {
match self {
Ok(ok) => Ok(ok),
Err(e) => Err(LibError::MessagedError(
text.into(),
Some(Box::new(e.into())),
)),
}
}
}

View File

@ -1,14 +1,21 @@
use super::api::{LocationModel, SharetokenModel, StateModel, TagModel, API};
use super::config::Config;
use super::errors::GenericError;
use super::gpx;
pub mod api;
mod config;
mod errors;
use api::{LocationModel, SharetokenModel, StateModel, TagModel, API};
use chrono::NaiveDateTime;
use errors::LibError;
use std::io::prelude::*;
use std::path::PathBuf;
use std::sync::mpsc::TryRecvError;
use std::time::Duration;
pub fn init(path: &PathBuf, api_key: Option<String>) -> Result<(), GenericError> {
pub use chrono;
pub use config::Config;
pub use errors::LibError as Error;
pub use errors::MessagedError;
pub fn init(path: &PathBuf, api_key: Option<String>) -> Result<(), LibError> {
let mut config = Config::default();
if let Some(api_key) = api_key {
config.api_key = api_key;
@ -21,7 +28,7 @@ pub fn between(
tag_str: String,
from: Option<NaiveDateTime>,
to: Option<NaiveDateTime>,
) -> Result<(TagModel, Vec<(NaiveDateTime, LocationModel)>), GenericError> {
) -> Result<(TagModel, Vec<(NaiveDateTime, LocationModel)>), LibError> {
let tags = api.get_tags()?;
let tag = find_tag(tag_str, &tags, &api.config)?;
let tag_id = tag.get_id()?;
@ -73,12 +80,10 @@ pub fn between(
let locs = API::get_between(&locations, from, to, false, &api.config);
let gpx = gpx::generate_gpx(&tag, &locs, &api.config)?;
gpx::write_gpx(&gpx)?;
Ok((tag, locs))
}
pub fn check_api(api: &API) -> Result<(), GenericError> {
pub fn check_api(api: &API) -> Result<(), LibError> {
let tags = api.get_tags()?;
if let Some(tag) = tags.get(0) {
let tag_id = tag.get_id()?;
@ -91,23 +96,23 @@ pub fn check_api(api: &API) -> Result<(), GenericError> {
api.get_locations(&tag_id, &state_id)?;
Ok(())
} else {
Err(GenericError::MessagedError(
Err(LibError::MessagedError(
"Could not find any states for the first device found.".to_owned(),
None,
))
}
} else {
Err(GenericError::MessagedError("Could not find any devices. Please make sure this API-key is paired with a device first.".to_owned(), None))
Err(LibError::MessagedError("Could not find any devices. Please make sure this API-key is paired with a device first.".to_owned(), None))
}
}
pub fn sharetoken(api: &API, tag: String) -> Result<SharetokenModel, GenericError> {
pub fn sharetoken(api: &API, tag: String) -> Result<SharetokenModel, LibError> {
let tags = api.get_tags()?;
let tag = find_tag(tag, &tags, &api.config)?;
Ok(api.get_sharetoken(&tag.get_id()?)?)
}
pub fn nick_list(api: &API) -> Result<Vec<((usize, String), TagModel)>, GenericError> {
pub fn nick_list(api: &API) -> Result<Vec<((usize, String), TagModel)>, LibError> {
let mut tags = api.get_tags()?;
tags.sort_by(|tag1, tag2| tag1.id.cmp(&tag2.id));
let list = tags
@ -127,33 +132,26 @@ pub fn nick_list(api: &API) -> Result<Vec<((usize, String), TagModel)>, GenericE
Ok(list)
}
pub fn nick_set(
api: &mut API,
config_path: &PathBuf,
tag_str: String,
nickname: String,
) -> Result<(), GenericError> {
pub fn nick_set(api: &mut API, tag_str: String, nickname: String) -> Result<(), LibError> {
let mut tags = api.get_tags()?;
tags.sort_by(|tag1, tag2| tag1.id.cmp(&tag2.id));
let tag = find_tag(tag_str.clone(), &tags, &api.config)?;
if let Some(id) = &tag.id {
api.config.nicknames.insert(id.clone(), nickname);
api.config.write_to(config_path)?;
Ok(())
} else {
Err(GenericError::MessagedError(
Err(LibError::MessagedError(
format!("Device {} does not have an id", tag_str),
None,
))
}
}
#[allow(dead_code)]
pub fn get_locations(
api: &API,
tag: String,
state: Option<String>,
) -> Result<Vec<LocationModel>, GenericError> {
) -> Result<Vec<LocationModel>, LibError> {
let tags = api.get_tags()?;
let tag = find_tag(tag, &tags, &api.config)?;
if let Some(state) = state {
@ -163,8 +161,7 @@ pub fn get_locations(
}
}
#[allow(dead_code)]
pub fn get_states(api: &API, tag: String) -> Result<Vec<StateModel>, GenericError> {
pub fn get_states(api: &API, tag: String) -> Result<Vec<StateModel>, LibError> {
let tags = api.get_tags()?;
let tag = find_tag(tag, &tags, &api.config)?;
Ok(api.get_states(&tag.get_id()?)?)
@ -175,11 +172,7 @@ fn exp_time(api: &API, reqs_left: u64) -> Duration {
Duration::from_millis(interval * reqs_left)
}
fn find_tag(
tag_str: String,
tags: &Vec<TagModel>,
config: &Config,
) -> Result<TagModel, GenericError> {
fn find_tag(tag_str: String, tags: &Vec<TagModel>, config: &Config) -> Result<TagModel, LibError> {
let mut tag = None;
if let Ok(num) = tag_str.parse::<i32>() {
if let Some(found) = tags.get((num - 1).max(0) as usize) {
@ -195,7 +188,7 @@ fn find_tag(
}
match tag {
Some(tag) => Ok(tag),
None => Err(GenericError::MessagedError(
None => Err(LibError::MessagedError(
format!("Could not find device {}", tag_str),
None,
)),