api2/admin/datastore: refactor list_dir_content in catalog_reader
we will reuse that later in the client, so we need it somewhere we can use from there Signed-off-by: Dominik Csapak <d.csapak@proxmox.com> [add strongly typed ArchiveEntry and put api code into helpers.rs] Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
This commit is contained in:
parent
89d25b1931
commit
227501c063
@ -27,6 +27,7 @@ use pxar::EntryKind;
|
|||||||
|
|
||||||
use crate::api2::types::*;
|
use crate::api2::types::*;
|
||||||
use crate::api2::node::rrd::create_value_from_rrd;
|
use crate::api2::node::rrd::create_value_from_rrd;
|
||||||
|
use crate::api2::helpers;
|
||||||
use crate::backup::*;
|
use crate::backup::*;
|
||||||
use crate::config::datastore;
|
use crate::config::datastore;
|
||||||
use crate::config::cached_user_info::CachedUserInfo;
|
use crate::config::cached_user_info::CachedUserInfo;
|
||||||
@ -1294,7 +1295,7 @@ pub fn catalog(
|
|||||||
backup_time: i64,
|
backup_time: i64,
|
||||||
filepath: String,
|
filepath: String,
|
||||||
rpcenv: &mut dyn RpcEnvironment,
|
rpcenv: &mut dyn RpcEnvironment,
|
||||||
) -> Result<Value, Error> {
|
) -> Result<Vec<ArchiveEntry>, Error> {
|
||||||
let datastore = DataStore::lookup_datastore(&store)?;
|
let datastore = DataStore::lookup_datastore(&store)?;
|
||||||
|
|
||||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||||
@ -1326,52 +1327,14 @@ pub fn catalog(
|
|||||||
let reader = BufferedDynamicReader::new(index, chunk_reader);
|
let reader = BufferedDynamicReader::new(index, chunk_reader);
|
||||||
|
|
||||||
let mut catalog_reader = CatalogReader::new(reader);
|
let mut catalog_reader = CatalogReader::new(reader);
|
||||||
let mut current = catalog_reader.root()?;
|
|
||||||
let mut components = vec![];
|
|
||||||
|
|
||||||
|
let path = if filepath != "root" {
|
||||||
if filepath != "root" {
|
base64::decode(filepath)?
|
||||||
components = base64::decode(filepath)?;
|
|
||||||
if !components.is_empty() && components[0] == b'/' {
|
|
||||||
components.remove(0);
|
|
||||||
}
|
|
||||||
for component in components.split(|c| *c == b'/') {
|
|
||||||
if let Some(entry) = catalog_reader.lookup(¤t, component)? {
|
|
||||||
current = entry;
|
|
||||||
} else {
|
} else {
|
||||||
bail!("path {:?} not found in catalog", &String::from_utf8_lossy(&components));
|
vec![b'/']
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res = Vec::new();
|
helpers::list_dir_content(&mut catalog_reader, &path)
|
||||||
|
|
||||||
for direntry in catalog_reader.read_dir(¤t)? {
|
|
||||||
let mut components = components.clone();
|
|
||||||
components.push(b'/');
|
|
||||||
components.extend(&direntry.name);
|
|
||||||
let path = base64::encode(components);
|
|
||||||
let text = String::from_utf8_lossy(&direntry.name);
|
|
||||||
let mut entry = json!({
|
|
||||||
"filepath": path,
|
|
||||||
"text": text,
|
|
||||||
"type": CatalogEntryType::from(&direntry.attr).to_string(),
|
|
||||||
"leaf": true,
|
|
||||||
});
|
|
||||||
match direntry.attr {
|
|
||||||
DirEntryAttribute::Directory { start: _ } => {
|
|
||||||
entry["leaf"] = false.into();
|
|
||||||
},
|
|
||||||
DirEntryAttribute::File { size, mtime } => {
|
|
||||||
entry["size"] = size.into();
|
|
||||||
entry["mtime"] = mtime.into();
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
res.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recurse_files<'a, T, W>(
|
fn recurse_files<'a, T, W>(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::io::{Read, Seek};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
@ -6,6 +7,9 @@ use hyper::{Body, Response, StatusCode, header};
|
|||||||
|
|
||||||
use proxmox::http_bail;
|
use proxmox::http_bail;
|
||||||
|
|
||||||
|
use crate::api2::types::ArchiveEntry;
|
||||||
|
use crate::backup::{CatalogReader, DirEntryAttribute};
|
||||||
|
|
||||||
pub async fn create_download_response(path: PathBuf) -> Result<Response<Body>, Error> {
|
pub async fn create_download_response(path: PathBuf) -> Result<Response<Body>, Error> {
|
||||||
let file = match tokio::fs::File::open(path.clone()).await {
|
let file = match tokio::fs::File::open(path.clone()).await {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
@ -27,3 +31,30 @@ pub async fn create_download_response(path: PathBuf) -> Result<Response<Body>, E
|
|||||||
.body(body)
|
.body(body)
|
||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the list of content of the given path
|
||||||
|
pub fn list_dir_content<R: Read + Seek>(
|
||||||
|
reader: &mut CatalogReader<R>,
|
||||||
|
path: &[u8],
|
||||||
|
) -> Result<Vec<ArchiveEntry>, Error> {
|
||||||
|
let dir = reader.lookup_recursive(path)?;
|
||||||
|
let mut res = vec![];
|
||||||
|
let mut path = path.to_vec();
|
||||||
|
if !path.is_empty() && path[0] == b'/' {
|
||||||
|
path.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for direntry in reader.read_dir(&dir)? {
|
||||||
|
let mut components = path.clone();
|
||||||
|
components.push(b'/');
|
||||||
|
components.extend(&direntry.name);
|
||||||
|
let mut entry = ArchiveEntry::new(&components, &direntry.attr);
|
||||||
|
if let DirEntryAttribute::File { size, mtime } = direntry.attr {
|
||||||
|
entry.size = size.into();
|
||||||
|
entry.mtime = mtime.into();
|
||||||
|
}
|
||||||
|
res.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
@ -12,6 +12,8 @@ use crate::{
|
|||||||
CryptMode,
|
CryptMode,
|
||||||
Fingerprint,
|
Fingerprint,
|
||||||
BACKUP_ID_REGEX,
|
BACKUP_ID_REGEX,
|
||||||
|
DirEntryAttribute,
|
||||||
|
CatalogEntryType,
|
||||||
},
|
},
|
||||||
server::UPID,
|
server::UPID,
|
||||||
config::acl::Role,
|
config::acl::Role,
|
||||||
@ -1303,6 +1305,47 @@ pub struct DatastoreNotify {
|
|||||||
pub sync: Option<Notify>,
|
pub sync: Option<Notify>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An entry in a hierarchy of files for restore and listing.
|
||||||
|
#[api()]
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ArchiveEntry {
|
||||||
|
/// Base64-encoded full path to the file, including the filename
|
||||||
|
pub filepath: String,
|
||||||
|
/// Displayable filename text for UIs
|
||||||
|
pub text: String,
|
||||||
|
/// File or directory type of this entry
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub entry_type: String,
|
||||||
|
/// Is this entry a leaf node, or does it have children (i.e. a directory)?
|
||||||
|
pub leaf: bool,
|
||||||
|
/// The file size, if entry_type is 'f' (file)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub size: Option<u64>,
|
||||||
|
/// The file "last modified" time stamp, if entry_type is 'f' (file)
|
||||||
|
#[serde(skip_serializing_if="Option::is_none")]
|
||||||
|
pub mtime: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArchiveEntry {
|
||||||
|
pub fn new(filepath: &[u8], entry_type: &DirEntryAttribute) -> Self {
|
||||||
|
Self {
|
||||||
|
filepath: base64::encode(filepath),
|
||||||
|
text: String::from_utf8_lossy(filepath.split(|x| *x == b'/').last().unwrap())
|
||||||
|
.to_string(),
|
||||||
|
entry_type: CatalogEntryType::from(entry_type).to_string(),
|
||||||
|
leaf: matches!(entry_type, DirEntryAttribute::Directory { .. }),
|
||||||
|
size: match entry_type {
|
||||||
|
DirEntryAttribute::File { size, .. } => Some(*size),
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
mtime: match entry_type {
|
||||||
|
DirEntryAttribute::File { mtime, .. } => Some(*mtime),
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
|
pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
|
||||||
"Datastore notification setting")
|
"Datastore notification setting")
|
||||||
.format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
|
.format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
|
||||||
|
@ -468,6 +468,32 @@ impl <R: Read + Seek> CatalogReader<R> {
|
|||||||
Ok(entry_list)
|
Ok(entry_list)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lookup a DirEntry from an absolute path
|
||||||
|
pub fn lookup_recursive(
|
||||||
|
&mut self,
|
||||||
|
path: &[u8],
|
||||||
|
) -> Result<DirEntry, Error> {
|
||||||
|
let mut current = self.root()?;
|
||||||
|
if path == b"/" {
|
||||||
|
return Ok(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
let components = if !path.is_empty() && path[0] == b'/' {
|
||||||
|
&path[1..]
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}.split(|c| *c == b'/');
|
||||||
|
|
||||||
|
for comp in components {
|
||||||
|
if let Some(entry) = self.lookup(¤t, comp)? {
|
||||||
|
current = entry;
|
||||||
|
} else {
|
||||||
|
bail!("path {:?} not found in catalog", String::from_utf8_lossy(&path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(current)
|
||||||
|
}
|
||||||
|
|
||||||
/// Lockup a DirEntry inside a parent directory
|
/// Lockup a DirEntry inside a parent directory
|
||||||
pub fn lookup(
|
pub fn lookup(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
Loading…
Reference in New Issue
Block a user