use env_logger::Builder;
use lazy_static::lazy_static;
use log::Log;
use std::{
io::{stdout, Write},
sync::Mutex,
};
use time::{Duration, OffsetDateTime};
struct State {
message: String,
next_update: OffsetDateTime,
is_logging: bool,
}
struct SafeLogger {
inner: Box<dyn Log>,
}
pub fn log_init() {
let mut st = STATE.lock().unwrap();
let inner = Builder::from_default_env().build();
let max_level = inner.filter();
let logger = SafeLogger {
inner: Box::new(inner),
};
log::set_boxed_logger(Box::new(logger)).expect("Set Logger");
log::set_max_level(max_level);
st.is_logging = true;
st.next_update = update_interval(true);
}
fn update_interval(is_logging: bool) -> OffsetDateTime {
if is_logging {
OffsetDateTime::now_utc() + Duration::milliseconds(250)
} else {
OffsetDateTime::now_utc() + Duration::seconds(5)
}
}
lazy_static! {
static ref STATE: Mutex<State> = Mutex::new(State {
message: String::new(),
next_update: update_interval(false),
is_logging: false,
});
}
impl State {
fn next(&mut self) {
self.next_update = update_interval(self.is_logging);
}
fn clear(&self) {
for ch in self.message.chars() {
if ch == '\n' {
print!("\x1b[1A\x1b[2K");
}
}
stdout().flush().expect("safe stdout write");
}
fn update(&mut self, message: String) {
self.clear();
self.message = message;
print!("{}", self.message);
stdout().flush().expect("safe stdout write");
self.next();
}
fn need_update(&self) -> bool {
OffsetDateTime::now_utc() >= self.next_update
}
}
impl Log for SafeLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
self.inner.enabled(metadata)
}
fn log(&self, record: &log::Record) {
let enabled = self.inner.enabled(record.metadata());
if enabled {
let st = STATE.lock().unwrap();
st.clear();
self.inner.log(record);
print!("{}", st.message);
stdout().flush().expect("safe stdout write");
}
}
fn flush(&self) {
let st = STATE.lock().unwrap();
st.clear();
self.inner.flush();
print!("{}", st.message);
stdout().flush().expect("safe stdout write");
}
}
pub struct Progress {
cur_files: u64,
total_files: u64,
cur_bytes: u64,
total_bytes: u64,
}
impl Progress {
pub fn new(files: u64, bytes: u64) -> Progress {
Progress {
cur_files: 0,
total_files: files,
cur_bytes: 0,
total_bytes: bytes,
}
}
pub fn update(&mut self, files: u64, bytes: u64) {
self.cur_files += files;
self.cur_bytes += bytes;
let mut st = STATE.lock().unwrap();
if st.need_update() {
st.update(self.message());
}
}
pub fn flush(&mut self) {
let mut st = STATE.lock().unwrap();
st.update(self.message());
st.message.clear();
}
pub fn message(&self) -> String {
format!(
"{:7}/{:7} ({:5.1}%) files, {}/{} ({:5.1}%) bytes\n",
self.cur_files,
self.total_files,
(self.cur_files as f64 * 100.0) / self.total_files as f64,
humanize(self.cur_bytes),
humanize(self.total_bytes),
(self.cur_bytes as f64 * 100.0) / self.total_bytes as f64
)
}
}
pub struct ScanProgress {
dirs: u64,
files: u64,
bytes: u64,
}
impl ScanProgress {
pub fn new() -> ScanProgress {
ScanProgress {
dirs: 0,
files: 0,
bytes: 0,
}
}
pub fn update(&mut self, dirs: u64, files: u64, bytes: u64) {
self.dirs += dirs;
self.files += files;
self.bytes += bytes;
let mut st = STATE.lock().unwrap();
if st.need_update() {
st.update(self.message());
}
}
fn message(&self) -> String {
format!(
"scan: {} dirs {} files, {} bytes\n",
self.dirs,
self.files,
humanize(self.bytes)
)
}
}
impl Drop for ScanProgress {
fn drop(&mut self) {
let mut st = STATE.lock().unwrap();
st.update(self.message());
st.message.clear();
}
}
pub fn humanize(value: u64) -> String {
let mut value = value as f64;
let mut unit = 0;
while value > 1024.0 {
value /= 1024.0;
unit += 1;
}
static UNITS: [&str; 9] = [
"B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB",
];
let precision = if value < 10.0 {
3
} else if value < 100.0 {
2
} else {
1
};
format!("{:6.*}{}", precision, value, UNITS[unit])
}