├── .gitignore ├── .travis.yml ├── Cargo.png ├── Cargo.toml ├── examples ├── basic_file.rs ├── basic_web.rs ├── multi.rs └── static_resources.rs ├── readme.dev.md ├── readme.md └── src ├── iostore.rs ├── lib.rs ├── multi_store.rs ├── static_store.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *.swp 4 | *.swo 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: nightly -------------------------------------------------------------------------------- /Cargo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PistonDevelopers/asset_store/15c344dfa00d3741b442c43a7faf63e4aa8d8db0/Cargo.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asset_store" 3 | version = "0.0.2" 4 | authors = ["Ty Overby "] 5 | 6 | [dependencies.hyper] 7 | version = "0.5.2" 8 | optional = true 9 | 10 | [dependencies] 11 | resources_package = "0.0.8" 12 | resources_package_package = "0.0.6" 13 | 14 | [features] 15 | default = ["url"] 16 | url = ["hyper"] 17 | 18 | -------------------------------------------------------------------------------- /examples/basic_file.rs: -------------------------------------------------------------------------------- 1 | extern crate asset_store; 2 | 3 | use asset_store::from_directory; 4 | use asset_store::AssetStore; 5 | 6 | fn to_string(bytes: &[u8]) -> String { 7 | String::from_utf8_lossy(bytes).into_owned() 8 | } 9 | 10 | fn main() { 11 | // Make a new asset store from this examples directory. 12 | let store = from_directory("./examples/"); 13 | // Asynchronously load this file. 14 | store.load("basic_file.rs"); 15 | 16 | // Block until the file is loaded. 17 | let contents = store.map_resource_block("basic_file.rs", to_string); 18 | // Print the bytes of the file. 19 | println!("{:?}", contents); 20 | } 21 | -------------------------------------------------------------------------------- /examples/basic_web.rs: -------------------------------------------------------------------------------- 1 | extern crate asset_store; 2 | 3 | use asset_store::from_url; 4 | use asset_store::AssetStore; 5 | 6 | fn to_string(bytes: &[u8]) -> String { 7 | String::from_utf8_lossy(bytes).into_owned() 8 | } 9 | 10 | fn main() { 11 | // Make a new asset store with google as the root 12 | let store = from_url("http://www.google.com/"); 13 | // Asynchronously load this file. 14 | store.load("basic_file.rs"); 15 | 16 | // Block until the file is loaded. 17 | let contents = store.map_resource_block("robots.txt", to_string); 18 | // Print the bytes of the file. 19 | println!("{}", contents.unwrap()); 20 | } 21 | -------------------------------------------------------------------------------- /examples/multi.rs: -------------------------------------------------------------------------------- 1 | fn main() {} 2 | 3 | /* 4 | extern crate asset_store; 5 | 6 | use asset_store::from_url; 7 | use asset_store::from_directory; 8 | use asset_store::AssetStore; 9 | use asset_store::MultiStore; 10 | 11 | fn id(a:A) -> A { a } 12 | 13 | fn to_string(bytes: &[u8]) -> String { 14 | String::from_utf8_lossy(bytes).into_string() 15 | } 16 | 17 | fn main() { 18 | // Create a file store for the local file system 19 | let file_store = from_directory("./examples/"); 20 | // Create a file store for the google 21 | let web_store = from_url("http://www.google.com/"); 22 | 23 | // Make a MultiStore to combine all our other storage methods 24 | let mut combo = MultiStore::new(); 25 | combo.add("web", web_store, id); 26 | combo.add("file", file_store, id); 27 | 28 | combo.load("file:multi.rs"); 29 | combo.load("web:robots.txt"); 30 | 31 | { 32 | let robots = combo.map_resource_block("web:robots.txt", to_string); 33 | println!("{}", robots.unwrap()); 34 | } { 35 | let multi = combo.map_resource_block("file:multi.rs", to_string); 36 | println!("{}", multi.unwrap()); 37 | } 38 | 39 | } 40 | */ 41 | -------------------------------------------------------------------------------- /examples/static_resources.rs: -------------------------------------------------------------------------------- 1 | // #![feature(phase)] 2 | 3 | // #[phase(plugin)] 4 | // extern crate resources_package; 5 | // extern crate resources_package_package; 6 | // extern crate asset_store; 7 | 8 | // use resources_package_package::Package; 9 | 10 | // use asset_store::StaticStore; 11 | // use asset_store::AssetStore; 12 | 13 | // fn to_string(bytes: &[u8]) -> String { 14 | // String::from_utf8_lossy(bytes).into_string() 15 | // } 16 | 17 | // // Store all .rs files in the examples directory in the 18 | // // binary during compilation 19 | // static PACKAGE: Package = 20 | // resources_package!([ 21 | // "./" 22 | // ] 23 | // ); 24 | 25 | // fn main() { 26 | // // Use an in memory store. 27 | // let store = StaticStore::new(&PACKAGE); 28 | 29 | // // Load the file right out of memory. 30 | // let stat = store.map_resource_block("static_resources.rs", to_string); 31 | // println!("{}", stat.unwrap()); 32 | // } 33 | 34 | fn main() { 35 | 36 | } -------------------------------------------------------------------------------- /readme.dev.md: -------------------------------------------------------------------------------- 1 | # Asset Store [![Build Status](https://travis-ci.org/PistonDevelopers/asset_store.svg?branch=master)](https://travis-ci.org/PistonDevelopers/asset_store) 2 | 3 | [Api Documentation](http://www.piston.rs/asset_store/asset_store/trait.AssetStore.html) 4 | 5 | A unified method for easily reading and caching files over the filesystem 6 | and network. 7 | 8 | Calls to `load()` process asynchronously, so it is possible to load files 9 | from different sources in parallel. 10 | 11 | ### Read files from disk 12 | 13 | When reading a files out of a directory store, it is impossible to read outside 14 | of the directory specified. 15 | 16 | ^code(examples/basic_file.rs) 17 | 18 | ### Read files over http 19 | 20 | You can also read files off of a web server. 21 | 22 | ^code(examples/basic_web.rs) 23 | 24 | ### Read files out of memory 25 | 26 | If your files are small enough, you can bundle them into your binary by using 27 | [resources-package](https://github.com/tomaka/rust-package.git). 28 | 29 | ^code(examples/static_resources.rs) 30 | 31 | ### Combine different stores into one. 32 | 33 | Having multiple stores laying around for different sources is a pain, so 34 | by combining them into one and using prefixes you can access many 35 | file stores of different types. 36 | 37 | ^code(examples/multi.rs) 38 | 39 | ## Dependencies 40 | 41 | ![dependencies](./Cargo.png) 42 | 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Asset Store [![Build Status](https://travis-ci.org/PistonDevelopers/asset_store.svg?branch=master)](https://travis-ci.org/PistonDevelopers/asset_store) 2 | 3 | [Api Documentation](http://www.piston.rs/asset_store/asset_store/trait.AssetStore.html) 4 | 5 | A unified method for easily reading and caching files over the filesystem 6 | and network. 7 | 8 | Calls to `load()` process asynchronously, so it is possible to load files 9 | from different sources in parallel. 10 | 11 | ### Read files from disk 12 | 13 | When reading a files out of a directory store, it is impossible to read outside 14 | of the directory specified. 15 | 16 | ```rust 17 | extern crate asset_store; 18 | 19 | use asset_store::from_directory; 20 | use asset_store::AssetStore; 21 | 22 | fn to_string(bytes: &[u8]) -> String { 23 | String::from_utf8_lossy(bytes).into_string() 24 | } 25 | 26 | fn main() { 27 | // Make a new asset store from this examples directory. 28 | let store = from_directory("./examples/"); 29 | // Asynchronously load this file. 30 | store.load("basic_file.rs"); 31 | 32 | // Block until the file is loaded. 33 | let contents = store.map_resource_block("basic_file.rs", to_string); 34 | // Print the bytes of the file. 35 | println!("{}", contents); 36 | } 37 | 38 | ``` 39 | 40 | ### Read files over http 41 | 42 | You can also read files off of a web server. 43 | 44 | ```rust 45 | extern crate asset_store; 46 | 47 | use asset_store::from_url; 48 | use asset_store::AssetStore; 49 | 50 | fn to_string(bytes: &[u8]) -> String { 51 | String::from_utf8_lossy(bytes).into_string() 52 | } 53 | 54 | fn main() { 55 | // Make a new asset store with google as the root 56 | let store = from_url("http://www.google.com/"); 57 | // Asynchronously load this file. 58 | store.load("basic_file.rs"); 59 | 60 | // Block until the file is loaded. 61 | let contents = store.map_resource_block("robots.txt", to_string); 62 | // Print the bytes of the file. 63 | println!("{}", contents.unwrap()); 64 | } 65 | 66 | ``` 67 | 68 | ### Read files out of memory 69 | 70 | If your files are small enough, you can bundle them into your binary by using 71 | [resources-package](https://github.com/tomaka/rust-package.git). 72 | 73 | ```rust 74 | #![feature(phase)] 75 | 76 | #[phase(plugin)] 77 | extern crate resources_package; 78 | extern crate resources_package_package; 79 | extern crate asset_store; 80 | 81 | use resources_package_package::Package; 82 | 83 | use asset_store::StaticStore; 84 | use asset_store::AssetStore; 85 | 86 | fn to_string(bytes: &[u8]) -> String { 87 | String::from_utf8_lossy(bytes).into_string() 88 | } 89 | 90 | // Store all .rs files in the examples directory in the 91 | // binary during compilation 92 | static PACKAGE: Package = 93 | resources_package!([ 94 | "./" 95 | ] 96 | ); 97 | 98 | fn main() { 99 | // Use an in memory store. 100 | let store = StaticStore::new(&PACKAGE); 101 | 102 | // Load the file right out of memory. 103 | let stat = store.map_resource_block("static_resources.rs", to_string); 104 | println!("{}", stat.unwrap()); 105 | } 106 | 107 | ``` 108 | 109 | ### Combine different stores into one. 110 | 111 | Having multiple stores laying around for different sources is a pain, so 112 | by combining them into one and using prefixes you can access many 113 | file stores of different types. 114 | 115 | ```rust 116 | extern crate asset_store; 117 | 118 | use asset_store::from_url; 119 | use asset_store::from_directory; 120 | use asset_store::AssetStore; 121 | use asset_store::MultiStore; 122 | 123 | fn id(a:A) -> A { a } 124 | 125 | fn to_string(bytes: &[u8]) -> String { 126 | String::from_utf8_lossy(bytes).into_string() 127 | } 128 | 129 | fn main() { 130 | // Create a file store for the local file system 131 | let file_store = from_directory("./examples/"); 132 | // Create a file store for the google 133 | let web_store = from_url("http://www.google.com/"); 134 | 135 | // Make a MultiStore to combine all our other storage methods 136 | let mut combo = MultiStore::new(); 137 | combo.add("web", web_store, id); 138 | combo.add("file", file_store, id); 139 | 140 | combo.load("file:multi.rs"); 141 | combo.load("web:robots.txt"); 142 | 143 | { 144 | let robots = combo.map_resource_block("web:robots.txt", to_string); 145 | println!("{}", robots.unwrap()); 146 | } { 147 | let multi = combo.map_resource_block("file:multi.rs", to_string); 148 | println!("{}", multi.unwrap()); 149 | } 150 | 151 | } 152 | 153 | ``` 154 | 155 | ## Dependencies 156 | 157 | ![dependencies](./Cargo.png) 158 | 159 | [How to contribute](https://github.com/PistonDevelopers/piston/blob/master/CONTRIBUTING.md) 160 | -------------------------------------------------------------------------------- /src/iostore.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::convert::From; 3 | use std::error::Error; 4 | use std::io::ErrorKind; 5 | use std::fs::File; 6 | #[allow(unused_imports)] use std::path::{self, Path, PathBuf}; 7 | use std::string::String; 8 | use std::sync::{Arc, RwLock}; 9 | #[allow(unused_imports)] use std::thread::{self, spawn, sleep_ms}; 10 | use hyper::client::{Client, Response}; 11 | use hyper::status::StatusCode; 12 | use hyper::Url; 13 | 14 | use std::io::Error as IoError; 15 | use std::io::Result as IoResult; 16 | 17 | // use hyper::Url; 18 | // use hyper::client::Response; 19 | // use hyper::Client; 20 | // use hyper::status::StatusCode; 21 | 22 | 23 | use super::AssetStore; 24 | 25 | type DistMap = Arc>>>>; 26 | pub trait IoBackend { 27 | fn go_get(&self, path: &str, mem: DistMap); 28 | } 29 | 30 | pub struct IoStore { 31 | backend: Backend, 32 | mem: DistMap, 33 | //awaiting: HashSet 34 | } 35 | 36 | pub fn from_directory(path: &str) -> IoStore { 37 | let path = PathBuf::from(String::from(path)); 38 | IoStore { 39 | backend: FsBackend { path: path }, 40 | mem: Arc::new(RwLock::new(HashMap::new())), 41 | //awaiting: HashSet::new(), 42 | } 43 | } 44 | 45 | impl AssetStore for IoStore { 46 | fn load(&self, path: &str) { 47 | //if !self.awaiting.contains_equiv(&path) { 48 | self.backend.go_get(path, self.mem.clone()); 49 | //} 50 | //self.awaiting.insert(path.to_string()); 51 | } 52 | 53 | fn is_loaded(&self, path: &str) -> Result { 54 | let mem = match self.mem.read() { 55 | Ok(mem) => { mem }, 56 | Err(_) => { return Err(IoError::new(ErrorKind::Other, "Poisoned thread")); } 57 | }; 58 | 59 | match mem.get(path) { 60 | Some(&Ok(_)) => Ok(true), 61 | Some(&Err(ref e)) => Err(IoError::new(e.kind(), e.description().clone())), 62 | None => Ok(false) 63 | } 64 | // Ok(true) 65 | } 66 | 67 | fn unload(&self, path: &str) { 68 | match self.mem.write() { 69 | Ok(mut mem) => { mem.remove(path); }, 70 | Err(_) => { } 71 | } 72 | } 73 | 74 | fn unload_everything(&self) { 75 | match self.mem.write() { 76 | Ok(mut mem) => { mem.clear(); }, 77 | Err(_) => { } 78 | } 79 | } 80 | 81 | fn map_resource(&self , path: &str, mapfn: F) -> 82 | Result, IoError> where F: Fn(&[u8]) -> O { 83 | 84 | let mem = match self.mem.read() { 85 | Ok(mem) => { mem }, 86 | Err(_) => { return Err(IoError::new(ErrorKind::Other, "Poisoned thread")); } 87 | }; 88 | 89 | match mem.get(path) { 90 | Some(&Ok(ref v)) => Ok(Some((mapfn)(&v[..]))), 91 | Some(&Err(ref e)) => Err(IoError::new(e.kind(), e.description().clone())), 92 | None => Ok(None) 93 | } 94 | } 95 | 96 | fn map_resource_block(&self , path: &str, mapfn: F) -> 97 | IoResult where F: Fn(&[u8]) -> O { 98 | self.load(path); 99 | loop { 100 | { 101 | return match self.map_resource(path, |x| mapfn(x)) { 102 | Ok(Some(v)) => Ok(v), 103 | Err(e) => Err(e), 104 | Ok(None) => { continue; } 105 | } 106 | } 107 | //sleep(Duration::milliseconds(0)); 108 | sleep_ms(0); 109 | } 110 | } 111 | } 112 | 113 | pub struct FsBackend { 114 | path: PathBuf, 115 | } 116 | 117 | impl FsBackend { 118 | fn process>(path: P, filen: String) -> (String, IoResult>) { 119 | use std::fs::PathExt; 120 | use std::io::Read; 121 | 122 | let mut base = path.as_ref().to_path_buf(); 123 | base.push(filen.clone()); 124 | 125 | // is the path valid? 126 | if !base.exists() { 127 | return ( 128 | filen.clone(), 129 | Err( 130 | IoError::new( 131 | ErrorKind::NotFound, 132 | format!("Given path does not exist: {} does not contain {}", match path.as_ref().to_str() { 133 | Some(s) => { s }, 134 | None => { "{Bad Path}"} 135 | }, filen) 136 | ) 137 | ) 138 | ); 139 | } 140 | 141 | match File::open(&base) { 142 | Ok(mut f) => { 143 | let mut buf: Vec = Vec::new(); 144 | match f.read_to_end(&mut buf) { 145 | Ok(_) => { (filen, Ok(buf)) } 146 | Err(e) => { (filen, Err(e)) } 147 | } 148 | }, 149 | Err(e) => { (filen, Err(e)) } 150 | } 151 | } 152 | } 153 | 154 | impl IoBackend for FsBackend { 155 | fn go_get(&self, file: &str, mem: DistMap) { 156 | let path = self.path.clone(); 157 | let file = file.to_string(); 158 | thread::spawn(move || { 159 | let (file, bytes) = FsBackend::process(path, file); 160 | if let Ok(mut mem) = mem.write() { 161 | mem.insert(file, bytes); 162 | }; 163 | }); 164 | } 165 | } 166 | 167 | pub fn from_url(base: &str) -> IoStore { 168 | IoStore { 169 | backend: NetBackend { base: base.to_string() }, 170 | mem: Arc::new(RwLock::new(HashMap::new())), 171 | //awaiting: HashSet::new(), 172 | } 173 | } 174 | 175 | pub struct NetBackend { 176 | base: String 177 | } 178 | 179 | impl NetBackend { 180 | fn http_get(path: &String) -> Result { 181 | let url = match Url::parse(&path) { 182 | Ok(url) => url, 183 | Err(parse_err) => return Err( 184 | format!("Error parsing url: {}", parse_err) 185 | ), 186 | }; 187 | 188 | let mut client = Client::new(); 189 | let request = client.get(url); 190 | 191 | request.send().map_err(|e| e.to_string()) 192 | } 193 | } 194 | 195 | impl IoBackend for NetBackend { 196 | fn go_get(&self, file: &str, mem: DistMap) { 197 | use std::io::Read; 198 | 199 | let path = vec![self.base.clone(), file.to_string()].concat(); 200 | let file = file.to_string(); 201 | spawn(move || { 202 | let mut res = match NetBackend::http_get(&path) { 203 | Ok(res) => res, 204 | Err(err) => { 205 | let error = Err(IoError::new( 206 | ErrorKind::Other, 207 | format!("Error fetching file over http {}: {}", path, err) 208 | )); 209 | let mut map = mem.write().unwrap(); 210 | map.insert(file, error); 211 | return; 212 | } 213 | }; 214 | 215 | if res.status == StatusCode::Ok { 216 | let mut map = mem.write().unwrap(); 217 | let mut data = vec![]; 218 | let res = res.read_to_end(&mut data); 219 | map.insert(file, res.map(|_| data)); 220 | } else { 221 | let error = Err(IoError::new( 222 | ErrorKind::Other, 223 | format!("Error fetching file over http {}: {}", path, res.status) 224 | )); 225 | let mut map = mem.write().unwrap(); 226 | map.insert(file, error); 227 | } 228 | }); 229 | } 230 | } 231 | 232 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(path_ext)] 2 | 3 | extern crate resources_package_package; 4 | #[allow(plugin_as_library)] 5 | extern crate resources_package; 6 | extern crate hyper; 7 | 8 | pub use iostore::{ 9 | IoStore, 10 | FsBackend, 11 | NetBackend, 12 | from_directory, 13 | from_url, 14 | }; 15 | 16 | // pub use multi_store::{ 17 | // MultiStore, 18 | // MultiStoreError, 19 | // }; 20 | pub use static_store::{ 21 | StaticStore, 22 | StaticStoreError 23 | }; 24 | 25 | mod multi_store; 26 | mod iostore; 27 | mod static_store; 28 | 29 | // #[cfg(test)] 30 | // mod test; 31 | 32 | pub trait AssetStore { 33 | /// Tell the asset store to begin loading a resource. 34 | fn load(&self, path: &str); 35 | /// Tell the asset store to begin loading all resources. 36 | fn load_all<'a, I: Iterator>(&self, paths: I) where Self: Sized{ 37 | let paths = paths; 38 | for s in paths { 39 | self.load(s); 40 | } 41 | } 42 | 43 | /// Check to see if a resource has been loaded or not. 44 | fn is_loaded(&self, path: &str) -> Result; 45 | /// Check to see if everything has been loaded. 46 | fn all_loaded<'a, I: Iterator>(&self, paths: I) -> 47 | Result> where Self: Sized { 48 | let paths = paths; 49 | let mut status = true; 50 | let mut errs = vec![]; 51 | for p in paths { 52 | match self.is_loaded(p) { 53 | Ok(b) => { 54 | status &= b; 55 | } 56 | Err(e) => { 57 | errs.push((p, e)); 58 | } 59 | } 60 | } 61 | if errs.len() == 0 { 62 | Ok(status) 63 | } else { 64 | Err(errs) 65 | } 66 | } 67 | 68 | /// Remove this resouce from this asset store if it is loaded. 69 | fn unload(&self, path: &str); 70 | /// Remove all these resouces from this asset store if they 71 | /// are loaded. 72 | fn unload_all<'a, I: Iterator>(&self, paths: I) where Self: Sized { 73 | let paths = paths; 74 | for p in paths { 75 | self.unload(p); 76 | } 77 | } 78 | /// Remove every resouce from this asset store 79 | fn unload_everything(&self); 80 | 81 | /// Given a path to a resource and a transformation function, 82 | /// returns the result of the transformation function applied 83 | /// to the bytes of the resource if that resource is loaded. 84 | /// 85 | /// Returns `Ok(value)` if the resource is loaded and where `value` 86 | /// is the result of the transformation. 87 | /// Returns Ok(None) if the resource is not yet loaded. 88 | /// Returns Err(e) if the resource failed to open with an error. 89 | fn map_resource(&self , path: &str, mapfn: F) -> Result, E> 90 | where F: Fn(&[u8]) -> O, Self: Sized; 91 | 92 | /// See `map_resource`. This function blocks on read, so the only 93 | /// possible return values are `Ok(value)`, or `Err(e)`. 94 | fn map_resource_block(&self , path: &str, mapfn: F) -> Result 95 | where F: Fn(&[u8]) -> O, Self: Sized; 96 | 97 | /// Similar to map_resource, the user provides a path and a 98 | /// function. The function is run only if the file is loaded 99 | /// without error. The return value of the provided function 100 | /// is ignored, and a status is returned in the format given by 101 | /// map_resource, but with the uint `()` value in place of 102 | /// a mapped value. 103 | fn with_bytes(&self, path:&str, with_fn: F) -> 104 | Result, E> where F: Fn(&[u8]) -> (), Self: Sized { 105 | self.map_resource(path, with_fn) 106 | } 107 | 108 | /// The same as `with_bytes_block` but blocking. 109 | fn with_bytes_block(&self, path:&str, with_fn: F) -> 110 | Result<(), E> where F: Fn(&[u8]) -> (), Self: Sized { 111 | self.map_resource_block(path, with_fn) 112 | } 113 | } 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/multi_store.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::marker::PhantomData; 3 | 4 | use super::AssetStore; 5 | use self::MultiStoreError::*; 6 | 7 | #[derive(Debug)] 8 | pub enum MultiStoreError { 9 | NoSplit, 10 | StoreNotFound(String), 11 | WrappedError(E) 12 | } 13 | 14 | struct StoreWrapper T> { 15 | store: S, 16 | trans: F, 17 | _e: PhantomData<*const E>, 18 | _t: PhantomData<*const T> 19 | } 20 | 21 | impl T> StoreWrapper { 22 | fn new(st: S, tr: F) -> StoreWrapper { 23 | StoreWrapper { 24 | store: st, 25 | trans: tr, 26 | _e: PhantomData, 27 | _t: PhantomData 28 | } 29 | } 30 | } 31 | 32 | impl , E, T, F: Fn(E) -> T> AssetStore for StoreWrapper { 33 | fn load(&self, path: &str) { 34 | self.store.load(path); 35 | } 36 | 37 | fn load_all<'a, I: Iterator>(&self, paths: I) { 38 | self.store.load_all(paths); 39 | } 40 | 41 | fn is_loaded(&self, path: &str) -> Result { 42 | self.store.is_loaded(path).map_err(|e| (self.trans)(e)) 43 | } 44 | 45 | fn all_loaded<'a, I: Iterator>(&self, paths: I) -> Result> { 46 | let res = self.store.all_loaded(paths); 47 | match res { 48 | Ok(b) => Ok(b), 49 | Err(errs) => 50 | Err(errs 51 | .into_iter() 52 | .map(|(n, e)| (n, (self.trans)(e))) 53 | .collect()) 54 | } 55 | } 56 | 57 | fn unload(&self, path: &str) { 58 | self.store.unload(path); 59 | } 60 | 61 | fn unload_all<'a, I: Iterator>(&self, paths: I) { 62 | self.store.unload_all(paths); 63 | } 64 | 65 | fn unload_everything(&self) { 66 | self.store.unload_everything(); 67 | } 68 | 69 | fn map_resource(&self , path: &str, mapfn: M) -> Result, T> 70 | where M: Fn(&[u8]) -> O { 71 | 72 | self.store.map_resource(path, mapfn).map_err(|x| (self.trans)(x)) 73 | } 74 | 75 | fn map_resource_block(&self, path: &str, mapfn: M) -> Result 76 | where M: Fn(&[u8]) -> O { 77 | 78 | self.store.map_resource_block(path, mapfn).map_err(|x| (self.trans)(x)) 79 | } 80 | } 81 | 82 | pub struct MultiStore<'a, T> { 83 | stores: HashMap + 'a>> 84 | } 85 | 86 | impl<'a, T: 'a> MultiStore<'a, T> { 87 | pub fn new() -> MultiStore<'a, T> { 88 | MultiStore { stores: HashMap::new() } 89 | } 90 | 91 | pub fn add( 92 | &mut self, 93 | prefix: &str, 94 | store: S, 95 | tr: F 96 | ) where S: 'a + AssetStore, F: Fn(E) -> T { 97 | let wrapped = StoreWrapper::new(store, tr); 98 | self.stores.insert(prefix.to_string(), Box::new(wrapped)); 99 | } 100 | 101 | fn get_store<'b>(&self, path: &'b str) -> 102 | Result<(&Box>, &'b str), MultiStoreError> { 103 | let split: Vec<&str> = path.splitn(1, ':').collect(); 104 | if split.len() == 1 { 105 | return Err(NoSplit) 106 | } 107 | let (before, after) = (split[0], split[1]); 108 | match self.stores.get(&before.to_string()) { 109 | Some(x) => Ok((x, after)), 110 | None => Err(StoreNotFound(before.to_string())) 111 | } 112 | } 113 | } 114 | 115 | // impl<'a, T: 'a> AssetStore> for MultiStore<'a, T> { 116 | // fn load(&self, path: &str) { 117 | // match self.get_store(path) { 118 | // Ok((store, path)) => store.load(path), 119 | // Err(_) => {} 120 | // } 121 | // } 122 | 123 | // fn load_all<'b, I: Iterator>(&self, paths: I) { 124 | // let mut paths = paths; 125 | // for path in paths { 126 | // match self.get_store(path) { 127 | // Ok((store, path)) => store.load(path), 128 | // Err(_) => {} 129 | // } 130 | // } 131 | // } 132 | 133 | // fn is_loaded(&self, path: &str) -> Result> { 134 | // let (store, path) = try!(self.get_store(path)); 135 | // store.is_loaded(path).map_err(|e| WrappedError(e)) 136 | // } 137 | 138 | // fn all_loaded<'b, I: Iterator>(&self, paths: I) -> Result)>> 139 | // where Self: Sized { 140 | // let mut paths = paths; 141 | // let mut errs = Vec::new(); 142 | // let mut loaded = true; 143 | // for path in paths { 144 | // match self.get_store(path) { 145 | // Ok((store, spath)) => { 146 | // match store.is_loaded(spath) { 147 | // Ok(b) => { 148 | // loaded &= b; 149 | // } 150 | // Err(x) => { 151 | // errs.push((path, WrappedError(x))); 152 | // } 153 | // } 154 | // } 155 | // Err(x) => errs.push((path, x)) 156 | // } 157 | // } 158 | // if errs.len() > 0 { 159 | // Err(errs) 160 | // } else { 161 | // Ok(loaded) 162 | // } 163 | // } 164 | 165 | // fn unload(&self, path: &str) { 166 | // match self.get_store(path) { 167 | // Ok((store, path)) => store.unload(path), 168 | // Err(_) => {} 169 | // } 170 | // } 171 | 172 | // fn unload_all<'b, I: Iterator>(&self, paths: I) { 173 | // let mut paths = paths; 174 | // for path in paths { 175 | // match self.get_store(path) { 176 | // Ok((store, path)) => store.unload(path), 177 | // Err(_) => {} 178 | // } 179 | // } 180 | // } 181 | 182 | // fn unload_everything(&self) { 183 | // for (_, store) in self.stores.iter() { 184 | // store.unload_everything(); 185 | // } 186 | // } 187 | 188 | // fn map_resource(&self , path: &str, mapfn: F) -> Result, MultiStoreError> 189 | // where F: Fn(&[u8]) -> O, Self: Sized { 190 | 191 | // let (store, path) = try!(self.get_store(path)); 192 | // let mut ret = None; 193 | // let out = store.with_bytes(path, |bytes| { 194 | // ret = Some(mapfn(bytes)); 195 | // }); 196 | // match (out, ret) { 197 | // (Ok(Some(_)), Some(x)) => Ok(Some(x)), 198 | // (Ok(None), _) => Ok(None), 199 | // (Err(e), _) => Err(WrappedError(e)), 200 | // (Ok(Some(())), None) => unreachable!() 201 | // } 202 | // } 203 | 204 | // fn map_resource_block(&self , path: &str, mapfn: F) -> Result> 205 | // where F: Fn(&[u8]) -> O, Self: Sized { 206 | 207 | // let (store, path) = try!(self.get_store(path)); 208 | // let mut ret = None; 209 | // let out = store.with_bytes_block(path, |bytes| { 210 | // ret = Some(mapfn(bytes)); 211 | // }); 212 | // match (out, ret) { 213 | // (_, Some(x)) => Ok(x), 214 | // (Err(e), _) => Err(WrappedError(e)), 215 | // (Ok(_), None) => unreachable!() 216 | // } 217 | // } 218 | // } 219 | 220 | -------------------------------------------------------------------------------- /src/static_store.rs: -------------------------------------------------------------------------------- 1 | use resources_package_package::Package; 2 | use super::AssetStore; 3 | 4 | use std::path::Path; 5 | 6 | #[derive(Debug)] 7 | pub enum StaticStoreError { 8 | NotFound(String) 9 | } 10 | 11 | pub struct StaticStore { 12 | mem: &'static Package, 13 | } 14 | 15 | impl StaticStore { 16 | pub fn new(m: &'static Package) -> StaticStore { 17 | StaticStore{ mem: m } 18 | } 19 | 20 | fn find(&self, path: &str) -> Option<&[u8]> { 21 | self.mem.find(&Path::new(path)) 22 | } 23 | } 24 | 25 | impl AssetStore for StaticStore { 26 | fn load(&self, _: &str) { } 27 | 28 | fn is_loaded(&self, path: &str) -> Result { 29 | Ok(self.find(path).is_some()) 30 | } 31 | 32 | fn unload(&self, _: &str) { } 33 | 34 | fn unload_everything(&self) { } 35 | 36 | fn map_resource(&self , path: &str, mapfn: F) -> Result, StaticStoreError> 37 | where F : Fn(&[u8]) -> O { 38 | 39 | match self.find(path) { 40 | Some(x) => Ok(Some(mapfn(x))), 41 | None => Err(StaticStoreError::NotFound(path.to_string())) 42 | } 43 | } 44 | 45 | fn map_resource_block(&self, path: &str, mapfn: F) -> Result 46 | where F : Fn(&[u8]) -> O { 47 | 48 | match self.map_resource(path, mapfn) { 49 | Ok(Some(x)) => Ok(x), 50 | Ok(None) => unreachable!(), 51 | Err(x) => Err(x) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::{ 3 | from_directory, 4 | from_url, 5 | AssetStore, 6 | }; 7 | 8 | fn to_unit(_: A) -> () {()} 9 | 10 | #[test] 11 | fn test_load() { 12 | let store = from_directory("./src/"); 13 | store.load("test.rs"); 14 | let loaded = store.map_resource_block("test.rs", |x| to_unit(x)); 15 | assert!(loaded.is_ok()); 16 | } 17 | 18 | #[test] 19 | fn test_load_web() { 20 | let store = from_url("http://www.google.com/"); 21 | store.load("robots.txt"); 22 | let loaded = store.map_resource_block("robots.txt", |x| to_unit(x)); 23 | assert!(loaded.is_ok()); 24 | } 25 | 26 | #[test] 27 | fn test_load_all() { 28 | let store = from_directory("./src/"); 29 | store.load_all(vec!["test.rs", "lib.rs"].into_iter()); 30 | { 31 | let test = store.map_resource_block("test.rs", |x| to_unit(x)); 32 | assert!(test.is_ok()); 33 | } 34 | { 35 | let lib = store.map_resource_block("lib.rs", |x| to_unit(x)); 36 | assert!(lib.is_ok()); 37 | } 38 | } 39 | 40 | #[test] 41 | fn test_load_fail() { 42 | let store = from_directory("./src/"); 43 | store.load("foo.rs"); 44 | let loaded = store.map_resource_block("foo.rs", |x| to_unit(x)); 45 | assert!(loaded.is_err()); 46 | } 47 | 48 | #[test] 49 | fn test_load_web_fail() { 50 | let store = from_url("http://www.google.com/"); 51 | store.load("foo.rs"); 52 | let loaded = store.map_resource_block("foo.rs", |x| to_unit(x)); 53 | assert!(loaded.is_err()); 54 | } 55 | 56 | #[test] 57 | fn test_load_same() { 58 | let store = from_directory("./src/"); 59 | store.load("foo.rs"); 60 | store.load("foo.rs"); 61 | let loaded = store.map_resource_block("foo.rs", |x| to_unit(x)); 62 | assert!(loaded.is_err()); 63 | } 64 | 65 | #[test] 66 | fn test_load_web_same() { 67 | let store = from_url("http://www.google.com/"); 68 | store.load("foo.rs"); 69 | store.load("foo.rs"); 70 | let loaded = store.map_resource_block("foo.rs", |x| to_unit(x)); 71 | assert!(loaded.is_err()); 72 | } 73 | 74 | #[test] 75 | fn test_fetch_regular() { 76 | let store = from_directory("./src/"); 77 | store.load("lib.rs"); 78 | // woooo, busy loop! 79 | loop { 80 | match store.map_resource("lib.rs", |x| to_unit(x)) { 81 | Ok(Some(_)) => { break; } 82 | Ok(None) => { continue; } 83 | Err(_) => { assert!(false) } 84 | } 85 | } 86 | } 87 | 88 | #[test] 89 | fn test_fetch_web_regular() { 90 | let store = from_url("http://www.google.com/"); 91 | store.load("robots.txt"); 92 | 93 | loop { 94 | match store.map_resource("robots.txt", |x| to_unit(x)) { 95 | Ok(Some(_)) => { break; } 96 | Ok(None) => { continue; } 97 | Err(_) => { assert!(false) } 98 | } 99 | } 100 | } 101 | 102 | /* Test is flaky 103 | #[test] 104 | fn test_unload() { 105 | let store = from_directory("./src/"); 106 | 107 | store.load("lib.rs"); 108 | assert!(store.map_resource_block("lib.rs", |x| to_unit(x)).is_ok()); 109 | assert!(store.map_resource("lib.rs", |x| to_unit(x)).is_ok()); 110 | 111 | store.unload("lib.rs"); 112 | match store.map_resource("lib.rs", |x| to_unit(x)) { 113 | Ok(None) => assert!(true), 114 | _ => assert!(false) 115 | } 116 | }*/ 117 | --------------------------------------------------------------------------------