d7sneakers/
fs.rs

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        //info!("filename {}", filename);
108        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            //let filename = entry.file_name().to_str()?;
129            //if filename == format!("{}.bundle", sanitize(bid)) {
130            return Some(entry.into_path());
131            //}
132        }
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}