Working Error Logging
All checks were successful
Run Tests on Code / run-tests (push) Successful in 0s

This commit is contained in:
Neshura 2023-12-17 22:29:18 +01:00
parent bba7fae8b9
commit 47e6cc59c0
Signed by: Neshura
GPG key ID: B6983AAA6B9A7A6C
7 changed files with 253 additions and 123 deletions

View file

@ -1,10 +1,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::sync::{Arc}; use std::sync::{Arc};
use chrono::{DateTime, Duration, Timelike, Utc}; use chrono::{DateTime, Duration, Timelike, Utc};
use lemmy_db_schema::newtypes::{CommunityId, LanguageId}; use lemmy_db_schema::newtypes::{CommunityId, LanguageId};
use lemmy_db_schema::PostFeatureType; use lemmy_db_schema::PostFeatureType;
use tokio::sync::{RwLock, RwLockWriteGuard}; use tokio::sync::{RwLock};
use crate::{jnovel, lemmy, Message, SharedData}; use crate::{jnovel, lemmy, Message, SharedData};
use crate::config::{Config, PostBody, SeriesConfig}; use crate::config::{Config, PostBody, SeriesConfig};
use crate::jnovel::PostInfo; use crate::jnovel::PostInfo;
@ -12,7 +11,7 @@ use crate::lemmy::{CustomCreatePost, Lemmy};
use crate::post_history::SeriesHistory; use crate::post_history::SeriesHistory;
use tokio::time::sleep; use tokio::time::sleep;
pub(crate) async fn run(data: Arc<RwLock<SharedData>>){ pub(crate) async fn run(data: Arc<RwLock<SharedData>>) {
let credentials = match lemmy::Credentials::set_credentials() { let credentials = match lemmy::Credentials::set_credentials() {
Ok(creds) => creds, Ok(creds) => creds,
Err(e) => panic!("{}", e.to_string()), Err(e) => panic!("{}", e.to_string()),
@ -23,17 +22,20 @@ pub(crate) async fn run(data: Arc<RwLock<SharedData>>){
let mut login_error: bool; let mut login_error: bool;
let mut communities; let mut communities;
{ {
let mut shared_data = data.write().await; let mut write = data.write().await;
// Errors during bot init are likely unrecoverable and therefore should panic the bot // Errors during bot init are likely unrecoverable and therefore should panic the bot
// Does not really matter since the bot will get restarted anyway but this way the uptime url logs a downtime // Does not really matter since the bot will get restarted anyway but this way the uptime url logs a downtime
shared_data.config = match Config::load() { write.config = match Config::load() {
Ok(data) => data, Ok(data) => data,
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
}; };
last_reload = Utc::now(); last_reload = Utc::now();
}
lemmy = match lemmy::login(&credentials, shared_data.config.instance.as_str()).await { {
let read = data.read().await;
lemmy = match lemmy::login(&credentials, read.config.instance.as_str()).await {
Ok(data) => data, Ok(data) => data,
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
}; };
@ -49,64 +51,101 @@ pub(crate) async fn run(data: Arc<RwLock<SharedData>>){
sleep(Duration::milliseconds(100).to_std().unwrap()).await; sleep(Duration::milliseconds(100).to_std().unwrap()).await;
} }
{
let mut write = data.write().await;
write.start = Utc::now();
}
loop { loop {
idle(&data).await; idle(&data).await;
let mut shared_data = data.write().await; {
let mut write = data.write().await;
shared_data.start = Utc::now(); write.start = Utc::now();
if shared_data.start - last_reload > Duration::seconds(shared_data.config.config_reload_seconds as i64) { if write.start - last_reload > Duration::seconds(write.config.config_reload_seconds as i64) {
shared_data.config = match Config::load() { write.config = match Config::load() {
Ok(data) => data, Ok(data) => data,
Err(e) => panic!("{}", e), Err(e) => panic!("{}", e),
}; };
}
if login_error {
lemmy = match lemmy::login(&credentials, shared_data.config.instance.as_str()).await {
Ok(data) => data,
Err(e) => {
shared_data.messages.push(Message::Error(format!("{}", e)));
continue
}
};
login_error = false;
}
if shared_data.start - last_reload > Duration::seconds(shared_data.config.config_reload_seconds as i64) {
communities = match lemmy.get_communities().await {
Ok(data) => data,
Err(e) => {
login_error = true;
shared_data.messages.push(Message::Error(format!("{}", e)));
continue
}
};
last_reload = Utc::now();
}
shared_data.post_history = match SeriesHistory::load_history() {
Ok(data) => data,
Err(e) => {
login_error = true;
shared_data.messages.push(Message::Warning(format!("{}", e)));
continue
} }
}; }
for series in shared_data.config.series.clone() { {
match handle_series(&series, &communities, &lemmy, &mut shared_data).await { let read = data.read().await;
if login_error {
lemmy = match lemmy::login(&credentials, read.config.instance.as_str()).await {
Ok(data) => data,
Err(e) => {
drop(read);
let mut write = data.write().await;
write.messages.push(Message::Error(e.to_string()));
continue
}
};
login_error = false;
}
}
{
let read = data.read().await;
if read.start - last_reload > Duration::seconds(read.config.config_reload_seconds as i64) {
communities = match lemmy.get_communities().await {
Ok(data) => data,
Err(e) => {
login_error = true;
drop(read);
let mut write = data.write().await;
write.messages.push(Message::Error(e.to_string()));
continue
}
};
last_reload = Utc::now();
}
}
{
let mut write = data.write().await;
write.post_history = match SeriesHistory::load_history() {
Ok(data) => data, Ok(data) => data,
Err(e) => { Err(e) => {
login_error = true; write.messages.push(Message::Warning(e.to_string()));
shared_data.messages.push(Message::Warning(format!("{}", e)));
continue continue
} }
}; };
} }
let _ = shared_data.downgrade(); {
let read = data.read().await;
let series = read.config.series.clone();
drop(read);
for series in series {
match handle_series(&series, &communities, &lemmy, &data).await {
Ok(data) => data,
Err(e) => {
login_error = true;
let mut write = data.write().await;
write.messages.push(Message::Warning(e.to_string()));
continue
}
};
}
}
let read = data.read().await;
if read.messages.len() > 15 {
let mut list = read.messages.clone();
drop(read);
list.reverse();
while list.len() > 15 {
list.pop();
}
list.reverse();
let mut write = data.write().await;
write.messages = list
}
idle(&data).await; idle(&data).await;
} }
@ -124,7 +163,7 @@ async fn idle(data: &Arc<RwLock<SharedData>>) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
let mut write = data.write().await; let mut write = data.write().await;
write.messages.push(Message::Error(format!("{}", e))) write.messages.push(Message::Error(e.to_string()))
}, },
} }
}; };
@ -134,26 +173,35 @@ async fn idle(data: &Arc<RwLock<SharedData>>) {
} }
} }
async fn handle_series<'a>( async fn handle_series(
series: &SeriesConfig, series: &SeriesConfig,
communities: &HashMap<String, CommunityId>, communities: &HashMap<String, CommunityId>,
lemmy: &Lemmy, lemmy: &Lemmy,
data: &mut RwLockWriteGuard<'a, SharedData>, data: &Arc<RwLock<SharedData>>,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), String> {
let mut post_list = match jnovel::check_feed(series.slug.as_str(), series.parted).await { let mut post_list = match jnovel::check_feed(series.slug.as_str(), series.parted).await {
Ok(data) => data, Ok(data) => data,
Err(e) => panic!("{:#?}", e), Err(e) => {
return Err(e.to_string());
},
}; };
for (index, post_info) in post_list.clone().iter().enumerate() { // todo .clone() likely not needed for (index, post_info) in post_list.clone().iter().enumerate() { // todo .clone() likely not needed
let post_part_info = post_info.get_part_info(); let post_part_info = post_info.get_part_info();
let post_lemmy_info = post_info.get_lemmy_info(); let post_lemmy_info = post_info.get_lemmy_info();
if data.post_history.check_for_post(series.slug.as_str(), post_part_info.as_string().as_str(), post_lemmy_info.title.as_str()) {
data.messages.push(Message::Info(format!("Skipping '{}' since already posted", post_lemmy_info.title))); {
let read = data.read().await;
if read.post_history.check_for_post(series.slug.as_str(), post_part_info.as_string().as_str(), post_lemmy_info.title.as_str()) {
drop(read);
let mut write = data.write().await;
write.messages.push(Message::Info(format!("Skipping '{}' since already posted", post_lemmy_info.title)));
post_list.remove(index); post_list.remove(index);
continue continue
} }
}
let post_series_config = match post_info { let post_series_config = match post_info {
PostInfo::Chapter {..} => {&series.prepub_community}, PostInfo::Chapter {..} => {&series.prepub_community},
@ -200,8 +248,10 @@ async fn handle_series<'a>(
lemmy.pin(post_id, PostFeatureType::Local).await?; lemmy.pin(post_id, PostFeatureType::Local).await?;
} }
let mut series_history = data.post_history.get_series(series.slug.as_str()); let read = data.read().await;
let mut series_history = read.post_history.get_series(series.slug.as_str());
let mut part_history = series_history.get_part(post_part_info.as_string().as_str()); let mut part_history = series_history.get_part(post_part_info.as_string().as_str());
drop(read);
match post_info { match post_info {
PostInfo::Chapter {..} => { PostInfo::Chapter {..} => {
@ -213,8 +263,12 @@ async fn handle_series<'a>(
} }
series_history.set_part(post_part_info.as_string().as_str(), part_history); series_history.set_part(post_part_info.as_string().as_str(), part_history);
data.post_history.set_series(series.slug.as_str(), series_history); let mut write = data.write().await;
let _ = data.post_history.save_history()?; write.post_history.set_series(series.slug.as_str(), series_history);
let _ = match write.post_history.save_history() {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
} }
Ok(()) Ok(())
} }

View file

@ -1,4 +1,3 @@
use std::{error::Error};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::config::PostBody::Description; use crate::config::PostBody::Description;
@ -11,8 +10,11 @@ pub(crate) struct Config {
} }
impl Config { impl Config {
pub(crate) fn load() -> Result<Self, Box<dyn Error>> { pub(crate) fn load() -> Result<Self, String> {
let cfg: Self = confy::load_path("./config.toml")?; let cfg: Self = match confy::load_path("./config.toml") {
Ok(data) => data,
Err(_) => panic!("config.toml not found!"),
};
if cfg.instance.is_empty() { if cfg.instance.is_empty() {
panic!("config.toml not found!") panic!("config.toml not found!")
} }

View file

@ -1,6 +1,5 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::ops::Sub; use std::ops::Sub;
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -9,7 +8,7 @@ use crate::{HTTP_CLIENT};
use crate::jnovel::PartInfo::{NoParts, Part}; use crate::jnovel::PartInfo::{NoParts, Part};
use crate::jnovel::PostInfo::{Chapter, Volume}; use crate::jnovel::PostInfo::{Chapter, Volume};
static PAST_DAYS_ELIGIBLE: u8 = 4; static PAST_DAYS_ELIGIBLE: u8 = 8;
macro_rules! api_url { macro_rules! api_url {
() => { () => {
@ -223,15 +222,24 @@ impl PartialOrd for PostInfo {
} }
} }
pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Result<Vec<PostInfo>, Box<dyn Error>> { pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Result<Vec<PostInfo>, String> {
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.get(api_url!() + "/series/" + series_slug + "/volumes?format=json") .get(api_url!() + "/series/" + series_slug + "/volumes?format=json")
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
}
},
Err(e) => return Err(format!("{}", e))
};
let mut volume_brief_data: VolumesWrapper = serde_json::from_str(&response)?; let mut volume_brief_data: VolumesWrapper = match serde_json::from_str(&response) {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
volume_brief_data.volumes.reverse(); // Makes breaking out of the volume loop easier volume_brief_data.volumes.reverse(); // Makes breaking out of the volume loop easier
// If no parts just use 0 as Part indicator as no Series with Parts has a Part 0 // If no parts just use 0 as Part indicator as no Series with Parts has a Part 0
@ -322,15 +330,25 @@ pub(crate) async fn check_feed(series_slug: &str, series_has_parts: bool) -> Res
Ok(result_vec) Ok(result_vec)
} }
async fn get_latest_prepub(volume_slug: &str) -> Result<Option<LemmyPostInfo>, Box<dyn Error>> { async fn get_latest_prepub(volume_slug: &str) -> Result<Option<LemmyPostInfo>, String> {
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json") .get(api_url!() + "/volumes/" + volume_slug + "/parts?format=json")
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(e.to_string())
}
},
Err(e) => return Err(format!("{}", e))
};
let mut volume_prepub_parts_data: ChapterWrapper = serde_json::from_str(&response)?;
let mut volume_prepub_parts_data: ChapterWrapper = match serde_json::from_str(&response) {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
volume_prepub_parts_data.parts.reverse(); // Makes breaking out of the parts loop easier volume_prepub_parts_data.parts.reverse(); // Makes breaking out of the parts loop easier
let mut post_details: Option<LemmyPostInfo> = None; let mut post_details: Option<LemmyPostInfo> = None;

View file

@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::env::VarError; use std::env::VarError;
use std::error::Error;
use lemmy_api_common::community::{ListCommunities}; use lemmy_api_common::community::{ListCommunities};
use lemmy_api_common::person::{Login, LoginResponse}; use lemmy_api_common::person::{Login, LoginResponse};
use lemmy_api_common::post::{GetPosts}; use lemmy_api_common::post::{GetPosts};
@ -42,18 +41,21 @@ pub(crate) struct Lemmy {
instance: String, instance: String,
} }
pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result<Lemmy, Box<dyn Error>> { pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result<Lemmy, String> {
let login_params = Login { let login_params = Login {
username_or_email: credentials.get_username(), username_or_email: credentials.get_username(),
password: credentials.get_password(), password: credentials.get_password(),
totp_2fa_token: None, totp_2fa_token: None,
}; };
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.post(instance.to_string() + "/api/v3/user/login") .post(instance.to_string() + "/api/v3/user/login")
.json(&login_params) .json(&login_params)
.send() .send()
.await?; .await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
match response.status() { match response.status() {
StatusCode::OK => { StatusCode::OK => {
@ -71,40 +73,55 @@ pub(crate) async fn login(credentials: &Credentials, instance: &str) -> Result<L
} }
impl Lemmy { impl Lemmy {
pub(crate) async fn post(&self, post: CustomCreatePost) -> Result<PostId, Box<dyn Error>> { pub(crate) async fn post(&self, post: CustomCreatePost) -> Result<PostId, String> {
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.post(format!("{}/api/v3/post", &self.instance)) .post(format!("{}/api/v3/post", &self.instance))
.bearer_auth(&self.jwt_token.to_string()) .bearer_auth(&self.jwt_token.to_string())
.json(&post) .json(&post)
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
}
},
Err(e) => return Err(format!("{}", e))
};
let json_data = serde_json::from_str::<HashMap<&str, CustomPostView>>(&response)? let json_data = match serde_json::from_str::<HashMap<&str, CustomPostView>>(&response) {
.remove("post_view") Ok(mut data) => data.remove("post_view").expect("Element should be present"),
.expect("Element should be present"); Err(e) => return Err(format!("{}", e))
};
Ok(json_data.post.id) Ok(json_data.post.id)
} }
async fn feature(&self, params: CustomFeaturePost) -> Result<CustomPostView, Box<dyn Error>> { async fn feature(&self, params: CustomFeaturePost) -> Result<CustomPostView, String> {
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.post(format!("{}/api/v3/post/feature", &self.instance)) .post(format!("{}/api/v3/post/feature", &self.instance))
.bearer_auth(&self.jwt_token.to_string()) .bearer_auth(&self.jwt_token.to_string())
.json(&params) .json(&params)
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
}
},
Err(e) => return Err(format!("{}", e))
};
let json_data = match serde_json::from_str::<HashMap<&str, CustomPostView>>(&response) {
Ok(mut data) => data.remove("post_view").expect("Element should be present"),
Err(e) => return Err(format!("{}", e))
};
let json_data = serde_json::from_str::<HashMap<&str, CustomPostView>>(&response)?
.remove("post_view")
.expect("Element should be present");
Ok(json_data) Ok(json_data)
} }
pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Result<CustomPostView, Box<dyn Error>> { pub(crate) async fn unpin(&self, post_id: PostId, location: PostFeatureType) -> Result<CustomPostView, String> {
let pin_params = CustomFeaturePost { let pin_params = CustomFeaturePost {
post_id, post_id,
featured: false, featured: false,
@ -113,7 +130,7 @@ impl Lemmy {
self.feature(pin_params).await self.feature(pin_params).await
} }
pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Result<CustomPostView, Box<dyn Error>> { pub(crate) async fn pin(&self, post_id: PostId, location: PostFeatureType) -> Result<CustomPostView, String> {
let pin_params = CustomFeaturePost { let pin_params = CustomFeaturePost {
post_id, post_id,
featured: true, featured: true,
@ -122,23 +139,32 @@ impl Lemmy {
self.feature(pin_params).await self.feature(pin_params).await
} }
pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Result<Vec<CustomPostView>, Box<dyn Error>> { pub(crate) async fn get_community_pinned(&self, community: CommunityId) -> Result<Vec<CustomPostView>, String> {
let list_params = GetPosts { let list_params = GetPosts {
community_id: Some(community), community_id: Some(community),
type_: Some(ListingType::Local), type_: Some(ListingType::Local),
..Default::default() ..Default::default()
}; };
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.get(format!("{}/api/v3/post/list", &self.instance)) .get(format!("{}/api/v3/post/list", &self.instance))
.bearer_auth(&self.jwt_token.to_string()) .bearer_auth(&self.jwt_token.to_string())
.query(&list_params) .query(&list_params)
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
}
},
Err(e) => return Err(format!("{}", e))
};
let json_data: CustomGetPostsResponse = serde_json::from_str(&response)?; let json_data: CustomGetPostsResponse = match serde_json::from_str(&response) {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
Ok(json_data.posts.iter().filter(|post| { Ok(json_data.posts.iter().filter(|post| {
post.post.featured_community post.post.featured_community
@ -148,22 +174,31 @@ impl Lemmy {
) )
} }
pub(crate) async fn get_local_pinned(&self) -> Result<Vec<CustomPostView>, Box<dyn Error>> { pub(crate) async fn get_local_pinned(&self) -> Result<Vec<CustomPostView>, String> {
let list_params = GetPosts { let list_params = GetPosts {
type_: Some(ListingType::Local), type_: Some(ListingType::Local),
..Default::default() ..Default::default()
}; };
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.get(format!("{}/api/v3/post/list", &self.instance)) .get(format!("{}/api/v3/post/list", &self.instance))
.bearer_auth(&self.jwt_token.to_string()) .bearer_auth(&self.jwt_token.to_string())
.query(&list_params) .query(&list_params)
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
}
},
Err(e) => return Err(format!("{}", e))
};
let json_data: CustomGetPostsResponse = serde_json::from_str(&response)?; let json_data: CustomGetPostsResponse = match serde_json::from_str(&response) {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
Ok(json_data.posts.iter().filter(|post| { Ok(json_data.posts.iter().filter(|post| {
post.post.featured_local post.post.featured_local
@ -173,22 +208,31 @@ impl Lemmy {
) )
} }
pub(crate) async fn get_communities(&self) -> Result<HashMap<String, CommunityId>, Box<dyn Error>> { pub(crate) async fn get_communities(&self) -> Result<HashMap<String, CommunityId>, String> {
let list_params = ListCommunities { let list_params = ListCommunities {
type_: Some(ListingType::Local), type_: Some(ListingType::Local),
..Default::default() ..Default::default()
}; };
let response = HTTP_CLIENT let response = match HTTP_CLIENT
.get(format!("{}/api/v3/community/list", &self.instance)) .get(format!("{}/api/v3/community/list", &self.instance))
.bearer_auth(&self.jwt_token.to_string()) .bearer_auth(&self.jwt_token.to_string())
.query(&list_params) .query(&list_params)
.send() .send()
.await? .await {
.text() Ok(data) => {
.await?; match data.text().await {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
}
},
Err(e) => return Err(format!("{}", e))
};
let json_data: CustomListCommunitiesResponse = serde_json::from_str(&response)?; let json_data: CustomListCommunitiesResponse = match serde_json::from_str(&response) {
Ok(data) => data,
Err(e) => return Err(format!("{}", e))
};
let mut communities: HashMap<String, CommunityId> = HashMap::new(); let mut communities: HashMap<String, CommunityId> = HashMap::new();
for community_view in json_data.communities { for community_view in json_data.communities {

View file

@ -69,6 +69,16 @@ pub(crate) enum Message {
Error(String), Error(String),
} }
impl Message {
pub(crate) fn content(&self) -> String {
match self {
Message::Info(msg) => msg.clone(),
Message::Warning(msg) => msg.clone(),
Message::Error(msg) => msg.clone(),
}
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
dotenv().ok(); dotenv().ok();

View file

@ -1,5 +1,4 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::error::Error;
use std::fs; use std::fs;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::io::Write; use std::io::Write;
@ -12,10 +11,13 @@ pub(crate) struct SeriesHistory {
} }
impl SeriesHistory { impl SeriesHistory {
pub(crate) fn load_history() -> Result<Self, Box<dyn Error>> { pub(crate) fn load_history() -> Result<Self, String> {
match Path::new("history.toml").exists() { match Path::new("history.toml").exists() {
true => { true => {
let file_contents: String = fs::read_to_string("history.toml")?; let file_contents: String = match fs::read_to_string("history.toml") {
Ok(data) => data,
Err(e) => return Err(format!("{}", e)),
};
let history: Result<SeriesHistory, toml::de::Error> = match file_contents.len() { let history: Result<SeriesHistory, toml::de::Error> = match file_contents.len() {
0 => return Ok(SeriesHistory { 0 => return Ok(SeriesHistory {
@ -26,7 +28,7 @@ impl SeriesHistory {
match history { match history {
Ok(data) => Ok(data), Ok(data) => Ok(data),
Err(e) => Err(Box::new(e)) Err(e) => Err(format!("{}", e))
} }
}, },
false => { false => {

View file

@ -79,10 +79,10 @@ async fn print_info<'a>(data: RwLockReadGuard<'a, SharedData>, min_len_series: &
}); });
println!("{:#<1$}", "", separator_width); println!("{:#<1$}", "", separator_width);
for error in data.get_messages(true, true, false).iter() { for error in data.get_messages(true, true, false).iter() {
println!("{}", error); println!("{}", error.content());
} }
println!("{:#<1$}", "", separator_width); println!("{:#<1$}", "", separator_width);
for message in data.get_messages(false, false, false).iter() { for message in data.get_messages(false, false, false).iter() {
println!("{}", message); println!("{}", message.content());
} }
} }