1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::fs::{create_dir, File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::path::PathBuf;

use appendix::Index;
use bytehash::ByteHash;

use crate::backend::{Backend, PutResult};

/// A backend that stores its data in an `appendix` index, and a flat file
pub struct DiskBackend<H: ByteHash> {
    index: Index<H::Digest, u64>,
    data: File,
    data_path: PathBuf,
    data_offset: u64,
}

impl<H: ByteHash> DiskBackend<H> {
    /// Create a new DiskBackend at given path, creates a new directory if neccesary
    pub fn new<P: Into<PathBuf>>(path: P) -> io::Result<Self> {
        let dir = path.into();
        if !dir.exists() {
            create_dir(&dir)?;
        }

        let index_dir = dir.join("index");
        if !index_dir.exists() {
            create_dir(&index_dir)?;
        }

        let index = Index::new(&index_dir)?;
        let data_path = dir.join("data");

        let mut data = OpenOptions::new()
            .create(true)
            .write(true)
            .open(&data_path)?;

        let data_offset = data.metadata()?.len();
        data.seek(SeekFrom::End(0))?;

        Ok(DiskBackend {
            index,
            data_path,
            data,
            data_offset,
        })
    }
}

impl<H: ByteHash> Backend<H> for DiskBackend<H> {
    fn get<'a>(&'a self, hash: &H::Digest) -> io::Result<Box<dyn Read + 'a>> {
        match self.index.get(hash)? {
            Some(offset) => {
                let mut file = File::open(&self.data_path)?;
                file.seek(SeekFrom::Start(*offset))?;
                Ok(Box::new(file))
            }
            None => {
                Err(io::Error::new(io::ErrorKind::NotFound, "Data not found"))
            }
        }
    }

    fn put(
        &mut self,
        hash: H::Digest,
        bytes: Vec<u8>,
    ) -> io::Result<PutResult> {
        if self.index.insert(hash, self.data_offset)? {
            // value already present
            Ok(PutResult::AlreadyThere)
        } else {
            self.data.write_all(&bytes)?;
            self.data_offset += bytes.len() as u64;
            Ok(PutResult::Ok)
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        self.data.flush()?;
        self.index.flush()
    }

    fn size(&self) -> usize {
        self.index.on_disk_size() + self.data_offset as usize
    }
}