2016-10-01 15:35:06 +00:00
|
|
|
//
|
|
|
|
// imag - the personal information management suite for the commandline
|
|
|
|
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
|
|
|
|
//
|
|
|
|
// This library is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
|
|
// License as published by the Free Software Foundation; version
|
|
|
|
// 2.1 of the License.
|
|
|
|
//
|
|
|
|
// This library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
|
|
// License along with this library; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
//
|
|
|
|
|
2016-01-13 20:47:23 +00:00
|
|
|
use std::collections::HashMap;
|
2016-01-13 20:51:40 +00:00
|
|
|
use std::ops::Drop;
|
2016-01-12 17:52:03 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::result::Result as RResult;
|
2016-01-13 10:53:56 +00:00
|
|
|
use std::sync::Arc;
|
2016-01-17 15:23:35 +00:00
|
|
|
use std::sync::RwLock;
|
2016-07-21 13:58:34 +00:00
|
|
|
use std::io::Read;
|
2016-02-12 21:07:15 +00:00
|
|
|
use std::convert::From;
|
2016-02-12 21:02:33 +00:00
|
|
|
use std::convert::Into;
|
2016-02-15 21:15:32 +00:00
|
|
|
use std::ops::Deref;
|
|
|
|
use std::ops::DerefMut;
|
2016-03-25 12:29:20 +00:00
|
|
|
use std::fmt::Formatter;
|
|
|
|
use std::fmt::Debug;
|
|
|
|
use std::fmt::Error as FMTError;
|
2016-01-16 18:32:12 +00:00
|
|
|
|
2016-11-15 20:30:39 +00:00
|
|
|
use toml::Value;
|
2016-01-23 16:30:01 +00:00
|
|
|
use regex::Regex;
|
2016-03-13 13:32:48 +00:00
|
|
|
use glob::glob;
|
2016-04-16 15:03:41 +00:00
|
|
|
use walkdir::WalkDir;
|
|
|
|
use walkdir::Iter as WalkDirIter;
|
2016-01-12 17:52:03 +00:00
|
|
|
|
2016-05-14 17:05:54 +00:00
|
|
|
use error::{StoreError as SE, StoreErrorKind as SEK};
|
2016-06-27 16:16:43 +00:00
|
|
|
use error::MapErrInto;
|
2016-05-12 15:27:41 +00:00
|
|
|
use storeid::{IntoStoreId, StoreId, StoreIdIterator};
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
use file_abstraction::FileAbstractionInstance;
|
2016-11-14 13:09:22 +00:00
|
|
|
use toml_ext::*;
|
2017-06-09 11:22:45 +00:00
|
|
|
// We re-export the following things so tests can use them
|
|
|
|
pub use file_abstraction::FileAbstraction;
|
|
|
|
pub use file_abstraction::FSFileAbstraction;
|
|
|
|
pub use file_abstraction::InMemoryFileAbstraction;
|
2016-01-12 17:52:03 +00:00
|
|
|
|
2016-05-27 08:11:51 +00:00
|
|
|
use libimagerror::into::IntoError;
|
2016-08-25 15:14:09 +00:00
|
|
|
use libimagerror::trace::trace_error;
|
2016-09-08 13:13:32 +00:00
|
|
|
use libimagutil::debug_result::*;
|
2016-05-27 08:11:51 +00:00
|
|
|
|
2016-05-12 15:27:41 +00:00
|
|
|
use self::glob_store_iter::*;
|
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// The Result Type returned by any interaction with the store that could fail
|
2016-05-14 17:05:54 +00:00
|
|
|
pub type Result<T> = RResult<T, SE>;
|
2016-01-12 17:52:03 +00:00
|
|
|
|
2016-01-17 14:09:10 +00:00
|
|
|
|
2016-03-25 12:29:20 +00:00
|
|
|
#[derive(Debug, PartialEq)]
|
2016-01-23 16:03:21 +00:00
|
|
|
enum StoreEntryStatus {
|
|
|
|
Present,
|
2016-01-23 15:15:34 +00:00
|
|
|
Borrowed
|
|
|
|
}
|
|
|
|
|
2016-01-23 16:03:21 +00:00
|
|
|
/// A store entry, depending on the option type it is either borrowed currently
|
|
|
|
/// or not.
|
2016-03-25 12:29:20 +00:00
|
|
|
#[derive(Debug)]
|
2016-01-23 16:03:21 +00:00
|
|
|
struct StoreEntry {
|
|
|
|
id: StoreId,
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
file: Box<FileAbstractionInstance>,
|
2016-01-23 16:03:21 +00:00
|
|
|
status: StoreEntryStatus,
|
2016-01-16 19:30:16 +00:00
|
|
|
}
|
|
|
|
|
2016-04-16 15:03:41 +00:00
|
|
|
pub enum StoreObject {
|
|
|
|
Id(StoreId),
|
|
|
|
Collection(PathBuf),
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Walk {
|
2016-08-22 10:07:50 +00:00
|
|
|
store_path: PathBuf,
|
2016-04-16 15:03:41 +00:00
|
|
|
dirwalker: WalkDirIter,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Walk {
|
|
|
|
|
|
|
|
fn new(mut store_path: PathBuf, mod_name: &str) -> Walk {
|
2016-08-22 10:07:50 +00:00
|
|
|
let pb = store_path.clone();
|
2016-04-16 15:03:41 +00:00
|
|
|
store_path.push(mod_name);
|
|
|
|
Walk {
|
2016-08-22 10:07:50 +00:00
|
|
|
store_path: pb,
|
2016-04-16 15:03:41 +00:00
|
|
|
dirwalker: WalkDir::new(store_path).into_iter(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ::std::ops::Deref for Walk {
|
|
|
|
type Target = WalkDirIter;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.dirwalker
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Iterator for Walk {
|
|
|
|
type Item = StoreObject;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
while let Some(something) = self.dirwalker.next() {
|
|
|
|
match something {
|
|
|
|
Ok(next) => if next.file_type().is_dir() {
|
|
|
|
return Some(StoreObject::Collection(next.path().to_path_buf()))
|
|
|
|
} else if next.file_type().is_file() {
|
2016-08-22 10:07:50 +00:00
|
|
|
let n = next.path().to_path_buf();
|
2016-08-25 15:14:09 +00:00
|
|
|
let sid = match StoreId::new(Some(self.store_path.clone()), n) {
|
|
|
|
Err(e) => {
|
|
|
|
trace_error(&e);
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Ok(o) => o,
|
|
|
|
};
|
2016-08-22 10:07:50 +00:00
|
|
|
return Some(StoreObject::Id(sid))
|
2016-04-16 15:03:41 +00:00
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
warn!("Error in Walker");
|
|
|
|
debug!("{:?}", e);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-16 19:30:16 +00:00
|
|
|
impl StoreEntry {
|
2016-01-24 16:28:52 +00:00
|
|
|
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
fn new(id: StoreId, backend: &Box<FileAbstraction>) -> Result<StoreEntry> {
|
2016-09-05 14:51:37 +00:00
|
|
|
let pb = try!(id.clone().into_pathbuf());
|
2017-06-04 17:30:13 +00:00
|
|
|
|
|
|
|
#[cfg(feature = "fs-lock")]
|
|
|
|
{
|
|
|
|
try!(open_file(pb.clone())
|
|
|
|
.and_then(|f| f.lock_exclusive().map_err_into(SEK::FileError))
|
|
|
|
.map_err_into(SEK::IoError));
|
|
|
|
}
|
|
|
|
|
2016-09-05 14:51:37 +00:00
|
|
|
Ok(StoreEntry {
|
|
|
|
id: id,
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
file: backend.new_instance(pb),
|
2016-01-24 16:28:52 +00:00
|
|
|
status: StoreEntryStatus::Present,
|
2016-09-05 14:51:37 +00:00
|
|
|
})
|
2016-01-24 16:28:52 +00:00
|
|
|
}
|
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// The entry is currently borrowed, meaning that some thread is currently
|
|
|
|
/// mutating it
|
2016-01-16 19:30:16 +00:00
|
|
|
fn is_borrowed(&self) -> bool {
|
2016-01-23 16:03:21 +00:00
|
|
|
self.status == StoreEntryStatus::Borrowed
|
2016-01-23 15:15:34 +00:00
|
|
|
}
|
|
|
|
|
2016-01-23 16:03:21 +00:00
|
|
|
fn get_entry(&mut self) -> Result<Entry> {
|
2016-10-13 12:38:33 +00:00
|
|
|
let id = &self.id.clone();
|
2016-01-23 16:03:21 +00:00
|
|
|
if !self.is_borrowed() {
|
2016-10-13 12:38:33 +00:00
|
|
|
self.file
|
|
|
|
.get_file_content()
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
.and_then(|content| Entry::from_str(id.clone(), &content))
|
2016-10-13 12:38:33 +00:00
|
|
|
.or_else(|err| if err.err_type() == SEK::FileNotFound {
|
|
|
|
Ok(Entry::new(id.clone()))
|
2016-01-23 16:03:21 +00:00
|
|
|
} else {
|
|
|
|
Err(err)
|
2016-10-13 12:38:33 +00:00
|
|
|
})
|
2016-01-23 15:15:34 +00:00
|
|
|
} else {
|
2016-05-14 17:05:54 +00:00
|
|
|
Err(SE::new(SEK::EntryAlreadyBorrowed, None))
|
2016-01-23 15:15:34 +00:00
|
|
|
}
|
2016-01-16 19:30:16 +00:00
|
|
|
}
|
2016-01-24 18:55:47 +00:00
|
|
|
|
|
|
|
fn write_entry(&mut self, entry: &Entry) -> Result<()> {
|
|
|
|
if self.is_borrowed() {
|
|
|
|
assert_eq!(self.id, entry.location);
|
2016-07-22 11:39:29 +00:00
|
|
|
self.file.write_file_content(entry.to_str().as_bytes())
|
|
|
|
.map_err_into(SEK::FileError)
|
|
|
|
.map(|_| ())
|
2016-03-21 18:45:09 +00:00
|
|
|
} else {
|
|
|
|
Ok(())
|
2016-01-24 18:55:47 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-16 19:30:16 +00:00
|
|
|
}
|
2016-01-16 18:52:06 +00:00
|
|
|
|
2017-06-04 17:30:13 +00:00
|
|
|
#[cfg(feature = "fs-lock")]
|
|
|
|
impl Drop for StoreEntry {
|
|
|
|
|
|
|
|
fn drop(self) {
|
|
|
|
self.get_entry()
|
|
|
|
.and_then(|entry| open_file(entry.get_location().clone()).map_err_into(SEK::IoError))
|
|
|
|
.and_then(|f| f.unlock().map_err_into(SEK::FileError))
|
|
|
|
.map_err_into(SEK::IoError)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// The Store itself, through this object one can interact with IMAG's entries
|
2016-01-16 18:04:15 +00:00
|
|
|
pub struct Store {
|
|
|
|
location: PathBuf,
|
2016-01-13 20:47:23 +00:00
|
|
|
|
2017-02-20 15:02:49 +00:00
|
|
|
///
|
|
|
|
/// Configuration object of the store
|
|
|
|
///
|
2016-03-05 17:16:05 +00:00
|
|
|
configuration: Option<Value>,
|
2016-03-05 17:08:31 +00:00
|
|
|
|
2017-02-20 15:03:03 +00:00
|
|
|
///
|
|
|
|
/// Internal Path->File cache map
|
|
|
|
///
|
|
|
|
/// Caches the files, so they remain flock()ed
|
|
|
|
///
|
|
|
|
/// Could be optimized for a threadsafe HashMap
|
|
|
|
///
|
2016-01-16 19:30:16 +00:00
|
|
|
entries: Arc<RwLock<HashMap<StoreId, StoreEntry>>>,
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
|
|
|
|
/// The backend to use
|
|
|
|
///
|
|
|
|
/// This provides the filesystem-operation functions (or pretends to)
|
|
|
|
backend: Box<FileAbstraction>,
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Store {
|
2016-01-17 15:04:31 +00:00
|
|
|
|
|
|
|
/// Create a new Store object
|
2017-02-20 15:03:03 +00:00
|
|
|
///
|
|
|
|
/// This opens a Store in `location` using the configuration from `store_config` (if absent, it
|
|
|
|
/// uses defaults).
|
|
|
|
///
|
|
|
|
/// If the configuration is not valid, this fails.
|
|
|
|
///
|
|
|
|
/// If the location does not exist, creating directories is by default denied and the operation
|
|
|
|
/// fails, if not configured otherwise.
|
|
|
|
/// An error is returned in this case.
|
|
|
|
///
|
|
|
|
/// If the path exists and is a file, the operation is aborted as well, an error is returned.
|
|
|
|
///
|
|
|
|
/// # Return values
|
|
|
|
///
|
|
|
|
/// - On success: Store object
|
|
|
|
/// - On Failure:
|
|
|
|
/// - ConfigurationError if config is faulty
|
|
|
|
/// - IoError(FileError(CreateStoreDirDenied())) if store location does not exist and creating
|
|
|
|
/// is denied
|
|
|
|
/// - StorePathCreate(_) if creating the store directory failed
|
|
|
|
/// - StorePathExists() if location exists but is a file
|
2016-03-05 17:16:05 +00:00
|
|
|
pub fn new(location: PathBuf, store_config: Option<Value>) -> Result<Store> {
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
let backend = Box::new(FSFileAbstraction::new());
|
|
|
|
Store::new_with_backend(location, store_config, backend)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a Store object as descripbed in `Store::new()` documentation, but with an alternative
|
|
|
|
/// backend implementation.
|
|
|
|
///
|
|
|
|
/// Do not use directly, only for testing purposes.
|
|
|
|
pub fn new_with_backend(location: PathBuf,
|
|
|
|
store_config: Option<Value>,
|
|
|
|
backend: Box<FileAbstraction>) -> Result<Store> {
|
2016-03-05 10:37:23 +00:00
|
|
|
use configuration::*;
|
|
|
|
|
|
|
|
debug!("Validating Store configuration");
|
2016-09-19 19:00:45 +00:00
|
|
|
let _ = try!(config_is_valid(&store_config).map_err_into(SEK::ConfigurationError));
|
2016-01-17 16:42:40 +00:00
|
|
|
|
2016-01-28 20:06:49 +00:00
|
|
|
debug!("Building new Store object");
|
2016-01-17 16:42:40 +00:00
|
|
|
if !location.exists() {
|
2016-07-16 20:37:07 +00:00
|
|
|
if !config_implicit_store_create_allowed(store_config.as_ref()) {
|
|
|
|
warn!("Implicitely creating store directory is denied");
|
|
|
|
warn!(" -> Either because configuration does not allow it");
|
|
|
|
warn!(" -> or because there is no configuration");
|
|
|
|
return Err(SEK::CreateStoreDirDenied.into_error())
|
|
|
|
.map_err_into(SEK::FileError)
|
|
|
|
.map_err_into(SEK::IoError);
|
|
|
|
}
|
|
|
|
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
try!(backend.create_dir_all(&location)
|
2016-09-08 13:13:32 +00:00
|
|
|
.map_err_into(SEK::StorePathCreate)
|
|
|
|
.map_dbg_err_str("Failed"));
|
2016-05-03 21:10:32 +00:00
|
|
|
} else if location.is_file() {
|
|
|
|
debug!("Store path exists as file");
|
2016-06-27 16:16:43 +00:00
|
|
|
return Err(SEK::StorePathExists.into_error());
|
2016-01-17 16:42:40 +00:00
|
|
|
}
|
|
|
|
|
2016-03-05 10:37:23 +00:00
|
|
|
let store = Store {
|
2016-05-26 16:40:58 +00:00
|
|
|
location: location.clone(),
|
2016-03-05 17:08:31 +00:00
|
|
|
configuration: store_config,
|
2016-01-17 15:04:31 +00:00
|
|
|
entries: Arc::new(RwLock::new(HashMap::new())),
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
backend: backend,
|
2016-03-05 10:37:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
debug!("Store building succeeded");
|
2016-07-15 22:15:10 +00:00
|
|
|
debug!("------------------------");
|
|
|
|
debug!("{:?}", store);
|
|
|
|
debug!("------------------------");
|
|
|
|
|
2016-03-05 10:37:23 +00:00
|
|
|
Ok(store)
|
2016-01-17 15:04:31 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 14:09:21 +00:00
|
|
|
/// Get the store configuration
|
|
|
|
pub fn config(&self) -> Option<&Value> {
|
|
|
|
self.configuration.as_ref()
|
|
|
|
}
|
|
|
|
|
2016-07-16 22:36:37 +00:00
|
|
|
/// Verify the store.
|
|
|
|
///
|
|
|
|
/// This function is not intended to be called by normal programs but only by `imag-store`.
|
|
|
|
#[cfg(feature = "verify")]
|
|
|
|
pub fn verify(&self) -> bool {
|
2017-02-10 16:04:39 +00:00
|
|
|
use libimagerror::trace::trace_error_dbg;
|
|
|
|
|
2016-07-16 22:36:37 +00:00
|
|
|
info!("Header | Content length | Path");
|
|
|
|
info!("-------+----------------+-----");
|
|
|
|
|
|
|
|
WalkDir::new(self.location.clone())
|
|
|
|
.into_iter()
|
2017-02-10 16:00:25 +00:00
|
|
|
.all(|res| match res {
|
|
|
|
Ok(dent) => {
|
|
|
|
if dent.file_type().is_file() {
|
|
|
|
match self.get(PathBuf::from(dent.path())) {
|
|
|
|
Ok(Some(fle)) => {
|
|
|
|
let p = fle.get_location();
|
|
|
|
let content_len = fle.get_content().len();
|
|
|
|
let header = if fle.get_header().verify().is_ok() {
|
|
|
|
"ok"
|
|
|
|
} else {
|
|
|
|
"broken"
|
|
|
|
};
|
2016-07-16 22:36:37 +00:00
|
|
|
|
2017-02-10 16:00:25 +00:00
|
|
|
info!("{: >6} | {: >14} | {:?}", header, content_len, p.deref());
|
|
|
|
true
|
|
|
|
},
|
|
|
|
|
|
|
|
Ok(None) => {
|
|
|
|
info!("{: >6} | {: >14} | {:?}", "?", "couldn't load", dent.path());
|
|
|
|
true
|
|
|
|
},
|
2016-07-16 22:36:37 +00:00
|
|
|
|
2017-02-10 16:00:25 +00:00
|
|
|
Err(e) => {
|
2017-02-10 16:04:39 +00:00
|
|
|
trace_error_dbg(&e);
|
|
|
|
if_cfg_panic!("Error verifying: {:?}", e);
|
2017-02-10 16:00:25 +00:00
|
|
|
debug!("{:?}", e);
|
|
|
|
false
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
info!("{: >6} | {: >14} | {:?}", "?", "<no file>", dent.path());
|
|
|
|
true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
Err(e) => {
|
2017-02-10 16:04:39 +00:00
|
|
|
trace_error_dbg(&e);
|
|
|
|
if_cfg_panic!("Error verifying: {:?}", e);
|
2017-02-10 16:00:25 +00:00
|
|
|
debug!("{:?}", e);
|
|
|
|
false
|
|
|
|
},
|
2016-07-16 22:36:37 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// Creates the Entry at the given location (inside the entry)
|
2017-02-20 15:03:11 +00:00
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// On success: FileLockEntry
|
|
|
|
///
|
|
|
|
/// On error:
|
|
|
|
/// - Errors StoreId::into_storeid() might return
|
|
|
|
/// - CreateCallError(LockPoisoned()) if the internal lock is poisened.
|
|
|
|
/// - CreateCallError(EntryAlreadyExists()) if the entry exists already.
|
|
|
|
///
|
2016-05-03 13:49:33 +00:00
|
|
|
pub fn create<'a, S: IntoStoreId>(&'a self, id: S) -> Result<FileLockEntry<'a>> {
|
2016-08-25 15:56:55 +00:00
|
|
|
let id = try!(id.into_storeid()).with_base(self.path().clone());
|
2016-02-16 19:42:51 +00:00
|
|
|
|
2017-06-04 14:59:23 +00:00
|
|
|
debug!("Creating id: '{}'", id);
|
|
|
|
|
2016-08-01 17:59:43 +00:00
|
|
|
{
|
|
|
|
let mut hsmap = match self.entries.write() {
|
|
|
|
Err(_) => return Err(SEK::LockPoisoned.into_error()).map_err_into(SEK::CreateCallError),
|
|
|
|
Ok(s) => s,
|
|
|
|
};
|
|
|
|
|
|
|
|
if hsmap.contains_key(&id) {
|
2017-06-04 14:59:23 +00:00
|
|
|
debug!("Cannot create, internal cache already contains: '{}'", id);
|
2016-08-01 17:59:43 +00:00
|
|
|
return Err(SEK::EntryAlreadyExists.into_error()).map_err_into(SEK::CreateCallError);
|
|
|
|
}
|
|
|
|
hsmap.insert(id.clone(), {
|
2017-06-04 14:59:23 +00:00
|
|
|
debug!("Creating: '{}'", id);
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
let mut se = try!(StoreEntry::new(id.clone(), &self.backend));
|
2016-08-01 17:59:43 +00:00
|
|
|
se.status = StoreEntryStatus::Borrowed;
|
|
|
|
se
|
|
|
|
});
|
2016-01-24 16:28:52 +00:00
|
|
|
}
|
2016-02-16 19:42:51 +00:00
|
|
|
|
2017-06-04 14:59:23 +00:00
|
|
|
debug!("Constructing FileLockEntry: '{}'", id);
|
|
|
|
|
2017-06-04 14:30:43 +00:00
|
|
|
Ok(FileLockEntry::new(self, Entry::new(id)))
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
2016-01-16 19:18:43 +00:00
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// Borrow a given Entry. When the `FileLockEntry` is either `update`d or
|
|
|
|
/// dropped, the new Entry is written to disk
|
2016-05-12 12:52:33 +00:00
|
|
|
///
|
|
|
|
/// Implicitely creates a entry in the store if there is no entry with the id `id`. For a
|
|
|
|
/// non-implicitely-create look at `Store::get`.
|
2017-02-20 15:03:24 +00:00
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// On success: FileLockEntry
|
|
|
|
///
|
|
|
|
/// On error:
|
|
|
|
/// - Errors StoreId::into_storeid() might return
|
|
|
|
/// - RetrieveCallError(LockPoisoned()) if the internal lock is poisened.
|
|
|
|
///
|
2016-05-03 13:49:33 +00:00
|
|
|
pub fn retrieve<'a, S: IntoStoreId>(&'a self, id: S) -> Result<FileLockEntry<'a>> {
|
2016-08-25 15:56:55 +00:00
|
|
|
let id = try!(id.into_storeid()).with_base(self.path().clone());
|
2017-06-04 17:04:42 +00:00
|
|
|
debug!("Retrieving id: '{}'", id);
|
2016-08-01 18:31:11 +00:00
|
|
|
let entry = try!({
|
|
|
|
self.entries
|
|
|
|
.write()
|
|
|
|
.map_err(|_| SE::new(SEK::LockPoisoned, None))
|
|
|
|
.and_then(|mut es| {
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
let new_se = try!(StoreEntry::new(id.clone(), &self.backend));
|
2016-09-05 14:51:37 +00:00
|
|
|
let mut se = es.entry(id.clone()).or_insert(new_se);
|
2016-08-01 18:31:11 +00:00
|
|
|
let entry = se.get_entry();
|
|
|
|
se.status = StoreEntryStatus::Borrowed;
|
|
|
|
entry
|
|
|
|
})
|
|
|
|
.map_err_into(SEK::RetrieveCallError)
|
|
|
|
});
|
2016-08-01 18:28:38 +00:00
|
|
|
|
2017-06-04 17:04:42 +00:00
|
|
|
debug!("Constructing FileLockEntry: '{}'", id);
|
2017-06-04 14:30:43 +00:00
|
|
|
Ok(FileLockEntry::new(self, entry))
|
2016-05-12 12:52:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get an entry from the store if it exists.
|
|
|
|
///
|
2017-02-20 15:03:32 +00:00
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// On success: Some(FileLockEntry) or None
|
|
|
|
///
|
|
|
|
/// On error:
|
|
|
|
/// - Errors StoreId::into_storeid() might return
|
|
|
|
/// - Errors Store::retrieve() might return
|
|
|
|
///
|
2016-05-27 10:23:26 +00:00
|
|
|
pub fn get<'a, S: IntoStoreId + Clone>(&'a self, id: S) -> Result<Option<FileLockEntry<'a>>> {
|
2016-09-20 14:52:14 +00:00
|
|
|
let id = try!(id.into_storeid()).with_base(self.path().clone());
|
|
|
|
|
2017-06-04 17:05:35 +00:00
|
|
|
debug!("Getting id: '{}'", id);
|
|
|
|
|
2017-02-27 11:43:55 +00:00
|
|
|
let exists = try!(id.exists()) || try!(self.entries
|
2016-09-20 14:52:14 +00:00
|
|
|
.read()
|
|
|
|
.map(|map| map.contains_key(&id))
|
|
|
|
.map_err(|_| SE::new(SEK::LockPoisoned, None))
|
|
|
|
.map_err_into(SEK::GetCallError)
|
|
|
|
);
|
|
|
|
|
2017-02-27 11:43:55 +00:00
|
|
|
if !exists {
|
2016-09-20 14:52:14 +00:00
|
|
|
debug!("Does not exist in internal cache or filesystem: {:?}", id);
|
2016-05-27 10:23:26 +00:00
|
|
|
return Ok(None);
|
2016-05-12 12:52:33 +00:00
|
|
|
}
|
2016-09-20 14:52:14 +00:00
|
|
|
|
2016-06-27 16:16:43 +00:00
|
|
|
self.retrieve(id).map(Some).map_err_into(SEK::GetCallError)
|
2016-05-12 12:52:33 +00:00
|
|
|
}
|
2016-01-16 19:18:43 +00:00
|
|
|
|
2016-01-24 11:17:41 +00:00
|
|
|
/// Iterate over all StoreIds for one module name
|
2017-02-20 15:03:45 +00:00
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
///
|
|
|
|
/// On success: An iterator over all entries in the module
|
|
|
|
///
|
|
|
|
/// On failure:
|
|
|
|
/// - RetrieveForModuleCallError(GlobError(EncodingError())) if the path string cannot be
|
|
|
|
/// encoded
|
|
|
|
/// - GRetrieveForModuleCallError(GlobError(lobError())) if the glob() failed.
|
|
|
|
///
|
2016-03-13 13:32:48 +00:00
|
|
|
pub fn retrieve_for_module(&self, mod_name: &str) -> Result<StoreIdIterator> {
|
|
|
|
let mut path = self.path().clone();
|
|
|
|
path.push(mod_name);
|
|
|
|
|
2017-06-04 17:06:22 +00:00
|
|
|
debug!("Retrieving for module: '{}'", mod_name);
|
|
|
|
|
2016-05-14 17:10:25 +00:00
|
|
|
path.to_str()
|
|
|
|
.ok_or(SE::new(SEK::EncodingError, None))
|
|
|
|
.and_then(|path| {
|
2016-05-28 15:10:34 +00:00
|
|
|
let path = [ path, "/**/*" ].join("");
|
2016-05-14 17:10:25 +00:00
|
|
|
debug!("glob()ing with '{}'", path);
|
2016-06-27 16:16:43 +00:00
|
|
|
glob(&path[..]).map_err_into(SEK::GlobError)
|
2016-05-14 17:10:25 +00:00
|
|
|
})
|
2016-08-22 10:22:39 +00:00
|
|
|
.map(|paths| GlobStoreIdIterator::new(paths, self.path().clone()).into())
|
2016-06-27 16:16:43 +00:00
|
|
|
.map_err_into(SEK::GlobError)
|
|
|
|
.map_err_into(SEK::RetrieveForModuleCallError)
|
2016-01-24 11:17:41 +00:00
|
|
|
}
|
|
|
|
|
2017-02-20 15:03:56 +00:00
|
|
|
/// Walk the store tree for the module
|
|
|
|
///
|
|
|
|
/// The difference between a `Walk` and a `StoreIdIterator` is that with a `Walk`, one can find
|
|
|
|
/// "collections" (folders).
|
2016-04-16 15:03:41 +00:00
|
|
|
pub fn walk<'a>(&'a self, mod_name: &str) -> Walk {
|
2017-06-04 17:06:54 +00:00
|
|
|
debug!("Creating Walk object for {}", mod_name);
|
2016-04-16 15:03:41 +00:00
|
|
|
Walk::new(self.path().clone(), mod_name)
|
|
|
|
}
|
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// Return the `FileLockEntry` and write to disk
|
2017-02-20 15:04:04 +00:00
|
|
|
///
|
|
|
|
/// See `Store::_update()`.
|
|
|
|
///
|
2017-02-26 18:55:35 +00:00
|
|
|
pub fn update<'a>(&'a self, entry: &mut FileLockEntry<'a>) -> Result<()> {
|
2017-06-04 17:08:47 +00:00
|
|
|
debug!("Updating FileLockEntry at '{}'", entry.get_location());
|
2017-02-26 18:54:40 +00:00
|
|
|
self._update(entry, false).map_err_into(SEK::UpdateCallError)
|
2016-01-17 14:09:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Internal method to write to the filesystem store.
|
|
|
|
///
|
|
|
|
/// # Assumptions
|
2017-02-20 15:04:17 +00:00
|
|
|
///
|
2016-01-17 14:09:10 +00:00
|
|
|
/// This method assumes that entry is dropped _right after_ the call, hence
|
|
|
|
/// it is not public.
|
2017-02-20 15:04:17 +00:00
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// On success: Entry
|
|
|
|
///
|
|
|
|
/// On error:
|
|
|
|
/// - UpdateCallError(LockPoisoned()) if the internal write lock cannot be aquierd.
|
|
|
|
/// - IdNotFound() if the entry was not found in the stor
|
|
|
|
/// - Errors Entry::verify() might return
|
|
|
|
/// - Errors StoreEntry::write_entry() might return
|
|
|
|
///
|
2017-06-04 14:30:43 +00:00
|
|
|
fn _update<'a>(&'a self, entry: &mut FileLockEntry<'a>, modify_presence: bool) -> Result<()> {
|
2016-05-14 17:12:13 +00:00
|
|
|
let mut hsmap = match self.entries.write() {
|
|
|
|
Err(_) => return Err(SE::new(SEK::LockPoisoned, None)),
|
|
|
|
Ok(e) => e,
|
|
|
|
};
|
|
|
|
|
2016-05-26 16:22:14 +00:00
|
|
|
let mut se = try!(hsmap.get_mut(&entry.location).ok_or(SE::new(SEK::IdNotFound, None)));
|
2016-01-24 18:55:47 +00:00
|
|
|
|
2016-01-29 15:50:20 +00:00
|
|
|
assert!(se.is_borrowed(), "Tried to update a non borrowed entry.");
|
2016-01-24 18:55:47 +00:00
|
|
|
|
2016-02-06 18:39:20 +00:00
|
|
|
debug!("Verifying Entry");
|
2016-02-06 17:50:39 +00:00
|
|
|
try!(entry.entry.verify());
|
|
|
|
|
2016-02-06 18:39:20 +00:00
|
|
|
debug!("Writing Entry");
|
2016-01-24 18:55:47 +00:00
|
|
|
try!(se.write_entry(&entry.entry));
|
2016-10-07 17:20:34 +00:00
|
|
|
if modify_presence {
|
|
|
|
se.status = StoreEntryStatus::Present;
|
|
|
|
}
|
2016-01-24 18:55:47 +00:00
|
|
|
|
2017-06-04 14:30:43 +00:00
|
|
|
Ok(())
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
2016-01-16 19:18:43 +00:00
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// Retrieve a copy of a given entry, this cannot be used to mutate
|
|
|
|
/// the one on disk
|
2017-02-20 15:04:28 +00:00
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// On success: Entry
|
|
|
|
///
|
|
|
|
/// On error:
|
|
|
|
/// - RetrieveCopyCallError(LockPoisoned()) if the internal write lock cannot be aquierd.
|
|
|
|
/// - RetrieveCopyCallError(IdLocked()) if the Entry is borrowed currently
|
|
|
|
/// - Errors StoreEntry::new() might return
|
|
|
|
///
|
2016-05-03 13:49:33 +00:00
|
|
|
pub fn retrieve_copy<S: IntoStoreId>(&self, id: S) -> Result<Entry> {
|
2016-08-25 15:56:55 +00:00
|
|
|
let id = try!(id.into_storeid()).with_base(self.path().clone());
|
2017-06-04 17:11:18 +00:00
|
|
|
debug!("Retrieving copy of '{}'", id);
|
2016-05-14 17:14:11 +00:00
|
|
|
let entries = match self.entries.write() {
|
2016-05-28 21:30:50 +00:00
|
|
|
Err(_) => {
|
|
|
|
return Err(SE::new(SEK::LockPoisoned, None))
|
2016-06-27 16:16:43 +00:00
|
|
|
.map_err_into(SEK::RetrieveCopyCallError);
|
2016-05-28 21:30:50 +00:00
|
|
|
},
|
2016-05-14 17:14:11 +00:00
|
|
|
Ok(e) => e,
|
|
|
|
};
|
2016-01-25 21:26:00 +00:00
|
|
|
|
|
|
|
// if the entry is currently modified by the user, we cannot drop it
|
|
|
|
if entries.get(&id).map(|e| e.is_borrowed()).unwrap_or(false) {
|
2016-06-27 16:16:43 +00:00
|
|
|
return Err(SE::new(SEK::IdLocked, None)).map_err_into(SEK::RetrieveCopyCallError);
|
2016-01-25 21:26:00 +00:00
|
|
|
}
|
|
|
|
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
try!(StoreEntry::new(id, &self.backend)).get_entry()
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
2016-01-16 19:18:43 +00:00
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// Delete an entry
|
2017-02-20 15:04:36 +00:00
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// On success: ()
|
|
|
|
///
|
|
|
|
/// On error:
|
|
|
|
/// - DeleteCallError(LockPoisoned()) if the internal write lock cannot be aquierd.
|
|
|
|
/// - DeleteCallError(FileNotFound()) if the StoreId refers to a non-existing entry.
|
|
|
|
/// - DeleteCallError(FileError()) if the internals failed to remove the file.
|
|
|
|
///
|
2016-05-03 13:49:33 +00:00
|
|
|
pub fn delete<S: IntoStoreId>(&self, id: S) -> Result<()> {
|
2016-08-25 15:56:55 +00:00
|
|
|
let id = try!(id.into_storeid()).with_base(self.path().clone());
|
2016-02-16 19:42:51 +00:00
|
|
|
|
2017-06-04 17:12:03 +00:00
|
|
|
debug!("Deleting id: '{}'", id);
|
|
|
|
|
2016-08-01 18:31:11 +00:00
|
|
|
{
|
|
|
|
let mut entries = match self.entries.write() {
|
|
|
|
Err(_) => return Err(SE::new(SEK::LockPoisoned, None))
|
|
|
|
.map_err_into(SEK::DeleteCallError),
|
|
|
|
Ok(e) => e,
|
|
|
|
};
|
2016-01-17 15:22:50 +00:00
|
|
|
|
2016-08-01 18:31:11 +00:00
|
|
|
// if the entry is currently modified by the user, we cannot drop it
|
2016-09-19 09:07:38 +00:00
|
|
|
match entries.get(&id) {
|
|
|
|
None => {
|
|
|
|
return Err(SEK::FileNotFound.into_error()).map_err_into(SEK::DeleteCallError)
|
|
|
|
},
|
|
|
|
Some(e) => if e.is_borrowed() {
|
|
|
|
return Err(SE::new(SEK::IdLocked, None)).map_err_into(SEK::DeleteCallError)
|
|
|
|
}
|
2016-08-01 18:31:11 +00:00
|
|
|
}
|
2016-01-17 15:22:50 +00:00
|
|
|
|
2016-08-01 18:31:11 +00:00
|
|
|
// remove the entry first, then the file
|
|
|
|
entries.remove(&id);
|
2016-09-05 14:51:37 +00:00
|
|
|
let pb = try!(id.clone().with_base(self.path().clone()).into_pathbuf());
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
if let Err(e) = self.backend.remove_file(&pb) {
|
2016-08-01 18:31:11 +00:00
|
|
|
return Err(SEK::FileError.into_error_with_cause(Box::new(e)))
|
|
|
|
.map_err_into(SEK::DeleteCallError);
|
|
|
|
}
|
2016-02-16 19:42:51 +00:00
|
|
|
}
|
|
|
|
|
2017-06-04 17:12:03 +00:00
|
|
|
debug!("Deleted");
|
2017-06-04 14:30:43 +00:00
|
|
|
Ok(())
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
2016-02-06 17:46:54 +00:00
|
|
|
|
2016-04-14 15:15:19 +00:00
|
|
|
/// Save a copy of the Entry in another place
|
|
|
|
pub fn save_to(&self, entry: &FileLockEntry, new_id: StoreId) -> Result<()> {
|
2017-06-04 17:13:06 +00:00
|
|
|
debug!("Saving '{}' to '{}'", entry.get_location(), new_id);
|
2016-04-17 10:30:34 +00:00
|
|
|
self.save_to_other_location(entry, new_id, false)
|
2016-04-14 15:15:19 +00:00
|
|
|
}
|
|
|
|
|
2016-03-17 12:40:06 +00:00
|
|
|
/// Save an Entry in another place
|
|
|
|
/// Removes the original entry
|
2016-02-21 14:34:49 +00:00
|
|
|
pub fn save_as(&self, entry: FileLockEntry, new_id: StoreId) -> Result<()> {
|
2017-06-04 17:13:24 +00:00
|
|
|
debug!("Saving '{}' as '{}'", entry.get_location(), new_id);
|
2016-04-17 10:30:34 +00:00
|
|
|
self.save_to_other_location(&entry, new_id, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn save_to_other_location(&self, entry: &FileLockEntry, new_id: StoreId, remove_old: bool)
|
|
|
|
-> Result<()>
|
|
|
|
{
|
2016-08-25 15:56:55 +00:00
|
|
|
let new_id = new_id.with_base(self.path().clone());
|
2016-09-08 13:16:07 +00:00
|
|
|
let hsmap = try!(
|
|
|
|
self.entries
|
|
|
|
.write()
|
|
|
|
.map_err(|_| SEK::LockPoisoned.into_error())
|
|
|
|
.map_err_into(SEK::MoveCallError)
|
|
|
|
);
|
|
|
|
|
|
|
|
if hsmap.contains_key(&new_id) {
|
|
|
|
return Err(SEK::EntryAlreadyExists.into_error()).map_err_into(SEK::MoveCallError)
|
2016-03-17 12:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let old_id = entry.get_location().clone();
|
|
|
|
|
2016-09-05 14:51:37 +00:00
|
|
|
let old_id_as_path = try!(old_id.clone().with_base(self.path().clone()).into_pathbuf());
|
|
|
|
let new_id_as_path = try!(new_id.clone().with_base(self.path().clone()).into_pathbuf());
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
self.backend.copy(&old_id_as_path, &new_id_as_path)
|
2016-04-17 10:30:34 +00:00
|
|
|
.and_then(|_| {
|
|
|
|
if remove_old {
|
2017-06-04 17:14:41 +00:00
|
|
|
debug!("Removing old '{:?}'", old_id_as_path);
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
self.backend.remove_file(&old_id_as_path)
|
2016-04-17 10:30:34 +00:00
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
2016-06-27 16:16:43 +00:00
|
|
|
.map_err_into(SEK::FileError)
|
|
|
|
.map_err_into(SEK::MoveCallError)
|
2016-02-21 14:34:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Move an entry without loading
|
2016-10-07 15:47:49 +00:00
|
|
|
///
|
|
|
|
/// This function moves an entry from one path to another.
|
|
|
|
///
|
|
|
|
/// Generally, this function shouldn't be used by library authors, if they "just" want to move
|
|
|
|
/// something around. A library for moving entries while caring about meta-data and links.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// This function returns an error in certain cases:
|
|
|
|
///
|
|
|
|
/// * If the about-to-be-moved entry is borrowed
|
|
|
|
/// * If the lock on the internal data structure cannot be aquired
|
|
|
|
/// * If the new path already exists
|
|
|
|
/// * If the about-to-be-moved entry does not exist
|
|
|
|
/// * If the FS-operation failed
|
|
|
|
///
|
|
|
|
/// # Warnings
|
|
|
|
///
|
|
|
|
/// This should be used with _great_ care, as moving an entry from `a` to `b` might result in
|
|
|
|
/// dangling links (see below).
|
|
|
|
///
|
|
|
|
/// ## Moving linked entries
|
|
|
|
///
|
|
|
|
/// If the entry which is moved is linked to another entry, these links get invalid (but we do
|
|
|
|
/// not detect this here). As links are always two-way-links, so `a` is not only linked to `b`,
|
|
|
|
/// but also the other way round, moving `b` to `c` results in the following scenario:
|
|
|
|
///
|
|
|
|
/// * `a` links to `b`, which does not exist anymore.
|
|
|
|
/// * `c` links to `a`, which does exist.
|
|
|
|
///
|
|
|
|
/// So the link is _partly dangling_, so to say.
|
|
|
|
///
|
2016-02-21 14:34:49 +00:00
|
|
|
pub fn move_by_id(&self, old_id: StoreId, new_id: StoreId) -> Result<()> {
|
2016-08-25 15:56:55 +00:00
|
|
|
let new_id = new_id.with_base(self.path().clone());
|
|
|
|
let old_id = old_id.with_base(self.path().clone());
|
2016-03-19 14:06:10 +00:00
|
|
|
|
2017-06-04 17:15:42 +00:00
|
|
|
debug!("Moving '{}' to '{}'", old_id, new_id);
|
|
|
|
|
2016-08-01 18:32:04 +00:00
|
|
|
{
|
2016-09-22 06:39:00 +00:00
|
|
|
let mut hsmap = match self.entries.write() {
|
2016-08-25 06:57:25 +00:00
|
|
|
Err(_) => return Err(SE::new(SEK::LockPoisoned, None)),
|
|
|
|
Ok(m) => m,
|
|
|
|
};
|
|
|
|
|
2016-09-22 06:39:00 +00:00
|
|
|
if hsmap.contains_key(&new_id) {
|
|
|
|
return Err(SEK::EntryAlreadyExists.into_error());
|
|
|
|
}
|
|
|
|
|
2016-10-04 14:36:40 +00:00
|
|
|
// if we do not have an entry here, we fail in `FileAbstraction::rename()` below.
|
|
|
|
// if we have one, but it is borrowed, we really should not rename it, as this might
|
|
|
|
// lead to strange errors
|
|
|
|
if hsmap.get(&old_id).map(|e| e.is_borrowed()).unwrap_or(false) {
|
|
|
|
return Err(SEK::EntryAlreadyBorrowed.into_error());
|
|
|
|
}
|
|
|
|
|
2016-09-30 10:50:59 +00:00
|
|
|
let old_id_pb = try!(old_id.clone().with_base(self.path().clone()).into_pathbuf());
|
|
|
|
let new_id_pb = try!(new_id.clone().with_base(self.path().clone()).into_pathbuf());
|
|
|
|
|
Transform backend code from compiletime-injection to dependency-injection
The title might be a bit inaccurate, but I cannot think of something
better.
Before the change
=================
Before this change, we had a compiletime backend for the store. This
means that the actual filesystem operations were compiled into the store
either as real filesystem operations (in a normal debug or release
build) but as a in-memory variant in the 'test' case.
So tests did not hit the filesystem when running.
This gave us us the possibility to run tests concurrently with multiple
stores that did not interfere with eachother.
Problem
=======
This approach worked perfectly well until we started to test not the
store itself but crates that depend on the store implementation.
When running tests in a crate A that depends on the store, the store
itself was compiled with the filesystem-hitting-backend.
This was problematic, as tests could not be implemented without hitting
the filesystem.
After the change
================
After this change, the backend code is injected into the store via
dependency injection (the `Store::new()` function automatically uses the
filesystem-backend).
The store can be created with a the in-memory backend when running tests
now.
Implementation
==============
The implementation of this is rather stupid, despite the big diff in
this commit.
Lets have a look at the `Store` code changes first and then we'll
discuss the `file_abstraction` changes this commit introduces.
libimagstore::fs_abstraction
----------------------------
The filesystem was abstracted via a trait `FileAbstraction` which
contains the essential functions for working with the filesystem.
Two implementations are provided in the code:
* FSFileAbstraction
* InMemoryFileAbstraction
whereas the first actually works with the filesystem and the latter
works with an in-memory HashMap that is used as filesystem.
Further, the trait `FileAbstractionInstance` was introduced for
functions which are executed on actual instances of content from the
filesystem, which was previousely tied into the general abstraction
mechanism.
So, the `FileAbstraction` trait is for working with the filesystem, the
`FileAbstractionInstance` trait is for working with instances of content
from the filesystem (speak: actual Files).
The `FileAbstraction` trait requires a function to be implemented that
can be used to create a `FileAbstractionInstance` object from it.
In case of the `FSFileAbstractionInstance`, which is the implementation
of the `FileAbstractionInstance` for the actual filesystem-hitting code,
the underlying resource is managed like with the old code before.
The `InMemoryFileAbstractionInstance` implementation is corrosponding to
the `InMemoryFileAbstraction` implementation - for the in-memory
"filesystem".
The implementation of the `get_file_content()` function had to be
changed to return a `String` rather than a `&mut Read` because of
lifetime issues.
This change is store-internally and the API of the store itself was not
affected.
libimagstore::store::Store changes
----------------------------------
So, first we need to make sure that the store knows the actual backend.
Therefor, the `new()` method was renamed to `new_with_backend()` and got
another parameter: the backend implementation.
A new `new()` function was created for convenience and
backwards-compatibility with the rest of the imag codebase (and also
because nobody wants to pass the backend manually all the time).
As the backend is abstracted via a trait, and the store should not
change its interface, the new `Store` member was introduced inside a
`Box`.
All calls (`remove_file()`, `copy()`, `rename()` and `create_dir_all()`)
were refactored from `FileAbstraction::*` calls into `self.backend.*`
calls.
libimagstore::store::StoreEntry changes
---------------------------------------
The `StoreEntry` type is constructed in the store internally for holding
a `StoreId` object as well as some status and the file abstraction code.
This object is constructed via a `new()` function that got a new
parameter: a `&Box<FileAbstraction>` to the backend abstraction the
store uses.
This backend is now used to create a new `FileAbstractionInstance`
object for the `StoreEntry`.
Also the `StoreEntry::get_entry()` code had to be adapted to the new
`Entry::from_str()` function interface.
This commit message is partially added as comment in the code.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
2017-06-07 21:09:27 +00:00
|
|
|
match self.backend.rename(&old_id_pb, &new_id_pb) {
|
2016-09-30 10:50:59 +00:00
|
|
|
Err(e) => return Err(SEK::EntryRenameError.into_error_with_cause(Box::new(e))),
|
|
|
|
Ok(_) => {
|
|
|
|
debug!("Rename worked on filesystem");
|
|
|
|
|
|
|
|
// assert enforced through check hsmap.contains_key(&new_id) above.
|
|
|
|
// Should therefor never fail
|
|
|
|
assert!(hsmap
|
|
|
|
.remove(&old_id)
|
2016-10-07 19:15:50 +00:00
|
|
|
.and_then(|mut entry| {
|
|
|
|
entry.id = new_id.clone();
|
|
|
|
hsmap.insert(new_id.clone(), entry)
|
|
|
|
}).is_none())
|
2016-07-22 13:12:56 +00:00
|
|
|
}
|
2016-08-25 07:30:47 +00:00
|
|
|
}
|
2016-08-01 18:32:04 +00:00
|
|
|
|
2016-03-17 12:39:32 +00:00
|
|
|
}
|
2016-03-19 14:06:10 +00:00
|
|
|
|
2017-06-04 17:15:42 +00:00
|
|
|
debug!("Moved");
|
2017-06-04 14:30:43 +00:00
|
|
|
Ok(())
|
2016-02-21 14:34:49 +00:00
|
|
|
}
|
|
|
|
|
2016-01-29 15:50:39 +00:00
|
|
|
/// Gets the path where this store is on the disk
|
|
|
|
pub fn path(&self) -> &PathBuf {
|
|
|
|
&self.location
|
|
|
|
}
|
2016-03-10 17:14:53 +00:00
|
|
|
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
|
|
|
|
2016-03-25 12:29:20 +00:00
|
|
|
impl Debug for Store {
|
|
|
|
|
2017-02-20 14:17:55 +00:00
|
|
|
/// TODO: Make pretty.
|
2016-03-25 12:29:20 +00:00
|
|
|
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FMTError> {
|
|
|
|
try!(write!(fmt, " --- Store ---\n"));
|
|
|
|
try!(write!(fmt, "\n"));
|
|
|
|
try!(write!(fmt, " - location : {:?}\n", self.location));
|
|
|
|
try!(write!(fmt, " - configuration : {:?}\n", self.configuration));
|
|
|
|
try!(write!(fmt, "\n"));
|
|
|
|
try!(write!(fmt, "Entries:\n"));
|
|
|
|
try!(write!(fmt, "{:?}", self.entries));
|
|
|
|
try!(write!(fmt, "\n"));
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-01-16 18:04:15 +00:00
|
|
|
impl Drop for Store {
|
|
|
|
|
2017-02-20 14:17:55 +00:00
|
|
|
///
|
|
|
|
/// Unlock all files on drop
|
|
|
|
//
|
|
|
|
/// TODO: Unlock them
|
|
|
|
///
|
2016-01-16 18:04:15 +00:00
|
|
|
fn drop(&mut self) {
|
2016-01-28 20:06:49 +00:00
|
|
|
debug!("Dropping store");
|
2016-01-16 18:04:15 +00:00
|
|
|
}
|
2016-01-13 20:51:40 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-01-16 19:53:38 +00:00
|
|
|
/// A struct that allows you to borrow an Entry
|
2016-01-16 18:04:15 +00:00
|
|
|
pub struct FileLockEntry<'a> {
|
|
|
|
store: &'a Store,
|
2016-01-16 18:32:12 +00:00
|
|
|
entry: Entry,
|
2016-01-16 17:25:48 +00:00
|
|
|
}
|
|
|
|
|
2016-01-16 18:04:15 +00:00
|
|
|
impl<'a> FileLockEntry<'a, > {
|
2017-02-20 14:18:08 +00:00
|
|
|
|
|
|
|
/// Create a new FileLockEntry based on a `Entry` object.
|
|
|
|
///
|
|
|
|
/// Only for internal use.
|
2016-05-26 16:22:14 +00:00
|
|
|
fn new(store: &'a Store, entry: Entry) -> FileLockEntry<'a> {
|
2016-01-16 17:25:48 +00:00
|
|
|
FileLockEntry {
|
|
|
|
store: store,
|
|
|
|
entry: entry,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 17:26:45 +00:00
|
|
|
impl<'a> Debug for FileLockEntry<'a> {
|
|
|
|
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FMTError> {
|
|
|
|
write!(fmt, "FileLockEntry(Store = {})", self.store.location.to_str()
|
|
|
|
.unwrap_or("Unknown Path"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-21 11:13:58 +00:00
|
|
|
impl<'a> Deref for FileLockEntry<'a> {
|
2016-01-16 17:25:48 +00:00
|
|
|
type Target = Entry;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.entry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-21 11:13:58 +00:00
|
|
|
impl<'a> DerefMut for FileLockEntry<'a> {
|
2016-01-16 17:25:48 +00:00
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
&mut self.entry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-07 16:46:50 +00:00
|
|
|
#[cfg(not(test))]
|
2016-01-16 18:32:12 +00:00
|
|
|
impl<'a> Drop for FileLockEntry<'a> {
|
2017-02-20 14:18:08 +00:00
|
|
|
|
2016-02-06 23:58:53 +00:00
|
|
|
/// This will silently ignore errors, use `Store::update` if you want to catch the errors
|
2017-02-20 14:18:08 +00:00
|
|
|
///
|
|
|
|
/// This might panic if the store was compiled with the early-panic feature (which is not
|
|
|
|
/// intended for production use, though).
|
2016-01-16 18:32:12 +00:00
|
|
|
fn drop(&mut self) {
|
2017-02-04 19:56:30 +00:00
|
|
|
use libimagerror::trace::trace_error_dbg;
|
|
|
|
match self.store._update(self, true) {
|
|
|
|
Err(e) => {
|
|
|
|
trace_error_dbg(&e);
|
|
|
|
if_cfg_panic!("ERROR WHILE DROPPING: {:?}", e);
|
|
|
|
},
|
|
|
|
Ok(_) => { },
|
|
|
|
}
|
2016-01-16 18:32:12 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-23 15:26:02 +00:00
|
|
|
|
2016-10-07 16:46:50 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
impl<'a> Drop for FileLockEntry<'a> {
|
2017-02-20 14:18:19 +00:00
|
|
|
|
2016-10-07 16:46:50 +00:00
|
|
|
/// This will not silently ignore errors but prints the result of the _update() call for testing
|
|
|
|
fn drop(&mut self) {
|
2016-10-11 16:28:00 +00:00
|
|
|
let _ = self.store._update(self, true).map_err(|e| trace_error(&e));
|
2016-10-07 16:46:50 +00:00
|
|
|
}
|
2017-02-20 14:18:19 +00:00
|
|
|
|
2016-10-07 16:46:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-05-03 21:10:32 +00:00
|
|
|
/// `EntryContent` type
|
2016-01-23 15:26:02 +00:00
|
|
|
pub type EntryContent = String;
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// An Entry of the store
|
|
|
|
//
|
|
|
|
/// Contains location, header and content part.
|
2016-01-23 15:26:02 +00:00
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Entry {
|
|
|
|
location: StoreId,
|
2016-11-15 20:30:39 +00:00
|
|
|
header: Value,
|
2016-01-23 15:26:02 +00:00
|
|
|
content: EntryContent,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Entry {
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Create a new store entry with its location at `loc`.
|
|
|
|
///
|
|
|
|
/// This creates the entry with the default header from `Entry::default_header()` and an empty
|
|
|
|
/// content.
|
2016-01-24 19:19:09 +00:00
|
|
|
pub fn new(loc: StoreId) -> Entry {
|
2016-01-23 15:26:02 +00:00
|
|
|
Entry {
|
|
|
|
location: loc,
|
2016-11-15 20:30:39 +00:00
|
|
|
header: Entry::default_header(),
|
2016-01-23 15:26:02 +00:00
|
|
|
content: EntryContent::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Get the default Header for an Entry.
|
|
|
|
///
|
|
|
|
/// This function should be used to get a new Header, as the default header may change. Via
|
|
|
|
/// this function, compatibility is ensured.
|
2016-11-15 20:30:39 +00:00
|
|
|
pub fn default_header() -> Value { // BTreeMap<String, Value>
|
|
|
|
Value::default_header()
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// See `Entry::from_str()`, as this function is used internally. This is just a wrapper for
|
|
|
|
/// convenience.
|
2016-07-21 13:58:34 +00:00
|
|
|
pub fn from_reader<S: IntoStoreId>(loc: S, file: &mut Read) -> Result<Entry> {
|
2016-01-24 15:01:37 +00:00
|
|
|
let text = {
|
|
|
|
let mut s = String::new();
|
|
|
|
try!(file.read_to_string(&mut s));
|
|
|
|
s
|
2016-01-23 16:30:01 +00:00
|
|
|
};
|
2016-01-24 15:01:37 +00:00
|
|
|
Self::from_str(loc, &text[..])
|
|
|
|
}
|
2016-01-23 16:30:01 +00:00
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Create a new Entry, with contents from the string passed.
|
|
|
|
///
|
|
|
|
/// The passed string _must_ be a complete valid store entry, including header. So this is
|
|
|
|
/// probably not what end-users want to call.
|
|
|
|
///
|
|
|
|
/// # Return value
|
|
|
|
///
|
|
|
|
/// This errors if
|
|
|
|
///
|
|
|
|
/// - String cannot be matched on regex to find header and content
|
|
|
|
/// - Header cannot be parsed into a TOML object
|
|
|
|
///
|
2016-05-04 10:00:31 +00:00
|
|
|
pub fn from_str<S: IntoStoreId>(loc: S, s: &str) -> Result<Entry> {
|
2016-01-28 20:06:49 +00:00
|
|
|
debug!("Building entry from string");
|
2016-02-20 20:06:47 +00:00
|
|
|
lazy_static! {
|
|
|
|
static ref RE: Regex = Regex::new(r"(?smx)
|
|
|
|
^---$
|
|
|
|
(?P<header>.*) # Header
|
|
|
|
^---$\n
|
|
|
|
(?P<content>.*) # Content
|
|
|
|
").unwrap();
|
|
|
|
}
|
|
|
|
|
2016-05-14 16:59:15 +00:00
|
|
|
let matches = match RE.captures(s) {
|
2016-05-14 17:05:54 +00:00
|
|
|
None => return Err(SE::new(SEK::MalformedEntry, None)),
|
2016-05-14 16:59:15 +00:00
|
|
|
Some(s) => s,
|
|
|
|
};
|
2016-01-23 16:30:01 +00:00
|
|
|
|
2016-05-14 16:59:15 +00:00
|
|
|
let header = match matches.name("header") {
|
2016-05-14 17:05:54 +00:00
|
|
|
None => return Err(SE::new(SEK::MalformedEntry, None)),
|
2016-05-14 16:59:15 +00:00
|
|
|
Some(s) => s
|
|
|
|
};
|
2016-01-23 16:30:01 +00:00
|
|
|
|
2017-01-30 09:01:13 +00:00
|
|
|
let content = matches.name("content").map(|r| r.as_str()).unwrap_or("");
|
2016-01-23 16:30:01 +00:00
|
|
|
|
2016-01-28 20:06:49 +00:00
|
|
|
debug!("Header and content found. Yay! Building Entry object now");
|
2016-01-23 16:30:01 +00:00
|
|
|
Ok(Entry {
|
2016-08-25 15:56:55 +00:00
|
|
|
location: try!(loc.into_storeid()),
|
2016-11-15 20:30:39 +00:00
|
|
|
header: try!(Value::parse(header.as_str())),
|
2017-01-30 09:01:13 +00:00
|
|
|
content: String::from(content),
|
2016-01-23 16:30:01 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Return the string representation of this entry
|
|
|
|
///
|
|
|
|
/// This means not only the content of the entry, but the complete entry (from memory, not from
|
|
|
|
/// disk).
|
2016-01-24 18:55:47 +00:00
|
|
|
pub fn to_str(&self) -> String {
|
2016-09-09 09:48:10 +00:00
|
|
|
format!("---\n{header}---\n{content}",
|
2017-05-03 16:09:57 +00:00
|
|
|
header = ::toml::ser::to_string(&self.header).unwrap(),
|
2016-01-24 18:55:47 +00:00
|
|
|
content = self.content)
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Get the location of the Entry
|
2016-01-23 15:26:02 +00:00
|
|
|
pub fn get_location(&self) -> &StoreId {
|
|
|
|
&self.location
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Get the header of the Entry
|
2016-11-15 20:30:39 +00:00
|
|
|
pub fn get_header(&self) -> &Value {
|
2016-01-23 15:26:02 +00:00
|
|
|
&self.header
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Get the header mutably of the Entry
|
2016-11-15 20:30:39 +00:00
|
|
|
pub fn get_header_mut(&mut self) -> &mut Value {
|
2016-01-23 15:26:02 +00:00
|
|
|
&mut self.header
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Get the content of the Entry
|
2016-01-23 15:26:02 +00:00
|
|
|
pub fn get_content(&self) -> &EntryContent {
|
|
|
|
&self.content
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Get the content mutably of the Entry
|
2016-01-23 15:26:02 +00:00
|
|
|
pub fn get_content_mut(&mut self) -> &mut EntryContent {
|
|
|
|
&mut self.content
|
|
|
|
}
|
|
|
|
|
2017-02-20 14:18:19 +00:00
|
|
|
/// Verify the entry.
|
|
|
|
///
|
|
|
|
/// Currently, this only verifies the header. This might change in the future.
|
2016-02-06 17:50:39 +00:00
|
|
|
pub fn verify(&self) -> Result<()> {
|
|
|
|
self.header.verify()
|
|
|
|
}
|
|
|
|
|
2016-01-23 15:26:02 +00:00
|
|
|
}
|
|
|
|
|
2016-11-03 17:47:11 +00:00
|
|
|
impl PartialEq for Entry {
|
|
|
|
|
|
|
|
fn eq(&self, other: &Entry) -> bool {
|
|
|
|
self.location == other.location && // As the location only compares from the store root
|
|
|
|
self.header == other.header && // and the other Entry could be from another store (not
|
|
|
|
self.content == other.content // implemented by now, but we think ahead here)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-05-12 15:27:41 +00:00
|
|
|
mod glob_store_iter {
|
|
|
|
use std::fmt::{Debug, Formatter};
|
|
|
|
use std::fmt::Error as FmtError;
|
2016-08-22 10:22:39 +00:00
|
|
|
use std::path::PathBuf;
|
2016-05-12 15:27:41 +00:00
|
|
|
use glob::Paths;
|
|
|
|
use storeid::StoreId;
|
2016-05-13 12:48:20 +00:00
|
|
|
use storeid::StoreIdIterator;
|
2016-05-12 15:27:41 +00:00
|
|
|
|
2016-08-25 15:10:16 +00:00
|
|
|
use error::StoreErrorKind as SEK;
|
|
|
|
use error::MapErrInto;
|
|
|
|
|
|
|
|
use libimagerror::trace::trace_error;
|
|
|
|
|
2016-05-12 15:27:41 +00:00
|
|
|
pub struct GlobStoreIdIterator {
|
2016-08-22 10:22:39 +00:00
|
|
|
store_path: PathBuf,
|
2016-05-12 15:27:41 +00:00
|
|
|
paths: Paths,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for GlobStoreIdIterator {
|
|
|
|
|
|
|
|
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
|
|
|
write!(fmt, "GlobStoreIdIterator")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2016-05-13 12:48:20 +00:00
|
|
|
|
|
|
|
impl Into<StoreIdIterator> for GlobStoreIdIterator {
|
|
|
|
|
|
|
|
fn into(self) -> StoreIdIterator {
|
|
|
|
StoreIdIterator::new(Box::new(self))
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2016-05-12 15:27:41 +00:00
|
|
|
|
|
|
|
impl GlobStoreIdIterator {
|
|
|
|
|
2016-08-22 10:22:39 +00:00
|
|
|
pub fn new(paths: Paths, store_path: PathBuf) -> GlobStoreIdIterator {
|
2016-09-05 16:12:29 +00:00
|
|
|
debug!("Create a GlobStoreIdIterator(store_path = {:?}, /* ... */)", store_path);
|
|
|
|
|
2016-05-12 15:27:41 +00:00
|
|
|
GlobStoreIdIterator {
|
2016-08-22 10:22:39 +00:00
|
|
|
store_path: store_path,
|
2016-05-12 15:27:41 +00:00
|
|
|
paths: paths,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Iterator for GlobStoreIdIterator {
|
|
|
|
type Item = StoreId;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<StoreId> {
|
2016-08-22 10:22:39 +00:00
|
|
|
self.paths
|
|
|
|
.next()
|
|
|
|
.and_then(|o| {
|
2016-09-05 16:12:29 +00:00
|
|
|
debug!("GlobStoreIdIterator::next() => {:?}", o);
|
2016-08-25 15:10:16 +00:00
|
|
|
o.map_err_into(SEK::StoreIdHandlingError)
|
2016-09-05 16:22:55 +00:00
|
|
|
.and_then(|p| StoreId::from_full_path(&self.store_path, p))
|
2016-08-25 15:10:16 +00:00
|
|
|
.map_err(|e| {
|
2016-08-22 10:22:39 +00:00
|
|
|
debug!("GlobStoreIdIterator error: {:?}", e);
|
2016-08-25 15:10:16 +00:00
|
|
|
trace_error(&e);
|
|
|
|
}).ok()
|
2016-08-22 10:22:39 +00:00
|
|
|
})
|
2016-05-12 15:27:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2016-02-11 14:15:31 +00:00
|
|
|
extern crate env_logger;
|
|
|
|
|
2016-01-23 15:26:02 +00:00
|
|
|
use std::collections::BTreeMap;
|
2016-08-25 16:03:25 +00:00
|
|
|
use storeid::StoreId;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
use toml::Value;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_imag_section() {
|
2016-11-15 20:34:42 +00:00
|
|
|
use toml_ext::has_main_section;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut map = BTreeMap::new();
|
|
|
|
map.insert("imag".into(), Value::Table(BTreeMap::new()));
|
|
|
|
|
|
|
|
assert!(has_main_section(&map));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_imag_invalid_section_type() {
|
2016-11-15 20:34:42 +00:00
|
|
|
use toml_ext::has_main_section;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut map = BTreeMap::new();
|
|
|
|
map.insert("imag".into(), Value::Boolean(false));
|
|
|
|
|
|
|
|
assert!(!has_main_section(&map));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_imag_abscent_main_section() {
|
2016-11-15 20:34:42 +00:00
|
|
|
use toml_ext::has_main_section;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut map = BTreeMap::new();
|
|
|
|
map.insert("not_imag".into(), Value::Boolean(false));
|
|
|
|
|
|
|
|
assert!(!has_main_section(&map));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_main_section_without_version() {
|
2016-11-18 08:18:51 +00:00
|
|
|
use toml_ext::has_imag_version_in_main_section;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut map = BTreeMap::new();
|
|
|
|
map.insert("imag".into(), Value::Table(BTreeMap::new()));
|
|
|
|
|
|
|
|
assert!(!has_imag_version_in_main_section(&map));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_main_section_with_version() {
|
2016-11-18 08:18:51 +00:00
|
|
|
use toml_ext::has_imag_version_in_main_section;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut map = BTreeMap::new();
|
|
|
|
let mut sub = BTreeMap::new();
|
|
|
|
sub.insert("version".into(), Value::String("0.0.0".into()));
|
|
|
|
map.insert("imag".into(), Value::Table(sub));
|
|
|
|
|
|
|
|
assert!(has_imag_version_in_main_section(&map));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_main_section_with_version_in_wrong_type() {
|
2016-11-18 08:18:51 +00:00
|
|
|
use toml_ext::has_imag_version_in_main_section;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut map = BTreeMap::new();
|
|
|
|
let mut sub = BTreeMap::new();
|
|
|
|
sub.insert("version".into(), Value::Boolean(false));
|
|
|
|
map.insert("imag".into(), Value::Table(sub));
|
|
|
|
|
|
|
|
assert!(!has_imag_version_in_main_section(&map));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verification_good() {
|
2016-11-15 20:30:39 +00:00
|
|
|
use toml_ext::verify_header_consistency;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut header = BTreeMap::new();
|
|
|
|
let sub = {
|
|
|
|
let mut sub = BTreeMap::new();
|
|
|
|
sub.insert("version".into(), Value::String(String::from("0.0.0")));
|
|
|
|
|
|
|
|
Value::Table(sub)
|
|
|
|
};
|
|
|
|
|
|
|
|
header.insert("imag".into(), sub);
|
|
|
|
|
|
|
|
assert!(verify_header_consistency(header).is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verification_invalid_versionstring() {
|
2016-11-15 20:30:39 +00:00
|
|
|
use toml_ext::verify_header_consistency;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut header = BTreeMap::new();
|
|
|
|
let sub = {
|
|
|
|
let mut sub = BTreeMap::new();
|
|
|
|
sub.insert("version".into(), Value::String(String::from("000")));
|
|
|
|
|
|
|
|
Value::Table(sub)
|
|
|
|
};
|
|
|
|
|
|
|
|
header.insert("imag".into(), sub);
|
|
|
|
|
|
|
|
assert!(!verify_header_consistency(header).is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_verification_current_version() {
|
2016-11-15 20:30:39 +00:00
|
|
|
use toml_ext::verify_header_consistency;
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
let mut header = BTreeMap::new();
|
|
|
|
let sub = {
|
|
|
|
let mut sub = BTreeMap::new();
|
2016-02-04 16:38:20 +00:00
|
|
|
sub.insert("version".into(), Value::String(String::from(version!())));
|
2016-01-23 15:26:02 +00:00
|
|
|
|
|
|
|
Value::Table(sub)
|
|
|
|
};
|
|
|
|
|
|
|
|
header.insert("imag".into(), sub);
|
|
|
|
|
|
|
|
assert!(verify_header_consistency(header).is_ok());
|
|
|
|
}
|
2016-01-24 15:01:37 +00:00
|
|
|
|
|
|
|
static TEST_ENTRY : &'static str = "---
|
|
|
|
[imag]
|
2016-01-24 18:55:47 +00:00
|
|
|
version = \"0.0.3\"
|
2016-01-24 15:01:37 +00:00
|
|
|
---
|
|
|
|
Hai";
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_entry_from_str() {
|
|
|
|
use super::Entry;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
println!("{}", TEST_ENTRY);
|
2016-08-25 16:03:25 +00:00
|
|
|
let entry = Entry::from_str(StoreId::new_baseless(PathBuf::from("test/foo~1.3")).unwrap(),
|
2016-01-24 15:01:37 +00:00
|
|
|
TEST_ENTRY).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(entry.content, "Hai");
|
|
|
|
}
|
|
|
|
|
2016-01-24 18:55:47 +00:00
|
|
|
#[test]
|
|
|
|
fn test_entry_to_str() {
|
|
|
|
use super::Entry;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
println!("{}", TEST_ENTRY);
|
2016-08-25 16:03:25 +00:00
|
|
|
let entry = Entry::from_str(StoreId::new_baseless(PathBuf::from("test/foo~1.3")).unwrap(),
|
2016-01-24 18:55:47 +00:00
|
|
|
TEST_ENTRY).unwrap();
|
|
|
|
let string = entry.to_str();
|
|
|
|
|
|
|
|
assert_eq!(TEST_ENTRY, string);
|
|
|
|
}
|
2016-01-24 15:01:37 +00:00
|
|
|
|
2016-02-05 13:47:06 +00:00
|
|
|
}
|
2016-01-23 15:26:02 +00:00
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod store_tests {
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
use super::Store;
|
2017-06-08 20:31:23 +00:00
|
|
|
use file_abstraction::InMemoryFileAbstraction;
|
2016-09-05 13:01:14 +00:00
|
|
|
|
2016-09-07 10:47:52 +00:00
|
|
|
pub fn get_store() -> Store {
|
2017-06-08 20:31:23 +00:00
|
|
|
let backend = Box::new(InMemoryFileAbstraction::new());
|
|
|
|
Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
|
2016-09-05 13:01:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_store_instantiation() {
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
assert_eq!(store.location, PathBuf::from("/"));
|
|
|
|
assert!(store.entries.read().unwrap().is_empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_store_create() {
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
let s = format!("test-{}", n);
|
|
|
|
let entry = store.create(PathBuf::from(s.clone())).unwrap();
|
|
|
|
assert!(entry.verify().is_ok());
|
|
|
|
let loc = entry.get_location().clone().into_pathbuf().unwrap();
|
|
|
|
assert!(loc.starts_with("/"));
|
|
|
|
assert!(loc.ends_with(s));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-17 19:46:01 +00:00
|
|
|
#[test]
|
|
|
|
fn test_store_create_with_io_backend() {
|
|
|
|
use std::io::Cursor;
|
|
|
|
use std::rc::Rc;
|
|
|
|
use std::cell::RefCell;
|
|
|
|
use serde_json::Value;
|
|
|
|
|
|
|
|
//let sink = vec![];
|
|
|
|
//let output : Cursor<&mut [u8]> = Cursor::new(&mut sink);
|
|
|
|
//let output = Rc::new(RefCell::new(output));
|
|
|
|
let output = Rc::new(RefCell::new(vec![]));
|
|
|
|
|
|
|
|
{
|
|
|
|
let store = {
|
|
|
|
use file_abstraction::stdio::StdIoFileAbstraction;
|
|
|
|
use file_abstraction::stdio::mapper::json::JsonMapper;
|
|
|
|
|
|
|
|
// Lets have an empty store as input
|
|
|
|
let mut input = Cursor::new(r#"
|
|
|
|
{ "version": "0.3.0",
|
|
|
|
"store": { }
|
|
|
|
}
|
|
|
|
"#);
|
|
|
|
|
|
|
|
let mapper = JsonMapper::new();
|
|
|
|
let backend = StdIoFileAbstraction::new(&mut input, output.clone(), mapper).unwrap();
|
|
|
|
let backend = Box::new(backend);
|
|
|
|
|
|
|
|
Store::new_with_backend(PathBuf::from("/"), None, backend).unwrap()
|
|
|
|
};
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
let s = format!("test-{}", n);
|
|
|
|
let entry = store.create(PathBuf::from(s.clone())).unwrap();
|
|
|
|
assert!(entry.verify().is_ok());
|
|
|
|
let loc = entry.get_location().clone().into_pathbuf().unwrap();
|
|
|
|
assert!(loc.starts_with("/"));
|
|
|
|
assert!(loc.ends_with(s));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let vec = Rc::try_unwrap(output).unwrap().into_inner();
|
|
|
|
|
|
|
|
let errstr = format!("Not UTF8: '{:?}'", vec);
|
|
|
|
let string = String::from_utf8(vec);
|
|
|
|
assert!(string.is_ok(), errstr);
|
|
|
|
let string = string.unwrap();
|
|
|
|
|
|
|
|
assert!(!string.is_empty(), format!("Expected not to be empty: '{}'", string));
|
|
|
|
|
|
|
|
let json : ::serde_json::Value = ::serde_json::from_str(&string).unwrap();
|
|
|
|
|
|
|
|
match json {
|
|
|
|
Value::Object(ref map) => {
|
|
|
|
assert!(map.get("version").is_some(), format!("No 'version' in JSON"));
|
|
|
|
match map.get("version").unwrap() {
|
|
|
|
&Value::String(ref s) => assert_eq!("0.3.0", s),
|
|
|
|
_ => panic!("Wrong type in JSON at 'version'"),
|
|
|
|
}
|
|
|
|
|
|
|
|
assert!(map.get("store").is_some(), format!("No 'store' in JSON"));
|
|
|
|
match map.get("store").unwrap() {
|
|
|
|
&Value::Object(ref objs) => {
|
|
|
|
for n in 1..100 {
|
|
|
|
let s = format!("/test-{}", n);
|
|
|
|
assert!(objs.get(&s).is_some(), format!("No entry: '{}'", s));
|
|
|
|
match objs.get(&s).unwrap() {
|
|
|
|
&Value::Object(ref entry) => {
|
|
|
|
match entry.get("header").unwrap() {
|
|
|
|
&Value::Object(_) => assert!(true),
|
|
|
|
_ => panic!("Wrong type in JSON at 'store.'{}'.header'", s),
|
|
|
|
}
|
|
|
|
|
|
|
|
match entry.get("content").unwrap() {
|
|
|
|
&Value::String(_) => assert!(true),
|
|
|
|
_ => panic!("Wrong type in JSON at 'store.'{}'.content'", s),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => panic!("Wrong type in JSON at 'store.'{}''", s),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => panic!("Wrong type in JSON at 'store'"),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => panic!("Wrong type in JSON at top level"),
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
#[test]
|
2016-09-20 14:43:32 +00:00
|
|
|
fn test_store_get_create_get_delete_get() {
|
2016-09-05 13:01:14 +00:00
|
|
|
let store = get_store();
|
|
|
|
|
2016-09-20 14:43:32 +00:00
|
|
|
for n in 1..100 {
|
|
|
|
let res = store.get(PathBuf::from(format!("test-{}", n)));
|
|
|
|
assert!(match res { Ok(None) => true, _ => false, })
|
|
|
|
}
|
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
for n in 1..100 {
|
|
|
|
let s = format!("test-{}", n);
|
|
|
|
let entry = store.create(PathBuf::from(s.clone())).unwrap();
|
2016-09-20 14:43:32 +00:00
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
assert!(entry.verify().is_ok());
|
2016-09-20 14:43:32 +00:00
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
let loc = entry.get_location().clone().into_pathbuf().unwrap();
|
2016-09-20 14:43:32 +00:00
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
assert!(loc.starts_with("/"));
|
|
|
|
assert!(loc.ends_with(s));
|
|
|
|
}
|
|
|
|
|
|
|
|
for n in 1..100 {
|
2016-09-20 14:43:32 +00:00
|
|
|
let res = store.get(PathBuf::from(format!("test-{}", n)));
|
|
|
|
assert!(match res { Ok(Some(_)) => true, _ => false, })
|
2016-09-05 13:01:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for n in 1..100 {
|
2016-09-20 14:43:32 +00:00
|
|
|
assert!(store.delete(PathBuf::from(format!("test-{}", n))).is_ok())
|
|
|
|
}
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
let res = store.get(PathBuf::from(format!("test-{}", n)));
|
|
|
|
assert!(match res { Ok(None) => true, _ => false, })
|
2016-09-05 13:01:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_store_create_twice() {
|
|
|
|
use error::StoreErrorKind as SEK;
|
|
|
|
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
let s = format!("test-{}", n % 50);
|
|
|
|
store.create(PathBuf::from(s.clone()))
|
|
|
|
.map_err(|e| assert!(is_match!(e.err_type(), SEK::CreateCallError) && n >= 50))
|
|
|
|
.ok()
|
|
|
|
.map(|entry| {
|
|
|
|
assert!(entry.verify().is_ok());
|
|
|
|
let loc = entry.get_location().clone().into_pathbuf().unwrap();
|
|
|
|
assert!(loc.starts_with("/"));
|
|
|
|
assert!(loc.ends_with(s));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-05 13:20:55 +00:00
|
|
|
#[test]
|
|
|
|
fn test_store_create_in_hm() {
|
|
|
|
use storeid::StoreId;
|
|
|
|
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
let pb = StoreId::new_baseless(PathBuf::from(format!("test-{}", n))).unwrap();
|
|
|
|
|
|
|
|
assert!(store.entries.read().unwrap().get(&pb).is_none());
|
|
|
|
assert!(store.create(pb.clone()).is_ok());
|
|
|
|
|
|
|
|
let pb = pb.with_base(store.path().clone());
|
|
|
|
assert!(store.entries.read().unwrap().get(&pb).is_some());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-05 13:22:14 +00:00
|
|
|
#[test]
|
|
|
|
fn test_store_retrieve_in_hm() {
|
|
|
|
use storeid::StoreId;
|
|
|
|
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
let pb = StoreId::new_baseless(PathBuf::from(format!("test-{}", n))).unwrap();
|
|
|
|
|
|
|
|
assert!(store.entries.read().unwrap().get(&pb).is_none());
|
|
|
|
assert!(store.retrieve(pb.clone()).is_ok());
|
|
|
|
|
|
|
|
let pb = pb.with_base(store.path().clone());
|
|
|
|
assert!(store.entries.read().unwrap().get(&pb).is_some());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-19 09:01:56 +00:00
|
|
|
#[test]
|
|
|
|
fn test_get_none() {
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
match store.get(PathBuf::from(format!("test-{}", n))) {
|
|
|
|
Ok(None) => assert!(true),
|
|
|
|
_ => assert!(false),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-19 09:03:38 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_delete_none() {
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
match store.delete(PathBuf::from(format!("test-{}", n))) {
|
|
|
|
Err(_) => assert!(true),
|
|
|
|
_ => assert!(false),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-19 09:18:49 +00:00
|
|
|
|
|
|
|
// Disabled because we cannot test this by now, as we rely on glob() in
|
|
|
|
// Store::retieve_for_module(), which accesses the filesystem and tests run in-memory, so there
|
|
|
|
// are no files on the filesystem in this test after Store::create().
|
|
|
|
//
|
|
|
|
// #[test]
|
|
|
|
// fn test_retrieve_for_module() {
|
|
|
|
// let pathes = vec![
|
|
|
|
// "foo/1", "foo/2", "foo/3", "foo/4", "foo/5",
|
|
|
|
// "bar/1", "bar/2", "bar/3", "bar/4", "bar/5",
|
|
|
|
// "bla/1", "bla/2", "bla/3", "bla/4", "bla/5",
|
|
|
|
// "boo/1", "boo/2", "boo/3", "boo/4", "boo/5",
|
|
|
|
// "glu/1", "glu/2", "glu/3", "glu/4", "glu/5",
|
|
|
|
// ];
|
|
|
|
|
|
|
|
// fn test(store: &Store, modulename: &str) {
|
|
|
|
// use std::path::Component;
|
|
|
|
// use storeid::StoreId;
|
|
|
|
|
|
|
|
// let retrieved = store.retrieve_for_module(modulename);
|
|
|
|
// assert!(retrieved.is_ok());
|
|
|
|
// let v : Vec<StoreId> = retrieved.unwrap().collect();
|
|
|
|
// println!("v = {:?}", v);
|
|
|
|
// assert!(v.len() == 5);
|
|
|
|
|
|
|
|
// let retrieved = store.retrieve_for_module(modulename);
|
|
|
|
// assert!(retrieved.is_ok());
|
|
|
|
|
|
|
|
// assert!(retrieved.unwrap().all(|e| {
|
|
|
|
// let first = e.components().next();
|
|
|
|
// assert!(first.is_some());
|
|
|
|
// match first.unwrap() {
|
|
|
|
// Component::Normal(s) => s == modulename,
|
|
|
|
// _ => false,
|
|
|
|
// }
|
|
|
|
// }))
|
|
|
|
// }
|
|
|
|
|
|
|
|
// let store = get_store();
|
|
|
|
// for path in pathes {
|
|
|
|
// assert!(store.create(PathBuf::from(path)).is_ok());
|
|
|
|
// }
|
|
|
|
|
|
|
|
// test(&store, "foo");
|
|
|
|
// test(&store, "bar");
|
|
|
|
// test(&store, "bla");
|
|
|
|
// test(&store, "boo");
|
|
|
|
// test(&store, "glu");
|
|
|
|
// }
|
|
|
|
|
2016-09-07 10:36:03 +00:00
|
|
|
#[test]
|
|
|
|
fn test_store_move_moves_in_hm() {
|
|
|
|
use storeid::StoreId;
|
|
|
|
|
|
|
|
let store = get_store();
|
|
|
|
|
|
|
|
for n in 1..100 {
|
|
|
|
if n % 2 == 0 { // every second
|
|
|
|
let id = StoreId::new_baseless(PathBuf::from(format!("t-{}", n))).unwrap();
|
|
|
|
let id_mv = StoreId::new_baseless(PathBuf::from(format!("t-{}", n - 1))).unwrap();
|
|
|
|
|
|
|
|
{
|
|
|
|
assert!(store.entries.read().unwrap().get(&id).is_none());
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
assert!(store.create(id.clone()).is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let id_with_base = id.clone().with_base(store.path().clone());
|
|
|
|
assert!(store.entries.read().unwrap().get(&id_with_base).is_some());
|
|
|
|
}
|
|
|
|
|
|
|
|
let r = store.move_by_id(id.clone(), id_mv.clone());
|
|
|
|
assert!(r.map_err(|e| println!("ERROR: {:?}", e)).is_ok());
|
|
|
|
|
|
|
|
{
|
2016-09-22 06:39:00 +00:00
|
|
|
let id_mv_with_base = id_mv.clone().with_base(store.path().clone());
|
|
|
|
assert!(store.entries.read().unwrap().get(&id_mv_with_base).is_some());
|
2016-09-07 10:36:03 +00:00
|
|
|
}
|
2016-10-03 10:33:38 +00:00
|
|
|
|
|
|
|
assert!(match store.get(id.clone()) { Ok(None) => true, _ => false },
|
|
|
|
"Moved id ({:?}) is still there", id);
|
|
|
|
assert!(match store.get(id_mv.clone()) { Ok(Some(_)) => true, _ => false },
|
|
|
|
"New id ({:?}) is not in store...", id_mv);
|
2016-09-07 10:36:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-05 13:01:14 +00:00
|
|
|
}
|
|
|
|
|