Add desktop search and entry mutation workflows
This commit is contained in:
@@ -6,20 +6,21 @@ use crate::{
|
||||
authentication::{
|
||||
AuthenticationTimeout, NativeAuthenticationHandle, NativeAuthenticationSession,
|
||||
},
|
||||
command::InitRequest,
|
||||
command::{CopyRequest, FindRequest, GrepRequest, InitRequest, MoveRequest, RemoveRequest},
|
||||
config::{Config, ConfigSettings, EditorCommand},
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider},
|
||||
document::{DocumentError, EntryDocument, EntryDocumentService},
|
||||
git::{AutomaticEntryCommitter, AutomaticPolicyCommitter, GitIdentity},
|
||||
git::{AutomaticEntryCommitter, AutomaticPolicyCommitter, AutomaticTreeCommitter, GitIdentity},
|
||||
mutation::{MutationOutcome, TreeMutator},
|
||||
presentation::ClipboardTimeout,
|
||||
read::{TreeModel, VaultReader},
|
||||
read::{FindResults, GrepResults, TreeModel, VaultReader},
|
||||
recipient::{
|
||||
PolicyCommit, PolicyCommitError, PolicyCommitter, RecipientPolicyManager,
|
||||
RecipientPolicyOutcome,
|
||||
},
|
||||
repository::{DirectoryPath, Repository},
|
||||
secret_store::SecretProtectionPolicy,
|
||||
write::{VaultWriter, WriteError, WriteOutcome},
|
||||
write::{OverwriteDecision, VaultWriter, WriteError, WriteOutcome},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -35,6 +36,7 @@ pub enum DesktopErrorKind {
|
||||
Unchanged,
|
||||
MissingDefaultKey,
|
||||
EntryExists,
|
||||
Mutation,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -80,6 +82,23 @@ pub struct DesktopStorage {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum DesktopMutationRequest {
|
||||
Remove(RemoveRequest),
|
||||
Move(MoveRequest),
|
||||
Copy(CopyRequest),
|
||||
}
|
||||
|
||||
impl DesktopMutationRequest {
|
||||
pub fn source(&self) -> &str {
|
||||
match self {
|
||||
Self::Remove(request) => &request.entry,
|
||||
Self::Move(request) => &request.source,
|
||||
Self::Copy(request) => &request.source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DesktopStorage {
|
||||
pub fn load(explicit: Option<&Path>) -> Result<Self, DesktopError> {
|
||||
Config::load(explicit)
|
||||
@@ -199,6 +218,80 @@ impl DesktopStorage {
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Read, error))
|
||||
}
|
||||
|
||||
pub fn find(&self, request: &FindRequest) -> Result<FindResults, DesktopError> {
|
||||
let repository = self.repository()?;
|
||||
let keys = self.keys()?;
|
||||
VaultReader::new(&repository, &keys)
|
||||
.find(&request.terms)
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Read, error))
|
||||
}
|
||||
|
||||
pub fn grep_active(
|
||||
&self,
|
||||
handle: &NativeAuthenticationHandle,
|
||||
request: &GrepRequest,
|
||||
) -> Result<GrepResults, DesktopError> {
|
||||
handle
|
||||
.ensure_active()
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Authentication, error))?;
|
||||
let mut provider = handle.clone();
|
||||
self.grep(request, &mut provider)
|
||||
}
|
||||
|
||||
pub fn grep(
|
||||
&self,
|
||||
request: &GrepRequest,
|
||||
provider: &mut impl SecretProvider,
|
||||
) -> Result<GrepResults, DesktopError> {
|
||||
let repository = self.repository()?;
|
||||
let keys = self.keys()?;
|
||||
VaultReader::new(&repository, &keys)
|
||||
.grep(request, provider)
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Read, error))
|
||||
}
|
||||
|
||||
pub fn mutate_active(
|
||||
&self,
|
||||
handle: &NativeAuthenticationHandle,
|
||||
request: &DesktopMutationRequest,
|
||||
overwrite: OverwriteDecision,
|
||||
) -> Result<MutationOutcome, DesktopError> {
|
||||
handle
|
||||
.ensure_active()
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Authentication, error))?;
|
||||
let mut provider = handle.clone();
|
||||
self.mutate(request, overwrite, &mut provider)
|
||||
}
|
||||
|
||||
pub fn mutate(
|
||||
&self,
|
||||
request: &DesktopMutationRequest,
|
||||
overwrite: OverwriteDecision,
|
||||
provider: &mut impl SecretProvider,
|
||||
) -> Result<MutationOutcome, DesktopError> {
|
||||
let repository = self.repository()?;
|
||||
let keys = self.keys()?;
|
||||
let mut committer = AutomaticTreeCommitter::for_source(
|
||||
&repository,
|
||||
request.source(),
|
||||
GitIdentity::ironstorage(),
|
||||
)
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Git, error))?;
|
||||
let mutator = TreeMutator::new(&repository, &keys);
|
||||
match request {
|
||||
DesktopMutationRequest::Remove(request) => {
|
||||
mutator.remove(request, overwrite, &mut committer)
|
||||
}
|
||||
DesktopMutationRequest::Move(request) => {
|
||||
mutator.move_tree(request, overwrite, None, provider, &mut committer)
|
||||
}
|
||||
DesktopMutationRequest::Copy(request) => {
|
||||
mutator.copy(request, overwrite, None, provider, &mut committer)
|
||||
}
|
||||
}
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Mutation, error))
|
||||
}
|
||||
|
||||
/// Enumerate storage-validated encryption keys for recipient selection.
|
||||
pub fn key_infos(&self) -> Result<Vec<KeyInfo>, DesktopError> {
|
||||
Ok(self.keys()?.infos().filter(KeyInfo::can_encrypt).collect())
|
||||
|
||||
@@ -229,17 +229,23 @@ impl fmt::Debug for ShowResult {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct NameMatch {
|
||||
path: String,
|
||||
kind: TreeNodeKind,
|
||||
id: TreeNodeId,
|
||||
}
|
||||
|
||||
impl NameMatch {
|
||||
pub fn path(&self) -> &str {
|
||||
&self.path
|
||||
self.id
|
||||
.path()
|
||||
.to_str()
|
||||
.expect("search construction rejects non-UTF-8 paths")
|
||||
}
|
||||
|
||||
pub fn kind(&self) -> TreeNodeKind {
|
||||
self.kind
|
||||
self.id.kind()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &TreeNodeId {
|
||||
&self.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,8 +495,7 @@ impl<'a> VaultReader<'a> {
|
||||
let name = display_name(directory.path().as_path())?;
|
||||
if folded.iter().any(|term| name.to_lowercase().contains(term)) {
|
||||
matches.push(NameMatch {
|
||||
path: path_text(directory.path().as_path())?,
|
||||
kind: TreeNodeKind::Directory,
|
||||
id: TreeNodeId::Directory(directory.path().clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -501,16 +506,12 @@ impl<'a> VaultReader<'a> {
|
||||
let name = display_name(entry.path().as_path())?;
|
||||
if folded.iter().any(|term| name.to_lowercase().contains(term)) {
|
||||
matches.push(NameMatch {
|
||||
path: path_text(entry.path().as_path())?,
|
||||
kind: TreeNodeKind::Entry,
|
||||
id: TreeNodeId::Entry(entry.path().clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
matches.sort_by(|left, right| left.path.cmp(&right.path));
|
||||
let included = matches
|
||||
.iter()
|
||||
.map(|matched| matched.path.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
matches.sort_by(|left, right| left.path().cmp(right.path()));
|
||||
let included = matches.iter().map(NameMatch::path).collect::<Vec<_>>();
|
||||
let tree = build_tree(&snapshot, &DirectoryPath::root(), Some(included.as_slice()))?;
|
||||
Ok(FindResults {
|
||||
terms: terms.to_vec(),
|
||||
|
||||
@@ -194,6 +194,10 @@ fn find_matches_entry_and_directory_names_case_insensitively() -> TestResult {
|
||||
);
|
||||
let personal = reader.find(&["PERSONAL".to_owned()])?;
|
||||
assert_eq!(personal.matches()[0].path(), "email/personal");
|
||||
assert_eq!(
|
||||
personal.matches()[0].id(),
|
||||
&ironstorage::read::TreeNodeId::Entry(EntryPath::parse("email/personal")?)
|
||||
);
|
||||
assert!(matches!(
|
||||
reader.find(&[]),
|
||||
Err(ReadError::MissingSearchTerms)
|
||||
|
||||
Reference in New Issue
Block a user