use std::collections::BTreeMap;
use crate::{
channel::Channel,
client::Client,
error::{Error, Result},
frame,
pstream::PObject,
};
#[derive(Debug, Clone)]
pub struct ShareInfo {
pub view_id: u64,
pub name: String,
pub file_id: String,
pub can_download: bool,
pub can_sync: bool,
}
#[derive(Debug, Clone)]
pub struct NodeInfo {
pub node_id: u64,
pub sync_id: u64,
pub name: String,
pub file_size: u64,
pub mtime: u64,
pub hash: String,
pub file_id: String,
pub file_type: FileType,
pub is_removed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileType {
File,
Dir,
Symlink,
}
pub(crate) async fn list_team_folder(ch: &mut Channel, client: &Client) -> Result<Vec<ShareInfo>> {
let request = client.build_request(
"list_team_folder",
pmap! {
"limit" => 0u64,
"offset" => 0u64,
"sort_by" => "name",
"sort_direction" => "asc",
},
);
let response = ch.request(frame::SCMD_REQUEST, &request).await?;
let view_list = response
.get("view_list")
.and_then(|v| v.as_array())
.ok_or_else(|| Error::Decode("missing view_list in response".into()))?;
let mut shares = Vec::new();
for view in view_list {
shares.push(parse_share_info(view)?);
}
Ok(shares)
}
pub(crate) async fn list_sync_to_device(
ch: &mut Channel,
client: &Client,
view_id: u64,
path: &str,
cursor: Option<&str>,
) -> Result<(Vec<NodeInfo>, Option<String>)> {
let mut fields = pmap! {
"path" => path,
"view_id" => view_id,
"merge_local" => 0u64,
"list_dir_only" => 0u64,
"include_node_locking" => 0u64,
};
if let Some(c) = cursor {
fields
.as_map_mut()
.expect("pmap! always produces a Map")
.insert("cursor".to_string(), PObject::from(c));
}
let request = client.build_request("list_sync_to_device", fields);
let resp = ch.request(frame::SCMD_LIST, &request).await?;
let cursor = resp
.get("cursor")
.and_then(|v| v.as_str())
.map(ToString::to_string);
let mut nodes = Vec::new();
if let Some(node_list) = resp.get("node_list").and_then(|v| v.as_array()) {
for node in node_list {
nodes.push(parse_node_info(node)?);
}
}
Ok((nodes, cursor))
}
pub(crate) async fn list(
ch: &mut Channel,
client: &Client,
view_id: u64,
path: &str,
) -> Result<Vec<NodeInfo>> {
let mut all_nodes = Vec::new();
let mut offset = 0u64;
let limit = 1000u64;
loop {
let request = client.build_request(
"list",
pmap! {
"path" => path,
"view_id" => view_id,
"merge_local" => 1u64,
"list_dir_only" => 0u64,
"search_criteria" => pmap! {
"limit" => limit,
"offset" => offset,
"sort_by" => "name",
"sort_direction" => "asc",
},
},
);
let resp = ch.request(frame::SCMD_LIST, &request).await?;
let node_list = resp
.get("node_list")
.and_then(|v| v.as_array())
.unwrap_or_default();
let total_count = resp
.get("total_count")
.and_then(PObject::as_int)
.unwrap_or(0);
if all_nodes.is_empty()
&& total_count > 0
&& let Some(count) = usize::try_from(total_count).ok()
{
all_nodes.reserve(count);
}
for node in node_list {
all_nodes.push(parse_node_info(node)?);
}
offset += node_list.len() as u64;
if offset >= total_count || node_list.is_empty() {
break;
}
}
Ok(all_nodes)
}
fn get_int(map: &BTreeMap<String, PObject>, key: &str) -> u64 {
map.get(key).and_then(PObject::as_int).unwrap_or(0)
}
fn get_str(map: &BTreeMap<String, PObject>, key: &str) -> String {
map.get(key)
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string()
}
fn parse_share_info(obj: &PObject) -> Result<ShareInfo> {
let map = obj
.as_map()
.ok_or_else(|| Error::Decode("view is not a map".into()))?;
let caps = |key: &str| -> bool {
map.get("capabilities")
.and_then(|c| c.get(key))
.and_then(PObject::as_int)
.is_some_and(|v| v != 0)
};
Ok(ShareInfo {
name: get_str(map, "name"),
can_sync: caps("can_sync"),
view_id: get_int(map, "view_id"),
file_id: get_str(map, "file_id"),
can_download: caps("can_download") && get_int(map, "disable_download") == 0,
})
}
fn parse_node_info(obj: &PObject) -> Result<NodeInfo> {
let map = obj
.as_map()
.ok_or_else(|| Error::Decode("node is not a map".into()))?;
Ok(NodeInfo {
name: get_str(map, "name"),
hash: get_str(map, "hash"),
mtime: get_int(map, "mtime"),
node_id: get_int(map, "node_id"),
sync_id: get_int(map, "sync_id"),
file_id: get_str(map, "file_id"),
file_size: get_int(map, "file_size"),
is_removed: get_int(map, "is_removed") != 0,
file_type: match map.get("file_type").and_then(|v| v.as_str()) {
Some("dir") => FileType::Dir,
Some("symlink") => FileType::Symlink,
_ => FileType::File,
},
})
}