feat: групповая отправка заданий по 3 для сохранения порядка в очереди
- Отправка заданий группами по 3 (оригинал + today + tomorrow) параллельно - Сохранение порядка внутри группы для корректной FIFO очереди Nexrender - Добавлен UID в логи отправки заданий - Версия обновлена до 0.2.4
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "ae_anons"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ae_anons"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = ["Alexey Barabanov <a.barabanov@tvstart.ru>"]
|
||||
|
||||
23
README.md
23
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. — <https://fontawesome.com>
|
||||
|
||||
## Лицензия
|
||||
|
||||
@@ -616,7 +610,12 @@ ae_anons/
|
||||
|
||||
## История изменений
|
||||
|
||||
### v0.2.3 (текущая)
|
||||
### v0.2.4 (текущая)
|
||||
|
||||
- Групповая отправка заданий по 3 (оригинал + today + tomorrow) для сохранения порядка в очереди
|
||||
- Улучшена читаемость логов отправки (добавлен UID)
|
||||
|
||||
### v0.2.3
|
||||
|
||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||
- Улучшенное debug-логирование
|
||||
|
||||
@@ -303,13 +303,11 @@ async fn generate_and_submit_jobs(
|
||||
|
||||
// Вспомогательная функция для поиска значения по части ключа
|
||||
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());
|
||||
@@ -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();
|
||||
// Отправляем задания группами по 3 (оригинал + today + tomorrow)
|
||||
for chunk in jobs.chunks(3) {
|
||||
let mut tasks = Vec::with_capacity(3);
|
||||
|
||||
tasks.push(tokio::spawn(async move {
|
||||
let nexrender_job = job.to_nexrender_job(&config_clone);
|
||||
info!("Submitting job: {}", job.outfile_name);
|
||||
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();
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
tasks.push(tokio::spawn(async move {
|
||||
let nexrender_job = job_owned.to_nexrender_job(&config_clone);
|
||||
info!("Submitting job: {}", job_owned.outfile_name);
|
||||
|
||||
let response = client.post(&api_url).json(&nexrender_job).send().await?;
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
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)))
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user