From fb7ee8cb77e7d61b3efc3cf51f66c82433a72314 Mon Sep 17 00:00:00 2001 From: Lucas Pickering Date: Mon, 4 Dec 2023 20:35:31 -0500 Subject: [PATCH] Move data and log files into subdirectory --- CHANGELOG.md | 4 +++- src/cli.rs | 4 ++-- src/collection.rs | 4 +++- src/http/repository.rs | 13 ++++++---- src/main.rs | 5 ++-- src/util.rs | 54 +++++++++++++++++++++++++++++++++--------- 6 files changed, 61 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b75a46e4..1b58c2ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,10 @@ ### Changed -- Hide sensitive chain values in preview - [BREAKING] Key profiles/chains/requests by ID in collection file +- [BREAKING] Move request history from `slumber/{id}.sqlite` to `slumber/{id}/requests.sqlite` + - Request history will be lost. If you want to recover it, you can move the old file to the new location (use `slumber show` to find the directory location) +- Hide sensitive chain values in preview - Add collection ID/path to help modal ([#59](https://github.com/LucasPickering/slumber/issues/59)) - Also add collection ID to terminal title diff --git a/src/cli.rs b/src/cli.rs index adcf16eb..ce5fc3d8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -2,7 +2,7 @@ use crate::{ collection::{ProfileId, RequestCollection, RequestRecipeId}, http::{HttpEngine, Repository, RequestBuilder}, template::{Prompt, Prompter, TemplateContext}, - util::{data_directory, ResultExt}, + util::{Directory, ResultExt}, }; use anyhow::{anyhow, Context}; use dialoguer::{Input, Password}; @@ -147,7 +147,7 @@ impl Subcommand { } Subcommand::Show => { - println!("Directory: {}", data_directory().display()); + println!("Directory: {}", Directory::root()); Ok(()) } } diff --git a/src/collection.rs b/src/collection.rs index d129df02..e441cc9b 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -57,7 +57,9 @@ pub struct RequestCollection { /// A unique ID for a collection. This is necessary to differentiate between /// responses from different collections in the repository. -#[derive(Clone, Debug, Default, Display, From, Serialize, Deserialize)] +#[derive( + Clone, Debug, Default, Deref, Display, From, Serialize, Deserialize, +)] pub struct CollectionId(String); /// Mutually exclusive hot-swappable config group diff --git a/src/http/repository.rs b/src/http/repository.rs index 044abdf9..e0856ee4 100644 --- a/src/http/repository.rs +++ b/src/http/repository.rs @@ -5,7 +5,7 @@ use crate::{ collection::{CollectionId, RequestRecipeId}, http::{Request, RequestId, RequestRecord, Response}, - util::{data_directory, ResultExt}, + util::{Directory, ResultExt}, }; use anyhow::Context; use rusqlite::{ @@ -48,7 +48,7 @@ impl Repository { /// Load the repository database. This will perform first-time setup, so /// this should only be called at the main session entrypoint. pub fn load(collection_id: &CollectionId) -> anyhow::Result { - let mut connection = Connection::open(Self::path(collection_id))?; + let mut connection = Connection::open(Self::path(collection_id)?)?; // Use WAL for concurrency connection.pragma_update(None, "journal_mode", "WAL")?; Self::setup(&mut connection)?; @@ -57,9 +57,12 @@ impl Repository { }) } - /// Path to the repository database file - fn path(collection_id: &CollectionId) -> PathBuf { - data_directory().join(format!("{collection_id}.sqlite")) + /// Path to the repository database file. This will create the directory if + /// it doesn't exist + fn path(collection_id: &CollectionId) -> anyhow::Result { + Ok(Directory::data(collection_id) + .create()? + .join("requests.sqlite")) } /// Apply first-time setup diff --git a/src/main.rs b/src/main.rs index ae215331..9395ccba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,7 @@ mod tui; mod util; use crate::{ - cli::Subcommand, collection::RequestCollection, tui::Tui, - util::data_directory, + cli::Subcommand, collection::RequestCollection, tui::Tui, util::Directory, }; use anyhow::Context; use clap::Parser; @@ -60,7 +59,7 @@ async fn main() -> anyhow::Result<()> { /// Set up tracing to log to a file fn initialize_tracing() -> anyhow::Result<()> { - let directory = data_directory(); + let directory = Directory::log().create()?; std::fs::create_dir_all(&directory) .context(format!("Error creating log directory {directory:?}"))?; diff --git a/src/util.rs b/src/util.rs index ba3122ae..23241c14 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,20 +1,51 @@ -use crate::http::RequestError; +use crate::{collection::CollectionId, http::RequestError}; use std::{ + fs, ops::Deref, path::{Path, PathBuf}, }; use tracing::error; -/// Where to store data and log files. The value is contextual: -/// - In development, use a directory in the current directory -/// - In release, use a platform-specific directory in the user's home -pub fn data_directory() -> PathBuf { - if cfg!(debug_assertions) { - Path::new("./data/").into() - } else { - // According to the docs, this dir will be present on all platforms - // https://docs.rs/dirs/latest/dirs/fn.data_dir.html - dirs::data_dir().unwrap().join("slumber") +/// A wrapper around `PathBuf` that makes it impossible to access a directory +/// path without creating the dir first. The idea is to prevent all the possible +/// bugs that could occur when a directory doesn't exist. +/// +/// If you just want to print the path without having to create it (e.g. for +/// debug output), use the `Debug` or `Display` impls. +#[derive(Debug, Display)] +#[display("{}", _0.display())] +pub struct Directory(PathBuf); + +impl Directory { + /// Root directory for all generated files. The value is contextual: + /// - In development, use a directory in the current directory + /// - In release, use a platform-specific directory in the user's home + pub fn root() -> Self { + if cfg!(debug_assertions) { + Self(Path::new("./data/").into()) + } else { + // According to the docs, this dir will be present on all platforms + // https://docs.rs/dirs/latest/dirs/fn.data_dir.html + Self(dirs::data_dir().unwrap().join("slumber")) + } + } + + /// Directory to store log files + pub fn log() -> Self { + Self(Self::root().0.join("log")) + } + + /// Directory to store collection-specific data files + pub fn data(collection_id: &CollectionId) -> Self { + Self(Self::root().0.join(collection_id.as_str())) + } + + /// Create this directory, and return the path. This is the only way to + /// access the path value directly, enforcing that it can't be used without + /// being created. + pub fn create(self) -> anyhow::Result { + fs::create_dir_all(&self.0)?; + Ok(self.0) } } @@ -61,3 +92,4 @@ macro_rules! assert_err { #[cfg(test)] pub(crate) use assert_err; +use derive_more::Display;