From b1b8ac417b17598219592f4cbc4d4339a83cda80 Mon Sep 17 00:00:00 2001 From: Teascade Date: Sat, 29 Aug 2020 02:27:23 +0300 Subject: [PATCH] Separate lib and ux --- .gitignore | 5 +- Cargo.lock | 9 ++- Cargo.toml | 12 ++-- src/errors.rs | 57 ++------------- src/gpx.rs | 4 +- src/main.rs | 38 +++++----- thingy_lib/Cargo.toml | 11 +++ {src => thingy_lib/src}/api/mod.rs | 40 +++++------ {src => thingy_lib/src}/api/structs.rs | 10 +-- {src => thingy_lib/src}/config.rs | 6 +- thingy_lib/src/errors.rs | 88 ++++++++++++++++++++++++ src/commands.rs => thingy_lib/src/lib.rs | 55 +++++++-------- 12 files changed, 196 insertions(+), 139 deletions(-) create mode 100644 thingy_lib/Cargo.toml rename {src => thingy_lib/src}/api/mod.rs (90%) rename {src => thingy_lib/src}/api/structs.rs (96%) rename {src => thingy_lib/src}/config.rs (92%) create mode 100644 thingy_lib/src/errors.rs rename src/commands.rs => thingy_lib/src/lib.rs (81%) diff --git a/.gitignore b/.gitignore index c4a2f2f..f2d6ed9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target secret.toml -*.gpx \ No newline at end of file +*.gpx +*.code-workspace +/thingy_lib/Cargo.lock +/thingy_lib/target/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bc39136..73680c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 9a0baaa..c53afbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,12 @@ authors = ["Teascade "] 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 = "*" \ No newline at end of file +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"] \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs index c1d6120..9d5bfb9 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -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>), } -impl From for GenericError { - fn from(error: toml::de::Error) -> Self { - GenericError::TomlError(error) - } -} - -impl From for GenericError { - fn from(error: minreq::Error) -> Self { - GenericError::MinreqError(error) - } -} - -impl From for GenericError { - fn from(error: chrono::ParseError) -> Self { - GenericError::ChronoParseError(error) - } -} - -impl From for GenericError { - fn from(error: ErrorModel) -> Self { - GenericError::YepzonServerError(error) - } -} - -impl From for GenericError { - fn from(error: io::Error) -> Self { - GenericError::IOError(error) - } -} - -impl From for GenericError { - fn from(error: std::string::FromUtf8Error) -> Self { - GenericError::FromUTF8Error(error) +impl From for GenericError { + fn from(error: thingy_lib::Error) -> Self { + GenericError::ThingyLibError(error) } } @@ -59,21 +22,13 @@ impl From 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) } diff --git a/src/gpx.rs b/src/gpx.rs index dbd0d9d..6347f8a 100644 --- a/src/gpx.rs +++ b/src/gpx.rs @@ -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, diff --git a/src/main.rs b/src/main.rs index 3114c78..0d0df55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::>(), 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(()) diff --git a/thingy_lib/Cargo.toml b/thingy_lib/Cargo.toml new file mode 100644 index 0000000..78567d8 --- /dev/null +++ b/thingy_lib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "thingy-lib" +version = "0.1.0" +authors = ["Teascade "] +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" \ No newline at end of file diff --git a/src/api/mod.rs b/thingy_lib/src/api/mod.rs similarity index 90% rename from src/api/mod.rs rename to thingy_lib/src/api/mod.rs index 5a08bf5..4da0f6d 100644 --- a/src/api/mod.rs +++ b/thingy_lib/src/api/mod.rs @@ -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 { + pub fn get_raw_string(&self, url: APIUrl) -> Result { Ok(self .request(API::api_url(url, &self.config))? .as_str()? .to_owned()) } - pub fn get_sharetoken(&self, tag_id: &String) -> Result { + pub fn get_sharetoken(&self, tag_id: &String) -> Result { 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, GenericError> { + pub fn get_tags(&self) -> Result, 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 { + pub fn get_tag(&self, tag_id: &String) -> Result { 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, GenericError> { + pub fn get_states(&self, tag_id: &String) -> Result, 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, GenericError> { + pub fn get_current_locations(&self, tag_id: &String) -> Result, 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, GenericError> { + ) -> Result, 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, ErrorModel>, GenericError>> { + ) -> Receiver, 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( + pub(crate) fn get_between( list: &Vec, from: Option, to: Option, @@ -210,7 +204,7 @@ impl API { } #[allow(dead_code)] - pub fn print_list(list: Result, GenericError>) { + pub fn print_list(list: Result, LibError>) { match list { Ok(items) => { for item in items { @@ -243,7 +237,7 @@ impl API { fn add_error_back( location: String, - rec: &Result, ErrorModel>, GenericError>, + rec: &Result, ErrorModel>, LibError>, locations: &mut Vec, ) -> bool { if let Ok(inner) = rec { @@ -258,12 +252,12 @@ impl API { } } - fn request(&self, url: String) -> Result { + fn request(&self, url: String) -> Result { 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( diff --git a/src/api/structs.rs b/thingy_lib/src/api/structs.rs similarity index 96% rename from src/api/structs.rs rename to thingy_lib/src/api/structs.rs index b7a5f7b..aacc254 100644 --- a/src/api/structs.rs +++ b/thingy_lib/src/api/structs.rs @@ -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 { + pub fn get_id(&self) -> Result { 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 { + pub fn get_id(&self) -> Result { 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, )), diff --git a/src/config.rs b/thingy_lib/src/config.rs similarity index 92% rename from src/config.rs rename to thingy_lib/src/config.rs index a222c37..0a50cb1 100644 --- a/src/config.rs +++ b/thingy_lib/src/config.rs @@ -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 { + 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(); @@ -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.")?; diff --git a/thingy_lib/src/errors.rs b/thingy_lib/src/errors.rs new file mode 100644 index 0000000..f3f2612 --- /dev/null +++ b/thingy_lib/src/errors.rs @@ -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>), +} + +impl From for LibError { + fn from(error: toml::de::Error) -> Self { + LibError::TomlError(error) + } +} + +impl From for LibError { + fn from(error: minreq::Error) -> Self { + LibError::MinreqError(error) + } +} + +impl From for LibError { + fn from(error: chrono::ParseError) -> Self { + LibError::ChronoParseError(error) + } +} + +impl From for LibError { + fn from(error: ErrorModel) -> Self { + LibError::YepzonServerError(error) + } +} + +impl From for LibError { + fn from(error: io::Error) -> Self { + LibError::IOError(error) + } +} + +impl From 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 { + fn with_msg>(self, text: T) -> Result; +} + +impl> MessagedError for Result { + fn with_msg>(self, text: T) -> Result { + match self { + Ok(ok) => Ok(ok), + Err(e) => Err(LibError::MessagedError( + text.into(), + Some(Box::new(e.into())), + )), + } + } +} diff --git a/src/commands.rs b/thingy_lib/src/lib.rs similarity index 81% rename from src/commands.rs rename to thingy_lib/src/lib.rs index 75d0756..b2af892 100644 --- a/src/commands.rs +++ b/thingy_lib/src/lib.rs @@ -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) -> 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) -> 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, to: Option, -) -> 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 { +pub fn sharetoken(api: &API, tag: String) -> Result { 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, GenericError> { +pub fn nick_list(api: &API) -> Result, 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, 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, -) -> Result, GenericError> { +) -> Result, 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, GenericError> { +pub fn get_states(api: &API, tag: String) -> Result, 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, - config: &Config, -) -> Result { +fn find_tag(tag_str: String, tags: &Vec, 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) { @@ -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, )),