use crate::client::UgosClient;
use crate::error::{Result, UgosError};
use crate::types::common::ResultWrapper;
use crate::types::files::{DirListing, FileEntry, Volume};
#[allow(clippy::module_name_repetitions)]
pub trait FilesApi {
fn fs_list(&self, path: &str) -> impl Future<Output = Result<Vec<FileEntry>>> + Send;
fn fs_volumes(&self) -> impl Future<Output = Result<Vec<Volume>>> + Send;
fn fs_mkdir(&self, path: &str) -> impl Future<Output = Result<()>> + Send;
fn fs_remove(&self, paths: &[String], forever: bool)
-> impl Future<Output = Result<()>> + Send;
fn fs_rename(&self, path: &str, new_name: &str) -> impl Future<Output = Result<()>> + Send;
fn fs_download(
&self,
remote: &str,
local: &std::path::Path,
progress: &(dyn Fn(u64) + Send + Sync),
) -> impl Future<Output = Result<u64>> + Send;
fn fs_upload(
&self,
local: &std::path::Path,
remote_dir: &str,
) -> impl Future<Output = Result<String>> + Send;
}
impl FilesApi for UgosClient {
async fn fs_list(&self, path: &str) -> Result<Vec<FileEntry>> {
let body = serde_json::json!({
"path": path,
"page": 1,
"limit": 1000,
"is_shield_recycle": false,
});
let listing: DirListing = self
.post_encrypted("v2/filemgr/getDirFileListV2", &body)
.await?;
Ok(listing.right_files.files.unwrap_or_default())
}
async fn fs_volumes(&self) -> Result<Vec<Volume>> {
let resp: ResultWrapper<Vec<Volume>> = self.get("filemgr/getVolumes").await?;
Ok(resp.result)
}
async fn fs_mkdir(&self, path: &str) -> Result<()> {
let body = serde_json::json!({"path": path});
let _: serde_json::Value = self
.post_encrypted("v2/filemgr/createFolder", &body)
.await?;
Ok(())
}
async fn fs_remove(&self, paths: &[String], forever: bool) -> Result<()> {
let body = serde_json::json!({"paths": paths, "forever": forever});
let _: serde_json::Value = self.post_encrypted("v2/filemgr/delPaths", &body).await?;
Ok(())
}
async fn fs_rename(&self, path: &str, new_name: &str) -> Result<()> {
let body = serde_json::json!({"path": path, "new_name": new_name});
let _: serde_json::Value = self.post_encrypted("v2/filemgr/rename", &body).await?;
Ok(())
}
async fn fs_download(
&self,
remote: &str,
local: &std::path::Path,
progress: &(dyn Fn(u64) + Send + Sync),
) -> Result<u64> {
use std::io::Write;
let mut resp = self
.get_bytes("filemgr/downloadFile", &[("paths", remote)])
.await?;
let temp = local.with_file_name(format!(
"{}.part",
local
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("download")
));
let transfer = async {
let mut out = std::fs::File::create(&temp)
.map_err(|e| UgosError::io(format!("creating '{}'", temp.display()), e))?;
let mut written = 0u64;
while let Some(chunk) = resp.chunk().await? {
out.write_all(&chunk)
.map_err(|e| UgosError::io("writing download", e))?;
written += chunk.len() as u64;
progress(written);
}
out.sync_all()
.map_err(|e| UgosError::io(format!("flushing '{}'", temp.display()), e))?;
Ok::<u64, UgosError>(written)
}
.await;
let written = match transfer {
Ok(written) => written,
Err(e) => {
let _ = std::fs::remove_file(&temp);
return Err(e);
}
};
std::fs::rename(&temp, local)
.map_err(|e| UgosError::io(format!("moving into '{}'", local.display()), e))?;
Ok(written)
}
async fn fs_upload(&self, local: &std::path::Path, remote_dir: &str) -> Result<String> {
let name = local
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.ok_or_else(|| UgosError::Encryption("source has no file name".into()))?;
let meta = std::fs::metadata(local)
.map_err(|e| UgosError::io(format!("reading '{}'", local.display()), e))?;
let size = meta.len();
let mtime = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map_or(0, |d| d.as_secs());
let uuid = upload_uuid();
let form = reqwest::multipart::Form::new()
.text("uuid", uuid.clone())
.text("dir", remote_dir.to_owned())
.text("action_type", "0")
.text("size", size.to_string())
.text("begin_size", "0")
.text("current_size", "0")
.text("change_time", mtime.to_string())
.text("filename", name.clone())
.text("resume", "true")
.text("first_request", "true");
let announced: Result<serde_json::Value> =
self.post_multipart("filemgr/fileUpload", form).await;
let _ = announced
.map_err(|e| UgosError::OperationFailed(format!("upload handshake rejected: {e}")))?;
let bytes = std::fs::read(local)
.map_err(|e| UgosError::io(format!("reading '{}'", local.display()), e))?;
let ug_param = serde_json::json!({
"uuid": uuid,
"file_name": name,
"action_type": 0,
"size": size,
"current_size": size,
"resume": true,
"dir": crate::crypto::urlencode_component(remote_dir),
"change_time": mtime,
"is_live_photo": false,
"first_request": false,
"begin_size": 0,
});
let sent: Result<serde_json::Value> = self
.post_bytes("filemgr/fileUploadV2", bytes, &name, &ug_param)
.await;
let placed = sent.map_err(|e| {
UgosError::OperationFailed(format!("sending file contents failed: {e}"))
})?;
Ok(placed
.get("result")
.and_then(|v| v.as_str())
.map_or_else(|| format!("{remote_dir}/{name}"), ToOwned::to_owned))
}
}
fn upload_uuid() -> String {
let mut raw = [0u8; 24];
rsa::rand_core::RngCore::fill_bytes(&mut rsa::rand_core::OsRng, &mut raw);
let h = hex::encode(raw);
format!(
"{}-{}-{}-{}-{}_1_{}",
&h[0..8],
&h[8..12],
&h[12..16],
&h[16..20],
&h[20..32],
&h[32..]
)
}