Last active
December 30, 2017 12:23
-
-
Save lo48576/a5a7662670897b1cf4f7558a43f5d80e to your computer and use it in GitHub Desktop.
ファイル遅延ロード(ちょっと設計が微妙なので採用見送り、これは供養兼バックアップ)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Lazily loadable file type and related stuff. | |
// This code is dual licensed under MIT/Apache-2. | |
// lazy-init = "^0.2" | |
extern crate lazy_init; | |
use std::borrow::Borrow; | |
use std::cmp; | |
use std::fmt; | |
use std::fs::File; | |
use std::hash; | |
use std::io; | |
use std::path::{Path, PathBuf}; | |
use std::sync::Arc; | |
/// A result type for lazy file operation. | |
pub type Result<T> = ::std::result::Result<T, DelayedLoadError>; | |
/// The error type for lazy file load by `LazyFile`. | |
#[derive(Debug, Clone)] | |
pub struct DelayedLoadError { | |
/// Inner I/O error. | |
inner: Arc<io::Error>, | |
} | |
impl DelayedLoadError { | |
/// Creates a new `DelayedLoadError`. | |
pub fn new(inner: Arc<io::Error>) -> Self { | |
Self { inner } | |
} | |
/// Consumes the error, returning `Arc` of its inner error. | |
pub fn into_inner_arc(self) -> Arc<io::Error> { | |
self.inner | |
} | |
} | |
impl From<io::Error> for DelayedLoadError { | |
fn from(e: io::Error) -> Self { | |
Self::new(Arc::new(e)) | |
} | |
} | |
impl From<Arc<io::Error>> for DelayedLoadError { | |
fn from(e: Arc<io::Error>) -> Self { | |
Self::new(e) | |
} | |
} | |
/// A trait for types which can be loaded from a file. | |
pub trait FileContent: Sized + Sync { | |
/// Loads a content from the given file. | |
fn from_file(file: File) -> io::Result<Self>; | |
} | |
impl FileContent for Vec<u8> { | |
fn from_file(mut file: File) -> io::Result<Self> { | |
use std::io::Read; | |
let size = file.metadata().map(|m| m.len() as usize).unwrap_or(0); | |
let mut vec = Vec::with_capacity(size); | |
file.read_to_end(&mut vec)?; | |
Ok(vec) | |
} | |
} | |
impl FileContent for String { | |
fn from_file(mut file: File) -> io::Result<Self> { | |
use std::io::Read; | |
let size = file.metadata().map(|m| m.len() as usize).unwrap_or(0); | |
let mut buf = String::with_capacity(size); | |
file.read_to_string(&mut buf)?; | |
Ok(buf) | |
} | |
} | |
/// Lazily loadable file handle (and optionally its content). | |
#[derive(Clone)] | |
pub struct LazyFile<C: FileContent> { | |
/// File path. | |
path: PathBuf, | |
/// File content (if loaded). | |
content: Arc<lazy_init::Lazy<Result<C>>>, | |
} | |
impl<C: FileContent> LazyFile<C> { | |
/// Creates a new `LazyFile`. | |
pub fn new<P: Into<PathBuf>>(path: P) -> Self { | |
Self { | |
path: path.into(), | |
content: Default::default(), | |
} | |
} | |
/// Returns the file path. | |
pub fn path(&self) -> &Path { | |
&self.path | |
} | |
/// Returns `true` if the file has already been loaded, `false` if not loaded or currently | |
/// loading. | |
// FIXME: Which is better, `is_loaded` or `is_prepared`? | |
pub fn is_loaded(&self) -> bool { | |
self.content.get().is_some() | |
} | |
/// Returns file content if already loaded. | |
/// | |
/// This does not block. | |
pub fn try_load_content_borrow<T>(&self) -> Result<Option<&T>> | |
where | |
C: Borrow<T>, | |
{ | |
opt_res_to_res_opt(self.try_load_content_opt_res()) | |
} | |
/// Returns file content if already loaded. | |
/// | |
/// This does not block. | |
pub fn try_load_content_asref<T>(&self) -> Result<Option<&T>> | |
where | |
C: AsRef<T>, | |
{ | |
self.try_load_content_borrow() | |
.map(|opt| opt.map(AsRef::as_ref)) | |
} | |
/// Loads the file if necessary, and returns file content. | |
/// | |
/// Note that this will block. | |
pub fn load_content_borrow<T>(&self) -> Result<&T> | |
where | |
C: Borrow<T>, | |
{ | |
let res = self.content.get_or_create(|| { | |
let file = File::open(&self.path)?; | |
let content = C::from_file(file)?; | |
Ok(content) | |
}); | |
ref_res_to_res_borrow(res) | |
} | |
/// Loads the file if necessary, and returns file content. | |
/// | |
/// Note that this will block. | |
pub fn load_content_asref<T>(&self) -> Result<&T> | |
where | |
C: AsRef<T>, | |
{ | |
self.load_content_borrow().map(AsRef::as_ref) | |
} | |
/// Loads the file if necessary, and returns optional result. | |
fn try_load_content_opt_res<T>(&self) -> Option<Result<&T>> | |
where | |
C: Borrow<T>, | |
{ | |
self.content.get().map(ref_res_to_res_borrow) | |
} | |
} | |
impl<C> fmt::Debug for LazyFile<C> | |
where | |
C: FileContent + fmt::Debug, | |
{ | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
let content_opt = self.try_load_content_borrow(); | |
f.debug_struct("LazyFile") | |
.field("path", &self.path) | |
.field("content", &content_opt) | |
.finish() | |
} | |
} | |
impl<C: FileContent> PartialOrd for LazyFile<C> { | |
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { | |
Some(self.cmp(other)) | |
} | |
} | |
impl<C: FileContent> Ord for LazyFile<C> { | |
fn cmp(&self, other: &Self) -> cmp::Ordering { | |
self.path.cmp(&other.path) | |
} | |
} | |
impl<C: FileContent> PartialEq for LazyFile<C> { | |
fn eq(&self, other: &Self) -> bool { | |
self.path.eq(&other.path) | |
} | |
} | |
impl<C: FileContent> hash::Hash for LazyFile<C> { | |
fn hash<H: hash::Hasher>(&self, state: &mut H) { | |
self.path.hash(state) | |
} | |
} | |
impl<C: FileContent> Eq for LazyFile<C> {} | |
/// Converts `Result<Option<T>, E>` into `Option<Result<T, E>>`. | |
fn opt_res_to_res_opt<T, E>( | |
opt_res: Option<::std::result::Result<T, E>>, | |
) -> ::std::result::Result<Option<T>, E> { | |
match opt_res { | |
Some(Ok(v)) => Ok(Some(v)), | |
Some(Err(e)) => Err(e), | |
None => Ok(None), | |
} | |
} | |
/// Converts `&Result<T, E>` into `Result<&U, E>` (where `T: AsRef<U>`). | |
/// | |
/// Error will be cloned. | |
fn ref_res_to_res_borrow<T, U, E>( | |
res_ref: &::std::result::Result<T, E>, | |
) -> ::std::result::Result<&U, E> | |
where | |
T: Borrow<U>, | |
U: ?Sized, | |
E: Clone, | |
{ | |
match *res_ref { | |
Ok(ref v) => Ok(v.borrow()), | |
Err(ref e) => Err(e.clone()), | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment