use std::future::Future;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use super::FileClient;
use crate::errors::{NetDiskError, NetDiskResult};
pub(crate) trait FileManagementExt {
fn create_folder(&self, path: &str) -> impl Future<Output = NetDiskResult<FolderInfo>> + Send;
fn create_folder_with_options(
&self,
path: &str,
options: FolderCreateOptions,
) -> impl Future<Output = NetDiskResult<FolderInfo>> + Send;
fn rename(&self, path: &str, new_name: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
fn delete(&self, path: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
fn move_file(&self, path: &str, dest: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
fn copy_file(&self, path: &str, dest: &str) -> impl Future<Output = NetDiskResult<()>> + Send;
}
impl FileManagementExt for FileClient {
async fn create_folder(&self, path: &str) -> NetDiskResult<FolderInfo> {
self.create_folder_with_options(path, FolderCreateOptions::default())
.await
}
async fn create_folder_with_options(
&self,
path: &str,
options: FolderCreateOptions,
) -> NetDiskResult<FolderInfo> {
let token = self.token_getter.get_token().await?;
let params = [("method", "create"), ("access_token", &token.access_token)];
let form = [
("path", path),
("isdir", "1"),
("rtype", &options.rtype.to_string()),
("mode", &options.mode.to_string()),
];
debug!(
"Creating folder at path: {} with options: {:?}",
path, options
);
let response: FolderResponse = self
.http_client()
.post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
.await?;
if response.errno != 0 {
let errmsg = response.errmsg.as_deref().unwrap_or("Unknown error");
return Err(NetDiskError::api_error(response.errno, errmsg));
}
info!("Folder created successfully at: {}", path);
Ok(FolderInfo {
fs_id: response.fs_id,
path: response.path.unwrap_or_else(|| path.to_string()),
ctime: response.ctime,
mtime: response.mtime,
isdir: response.isdir,
category: response.category,
})
}
async fn rename(&self, path: &str, new_name: &str) -> NetDiskResult<()> {
let token = self.token_getter.get_token().await?;
let filelist = serde_json::json!([
{
"path": path,
"newname": new_name
}
]);
let params = [
("method", "filemanager"),
("access_token", &token.access_token),
("opera", "rename"),
];
let form = [
("async", "0"),
("filelist", &filelist.to_string()),
("ondup", "overwrite"),
];
debug!("Renaming path: {} to: {}", path, new_name);
let response: FileOperationResponse = self
.http_client()
.post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
.await?;
if response.errno != 0 {
return Err(NetDiskError::api_error(response.errno, "Rename failed"));
}
info!("Renamed successfully: {} -> {}", path, new_name);
Ok(())
}
async fn delete(&self, path: &str) -> NetDiskResult<()> {
let token = self.token_getter.get_token().await?;
let filelist = serde_json::json!([
{
"path": path
}
]);
let params = [
("method", "filemanager"),
("access_token", &token.access_token),
("opera", "delete"),
];
let form = [
("async", "0"),
("filelist", &filelist.to_string()),
("ondup", "overwrite"),
];
debug!("Deleting path: {}", path);
let response: FileOperationResponse = self
.http_client()
.post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
.await?;
if response.errno != 0 {
return Err(NetDiskError::api_error(response.errno, "Delete failed"));
}
info!("Deleted successfully: {}", path);
Ok(())
}
async fn move_file(&self, path: &str, dest: &str) -> NetDiskResult<()> {
let token = self.token_getter.get_token().await?;
let filename = path.rsplit('/').next().unwrap_or(path);
let filelist = serde_json::json!([
{
"path": path,
"dest": dest,
"newname": filename,
"ondup": "fail"
}
]);
let params = [
("method", "filemanager"),
("access_token", &token.access_token),
("opera", "move"),
];
let form = [
("async", "0"),
("filelist", &filelist.to_string()),
("ondup", "fail"),
];
debug!("Moving path: {} to: {}", path, dest);
let response: FileOperationResponse = self
.http_client()
.post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
.await?;
if response.errno != 0 {
return Err(NetDiskError::api_error(response.errno, "Move failed"));
}
info!("Moved successfully: {} -> {}", path, dest);
Ok(())
}
async fn copy_file(&self, path: &str, dest: &str) -> NetDiskResult<()> {
let token = self.token_getter.get_token().await?;
let filename = path.rsplit('/').next().unwrap_or(path);
let filelist = serde_json::json!([
{
"path": path,
"dest": dest,
"newname": filename,
"ondup": "fail"
}
]);
let params = [
("method", "filemanager"),
("access_token", &token.access_token),
("opera", "copy"),
];
let form = [
("async", "0"),
("filelist", &filelist.to_string()),
("ondup", "fail"),
];
debug!("Copying path: {} to: {}", path, dest);
let response: FileOperationResponse = self
.http_client()
.post_form("/rest/2.0/xpan/file", Some(&form), Some(¶ms))
.await?;
if response.errno != 0 {
return Err(NetDiskError::api_error(response.errno, "Copy failed"));
}
info!("Copied successfully: {} -> {}", path, dest);
Ok(())
}
}
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct FolderCreateOptions {
pub rtype: i32,
pub mode: i32,
}
impl FolderCreateOptions {
pub fn new() -> Self {
Self::default()
}
pub fn rtype(mut self, rtype: i32) -> Self {
self.rtype = rtype;
self
}
pub fn mode(mut self, mode: i32) -> Self {
self.mode = mode;
self
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct FolderInfo {
pub fs_id: Option<u64>,
pub path: String,
pub ctime: Option<u64>,
pub mtime: Option<u64>,
pub isdir: Option<i32>,
pub category: Option<i32>,
}
#[derive(Debug, Deserialize)]
struct FolderResponse {
errno: i32,
errmsg: Option<String>,
fs_id: Option<u64>,
path: Option<String>,
ctime: Option<u64>,
mtime: Option<u64>,
isdir: Option<i32>,
category: Option<i32>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct FileOperationResponse {
errno: i32,
info: Option<Vec<FileProcessStatus>>,
taskid: Option<u64>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct FileProcessStatus {
errno: i32,
path: String,
}