paths: remove hardcoded basepath

Before we were hardcoding the basepath with dirs, which isn't that
useful for testing, previews, or for android. Let's fix that.

This also moves the db and cache directories into our root DataPaths.
This is a breaking change, we don't have a migration step. sorry.

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2024-11-12 14:00:57 -08:00
parent 6c9693dbf0
commit fab1257f6e
11 changed files with 167 additions and 144 deletions

View File

@@ -15,7 +15,7 @@ use crate::{
notecache::{CachedNote, NoteCache}, notecache::{CachedNote, NoteCache},
notes_holder::NotesHolderStorage, notes_holder::NotesHolderStorage,
profile::Profile, profile::Profile,
storage::{Directory, FileKeyStorage, KeyStorageType}, storage::{DataPath, DataPathType, Directory, FileKeyStorage, KeyStorageType},
subscriptions::{SubKind, Subscriptions}, subscriptions::{SubKind, Subscriptions},
support::Support, support::Support,
thread::Thread, thread::Thread,
@@ -23,7 +23,7 @@ use crate::{
ui::{self, DesktopSidePanel}, ui::{self, DesktopSidePanel},
unknowns::UnknownIds, unknowns::UnknownIds,
view_state::ViewState, view_state::ViewState,
DataPaths, Result, Result,
}; };
use enostr::{ClientMessage, RelayEvent, RelayMessage, RelayPool}; use enostr::{ClientMessage, RelayEvent, RelayMessage, RelayPool};
@@ -67,6 +67,7 @@ pub struct Damus {
frame_history: crate::frame_history::FrameHistory, frame_history: crate::frame_history::FrameHistory,
pub path: DataPath,
// TODO: make these bitflags // TODO: make these bitflags
pub debug: bool, pub debug: bool,
pub since_optimize: bool, pub since_optimize: bool,
@@ -660,31 +661,25 @@ impl Damus {
let data_path = parsed_args let data_path = parsed_args
.datapath .datapath
.unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string()); .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string());
let dbpath = parsed_args.dbpath.unwrap_or(data_path.clone()); let path = DataPath::new(&data_path);
let dbpath_ = path.path(DataPathType::Db);
let dbpath = dbpath_.to_str().unwrap();
let _ = std::fs::create_dir_all(dbpath.clone()); let _ = std::fs::create_dir_all(dbpath);
let imgcache_dir = format!("{}/{}", data_path, ImageCache::rel_datadir()); let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir());
let _ = std::fs::create_dir_all(imgcache_dir.clone()); let _ = std::fs::create_dir_all(imgcache_dir.clone());
let mut config = Config::new(); let mut config = Config::new();
config.set_ingester_threads(4); config.set_ingester_threads(4);
let keystore = if parsed_args.use_keystore { let keystore = if parsed_args.use_keystore {
if let Ok(keys_path) = DataPaths::Keys.get_path() { let keys_path = path.path(DataPathType::Keys);
if let Ok(selected_key_path) = DataPaths::SelectedKey.get_path() { let selected_key_path = path.path(DataPathType::SelectedKey);
KeyStorageType::FileSystem(FileKeyStorage::new( KeyStorageType::FileSystem(FileKeyStorage::new(
Directory::new(keys_path), Directory::new(keys_path),
Directory::new(selected_key_path), Directory::new(selected_key_path),
)) ))
} else {
error!("Could not find path for selected key");
KeyStorageType::None
}
} else {
error!("Could not find data path for keys");
KeyStorageType::None
}
} else { } else {
KeyStorageType::None KeyStorageType::None
}; };
@@ -725,7 +720,7 @@ impl Damus {
.get_selected_account() .get_selected_account()
.as_ref() .as_ref()
.map(|a| a.pubkey.bytes()); .map(|a| a.pubkey.bytes());
let ndb = Ndb::new(&dbpath, &config).expect("ndb"); let ndb = Ndb::new(dbpath, &config).expect("ndb");
let mut columns: Columns = Columns::new(); let mut columns: Columns = Columns::new();
for col in parsed_args.columns { for col in parsed_args.columns {
@@ -740,6 +735,9 @@ impl Damus {
columns.new_column_picker(); columns.new_column_picker();
} }
let app_rect_handler = AppSizeHandler::new(&path);
let support = Support::new(&path);
Self { Self {
pool, pool,
debug, debug,
@@ -750,7 +748,7 @@ impl Damus {
profiles: NotesHolderStorage::default(), profiles: NotesHolderStorage::default(),
drafts: Drafts::default(), drafts: Drafts::default(),
state: DamusState::Initializing, state: DamusState::Initializing,
img_cache: ImageCache::new(imgcache_dir.into()), img_cache: ImageCache::new(imgcache_dir),
note_cache: NoteCache::default(), note_cache: NoteCache::default(),
columns, columns,
textmode: parsed_args.textmode, textmode: parsed_args.textmode,
@@ -758,8 +756,9 @@ impl Damus {
accounts, accounts,
frame_history: FrameHistory::default(), frame_history: FrameHistory::default(),
view_state: ViewState::default(), view_state: ViewState::default(),
app_rect_handler: AppSizeHandler::default(), path,
support: Support::default(), app_rect_handler,
support,
} }
} }
@@ -819,12 +818,17 @@ impl Damus {
columns.add_new_timeline_column(timeline); columns.add_new_timeline_column(timeline);
let imgcache_dir = data_path.as_ref().join(ImageCache::rel_datadir()); let path = DataPath::new(&data_path);
let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir());
let _ = std::fs::create_dir_all(imgcache_dir.clone()); let _ = std::fs::create_dir_all(imgcache_dir.clone());
let debug = true; let debug = true;
let app_rect_handler = AppSizeHandler::new(&path);
let support = Support::new(&path);
let mut config = Config::new(); let mut config = Config::new();
config.set_ingester_threads(2); config.set_ingester_threads(2);
Self { Self {
debug, debug,
unknown_ids: UnknownIds::default(), unknown_ids: UnknownIds::default(),
@@ -839,12 +843,20 @@ impl Damus {
note_cache: NoteCache::default(), note_cache: NoteCache::default(),
columns, columns,
textmode: false, textmode: false,
ndb: Ndb::new(data_path.as_ref().to_str().expect("db path ok"), &config).expect("ndb"), ndb: Ndb::new(
path.path(DataPathType::Db)
.to_str()
.expect("db path should be ok"),
&config,
)
.expect("ndb"),
accounts: AccountManager::new(KeyStorageType::None), accounts: AccountManager::new(KeyStorageType::None),
frame_history: FrameHistory::default(), frame_history: FrameHistory::default(),
view_state: ViewState::default(), view_state: ViewState::default(),
app_rect_handler: AppSizeHandler::default(),
support: Support::default(), path,
app_rect_handler,
support,
} }
} }

View File

@@ -1,25 +1,32 @@
use crate::app_size_handler::AppSizeHandler; use crate::{
use crate::app_style::{ app_size_handler::AppSizeHandler,
create_custom_style, dark_mode, desktop_font_size, light_mode, mobile_font_size, app_style::{create_custom_style, dark_mode, desktop_font_size, light_mode, mobile_font_size},
fonts::setup_fonts,
storage::DataPath,
}; };
use crate::fonts::setup_fonts;
use eframe::NativeOptions; use eframe::NativeOptions;
//pub const UI_SCALE_FACTOR: f32 = 0.2; //pub const UI_SCALE_FACTOR: f32 = 0.2;
pub fn generate_native_options() -> NativeOptions { pub fn generate_native_options(paths: DataPath) -> NativeOptions {
generate_native_options_with_builder_modifiers(|builder| { let window_builder = Box::new(move |builder: egui::ViewportBuilder| {
let builder = builder let builder = builder
.with_fullsize_content_view(true) .with_fullsize_content_view(true)
.with_titlebar_shown(false) .with_titlebar_shown(false)
.with_title_shown(false); .with_title_shown(false);
if let Some(window_size) = AppSizeHandler::default().get_app_size() { if let Some(window_size) = AppSizeHandler::new(&paths).get_app_size() {
builder.with_inner_size(window_size) builder.with_inner_size(window_size)
} else { } else {
builder builder
} }
}) });
eframe::NativeOptions {
window_builder: Some(window_builder),
..Default::default()
}
} }
fn generate_native_options_with_builder_modifiers( fn generate_native_options_with_builder_modifiers(

View File

@@ -1,15 +1,12 @@
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use egui::Context; use egui::Context;
use tracing::{error, info}; use tracing::info;
use crate::{ use crate::storage::{write_file, DataPath, DataPathType, Directory};
storage::{write_file, Directory},
DataPaths,
};
pub struct AppSizeHandler { pub struct AppSizeHandler {
directory: Option<Directory>, directory: Directory,
saved_size: Option<egui::Vec2>, saved_size: Option<egui::Vec2>,
last_saved: Instant, last_saved: Instant,
} }
@@ -17,15 +14,9 @@ pub struct AppSizeHandler {
static FILE_NAME: &str = "app_size.json"; static FILE_NAME: &str = "app_size.json";
static DELAY: Duration = Duration::from_millis(500); static DELAY: Duration = Duration::from_millis(500);
impl Default for AppSizeHandler { impl AppSizeHandler {
fn default() -> Self { pub fn new(path: &DataPath) -> Self {
let directory = match DataPaths::Setting.get_path() { let directory = Directory::new(path.path(DataPathType::Setting));
Ok(path) => Some(Directory::new(path)),
Err(e) => {
error!("Could not load settings path: {}", e);
None
}
};
Self { Self {
directory, directory,
@@ -33,16 +24,12 @@ impl Default for AppSizeHandler {
last_saved: Instant::now() - DELAY, last_saved: Instant::now() - DELAY,
} }
} }
}
impl AppSizeHandler {
pub fn try_save_app_size(&mut self, ctx: &Context) { pub fn try_save_app_size(&mut self, ctx: &Context) {
if let Some(interactor) = &self.directory { // There doesn't seem to be a way to check if user is resizing window, so if the rect is different than last saved, we'll wait DELAY before saving again to avoid spamming io
// There doesn't seem to be a way to check if user is resizing window, so if the rect is different than last saved, we'll wait DELAY before saving again to avoid spamming io if self.last_saved.elapsed() >= DELAY {
if self.last_saved.elapsed() >= DELAY { internal_try_save_app_size(&self.directory, &mut self.saved_size, ctx);
internal_try_save_app_size(interactor, &mut self.saved_size, ctx); self.last_saved = Instant::now();
self.last_saved = Instant::now();
}
} }
} }
@@ -51,14 +38,12 @@ impl AppSizeHandler {
return self.saved_size; return self.saved_size;
} }
if let Some(directory) = &self.directory { if let Ok(file_contents) = self.directory.get_file(FILE_NAME.to_owned()) {
if let Ok(file_contents) = directory.get_file(FILE_NAME.to_owned()) { if let Ok(rect) = serde_json::from_str::<egui::Vec2>(&file_contents) {
if let Ok(rect) = serde_json::from_str::<egui::Vec2>(&file_contents) { return Some(rect);
return Some(rect);
}
} else {
info!("Could not find {}", FILE_NAME);
} }
} else {
info!("Could not find {}", FILE_NAME);
} }
None None

View File

@@ -1,7 +1,12 @@
#![warn(clippy::all, rust_2018_idioms)] #![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use notedeck::app_creation::generate_native_options; use notedeck::{
use notedeck::Damus; app_creation::generate_native_options,
storage::{DataPath, DataPathType},
Damus,
};
use std::{path::PathBuf, str::FromStr};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
// Entry point for wasm // Entry point for wasm
@@ -12,34 +17,35 @@ use tracing_subscriber::EnvFilter;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let base_path = DataPath::default_base().unwrap_or(PathBuf::from_str(".").unwrap());
let path = DataPath::new(&base_path);
#[allow(unused_variables)] // need guard to live for lifetime of program #[allow(unused_variables)] // need guard to live for lifetime of program
let (maybe_non_blocking, maybe_guard) = let (maybe_non_blocking, maybe_guard) = {
if let Ok(log_path) = notedeck::DataPaths::Log.get_path() { let log_path = path.path(DataPathType::Log);
// Setup logging to file // Setup logging to file
use std::panic; use std::panic;
use tracing::error; use tracing::error;
use tracing_appender::{ use tracing_appender::{
non_blocking, non_blocking,
rolling::{RollingFileAppender, Rotation}, rolling::{RollingFileAppender, Rotation},
};
let file_appender = RollingFileAppender::new(
Rotation::DAILY,
log_path,
format!("notedeck-{}.log", env!("CARGO_PKG_VERSION")),
);
panic::set_hook(Box::new(|panic_info| {
error!("Notedeck panicked: {:?}", panic_info);
}));
let (non_blocking, _guard) = non_blocking(file_appender);
(Some(non_blocking), Some(_guard))
} else {
(None, None)
}; };
let file_appender = RollingFileAppender::new(
Rotation::DAILY,
log_path,
format!("notedeck-{}.log", env!("CARGO_PKG_VERSION")),
);
panic::set_hook(Box::new(|panic_info| {
error!("Notedeck panicked: {:?}", panic_info);
}));
let (non_blocking, _guard) = non_blocking(file_appender);
(Some(non_blocking), Some(_guard))
};
// Log to stdout (if you run with `RUST_LOG=debug`). // Log to stdout (if you run with `RUST_LOG=debug`).
if let Some(non_blocking_writer) = maybe_non_blocking { if let Some(non_blocking_writer) = maybe_non_blocking {
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
@@ -68,8 +74,14 @@ async fn main() {
let _res = eframe::run_native( let _res = eframe::run_native(
"Damus NoteDeck", "Damus NoteDeck",
generate_native_options(), generate_native_options(path),
Box::new(|cc| Ok(Box::new(Damus::new(cc, ".", std::env::args().collect())))), Box::new(|cc| {
Ok(Box::new(Damus::new(
cc,
base_path,
std::env::args().collect(),
)))
}),
); );
} }

View File

@@ -25,8 +25,8 @@ impl ImageCache {
} }
} }
pub fn rel_datadir() -> &'static str { pub fn rel_dir() -> &'static str {
"cache/img" "img"
} }
pub fn write(cache_dir: &path::Path, url: &str, data: ColorImage) -> Result<()> { pub fn write(cache_dir: &path::Path, url: &str, data: ColorImage) -> Result<()> {

View File

@@ -46,12 +46,11 @@ mod view_state;
#[macro_use] #[macro_use]
mod test_utils; mod test_utils;
mod storage; pub mod storage;
pub use app::Damus; pub use app::Damus;
pub use error::Error; pub use error::Error;
pub use profile::DisplayName; pub use profile::DisplayName;
pub use storage::DataPaths;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
use winit::platform::android::EventLoopBuilderExtAndroid; use winit::platform::android::EventLoopBuilderExtAndroid;

View File

@@ -8,33 +8,45 @@ use std::{
use crate::Error; use crate::Error;
pub enum DataPaths { #[derive(Debug, Clone)]
pub struct DataPath {
base: PathBuf,
}
impl DataPath {
pub fn new(base: impl AsRef<Path>) -> Self {
let base = base.as_ref().to_path_buf();
Self { base }
}
pub fn default_base() -> Option<PathBuf> {
dirs::data_local_dir().map(|pb| pb.join("notedeck"))
}
}
pub enum DataPathType {
Log, Log,
Setting, Setting,
Keys, Keys,
SelectedKey, SelectedKey,
Db,
Cache,
} }
impl DataPaths { impl DataPath {
pub fn get_path(&self) -> Result<PathBuf, Error> { pub fn rel_path(&self, typ: DataPathType) -> PathBuf {
let base_path = match self { match typ {
DataPaths::Log => dirs::data_local_dir(), DataPathType::Log => PathBuf::from("logs"),
DataPaths::Setting | DataPaths::Keys | DataPaths::SelectedKey => { DataPathType::Setting => PathBuf::from("settings"),
dirs::config_local_dir() DataPathType::Keys => PathBuf::from("storage").join("accounts"),
} DataPathType::SelectedKey => PathBuf::from("storage").join("selected_account"),
DataPathType::Db => PathBuf::from("db"),
DataPathType::Cache => PathBuf::from("cache"),
} }
.ok_or(Error::Generic( }
"Could not open well known OS directory".to_owned(),
))?;
let specific_path = match self { pub fn path(&self, typ: DataPathType) -> PathBuf {
DataPaths::Log => PathBuf::from("logs"), self.base.join(self.rel_path(typ))
DataPaths::Setting => PathBuf::from("settings"),
DataPaths::Keys => PathBuf::from("storage").join("accounts"),
DataPaths::SelectedKey => PathBuf::from("storage").join("selected_account"),
};
Ok(base_path.join("notedeck").join(specific_path))
} }
} }

View File

@@ -3,8 +3,8 @@ mod file_storage;
pub use file_key_storage::FileKeyStorage; pub use file_key_storage::FileKeyStorage;
pub use file_storage::write_file; pub use file_storage::write_file;
pub use file_storage::DataPaths;
pub use file_storage::Directory; pub use file_storage::Directory;
pub use file_storage::{DataPath, DataPathType};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod security_framework_key_storage; mod security_framework_key_storage;

View File

@@ -1,26 +1,20 @@
use tracing::error; use tracing::error;
use crate::{storage::Directory, DataPaths}; use crate::storage::{DataPath, DataPathType, Directory};
pub struct Support { pub struct Support {
directory: Option<Directory>, directory: Directory,
mailto_url: String, mailto_url: String,
most_recent_log: Option<String>, most_recent_log: Option<String>,
} }
fn new_log_dir() -> Option<Directory> { fn new_log_dir(paths: &DataPath) -> Directory {
match DataPaths::Log.get_path() { Directory::new(paths.path(DataPathType::Log))
Ok(path) => Some(Directory::new(path)),
Err(e) => {
error!("Support could not open directory: {}", e.to_string());
None
}
}
} }
impl Default for Support { impl Support {
fn default() -> Self { pub fn new(path: &DataPath) -> Self {
let directory = new_log_dir(); let directory = new_log_dir(path);
Self { Self {
mailto_url: MailtoBuilder::new(SUPPORT_EMAIL.to_string()) mailto_url: MailtoBuilder::new(SUPPORT_EMAIL.to_string())
@@ -39,11 +33,7 @@ static EMAIL_TEMPLATE: &str = "Describe the bug you have encountered:\n<-- your
impl Support { impl Support {
pub fn refresh(&mut self) { pub fn refresh(&mut self) {
if let Some(directory) = &self.directory { self.most_recent_log = get_log_str(&self.directory);
self.most_recent_log = get_log_str(directory);
} else {
self.directory = new_log_dir();
}
} }
pub fn get_mailto_url(&self) -> &str { pub fn get_mailto_url(&self) -> &str {
@@ -51,7 +41,7 @@ impl Support {
} }
pub fn get_log_dir(&self) -> Option<&str> { pub fn get_log_dir(&self) -> Option<&str> {
self.directory.as_ref()?.file_path.to_str() self.directory.file_path.to_str()
} }
pub fn get_most_recent_log(&self) -> Option<&String> { pub fn get_most_recent_log(&self) -> Option<&String> {

View File

@@ -1,5 +1,6 @@
use crate::app_style::NotedeckTextStyle; use crate::app_style::NotedeckTextStyle;
use crate::imgcache::ImageCache; use crate::imgcache::ImageCache;
use crate::storage::{DataPath, DataPathType};
use crate::ui::ProfilePic; use crate::ui::ProfilePic;
use crate::user_account::UserAccount; use crate::user_account::UserAccount;
use crate::{colors, images, DisplayName}; use crate::{colors, images, DisplayName};
@@ -126,7 +127,10 @@ mod previews {
impl<'a> ProfilePreviewPreview<'a> { impl<'a> ProfilePreviewPreview<'a> {
pub fn new() -> Self { pub fn new() -> Self {
let profile = test_profile_record(); let profile = test_profile_record();
let cache = ImageCache::new(ImageCache::rel_datadir().into()); let path = DataPath::new("previews")
.path(DataPathType::Cache)
.join(ImageCache::rel_dir());
let cache = ImageCache::new(path);
ProfilePreviewPreview { profile, cache } ProfilePreviewPreview { profile, cache }
} }
} }

View File

@@ -1,10 +1,11 @@
use notedeck::app_creation::{
generate_mobile_emulator_native_options, generate_native_options, setup_cc,
};
use notedeck::ui::add_column::AddColumnView;
use notedeck::ui::{ use notedeck::ui::{
account_login_view::AccountLoginView, account_management::AccountsView, DesktopSidePanel, account_login_view::AccountLoginView, account_management::AccountsView,
PostView, Preview, PreviewApp, PreviewConfig, ProfilePic, ProfilePreview, RelayView, add_column::AddColumnView, DesktopSidePanel, PostView, Preview, PreviewApp, PreviewConfig,
ProfilePic, ProfilePreview, RelayView,
};
use notedeck::{
app_creation::{generate_mobile_emulator_native_options, generate_native_options, setup_cc},
storage::DataPath,
}; };
use std::env; use std::env;
@@ -30,7 +31,8 @@ impl PreviewRunner {
let native_options = if self.force_mobile { let native_options = if self.force_mobile {
generate_mobile_emulator_native_options() generate_mobile_emulator_native_options()
} else { } else {
generate_native_options() // TODO: tmp preview pathbuf?
generate_native_options(DataPath::new("previews"))
}; };
let is_mobile = self.force_mobile; let is_mobile = self.force_mobile;