Implemented api/serve
This commit is contained in:
parent
5e23a0b5d0
commit
ac7ffa0909
7 changed files with 108 additions and 72 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -6,7 +6,6 @@ dependencies = [
|
||||||
"mount 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mount 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"router 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"router 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"staticfile 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"url 1.2.0 (git+https://github.com/servo/rust-url)",
|
"url 1.2.0 (git+https://github.com/servo/rust-url)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -224,18 +223,6 @@ dependencies = [
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "staticfile"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"mount 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.35"
|
version = "0.1.35"
|
||||||
|
|
|
@ -14,5 +14,4 @@ rustc-serialize = "0.3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
router = "*"
|
router = "*"
|
||||||
staticfile = "*"
|
|
||||||
mount = "*"
|
mount = "*"
|
86
src/api.rs
86
src/api.rs
|
@ -1,5 +1,7 @@
|
||||||
use core::str::Utf8Error;
|
use core::str::Utf8Error;
|
||||||
use core::ops::DerefMut;
|
use core::ops::DerefMut;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -14,13 +16,14 @@ use url::percent_encoding::percent_decode;
|
||||||
use collection::*;
|
use collection::*;
|
||||||
use error::*;
|
use error::*;
|
||||||
|
|
||||||
impl From<CollectionError> for IronError {
|
impl From<SwineError> for IronError {
|
||||||
fn from(err: CollectionError) -> IronError {
|
fn from(err: SwineError) -> IronError {
|
||||||
match err {
|
match err {
|
||||||
CollectionError::Io(e) => IronError::new(e, status::NotFound),
|
SwineError::Io(e) => IronError::new(e, status::NotFound),
|
||||||
CollectionError::PathDecoding => IronError::new(err, status::InternalServerError),
|
SwineError::PathDecoding => IronError::new(err, status::InternalServerError),
|
||||||
CollectionError::ConflictingMount => IronError::new(err, status::BadRequest),
|
SwineError::ConflictingMount => IronError::new(err, status::BadRequest),
|
||||||
CollectionError::PathNotInVfs => IronError::new(err, status::NotFound),
|
SwineError::PathNotInVfs => IronError::new(err, status::NotFound),
|
||||||
|
SwineError::CannotServeDirectory => IronError::new(err, status::BadRequest),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +44,13 @@ pub fn get_api_handler(collection: Arc<Mutex<Collection>>) -> Mount {
|
||||||
self::flatten(request, acquired_collection.deref_mut())
|
self::flatten(request, acquired_collection.deref_mut())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let collection = collection.clone();
|
||||||
|
mount.mount("/serve/", move |request: &mut Request| {
|
||||||
|
let mut acquired_collection = collection.deref().lock().unwrap();
|
||||||
|
self::serve(request, acquired_collection.deref_mut())
|
||||||
|
});
|
||||||
|
}
|
||||||
mount
|
mount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,34 +62,66 @@ fn path_from_request(request: &Request) -> Result<PathBuf, Utf8Error> {
|
||||||
|
|
||||||
fn browse(request: &mut Request, collection: &mut Collection) -> IronResult<Response> {
|
fn browse(request: &mut Request, collection: &mut Collection) -> IronResult<Response> {
|
||||||
let path = path_from_request(request);
|
let path = path_from_request(request);
|
||||||
if path.is_err() {
|
let path = match path {
|
||||||
return Ok(Response::with(status::BadRequest));
|
Err(e) => return Err(IronError::new(e, status::BadRequest)),
|
||||||
}
|
Ok(p) => p,
|
||||||
let path = path.unwrap();
|
};
|
||||||
let browse_result = try!(collection.browse(&path));
|
let browse_result = try!(collection.browse(&path));
|
||||||
|
|
||||||
let result_json = json::encode(&browse_result);
|
let result_json = json::encode(&browse_result);
|
||||||
if result_json.is_err() {
|
let result_json = match result_json {
|
||||||
return Ok(Response::with(status::InternalServerError));
|
Ok(j) => j,
|
||||||
}
|
Err(e) => return Err(IronError::new(e, status::InternalServerError)),
|
||||||
let result_json = result_json.unwrap();
|
};
|
||||||
|
|
||||||
Ok(Response::with((status::Ok, result_json)))
|
Ok(Response::with((status::Ok, result_json)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten(request: &mut Request, collection: &mut Collection) -> IronResult<Response> {
|
fn flatten(request: &mut Request, collection: &mut Collection) -> IronResult<Response> {
|
||||||
let path = path_from_request(request);
|
let path = path_from_request(request);
|
||||||
if path.is_err() {
|
let path = match path {
|
||||||
return Ok(Response::with((status::BadRequest)));
|
Err(e) => return Err(IronError::new(e, status::BadRequest)),
|
||||||
}
|
Ok(p) => p,
|
||||||
let path = path.unwrap();
|
};
|
||||||
let flatten_result = try!(collection.flatten(&path));
|
let flatten_result = try!(collection.flatten(&path));
|
||||||
|
|
||||||
let result_json = json::encode(&flatten_result);
|
let result_json = json::encode(&flatten_result);
|
||||||
if result_json.is_err() {
|
let result_json = match result_json {
|
||||||
return Ok(Response::with(status::InternalServerError));
|
Ok(j) => j,
|
||||||
}
|
Err(e) => return Err(IronError::new(e, status::InternalServerError)),
|
||||||
let result_json = result_json.unwrap();
|
};
|
||||||
|
|
||||||
Ok(Response::with((status::Ok, result_json)))
|
Ok(Response::with((status::Ok, result_json)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serve(request: &mut Request, collection: &mut Collection) -> IronResult<Response> {
|
||||||
|
let virtual_path = path_from_request(request);
|
||||||
|
let virtual_path = match virtual_path {
|
||||||
|
Err(e) => return Err(IronError::new(e, status::BadRequest)),
|
||||||
|
Ok(p) => p,
|
||||||
|
};
|
||||||
|
|
||||||
|
let real_path = collection.locate(virtual_path.as_path());
|
||||||
|
let real_path = match real_path {
|
||||||
|
Err(e) => return Err(IronError::new(e, status::NotFound)),
|
||||||
|
Ok(p) => p,
|
||||||
|
};
|
||||||
|
|
||||||
|
let metadata = match fs::metadata(real_path.as_path()) {
|
||||||
|
Ok(meta) => meta,
|
||||||
|
Err(e) => {
|
||||||
|
let status = match e.kind() {
|
||||||
|
io::ErrorKind::NotFound => status::NotFound,
|
||||||
|
io::ErrorKind::PermissionDenied => status::Forbidden,
|
||||||
|
_ => status::InternalServerError,
|
||||||
|
};
|
||||||
|
return Err(IronError::new(e, status));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if !metadata.is_file() {
|
||||||
|
return Err(IronError::new(SwineError::CannotServeDirectory, status::BadRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response::with((status::Ok, real_path)))
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use vfs::*;
|
use vfs::*;
|
||||||
use error::*;
|
use error::*;
|
||||||
|
@ -11,14 +12,14 @@ pub struct Song {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Song {
|
impl Song {
|
||||||
pub fn read(collection: &Collection, file: &fs::DirEntry) -> Result<Song, CollectionError> {
|
pub fn read(collection: &Collection, file: &fs::DirEntry) -> Result<Song, SwineError> {
|
||||||
let file_meta = try!(file.metadata());
|
let file_meta = try!(file.metadata());
|
||||||
assert!(file_meta.is_file());
|
assert!(file_meta.is_file());
|
||||||
|
|
||||||
let file_path = file.path();
|
let file_path = file.path();
|
||||||
let file_path = file_path.as_path();
|
let file_path = file_path.as_path();
|
||||||
let virtual_path = try!(collection.vfs.real_to_virtual(file_path));
|
let virtual_path = try!(collection.vfs.real_to_virtual(file_path));
|
||||||
let path_string = try!(virtual_path.to_str().ok_or(CollectionError::PathDecoding));
|
let path_string = try!(virtual_path.to_str().ok_or(SwineError::PathDecoding));
|
||||||
|
|
||||||
let display_name = virtual_path.file_stem().unwrap();
|
let display_name = virtual_path.file_stem().unwrap();
|
||||||
let display_name = display_name.to_str().unwrap();
|
let display_name = display_name.to_str().unwrap();
|
||||||
|
@ -40,14 +41,14 @@ pub struct Directory {
|
||||||
impl Directory {
|
impl Directory {
|
||||||
pub fn read(collection: &Collection,
|
pub fn read(collection: &Collection,
|
||||||
file: &fs::DirEntry)
|
file: &fs::DirEntry)
|
||||||
-> Result<Directory, CollectionError> {
|
-> Result<Directory, SwineError> {
|
||||||
let file_meta = try!(file.metadata());
|
let file_meta = try!(file.metadata());
|
||||||
assert!(file_meta.is_dir());
|
assert!(file_meta.is_dir());
|
||||||
|
|
||||||
let file_path = file.path();
|
let file_path = file.path();
|
||||||
let file_path = file_path.as_path();
|
let file_path = file_path.as_path();
|
||||||
let virtual_path = try!(collection.vfs.real_to_virtual(file_path));
|
let virtual_path = try!(collection.vfs.real_to_virtual(file_path));
|
||||||
let path_string = try!(virtual_path.to_str().ok_or(CollectionError::PathDecoding));
|
let path_string = try!(virtual_path.to_str().ok_or(SwineError::PathDecoding));
|
||||||
|
|
||||||
let display_name = virtual_path.iter().last().unwrap();
|
let display_name = virtual_path.iter().last().unwrap();
|
||||||
let display_name = display_name.to_str().unwrap();
|
let display_name = display_name.to_str().unwrap();
|
||||||
|
@ -75,11 +76,11 @@ impl Collection {
|
||||||
Collection { vfs: Vfs::new() }
|
Collection { vfs: Vfs::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount(&mut self, name: &str, real_path: &Path) -> Result<(), CollectionError> {
|
pub fn mount(&mut self, name: &str, real_path: &Path) -> Result<(), SwineError> {
|
||||||
self.vfs.mount(name, real_path)
|
self.vfs.mount(name, real_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn browse(&self, path: &Path) -> Result<Vec<CollectionFile>, CollectionError> {
|
pub fn browse(&self, path: &Path) -> Result<Vec<CollectionFile>, SwineError> {
|
||||||
|
|
||||||
let full_path = try!(self.vfs.virtual_to_real(path));
|
let full_path = try!(self.vfs.virtual_to_real(path));
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ impl Collection {
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_internal(&self, path: &Path) -> Result<Vec<Song>, CollectionError> {
|
fn flatten_internal(&self, path: &Path) -> Result<Vec<Song>, SwineError> {
|
||||||
let files = try!(fs::read_dir(path));
|
let files = try!(fs::read_dir(path));
|
||||||
files.fold(Ok(vec![]), |acc, file| {
|
files.fold(Ok(vec![]), |acc, file| {
|
||||||
let mut acc = try!(acc);
|
let mut acc = try!(acc);
|
||||||
|
@ -118,8 +119,12 @@ impl Collection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flatten(&self, path: &Path) -> Result<Vec<Song>, CollectionError> {
|
pub fn flatten(&self, path: &Path) -> Result<Vec<Song>, SwineError> {
|
||||||
let real_path = try!(self.vfs.virtual_to_real(path));
|
let real_path = try!(self.vfs.virtual_to_real(path));
|
||||||
self.flatten_internal(real_path.as_path())
|
self.flatten_internal(real_path.as_path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn locate(&self, virtual_path: &Path) -> Result<PathBuf, SwineError> {
|
||||||
|
self.vfs.virtual_to_real(virtual_path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
42
src/error.rs
42
src/error.rs
|
@ -3,52 +3,58 @@ use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CollectionError {
|
pub enum SwineError {
|
||||||
PathDecoding,
|
PathDecoding,
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
ConflictingMount,
|
ConflictingMount,
|
||||||
PathNotInVfs,
|
PathNotInVfs,
|
||||||
|
CannotServeDirectory,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<io::Error> for CollectionError {
|
impl From<io::Error> for SwineError {
|
||||||
fn from(err: io::Error) -> CollectionError {
|
fn from(err: io::Error) -> SwineError {
|
||||||
CollectionError::Io(err)
|
SwineError::Io(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for CollectionError {
|
impl error::Error for SwineError {
|
||||||
fn description(&self) -> &str {
|
fn description(&self) -> &str {
|
||||||
match *self {
|
match *self {
|
||||||
CollectionError::Io(ref err) => err.description(),
|
SwineError::Io(ref err) => err.description(),
|
||||||
CollectionError::PathDecoding => "Error while decoding a Path as a UTF-8 string",
|
SwineError::PathDecoding => "Error while decoding a Path as a UTF-8 string",
|
||||||
CollectionError::ConflictingMount => {
|
SwineError::ConflictingMount => {
|
||||||
"Attempting to mount multiple directories under the same name"
|
"Attempting to mount multiple directories under the same name"
|
||||||
}
|
}
|
||||||
CollectionError::PathNotInVfs => "Requested path does not index a mount point",
|
SwineError::PathNotInVfs => "Requested path does not index a mount point",
|
||||||
|
SwineError::CannotServeDirectory => "Only individual files can be served",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&error::Error> {
|
fn cause(&self) -> Option<&error::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
CollectionError::Io(ref err) => Some(err),
|
SwineError::Io(ref err) => Some(err),
|
||||||
CollectionError::PathDecoding => None,
|
SwineError::PathDecoding => None,
|
||||||
CollectionError::ConflictingMount => None,
|
SwineError::ConflictingMount => None,
|
||||||
CollectionError::PathNotInVfs => None,
|
SwineError::PathNotInVfs => None,
|
||||||
|
SwineError::CannotServeDirectory => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CollectionError {
|
impl fmt::Display for SwineError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
CollectionError::Io(ref err) => write!(f, "IO error: {}", err),
|
SwineError::Io(ref err) => write!(f, "IO error: {}", err),
|
||||||
CollectionError::PathDecoding => write!(f, "Path decoding error"),
|
SwineError::PathDecoding => write!(f, "Path decoding error"),
|
||||||
CollectionError::ConflictingMount => {
|
SwineError::ConflictingMount => {
|
||||||
write!(f, "Mount point already has a target directory")
|
write!(f, "Mount point already has a target directory")
|
||||||
}
|
}
|
||||||
CollectionError::PathNotInVfs => {
|
SwineError::PathNotInVfs => {
|
||||||
write!(f, "Requested path does not index a mount point")
|
write!(f, "Requested path does not index a mount point")
|
||||||
}
|
}
|
||||||
|
SwineError::CannotServeDirectory => {
|
||||||
|
write!(f, "Only individual files can be served")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ extern crate core;
|
||||||
extern crate iron;
|
extern crate iron;
|
||||||
extern crate mount;
|
extern crate mount;
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate staticfile;
|
|
||||||
extern crate url;
|
extern crate url;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -11,7 +10,6 @@ use std::sync::Mutex;
|
||||||
|
|
||||||
use iron::prelude::*;
|
use iron::prelude::*;
|
||||||
use mount::Mount;
|
use mount::Mount;
|
||||||
use staticfile::Static;
|
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod collection;
|
mod collection;
|
||||||
|
@ -29,8 +27,7 @@ fn main() {
|
||||||
|
|
||||||
let mut mount = Mount::new();
|
let mut mount = Mount::new();
|
||||||
let api_handler = get_api_handler(collection);
|
let api_handler = get_api_handler(collection);
|
||||||
mount.mount("/static/", Static::new("samplemusic/"))
|
mount.mount("/api/", api_handler);
|
||||||
.mount("/api/", api_handler);
|
|
||||||
|
|
||||||
Iron::new(mount).http("localhost:3000").unwrap();
|
Iron::new(mount).http("localhost:3000").unwrap();
|
||||||
}
|
}
|
||||||
|
|
12
src/vfs.rs
12
src/vfs.rs
|
@ -14,16 +14,16 @@ impl Vfs {
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mount(&mut self, name: &str, real_path: &Path) -> Result<(), CollectionError> {
|
pub fn mount(&mut self, name: &str, real_path: &Path) -> Result<(), SwineError> {
|
||||||
let name = name.to_string();
|
let name = name.to_string();
|
||||||
if self.mount_points.contains_key(&name) {
|
if self.mount_points.contains_key(&name) {
|
||||||
return Err(CollectionError::ConflictingMount);
|
return Err(SwineError::ConflictingMount);
|
||||||
}
|
}
|
||||||
self.mount_points.insert(name, real_path.to_path_buf());
|
self.mount_points.insert(name, real_path.to_path_buf());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn real_to_virtual(&self, real_path: &Path) -> Result<PathBuf, CollectionError> {
|
pub fn real_to_virtual(&self, real_path: &Path) -> Result<PathBuf, SwineError> {
|
||||||
for (name, target) in &self.mount_points {
|
for (name, target) in &self.mount_points {
|
||||||
match real_path.strip_prefix(target) {
|
match real_path.strip_prefix(target) {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
|
@ -33,10 +33,10 @@ impl Vfs {
|
||||||
Err(_) => (),
|
Err(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(CollectionError::PathNotInVfs)
|
Err(SwineError::PathNotInVfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn virtual_to_real(&self, virtual_path: &Path) -> Result<PathBuf, CollectionError> {
|
pub fn virtual_to_real(&self, virtual_path: &Path) -> Result<PathBuf, SwineError> {
|
||||||
for (name, target) in &self.mount_points {
|
for (name, target) in &self.mount_points {
|
||||||
let mount_path = Path::new(&name);
|
let mount_path = Path::new(&name);
|
||||||
match virtual_path.strip_prefix(mount_path) {
|
match virtual_path.strip_prefix(mount_path) {
|
||||||
|
@ -44,7 +44,7 @@ impl Vfs {
|
||||||
Err(_) => (),
|
Err(_) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(CollectionError::PathNotInVfs)
|
Err(SwineError::PathNotInVfs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue