Skip to content

Commit

Permalink
refactor(term): abstract file system
Browse files Browse the repository at this point in the history
  • Loading branch information
ymgyt committed Aug 3, 2024
1 parent 8191d7e commit 9cbef55
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 82 deletions.
22 changes: 18 additions & 4 deletions crates/synd_term/src/application/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,30 @@ use std::{borrow::Borrow, io, path::PathBuf};
use crate::{
auth::{Credential, CredentialError, Unverified},
config,
filesystem::{fsimpl, FileSystem},
ui::components::gh_notifications::GhNotificationFilterOptions,
};

pub struct Cache {
pub struct Cache<FS = fsimpl::FileSystem> {
dir: PathBuf,
fs: FS,
}

impl Cache {
impl Cache<fsimpl::FileSystem> {
pub fn new(dir: impl Into<PathBuf>) -> Self {
Self { dir: dir.into() }
Self::with(dir, fsimpl::FileSystem::new())
}
}

impl<FS> Cache<FS>
where
FS: FileSystem,
{
pub fn with(dir: impl Into<PathBuf>, fs: FS) -> Self {
Self {
dir: dir.into(),
fs,
}
}

/// Persist credential in filesystem.
Expand Down Expand Up @@ -85,7 +99,7 @@ impl Cache {
pub(crate) fn clean(&self) -> io::Result<()> {
// User can specify any directory as the cache
// so instead of deleting the entire directory with `remove_dir_all`, delete files individually.
match std::fs::remove_file(self.credential_file()) {
match self.fs.remove_file(self.credential_file()) {
Ok(()) => Ok(()),
Err(err) => match err.kind() {
io::ErrorKind::NotFound => Ok(()),
Expand Down
56 changes: 0 additions & 56 deletions crates/synd_term/src/cli/clean.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{io, path::Path, time::Duration};
use std::{io, path::Path, process::ExitCode, time::Duration};

use anyhow::Context;
use clap::Args;
Expand All @@ -22,12 +22,12 @@ pub struct CheckCommand {

impl CheckCommand {
#[allow(clippy::unused_self)]
pub async fn run(self, endpoint: Url) -> i32 {
pub async fn run(self, endpoint: Url) -> ExitCode {
if let Err(err) = self.check(endpoint).await {
tracing::error!("{err:?}");
1
ExitCode::from(1)
} else {
0
ExitCode::SUCCESS
}
}

Expand Down
116 changes: 116 additions & 0 deletions crates/synd_term/src/cli/command/clean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::{
io::ErrorKind,
path::{Path, PathBuf},
process::ExitCode,
};

use anyhow::Context;
use clap::Args;

use crate::{application::Cache, config, filesystem::FileSystem};

/// Clean cache and logs
#[derive(Args, Debug)]
pub struct CleanCommand {
/// Cache directory
#[arg(
long,
default_value = config::cache::dir().to_path_buf().into_os_string(),
)]
cache_dir: PathBuf,
}

impl CleanCommand {
#[allow(clippy::unused_self)]
pub fn run<FS>(self, fs: &FS) -> ExitCode
where
FS: FileSystem + Clone,
{
ExitCode::from(self.clean(fs, config::log_path().as_path()))
}

fn clean<FS>(self, fs: &FS, log: &Path) -> u8
where
FS: FileSystem + Clone,
{
if let Err(err) = self.try_clean(fs, log) {
tracing::error!("{err}");
1
} else {
0
}
}
fn try_clean<FS>(self, fs: &FS, log: &Path) -> anyhow::Result<()>
where
FS: FileSystem + Clone,
{
let CleanCommand { cache_dir } = self;

let cache = Cache::with(&cache_dir, fs.clone());
cache
.clean()
.map_err(anyhow::Error::from)
.with_context(|| format!("path: {}", cache_dir.display()))?;

// remove log
match fs.remove_file(log) {
Ok(()) => {
tracing::info!("Remove {}", log.display());
}
Err(err) => match err.kind() {
ErrorKind::NotFound => {}
_ => {
return Err(anyhow::Error::from(err))
.with_context(|| format!("path: {}", log.display()))
}
},
}

Ok(())
}
}

#[cfg(test)]
mod tests {
use std::io;

use tempfile::{NamedTempFile, TempDir};

use crate::filesystem::{fsimpl, mock::MockFileSystem};

use super::*;

#[test]
fn remove_log_file() {
let clean = CleanCommand {
cache_dir: TempDir::new().unwrap().into_path(),
};
let log_file = NamedTempFile::new().unwrap();
let exit_code = clean.clean(&fsimpl::FileSystem::new(), log_file.path());
assert_eq!(exit_code, 0);
assert!(!log_file.path().exists());
}

#[test]
fn ignore_log_file_not_found() {
let clean = CleanCommand {
cache_dir: TempDir::new().unwrap().into_path(),
};
let log_file = Path::new("./not_exists");
let fs = MockFileSystem::default().with_remove_errors(log_file, io::ErrorKind::NotFound);
let exit_code = clean.clean(&fs, log_file);
assert_eq!(exit_code, 0);
}

#[test]
fn exit_code_on_permission_error() {
let clean = CleanCommand {
cache_dir: TempDir::new().unwrap().into_path(),
};
let log_file = Path::new("./not_allowed");
let fs =
MockFileSystem::default().with_remove_errors(log_file, io::ErrorKind::PermissionDenied);
let exit_code = clean.clean(&fs, log_file);
assert_eq!(exit_code, 1);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{path::PathBuf, time::Duration};
use std::{path::PathBuf, process::ExitCode, time::Duration};

use anyhow::anyhow;
use clap::Args;
Expand Down Expand Up @@ -39,17 +39,17 @@ pub struct ExportCommand {
}

impl ExportCommand {
pub async fn run(self, endpoint: Url) -> i32 {
pub async fn run(self, endpoint: Url) -> ExitCode {
let err = if self.print_schema {
Self::print_json_schema()
} else {
self.export(endpoint).await
};
if let Err(err) = err {
tracing::error!("{err:?}");
1
ExitCode::from(1)
} else {
0
ExitCode::SUCCESS
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/synd_term/src/cli/command/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod check;
pub mod clean;
pub mod export;
10 changes: 4 additions & 6 deletions crates/synd_term/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ use url::Url;

use crate::{config, ui::theme};

mod check;
mod clean;
mod export;
mod command;

#[derive(Copy, Clone, PartialEq, Eq, Debug, clap::ValueEnum)]
pub enum Palette {
Expand Down Expand Up @@ -102,9 +100,9 @@ pub struct GithubOptions {
#[derive(Subcommand, Debug)]
pub enum Command {
#[command(alias = "clear")]
Clean(clean::CleanCommand),
Check(check::CheckCommand),
Export(export::ExportCommand),
Clean(command::clean::CleanCommand),
Check(command::check::CheckCommand),
Export(command::export::ExportCommand),
}

pub fn parse() -> Args {
Expand Down
53 changes: 53 additions & 0 deletions crates/synd_term/src/filesystem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::{io, path::Path};

pub trait FileSystem {
fn remove_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
}

pub mod fsimpl {
#[derive(Debug, Clone)]
pub struct FileSystem {}

impl FileSystem {
pub fn new() -> Self {
Self {}
}
}

impl super::FileSystem for FileSystem {
fn remove_file<P: AsRef<std::path::Path>>(&self, path: P) -> std::io::Result<()> {
std::fs::remove_file(path)
}
}
}

#[cfg(test)]
pub(crate) mod mock {
use std::{collections::HashMap, io, path::PathBuf};

#[derive(Default, Clone)]
pub(crate) struct MockFileSystem {
remove_errors: HashMap<PathBuf, io::ErrorKind>,
}

impl MockFileSystem {
pub(crate) fn with_remove_errors(
mut self,
path: impl Into<PathBuf>,
err: io::ErrorKind,
) -> Self {
self.remove_errors.insert(path.into(), err);
self
}
}

impl super::FileSystem for MockFileSystem {
fn remove_file<P: AsRef<std::path::Path>>(&self, path: P) -> io::Result<()> {
let path = path.as_ref();
match self.remove_errors.get(path) {
Some(err) => Err(io::Error::from(*err)),
None => Ok(()),
}
}
}
}
1 change: 1 addition & 0 deletions crates/synd_term/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod cli;
pub mod client;
pub(crate) mod command;
pub mod config;
pub mod filesystem;
pub mod interact;
pub mod job;
pub mod keymap;
Expand Down
Loading

0 comments on commit 9cbef55

Please sign in to comment.