From 9929dab66fffbd96912b8b3b4be3ab8406e06936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=91=D0=B0?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Fri, 17 Apr 2026 18:29:04 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=8F=20=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B9=20=D0=BF?= =?UTF-8?q?=D0=BE=203=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=BE=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=80=D1=8F?= =?UTF-8?q?=D0=B4=D0=BA=D0=B0=20=D0=B2=20=D0=BE=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Отправка заданий группами по 3 (оригинал + today + tomorrow) параллельно - Сохранение порядка внутри группы для корректной FIFO очереди Nexrender - Добавлен UID в логи отправки заданий - Версия обновлена до 0.2.4 --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 23 +++++------ src/processor.rs | 102 ++++++++++++++++++++++++----------------------- 4 files changed, 66 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 169c52d..f7177ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ae_anons" -version = "0.2.3" +version = "0.2.4" dependencies = [ "anyhow", "askama", diff --git a/Cargo.toml b/Cargo.toml index c068a49..ba9b722 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ae_anons" -version = "0.2.3" +version = "0.2.4" edition = "2021" license = "MIT" authors = ["Alexey Barabanov "] diff --git a/README.md b/README.md index 394b59e..a5df4b7 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,10 @@ Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office. -## Новое в версии 0.2.3 +## Новое в версии v0.2.4 -- Веб-интерфейс с настраиваемым портом (WEB_PORT) -- Тёмная/светлая тема с автоопределением ОС -- Сортировка по всем колонкам -- Фильтрация по имени файла/UID -- Остановка всех активных заданий -- Автообновление каждые 60 секунд -- Улучшенное debug-логирование -- Исправлена обработка UNC/SMB путей -- Нормализация множественных пробелов в именах файлов" +- Групповая отправка заданий по 3 (оригинал + today + tomorrow) для сохранения порядка в очереди +- Улучшена читаемость логов отправки (добавлен UID) ## Обзор @@ -576,6 +569,7 @@ ae_anons/ ## 🙏 Благодарности ### Nexrender + Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)** ([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы для автоматизации After Effects. @@ -587,7 +581,7 @@ ae_anons/ - **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) - **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL) -© 2023 Fonticons, Inc. — https://fontawesome.com +© 2023 Fonticons, Inc. — ## Лицензия @@ -616,7 +610,12 @@ ae_anons/ ## История изменений -### v0.2.3 (текущая) +### v0.2.4 (текущая) + +- Групповая отправка заданий по 3 (оригинал + today + tomorrow) для сохранения порядка в очереди +- Улучшена читаемость логов отправки (добавлен UID) + +### v0.2.3 - Веб-интерфейс с настраиваемым портом (WEB_PORT) - Улучшенное debug-логирование diff --git a/src/processor.rs b/src/processor.rs index 97de5e7..4a655a8 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -303,13 +303,11 @@ async fn generate_and_submit_jobs( // Вспомогательная функция для поиска значения по части ключа fn get_cell_fuzzy(row: &HashMap, key_part: &str) -> Option { - // Точное совпадение 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()); @@ -427,55 +425,61 @@ async fn generate_and_submit_jobs( let http_client = Client::new(); let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len()); - let mut tasks = Vec::with_capacity(jobs.len()); - for job in jobs { - let client = http_client.clone(); - let api_url = config.nexrender_api_url.clone(); - let config_clone = config.clone(); - - tasks.push(tokio::spawn(async move { - 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() { - let result: Value = response.json().await?; - if let Some(uid) = result.get("uid").and_then(|u| u.as_str()) { - Ok::<_, anyhow::Error>(Some((uid.to_string(), job.outfile_name))) + // Отправляем задания группами по 3 (оригинал + today + tomorrow) + for chunk in jobs.chunks(3) { + let mut tasks = Vec::with_capacity(3); + + for job in chunk { + let client = http_client.clone(); + let api_url = config.nexrender_api_url.clone(); + let config_clone = config.clone(); + let job_owned = job.clone(); + + tasks.push(tokio::spawn(async move { + let nexrender_job = job_owned.to_nexrender_job(&config_clone); + info!("Submitting job: {}", job_owned.outfile_name); + + if log_enabled!(Level::Debug) { + debug!( + "Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'", + job_owned.sport, job_owned.league, job_owned.channel, job_owned.team_a, job_owned.team_b + ); + } + + let response = client.post(&api_url).json(&nexrender_job).send().await?; + + if response.status().is_success() { + let result: Value = response.json().await?; + if let Some(uid) = result.get("uid").and_then(|u| u.as_str()) { + Ok::<_, anyhow::Error>(Some((uid.to_string(), job_owned.outfile_name))) + } else { + Ok(None) + } } else { + let status = response.status(); + let text = response.text().await.unwrap_or_default(); + error!("Failed to submit job ({}): {}", status, text); Ok(None) } - } else { - let status = response.status(); - let text = response.text().await.unwrap_or_default(); - error!("Failed to submit job ({}): {}", status, text); - Ok(None) - } - })); - } - - for task in tasks { - match task.await { - Ok(Ok(Some((uid, outfile_name)))) => { - info!("Job submitted: {}", outfile_name); - submitted_jobs.push((uid, outfile_name)); - } - Ok(Ok(None)) => { - debug!("Job submission returned no UID"); - } - Ok(Err(e)) => { - error!("Job submission error: {}", e); - } - Err(e) => { - error!("Task join error: {}", e); + })); + } + + // Ждём завершения группы + for task in tasks { + match task.await { + Ok(Ok(Some((uid, outfile_name)))) => { + info!("Job submitted: {} (UID: {})", outfile_name, uid); + submitted_jobs.push((uid, outfile_name)); + } + Ok(Ok(None)) => { + debug!("Job submission returned no UID"); + } + Ok(Err(e)) => { + error!("Job submission error: {}", e); + } + Err(e) => { + error!("Task join error: {}", e); + } } } } @@ -518,4 +522,4 @@ pub async fn fetch_all_jobs(api_url: &str) -> Result> { } else { Ok(Vec::new()) } -} +} \ No newline at end of file