Skip to content

Commit

Permalink
new status --all method
Browse files Browse the repository at this point in the history
  • Loading branch information
vsbuffalo committed Aug 31, 2023
1 parent 7a7bb41 commit 754c3b6
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 69 deletions.
24 changes: 14 additions & 10 deletions src/lib/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ impl StatusEntry {
}
}
}
pub fn columns(&self, abbrev: Option<i32>) -> Result<Vec<String>> {
pub fn columns(&self, abbrev: Option<i32>) -> Vec<String> {
let local_status = &self.local_status;

let md5_string = self.local_md5_column(abbrev)?;
let md5_string = self.local_md5_column(abbrev).expect("Internal Error: StatusEntry::local_md5_column().");

let mod_time_pretty = self.local_mod_time.map(format_mod_time).unwrap_or_default();

Expand All @@ -105,7 +105,7 @@ impl StatusEntry {
(false, _) => "".to_string(),
(true, Some(true)) => ", tracked".to_string(),
(true, Some(false)) => ", untracked".to_string(),
(_, _) => return Err(anyhow!("Invalid tracking state"))
(true, None) => ", not in manifest".to_string()
};
let mut columns = vec![
self.name.clone(),
Expand All @@ -119,7 +119,8 @@ impl StatusEntry {
Some(RemoteStatusCode::Current) => "identical remote".to_string(),
Some(RemoteStatusCode::MessyLocal) => "messy local".to_string(),
Some(RemoteStatusCode::Different) => {
format!("different remote version ({:})", self.remote_md5_column(abbrev)?)
let remote_md5 = self.remote_md5_column(abbrev).expect("Internal Error: StatusEntry::remote_md5_column().");
format!("different remote version ({:})", remote_md5)
},
Some(RemoteStatusCode::NotExists) => "not on remote".to_string(),
Some(RemoteStatusCode::NoLocal) => "unknown (messy remote)".to_string(),
Expand All @@ -129,8 +130,7 @@ impl StatusEntry {
};
columns.push(remote_status_msg.to_string());
}

Ok(columns)
columns
}
}

Expand All @@ -140,6 +140,7 @@ pub struct DataFile {
pub tracked: bool,
pub md5: String,
pub size: u64,
pub url: Option<String>
//modified: Option<DateTime<Utc>>,
}

Expand Down Expand Up @@ -316,7 +317,7 @@ impl MergedFile {


impl DataFile {
pub fn new(path: String, path_context: &Path) -> Result<DataFile> {
pub fn new(path: String, url: Option<&str>, path_context: &Path) -> Result<DataFile> {
let full_path = path_context.join(&path);
if !full_path.exists() {
return Err(anyhow!("File '{}' does not exist.", path))
Expand All @@ -328,11 +329,13 @@ impl DataFile {
let size = metadata(full_path)
.map_err(|err| anyhow!("Failed to get metadata for file {:?}: {}", path, err))?
.len();
let maybe_url: Option<String> = url.map(|s| s.to_string());
Ok(DataFile {
path,
tracked: false,
md5,
size,
url: maybe_url,
})
}

Expand Down Expand Up @@ -623,7 +626,7 @@ impl DataCollection {
None => Err(anyhow!("No such remote")),
}
}
pub fn track_file(&mut self, filepath: &String) -> Result<()> {
pub fn track_file(&mut self, filepath: &String, path_context: &Path) -> Result<()> {
trace!("complete files: {:?}", self.files);
let data_file = self.files.get_mut(filepath);

Expand All @@ -640,8 +643,9 @@ impl DataCollection {
None => Err(anyhow!("Data file '{}' is not in the data manifest. Add it first using:\n \
$ sdf track {}\n", filepath, filepath)),
Some(data_file) => {
let path = Path::new(filepath);
let file_size = data_file.get_size(path)?;
// check that the file isn't empty
// (this is why a path_context is needed)
let file_size = data_file.get_size(&path_context)?;
if file_size == 0 {
return Err(anyhow!("Cannot track an empty file, and '{}' has a file size of 0.", filepath));
}
Expand Down
35 changes: 29 additions & 6 deletions src/lib/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use dirs;
#[allow(unused_imports)]
use crate::{print_warn,print_info};
use crate::lib::data::{DataFile,DataCollection};
use crate::lib::utils::{load_file,print_status};
use crate::lib::utils::{load_file,print_status, pluralize};
use crate::lib::remote::{AuthKeys,authenticate_remote};
use crate::lib::remote::Remote;
use crate::lib::api::figshare::FigShareAPI;
Expand Down Expand Up @@ -171,6 +171,17 @@ impl Project {
Ok(())
}

// TODO could add support for other metadata here
pub fn set_metadata(&mut self, title: &Option<String>, description: &Option<String>) -> Result<()> {
if let Some(new_title) = title {
self.data.metadata.title = Some(new_title.to_string());
}
if let Some(new_description) = description {
self.data.metadata.description = Some(new_description.to_string());
}
self.save()
}

pub fn set_config(name: &Option<String>, email: &Option<String>, affiliation: &Option<String>) -> Result<()> {
let mut config = Project::load_config().unwrap_or_else(|_| Config {
user: User {
Expand Down Expand Up @@ -254,13 +265,13 @@ impl Project {
Ok(self.relative_path(path)?.to_string_lossy().to_string())
}

pub async fn status(&mut self, include_remotes: bool) -> Result<()> {
pub async fn status(&mut self, include_remotes: bool, all: bool) -> Result<()> {
// if include_remotes (e.g. --remotes) is set, we need to merge
// in the remotes, so we authenticate first and then get them.
let path_context = &canonicalize(self.path_context())?;
let status_rows = self.data.status(path_context, include_remotes).await?;
//let remotes: Option<_> = include_remotes.then(|| &self.data.remotes);
print_status(status_rows, Some(&self.data.remotes));
print_status(status_rows, Some(&self.data.remotes), all);
Ok(())
}

Expand Down Expand Up @@ -298,12 +309,12 @@ impl Project {
let mut num_added = 0;
for filepath in files {
let filename = self.relative_path_string(Path::new(&filepath.clone()))?;
let data_file = DataFile::new(filename.clone(), &self.path_context())?;
let data_file = DataFile::new(filename.clone(), None, &self.path_context())?;
info!("Adding file '{}'.", filename);
self.data.register(data_file)?;
num_added += 1;
}
println!("Added {} files.", num_added);
println!("Added {}.", pluralize(num_added as u64, "file"));
self.save()
}

Expand Down Expand Up @@ -371,6 +382,18 @@ impl Project {
Ok(())
}

pub async fn get(&mut self, url: &str, filename: &str) -> Result<()> {
let data_file = DataFile::new(filename.to_string(), Some(url), &self.path_context())?;
info!("Adding file '{}'.", filename);
self.data.register(data_file)?;
Ok(())
}

pub async fn get_from_file(&mut self, filename: &str, column: u64) -> Result<()> {
// TODO
Ok(())
}

pub fn untrack(&mut self, filepath: &String) -> Result<()> {
let filepath = self.relative_path_string(Path::new(filepath))?;
self.data.untrack_file(&filepath)?;
Expand All @@ -379,7 +402,7 @@ impl Project {

pub fn track(&mut self, filepath: &String) -> Result<()> {
let filepath = self.relative_path_string(Path::new(filepath))?;
self.data.track_file(&filepath)?;
self.data.track_file(&filepath, &self.path_context())?;
self.save()
}

Expand Down
96 changes: 70 additions & 26 deletions src/lib/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ pub fn print_fixed_width(rows: HashMap<String, Vec<StatusEntry>>, nspaces: Optio
*/
// More specialized version of print_fixed_width() for statuses.
// Handles coloring, manual annotation, etc
pub fn print_fixed_width_status(rows: BTreeMap<String, Vec<StatusEntry>>, nspaces: Option<usize>, indent: Option<usize>, color: bool) {
pub fn print_fixed_width_status(rows: BTreeMap<String, Vec<StatusEntry>>, nspaces: Option<usize>,
indent: Option<usize>, color: bool, all: bool) {
//debug!("rows: {:?}", rows);
let indent = indent.unwrap_or(0);
let nspaces = nspaces.unwrap_or(6);

Expand All @@ -130,43 +132,46 @@ pub fn print_fixed_width_status(rows: BTreeMap<String, Vec<StatusEntry>>, nspace
// get the max number of columns (in case ragged)
let max_cols = rows.values()
.flat_map(|v| v.iter())
.filter_map(|entry| entry.columns(abbrev).ok().map(|cols| cols.len()))
.map(|entry| entry.columns(abbrev).len())
.max()
.unwrap_or(0);

let mut max_lengths = vec![0; max_cols];

// compute max lengths across all rows
for status in rows.values().flat_map(|v| v.iter()) {
if let Ok(cols) = status.columns(abbrev) { // Assuming columns returns Result<Vec<String>>
for (i, col) in cols.iter().enumerate() {
max_lengths[i] = max_lengths[i].max(col.len()); // Assuming col is a string
}
let cols = status.columns(abbrev);
for (i, col) in cols.iter().enumerate() {
max_lengths[i] = max_lengths[i].max(col.len()); // Assuming col is a string
}
}

// print status table
let mut keys: Vec<&String> = rows.keys().collect();
keys.sort();
for (key, value) in &rows {
let mut dir_keys: Vec<&String> = rows.keys().collect();
dir_keys.sort();
for key in dir_keys {
let statuses = &rows[key];
let pretty_key = if color { key.bold().to_string() } else { key.clone() };
println!("[{}]", pretty_key);

// Print the rows with the correct widths
for status in value {
if let Ok(cols) = status.columns(abbrev) {
let mut fixed_row = Vec::new();
for (i, col) in cols.iter().enumerate() {
// push a fixed-width column to vector
let spacer = if i == 0 { " " } else { "" };
let fixed_col = format!("{}{:width$}", spacer, col, width = max_lengths[i]);
fixed_row.push(fixed_col);
}
let spacer = " ".repeat(nspaces);
let line = fixed_row.join(&spacer);
let status_line = if color { status.color(line) } else { line.to_string() };
println!("{}{}", " ".repeat(indent), status_line);
for status in statuses {
if status.local_status.is_none() && !all {
// ignore things that aren't in the manifest, unless --all
continue;
}
let cols = status.columns(abbrev);
let mut fixed_row = Vec::new();
for (i, col) in cols.iter().enumerate() {
// push a fixed-width column to vector
let spacer = if i == 0 { " " } else { "" };
let fixed_col = format!("{}{:width$}", spacer, col, width = max_lengths[i]);
fixed_row.push(fixed_col);
}
let spacer = " ".repeat(nspaces);
let line = fixed_row.join(&spacer);
let status_line = if color { status.color(line) } else { line.to_string() };
println!("{}{}", " ".repeat(indent), status_line);
}
println!();
}
Expand Down Expand Up @@ -199,10 +204,49 @@ pub fn pluralize<T: Into<u64>>(count: T, noun: &str) -> String {
}
}

pub fn print_status(rows: BTreeMap<String,Vec<StatusEntry>>, remote: Option<&HashMap<String,Remote>>) {
struct FileCounts {
local: u64,
remote: u64,
both: u64,
total: u64
}

fn get_counts(rows: &BTreeMap<String,Vec<StatusEntry>>) -> Result<FileCounts> {
let mut local = 0;
let mut remote = 0;
let mut both = 0;
let mut total = 0;
for files in rows.values() {
for file in files {
total += 1;
match (&file.local_status, &file.remote_status) {
(None, None) => {
return Err(anyhow!("Internal Error: get_counts found a file with both local/remote set to None."));
},
(Some(_), None) => {
local += 1;
},
(None, Some(_)) => {
remote += 1;
},
(Some(_), Some(_)) => {
both += 1;
}
}
}
}
Ok(FileCounts { local, remote, both, total })
}

pub fn print_status(rows: BTreeMap<String,Vec<StatusEntry>>, remote: Option<&HashMap<String,Remote>>,
all: bool) {
println!("{}", "Project data status:".bold());
let total: usize = rows.values().map(|v| v.len()).sum();
println!("{} registered.\n", pluralize(total as u64, "data file"));
let counts = get_counts(&rows).expect("Internal Error: get_counts() panicked.");
println!("{} on local and remotes ({} only local, {} only remote), {} total.\n",
pluralize(counts.both as u64, "file"),
pluralize(counts.local as u64, "file"),
pluralize(counts.remote as u64, "file"),
pluralize(counts.total as u64, "file"));

// this brings the remote name (if there is a corresponding remote) into
// the key, so the linked remote can be displayed in the status
Expand All @@ -222,7 +266,7 @@ pub fn print_status(rows: BTreeMap<String,Vec<StatusEntry>>, remote: Option<&Has
None => rows,
};

print_fixed_width_status(rows_by_dir, None, None, true);
print_fixed_width_status(rows_by_dir, None, None, true, all);
}

pub fn format_bytes(size: u64) -> String {
Expand Down
Loading

0 comments on commit 754c3b6

Please sign in to comment.