use crate::docker::{FileOp, Listing, Preview, parent_path};
pub struct FileView {
pub container_id: String,
pub path: String,
pub listing: Option<Result<Listing, String>>,
pub preview: Option<(String, Option<Result<Preview, String>>)>,
pub transfers: usize,
pub stale: bool,
}
impl FileView {
fn new(container_id: &str, path: &str) -> Self {
Self {
container_id: container_id.to_owned(),
path: path.to_owned(),
listing: None,
preview: None,
transfers: 0,
stale: false,
}
}
pub fn shows(&self, container: &str, path: &str) -> bool {
self.container_id == container && self.path == path && !self.stale
}
}
pub fn requested(view: &mut Option<FileView>, op: &FileOp) {
let (FileOp::List { container, .. }
| FileOp::Preview { container, .. }
| FileOp::Download { container, .. }
| FileOp::Upload { container, .. }) = op
else {
return;
};
if view.as_ref().is_none_or(|v| v.container_id != *container) {
*view = Some(FileView::new(container, "/"));
}
let Some(v) = view else { return };
match op {
FileOp::List { path, .. } => {
if v.preview
.as_ref()
.is_some_and(|(file, _)| parent_path(file) != *path)
{
v.preview = None;
}
v.path = path.clone();
v.listing = None;
v.stale = false;
}
FileOp::Preview { path, .. } => v.preview = Some((path.clone(), None)),
FileOp::Download { .. } | FileOp::Upload { .. } => v.transfers += 1,
FileOp::BrowseVolume { .. } | FileOp::EndBrowse { .. } => {}
}
}
pub fn listed(view: &mut Option<FileView>, result: Result<Listing, (String, String, String)>) {
let (container, path) = match &result {
Ok(l) => (l.container_id.clone(), l.path.clone()),
Err((c, p, _)) => (c.clone(), p.clone()),
};
if let Some(v) = view
&& v.container_id == container
&& v.path == path
{
v.listing = Some(result.map_err(|(_, _, e)| e));
}
}
pub fn previewed(
view: &mut Option<FileView>,
container: &str,
path: &str,
preview: Result<Preview, String>,
) {
if let Some(v) = view
&& v.container_id == container
&& let Some((asked, slot)) = &mut v.preview
&& asked == path
{
*slot = Some(preview);
}
}
pub fn transferred(view: &mut Option<FileView>, container: &str, changed_dir: Option<&str>) {
if let Some(v) = view
&& v.container_id == container
{
v.transfers = v.transfers.saturating_sub(1);
if changed_dir == Some(v.path.as_str()) {
v.stale = true;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn list(path: &str) -> FileOp {
FileOp::List {
container: "c1".into(),
path: path.into(),
}
}
#[test]
fn follows_requests_and_answers() {
let mut view = None;
requested(&mut view, &list("/etc"));
assert!(view.as_ref().unwrap().shows("c1", "/etc"));
let listing = Listing {
container_id: "c1".into(),
path: "/etc".into(),
entries: Vec::new(),
complete: true,
};
listed(
&mut view,
Ok(Listing {
path: "/".into(),
..listing.clone()
}),
);
assert!(view.as_ref().unwrap().listing.is_none());
listed(&mut view, Ok(listing));
assert!(view.as_ref().unwrap().listing.is_some());
requested(
&mut view,
&FileOp::Upload {
container: "c1".into(),
dir: "/etc".into(),
files: Vec::new(),
},
);
assert_eq!(view.as_ref().unwrap().transfers, 1);
transferred(&mut view, "c1", Some("/etc"));
let v = view.as_ref().unwrap();
assert_eq!(v.transfers, 0);
assert!(!v.shows("c1", "/etc"), "an upload makes the folder stale");
requested(&mut view, &list("/"));
requested(
&mut view,
&FileOp::List {
container: "c2".into(),
path: "/".into(),
},
);
assert_eq!(view.as_ref().unwrap().container_id, "c2");
}
}