SMB чтение/запись работают

This commit is contained in:
2026-05-07 12:58:09 +03:00
parent 6ec569f6e6
commit a3fc5e2940
3 changed files with 48 additions and 20 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
# Логи # Логи
logs/ logs/
*.log *.log
out.txt
# Выходные файлы # Выходные файлы
output/ output/

View File

@@ -13,16 +13,31 @@ fn smb_url_to_unc(url_str: &str) -> Result<UncPath> {
let host = url let host = url
.host_str() .host_str()
.ok_or_else(|| anyhow!("No host in SMB URL"))?; .ok_or_else(|| anyhow!("No host in SMB URL"))?;
let path_segments: Vec<&str> = url.path().trim_matches('/').split('/').collect();
let share = path_segments.first().cloned().unwrap_or("IPC$"); // Берём сырой путь
let raw_path = url.path().trim_start_matches('/');
// Декодируем каждый сегмент с помощью urlencoding (уже есть в зависимостях)
let decoded_segments: Vec<String> = raw_path
.split('/')
.filter(|s| !s.is_empty())
.map(|s| {
urlencoding::decode(s)
.unwrap_or_else(|_| s.to_string().into())
.into_owned()
})
.collect();
if decoded_segments.is_empty() {
return Err(anyhow!("No path segments in SMB URL"));
}
let share = &decoded_segments[0];
let base = UncPath::from_str(&format!("\\\\{}\\{}", host, share)) let base = UncPath::from_str(&format!("\\\\{}\\{}", host, share))
.map_err(|e| anyhow!("Invalid UNC: {}", e))?; .map_err(|e| anyhow!("Invalid UNC: {}", e))?;
let mut full = base.clone(); let mut full = base.clone();
for &segment in path_segments.iter().skip(1) { for segment in decoded_segments.iter().skip(1) {
if !segment.is_empty() {
full = full.with_path(segment); full = full.with_path(segment);
} }
}
Ok(full) Ok(full)
} }
@@ -130,10 +145,14 @@ impl SmbFileAccess {
Ok((client, base_unc)) Ok((client, base_unc))
} }
/// Построить полный UNC путь к файлу, добавляя filename к базовому пути (который уже включает подпапки) /// Построить полный UNC путь к файлу, гарантируя один слеш между базой и именем
fn make_file_unc(base: &UncPath, filename: &str) -> Result<UncPath> { fn make_file_unc(base: &UncPath, filename: &str) -> Result<UncPath> {
let base_str = base.to_string(); // \\server\share\path let base_str = base.to_string(); // \\server\share\path
let full = format!("{}\\{}", base_str.trim_end_matches('\\'), filename); // Убираем завершающий слеш, если есть
let clean_base = base_str.trim_end_matches('\\');
// Убираем возможный ведущий слеш у filename, чтобы избежать двойного
let clean_filename = filename.trim_start_matches('\\');
let full = format!("{}\\{}", clean_base, clean_filename);
UncPath::from_str(&full).map_err(|e| anyhow!("Invalid file UNC: {}", e)) UncPath::from_str(&full).map_err(|e| anyhow!("Invalid file UNC: {}", e))
} }
} }
@@ -141,8 +160,7 @@ impl SmbFileAccess {
#[async_trait] #[async_trait]
impl FileAccess for SmbFileAccess { impl FileAccess for SmbFileAccess {
async fn list_files(&self) -> Result<Vec<String>> { async fn list_files(&self) -> Result<Vec<String>> {
// SMB list directory пока не реализован в текущем API, // Заглушка
// оставим заглушку для будущей доработки
Ok(Vec::new()) Ok(Vec::new())
} }
@@ -173,8 +191,13 @@ impl FileAccess for SmbFileAccess {
let file_path = Self::make_file_unc(&base_unc, filename)?; let file_path = Self::make_file_unc(&base_unc, filename)?;
log::info!("SMB writing file: {:?}", file_path); log::info!("SMB writing file: {:?}", file_path);
let args = let access_mask = FileAccessMask::new()
.with_generic_write(true)
.with_generic_read(true);
let mut args =
FileCreateArgs::make_overwrite(FileAttributes::default(), CreateOptions::default()); FileCreateArgs::make_overwrite(FileAttributes::default(), CreateOptions::default());
args.desired_access = access_mask;
let resource = client.create_file(&file_path, &args).await.map_err(|e| { let resource = client.create_file(&file_path, &args).await.map_err(|e| {
log::error!("Failed to create SMB file {:?}: {}", file_path, e); log::error!("Failed to create SMB file {:?}: {}", file_path, e);
anyhow::anyhow!("Failed to create SMB file: {}", e) anyhow::anyhow!("Failed to create SMB file: {}", e)
@@ -186,9 +209,12 @@ impl FileAccess for SmbFileAccess {
Ok(()) Ok(())
} }
async fn delete_file(&self, _filename: &str) -> Result<()> { async fn delete_file(&self, filename: &str) -> Result<()> {
// Удаление будет добавлено позже log::warn!(
Err(anyhow!("Delete not yet implemented for SMB")) "SMB delete not supported file left at source: {}",
filename
);
Ok(())
} }
} }

View File

@@ -144,14 +144,15 @@ async fn create_file_access(
let url = access_config.smb_url.as_ref().unwrap().clone(); let url = access_config.smb_url.as_ref().unwrap().clone();
let user = access_config.smb_user.clone(); let user = access_config.smb_user.clone();
let password = access_config.smb_pass.clone(); let password = access_config.smb_pass.clone();
let domain = access_config.smb_domain.clone(); // теперь читаем домен let domain = access_config.smb_domain.clone();
let smb_config = SmbAccessConfig { let smb_config = SmbAccessConfig {
url, url,
user, user,
password, password,
domain, domain,
}; };
Ok(Arc::new(SmbFileAccess::new(smb_config))) let access: Arc<dyn FileAccess> = Arc::new(SmbFileAccess::new(smb_config));
Ok(access)
} }
AccessType::Synology => { AccessType::Synology => {
let mut client = SynologyClient::new(&nas_config.nas_fqdn); let mut client = SynologyClient::new(&nas_config.nas_fqdn);
@@ -368,18 +369,18 @@ async fn move_video(
.await .await
.map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let mut message = format!("File {} moved successfully.", filename);
if remove { if remove {
state state
.source_access .source_access
.delete_file(&filename) .delete_file(&filename)
.await .await
.map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; .map_err(|e| AppError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
message.push_str(" Please delete the source file manually if needed.");
} }
Ok(( Ok((StatusCode::OK, message))
StatusCode::OK,
format!("File {} moved successfully", filename),
))
} }
// Шрифты Font Awesome // Шрифты Font Awesome