use crate::config::{PlayerPlatform, TargetAuth};
use super::{AppSummary, ReplayUploadRequest, SyncRunState};
pub fn short_match_id(match_id: &str) -> &str {
match_id.get(..8).unwrap_or(match_id)
}
pub fn now_label() -> String {
chrono::Local::now()
.format("%Y-%m-%d %H:%M:%S %Z")
.to_string()
}
pub fn format_backfill_message(message: String, failed_uploads: &[ReplayUploadRequest]) -> String {
if failed_uploads.is_empty() {
message
} else {
let suffix = if failed_uploads.len() == 1 {
String::new()
} else {
format!("; {} total blocked/failed uploads", failed_uploads.len())
};
format!(
"{message}; first issue: {}{suffix}",
format_failed_upload(&failed_uploads[0])
)
}
}
pub fn dedupe_upload_requests(requests: Vec<ReplayUploadRequest>) -> Vec<ReplayUploadRequest> {
let mut deduped = Vec::new();
for request in requests {
upsert_failed_upload(&mut deduped, request);
}
deduped
}
pub fn upsert_failed_upload(
failed_uploads: &mut Vec<ReplayUploadRequest>,
request: ReplayUploadRequest,
) {
if let Some(existing) = failed_uploads
.iter_mut()
.find(|failure| is_same_upload_request(failure, &request))
{
*existing = request;
} else {
failed_uploads.push(request);
}
}
pub fn is_same_upload_request(left: &ReplayUploadRequest, right: &ReplayUploadRequest) -> bool {
is_same_upload(left, &right.target_name, &right.match_id)
}
pub fn is_same_upload(request: &ReplayUploadRequest, target_name: &str, match_id: &str) -> bool {
request.target_name == target_name && request.match_id == match_id
}
pub fn failed_upload<'a>(
failed_uploads: &'a [ReplayUploadRequest],
target_name: &str,
match_id: &str,
) -> Option<&'a ReplayUploadRequest> {
failed_uploads
.iter()
.find(|failure| is_same_upload(failure, target_name, match_id))
}
pub fn upload_failure_reason(
failed_uploads: &[ReplayUploadRequest],
target_name: &str,
match_id: &str,
) -> String {
failed_upload(failed_uploads, target_name, match_id)
.and_then(|failure| failure.reason.clone())
.unwrap_or_default()
}
pub fn format_failed_upload(failure: &ReplayUploadRequest) -> String {
let base = format!(
"{} to {}",
short_match_id(&failure.match_id),
failure.target_name
);
match &failure.reason {
Some(reason) => format!("{base}: {reason}"),
None => base,
}
}
pub fn format_failed_upload_retry_label(failure: &ReplayUploadRequest) -> String {
format!("Retry {}", format_failed_upload(failure))
}
pub fn auto_upload_label(summary: &AppSummary) -> &'static str {
if summary.auto_upload {
"enabled"
} else {
"disabled"
}
}
pub fn tray_sync_label(sync_run: &SyncRunState) -> String {
if sync_run.running {
return sync_run
.last_started_at
.as_ref()
.map(|started| format!("Sync running since {started}"))
.unwrap_or_else(|| "Sync running".to_string());
}
if let Some(error) = &sync_run.last_error {
return sync_run
.last_completed_at
.as_ref()
.map(|completed| format!("Last sync failed at {completed}: {error}"))
.unwrap_or_else(|| format!("Last sync failed: {error}"));
}
match (&sync_run.last_completed_at, &sync_run.last_summary) {
(Some(completed), Some(summary)) => format!(
"Last sync {completed}: {} uploaded, {} duplicate, {} cached, {} failed",
summary.uploaded, summary.duplicates, summary.cached, summary.failed
),
(Some(completed), None) => format!("Last sync {completed}"),
_ => "No sync run yet".to_string(),
}
}
pub fn tray_tooltip(summary: &AppSummary, sync_run: &SyncRunState, failed_count: usize) -> String {
format!(
"rlru\n{}\nAuto upload: {}, {}\nFailed uploads: {}",
tray_sync_label(sync_run),
auto_upload_label(summary),
summary.interval,
failed_count
)
}
pub(super) fn platform_label(platform: &PlayerPlatform) -> &'static str {
match platform {
PlayerPlatform::Epic => "Epic",
PlayerPlatform::Steam => "Steam",
PlayerPlatform::PlayStation => "PlayStation",
PlayerPlatform::Xbox => "Xbox",
PlayerPlatform::Nintendo => "Nintendo",
}
}
pub(super) fn auth_label(auth: &TargetAuth) -> String {
match auth {
TargetAuth::None => "No auth".to_string(),
TargetAuth::AuthorizationHeader { .. } => "Authorization header".to_string(),
TargetAuth::Bearer { .. } => "Bearer token".to_string(),
TargetAuth::BearerEnv { variable } => {
if std::env::var_os(variable).is_some() {
format!("Bearer env token ({variable})")
} else {
format!("Bearer env token missing ({variable})")
}
}
TargetAuth::BearerCommand { command } => command
.first()
.map(|program| format!("Bearer command token ({program})"))
.unwrap_or_else(|| "Bearer command token missing command".to_string()),
}
}
pub(super) fn format_record_start_timestamp(timestamp: i64) -> String {
use chrono::TimeZone;
chrono::Local
.timestamp_opt(timestamp, 0)
.single()
.map(|datetime| datetime.format("%Y-%m-%d %H:%M:%S %Z").to_string())
.unwrap_or_else(|| timestamp.to_string())
}