bufkit_data/archive/
root.rs1use crate::{errors::BufkitDataErr, models::Model, site::StationNumber, Archive};
2use rusqlite::ToSql;
3
4impl Archive {
5 const DATA_DIR: &'static str = "data";
6 const DB_FILE: &'static str = "index.db";
7
8 pub fn create(root: &dyn AsRef<std::path::Path>) -> Result<Self, BufkitDataErr> {
10 let data_root = root.as_ref().join(Archive::DATA_DIR);
11 let db_file = root.as_ref().join(Archive::DB_FILE);
12 let root = root.as_ref().to_path_buf();
13
14 std::fs::create_dir_all(&data_root)?; let db_conn = rusqlite::Connection::open_with_flags(
18 db_file,
19 rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE,
20 )?;
21
22 db_conn.execute_batch(include_str!("root/create_index.sql"))?;
23
24 Ok(Archive { root, db_conn })
25 }
26
27 pub fn connect(root: &dyn AsRef<std::path::Path>) -> Result<Self, BufkitDataErr> {
29 let db_file = root.as_ref().join(Archive::DB_FILE);
30 let root = root.as_ref().to_path_buf();
31
32 let db_conn = rusqlite::Connection::open_with_flags(
34 db_file,
35 rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE,
36 )?;
37
38 Ok(Archive { root, db_conn })
39 }
40
41 pub fn root(&self) -> &std::path::Path {
43 &self.root
44 }
45
46 pub(crate) fn data_root(&self) -> std::path::PathBuf {
48 self.root.join(Archive::DATA_DIR)
49 }
50
51 pub fn export(
53 &self,
54 stations: &[StationNumber],
55 models: &[Model],
56 start: chrono::NaiveDateTime,
57 end: chrono::NaiveDateTime,
58 dest: &std::path::Path,
59 ) -> Result<(), BufkitDataErr> {
60 let new_db = Self::create(&dest)?;
61 let db_file = new_db.root.join(Archive::DB_FILE);
62
63 let statement = &format!(
64 "ATTACH '{}' AS ex;",
65 db_file.to_str().ok_or_else(|| BufkitDataErr::GeneralError(
66 "Unable to convert path to string".to_owned()
67 ))?
68 );
69 self.db_conn.execute(statement, [])?;
70
71 let mut sites_stmt = self.db_conn.prepare(
72 "
73 INSERT INTO ex.sites
74 SELECT * FROM main.sites
75 WHERE main.sites.station_num = ?1
76 ",
77 )?;
78
79 let mut files_stmt = self.db_conn.prepare(
80 "
81 INSERT INTO ex.files
82 SELECT * FROM main.files
83 WHERE main.files.station_num = ?1 AND main.files.model = ?2
84 AND main.files.init_time >= ?3 AND main.files.init_time <= ?4
85 ",
86 )?;
87
88 let source_dir = self.root.join(Archive::DATA_DIR);
89 let dest_dir = dest.join(Archive::DATA_DIR);
90 let mut file_names_stmt = self.db_conn.prepare(
91 "
92 SELECT ex.files.file_name FROM ex.files
93 WHERE ex.files.station_num = ?1 AND ex.files.model = ?2
94 AND ex.files.init_time >= ?3 AND ex.files.init_time <= ?4
95 ",
96 )?;
97
98 for &stn in stations {
99 let stn_num: u32 = stn.into();
100 sites_stmt.execute([stn_num])?;
101
102 for &model in models {
103 files_stmt.execute(&[
104 &stn_num as &dyn ToSql,
105 &model.as_static_str(),
106 &start,
107 &end,
108 ])?;
109
110 let fnames = file_names_stmt.query_and_then(
111 &[&stn_num as &dyn ToSql, &model.as_static_str(), &start, &end],
112 |row| -> Result<String, _> { row.get(0) },
113 )?;
114
115 for fname in fnames {
116 let fname = fname?;
117 let src = source_dir.join(&fname);
118 let dest = dest_dir.join(fname);
119 std::fs::copy(src, dest)?;
120 }
121 }
122 }
123
124 Ok(())
125 }
126}
127
128#[cfg(test)]
129mod unit {
130 use super::*;
131 use crate::archive::unit::*; #[test]
134 fn test_archive_create_new() {
135 assert!(create_test_archive().is_ok());
136 }
137
138 #[test]
139 fn test_archive_connect() {
140 let TestArchive { tmp, arch } =
141 create_test_archive().expect("Failed to create test archive.");
142 drop(arch);
143
144 assert!(Archive::connect(&tmp.path()).is_ok());
145 assert!(Archive::connect(&"unlikely_directory_in_my_project").is_err());
146 }
147
148 #[test]
149 fn test_get_root() {
150 let TestArchive { tmp, arch } =
151 create_test_archive().expect("Failed to create test archive.");
152
153 let root = arch.root();
154 assert_eq!(root, tmp.path());
155 }
156}