From ff0652b992216a1efbcf8ad71282dae180201067 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: Thu, 7 May 2026 14:32:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=84=D0=B8=D1=82=D0=B1=D0=B5=D0=BA=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BD=D0=BE=D1=81=D0=B0=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/file_access.rs | 8 ++++---- src/static/index.html | 44 +++++++++++++++++++++++++++++---------- src/web.rs | 48 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index f12bff5..2a6238f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ logs/ *.log out.txt +.build_timing # Выходные файлы output/ diff --git a/src/file_access.rs b/src/file_access.rs index 37a374d..05cb97c 100644 --- a/src/file_access.rs +++ b/src/file_access.rs @@ -147,11 +147,11 @@ impl SmbFileAccess { /// Построить полный UNC путь к файлу, гарантируя один слеш между базой и именем fn make_file_unc(base: &UncPath, filename: &str) -> Result { - let base_str = base.to_string(); // \\server\share\path - // Убираем завершающий слеш, если есть + let base_str = base.to_string(); let clean_base = base_str.trim_end_matches('\\'); - // Убираем возможный ведущий слеш у filename, чтобы избежать двойного - let clean_filename = filename.trim_start_matches('\\'); + // Заменяем все прямые слеши на обратные для UNC + let sanitized = filename.replace('/', "\\"); + let clean_filename = sanitized.trim_start_matches('\\'); let full = format!("{}\\{}", clean_base, clean_filename); UncPath::from_str(&full).map_err(|e| anyhow!("Invalid file UNC: {}", e)) } diff --git a/src/static/index.html b/src/static/index.html index a50ad1a..3eb2e02 100644 --- a/src/static/index.html +++ b/src/static/index.html @@ -222,21 +222,23 @@ const previewBtn = job.state === 'finished' ? `` : ''; - const moveBtn = job.state === 'finished' - ? `` + // Заглушка‑иконка, обновится асинхронно + const moveIcon = job.state === 'finished' + ? `` : ''; return ` -
${escapeHtml(job.outfile_name)}
- ${escapeHtml(job.state)} - ${created} - ${updated} - ${job.uid.substring(0, 10)}... - ${previewBtn} - ${moveBtn} - `; +
${escapeHtml(job.outfile_name)}
+ ${escapeHtml(job.state)} + ${created} + ${updated} + ${job.uid.substring(0, 10)}... + ${previewBtn} + ${moveIcon} + `; }).join(''); + // Запускаем проверку статусов после рендера + updateMoveStatuses(); } - function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function getStateClass(state) { const classes = { 'finished': 'badge-finished', 'started': 'badge-started', 'processing': 'badge-processing', 'queued': 'badge-queued', 'error': 'badge-error', 'pending': 'badge-pending' }; @@ -375,6 +377,26 @@ } } + async function updateMoveStatuses() { + const icons = document.querySelectorAll('.move-icon'); + for (const icon of icons) { + const filename = icon.dataset.file; + try { + const resp = await fetch('/api/videos/' + encodeURIComponent(filename) + '/status'); + const status = await resp.json(); + const exists = status.exists_in_dest; + const subfolder = status.subfolder; + if (exists) { + icon.innerHTML = ``; + } else { + icon.innerHTML = ``; + } + } catch (e) { + icon.innerHTML = ``; + } + } + } + // ========== ИНИЦИАЛИЗАЦИЯ ========== initTheme(); refreshJobs(); diff --git a/src/web.rs b/src/web.rs index 6a3f609..d8a9d2f 100644 --- a/src/web.rs +++ b/src/web.rs @@ -121,6 +121,7 @@ pub async fn run_web_server(config: Config) -> anyhow::Result<()> { .route("/api/videos", get(list_videos)) .route("/api/videos/{filename}", get(stream_video)) .route("/api/videos/{filename}/move", post(move_video)) + .route("/api/videos/{filename}/status", get(get_video_status)) .layer(TraceLayer::new_for_http()) .with_state(state); @@ -361,15 +362,37 @@ async fn move_video( .source_access .read_file(&filename) .await - .map_err(|e| AppError(StatusCode::NOT_FOUND, e.to_string()))?; + .map_err(|e| AppError(StatusCode::NOT_FOUND, format!("File not found: {}", e)))?; + // Определяем целевую подпапку по имени файла + let subfolder = if filename.contains("_Today") { + "Today" + } else if filename.contains("_Tomorrow") { + "Tomorrow" + } else { + "Data" + }; + let dest_filename = format!("{}/{}", subfolder, filename); + + // Проверяем, существует ли уже файл в целевой папке + if state.dest_access.read_file(&dest_filename).await.is_ok() { + return Ok(( + StatusCode::OK, + format!("File {} already exists in {} folder.", filename, subfolder), + )); + } + + // Запись state .dest_access - .write_file(&filename, &data) + .write_file(&dest_filename, &data) .await .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - let mut message = format!("File {} moved successfully.", filename); + let mut message = format!( + "File {} moved successfully to {} folder.", + filename, subfolder + ); if remove { state @@ -405,6 +428,25 @@ async fn fa_brands_woff2() -> impl IntoResponse { ) } +async fn get_video_status( + Path(filename): Path, + State(state): State, +) -> Result, AppError> { + let subfolder = if filename.contains("_Today") { + "Today" + } else if filename.contains("_Tomorrow") { + "Tomorrow" + } else { + "Data" + }; + let dest_filename = format!("{}/{}", subfolder, filename); + let exists = state.dest_access.read_file(&dest_filename).await.is_ok(); + Ok(Json(serde_json::json!({ + "exists_in_dest": exists, + "subfolder": subfolder + }))) +} + struct AppError(StatusCode, String); impl IntoResponse for AppError {