Compare commits
2 Commits
0b2091b1e6
...
v0.2.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f2fe50eab | |||
| fbcb619544 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ae_anons"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
license-file = "LICENSE"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use anyhow::{Context, Result};
|
||||
use dotenv::dotenv;
|
||||
use std::env;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -24,8 +23,6 @@ pub struct Config {
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> Result<Self> {
|
||||
dotenv().ok();
|
||||
|
||||
Ok(Self {
|
||||
nas_fqdn: env::var("NAS_FQDN").context("NAS_FQDN not set")?,
|
||||
nas_user: env::var("NAS_USER").context("NAS_USER not set")?,
|
||||
|
||||
@@ -7,6 +7,7 @@ mod web;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use config::Config;
|
||||
use dotenv::dotenv;
|
||||
use log::info;
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -23,6 +24,10 @@ struct Cli {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Загружаем .env до инициализации логгера
|
||||
dotenv().ok();
|
||||
|
||||
// Инициализируем логгер
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
@@ -35,7 +40,6 @@ async fn main() -> Result<()> {
|
||||
);
|
||||
web::run_web_server(config).await?;
|
||||
} else {
|
||||
// По умолчанию или с флагом --once выполняем однократную обработку
|
||||
info!(
|
||||
"Starting AE Anons processor v{} (one-time mode)",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
@@ -45,4 +49,4 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,15 +537,23 @@ impl LogoRegistry {
|
||||
self.logos.insert((team, sport), link);
|
||||
}
|
||||
|
||||
// В impl LogoRegistry, функция find()
|
||||
pub fn find(&self, team: &str, sport: &str) -> Option<String> {
|
||||
log::debug!("LogoRegistry::find: team='{}', sport='{}'", team, sport);
|
||||
|
||||
if let Some(link) = self.logos.get(&(team.to_string(), sport.to_string())) {
|
||||
log::debug!("Found exact match: {}", link);
|
||||
return Some(link.clone());
|
||||
}
|
||||
|
||||
for ((t, s), link) in &self.logos {
|
||||
if s == sport && team.starts_with(t) {
|
||||
log::debug!("Found prefix match: t='{}', link='{}'", t, link);
|
||||
return Some(link.clone());
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("No logo found for team='{}', sport='{}'", team, sport);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -603,12 +611,26 @@ fn unc_to_uri(path: &str) -> String {
|
||||
if path.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Уже правильный URI
|
||||
if path.starts_with("http://") || path.starts_with("https://") || path.starts_with("file://") {
|
||||
return path.to_string();
|
||||
}
|
||||
format!("file://{}", path.replace('\\', "/").trim_start_matches('/'))
|
||||
}
|
||||
|
||||
// Mac SMB путь: smb://server/share/path -> file://server/share/path
|
||||
if path.starts_with("smb://") {
|
||||
let without_smb = path.strip_prefix("smb:").unwrap_or(path);
|
||||
return format!("file:{}", without_smb.replace('\\', "/"));
|
||||
}
|
||||
|
||||
// Windows UNC путь: \\server\share\path -> file://server/share/path
|
||||
let normalized = path.replace('\\', "/");
|
||||
|
||||
// Убираем ведущие слеши (для UNC их быть не должно)
|
||||
let trimmed = normalized.trim_start_matches('/');
|
||||
|
||||
format!("file://{}", trimmed)
|
||||
}
|
||||
fn logo_scale_expression(target_size: &str) -> String {
|
||||
format!(
|
||||
"if (width > height) {{max_size = width;}} else {{max_size = height;}} var real_size = {}/max_size*100;[real_size,real_size]",
|
||||
|
||||
106
src/processor.rs
106
src/processor.rs
@@ -4,10 +4,10 @@ use crate::synology::SynologyClient;
|
||||
use anyhow::{anyhow, Result};
|
||||
use calamine::{Data, Reader, Xlsx};
|
||||
use chrono::{Duration, NaiveDate};
|
||||
use log::{debug, error, info};
|
||||
use log::{debug, error, info, log_enabled, Level};
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -211,10 +211,8 @@ fn cell_to_string_optimized(cell: &Data) -> String {
|
||||
if is_excel_date(*f) {
|
||||
excel_date_to_string(*f)
|
||||
} else if f.fract() == 0.0 {
|
||||
// Используем itoa для целых чисел (опционально)
|
||||
format!("{:.0}", f)
|
||||
} else {
|
||||
// Используем ryu для float (опционально)
|
||||
f.to_string()
|
||||
}
|
||||
}
|
||||
@@ -256,7 +254,6 @@ fn excel_date_to_string(serial: f64) -> String {
|
||||
let days = serial as i64;
|
||||
let base = NaiveDate::from_ymd_opt(1899, 12, 30).unwrap();
|
||||
if let Some(date) = base.checked_add_signed(Duration::days(days)) {
|
||||
// Используем метод format напрямую - он публичный
|
||||
date.format("%d.%m.%Y").to_string()
|
||||
} else {
|
||||
serial.to_string()
|
||||
@@ -296,6 +293,7 @@ async fn generate_and_submit_jobs(
|
||||
.get_sheet("CHANELL")
|
||||
.ok_or_else(|| anyhow!("Sheet 'CHANELL' not found"))?;
|
||||
|
||||
// Загружаем sport packs
|
||||
let packs: HashMap<String, String> = sport_sheet
|
||||
.rows
|
||||
.iter()
|
||||
@@ -303,16 +301,63 @@ async fn generate_and_submit_jobs(
|
||||
.collect();
|
||||
info!("Loaded {} sport packs", packs.len());
|
||||
|
||||
// Вспомогательная функция для поиска значения по части ключа
|
||||
fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
|
||||
// Точное совпадение
|
||||
if let Some(val) = row.get(key_part) {
|
||||
if !val.is_empty() {
|
||||
return Some(val.clone());
|
||||
}
|
||||
}
|
||||
// Поиск по содержанию (без учёта регистра)
|
||||
for (k, v) in row.iter() {
|
||||
if k.to_lowercase().contains(&key_part.to_lowercase()) && !v.is_empty() {
|
||||
return Some(v.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Загружаем team logos
|
||||
debug!("TEAMS headers: {:?}", teams_sheet.headers);
|
||||
for (i, row) in teams_sheet.rows.iter().take(3).enumerate() {
|
||||
debug!("TEAMS row {}: {:?}", i, row);
|
||||
}
|
||||
|
||||
let mut logos = LogoRegistry::with_capacity(teams_sheet.rows.len());
|
||||
for row in &teams_sheet.rows {
|
||||
if let (Some(team), Some(sport), Some(link)) =
|
||||
(row.get("TEAM"), row.get("SPORT"), row.get("LINK"))
|
||||
{
|
||||
let team = get_cell_fuzzy(row, "TEAM");
|
||||
let sport = get_cell_fuzzy(row, "SPORT");
|
||||
let link = get_cell_fuzzy(row, "LINK");
|
||||
|
||||
if let (Some(team), Some(sport), Some(link)) = (team, sport, link) {
|
||||
logos.insert(team.clone(), sport.clone(), link.clone());
|
||||
}
|
||||
}
|
||||
info!("Loaded {} team logos", teams_sheet.rows.len());
|
||||
|
||||
// Дебаг: выводим статистику по TEAMS
|
||||
if log_enabled!(Level::Debug) {
|
||||
let sports: HashSet<_> = teams_sheet
|
||||
.rows
|
||||
.iter()
|
||||
.filter_map(|r| r.get("SPORT"))
|
||||
.collect();
|
||||
debug!("Sports found in TEAMS sheet: {:?}", sports);
|
||||
|
||||
for sport in sports.iter() {
|
||||
let examples: Vec<_> = teams_sheet
|
||||
.rows
|
||||
.iter()
|
||||
.filter(|r| r.get("SPORT") == Some(*sport))
|
||||
.take(5)
|
||||
.filter_map(|r| r.get("TEAM"))
|
||||
.collect();
|
||||
debug!(" TEAMS examples for '{}': {:?}", sport, examples);
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем channel logos
|
||||
let channels: HashMap<String, String> = channel_sheet
|
||||
.rows
|
||||
.iter()
|
||||
@@ -320,11 +365,37 @@ async fn generate_and_submit_jobs(
|
||||
.collect();
|
||||
info!("Loaded {} channel logos", channels.len());
|
||||
|
||||
if log_enabled!(Level::Debug) {
|
||||
debug!("Channels loaded: {:?}", channels.keys().collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
let mut jobs: Vec<JobData> = Vec::with_capacity(start_sheet.rows.len() * 3);
|
||||
|
||||
// Собираем статистику по ненайденным логотипам для дебага
|
||||
let mut missing_teams: HashSet<String> = HashSet::new();
|
||||
let mut missing_sports: HashSet<String> = HashSet::new();
|
||||
|
||||
for (idx, row) in start_sheet.rows.iter().enumerate() {
|
||||
if let Some(state) = row.get("STATE") {
|
||||
if state == "FALSE" {
|
||||
let team_a = row.get("TEAM A").cloned().unwrap_or_default();
|
||||
let team_b = row.get("TEAM B").cloned().unwrap_or_default();
|
||||
let sport = row.get("SPORT").cloned().unwrap_or_default();
|
||||
|
||||
if log_enabled!(Level::Debug) {
|
||||
let logo_a = logos.find(&team_a, &sport);
|
||||
let logo_b = logos.find(&team_b, &sport);
|
||||
|
||||
if !team_a.is_empty() && logo_a.is_none() {
|
||||
missing_teams.insert(team_a.clone());
|
||||
missing_sports.insert(sport.clone());
|
||||
}
|
||||
if !team_b.is_empty() && logo_b.is_none() {
|
||||
missing_teams.insert(team_b.clone());
|
||||
missing_sports.insert(sport.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(job) = JobData::from_row(row, idx, &packs, &logos, &channels) {
|
||||
jobs.extend(job.create_variants());
|
||||
}
|
||||
@@ -332,6 +403,18 @@ async fn generate_and_submit_jobs(
|
||||
}
|
||||
}
|
||||
|
||||
// Дебаг: выводим сводку по отсутствующим логотипам
|
||||
if log_enabled!(Level::Debug) && !missing_teams.is_empty() {
|
||||
debug!("=== MISSING LOGOS SUMMARY ===");
|
||||
debug!("Total unique teams missing logos: {}", missing_teams.len());
|
||||
debug!("Sports involved: {:?}", missing_sports);
|
||||
debug!(
|
||||
"Missing teams (first 20): {:?}",
|
||||
missing_teams.iter().take(20).collect::<Vec<_>>()
|
||||
);
|
||||
debug!("==============================");
|
||||
}
|
||||
|
||||
info!("Generated {} total jobs (including variants)", jobs.len());
|
||||
|
||||
if jobs.is_empty() {
|
||||
@@ -354,6 +437,13 @@ async fn generate_and_submit_jobs(
|
||||
let nexrender_job = job.to_nexrender_job(&config_clone);
|
||||
info!("Submitting job: {}", job.outfile_name);
|
||||
|
||||
if log_enabled!(Level::Debug) {
|
||||
debug!(
|
||||
"Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'",
|
||||
job.sport, job.league, job.channel, job.team_a, job.team_b
|
||||
);
|
||||
}
|
||||
|
||||
let response = client.post(&api_url).json(&nexrender_job).send().await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
|
||||
Reference in New Issue
Block a user