Build Iced desktop application shell
This commit is contained in:
178
crates/storage/src/desktop.rs
Normal file
178
crates/storage/src/desktop.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
//! Storage-owned service boundary for the Iced desktop presentation adapter.
|
||||
|
||||
use std::{error::Error, fmt, path::Path};
|
||||
|
||||
use crate::{
|
||||
authentication::{NativeAuthenticationHandle, NativeAuthenticationSession},
|
||||
config::Config,
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider},
|
||||
document::{DocumentError, EntryDocument, EntryDocumentService},
|
||||
git::{AutomaticEntryCommitter, GitIdentity},
|
||||
presentation::ClipboardTimeout,
|
||||
read::{TreeModel, VaultReader},
|
||||
repository::{DirectoryPath, Repository},
|
||||
secret_store::SecretProtectionPolicy,
|
||||
write::{WriteError, WriteOutcome},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DesktopErrorKind {
|
||||
Configuration,
|
||||
Authentication,
|
||||
KeyMaterial,
|
||||
Repository,
|
||||
Read,
|
||||
Document,
|
||||
Git,
|
||||
Conflict,
|
||||
Unchanged,
|
||||
MissingDefaultKey,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DesktopError {
|
||||
kind: DesktopErrorKind,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl DesktopError {
|
||||
pub fn kind(&self) -> DesktopErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
fn new(kind: DesktopErrorKind, error: impl fmt::Display) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
message: error.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn document(error: DocumentError) -> Self {
|
||||
let kind = match &error {
|
||||
DocumentError::Write(WriteError::ConcurrentModification { .. }) => {
|
||||
DesktopErrorKind::Conflict
|
||||
}
|
||||
DocumentError::Write(WriteError::Unchanged) => DesktopErrorKind::Unchanged,
|
||||
_ => DesktopErrorKind::Document,
|
||||
};
|
||||
Self::new(kind, error)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DesktopError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for DesktopError {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DesktopStorage {
|
||||
config: Config,
|
||||
}
|
||||
|
||||
impl DesktopStorage {
|
||||
pub fn load(explicit: Option<&Path>) -> Result<Self, DesktopError> {
|
||||
Config::load(explicit)
|
||||
.map(|config| Self { config })
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Configuration, error))
|
||||
}
|
||||
|
||||
pub fn system() -> Result<DesktopBootstrap, DesktopError> {
|
||||
let storage = Self::load(None)?;
|
||||
let keys = KeyStore::load(storage.config.key_material())
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::KeyMaterial, error))?;
|
||||
let handle = keys
|
||||
.resolve(storage.config.default_key().as_str())
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::KeyMaterial, error))?;
|
||||
let key = keys
|
||||
.infos()
|
||||
.find(|key| key.fingerprint() == handle.fingerprint())
|
||||
.ok_or_else(|| {
|
||||
DesktopError::new(
|
||||
DesktopErrorKind::MissingDefaultKey,
|
||||
"the configured GPG key is unavailable",
|
||||
)
|
||||
})?;
|
||||
let authentication = NativeAuthenticationSession::system(
|
||||
SecretProtectionPolicy::default(),
|
||||
storage.config.authentication_timeout(),
|
||||
)
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Authentication, error))?;
|
||||
Ok(DesktopBootstrap {
|
||||
storage,
|
||||
authentication,
|
||||
key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clipboard_timeout(&self) -> ClipboardTimeout {
|
||||
self.config.clipboard_timeout()
|
||||
}
|
||||
|
||||
pub fn tree(&self) -> Result<TreeModel, DesktopError> {
|
||||
let repository = self.repository()?;
|
||||
let keys = self.keys()?;
|
||||
VaultReader::new(&repository, &keys)
|
||||
.list(&DirectoryPath::root())
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Read, error))
|
||||
}
|
||||
|
||||
pub fn open_document(
|
||||
&self,
|
||||
entry: &str,
|
||||
secrets: &mut impl SecretProvider,
|
||||
) -> Result<EntryDocument, DesktopError> {
|
||||
let repository = self.repository()?;
|
||||
let keys = self.keys()?;
|
||||
EntryDocumentService::new(&repository, &keys)
|
||||
.open(entry, secrets)
|
||||
.map_err(DesktopError::document)
|
||||
}
|
||||
|
||||
pub fn save_document(&self, document: &EntryDocument) -> Result<WriteOutcome, DesktopError> {
|
||||
let repository = self.repository()?;
|
||||
let keys = self.keys()?;
|
||||
let entry = document.path().to_string();
|
||||
let mut committer =
|
||||
AutomaticEntryCommitter::for_entry(&repository, &entry, GitIdentity::ironstorage())
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Git, error))?;
|
||||
EntryDocumentService::new(&repository, &keys)
|
||||
.save_recoverable(document, None, &mut committer)
|
||||
.map_err(DesktopError::document)
|
||||
}
|
||||
|
||||
pub fn save_active_document(
|
||||
&self,
|
||||
handle: &NativeAuthenticationHandle,
|
||||
document: &EntryDocument,
|
||||
) -> Result<WriteOutcome, DesktopError> {
|
||||
handle
|
||||
.ensure_active()
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Authentication, error))?;
|
||||
self.save_document(document)
|
||||
}
|
||||
|
||||
fn repository(&self) -> Result<Repository, DesktopError> {
|
||||
Repository::open(self.config.vault())
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::Repository, error))
|
||||
}
|
||||
|
||||
fn keys(&self) -> Result<KeyStore, DesktopError> {
|
||||
KeyStore::load(self.config.key_material())
|
||||
.map_err(|error| DesktopError::new(DesktopErrorKind::KeyMaterial, error))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DesktopBootstrap {
|
||||
storage: DesktopStorage,
|
||||
authentication: NativeAuthenticationSession,
|
||||
key: KeyInfo,
|
||||
}
|
||||
|
||||
impl DesktopBootstrap {
|
||||
pub fn into_parts(self) -> (DesktopStorage, NativeAuthenticationSession, KeyInfo) {
|
||||
(self.storage, self.authentication, self.key)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod authentication;
|
||||
pub mod command;
|
||||
pub mod config;
|
||||
pub mod crypto;
|
||||
pub mod desktop;
|
||||
pub mod document;
|
||||
pub mod generate;
|
||||
pub mod git;
|
||||
|
||||
Reference in New Issue
Block a user