1pub 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 fn compression(&self) -> Compression;
50 async fn exists(&self, xyz: &Xyz) -> bool;
54 async fn put_tile(&self, xyz: &Xyz, data: Vec<u8>) -> Result<(), TileStoreError>;
56 async fn put_tile_mut(&mut self, xyz: &Xyz, data: Vec<u8>) -> Result<(), TileStoreError> {
58 self.put_tile(xyz, data).await
60 }
61 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()) .await;
67 }
68 Ok(())
69 }
70 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 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 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 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}