EvanZhu0721 / 公开仓库
SBMS
SBMS complete the calculation rules for the default logical desktop topology relationship and the physical dimensions of the displays in the Windows multi-screen collaboration system.
文件
../ diagnostic_view.rs6.1 KBgeometry_view.rs15.9 KBhandlers.rs32.1 KBprojection.rs6.7 KBstate.rs13.2 KBsrc/ui/diagnostic_view.rs6.1 KB
1
use std::fs;2
use std::path::{Path, PathBuf};3
use std::process::Command;4
5
use crate::diagnostics::{self, Level, Record};6
7
use super::QuickAccess;8
9
pub(super) fn populate_diagnostics(ui: &QuickAccess) {10
let (records, read_error) = match diagnostics::persisted_recent() {11
Ok(records) => (records, None),12
Err(error) => (diagnostics::recent(), Some(error)),13
};14
ui.set_diagnostic_summary(format_last_error(&records).into());15
ui.set_diagnostic_text(format_recent_diagnostics(&records).into());16
let path = diagnostics::default_log_path()17
.map(|path| path.display().to_string())18
.unwrap_or_else(|error| format!("Log path unavailable: {error}"));19
ui.set_diagnostic_path(path.into());20
ui.set_diagnostic_action_error(21
read_error22
.map(|error| format!("Couldn’t read the log file; showing this process only: {error}"))23
.unwrap_or_default()24
.into(),25
);26
}27
28
pub(super) fn format_recent_diagnostics(records: &[Record]) -> String {29
format_recent_diagnostics_at(records)30
}31
32
fn format_recent_diagnostics_at(records: &[Record]) -> String {33
let mut selected = records.iter().collect::<Vec<_>>();34
selected.sort_by_key(|record| std::cmp::Reverse(record.time));35
selected.truncate(40);36
if selected.is_empty() {37
return "No diagnostic records are available.".into();38
}39
40
selected41
.into_iter()42
.map(|record| {43
let local_time = if record.local_time.is_empty() {44
diagnostics::local_time_string(record.time)45
.unwrap_or_else(|| record.time.to_string())46
} else {47
record.local_time.clone()48
};49
let session = if record.mapping_session_id == "-" {50
String::new()51
} else {52
format!(" · {}", record.mapping_session_id)53
};54
let message = record.message.replace(['\r', '\n'], " ");55
format!(56
"[{local_time}] {} · {}/{}{}\n{}",57
diagnostic_level(record),58
record.module,59
record.stage,60
session,61
message62
)63
})64
.collect::<Vec<_>>()65
.join("\n\n")66
}67
68
fn format_last_error(records: &[Record]) -> String {69
records70
.iter()71
.filter(|record| record.level == Level::Error)72
.max_by_key(|record| record.time)73
.map(|record| {74
format!(75
"{} / {}: {}",76
record.module,77
record.stage,78
record.message.replace(['\r', '\n'], " ")79
)80
})81
.unwrap_or_default()82
}83
84
fn diagnostic_level(record: &Record) -> &'static str {85
match record.level {86
Level::Debug => "DEBUG",87
Level::Info => "INFO",88
Level::Warn => "WARN",89
Level::Error => "ERROR",90
}91
}92
93
pub(super) fn open_log_folder() -> Result<(), String> {94
let path = diagnostics::default_log_path()95
.map_err(|error| format!("Couldn’t locate the log folder: {error}"))?;96
let folder = ensure_log_folder(&path)?;97
Command::new("explorer.exe")98
.arg(folder)99
.spawn()100
.map(|_| ())101
.map_err(|error| format!("Couldn’t open the log folder: {error}"))102
}103
104
fn ensure_log_folder(log_path: &Path) -> Result<PathBuf, String> {105
let folder = log_path106
.parent()107
.filter(|folder| !folder.as_os_str().is_empty())108
.ok_or_else(|| "The diagnostic log path has no parent folder".to_string())?;109
fs::create_dir_all(folder).map_err(|error| {110
format!(111
"Couldn’t create the log folder at {}: {error}",112
folder.display()113
)114
})?;115
Ok(folder.to_path_buf())116
}117
118
#[cfg(test)]119
mod tests {120
use std::sync::atomic::{AtomicUsize, Ordering};121
122
use super::*;123
124
static NEXT_TEMP_DIRECTORY: AtomicUsize = AtomicUsize::new(1);125
126
fn record(time: u64, level: Level, session: &str, message: &str) -> Record {127
Record {128
time,129
local_time: String::new(),130
level,131
module: "mapping".into(),132
stage: "test".into(),133
mapping_session_id: session.into(),134
message: message.into(),135
}136
}137
138
#[test]139
fn recent_activity_keeps_newer_sessions_after_an_error() {140
let records = [141
record(1_000, Level::Error, "1-1", "failed"),142
record(2_000, Level::Info, "1-2", "recovered"),143
];144
145
let text = format_recent_diagnostics_at(&records);146
147
assert!(text.contains("failed"));148
assert!(text.contains("recovered"));149
assert!(text.find("recovered").unwrap() < text.find("failed").unwrap());150
}151
152
#[test]153
fn last_error_is_empty_without_an_error() {154
assert_eq!(155
format_last_error(&[record(1_000, Level::Info, "1-1", "healthy")]),156
""157
);158
}159
160
#[test]161
fn last_error_uses_the_latest_error_across_sessions() {162
let records = [163
record(1_000, Level::Error, "1-1", "first"),164
record(2_000, Level::Info, "1-2", "healthy"),165
record(3_000, Level::Error, "1-3", "latest"),166
];167
168
assert_eq!(format_last_error(&records), "mapping / test: latest");169
}170
171
#[test]172
fn ensure_log_folder_creates_the_directory_containing_the_log() {173
let sequence = NEXT_TEMP_DIRECTORY.fetch_add(1, Ordering::Relaxed);174
let test_root = std::env::temp_dir().join(format!(175
"sbms-open-log-folder-{}-{sequence}",176
std::process::id()177
));178
let log_path = test_root.join("SBMS").join("logs").join("sbms.log");179
180
let folder = ensure_log_folder(&log_path).expect("log folder should be created");181
182
assert_eq!(folder, test_root.join("SBMS").join("logs"));183
assert!(folder.is_dir());184
185
fs::remove_dir_all(&test_root).expect("temporary log folder should be removable");186
}187
188
#[test]189
fn ensure_log_folder_rejects_a_path_without_a_directory() {190
let error = ensure_log_folder(Path::new("sbms.log"))191
.expect_err("a bare file name has no log folder");192
193
assert_eq!(error, "The diagnostic log path has no parent folder");194
}195
}196