Skip to content

Commit

Permalink
feat(rust): fixes many papercuts and better avoid and handle state re…
Browse files Browse the repository at this point in the history
…sets
  • Loading branch information
davide-baldo authored and adrianbenavides committed Nov 7, 2023
1 parent 737ea00 commit 9adf6b4
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ impl Invitations for Controller {
debug!(?invitation_id, "sending request to ignore invitation");
let req = Request::post(format!("/v0/invites/{invitation_id}/ignore"));
self.secure_client
.ask(ctx, API_SERVICE, req)
.tell(ctx, API_SERVICE, req)
.await
.into_diagnostic()?
.success()
Expand Down
40 changes: 32 additions & 8 deletions implementations/rust/ockam/ockam_app_lib/src/api/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::api::to_c_string;
use crate::cli::check_ockam_executable;
use crate::state::AppState;
use libc::c_char;
use ockam_api::cli_state::CliState;
use std::pin::Pin;
use tracing::{error, info};

Expand All @@ -28,6 +29,8 @@ const ERROR_NOT_INITIALIZED: &str =

/// This functions initializes the application state.
/// It must be called before any other function.
/// Returns true if successful, false otherwise.
/// In case of failure the application should propose a reset to the user.
#[no_mangle]
extern "C" fn initialize_application(
// we can't use any type alias because cbindgen doesn't support them
Expand All @@ -37,7 +40,7 @@ extern "C" fn initialize_application(
notification_callback: unsafe extern "C" fn(
notification: super::notification::c::Notification,
) -> (),
) {
) -> bool {
for &key in &[
"OCKAM_CONTROLLER_ADDR",
"OCKAM_CONTROLLER_IDENTITY_ID",
Expand All @@ -49,18 +52,27 @@ extern "C" fn initialize_application(
}
}

let app_state = AppState::new(
let result = AppState::new(
Some(super::state::rust::ApplicationStateCallback::new(
application_state_callback,
)),
Some(super::notification::rust::NotificationCallback::new(
notification_callback,
)),
);

let app_state = match result {
Ok(app_state) => app_state,
Err(err) => {
eprintln!("Failed to load the local Ockam configuration: {err}");
return false;
}
};
app_state.setup_logging();

#[cfg(target_os = "macos")]
crate::cli::add_homebrew_to_path();
crate::cli::set_no_automatic_reset();

if let Err(err) = check_ockam_executable() {
error!(?err, "Couldn't find the ockam executable");
Expand All @@ -83,6 +95,8 @@ extern "C" fn initialize_application(
app_state.publish_state().await;
app_state.load_model_state().await;
});

true
}

/// Accept the invitation with the provided id.
Expand Down Expand Up @@ -252,13 +266,23 @@ extern "C" fn create_local_service(
/// A restart is **required** afterward.
#[no_mangle]
extern "C" fn reset_application_state() {
let app_state = unsafe { APPLICATION_STATE.as_ref() }.expect(ERROR_NOT_INITIALIZED);
app_state.context().runtime().block_on(async move {
let result = app_state.reset().await;
if let Err(err) = result {
error!(?err, "Cannot reset the application state");
let app_state = unsafe { APPLICATION_STATE.as_ref() };
match app_state {
Some(app_state) => {
app_state.context().runtime().block_on(async move {
let result = app_state.reset().await;
if let Err(err) = result {
error!(?err, "Cannot reset the application state");
}
});
}
});
None => {
// allow disk state reset even if we don't have an application state
CliState::backup_and_reset().expect(
"Failed to initialize CliState. Try to manually remove the '~/.ockam' directory",
);
}
}
}

/// Starts user enrollment
Expand Down
7 changes: 7 additions & 0 deletions implementations/rust/ockam/ockam_app_lib/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ pub(crate) fn add_homebrew_to_path() {
}
}

/// Set the OCKAM_NO_AUTOMATIC_RESET environment variable to avoid
/// automatically resetting the node when the application find an incoherent state
/// this may happen if a command is launched during a write operation
pub(crate) fn set_no_automatic_reset() {
std::env::set_var("OCKAM_NO_AUTOMATIC_RESET", "true");
}

/// Check that the OCKAM environment variable defines an absolute path
/// Otherwise we might fail to run the ockam command when starting the desktop application from an unexpected path
/// Check that the ockam command can at least be called with the `--version` option and log
Expand Down
9 changes: 4 additions & 5 deletions implementations/rust/ockam/ockam_app_lib/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,12 @@ pub struct AppState {
}

impl AppState {
/// Create a new AppState
/// Create a new AppState, if it fails you can assume it's because the state cannot be loaded
pub fn new(
application_state_callback: Option<ApplicationStateCallback>,
notification_callback: Option<NotificationCallback>,
) -> AppState {
let cli_state =
CliState::initialize().expect("Failed to load the local Ockam configuration");
) -> Result<AppState> {
let cli_state = CliState::initialize()?;
let (context, mut executor) = NodeBuilder::new().no_logging().build();
let context = Arc::new(context);
let runtime = context.runtime().clone();
Expand Down Expand Up @@ -133,7 +132,7 @@ impl AppState {
}
};

runtime.block_on(future)
Ok(runtime.block_on(future))
}

/// Load a previously persisted ModelState and start refreshing schedule
Expand Down
3 changes: 3 additions & 0 deletions implementations/rust/ockam/ockam_command/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ impl CommandGlobalOpts {
Ok(state) => state,
Err(err) => {
eprintln!("Failed to initialize state: {}", err);
if std::env::var("OCKAM_NO_AUTOMATIC_RESET").is_ok() {
std::process::exit(exitcode::CONFIG);
}
let state = CliState::backup_and_reset().expect(
"Failed to initialize CliState. Try to manually remove the '~/.ockam' directory",
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
782D5BAF2AC33F9400D1B27F /* libockam_app_lib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 782D5B792AC1E36700D1B27F /* libockam_app_lib.a */; };
783B62722AC432B700880261 /* Bridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 783B62712AC432B700880261 /* Bridge.swift */; };
789FA6462AE142280009FF7F /* ExportOptions.plist in Resources */ = {isa = PBXBuildFile; fileRef = 789FA6452AE142280009FF7F /* ExportOptions.plist */; };
78BD349D2AFA621500F09058 /* BrokenStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78BD349C2AFA621500F09058 /* BrokenStateView.swift */; };
78D520832ADD5CAE00F36B64 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D520822ADD5CAE00F36B64 /* Helpers.swift */; };
78ED0DA12AD4354400574AE9 /* CreateService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED0DA02AD4354400574AE9 /* CreateService.swift */; };
78ED0DA32AD4501200574AE9 /* ShareService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED0DA22AD4501200574AE9 /* ShareService.swift */; };
Expand All @@ -44,6 +45,7 @@
783B62712AC432B700880261 /* Bridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bridge.swift; sourceTree = "<group>"; };
785D8D372AD5369100DF5004 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
789FA6452AE142280009FF7F /* ExportOptions.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = ExportOptions.plist; sourceTree = "<group>"; };
78BD349C2AFA621500F09058 /* BrokenStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenStateView.swift; sourceTree = "<group>"; };
78D520822ADD5CAE00F36B64 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
78ED0DA02AD4354400574AE9 /* CreateService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateService.swift; sourceTree = "<group>"; };
78ED0DA22AD4501200574AE9 /* ShareService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -100,6 +102,7 @@
7827D5062AD93EE700F7A20F /* ProfilePicture.swift */,
7827D5082AD93F1700F7A20F /* ClickableMenuEntry.swift */,
78D520822ADD5CAE00F36B64 /* Helpers.swift */,
78BD349C2AFA621500F09058 /* BrokenStateView.swift */,
);
path = Ockam;
sourceTree = "<group>";
Expand Down Expand Up @@ -206,6 +209,7 @@
7827D5072AD93EE700F7A20F /* ProfilePicture.swift in Sources */,
782D5B492AC1D3D700D1B27F /* OckamApp.swift in Sources */,
78ED0DA32AD4501200574AE9 /* ShareService.swift in Sources */,
78BD349D2AFA621500F09058 /* BrokenStateView.swift in Sources */,
4CB45C522AEB06C700D27F22 /* OpenUrlView.swift in Sources */,
783B62722AC432B700880261 /* Bridge.swift in Sources */,
);
Expand Down
2 changes: 1 addition & 1 deletion implementations/swift/ockam/ockam_app/Ockam/Bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ typedef struct C_Notification {
/**
* This functions initializes the application state.
*/
void initialize_application(void (*application_state_callback)(struct C_ApplicationState state),
bool initialize_application(void (*application_state_callback)(struct C_ApplicationState state),
void (*notification_callback)(struct C_Notification notification));

/**
Expand Down
6 changes: 4 additions & 2 deletions implementations/swift/ockam/ockam_app/Ockam/Bridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func swift_application_snapshot() -> ApplicationState {
return convertApplicationState(cState: application_state_snapshot())
}

func swift_initialize_application() {
func swift_initialize_application() -> Bool {
let applicationStateClosure: @convention(c) (C_ApplicationState) -> Void = { state in
StateContainer.shared.update(state: convertApplicationState(cState: state))
}
Expand All @@ -277,7 +277,7 @@ func swift_initialize_application() {
}
}

initialize_application(applicationStateClosure, notificationClosure)
let result = initialize_application(applicationStateClosure, notificationClosure)

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {
granted, error in
Expand All @@ -287,6 +287,8 @@ func swift_initialize_application() {
print("Notifications not allowed")
}
}

return result
}

func optional_string(str: UnsafePointer<Int8>?) -> String? {
Expand Down
44 changes: 44 additions & 0 deletions implementations/swift/ockam/ockam_app/Ockam/BrokenStateView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Foundation

import SwiftUI

struct BrokenStateView: View {
var body: some View {
VStack {
Text("The Ockam state is either corrupted or incompatible with the current version.\n\nYou can reset the state or quit the application and try another version.")
.multilineTextAlignment(.center)
.padding()
Spacer()
HStack {
Spacer()
Button(
action: {
restartCurrentProcess()
},
label: {
Text("Reset State")
}
)
Button(
action: {
exit(1)
},
label: {
Text("Quit")
}
)
.keyboardShortcut(.defaultAction)
.padding(10)
}
.background(.black.opacity(0.1))
}
.frame(width: 300, height: 160)
}

}

struct BrokenState_Previews: PreviewProvider {
static var previews: some View {
BrokenStateView()
}
}
8 changes: 8 additions & 0 deletions implementations/swift/ockam/ockam_app/Ockam/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ struct MainView: View {
.onReceive(timer) { time in
optionPressed = NSEvent.modifierFlags.contains(.option)
}
.onReceive(state.$groups) { _ in
// the selected group could have been deleted, if so, reset the selection
if selectedGroup != "" {
if !state.groups.contains(where: { $0.id == selectedGroup }) {
selectedGroup = ""
}
}
}
}

func closeWindow() {
Expand Down
44 changes: 34 additions & 10 deletions implementations/swift/ockam/ockam_app/Ockam/OckamApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,36 +44,58 @@ class StateContainer {
@main
struct OckamApp: App {
@State var state: ApplicationState = StateContainer.shared.state
// when the application initialization fails to load we enter a broken state
// where we only propose a reset to the user
var broken: Bool = false
@Environment(\.openWindow) var openWindow

var body: some Scene {
MenuBarExtra {
MainView(state: $state)
.onAppear(perform: {
StateContainer.shared.callback(callback: { state in
self.state = state
})
if broken {
// we need to give the user a way to re-open the window
// to provide at least a way to quit the application
ClickableMenuEntry(text: "Open Window", action: {
openWindow(id: "broken-state")
})
.frame(width: 120,height: 40)
} else {
MainView(state: $state)
.onAppear(perform: {
StateContainer.shared.callback(callback: { state in
self.state = state
})
})
}
} label: {
Image("MenuBarIcon")
.renderingMode(.template)
.contentShape(Rectangle())
.buttonStyle(PlainButtonStyle())
.onAppear(perform: {
if broken {
openWindow(id: "broken-state")
}
})
}
.menuBarExtraStyle(.window)
.commandsRemoved()

Window("Cannot load state", id: "broken-state") {
BrokenStateView()
}
.windowResizability(.contentSize)

// Declare a window with an empty view to handle the ockam:// url
// A hack to overcome the fact that `onOpenURL` only works on `Windows`
Window("Accepting invitation", id: "accepting-invitation") {
OpenUrlView(enrolled: $state.enrolled)
}
.windowResizability(.contentSize)
.commandsRemoved()

// Declare a state-independent window, not open by default
Window("Create a service", id: "create-service") {
CreateServiceView()
}
.windowResizability(.contentSize)
.commandsRemoved()

// Declare a "template" of windows, dependent on the LocalService.ID, not open by default
WindowGroup("Share a service", id: "share-service", for: LocalService.ID.self) {
Expand All @@ -84,10 +106,12 @@ struct OckamApp: App {
).unsafelyUnwrapped)
}
.windowResizability(.contentSize)
.commandsRemoved()
}

init() {
swift_initialize_application()
if !swift_initialize_application() {
broken = true
print("Could not initialize application: entering broken state")
}
}
}
2 changes: 1 addition & 1 deletion implementations/swift/ockam/ockam_app/Ockam/Services.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ struct ServiceGroupButton: View {
Spacer()
Circle()
.fill(Color.orange)
.frame(width: 8)
.frame(width: 8, height: 8)
.opacity(group.invitations.isEmpty ? 0 : 1)
Image(systemName: "chevron.right")
.frame(width: 32, height: 32)
Expand Down

0 comments on commit 9adf6b4

Please sign in to comment.