SMB чтение/запись работают
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@
|
|||||||
# Логи
|
# Логи
|
||||||
logs/
|
logs/
|
||||||
*.log
|
*.log
|
||||||
|
out.txt
|
||||||
|
|
||||||
# Выходные файлы
|
# Выходные файлы
|
||||||
output/
|
output/
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
13
src/web.rs
13
src/web.rs
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user