Coverage Report

Created: 2024-02-20 21:15

/builds/xfbs/cindy/src/cindy.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::{
2
    cli::{Command, Options},
3
    config::Config,
4
    database::Database,
5
    hash::{Digester, Hash},
6
};
7
use anyhow::{bail, Result};
8
use rusqlite::Connection;
9
use std::{
10
    path::{Path, PathBuf},
11
    sync::Arc,
12
};
13
use tokio::{
14
    fs::{create_dir, create_dir_all, read_to_string, try_exists, write},
15
    sync::{Mutex, OwnedMutexGuard},
16
};
17
18
const CINDY_CONFIG: &str = "config.toml";
19
const CINDY_FOLDER: &str = ".cindy";
20
21
1.78k
#[derive(Clone, 
Debug0
)]
22
pub struct Cindy {
23
    /// Root of the Cindy project.
24
    root: PathBuf,
25
    /// Configuration.
26
    config: Arc<Config>,
27
    /// Hasher.
28
    hasher: Arc<dyn Digester + Send + Sync>,
29
    /// Database handle.
30
    database: Arc<Mutex<Database>>,
31
}
32
33
impl Cindy {
34
    /// Create or open Cindy project, depending on command.
35
0
    pub async fn new(options: &Options) -> Result<Self> {
36
0
        match &options.command {
37
0
            Command::Init(command) => {
38
0
                let config = Config::default();
39
0
                Cindy::initialize(&command.path, &config).await
40
            }
41
0
            _ => Cindy::discover(&std::env::current_dir()?).await,
42
        }
43
0
    }
44
45
    /// Root of Cindy project.
46
51
    pub fn root(&self) -> &Path {
47
51
        &self.root
48
51
    }
49
50
    /// Cindy folder
51
51
    pub fn cindy_folder(&self) -> PathBuf {
52
51
        self.root.join(CINDY_FOLDER)
53
51
    }
54
55
2
    pub fn config_path(&self) -> PathBuf {
56
2
        self.cindy_folder().join(CINDY_CONFIG)
57
2
    }
58
59
1
    pub fn database_path(&self) -> PathBuf {
60
1
        self.cindy_folder().join(&self.config.index.path)
61
1
    }
62
63
2
    pub fn data_path(&self) -> PathBuf {
64
2
        self.cindy_folder().join(&self.config.data.path)
65
2
    }
66
67
2
    pub fn thumbs_path(&self) -> PathBuf {
68
2
        self.cindy_folder().join(&self.config.thumbs.path)
69
2
    }
70
71
    /// Config of Cindy.
72
3
    pub fn config(&self) -> &Arc<Config> {
73
3
        &self.config
74
3
    }
75
76
    /// Hasher, this is used to calculate hashes of files.
77
28
    pub fn hasher(&self) -> &Arc<dyn Digester + Send + Sync> {
78
28
        &self.hasher
79
28
    }
80
81
    /// Get a handle to the database.
82
61
    pub async fn database(&self) -> OwnedMutexGuard<Database> {
83
61
        self.database.clone().lock_owned().
await0
84
61
    }
85
86
    /// Given a hash, determine a path.
87
40
    pub fn hash_path(&self, hash: &Hash) -> PathBuf {
88
40
        self.cindy_folder().join(self.config.data.data_path(hash))
89
40
    }
90
91
    /// Initialize new Cindy project.
92
25
    pub async fn initialize(path: &Path, config: &Config) -> Result<Self> {
93
25
        if !try_exists(path).await
?0
{
94
0
            create_dir(path).await?;
95
25
        }
96
97
25
        let cindy_dir = path.join(CINDY_FOLDER);
98
25
        create_dir(&cindy_dir).await
?0
;
99
100
        // write config
101
25
        let config_string = toml::to_string(config)
?0
;
102
25
        write(cindy_dir.join(CINDY_CONFIG), config_string).await
?0
;
103
104
25
        create_dir_all(cindy_dir.join(&config.data.path)).await
?0
;
105
25
        create_dir_all(cindy_dir.join(&config.thumbs.path)).await
?0
;
106
107
25
        let database: Database = Connection::open(cindy_dir.join(&config.index.path))
?0
.into();
108
25
        database.migrate()
?0
;
109
110
25
        Ok(Self {
111
25
            root: path.into(),
112
25
            config: config.clone().into(),
113
25
            hasher: Arc::new(config.data.hash.clone()),
114
25
            database: Arc::new(Mutex::new(database)),
115
25
        })
116
25
    }
117
118
    /// Load Cindy project, will parse Config file.
119
3
    pub async fn load(path: &Path) -> Result<Self> {
120
3
        let config_string = read_to_string(&path.join(CINDY_FOLDER).join(CINDY_CONFIG)).await
?0
;
121
3
        let config: Config = toml::from_str(&config_string)
?0
;
122
3
        Self::open(path, &config).
await0
123
3
    }
124
125
    /// Open Cindy project with supplied configuration.
126
3
    pub async fn open(path: &Path, config: &Config) -> Result<Self> {
127
3
        let database = Connection::open(path.join(CINDY_FOLDER).join(&config.index.path))
?0
;
128
3
        Ok(Self {
129
3
            root: path.into(),
130
3
            config: config.clone().into(),
131
3
            hasher: Arc::new(config.data.hash.clone()),
132
3
            database: Arc::new(Mutex::new(database.into())),
133
3
        })
134
3
    }
135
136
    /// Discover a Cindy project starting at the supplied path.
137
2
    pub async fn discover(path: &Path) -> Result<Self> {
138
2
        let path = path.canonicalize()
?0
;
139
4
        for ancestor in path.
ancestors()2
{
140
4
            if try_exists(&ancestor.join(CINDY_FOLDER)).await
?0
{
141
2
                return Self::load(ancestor).await;
142
2
            }
143
        }
144
0
        bail!("No cindy project found");
145
2
    }
146
}