Persist last publish report across restarts
This commit is contained in:
parent
e936524f72
commit
bab82f5148
7 changed files with 63 additions and 3 deletions
|
|
@ -140,6 +140,12 @@ export interface AppState {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
/** Recently deleted profiles, newest last, for undo. */
|
/** Recently deleted profiles, newest last, for undo. */
|
||||||
undo_history?: ProfileSummary[];
|
undo_history?: ProfileSummary[];
|
||||||
|
/** The most recent publish report, persisted across restarts. */
|
||||||
|
last_publish?: {
|
||||||
|
event_id: string;
|
||||||
|
succeeded: string[];
|
||||||
|
failed: RelayFailure[];
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A secret key revealed after the vault is unlocked. */
|
/** A secret key revealed after the vault is unlocked. */
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,9 @@ export function AppProvider({ children }: { children: ReactNode }) {
|
||||||
const initial = await api.init();
|
const initial = await api.init();
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setState(initial);
|
setState(initial);
|
||||||
|
if (initial.last_publish) {
|
||||||
|
setLastPublish({ report: initial.last_publish, error: null, details: null, at: Date.now() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ export function makeState(overrides?: Partial<AppState>): AppState {
|
||||||
active_profile: alice,
|
active_profile: alice,
|
||||||
profiles: [alice, bob],
|
profiles: [alice, bob],
|
||||||
settings,
|
settings,
|
||||||
|
last_publish: null,
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/app.rs
10
src/app.rs
|
|
@ -7,7 +7,7 @@ use crate::crypto::{self, VaultKey};
|
||||||
use crate::errors::AppError;
|
use crate::errors::AppError;
|
||||||
use crate::profiles::{self, ProfileSummary};
|
use crate::profiles::{self, ProfileSummary};
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::vault::{self, KdfParams, StoredProfile, Vault, VaultCrypto};
|
use crate::vault::{self, KdfParams, StoredProfile, StoredPublishReport, Vault, VaultCrypto};
|
||||||
|
|
||||||
/// Minimum password length accepted when encrypting the vault.
|
/// Minimum password length accepted when encrypting the vault.
|
||||||
pub const MIN_PASSWORD_LEN: usize = 8;
|
pub const MIN_PASSWORD_LEN: usize = 8;
|
||||||
|
|
@ -20,6 +20,8 @@ pub struct App {
|
||||||
unlock_key: Option<VaultKey>,
|
unlock_key: Option<VaultKey>,
|
||||||
/// Stack of deleted profiles for undo functionality.
|
/// Stack of deleted profiles for undo functionality.
|
||||||
pub undo_history: Vec<ProfileSummary>,
|
pub undo_history: Vec<ProfileSummary>,
|
||||||
|
/// The most recent publish report, persisted across restarts.
|
||||||
|
pub last_publish: Option<StoredPublishReport>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot of everything the UI needs, containing no secret keys.
|
/// Snapshot of everything the UI needs, containing no secret keys.
|
||||||
|
|
@ -38,6 +40,9 @@ pub struct AppStateView {
|
||||||
/// Recently deleted profiles, newest last, for undo.
|
/// Recently deleted profiles, newest last, for undo.
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
pub undo_history: Vec<ProfileSummary>,
|
pub undo_history: Vec<ProfileSummary>,
|
||||||
|
/// The most recent publish report, persisted across restarts.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub last_publish: Option<StoredPublishReport>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -48,6 +53,7 @@ impl App {
|
||||||
settings: vault::load_settings()?,
|
settings: vault::load_settings()?,
|
||||||
unlock_key: None,
|
unlock_key: None,
|
||||||
undo_history: Vec::new(),
|
undo_history: Vec::new(),
|
||||||
|
last_publish: vault::load_last_publish(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,6 +246,7 @@ impl App {
|
||||||
profiles: profiles::summaries(&self.vault),
|
profiles: profiles::summaries(&self.vault),
|
||||||
settings: self.settings.clone(),
|
settings: self.settings.clone(),
|
||||||
undo_history: self.undo_history.clone(),
|
undo_history: self.undo_history.clone(),
|
||||||
|
last_publish: self.last_publish.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -302,6 +309,7 @@ mod tests {
|
||||||
settings: offline_settings(),
|
settings: offline_settings(),
|
||||||
unlock_key: None,
|
unlock_key: None,
|
||||||
undo_history: Vec::new(),
|
undo_history: Vec::new(),
|
||||||
|
last_publish: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -464,6 +464,13 @@ async fn run_with_app(app: &mut App, request: Request) -> Result<serde_json::Val
|
||||||
let report =
|
let report =
|
||||||
publish::publish_active(&app.vault, &app.settings, &content, app.vault_key())
|
publish::publish_active(&app.vault, &app.settings, &content, app.vault_key())
|
||||||
.await?;
|
.await?;
|
||||||
|
let stored = crate::vault::StoredPublishReport {
|
||||||
|
event_id: report.event_id.clone(),
|
||||||
|
succeeded: report.succeeded.clone(),
|
||||||
|
failed: report.failed.clone(),
|
||||||
|
};
|
||||||
|
let _ = crate::vault::save_last_publish(&stored);
|
||||||
|
app.last_publish = Some(stored);
|
||||||
Ok(json!(report))
|
Ok(json!(report))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::collections::HashSet;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::crypto::VaultKey;
|
use crate::crypto::VaultKey;
|
||||||
use crate::errors::{AppError, ErrorKind};
|
use crate::errors::{AppError, ErrorKind};
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::vault::Vault;
|
||||||
const RELAY_SEND_TIMEOUT: Duration = Duration::from_secs(6);
|
const RELAY_SEND_TIMEOUT: Duration = Duration::from_secs(6);
|
||||||
|
|
||||||
/// A relay that rejected a published note.
|
/// A relay that rejected a published note.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct RelayFailure {
|
pub struct RelayFailure {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
/// Concise, user-facing reason.
|
/// Concise, user-facing reason.
|
||||||
|
|
|
||||||
35
src/vault.rs
35
src/vault.rs
|
|
@ -18,6 +18,8 @@ pub const VAULT_VERSION: u32 = 2;
|
||||||
pub const VAULT_FILE_NAME: &str = "profiles_vault.json";
|
pub const VAULT_FILE_NAME: &str = "profiles_vault.json";
|
||||||
/// Filename of the settings file.
|
/// Filename of the settings file.
|
||||||
pub const SETTINGS_FILE_NAME: &str = "settings.json";
|
pub const SETTINGS_FILE_NAME: &str = "settings.json";
|
||||||
|
/// Filename of the last publish report.
|
||||||
|
pub const LAST_PUBLISH_FILE_NAME: &str = "last_publish.json";
|
||||||
|
|
||||||
/// A profile stored on disk.
|
/// A profile stored on disk.
|
||||||
///
|
///
|
||||||
|
|
@ -180,6 +182,39 @@ pub fn settings_path() -> PathBuf {
|
||||||
data_dir().join(SETTINGS_FILE_NAME)
|
data_dir().join(SETTINGS_FILE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn last_publish_path() -> PathBuf {
|
||||||
|
data_dir().join(LAST_PUBLISH_FILE_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A minimal publish report persisted across restarts so the Home screen can
|
||||||
|
/// show the most recent publication result.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StoredPublishReport {
|
||||||
|
pub event_id: String,
|
||||||
|
pub succeeded: Vec<String>,
|
||||||
|
pub failed: Vec<crate::publish::RelayFailure>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the last publish report, returning `None` when absent or unreadable.
|
||||||
|
pub fn load_last_publish() -> Option<StoredPublishReport> {
|
||||||
|
let path = last_publish_path();
|
||||||
|
if !path.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let content = fs::read_to_string(&path).ok()?;
|
||||||
|
if content.trim().is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
serde_json::from_str(&content).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist the last publish report with restrictive permissions.
|
||||||
|
pub fn save_last_publish(report: &StoredPublishReport) -> Result<(), AppError> {
|
||||||
|
let content = serde_json::to_string_pretty(report)
|
||||||
|
.map_err(|e| AppError::json("Could not prepare the last publish report for saving", e))?;
|
||||||
|
write_restricted(&last_publish_path(), &content)
|
||||||
|
}
|
||||||
|
|
||||||
/// Candidate locations for a legacy vault created by the old CLI version.
|
/// Candidate locations for a legacy vault created by the old CLI version.
|
||||||
///
|
///
|
||||||
/// The old application wrote `profiles_vault.json` in its working directory.
|
/// The old application wrote `profiles_vault.json` in its working directory.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue