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]]
|
[[package]]
|
||||||
name = "ae_anons"
|
name = "ae_anons"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"askama",
|
"askama",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ae_anons"
|
name = "ae_anons"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Alexey Barabanov <a.barabanov@tvstart.ru>"]
|
authors = ["Alexey Barabanov <a.barabanov@tvstart.ru>"]
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -12,17 +12,10 @@
|
|||||||
|
|
||||||
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
Автоматизированная система для создания спортивных анонсов с использованием шаблонов After Effects через Nexrender, с данными из электронных таблиц Synology Office.
|
||||||
|
|
||||||
## Новое в версии 0.2.3
|
## Новое в версии v0.2.4
|
||||||
|
|
||||||
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
- Групповая отправка заданий по 3 (оригинал + today + tomorrow) для сохранения порядка в очереди
|
||||||
- Тёмная/светлая тема с автоопределением ОС
|
- Улучшена читаемость логов отправки (добавлен UID)
|
||||||
- Сортировка по всем колонкам
|
|
||||||
- Фильтрация по имени файла/UID
|
|
||||||
- Остановка всех активных заданий
|
|
||||||
- Автообновление каждые 60 секунд
|
|
||||||
- Улучшенное debug-логирование
|
|
||||||
- Исправлена обработка UNC/SMB путей
|
|
||||||
- Нормализация множественных пробелов в именах файлов"
|
|
||||||
|
|
||||||
## Обзор
|
## Обзор
|
||||||
|
|
||||||
@@ -576,6 +569,7 @@ ae_anons/
|
|||||||
## 🙏 Благодарности
|
## 🙏 Благодарности
|
||||||
|
|
||||||
### Nexrender
|
### Nexrender
|
||||||
|
|
||||||
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
Особая благодарность проекту **[Nexrender](https://github.com/inlife/nexrender)**
|
||||||
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
([@inlife](https://github.com/inlife) и контрибьюторам) за создание надёжной платформы
|
||||||
для автоматизации After Effects.
|
для автоматизации After Effects.
|
||||||
@@ -587,7 +581,7 @@ ae_anons/
|
|||||||
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
- **Иконки**: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
- **Шрифты**: [SIL OFL 1.1](https://scripts.sil.org/OFL)
|
- **Шрифты**: [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)
|
- Веб-интерфейс с настраиваемым портом (WEB_PORT)
|
||||||
- Улучшенное debug-логирование
|
- Улучшенное debug-логирование
|
||||||
|
|||||||
102
src/processor.rs
102
src/processor.rs
@@ -303,13 +303,11 @@ async fn generate_and_submit_jobs(
|
|||||||
|
|
||||||
// Вспомогательная функция для поиска значения по части ключа
|
// Вспомогательная функция для поиска значения по части ключа
|
||||||
fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
|
fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
|
||||||
// Точное совпадение
|
|
||||||
if let Some(val) = row.get(key_part) {
|
if let Some(val) = row.get(key_part) {
|
||||||
if !val.is_empty() {
|
if !val.is_empty() {
|
||||||
return Some(val.clone());
|
return Some(val.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Поиск по содержанию (без учёта регистра)
|
|
||||||
for (k, v) in row.iter() {
|
for (k, v) in row.iter() {
|
||||||
if k.to_lowercase().contains(&key_part.to_lowercase()) && !v.is_empty() {
|
if k.to_lowercase().contains(&key_part.to_lowercase()) && !v.is_empty() {
|
||||||
return Some(v.clone());
|
return Some(v.clone());
|
||||||
@@ -427,55 +425,61 @@ async fn generate_and_submit_jobs(
|
|||||||
let http_client = Client::new();
|
let http_client = Client::new();
|
||||||
let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len());
|
let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len());
|
||||||
|
|
||||||
let mut tasks = Vec::with_capacity(jobs.len());
|
// Отправляем задания группами по 3 (оригинал + today + tomorrow)
|
||||||
for job in jobs {
|
for chunk in jobs.chunks(3) {
|
||||||
let client = http_client.clone();
|
let mut tasks = Vec::with_capacity(3);
|
||||||
let api_url = config.nexrender_api_url.clone();
|
|
||||||
let config_clone = config.clone();
|
for job in chunk {
|
||||||
|
let client = http_client.clone();
|
||||||
tasks.push(tokio::spawn(async move {
|
let api_url = config.nexrender_api_url.clone();
|
||||||
let nexrender_job = job.to_nexrender_job(&config_clone);
|
let config_clone = config.clone();
|
||||||
info!("Submitting job: {}", job.outfile_name);
|
let job_owned = job.clone();
|
||||||
|
|
||||||
if log_enabled!(Level::Debug) {
|
tasks.push(tokio::spawn(async move {
|
||||||
debug!(
|
let nexrender_job = job_owned.to_nexrender_job(&config_clone);
|
||||||
"Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'",
|
info!("Submitting job: {}", job_owned.outfile_name);
|
||||||
job.sport, job.league, job.channel, job.team_a, job.team_b
|
|
||||||
);
|
if log_enabled!(Level::Debug) {
|
||||||
}
|
debug!(
|
||||||
|
"Job details: sport='{}', league='{}', channel='{}', team_a='{}', team_b='{}'",
|
||||||
let response = client.post(&api_url).json(&nexrender_job).send().await?;
|
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()) {
|
let response = client.post(&api_url).json(&nexrender_job).send().await?;
|
||||||
Ok::<_, anyhow::Error>(Some((uid.to_string(), job.outfile_name)))
|
|
||||||
|
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 {
|
} else {
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await.unwrap_or_default();
|
||||||
|
error!("Failed to submit job ({}): {}", status, text);
|
||||||
Ok(None)
|
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: {} (UID: {})", outfile_name, uid);
|
||||||
|
submitted_jobs.push((uid, outfile_name));
|
||||||
for task in tasks {
|
}
|
||||||
match task.await {
|
Ok(Ok(None)) => {
|
||||||
Ok(Ok(Some((uid, outfile_name)))) => {
|
debug!("Job submission returned no UID");
|
||||||
info!("Job submitted: {}", outfile_name);
|
}
|
||||||
submitted_jobs.push((uid, outfile_name));
|
Ok(Err(e)) => {
|
||||||
}
|
error!("Job submission error: {}", e);
|
||||||
Ok(Ok(None)) => {
|
}
|
||||||
debug!("Job submission returned no UID");
|
Err(e) => {
|
||||||
}
|
error!("Task join error: {}", e);
|
||||||
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<Vec<Value>> {
|
|||||||
} else {
|
} else {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user