Skip to content

Instantly share code, notes, and snippets.

@lo48576
Last active December 30, 2017 12:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lo48576/a5a7662670897b1cf4f7558a43f5d80e to your computer and use it in GitHub Desktop.
Save lo48576/a5a7662670897b1cf4f7558a43f5d80e to your computer and use it in GitHub Desktop.
ファイル遅延ロード(ちょっと設計が微妙なので採用見送り、これは供養兼バックアップ)
//! 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