Present iPhone Git actions (#51)
This commit is contained in:
1 parent
fcf649e56d
commit
65eb1da71c
8 files changed
+1054
-90
No files matched your search
@@ -195,9 +195,38 @@ struct MobileAuthenticationStatus {
|
||||
next_editor_id: u64,
|
||||
editors: BTreeMap<u64, MobileEntryDraft>,
|
||||
mutation_active: bool,
|
||||
repository_operation_active: bool,
|
||||
watch_shared_totp_entries: std::collections::BTreeSet<EntryPath>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum MobileRepositoryOperation {
|
||||
Commit,
|
||||
Fetch,
|
||||
Pull,
|
||||
Push,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum MobileRepositoryOperationError {
|
||||
Busy,
|
||||
DirtyEditor,
|
||||
OpenEditor,
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
pub(crate) struct MobileRepositoryOperationGuard<'a> {
|
||||
authentication: &'a MobileAuthentication,
|
||||
}
|
||||
|
||||
impl Drop for MobileRepositoryOperationGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut status) = self.authentication.status.lock() {
|
||||
status.repository_operation_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One process-wide mobile authentication lease shared by every tab and viewer.
|
||||
pub struct MobileAuthentication {
|
||||
config: Config,
|
||||
@@ -232,6 +261,7 @@ impl MobileAuthentication {
|
||||
next_editor_id: 0,
|
||||
editors: BTreeMap::new(),
|
||||
mutation_active: false,
|
||||
repository_operation_active: false,
|
||||
watch_shared_totp_entries: config.watch_shared_totp_entries().clone(),
|
||||
}),
|
||||
config,
|
||||
@@ -667,6 +697,7 @@ impl MobileAuthentication {
|
||||
field: u64,
|
||||
value: String,
|
||||
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
||||
self.ensure_repository_idle()?;
|
||||
let mut document = self.open_active_document(path)?;
|
||||
document
|
||||
.replace_field_value(EntryFieldId::from_value(field), value.into_bytes())
|
||||
@@ -824,6 +855,7 @@ impl MobileAuthentication {
|
||||
fields: Vec<MobileEntryEditorInput>,
|
||||
) -> Result<MobileEntryPage, MobileAuthenticationError> {
|
||||
self.ensure_active()?;
|
||||
self.ensure_repository_idle()?;
|
||||
let mut draft = self
|
||||
.status()?
|
||||
.editors
|
||||
@@ -939,10 +971,10 @@ impl MobileAuthentication {
|
||||
draft: MobileEntryDraft,
|
||||
) -> Result<MobileEntryEditorSession, MobileAuthenticationError> {
|
||||
let mut status = self.status()?;
|
||||
if status.mutation_active {
|
||||
if status.mutation_active || status.repository_operation_active {
|
||||
return Err(entry_detail(
|
||||
"Password Action In Progress",
|
||||
"wait for the current move, copy, or delete action to finish",
|
||||
"wait for the current password-store action to finish",
|
||||
));
|
||||
}
|
||||
let id = status.next_editor_id;
|
||||
@@ -960,7 +992,7 @@ impl MobileAuthentication {
|
||||
fn reserve_entry_mutation(&self) -> Result<(), MobileAuthenticationError> {
|
||||
let mut status = self.status()?;
|
||||
// ponytail: serialize mobile mutations; use per-path reservations if concurrent UI needs it.
|
||||
if status.mutation_active {
|
||||
if status.mutation_active || status.repository_operation_active {
|
||||
return Err(MobileAuthenticationError::new(
|
||||
MobileAuthenticationErrorKind::Conflict,
|
||||
"Password Action In Progress",
|
||||
@@ -971,6 +1003,44 @@ impl MobileAuthentication {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn reserve_repository_operation(
|
||||
&self,
|
||||
operation: MobileRepositoryOperation,
|
||||
) -> Result<MobileRepositoryOperationGuard<'_>, MobileRepositoryOperationError> {
|
||||
let mut status = self
|
||||
.status
|
||||
.lock()
|
||||
.map_err(|_| MobileRepositoryOperationError::Unavailable)?;
|
||||
let dirty_editor = status
|
||||
.editors
|
||||
.values()
|
||||
.any(|draft| draft.document().is_modified());
|
||||
if let Some(error) = repository_operation_conflict(
|
||||
operation,
|
||||
status.mutation_active || status.repository_operation_active,
|
||||
!status.editors.is_empty(),
|
||||
dirty_editor,
|
||||
) {
|
||||
return Err(error);
|
||||
}
|
||||
status.repository_operation_active = true;
|
||||
drop(status);
|
||||
Ok(MobileRepositoryOperationGuard {
|
||||
authentication: self,
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_repository_idle(&self) -> Result<(), MobileAuthenticationError> {
|
||||
if self.status()?.repository_operation_active {
|
||||
Err(entry_detail(
|
||||
"Repository Action In Progress",
|
||||
"wait for the current commit, fetch, pull, or push action to finish",
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn release_entry_mutation(&self) {
|
||||
if let Ok(mut status) = self.status.lock() {
|
||||
status.mutation_active = false;
|
||||
@@ -1061,6 +1131,27 @@ impl MobileAuthentication {
|
||||
}
|
||||
}
|
||||
|
||||
fn repository_operation_conflict(
|
||||
operation: MobileRepositoryOperation,
|
||||
busy: bool,
|
||||
open_editor: bool,
|
||||
dirty_editor: bool,
|
||||
) -> Option<MobileRepositoryOperationError> {
|
||||
if busy {
|
||||
Some(MobileRepositoryOperationError::Busy)
|
||||
} else if operation == MobileRepositoryOperation::Pull && open_editor {
|
||||
Some(if dirty_editor {
|
||||
MobileRepositoryOperationError::DirtyEditor
|
||||
} else {
|
||||
MobileRepositoryOperationError::OpenEditor
|
||||
})
|
||||
} else if operation == MobileRepositoryOperation::Push && dirty_editor {
|
||||
Some(MobileRepositoryOperationError::DirtyEditor)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyOnlyProvider<'a> {
|
||||
handle: NativeAuthenticationHandle,
|
||||
fingerprint: &'a str,
|
||||
@@ -1198,3 +1289,34 @@ fn entry_detail(title: &str, error: impl fmt::Display) -> MobileAuthenticationEr
|
||||
error.to_string(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
MobileRepositoryOperation, MobileRepositoryOperationError, repository_operation_conflict,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn repository_actions_protect_editors_and_serialize_mutations() {
|
||||
assert_eq!(
|
||||
repository_operation_conflict(MobileRepositoryOperation::Pull, false, true, false),
|
||||
Some(MobileRepositoryOperationError::OpenEditor)
|
||||
);
|
||||
assert_eq!(
|
||||
repository_operation_conflict(MobileRepositoryOperation::Pull, false, true, true),
|
||||
Some(MobileRepositoryOperationError::DirtyEditor)
|
||||
);
|
||||
assert_eq!(
|
||||
repository_operation_conflict(MobileRepositoryOperation::Push, false, true, true),
|
||||
Some(MobileRepositoryOperationError::DirtyEditor)
|
||||
);
|
||||
assert_eq!(
|
||||
repository_operation_conflict(MobileRepositoryOperation::Fetch, false, true, true),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
repository_operation_conflict(MobileRepositoryOperation::Commit, true, false, false),
|
||||
Some(MobileRepositoryOperationError::Busy)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user