1use crate::model::clock;
2use crate::model::errors::LbResult;
3use crate::{Lb, get_code_version};
4use basic_human_duration::ChronoHumanDuration;
5use chrono::{Local, NaiveDateTime, TimeZone};
6use serde::Serialize;
7use std::env;
8use std::path::{Path, PathBuf};
9use std::sync::atomic::Ordering;
10use time::Duration;
11use tokio::fs::{self, OpenOptions};
12use tokio::io::AsyncWriteExt;
13
14#[derive(Serialize)]
15pub struct DebugInfo {
16 pub time: String,
17 pub name: String,
18 pub last_synced: String,
19 pub lb_version: String,
20 pub rust_triple: String,
21 pub os_info: String,
22 pub lb_dir: String,
23 pub server_url: String,
24 pub integrity: String,
25 pub is_syncing: bool,
26 pub status: String,
27 pub panics: Vec<String>,
28}
29
30impl Lb {
31 async fn human_last_synced(&self) -> String {
32 let tx = self.ro_tx().await;
33 let db = tx.db();
34
35 let last_synced = *db.last_synced.get().unwrap_or(&0);
36
37 if last_synced != 0 {
38 Duration::milliseconds(clock::get_time().0 - last_synced)
39 .format_human()
40 .to_string()
41 } else {
42 "never".to_string()
43 }
44 }
45
46 fn now(&self) -> String {
47 let now = chrono::Local::now();
48 now.format("%Y-%m-%d %H:%M:%S %Z").to_string()
49 }
50
51 async fn collect_panics(&self, populate_content: bool) -> LbResult<Vec<PanicInfo>> {
52 let mut panics = vec![];
53
54 let dir_path = &self.config.writeable_path;
55 let path = Path::new(dir_path);
56
57 let prefix = "panic---";
58 let suffix = ".log";
59 let timestamp_format = "%Y-%m-%d---%H-%M-%S";
60
61 let mut entries = fs::read_dir(path).await?;
62 while let Some(entry) = entries.next_entry().await? {
63 let file_name = entry.file_name().into_string().unwrap_or_default();
64
65 if file_name.starts_with(prefix) && file_name.ends_with(suffix) {
67 let timestamp_str = &file_name[prefix.len()..file_name.len() - suffix.len()];
69
70 if let Ok(time) = NaiveDateTime::parse_from_str(timestamp_str, timestamp_format) {
72 let file_path = path.join(file_name);
73 let content = if populate_content {
74 let contents = fs::read_to_string(&file_path).await?;
75 let contents = format!("time: {time}: contents: {contents}");
76 contents
77 } else {
78 Default::default()
79 };
80
81 panics.push(PanicInfo { time, file_path, content });
82 }
83 }
84 }
85 panics.reverse();
86
87 Ok(panics)
88 }
89
90 pub async fn recent_panic(&self) -> LbResult<bool> {
92 let panics = self.collect_panics(false).await?;
93 for panic in panics {
94 let timestamp_local_time = Local
95 .from_local_datetime(&panic.time)
96 .single()
97 .unwrap_or_default();
98
99 let seconds_ago = (Local::now() - timestamp_local_time).abs().num_seconds();
100
101 if seconds_ago <= 5 {
102 return Ok(true);
103 }
104 }
105
106 Ok(false)
107 }
108
109 #[instrument(level = "debug", skip(self), err(Debug))]
110 pub async fn write_panic_to_file(&self, error_header: String, bt: String) -> LbResult<String> {
111 let file_name = generate_panic_filename(&self.config.writeable_path);
112 let content = generate_panic_content(&error_header, &bt);
113
114 let mut file = OpenOptions::new()
115 .create(true)
116 .append(true)
117 .open(&file_name)
118 .await?;
119
120 file.write_all(content.as_bytes()).await?;
121
122 Ok(file_name)
123 }
124
125 #[instrument(level = "debug", skip(self), err(Debug))]
126 pub async fn debug_info(&self, os_info: String) -> LbResult<String> {
127 let account = self.get_account()?;
128
129 let arch = env::consts::ARCH;
130 let os = env::consts::OS;
131 let family = env::consts::FAMILY;
132
133 let (integrity, last_synced, panics) = tokio::join!(
134 self.test_repo_integrity(),
135 self.human_last_synced(),
136 self.collect_panics(true)
137 );
138
139 let panics = panics?.into_iter().map(|panic| panic.content).collect();
140
141 let mut status = self.status().await;
142 status.space_used = None;
143 let status = format!("{status:?}");
144 let is_syncing = self.syncing.load(Ordering::Relaxed);
145
146 Ok(serde_json::to_string_pretty(&DebugInfo {
147 time: self.now(),
148 name: account.username.clone(),
149 lb_version: get_code_version().into(),
150 rust_triple: format!("{arch}.{family}.{os}"),
151 server_url: account.api_url.clone(),
152 integrity: format!("{integrity:?}"),
153 lb_dir: self.config.writeable_path.clone(),
154 last_synced,
155 os_info,
156 status,
157 is_syncing,
158 panics,
159 })?)
160 }
161}
162
163#[derive(Debug, Clone)]
164pub struct PanicInfo {
165 pub time: NaiveDateTime,
166 pub file_path: PathBuf,
167 pub content: String,
168}
169
170pub fn generate_panic_filename(path: &str) -> String {
171 let timestamp = chrono::Local::now().format("%Y-%m-%d---%H-%M-%S");
172 format!("{path}/panic---{timestamp}.log")
173}
174
175pub fn generate_panic_content(panic_info: &str, bt: &str) -> String {
176 format!("INFO: {panic_info}\nBT: {bt}")
177}