LCE-Emerald-Launcher/src-tauri/src/playtime.rs

118 lines
3.8 KiB
Rust

use std::collections::HashMap;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::AppHandle;
use serde::{Serialize, Deserialize};
use crate::util;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PlaytimeSession {
pub start: u64,
pub end: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PlaytimeData {
pub sessions: HashMap<String, Vec<PlaytimeSession>>,
}
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PlaytimeResponse {
pub total_seconds: u64,
pub week_seconds: u64,
pub day_seconds: u64,
}
fn playtime_path(app: &AppHandle) -> PathBuf {
util::get_app_dir(app).join("playtime.json")
}
pub fn load(app: &AppHandle) -> PlaytimeData {
let path = playtime_path(app);
if path.exists() {
let content = std::fs::read_to_string(&path).unwrap_or_default();
serde_json::from_str(&content).unwrap_or(PlaytimeData { sessions: HashMap::new() })
} else {
PlaytimeData { sessions: HashMap::new() }
}
}
pub fn save(app: &AppHandle, data: &PlaytimeData) {
let path = playtime_path(app);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
if let Ok(content) = serde_json::to_string_pretty(data) {
let _ = std::fs::write(&path, content);
}
}
pub fn record_session(app: &AppHandle, instance_id: &str, start: u64, end: u64) {
let mut data = load(app);
let sessions = data.sessions.entry(instance_id.to_string()).or_default();
sessions.push(PlaytimeSession { start, end });
save(app, &data);
}
pub fn get_playtime(app: &AppHandle, instance_id: &str) -> PlaytimeResponse {
let data = load(app);
let sessions = data.sessions.get(instance_id);
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let week_ago = now - 7 * 24 * 60 * 60;
let day_ago = now - 24 * 60 * 60;
let total_seconds: u64 = sessions.map_or(0, |s| s.iter().map(|s| s.end - s.start).sum());
let week_seconds: u64 = sessions.map_or(0, |s| s.iter()
.filter(|s| s.start >= week_ago)
.map(|s| s.end - s.start)
.sum());
let day_seconds: u64 = sessions.map_or(0, |s| s.iter()
.filter(|s| s.start >= day_ago)
.map(|s| s.end - s.start)
.sum());
PlaytimeResponse { total_seconds, week_seconds, day_seconds }
}
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PlaytimeDayEntry {
pub label: String,
pub seconds: u64,
}
pub fn get_playtime_daily(app: &AppHandle, instance_id: &str, days: u64) -> Vec<PlaytimeDayEntry> {
let data = load(app);
let sessions = data.sessions.get(instance_id);
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
let day_secs = 24 * 60 * 60;
let mut entries: Vec<(u64, u64)> = (0..days).map(|d| {
let day_start = now - (d * day_secs) - (now % day_secs);
(day_start, 0)
}).collect();
if let Some(sessions) = sessions {
for session in sessions {
for entry in entries.iter_mut() {
let day_end = entry.0 + day_secs;
if session.start < day_end && session.end > entry.0 {
let overlap_start = std::cmp::max(session.start, entry.0);
let overlap_end = std::cmp::min(session.end, day_end);
if overlap_end > overlap_start {
entry.1 += overlap_end - overlap_start;
}
}
}
}
}
entries.reverse();
let day_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
entries.into_iter().map(|(ts, secs)| {
let weekday = ((ts / day_secs) + 4) % 7;
PlaytimeDayEntry {
label: day_names[weekday as usize].to_string(),
seconds: secs,
}
}).collect()
}