Skip to content

Commit

Permalink
Further refinement of backend-frontend communication
Browse files Browse the repository at this point in the history
  • Loading branch information
chipnertkj committed May 4, 2024
1 parent 3030aa0 commit 491832f
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 81 deletions.
83 changes: 60 additions & 23 deletions backend/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@ pub use chipbox_common as common;
pub use editor::audio_engine::stream_handle;
pub use editor::Editor;

use common::app::AwaitConfig;
use common::app::AwaitConfigReason;
use settings::SettingsExt as _;
use tauri::{async_runtime, Manager as _};

pub mod editor;
mod error;
mod project_selection;
mod settings;

/// Messages sent to the app thread.
/// Messages received by the app thread.
#[derive(Debug, PartialEq)]
pub enum ThreadMsg {
/// A message from the frontend.
/// Tauri forwarded a message from the frontend.
Frontend(common::app::FrontendMsg),
/// Main requested to exit.
/// Immediatelly closes the app thread.
Exit,
}
Expand All @@ -40,7 +42,9 @@ pub enum AppState {
#[default]
ReadingSettings,
/// Settings read has been attempted, but no valid configuration was found.
AwaitConfig(AwaitConfig),
AwaitConfig {
reason: AwaitConfigReason,
},
Idle {
settings: common::Settings,
},
Expand All @@ -50,16 +54,29 @@ pub enum AppState {
},
}

impl From<Option<common::Settings>> for AppState {
fn from(settings_opt: Option<common::Settings>) -> Self {
match settings_opt {
Some(settings) => AppState::Idle { settings },
None => AppState::AwaitConfig {
reason: AwaitConfigReason::NoConfig,
},
}
}
}

/// ???
impl From<&AppState> for common::app::State {
impl From<&AppState> for common::app::BackendAppState {
fn from(app: &AppState) -> Self {
match app {
AppState::ReadingSettings => common::app::State::ReadingSettings,
AppState::AwaitConfig(state) => {
common::app::State::AwaitConfig(state.clone())
AppState::ReadingSettings => {
common::app::BackendAppState::ReadingSettings
}
AppState::AwaitConfig { ref reason } => {
common::app::BackendAppState::AwaitConfig { reason: *reason }
}
AppState::Idle { .. } => common::app::State::Idle,
AppState::Edit { .. } => common::app::State::Editor,
AppState::Idle { .. } => common::app::BackendAppState::Idle,
AppState::Edit { .. } => common::app::BackendAppState::Editor,
}
}
}
Expand Down Expand Up @@ -103,10 +120,7 @@ impl AppThread {
);

// Update state based on whether there was a valid config.
self.data.state = match settings_opt {
Some(settings) => AppState::Idle { settings },
None => AppState::AwaitConfig(AwaitConfig::NoConfig),
};
self.data.state = settings_opt.into();

// Enter message loop.
self.poll_messages().await
Expand Down Expand Up @@ -189,9 +203,10 @@ impl AppThread {
/// Polls messages from the channel in a loop.
async fn poll_messages(&mut self) {
Self::poll_message_until(&mut self.rx, |msg| {
self.data
.handle_msg(msg)
.into()
match self.data.handle_msg(msg) {
true => Some(()),
false => None,
}
})
.await;
// Channel was closed.
Expand Down Expand Up @@ -228,10 +243,34 @@ impl AppData {
/// Handles a frontend message.
fn handle_frontend_msg(&mut self, msg: common::app::FrontendMsg) {
match msg {
common::app::FrontendMsg::QueryApp => AppThread::send_message(
&self.tauri_app,
common::app::BackendMsg::QueryAppResponse((&self.state).into()),
),
common::app::FrontendMsg::Query(query) => match query {
common::app::FrontendQuery::BackendAppState => {
AppThread::send_message(
&self.tauri_app,
common::app::BackendMsg::Response(
common::app::BackendResponse::BackendAppState(
(&self.state).into(),
),
),
)
}
common::app::FrontendQuery::Settings => {
let settings_opt = match self.state {
AppState::Idle { ref settings } => {
Some(settings.clone())
}
_ => None,
};
AppThread::send_message(
&self.tauri_app,
common::app::BackendMsg::Response(
common::app::BackendResponse::Settings(
settings_opt,
),
),
)
}
},
}
}
}
Expand All @@ -240,8 +279,6 @@ impl AppData {
/// Returns `Ok(None)` if the config file does not exist.
pub async fn read_settings() -> Result<Option<common::Settings>, settings::Error>
{
use settings::SettingsExt as _;

match common::Settings::read().await {
// Settings found.
Ok(settings) => Ok(Some(settings)),
Expand Down
17 changes: 13 additions & 4 deletions backend/lib/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ pub enum Error {
HomeDir,
}

/// Convenience conversion from `error::io::Error` to `settings::Error`.
/// Convenience conversion.
impl From<error::io::Error> for Error {
fn from(e: error::io::Error) -> Self {
Error::Io(e)
}
}

/// Convenience conversion from `error::serde::Error` to `settings::Error`.
/// Convenience conversion.
impl From<error::serde::Error> for Error {
fn from(e: error::serde::Error) -> Self {
Error::Serde(e)
}
}

/// `std::error::Error` implementation for `settings::Error`.
/// Error implementation.
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Expand All @@ -42,7 +42,7 @@ impl std::error::Error for Error {
}
}

/// `std::fmt::Display` implementation for `settings::Error`.
/// Display implementation.
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down Expand Up @@ -76,19 +76,22 @@ impl SettingsExt<common::Settings> for common::Settings {
const DATA_DIR: &str = ".chipbox";
/// Name of the settings file.
const FILENAME: &str = "settings.json";

// Retrieve the path to the HOME directory.
let home_path = home::home_dir().ok_or(Error::HomeDir)?;
// Construct the path to the settings file.
let path = home_path
.join(DATA_DIR)
.join(FILENAME);

// Ensure the directory exists.
fs::create_dir_all(path.parent().unwrap())
.await
.map_err(|e| error::io::Error {
inner: e,
path: path.clone(),
})?;

// Convert the path to a canonical path.
let canonical = path
.canonicalize()
Expand All @@ -100,32 +103,38 @@ impl SettingsExt<common::Settings> for common::Settings {
async fn read() -> Result<Self> {
// Retrieve the path to the settings file.
let path = Self::file_path().await?;

// Read the settings file.
let data = fs::read_to_string(&path)
.await
.map_err(|e| {
let path = path.to_owned();
error::io::Error { inner: e, path }
})?;

// Parse the settings file.
let settings = serde_json::from_str(&data)
.map_err(|e| error::serde::Error { e, path })?;

// Return settings.
Ok(settings)
}

async fn write(&self) -> Result<()> {
// Retrieve the path to the settings file.
let path = Self::file_path().await?;

// Serialize the settings file.
let data = serde_json::to_string(&self).map_err(|e| {
let path = path.to_owned();
error::serde::Error { e, path }
})?;

// Write the settings file.
fs::write(&path, data)
.await
.map_err(|e| error::io::Error { inner: e, path })?;

// All done.
Ok(())
}
Expand Down
10 changes: 5 additions & 5 deletions backend/src/app_thread.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::backend_lib::{AppThread, ThreadMsg};
use crate::glue::msg::BackendLibTx;
use crate::glue::msg::BackendAppTx;
use tauri::async_runtime::{self, JoinHandle, Mutex};
use tauri::Manager as _;

Expand Down Expand Up @@ -54,7 +54,7 @@ pub async fn start(
let (tx, rx) = async_runtime::channel::<ThreadMsg>(128);

// Store the message TX in the app state.
app.manage(BackendLibTx(tx));
app.manage(BackendAppTx(tx));

// Start the app thread.
let join_handle =
Expand All @@ -74,11 +74,11 @@ pub async fn close(
tracing::trace!("Sending exit message to app thread.");

// Get message sender.
match app.try_state::<BackendLibTx>() {
match app.try_state::<BackendAppTx>() {
// Message sender state is available.
Some(state) => {
// Get message sender.
let BackendLibTx(ref tx) = state.inner();
let BackendAppTx(ref tx) = state.inner();

// Send exit message.
match tx.send(ThreadMsg::Exit).await {
Expand All @@ -104,5 +104,5 @@ pub async fn close(
};

// All ok.
tracing::trace!("App thread closed.");
tracing::trace!("App closed.");
}
45 changes: 38 additions & 7 deletions common/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,33 +1,64 @@
use crate::Settings;
use serde::{Deserialize, Serialize};

/// Messages sent by the backend app thread.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum BackendMsg {
ReadingSettings,
QueryAppResponse(State),
Response(BackendResponse),
}

impl BackendMsg {
/// JS Event name used by the frontend.
pub const fn event_name() -> &'static str {
"chipbox-app-message"
}
}

/// Messages sent by the backend app thread in response to frontend queries.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum BackendResponse {
/// Respond with current `BackendAppState`.
BackendAppState(BackendAppState),
/// Respond with current `Settings`.
Settings(Option<Settings>),
}

/// Messages sent by the frontend app thread.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum FrontendMsg {
QueryApp,
/// Query information from the backend.
Query(FrontendQuery),
}

/// Messages sent by the frontend app thread requesting information from the backend.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum FrontendQuery {
/// Query current `BackendAppState`.
BackendAppState,
/// Query current `Settings`.
Settings,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)]
pub enum AwaitConfig {
/// Reason why the user config is not ready.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy, Default)]
pub enum AwaitConfigReason {
#[default]
/// It's the first time the application has been started.
/// The user has not yet configured the application.
NoConfig,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum State {
/// Minimal description of the current state of the backend.
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy, Default)]
pub enum BackendAppState {
/// Backend is currently reading user config.
#[default]
ReadingSettings,
AwaitConfig(AwaitConfig),
/// User config has been read, but no valid configuration was found.
AwaitConfig { reason: AwaitConfigReason },
/// User config was read and is valid.
Idle,
/// Backend is ready to edit a project.
Editor,
}
Loading

0 comments on commit 491832f

Please sign in to comment.