Add explicit vocabulary and named operation contexts
This commit is contained in:
+107
-44
@@ -287,17 +287,30 @@ pub trait OperationExecutor: Send + 'static {
|
||||
}
|
||||
}
|
||||
|
||||
enum DeliveryMode {
|
||||
FirstIssue,
|
||||
Reissue,
|
||||
}
|
||||
|
||||
struct PreparedDestinationContext {
|
||||
identity: PublicKey,
|
||||
include_qr: bool,
|
||||
mode: DeliveryMode,
|
||||
attempt: FirstIssueAttempt,
|
||||
generation: u64,
|
||||
destination: PreparedDestination,
|
||||
}
|
||||
|
||||
struct RestorePreviewContext {
|
||||
_archive_name: String,
|
||||
generation: u64,
|
||||
preview: RestorePreview,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ApplicationExecutor {
|
||||
prepared_destination: Option<(
|
||||
PublicKey,
|
||||
bool,
|
||||
bool,
|
||||
FirstIssueAttempt,
|
||||
u64,
|
||||
PreparedDestination,
|
||||
)>,
|
||||
restore_preview: Option<(String, u64, RestorePreview)>,
|
||||
prepared_destination: Option<PreparedDestinationContext>,
|
||||
restore_preview: Option<RestorePreviewContext>,
|
||||
}
|
||||
|
||||
impl OperationExecutor for ApplicationExecutor {
|
||||
@@ -379,14 +392,18 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
attempt,
|
||||
prepared_generation: generation,
|
||||
};
|
||||
self.prepared_destination = Some((
|
||||
self.prepared_destination = Some(PreparedDestinationContext {
|
||||
identity,
|
||||
include_qr,
|
||||
reissue,
|
||||
mode: if reissue {
|
||||
DeliveryMode::Reissue
|
||||
} else {
|
||||
DeliveryMode::FirstIssue
|
||||
},
|
||||
attempt,
|
||||
generation,
|
||||
destination,
|
||||
));
|
||||
});
|
||||
CommandResult::DeliveryDestination(summary)
|
||||
}),
|
||||
UiCommand::DeliverFirstIssue {
|
||||
@@ -394,23 +411,16 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
overwrite_confirmed,
|
||||
prepared_generation,
|
||||
} => {
|
||||
let (
|
||||
identity,
|
||||
include_qr,
|
||||
reissue,
|
||||
prepared_attempt,
|
||||
destination_generation,
|
||||
mut destination,
|
||||
) = self
|
||||
let prepared = self
|
||||
.prepared_destination
|
||||
.take()
|
||||
.ok_or_else(|| OperationError {
|
||||
message: "No prepared output destination is available.".to_owned(),
|
||||
})?;
|
||||
if reissue
|
||||
|| prepared_attempt != attempt
|
||||
if !matches!(prepared.mode, DeliveryMode::FirstIssue)
|
||||
|| prepared.attempt != attempt
|
||||
|| !destination_generation_matches(
|
||||
destination_generation,
|
||||
prepared.generation,
|
||||
prepared_generation,
|
||||
generation,
|
||||
)
|
||||
@@ -420,6 +430,9 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
.to_owned(),
|
||||
});
|
||||
}
|
||||
let identity = prepared.identity;
|
||||
let include_qr = prepared.include_qr;
|
||||
let mut destination = prepared.destination;
|
||||
if destination.overwrite_confirmation_required() {
|
||||
if !overwrite_confirmed {
|
||||
return Err(OperationError {
|
||||
@@ -519,7 +532,11 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
changes,
|
||||
preview_generation: generation,
|
||||
};
|
||||
self.restore_preview = Some((file_name, generation, preview));
|
||||
self.restore_preview = Some(RestorePreviewContext {
|
||||
_archive_name: file_name,
|
||||
generation,
|
||||
preview,
|
||||
});
|
||||
CommandResult::RestorePreview(summary)
|
||||
})
|
||||
}
|
||||
@@ -532,14 +549,12 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
Ok(CommandResult::Cancelled)
|
||||
}
|
||||
UiCommand::ConfirmRestore { preview_generation } => {
|
||||
let (_, stored_generation, preview) =
|
||||
self.restore_preview.take().ok_or_else(|| OperationError {
|
||||
message:
|
||||
"Restore preview is no longer available; select the archive again."
|
||||
.to_owned(),
|
||||
})?;
|
||||
let context = self.restore_preview.take().ok_or_else(|| OperationError {
|
||||
message: "Restore preview is no longer available; select the archive again."
|
||||
.to_owned(),
|
||||
})?;
|
||||
if !restore_preview_generation_matches(
|
||||
stored_generation,
|
||||
context.generation,
|
||||
preview_generation,
|
||||
generation,
|
||||
) {
|
||||
@@ -547,7 +562,8 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
message: "Restore preview is stale; select the archive again.".to_owned(),
|
||||
});
|
||||
}
|
||||
app.confirm_restore(preview).map(CommandResult::Restored)
|
||||
app.confirm_restore(context.preview)
|
||||
.map(CommandResult::Restored)
|
||||
}
|
||||
};
|
||||
result.map_err(|error| OperationError {
|
||||
@@ -573,28 +589,23 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
overwrite_confirmed,
|
||||
prepared_generation,
|
||||
} => {
|
||||
let (
|
||||
prepared_identity,
|
||||
_include_qr,
|
||||
reissue,
|
||||
_attempt,
|
||||
destination_generation,
|
||||
mut destination,
|
||||
) = self
|
||||
let prepared = self
|
||||
.prepared_destination
|
||||
.take()
|
||||
.ok_or_else(|| OperationError {
|
||||
message: "No prepared output destination is available.".to_owned(),
|
||||
})?;
|
||||
if prepared_identity != identity
|
||||
|| !reissue
|
||||
|| destination_generation != prepared_generation
|
||||
if prepared.identity != identity
|
||||
|| !matches!(prepared.mode, DeliveryMode::Reissue)
|
||||
|| prepared.generation != prepared_generation
|
||||
{
|
||||
return Err(OperationError {
|
||||
message: "Prepared output destination is stale; select it again."
|
||||
.to_owned(),
|
||||
});
|
||||
}
|
||||
let include_qr = prepared.include_qr;
|
||||
let mut destination = prepared.destination;
|
||||
if destination.overwrite_confirmation_required() {
|
||||
if !overwrite_confirmed {
|
||||
return Err(OperationError {
|
||||
@@ -604,7 +615,7 @@ impl OperationExecutor for ApplicationExecutor {
|
||||
destination = destination.confirm_overwrite().map_err(operation_error)?;
|
||||
}
|
||||
let delivery = app
|
||||
.reissue_client_file(identity, password.expose(), destination, _include_qr)
|
||||
.reissue_client_file(identity, password.expose(), destination, include_qr)
|
||||
.map_err(|error| OperationError {
|
||||
message: error.to_string(),
|
||||
})?;
|
||||
@@ -1088,6 +1099,58 @@ mod tests {
|
||||
assert!(!restore_preview_generation_matches(8, 8, 8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prepared_delivery_is_cleared_by_cancel_and_unrelated_commands() {
|
||||
let (_root, mut app) = application();
|
||||
let identity = PublicKey::from_bytes([44; 32]);
|
||||
let mut executor = ApplicationExecutor::default();
|
||||
let path = _root.path().join("prepared.conf");
|
||||
let prepare = || UiCommand::PrepareDelivery {
|
||||
identity,
|
||||
path: path.clone(),
|
||||
include_qr: false,
|
||||
reissue: false,
|
||||
attempt: FirstIssueAttempt::Initial,
|
||||
};
|
||||
let deliver = || UiCommand::DeliverFirstIssue {
|
||||
attempt: FirstIssueAttempt::Initial,
|
||||
overwrite_confirmed: false,
|
||||
prepared_generation: 7,
|
||||
};
|
||||
|
||||
assert!(matches!(
|
||||
executor.execute_at(&mut app, prepare(), 7),
|
||||
Ok(CommandResult::DeliveryDestination(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
executor.execute_at(&mut app, UiCommand::CancelPreparedDelivery, 7),
|
||||
Ok(CommandResult::Cancelled)
|
||||
));
|
||||
assert_eq!(
|
||||
executor
|
||||
.execute_at(&mut app, deliver(), 7)
|
||||
.unwrap_err()
|
||||
.message,
|
||||
"No prepared output destination is available."
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
executor.execute_at(&mut app, prepare(), 7),
|
||||
Ok(CommandResult::DeliveryDestination(_))
|
||||
));
|
||||
assert!(matches!(
|
||||
executor.execute_at(&mut app, UiCommand::ListBackups, 7),
|
||||
Ok(CommandResult::Backups(_))
|
||||
));
|
||||
assert_eq!(
|
||||
executor
|
||||
.execute_at(&mut app, deliver(), 7)
|
||||
.unwrap_err()
|
||||
.message,
|
||||
"No prepared output destination is available."
|
||||
);
|
||||
}
|
||||
|
||||
struct FakeStatistics {
|
||||
started: Option<mpsc::SyncSender<()>>,
|
||||
release: Option<Receiver<()>>,
|
||||
|
||||
Reference in New Issue
Block a user