v0.2.6-pre.004
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/app_state.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
//! Shared backend state owned by the Wallet Desk Tauri application.
|
||||
|
||||
@@ -61,7 +61,7 @@ impl AppState {
|
||||
});
|
||||
}
|
||||
|
||||
/// Builds the safe Config-composition status DTO exposed during pre.003.
|
||||
/// Builds the safe runtime status DTO exposed by the Wallet Desk shell.
|
||||
pub(crate) fn runtime_status(&self) -> ksp_core_lib::Result<crate::RuntimeStatusDto> {
|
||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
||||
let document_count = u32::try_from(document_count);
|
||||
@@ -100,13 +100,19 @@ impl AppState {
|
||||
effective_wallets_directory_created_on_startup: self.wallet_config_startup.effective_directory_created_on_startup(),
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
root_wallets_directory_created_on_startup: self.wallet_config_startup.root_directory_created_on_startup(),
|
||||
shell_phase: "pre.003-config-wallet".to_owned(),
|
||||
shell_phase: "pre.004-wallet-inventory".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
wallets_directory: resolved.wallets_directory().to_string_lossy().into_owned(),
|
||||
wallets_subdirectory,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the effective Config-managed Wallet directory used by inventory operations.
|
||||
#[must_use]
|
||||
pub(crate) fn wallet_inventory_root(&self) -> &std::path::Path {
|
||||
return self.wallet_config_startup.resolved().effective_wallets_directory();
|
||||
}
|
||||
|
||||
/// Returns the resolved common splash timings captured during bootstrap.
|
||||
#[must_use]
|
||||
pub(crate) const fn splash_settings(&self) -> crate::SplashSettings {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/constants.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Logging targets, domains and composite component identifiers owned by Wallet Desk.
|
||||
|
||||
@@ -17,6 +17,8 @@ pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
pub(crate) const TRACING_DOMAIN_SHELL: &str = "wallet.shell";
|
||||
/// Structured domain used while preparing Wallet filesystem roots.
|
||||
pub(crate) const TRACING_DOMAIN_WALLET_CONFIG: &str = "wallet.config";
|
||||
/// Structured domain used while enumerating and inspecting locked Wallet files.
|
||||
pub(crate) const TRACING_DOMAIN_WALLET_INVENTORY: &str = "wallet.inventory";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) const TRACING_DOMAIN_WINDOWS: &str = "desktop.window";
|
||||
/// Owning target for backend events emitted by Wallet Desk.
|
||||
@@ -27,3 +29,5 @@ pub(crate) const TRACING_TARGET_FRONTEND: &str = "ksp-app-wallet-desk.frontend";
|
||||
pub(crate) const TRACING_TARGET_FRONTEND_MAIN: &str = "ksp-app-wallet-desk.frontend.main";
|
||||
/// Owning target for splash-window frontend events.
|
||||
pub(crate) const TRACING_TARGET_FRONTEND_SPLASH: &str = "ksp-app-wallet-desk.frontend.splash";
|
||||
/// Native Wallet filename suffix accepted by the Wallet Desk inventory.
|
||||
pub(crate) const WALLET_FILE_SUFFIX: &str = ".kspwallet";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/dto_common.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Common Tauri DTOs shared by Wallet Desk shell commands.
|
||||
|
||||
@@ -53,7 +53,7 @@ pub(crate) struct RuntimeStatusDto {
|
||||
pub(crate) fallback_logging_active: bool,
|
||||
/// Whether bootstrap created the configured global Wallet root.
|
||||
pub(crate) root_wallets_directory_created_on_startup: bool,
|
||||
/// Current implementation phase exposed for the Config composition tranche.
|
||||
/// Current implementation phase exposed by the Wallet Desk shell.
|
||||
pub(crate) shell_phase: String,
|
||||
/// Safe startup diagnostic that caused fallback Logging, when applicable.
|
||||
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/errors.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Application-local error codes for Wallet Desk composition and desktop runtime surfaces.
|
||||
|
||||
@@ -31,3 +31,7 @@ pub(crate) const ERROR_CODE_WALLET_DIRECTORY_INVALID: ksp_core_lib::ErrorCode =
|
||||
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||
pub(crate) const ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED: ksp_core_lib::ErrorCode =
|
||||
ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_directory_prepare_failed");
|
||||
/// Wallet Desk cannot enumerate or inspect its effective Wallet inventory directory.
|
||||
pub(crate) const ERROR_CODE_WALLET_INVENTORY_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_inventory_failed");
|
||||
/// A requested Wallet inventory identifier is unsafe or does not resolve to an eligible regular `.kspwallet` file.
|
||||
pub(crate) const ERROR_CODE_WALLET_SELECTION_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("wallet_desk", "wallet_selection_invalid");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/lib.rs
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
//! Tauri desktop application shell for KSP Wallet management and inspection.
|
||||
|
||||
@@ -19,6 +19,7 @@ mod tauri;
|
||||
mod tw_main;
|
||||
mod tw_splash;
|
||||
mod wallet_config;
|
||||
mod wallet_inventory;
|
||||
|
||||
/// Runs the KSP wallet desktop application.
|
||||
pub use self::tauri::run;
|
||||
@@ -49,6 +50,8 @@ pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||
/// Structured domain used while preparing Wallet filesystem roots.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_CONFIG;
|
||||
/// Structured domain used while enumerating and inspecting locked Wallet files.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WALLET_INVENTORY;
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_WINDOWS;
|
||||
/// Owning target for backend events emitted by Wallet Desk.
|
||||
@@ -59,6 +62,8 @@ pub(crate) use self::constants::TRACING_TARGET_FRONTEND;
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_MAIN;
|
||||
/// Owning target for splash-window frontend events.
|
||||
pub(crate) use self::constants::TRACING_TARGET_FRONTEND_SPLASH;
|
||||
/// Native Wallet filename suffix accepted by inventory operations.
|
||||
pub(crate) use self::constants::WALLET_FILE_SUFFIX;
|
||||
/// Safe command error projection exposed to Tauri commands.
|
||||
pub(crate) use self::dto_common::CommandErrorDto;
|
||||
/// Initial application/runtime snapshot exposed to the Wallet Desk shell.
|
||||
@@ -89,6 +94,10 @@ pub(crate) use self::errors::ERROR_CODE_TAURI_WINDOW_OPERATION_FAILED;
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_INVALID;
|
||||
/// Wallet Desk could not inspect or create the configured Wallet directory tree.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_DIRECTORY_PREPARE_FAILED;
|
||||
/// Wallet inventory filesystem operation failed.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_INVENTORY_FAILED;
|
||||
/// Wallet selection identifier is invalid or no longer eligible.
|
||||
pub(crate) use self::errors::ERROR_CODE_WALLET_SELECTION_INVALID;
|
||||
/// Log payload sent by Wallet Desk frontend scripts.
|
||||
pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||
/// Emits one validated frontend event through the KSP Logging facade.
|
||||
@@ -111,3 +120,17 @@ pub(crate) use self::tw_splash::splash_frontend_ready_service;
|
||||
pub(crate) use self::wallet_config::WalletConfigStartup;
|
||||
/// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree.
|
||||
pub(crate) use self::wallet_config::initialize_wallet_config;
|
||||
/// Safe locked Wallet projection returned after selection.
|
||||
pub(crate) use self::wallet_inventory::LockedWalletDto;
|
||||
/// Locked inspection outcome exposed by Wallet inventory rows.
|
||||
pub(crate) use self::wallet_inventory::WalletInspectionStatusDto;
|
||||
/// Safe Wallet inventory row projection.
|
||||
pub(crate) use self::wallet_inventory::WalletInventoryEntryDto;
|
||||
/// Visual lock/error state exposed by Wallet inventory rows.
|
||||
pub(crate) use self::wallet_inventory::WalletInventoryStateDto;
|
||||
/// Request DTO used for one root-scoped Wallet selection.
|
||||
pub(crate) use self::wallet_inventory::WalletSelectionRequestDto;
|
||||
/// Enumerates native Wallet files under the effective Config-managed root.
|
||||
pub(crate) use self::wallet_inventory::list_wallet_inventory;
|
||||
/// Re-inspects one root-scoped Wallet selection.
|
||||
pub(crate) use self::wallet_inventory::select_locked_wallet;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/tauri.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Tauri runtime assembly for the KSP wallet desktop application.
|
||||
|
||||
@@ -37,7 +37,14 @@ fn configure_plugins(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<taur
|
||||
|
||||
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
|
||||
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
return builder.invoke_handler(tauri::generate_handler![emit_frontend_log, get_runtime_status, splash_frontend_ready]);
|
||||
return builder.invoke_handler(tauri::generate_handler![
|
||||
emit_frontend_log,
|
||||
get_runtime_status,
|
||||
list_wallets,
|
||||
refresh_wallets,
|
||||
select_wallet,
|
||||
splash_frontend_ready
|
||||
]);
|
||||
}
|
||||
|
||||
fn configure_setup(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
||||
@@ -73,6 +80,41 @@ fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_wallets(state: tauri::State<'_, crate::AppState>) -> std::result::Result<std::vec::Vec<crate::WalletInventoryEntryDto>, crate::CommandErrorDto> {
|
||||
let root = state.wallet_inventory_root().to_path_buf();
|
||||
let result = crate::list_wallet_inventory(root.as_path()).await;
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn refresh_wallets(
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<std::vec::Vec<crate::WalletInventoryEntryDto>, crate::CommandErrorDto> {
|
||||
let root = state.wallet_inventory_root().to_path_buf();
|
||||
let result = crate::list_wallet_inventory(root.as_path()).await;
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn select_wallet(
|
||||
request: crate::WalletSelectionRequestDto,
|
||||
state: tauri::State<'_, crate::AppState>,
|
||||
) -> std::result::Result<crate::LockedWalletDto, crate::CommandErrorDto> {
|
||||
let root = state.wallet_inventory_root().to_path_buf();
|
||||
let result = crate::select_locked_wallet(root.as_path(), request).await;
|
||||
return match result {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(error) => std::result::Result::Err(crate::CommandErrorDto::from_error(&error)),
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn splash_frontend_ready(
|
||||
app: tauri::AppHandle,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/wallet_config.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Wallet Desk composition adapter for the standard Wallet Config and its application-owned directory preparation.
|
||||
|
||||
@@ -105,8 +105,9 @@ fn prepare_wallet_directories(resolved: &ksp_config_lib::ResolvedWalletConfig) -
|
||||
}
|
||||
|
||||
fn ensure_global_wallet_root(path: &std::path::Path) -> ksp_core_lib::Result<bool> {
|
||||
let metadata = std::fs::metadata(path);
|
||||
let metadata = std::fs::symlink_metadata(path);
|
||||
return match metadata {
|
||||
std::result::Result::Ok(metadata) if metadata.file_type().is_symlink() => directory_invalid(path, "configured Wallet root cannot be a symbolic link"),
|
||||
std::result::Result::Ok(metadata) if metadata.is_dir() => std::result::Result::Ok(false),
|
||||
std::result::Result::Ok(_) => directory_invalid(path, "configured Wallet root exists but is not a directory"),
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
|
||||
233
crates/ksp-app-wallet-desk/src/wallet_inventory.rs
Normal file
233
crates/ksp-app-wallet-desk/src/wallet_inventory.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
// file: crates/ksp-app-wallet-desk/src/wallet_inventory.rs
|
||||
// version: 1
|
||||
|
||||
//! Root-scoped Wallet inventory and locked-file selection for Wallet Desk.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
/// Visual lock state projected for one Wallet inventory row.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_inventory/WalletInventoryStateDto.ts")]
|
||||
pub(crate) enum WalletInventoryStateDto {
|
||||
/// Eligible Wallet remains locked.
|
||||
Locked,
|
||||
/// Candidate could not be inspected as a valid locked Wallet.
|
||||
Error,
|
||||
}
|
||||
|
||||
/// Locked inspection outcome projected for one Wallet inventory row.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_inventory/WalletInspectionStatusDto.ts")]
|
||||
pub(crate) enum WalletInspectionStatusDto {
|
||||
/// Native Wallet inspection succeeded.
|
||||
Valid,
|
||||
/// Candidate inspection failed with a safe diagnostic.
|
||||
Invalid,
|
||||
}
|
||||
|
||||
/// Safe inventory row exposed while Wallet contents remain locked.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_inventory/WalletInventoryEntryDto.ts")]
|
||||
pub(crate) struct WalletInventoryEntryDto {
|
||||
/// Safe inspection diagnostic when the candidate is invalid.
|
||||
pub(crate) diagnostic: std::option::Option<crate::CommandErrorDto>,
|
||||
/// Native Wallet filename without its parent path.
|
||||
pub(crate) filename: String,
|
||||
/// Native Wallet format version when locked inspection succeeds.
|
||||
pub(crate) format_version: std::option::Option<u32>,
|
||||
/// Locked inspection outcome.
|
||||
pub(crate) inspection_status: WalletInspectionStatusDto,
|
||||
/// Current inventory lock/error state.
|
||||
pub(crate) state: WalletInventoryStateDto,
|
||||
/// Root-scoped identifier accepted by selection commands.
|
||||
pub(crate) wallet_id: String,
|
||||
/// Whether the locked Wallet advertises an enabled VIEW slot.
|
||||
pub(crate) view_enabled: std::option::Option<bool>,
|
||||
}
|
||||
|
||||
/// Safe locked Wallet projection returned after an explicit row selection.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_inventory/LockedWalletDto.ts")]
|
||||
pub(crate) struct LockedWalletDto {
|
||||
/// Native Wallet filename without its parent path.
|
||||
pub(crate) filename: String,
|
||||
/// Native Wallet format version.
|
||||
pub(crate) format_version: u32,
|
||||
/// Root-scoped identifier accepted by later Wallet Desk operations.
|
||||
pub(crate) wallet_id: String,
|
||||
/// Whether the locked Wallet advertises an enabled VIEW slot.
|
||||
pub(crate) view_enabled: bool,
|
||||
}
|
||||
|
||||
/// Request DTO for selecting one root-scoped Wallet inventory entry.
|
||||
#[derive(serde::Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_wallet_desk/wallet_inventory/WalletSelectionRequestDto.ts")]
|
||||
pub(crate) struct WalletSelectionRequestDto {
|
||||
/// Root-scoped Wallet identifier returned by the inventory.
|
||||
pub(crate) wallet_id: String,
|
||||
}
|
||||
|
||||
/// Enumerates eligible native Wallet files and returns deterministic locked projections.
|
||||
pub(crate) async fn list_wallet_inventory(root: &std::path::Path) -> ksp_core_lib::Result<std::vec::Vec<WalletInventoryEntryDto>> {
|
||||
let root_text = root.to_string_lossy().into_owned();
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, root_path = root_text.as_str(), "Wallet inventory refresh started");
|
||||
let reader = tokio::fs::read_dir(root).await;
|
||||
let mut reader = match reader {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(inventory_io_error(root, "effective Wallet directory cannot be enumerated", error)),
|
||||
};
|
||||
let mut entries = std::vec::Vec::new();
|
||||
loop {
|
||||
let next = reader.next_entry().await;
|
||||
let entry = match next {
|
||||
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||
std::result::Result::Ok(std::option::Option::None) => break,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(inventory_io_error(root, "Wallet directory entry cannot be read", error)),
|
||||
};
|
||||
let filename = entry.file_name();
|
||||
let filename = match filename.to_str() {
|
||||
std::option::Option::Some(value) if is_wallet_filename(value) => value.to_owned(),
|
||||
std::option::Option::Some(_) => continue,
|
||||
std::option::Option::None => {
|
||||
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, "Wallet inventory skipped a non-UTF-8 filename");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let path = entry.path();
|
||||
let metadata = tokio::fs::symlink_metadata(path.as_path()).await;
|
||||
let metadata = match metadata {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
entries.push(invalid_inventory_entry(filename, inventory_io_error(path.as_path(), "Wallet candidate metadata cannot be inspected", error)));
|
||||
continue;
|
||||
},
|
||||
};
|
||||
if metadata.file_type().is_symlink() || !metadata.is_file() {
|
||||
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, filename = filename.as_str(), is_symlink = metadata.file_type().is_symlink(), "Wallet inventory skipped a non-regular candidate");
|
||||
continue;
|
||||
}
|
||||
let inspected = ksp_wallet_lib::inspect_locked_wallet_file_v1(path.as_path()).await;
|
||||
match inspected {
|
||||
std::result::Result::Ok(locked) => entries.push(valid_inventory_entry(filename, locked)),
|
||||
std::result::Result::Err(error) => {
|
||||
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, filename = filename.as_str(), error_domain = error.code().domain(), error_code = error.code().code(), "Wallet inventory candidate failed locked inspection");
|
||||
entries.push(invalid_inventory_entry(filename, error));
|
||||
},
|
||||
}
|
||||
}
|
||||
entries.sort_by(|left, right| {
|
||||
return left.filename.cmp(&right.filename);
|
||||
});
|
||||
let invalid_count = entries
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
return entry.inspection_status == WalletInspectionStatusDto::Invalid;
|
||||
})
|
||||
.count();
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, root_path = root_text.as_str(), entry_count = entries.len(), invalid_count, "Wallet inventory refresh completed");
|
||||
return std::result::Result::Ok(entries);
|
||||
}
|
||||
|
||||
/// Re-resolves and re-inspects one selected inventory identifier without exposing its full path.
|
||||
pub(crate) async fn select_locked_wallet(root: &std::path::Path, request: WalletSelectionRequestDto) -> ksp_core_lib::Result<LockedWalletDto> {
|
||||
let path = resolve_wallet_path(root, request.wallet_id.as_str()).await;
|
||||
let path = match path {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let locked = ksp_wallet_lib::inspect_locked_wallet_file_v1(path.as_path()).await;
|
||||
let locked = match locked {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, wallet_id = request.wallet_id.as_str(), format_version = locked.format_version(), view_enabled = locked.view_enabled(), "Locked Wallet selected from inventory");
|
||||
return std::result::Result::Ok(LockedWalletDto {
|
||||
filename: request.wallet_id.clone(),
|
||||
format_version: locked.format_version(),
|
||||
wallet_id: request.wallet_id,
|
||||
view_enabled: locked.view_enabled(),
|
||||
});
|
||||
}
|
||||
|
||||
fn valid_inventory_entry(filename: String, locked: ksp_wallet_lib::LockedWalletInfo) -> WalletInventoryEntryDto {
|
||||
return WalletInventoryEntryDto {
|
||||
diagnostic: std::option::Option::None,
|
||||
filename: filename.clone(),
|
||||
format_version: std::option::Option::Some(locked.format_version()),
|
||||
inspection_status: WalletInspectionStatusDto::Valid,
|
||||
state: WalletInventoryStateDto::Locked,
|
||||
wallet_id: filename,
|
||||
view_enabled: std::option::Option::Some(locked.view_enabled()),
|
||||
};
|
||||
}
|
||||
|
||||
fn invalid_inventory_entry(filename: String, error: ksp_core_lib::Error) -> WalletInventoryEntryDto {
|
||||
return WalletInventoryEntryDto {
|
||||
diagnostic: std::option::Option::Some(crate::CommandErrorDto::from_error(&error)),
|
||||
filename: filename.clone(),
|
||||
format_version: std::option::Option::None,
|
||||
inspection_status: WalletInspectionStatusDto::Invalid,
|
||||
state: WalletInventoryStateDto::Error,
|
||||
wallet_id: filename,
|
||||
view_enabled: std::option::Option::None,
|
||||
};
|
||||
}
|
||||
|
||||
fn is_wallet_filename(filename: &str) -> bool {
|
||||
let path = std::path::Path::new(filename);
|
||||
let components = path.components().count();
|
||||
return components == 1
|
||||
&& !filename.contains('/')
|
||||
&& !filename.contains('\\')
|
||||
&& filename.ends_with(crate::WALLET_FILE_SUFFIX)
|
||||
&& filename.len() > crate::WALLET_FILE_SUFFIX.len();
|
||||
}
|
||||
|
||||
async fn resolve_wallet_path(root: &std::path::Path, wallet_id: &str) -> ksp_core_lib::Result<std::path::PathBuf> {
|
||||
if !is_wallet_filename(wallet_id) {
|
||||
return selection_invalid(wallet_id, "Wallet identifier must be one UTF-8 filename ending in .kspwallet");
|
||||
}
|
||||
let path = root.join(wallet_id);
|
||||
let metadata = tokio::fs::symlink_metadata(path.as_path()).await;
|
||||
let metadata = match metadata {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
return selection_invalid(wallet_id, "Wallet selection no longer exists in the effective inventory directory");
|
||||
},
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(inventory_io_error(path.as_path(), "Wallet selection metadata cannot be inspected", error));
|
||||
},
|
||||
};
|
||||
if metadata.file_type().is_symlink() || !metadata.is_file() {
|
||||
return selection_invalid(wallet_id, "Wallet selection must resolve to a regular non-symlink file");
|
||||
}
|
||||
return std::result::Result::Ok(path);
|
||||
}
|
||||
|
||||
fn selection_invalid<T>(wallet_id: &str, reason: &'static str) -> ksp_core_lib::Result<T> {
|
||||
ksp_logging_lib::warn!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, wallet_id, reason, "Wallet inventory selection rejected");
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_SELECTION_INVALID, "Wallet inventory selection is invalid")
|
||||
.with_context("wallet_id", wallet_id)
|
||||
.with_context("reason", reason),
|
||||
);
|
||||
}
|
||||
|
||||
fn inventory_io_error(path: &std::path::Path, reason: &'static str, source: std::io::Error) -> ksp_core_lib::Error {
|
||||
let path_text = path.to_string_lossy().into_owned();
|
||||
let source_kind = std::format!("{:?}", source.kind());
|
||||
ksp_logging_lib::error!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_WALLET_INVENTORY, path = path_text.as_str(), reason, source_kind = source_kind.as_str(), "Wallet inventory filesystem operation failed");
|
||||
return ksp_core_lib::Error::new(crate::ERROR_CODE_WALLET_INVENTORY_FAILED, "Wallet inventory filesystem operation failed")
|
||||
.with_context("path", path_text)
|
||||
.with_context("reason", reason)
|
||||
.with_source(source);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/wallet_inventory.rs"]
|
||||
mod tests;
|
||||
Reference in New Issue
Block a user