use super::error::ApiError;
use super::models::*;
use reqwest::blocking;
use reqwest::header::USER_AGENT;
use serde::Serialize;
use serde_json::Value;
use serde_urlencoded::to_string;
enum YunNode {
GetUserInfo,
GetQuotaInfo,
GetFileList,
GetFileInfo,
Search,
#[allow(dead_code)]
PreCreate, #[allow(dead_code)]
UpLoad, #[allow(dead_code)]
Create, }
pub struct YunApi {
access_token: String,
client: blocking::Client,
}
fn get_node_addr(in_node: YunNode) -> String {
match in_node {
YunNode::GetUserInfo => {
String::from("https://pan.baidu.com/rest/2.0/xpan/nas?method=uinfo")
}
YunNode::GetQuotaInfo => {
String::from("https://pan.baidu.com/api/quota?checkfree=1&checkexpire=1")
}
YunNode::GetFileList => {
String::from("https://pan.baidu.com/rest/2.0/xpan/file?method=list")
}
YunNode::GetFileInfo => {
String::from("http://pan.baidu.com/rest/2.0/xpan/multimedia?method=filemetas")
}
YunNode::Search => String::from("https://pan.baidu.com/rest/2.0/xpan/file?method=search"),
YunNode::PreCreate => {
String::from("https://pan.baidu.com/rest/2.0/xpan/file?method=precreate")
}
YunNode::UpLoad => {
String::from("https://d.pcs.baidu.com/rest/2.0/pcs/superfile2?method=upload")
}
YunNode::Create => String::from("https://pan.baidu.com/rest/2.0/xpan/file?method=create"),
}
}
impl YunApi {
pub fn new(in_token: &str) -> YunApi {
YunApi {
access_token: String::from(in_token),
client: blocking::Client::new(),
}
}
fn get_addr<T: Serialize>(&self, in_node: YunNode, params: &T) -> String {
let node_addr = get_node_addr(in_node);
let query_string = to_string(params).unwrap_or_default();
if node_addr.contains('?') {
let mut addr = format!("{}&access_token={}", node_addr, self.access_token);
if !query_string.is_empty() {
addr.push_str(&format!("&{}", query_string));
}
addr
} else {
let mut addr = format!("{}?access_token={}", node_addr, self.access_token);
if !query_string.is_empty() {
addr.push_str(&format!("&{}", query_string));
}
addr
}
}
fn reqest<T: Serialize>(&self, in_node: YunNode, params: &T) -> Result<Value, ApiError> {
let addr = self.get_addr(in_node, params);
if let Ok(send_result) = self
.client
.get(&addr)
.header(USER_AGENT, "pan.baidu.com")
.send()
{
if let Ok(text) = send_result.text() {
Ok(serde_json::from_str(&text).unwrap())
} else {
Err(ApiError::from("decode text error."))
}
} else {
Err(ApiError::from("send request error."))
}
}
pub fn get_user_info(&self) -> Result<UserInfo, ApiError> {
let params = EmptyParams;
let value = self.reqest(YunNode::GetUserInfo, ¶ms).unwrap();
let error = value["errno"].as_i64().unwrap();
if error == 0 {
Ok(serde_json::from_value(value).unwrap())
} else {
Err(ApiError::new(error, "Get User infomation error."))
}
}
pub fn get_quota_info(&self) -> Result<QuotaInfo, ApiError> {
let params = EmptyParams;
let value = self.reqest(YunNode::GetQuotaInfo, ¶ms).unwrap();
let error = value["errno"].as_i64().unwrap();
if error == 0 {
Ok(serde_json::from_value(value).unwrap())
} else {
Err(ApiError::new(error, "Get quta infomation error."))
}
}
pub fn get_files_info<T>(&self, file_ids: &[T]) -> Result<Vec<FileInfoEx>, ApiError>
where
T: FileId,
{
let dlink = 1_i64;
let extra = 1_i64;
let fsids: Vec<i64> = file_ids.iter().map(|x| x.ret_file_id()).collect();
let params = GetFileInfoParams {
fsids,
dlink,
extra,
};
let value = self.reqest(YunNode::GetFileInfo, ¶ms).unwrap();
let errno = value["errno"].as_i64().unwrap();
if errno == 0 {
let len = value["list"].as_array().unwrap().len();
let mut info_vec = Vec::new();
for index in 0..len {
let file_info: FileInfoEx = FileInfoEx {
category: value["list"][index]["category"].as_i64().unwrap(),
date_taken: value["list"][index]["date_taken"].as_i64().unwrap(),
dlink: value["list"][index]["dlink"].as_str().unwrap().to_string(),
file_name: value["list"][index]["file_name"]
.as_str()
.unwrap()
.to_string(),
height: value["list"][index]["height"].as_i64().unwrap(),
is_dir: value["list"][index]["is_dir"].as_i64().unwrap(),
server_ctime: value["list"][index]["server_ctime"].as_i64().unwrap(),
server_mtime: value["list"][index]["server_mtime"].as_i64().unwrap(),
size: value["list"][index]["size"].as_i64().unwrap(),
width: value["list"][index]["width"].as_i64().unwrap(),
};
info_vec.push(file_info);
}
Ok(info_vec)
} else {
Err(ApiError::new(errno, "Get files info error."))
}
}
pub fn get_files_list(
&self,
dir: &str,
start: i64,
limit: i64,
) -> Result<FileInfoIter, ApiError> {
if !(0..=10000).contains(&limit) {
return Err(ApiError::new(8989, "limit arg error."));
}
if start < 0 {
return Err(ApiError::new(8989, "start arg error."));
}
let params = GetFileListParams {
dir: dir.to_string(),
start,
limit,
};
let value = self.reqest(YunNode::GetFileList, ¶ms).unwrap();
let errno = value["errno"].as_i64().unwrap();
if errno == 0 {
let len = value["list"].as_array().unwrap().len();
let mut file_vec = Vec::new();
for index in 0..len {
let file_info: FileInfo =
serde_json::from_value(value["list"][index].clone()).unwrap();
file_vec.push(file_info);
}
Ok(FileInfoIter::new(file_vec))
} else {
Err(ApiError::new(errno, "Get files list error."))
}
}
pub fn get_files_dlink_vec<T>(&self, files: &[T]) -> Result<Vec<String>, ApiError>
where
T: FileId,
{
let fsids: Vec<i64> = files.iter().map(|x| x.ret_file_id()).collect();
let dlink = 1_i64;
let params = GetFileInfoParams {
fsids,
dlink,
extra: 0,
};
let value = self.reqest(YunNode::GetFileInfo, ¶ms).unwrap();
let errno = value["errno"].as_i64().unwrap();
if errno == 0 {
let mut dlink_vec: Vec<String> = Vec::new();
let len = value["list"].as_array().unwrap().len();
for index in 0..len {
let dlink = value["list"][index]["dlink"].as_str().unwrap().to_string();
dlink_vec.push(dlink);
}
Ok(dlink_vec)
} else {
Err(ApiError::new(errno, "Get files dlinks error."))
}
}
pub fn get_file_dlink<T>(&self, file: T) -> Result<String, ApiError>
where
T: FileId,
{
let file_vec = vec![file];
match self.get_files_dlink_vec(&file_vec) {
Ok(mut link_vec) => {
let link = std::mem::take(&mut link_vec[0]);
Ok(link)
}
Err(error) => Err(ApiError::new(error.ret_errno(), "Get file dlink error.")),
}
}
pub fn search_with_key(
&self,
search_key: &str,
search_dir: &str,
is_recursive: bool,
in_page: i64,
in_num: i64,
in_web: bool,
) -> Result<Vec<SearchResult>, ApiError> {
let params = SearchParams {
key: search_key.to_string(),
dir: search_dir.to_string(),
recursion: is_recursive as i64,
page: in_page,
num: in_num,
web: in_web as i64,
};
if in_page < 1 {
return Err(ApiError::new(8989, "Page is less than 1."));
}
if in_num > 1000 {
return Err(ApiError::new(8989, "Num is more than 1000."));
}
let value = self.reqest(YunNode::Search, ¶ms).unwrap();
let errno = value["errno"].as_i64().unwrap();
if errno == 0 {
let len = value["list"].as_array().unwrap().len();
let mut search_vec = Vec::new();
for index in 0..len {
let file_info: SearchResult =
serde_json::from_value(value["list"][index].clone()).unwrap();
search_vec.push(file_info);
}
Ok(search_vec)
} else {
Err(ApiError::new(errno, "Get files info error."))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_addr_with_existing_query_params() {
let api = YunApi::new("test_token");
let params = EmptyParams;
let addr = api.get_addr(YunNode::GetUserInfo, ¶ms);
assert_eq!(
addr,
"https://pan.baidu.com/rest/2.0/xpan/nas?method=uinfo&access_token=test_token"
);
}
#[test]
fn test_get_addr_without_existing_query_params() {
let api = YunApi::new("test_token");
let params = GetFileListParams {
dir: "/".to_string(),
start: 0,
limit: 10,
};
let addr = api.get_addr(YunNode::GetFileList, ¶ms);
assert_eq!(addr, "https://pan.baidu.com/rest/2.0/xpan/file?method=list&access_token=test_token&dir=%2F&start=0&limit=10");
}
#[test]
fn test_get_addr_with_special_chars() {
let api = YunApi::new("test_token");
let params = SearchParams {
key: "测试中文".to_string(),
dir: "/test dir".to_string(),
recursion: 0,
page: 1,
num: 50,
web: 1,
};
let addr = api.get_addr(YunNode::Search, ¶ms);
assert!(addr.contains("key=%E6%B5%8B%E8%AF%95%E4%B8%AD%E6%96%87"));
assert!(addr.contains("dir=%2Ftest+dir"));
assert!(addr.contains("access_token=test_token"));
}
#[test]
fn test_get_addr_with_file_info_params() {
let api = YunApi::new("test_token");
let params = GetFileInfoParams {
fsids: vec![123, 456],
dlink: 1,
extra: 1,
};
let addr = api.get_addr(YunNode::GetFileInfo, ¶ms);
assert!(addr.contains("fsids=%5B123%2C456%5D"));
assert!(addr.contains("dlink=1"));
assert!(addr.contains("extra=1"));
assert!(addr.contains("access_token=test_token"));
}
#[test]
fn test_get_addr_with_quota_info() {
let api = YunApi::new("test_token");
let params = EmptyParams;
let addr = api.get_addr(YunNode::GetQuotaInfo, ¶ms);
assert_eq!(
addr,
"https://pan.baidu.com/api/quota?checkfree=1&checkexpire=1&access_token=test_token"
);
}
}