aboutsummaryrefslogtreecommitdiff
path: root/src/storage.rs
blob: 51a418eedcfc6d7a0bd8183ede4fad33346f44eb (plain)
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use color_eyre::{
    eyre::{bail, WrapErr},
    Result,
};
use rusqlite::{params, Connection};
use std::{
    fs,
    io::ErrorKind,
    path::{Path, PathBuf},
};

pub trait Storage {
    fn prepare(&mut self) -> Result<()>;
    fn prepare_zoom(&mut self, zoom: u32) -> Result<()>;
    fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()>;
    fn finish(&mut self) -> Result<()>;
}

#[derive(Debug)]
pub struct Folder {
    base_dir: PathBuf,
}

impl Folder {
    pub fn new(base_dir: PathBuf) -> Self {
        Folder { base_dir }
    }
}

impl Storage for Folder {
    fn prepare(&mut self) -> Result<()> {
        let path = &self.base_dir;
        let metadata = fs::metadata(path);
        match metadata {
            Err(e) if e.kind() == ErrorKind::NotFound => {
                let parent = path.parent().unwrap_or_else(|| Path::new("/"));
                fs::create_dir(path)
                    .context(format!("Could not create output directory at {parent:?}"))?
            }
            Err(e) => Err(e).context("Error while checking output directory")?,
            Ok(m) if m.is_dir() => (),
            Ok(_) => bail!("Output directory is not a directory"),
        }
        Ok(())
    }

    fn prepare_zoom(&mut self, zoom: u32) -> Result<()> {
        let target = [&self.base_dir, &zoom.to_string().into()]
            .iter()
            .collect::<PathBuf>();
        fs::create_dir(target)?;
        Ok(())
    }

    fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()> {
        let folder = self.base_dir.join(zoom.to_string()).join(x.to_string());
        let metadata = folder.metadata();
        match metadata {
            Err(_) => fs::create_dir(&folder)?,
            Ok(m) if !m.is_dir() => bail!("Output path is not a directory"),
            _ => {}
        }
        let file = folder.join(format!("{y}.png"));
        fs::write(file, data)?;
        Ok(())
    }

    fn finish(&mut self) -> Result<()> {
        Ok(())
    }
}

#[derive(Debug)]
pub struct Sqlite {
    connection: Connection,
}

impl Sqlite {
    pub fn connect<P: AsRef<Path>>(file: P) -> Result<Self> {
        let path = file.as_ref();
        if fs::metadata(path).is_ok() {
            bail!("Path {path:?} already exists, refusing to open")
        }
        let connection = Connection::open(path)?;
        Ok(Sqlite { connection })
    }
}

impl Storage for Sqlite {
    fn prepare(&mut self) -> Result<()> {
        self.connection.execute(
            "CREATE TABLE tiles (
            zoom INTEGER,
            x INTEGER,
            y INTEGER,
            data BLOB,
            PRIMARY KEY (zoom, x, y)
        );",
            (),
        )?;
        self.connection.execute("BEGIN;", ())?;
        Ok(())
    }

    fn prepare_zoom(&mut self, _zoom: u32) -> Result<()> {
        Ok(())
    }

    fn store(&mut self, zoom: u32, x: u64, y: u64, data: &[u8]) -> Result<()> {
        self.connection.execute(
            "INSERT INTO tiles (zoom, x, y, data) VALUES (?, ?, ?, ?)",
            params![zoom, x, y, data],
        )?;
        Ok(())
    }

    fn finish(&mut self) -> Result<()> {
        self.connection.execute("COMMIT;", ())?;
        Ok(())
    }
}