Update book handling and improve offline/online sync logic
- Enhanced the `BookProps` struct with updated field mappings for better API compatibility. - Improved offline/online sync workflows in `BookList` by adding `localBook` property handling and new item count methods for segmented tracking of local/online items. - Updated click handlers in `BookList` to fetch data based on connectivity state and prioritize local data when offline. - Refactored the decryption and vault handling logic in Rust to remove obsolete legacy methods and standardize debug behavior. - Introduced `ScribeShell` layout component with foundational logic for book/chapter syncing and offline queue handling. - Added `init_panic_hook` to improve crash reporting during Rust app initialization.
This commit is contained in:
@@ -6,7 +6,6 @@ use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::error::{AppError, AppResult};
|
||||
|
||||
@@ -44,9 +43,6 @@ const KEYRING_USER: &str = "vault-key";
|
||||
/// Falls back to the old derivation method if the keyring is unavailable,
|
||||
/// and attempts to migrate the key into the keyring for next time.
|
||||
fn get_vault_key() -> [u8; 32] {
|
||||
if cfg!(debug_assertions) {
|
||||
return derive_machine_key_legacy();
|
||||
}
|
||||
let entry = keyring::Entry::new(SERVICE_NAME, KEYRING_USER);
|
||||
if let Ok(entry) = &entry {
|
||||
if let Ok(stored) = entry.get_password() {
|
||||
@@ -68,19 +64,6 @@ fn get_vault_key() -> [u8; 32] {
|
||||
key
|
||||
}
|
||||
|
||||
/// Legacy derivation for migrating vaults created before keyring support.
|
||||
fn derive_machine_key_legacy() -> [u8; 32] {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(SERVICE_NAME.as_bytes());
|
||||
if let Ok(name) = hostname::get() {
|
||||
hasher.update(name.as_encoded_bytes());
|
||||
}
|
||||
if let Some(dir) = dirs_next::home_dir() {
|
||||
hasher.update(dir.to_string_lossy().as_bytes());
|
||||
}
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
fn encrypt_vault(data: &[u8], key: &[u8; 32]) -> AppResult<Vec<u8>> {
|
||||
let mut iv = [0u8; IV_LENGTH];
|
||||
rand::rng().fill_bytes(&mut iv);
|
||||
@@ -119,6 +102,12 @@ fn read_vault() -> AppResult<SecureVault> {
|
||||
}
|
||||
let content = fs::read_to_string(&path)
|
||||
.map_err(|e| AppError::Keyring(format!("Failed to read vault: {}", e)))?;
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
return serde_json::from_str::<SecureVault>(&content)
|
||||
.map_err(|e| AppError::Keyring(format!("Vault JSON parse error: {}", e)));
|
||||
}
|
||||
|
||||
let raw = BASE64.decode(content.trim())
|
||||
.map_err(|e| AppError::Keyring(format!("Vault corrupted (base64): {}", e)))?;
|
||||
|
||||
@@ -129,34 +118,29 @@ fn read_vault() -> AppResult<SecureVault> {
|
||||
}
|
||||
}
|
||||
|
||||
let legacy_key = derive_machine_key_legacy();
|
||||
if let Ok(decrypted) = decrypt_vault(&raw, &legacy_key) {
|
||||
if let Ok(vault) = serde_json::from_slice::<SecureVault>(&decrypted) {
|
||||
let _ = write_vault_with_key(&vault, &key);
|
||||
return Ok(vault);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SecureVault::default())
|
||||
Err(AppError::Keyring("Vault decryption failed — cannot read existing vault".into()))
|
||||
}
|
||||
|
||||
fn write_vault_with_key(vault: &SecureVault, key: &[u8; 32]) -> AppResult<()> {
|
||||
fn write_vault(vault: &SecureVault) -> AppResult<()> {
|
||||
let path = vault_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| AppError::Internal(format!("Failed to create vault dir: {}", e)))?;
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
let json = serde_json::to_string_pretty(vault)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to serialize vault: {}", e)))?;
|
||||
return fs::write(&path, json).map_err(|e| AppError::Internal(format!("Failed to write vault: {}", e)));
|
||||
}
|
||||
|
||||
let key = get_vault_key();
|
||||
let json = serde_json::to_string(vault)
|
||||
.map_err(|e| AppError::Internal(format!("Failed to serialize vault: {}", e)))?;
|
||||
let encrypted = encrypt_vault(json.as_bytes(), key)?;
|
||||
let encrypted = encrypt_vault(json.as_bytes(), &key)?;
|
||||
let encoded = BASE64.encode(&encrypted);
|
||||
fs::write(&path, encoded).map_err(|e| AppError::Internal(format!("Failed to write vault: {}", e)))
|
||||
}
|
||||
|
||||
fn write_vault(vault: &SecureVault) -> AppResult<()> {
|
||||
let key = get_vault_key();
|
||||
write_vault_with_key(vault, &key)
|
||||
}
|
||||
|
||||
// ===== Public API (same signatures as before) =====
|
||||
|
||||
pub fn get_user_encryption_key(user_id: &str) -> AppResult<String> {
|
||||
|
||||
@@ -35,15 +35,19 @@ pub struct BookToolsSettings {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BookProps {
|
||||
pub book_id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub book_type: String,
|
||||
pub author_id: String,
|
||||
pub title: String,
|
||||
pub sub_title: String,
|
||||
pub summary: String,
|
||||
#[serde(rename = "serie")]
|
||||
pub serie_id: i64,
|
||||
pub series_id: Option<String>,
|
||||
#[serde(rename = "publicationDate")]
|
||||
pub desired_release_date: String,
|
||||
pub desired_word_count: i64,
|
||||
#[serde(rename = "totalWordCount")]
|
||||
pub word_count: i64,
|
||||
pub cover_image: String,
|
||||
pub book_meta: Option<String>,
|
||||
|
||||
@@ -6,10 +6,13 @@ mod helpers;
|
||||
mod shared;
|
||||
|
||||
use db::connection::create_db_manager;
|
||||
use shared::crash_reporter::init_panic_hook;
|
||||
use shared::session::create_session;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn run() {
|
||||
init_panic_hook();
|
||||
|
||||
let app_data_dir = dirs_next::data_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join("com.eritors.scribe.desktop");
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod crash_reporter;
|
||||
pub mod session;
|
||||
pub mod types;
|
||||
|
||||
Reference in New Issue
Block a user