pmtiles 0.22.0

Implementation of the PMTiles v3 spec with multiple sync and async backends.
Documentation
use std::io;
use std::path::Path;

use bytes::{Buf, Bytes};
use fmmap::tokio::{AsyncMmapFile, AsyncMmapFileExt as _, AsyncOptions};

use crate::{AsyncBackend, AsyncPmTilesReader, DirectoryCache, NoCache, PmtError, PmtResult};

impl AsyncPmTilesReader<MmapBackend, NoCache> {
    /// Creates a new `PMTiles` reader from a file path using the async mmap backend.
    ///
    /// Fails if `path` does not exist or is an invalid archive.
    ///
    /// # Errors
    ///
    /// This function will return an error if the
    /// - file cannot be opened for memory mapping,
    /// - backend fails to read the header/root directory or
    /// - root directory is malformed
    pub async fn new_with_path<P: AsRef<Path>>(path: P) -> PmtResult<Self> {
        Self::new_with_cached_path(NoCache, path).await
    }
}

impl<C: DirectoryCache + Sync + Send> AsyncPmTilesReader<MmapBackend, C> {
    /// Creates a new cached `PMTiles` reader from a file path using the async mmap backend.
    ///
    /// Fails if `path` does not exist or is an invalid archive.
    ///
    /// # Errors
    ///
    /// This function will return an error if the
    /// - file cannot be opened for memory mapping,
    /// - backend fails to read the header/root directory or
    /// - root directory is malformed
    pub async fn new_with_cached_path<P: AsRef<Path>>(cache: C, path: P) -> PmtResult<Self> {
        let backend = MmapBackend::try_from(path).await?;

        Self::try_from_cached_source(backend, cache).await
    }
}

/// Backend for reading `PMTiles` from a memory-mapped file.
pub struct MmapBackend {
    file: AsyncMmapFile,
}

impl MmapBackend {
    /// Creates a new memory-mapped file backend.
    ///
    /// # Errors
    ///
    /// This function will return an error if the file cannot be opened for memory mapping.
    pub async fn try_from<P: AsRef<Path>>(p: P) -> PmtResult<Self> {
        Ok(Self {
            file: AsyncMmapFile::open_with_options(p, AsyncOptions::new().read(true))
                .await
                .map_err(|_| PmtError::UnableToOpenMmapFile)?,
        })
    }
}

impl From<fmmap::error::Error> for PmtError {
    fn from(_: fmmap::error::Error) -> Self {
        Self::Reading(io::Error::from(io::ErrorKind::UnexpectedEof))
    }
}

impl AsyncBackend for MmapBackend {
    async fn read_exact(&self, offset: usize, length: usize) -> PmtResult<Bytes> {
        if self.file.len() >= offset + length {
            Ok(self.file.reader(offset)?.copy_to_bytes(length))
        } else {
            Err(PmtError::Reading(io::Error::from(
                io::ErrorKind::UnexpectedEof,
            )))
        }
    }

    async fn read(&self, offset: usize, length: usize) -> PmtResult<Bytes> {
        let reader = self.file.reader(offset)?;

        let read_length = length.min(reader.len());

        Ok(self.file.reader(offset)?.copy_to_bytes(read_length))
    }
}