use std::path::PathBuf;
const TELEMETRY_ENDPOINT: &str = "https://vastlint.org/ping";
pub fn ping(file_count: usize) {
if TELEMETRY_ENDPOINT.is_empty() {
return;
}
let version = env!("CARGO_PKG_VERSION");
let os = os_name();
let id = install_id();
let endpoint = format!(
"{}?v={}&os={}&id={}&files={}",
TELEMETRY_ENDPOINT, version, os, id, file_count
);
std::thread::spawn(move || {
let _ = ureq::get(&endpoint)
.timeout(std::time::Duration::from_secs(2))
.call();
});
}
pub fn maybe_show_notice() {
if !std::io::IsTerminal::is_terminal(&std::io::stderr()) {
return;
}
let Some(sentinel) = notice_path() else {
return;
};
if sentinel.exists() {
return;
}
eprintln!(
"\n\x1b[1mtip:\x1b[0m help improve vastlint with anonymous usage stats:\n\
\n\
\x20 vastlint check --telemetry FILE.xml\n\
\x20 # or set telemetry = true in vastlint.toml\n\
\n\
\x20 Only version, OS, and file count are sent. No file contents.\n\
\x20 Details: https://vastlint.org/telemetry\n"
);
if let Some(parent) = sentinel.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&sentinel, "1");
}
fn notice_path() -> Option<PathBuf> {
let config_dir = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| dirs_next().map(|h| h.join(".config")))?;
Some(config_dir.join("vastlint").join("notice-shown"))
}
fn os_name() -> &'static str {
if cfg!(target_os = "linux") {
"linux"
} else if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "windows") {
"windows"
} else {
"other"
}
}
fn install_id() -> String {
if let Some(path) = id_path() {
if let Ok(existing) = std::fs::read_to_string(&path) {
let trimmed = existing.trim().to_owned();
if trimmed.len() == 32 && trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
return trimmed;
}
}
let id = random_hex_id();
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&path, &id);
return id;
}
random_hex_id()
}
fn id_path() -> Option<PathBuf> {
let config_dir = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| dirs_next().map(|h| h.join(".config")))?;
Some(config_dir.join("vastlint").join("id"))
}
fn dirs_next() -> Option<PathBuf> {
std::env::var_os("HOME").map(PathBuf::from)
}
fn random_hex_id() -> String {
let bytes = os_random_bytes();
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
fn os_random_bytes() -> [u8; 16] {
#[cfg(unix)]
{
use std::io::Read;
if let Ok(mut f) = std::fs::File::open("/dev/urandom") {
let mut buf = [0u8; 16];
if f.read_exact(&mut buf).is_ok() {
return buf;
}
}
}
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let pid = std::process::id() as u128;
let mixed = ts ^ (pid << 32) ^ (pid >> 32);
mixed.to_le_bytes()
}