1use anyhow::{bail, Result};
2use bp7::Bundle;
3use log::{debug, error, info};
4use sanitize_filename_reader_friendly::sanitize;
5use std::path::{Path, PathBuf};
6use std::{convert::TryInto, fs};
7use walkdir::{DirEntry, WalkDir};
8
9use crate::db::BundleEntry;
10
11#[derive(Debug, Clone)]
12pub struct D7sFs {
13 base: String,
14}
15
16impl D7sFs {
17 pub fn open(base: &str) -> Result<Self> {
18 let me = Self { base: base.into() };
19 me.setup()?;
20 Ok(me)
21 }
22 fn setup(&self) -> Result<()> {
23 let basepath = Path::new(&self.base);
24 fs::create_dir_all(basepath)?;
25
26 fs::create_dir_all(self.path_single())?;
27 fs::create_dir_all(self.path_administrative())?;
28 fs::create_dir_all(self.path_group())?;
29
30 let version_file = basepath.join("version.txt");
31 if version_file.exists() {
32 let version: u32 = fs::read_to_string(version_file)?.parse()?;
33 if version < crate::D7S_VERSION {
34 info!("old filesystem structure detected, upgrade needed");
35 unimplemented!();
36 } else if version > crate::D7S_VERSION {
37 error!("filesystem structure is newer, upgrade program to newest version");
38 bail!("outdated program version");
39 }
40 }
41 let version_file = basepath.join("version.txt");
42 fs::write(version_file, format!("{}", crate::D7S_VERSION))?;
43
44 Ok(())
45 }
46 pub fn path_single(&self) -> PathBuf {
47 let basepath = Path::new(&self.base);
48 basepath.join("single")
49 }
50 pub fn path_administrative(&self) -> PathBuf {
51 let basepath = Path::new(&self.base);
52 basepath.join("adm")
53 }
54 pub fn path_group(&self) -> PathBuf {
55 let basepath = Path::new(&self.base);
56 basepath.join("group")
57 }
58 pub fn path_for_bundle(&self, bndl: &Bundle) -> PathBuf {
59 let dst = sanitize(
60 &bndl
61 .primary
62 .destination
63 .node()
64 .unwrap_or_else(|| "none".to_owned()),
65 );
66 if bndl.is_administrative_record() {
67 self.path_administrative().join(&dst)
68 } else {
69 match &bndl.primary.destination {
70 bp7::EndpointID::Dtn(_, addr) => {
71 if addr.is_non_singleton() {
72 self.path_group().join(&dst)
73 } else {
74 self.path_single().join(&dst)
75 }
76 }
77 bp7::EndpointID::DtnNone(_, _) => {
78 unimplemented!()
79 }
80 bp7::EndpointID::Ipn(_, _addr) => {
81 unimplemented!()
82 }
83 }
84 }
85 }
86
87 pub fn path_for_bundle_with_filename(&self, bndl: &Bundle) -> PathBuf {
88 let filename = format!("{}.bundle", sanitize(&bndl.id()));
89 self.path_for_bundle(bndl).join(&filename)
90 }
91 pub fn exists(&self, bndl: &Bundle) -> bool {
92 self.path_for_bundle_with_filename(bndl).exists()
93 }
94 pub fn save_bundle(&self, bndl: &mut Bundle) -> Result<(u64, String)> {
95 let bid = bndl.id();
96 let filename = format!("{}.bundle", sanitize(&bid));
97 let dest_path = self.path_for_bundle(bndl);
98
99 fs::create_dir_all(&dest_path)?;
100 let dest_path = dest_path.join(&filename);
101 if dest_path.exists() {
102 debug!("File {} already exists, skipping", filename);
103 } else {
104 fs::write(&dest_path, bndl.to_cbor())?;
105 debug!("saved {} to {}", bid, dest_path.to_string_lossy());
106 }
107 Ok((
109 fs::metadata(&dest_path)?.len(),
110 dest_path.to_string_lossy().into(),
111 ))
112 }
113 pub fn remove_bundle(&self, bid: &str) -> Result<()> {
114 if let Some(filename) = self.find_file_by_bid(bid) {
115 fs::remove_file(filename)?;
116 } else {
117 bail!("bundle ID not found");
118 }
119 Ok(())
120 }
121 pub fn find_file_by_bid(&self, bid: &str) -> Option<PathBuf> {
122 let target = format!("{}.bundle", sanitize(bid));
123 for entry in WalkDir::new(&self.base)
124 .into_iter()
125 .filter_map(|e| e.ok())
126 .filter(|f| f.file_name().to_str().unwrap_or_default() == target)
127 {
128 return Some(entry.into_path());
131 }
133 None
134 }
135 pub fn all_bids(&self) -> Vec<String> {
136 let mut bids = Vec::new();
137 for entry in WalkDir::new(&self.base)
138 .into_iter()
139 .filter_map(|e| e.ok())
140 .filter(|f| {
141 f.file_name()
142 .to_str()
143 .unwrap_or_default()
144 .ends_with(".bundle")
145 })
146 {
147 let filename = entry
148 .file_name()
149 .to_str()
150 .unwrap()
151 .rsplit('.')
152 .collect::<Vec<&str>>()[1]
153 .to_string();
154 if filename.starts_with("dtn_") {
155 let bid = if filename.starts_with("dtn_none") {
156 filename.replacen('_', ":", 1)
157 } else {
158 filename.replacen('_', "://", 1)
159 };
160 let bid = bid.replacen('_', "/", 1);
161 bids.push(bid);
162 } else {
163 unimplemented!("only dtn bundle scheme support at the moment!");
164 }
165 }
166 bids
167 }
168 pub fn get_bundle(&self, bid: &str) -> Result<Bundle> {
169 if let Some(filename) = self.find_file_by_bid(bid) {
170 let buffer = fs::read(filename)?;
171 let bndl: Bundle = buffer.try_into()?;
172 Ok(bndl)
173 } else {
174 bail!("bundle ID not found");
175 }
176 }
177 fn check_file_from_store(
178 &self,
179 entry: DirEntry,
180 db: &crate::D7DB,
181 ) -> Result<Option<(String, BundleEntry)>> {
182 let (filebase, _extension) = entry
183 .file_name()
184 .to_str()
185 .unwrap()
186 .rsplit_once('.')
187 .unwrap();
188 let res = if filebase.starts_with("dtn") {
189 let bid = filebase.replace('_', "/").replacen("dtn", "dtn:/", 1);
190 let is_in_db = db.exists(&bid);
191 debug!("{} in db: {}", entry.path().display(), is_in_db);
192 if !is_in_db {
193 let buf = std::fs::read(entry.path())?;
194 let bundle_size = buf.len();
195
196 let bndl: Bundle = buf.try_into()?;
197 let mut be = BundleEntry::from(&bndl);
198 be.size = bundle_size as u64;
199 info!("adding {} to db", bndl.id());
200 Some((bndl.id(), be))
201 } else {
202 debug!("{} already in store", &bid);
203 None
204 }
205 } else {
206 None
207 };
208 Ok(res)
209 }
210 pub fn sync_to_db(&self, db: &crate::D7DB) -> Result<()> {
211 info!("syncing fs to db");
212 let mut bes = Vec::new();
213 for entry in WalkDir::new(&self.base)
214 .into_iter()
215 .filter_map(|e| e.ok())
216 .filter(|f| {
217 f.file_name()
218 .to_str()
219 .unwrap_or_default()
220 .ends_with(".bundle")
221 })
222 {
223 let file_path = entry.path().to_string_lossy().to_string();
224 if let Ok(Some((bid, be))) = self.check_file_from_store(entry, db) {
225 bes.push((bid, be, Some(file_path)));
226 }
227 }
228 db.insert_bulk(&bes)?;
229 Ok(())
230 }
231 pub fn import_hex(&self, hexstr: &str) -> Result<(Bundle, u64, String)> {
232 let mut bndl: Bundle = bp7::helpers::unhexify(hexstr)?.try_into()?;
233
234 let (bundle_size, path) = self.save_bundle(&mut bndl)?;
235 Ok((bndl, bundle_size, path))
236 }
237
238 pub fn import_vec(&self, buf: Vec<u8>) -> Result<(Bundle, u64, String)> {
239 let mut bndl: Bundle = buf.try_into()?;
240
241 let (bundle_size, path) = self.save_bundle(&mut bndl)?;
242 Ok((bndl, bundle_size, path))
243 }
244}