dias 0.2.0

Minimal cross-platform support for common platform specific things, intended for small games for web plus desktopy platforms.
Documentation
use super::super::OuterDirectoryError;
use super::binary_values::{BinaryStorageReader, BinaryStorageWriter};
use super::text_values::{TextStorageReader, TextStorageWriter};
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::marker::PhantomData;
use web_sys::Storage as WebStorage;

static SEP: char = '/';

#[derive(Debug)]
pub enum WebStorageAvailabilityError {
    NoWindow,
    NoLocalStorage,
}

impl fmt::Display for WebStorageAvailabilityError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl Error for WebStorageAvailabilityError {}

fn exists(web_storage: &WebStorage, path: &str) -> std::io::Result<bool> {
    Ok(web_storage
        .get_item(path)
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "cannot get value"))?
        .is_some())
}

fn remove(web_storage: &WebStorage, path: &str) -> std::io::Result<()> {
    web_storage
        .remove_item(path)
        .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "cannot remove value"))
}

pub struct ReadOnly;
pub struct ReadWrite;

pub struct File<R> {
    _phantom: PhantomData<R>,
    web_storage: WebStorage,
    path: String,
}

impl<R> File<R> {
    fn new(parent_path: String, name: Cow<'static, str>, web_storage: WebStorage) -> Self {
        let mut path = parent_path;
        path.push_str(name.as_ref());
        Self {
            _phantom: PhantomData,
            path,
            web_storage,
        }
    }
}

impl<R> super::super::File for File<R> {
    type ReadText = TextStorageReader;
    type ReadBinary = BinaryStorageReader;

    fn exists(&self) -> std::io::Result<bool> {
        exists(&self.web_storage, &self.path)
    }

    fn read_text(&self) -> std::io::Result<Self::ReadText> {
        TextStorageReader::new(&self.web_storage, &self.path)
    }

    fn read_binary(&self) -> std::io::Result<Self::ReadBinary> {
        BinaryStorageReader::new(&self.web_storage, &self.path)
    }
}

impl super::super::WritableFile for File<ReadWrite> {
    type WriteText = TextStorageWriter;
    type WriteBinary = BinaryStorageWriter;

    fn remove(&mut self) -> std::io::Result<()> {
        remove(&self.web_storage, &self.path)
    }

    fn write_text(&mut self) -> std::io::Result<Self::WriteText> {
        TextStorageWriter::new(&self.web_storage, &self.path)
    }

    fn write_binary(&mut self) -> std::io::Result<Self::WriteBinary> {
        BinaryStorageWriter::new(&self.web_storage, &self.path)
    }
}

pub struct Dir<R> {
    _phantom: PhantomData<R>,
    web_storage: WebStorage,
    path: String,
}

impl<R> Dir<R> {
    fn new(parent_path: String, name: Cow<'static, str>, web_storage: WebStorage) -> Self {
        let mut path = parent_path;
        path.push_str(name.as_ref());
        path.push(SEP);
        Dir {
            _phantom: PhantomData,
            path,
            web_storage,
        }
    }
}

impl<R> super::super::Dir for Dir<R> {
    type File = File<R>;

    fn file(&self, name: Cow<'static, str>) -> Self::File {
        File::new(self.path.clone(), name, self.web_storage.clone())
    }
}

impl<R> super::super::ParentDir for Dir<R> {
    type LeafDir = Dir<R>;

    fn subdir(&self, name: Cow<'static, str>) -> Self {
        Self::new(self.path.clone(), name, self.web_storage.clone())
    }

    fn into_leaf(self) -> Self::LeafDir {
        self
    }
}

impl super::super::WritableDir for Dir<ReadWrite> {
    type WritableFile = File<ReadWrite>;

    fn writable_file(&mut self, name: Cow<'static, str>) -> Self::WritableFile {
        File::new(self.path.clone(), name, self.web_storage.clone())
    }
}

impl super::super::WritableParentDir for Dir<ReadWrite> {
    type WritableLeafDir = Dir<ReadWrite>;

    fn writable_subdir(&mut self, name: Cow<'static, str>) -> Self {
        Self::new(self.path.clone(), name, self.web_storage.clone())
    }

    fn into_writable_leaf(self) -> Self::WritableLeafDir {
        self
    }
}

pub struct Storage {
    web_storage: WebStorage,
}

impl Storage {
    pub fn new() -> Result<Self, WebStorageAvailabilityError> {
        Ok(Self {
            web_storage: web_sys::window()
                .ok_or(WebStorageAvailabilityError::NoWindow)?
                .local_storage()
                .map_err(|_| WebStorageAvailabilityError::NoLocalStorage)?
                .ok_or(WebStorageAvailabilityError::NoLocalStorage)?,
        })
    }
}

impl super::super::Storage for Storage {
    type Dir = Dir<ReadOnly>;
    type WritableDir = Dir<ReadWrite>;

    fn data(&self) -> Result<Self::Dir, OuterDirectoryError> {
        Ok(Dir::new(
            "".to_string(),
            "data".into(),
            self.web_storage.clone(),
        ))
    }

    fn config(&self) -> Result<Self::Dir, OuterDirectoryError> {
        Ok(Dir::new(
            "".to_string(),
            "config".into(),
            self.web_storage.clone(),
        ))
    }

    fn cache(&self) -> Result<Self::Dir, OuterDirectoryError> {
        Ok(Dir::new(
            "".to_string(),
            "cache".into(),
            self.web_storage.clone(),
        ))
    }

    fn writable_data(&mut self) -> Result<Self::WritableDir, OuterDirectoryError> {
        Ok(Dir::new(
            "".to_string(),
            "data".into(),
            self.web_storage.clone(),
        ))
    }

    fn writable_config(&mut self) -> Result<Self::WritableDir, OuterDirectoryError> {
        Ok(Dir::new(
            "".to_string(),
            "config".into(),
            self.web_storage.clone(),
        ))
    }

    fn writable_cache(&mut self) -> Result<Self::WritableDir, OuterDirectoryError> {
        Ok(Dir::new(
            "".to_string(),
            "cache".into(),
            self.web_storage.clone(),
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::super::super::generic::tests as generic_tests;
    use super::*;
    use wasm_bindgen_test::wasm_bindgen_test;

    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);

    fn make_storage() -> Storage {
        Storage::new().unwrap()
    }

    #[wasm_bindgen_test]
    fn text_file() {
        generic_tests::text_file(make_storage());
    }

    #[wasm_bindgen_test]
    fn binary_file() {
        generic_tests::binary_file(make_storage());
    }

    #[wasm_bindgen_test]
    fn file_uniqueness() {
        generic_tests::file_uniqueness(make_storage());
    }
}