bbox_tile_server/store/
mod.rs

1//! Tile storage implementations.
2pub mod files;
3pub mod mbtiles;
4pub mod pmtiles;
5pub mod s3;
6pub mod s3putfiles;
7
8use crate::config::{StoreCompressionCfg, TileStoreCfg};
9use crate::mbtiles_ds::Error as MbtilesDsError;
10use crate::store::files::FileStore;
11use crate::store::mbtiles::MbtilesStore;
12use crate::store::pmtiles::{PmtilesStoreReader, PmtilesStoreWriter};
13use crate::store::s3::{S3Store, S3StoreError};
14use async_trait::async_trait;
15use bbox_core::config::error_exit;
16use bbox_core::{Compression, Format, TileResponse};
17use dyn_clone::{clone_trait_object, DynClone};
18use log::warn;
19use martin_mbtiles::{MbtError, Metadata};
20use std::path::{Path, PathBuf};
21use tile_grid::Xyz;
22
23#[derive(thiserror::Error, Debug)]
24pub enum TileStoreError {
25    #[error("{0}: {1}")]
26    FileError(PathBuf, #[source] std::io::Error),
27    #[error("Missing argument: {0}")]
28    ArgMissing(String),
29    #[error("Operation not supported on readonly data store")]
30    ReadOnly,
31    #[error(transparent)]
32    IoError(#[from] std::io::Error),
33    #[error(transparent)]
34    DbError(#[from] sqlx::Error),
35    #[error(transparent)]
36    S3StoreError(#[from] S3StoreError),
37    #[error(transparent)]
38    MbtilesDsError(#[from] MbtilesDsError),
39    #[error(transparent)]
40    MbtError(#[from] MbtError),
41    #[error(transparent)]
42    PmtilesError(#[from] ::pmtiles::error::Error),
43}
44
45#[async_trait]
46pub trait TileWriter: DynClone + Send + Sync {
47    /// Tile storage compression
48    // TODO: move into common store trait?
49    fn compression(&self) -> Compression;
50    /// Check for existing tile
51    /// Must not be implemented for cases where generating a tile is less expensive than checking
52    // Method should probably return date of last change if known
53    async fn exists(&self, xyz: &Xyz) -> bool;
54    /// Write tile into store
55    async fn put_tile(&self, xyz: &Xyz, data: Vec<u8>) -> Result<(), TileStoreError>;
56    /// Write tile into store requiring &mut self
57    async fn put_tile_mut(&mut self, xyz: &Xyz, data: Vec<u8>) -> Result<(), TileStoreError> {
58        // Most implementations support writing without &mut self
59        self.put_tile(xyz, data).await
60    }
61    /// Write multiple tiles into store
62    async fn put_tiles(&mut self, tiles: &[(u8, u32, u32, Vec<u8>)]) -> Result<(), TileStoreError> {
63        for (z, x, y, tile) in tiles {
64            let _ = self
65                .put_tile_mut(&Xyz::new(*x as u64, *y as u64, *z), tile.to_vec()) //FIXME: avoid clone!
66                .await;
67        }
68        Ok(())
69    }
70    /// Finalize writing
71    fn finalize(&mut self) -> Result<(), TileStoreError> {
72        Ok(())
73    }
74}
75
76clone_trait_object!(TileWriter);
77
78#[async_trait]
79pub trait TileReader: DynClone + Send + Sync {
80    /// Lookup tile and return Read stream, if found
81    async fn get_tile(&self, xyz: &Xyz) -> Result<Option<TileResponse>, TileStoreError>;
82}
83
84clone_trait_object!(TileReader);
85
86#[derive(Clone, Debug)]
87pub enum CacheLayout {
88    Zxy,
89}
90
91impl CacheLayout {
92    pub fn path(&self, base_dir: &Path, xyz: &Xyz, format: &Format) -> PathBuf {
93        let mut path = base_dir.to_path_buf();
94        match self {
95            CacheLayout::Zxy => {
96                // "{z}/{x}/{y}.{format}"
97                path.push(xyz.z.to_string());
98                path.push(xyz.x.to_string());
99                path.push(xyz.y.to_string());
100                path.set_extension(format.file_suffix());
101            }
102        }
103        path
104    }
105    pub fn path_string(&self, base_dir: &Path, xyz: &Xyz, format: &Format) -> String {
106        self.path(base_dir, xyz, format)
107            .into_os_string()
108            .to_string_lossy()
109            .to_string()
110    }
111}
112
113#[derive(Clone)]
114pub struct NoStore;
115
116#[async_trait]
117impl TileWriter for NoStore {
118    fn compression(&self) -> Compression {
119        Compression::None
120    }
121    async fn exists(&self, _xyz: &Xyz) -> bool {
122        false
123    }
124    async fn put_tile(&self, _xyz: &Xyz, _data: Vec<u8>) -> Result<(), TileStoreError> {
125        Ok(())
126    }
127}
128
129#[async_trait]
130impl TileReader for NoStore {
131    async fn get_tile(&self, _xyz: &Xyz) -> Result<Option<TileResponse>, TileStoreError> {
132        Ok(None)
133    }
134}
135
136pub async fn store_reader_from_config(
137    config: &TileStoreCfg,
138    compression: &Option<StoreCompressionCfg>,
139    tileset_name: &str,
140    format: &Format,
141) -> Box<dyn TileReader> {
142    match &config {
143        TileStoreCfg::Files(cfg) => Box::new(FileStore::from_config(
144            cfg,
145            compression,
146            tileset_name,
147            format,
148        )),
149        TileStoreCfg::S3(cfg) => {
150            Box::new(S3Store::from_config(cfg, compression, format).unwrap_or_else(error_exit))
151        }
152        TileStoreCfg::Mbtiles(cfg) => Box::new(
153            MbtilesStore::from_config(cfg)
154                .await
155                .unwrap_or_else(error_exit),
156        ),
157        TileStoreCfg::Pmtiles(cfg) => {
158            if let Ok(reader) = PmtilesStoreReader::from_config(cfg).await {
159                Box::new(reader)
160            } else {
161                // We continue, because for seeding into a new file, the reader cannot be created and is not needed
162                warn!(
163                    "Couldn't open PmtilesStoreReader {}",
164                    cfg.abs_path().display()
165                );
166                Box::new(NoStore)
167            }
168        }
169        TileStoreCfg::NoStore => Box::new(NoStore),
170    }
171}
172
173pub async fn store_writer_from_config(
174    config: &TileStoreCfg,
175    compression: &Option<StoreCompressionCfg>,
176    tileset_name: &str,
177    format: &Format,
178    metadata: Metadata,
179) -> Box<dyn TileWriter> {
180    match &config {
181        TileStoreCfg::Files(cfg) => Box::new(FileStore::from_config(
182            cfg,
183            compression,
184            tileset_name,
185            format,
186        )),
187        TileStoreCfg::S3(cfg) => {
188            Box::new(S3Store::from_config(cfg, compression, format).unwrap_or_else(error_exit))
189        }
190        TileStoreCfg::Mbtiles(cfg) => Box::new(
191            MbtilesStore::from_config_writable(cfg, metadata)
192                .await
193                .unwrap_or_else(error_exit),
194        ),
195        TileStoreCfg::Pmtiles(cfg) => {
196            Box::new(PmtilesStoreWriter::from_config(cfg, metadata, format))
197        }
198        TileStoreCfg::NoStore => Box::new(NoStore),
199    }
200}