This commit is contained in:
Antoine Gersant 2016-11-13 16:49:45 -08:00
parent f21e4e055f
commit 2150241ae1
8 changed files with 635 additions and 588 deletions

View file

@ -52,9 +52,8 @@ pub fn get_api_handler(collection: Arc<Collection>) -> Mount {
{ {
let collection = collection.clone(); let collection = collection.clone();
api_handler.mount("/auth/", move |request: &mut Request| { api_handler.mount("/auth/",
self::auth(request, collection.deref()) move |request: &mut Request| self::auth(request, collection.deref()));
});
} }
{ {
@ -189,7 +188,7 @@ fn serve(request: &mut Request, collection: &Collection) -> IronResult<Response>
} }
if is_song(real_path.as_path()) { if is_song(real_path.as_path()) {
return Ok(Response::with((status::Ok, real_path))) return Ok(Response::with((status::Ok, real_path)));
} }
if is_image(real_path.as_path()) { if is_image(real_path.as_path()) {
@ -203,6 +202,6 @@ fn art(_: &mut Request, real_path: &Path) -> IronResult<Response> {
let thumb = get_thumbnail(real_path, 400); let thumb = get_thumbnail(real_path, 400);
match thumb { match thumb {
Ok(path) => Ok(Response::with((status::Ok, path))), Ok(path) => Ok(Response::with((status::Ok, path))),
Err(e) => Err(IronError::from(e)) Err(e) => Err(IronError::from(e)),
} }
} }

View file

@ -24,14 +24,14 @@ const CONFIG_DDNS_PASSWORD: &'static str = "password";
#[derive(Debug)] #[derive(Debug)]
pub enum ConfigError { pub enum ConfigError {
IoError(io::Error), IoError(io::Error),
TOMLParseError, TOMLParseError,
RegexError(regex::Error), RegexError(regex::Error),
SecretParseError, SecretParseError,
AlbumArtPatternParseError, AlbumArtPatternParseError,
UsersParseError, UsersParseError,
MountDirsParseError, MountDirsParseError,
DDNSParseError, DDNSParseError,
ConflictingMounts, ConflictingMounts,
} }
@ -48,46 +48,46 @@ impl From<regex::Error> for ConfigError {
} }
pub struct Config { pub struct Config {
pub secret: String, pub secret: String,
pub vfs: VfsConfig, pub vfs: VfsConfig,
pub users: Vec<User>, pub users: Vec<User>,
pub album_art_pattern: Option<regex::Regex>, pub album_art_pattern: Option<regex::Regex>,
pub ddns: Option<DDNSConfig>, pub ddns: Option<DDNSConfig>,
} }
impl Config { impl Config {
pub fn parse(config_path: &path::Path) -> Result<Config, ConfigError> { pub fn parse(config_path: &path::Path) -> Result<Config, ConfigError> {
let mut config_file = try!(fs::File::open(config_path)); let mut config_file = try!(fs::File::open(config_path));
let mut config_file_content = String::new(); let mut config_file_content = String::new();
try!(config_file.read_to_string(&mut config_file_content)); try!(config_file.read_to_string(&mut config_file_content));
let parsed_config = toml::Parser::new(config_file_content.as_str()).parse(); let parsed_config = toml::Parser::new(config_file_content.as_str()).parse();
let parsed_config = try!(parsed_config.ok_or(ConfigError::TOMLParseError)); let parsed_config = try!(parsed_config.ok_or(ConfigError::TOMLParseError));
let mut config = Config { let mut config = Config {
secret: String::new(), secret: String::new(),
vfs: VfsConfig::new(), vfs: VfsConfig::new(),
users: Vec::new(), users: Vec::new(),
album_art_pattern: None, album_art_pattern: None,
ddns: None, ddns: None,
}; };
try!(config.parse_secret(&parsed_config)); try!(config.parse_secret(&parsed_config));
try!(config.parse_mount_points(&parsed_config)); try!(config.parse_mount_points(&parsed_config));
try!(config.parse_users(&parsed_config)); try!(config.parse_users(&parsed_config));
try!(config.parse_album_art_pattern(&parsed_config)); try!(config.parse_album_art_pattern(&parsed_config));
try!(config.parse_ddns(&parsed_config)); try!(config.parse_ddns(&parsed_config));
Ok(config) Ok(config)
} }
fn parse_secret(&mut self, source: &toml::Table) -> Result<(), ConfigError> { fn parse_secret(&mut self, source: &toml::Table) -> Result<(), ConfigError> {
let secret = try!(source.get(CONFIG_SECRET).ok_or(ConfigError::SecretParseError)); let secret = try!(source.get(CONFIG_SECRET).ok_or(ConfigError::SecretParseError));
let secret = try!(secret.as_str().ok_or(ConfigError::SecretParseError)); let secret = try!(secret.as_str().ok_or(ConfigError::SecretParseError));
self.secret = secret.to_owned(); self.secret = secret.to_owned();
Ok(()) Ok(())
} }
fn parse_album_art_pattern(&mut self, source: &toml::Table) -> Result<(), ConfigError> { fn parse_album_art_pattern(&mut self, source: &toml::Table) -> Result<(), ConfigError> {
let pattern = match source.get(CONFIG_ALBUM_ART_PATTERN) { let pattern = match source.get(CONFIG_ALBUM_ART_PATTERN) {
Some(s) => s, Some(s) => s,
None => return Ok(()), None => return Ok(()),
@ -100,7 +100,7 @@ impl Config {
Ok(()) Ok(())
} }
fn parse_users(&mut self, source: &toml::Table) -> Result<(), ConfigError> { fn parse_users(&mut self, source: &toml::Table) -> Result<(), ConfigError> {
let users = match source.get(CONFIG_USERS) { let users = match source.get(CONFIG_USERS) {
Some(s) => s, Some(s) => s,
None => return Ok(()), None => return Ok(()),
@ -137,7 +137,7 @@ impl Config {
Ok(()) Ok(())
} }
fn parse_mount_points(&mut self, source: &toml::Table) -> Result<(), ConfigError> { fn parse_mount_points(&mut self, source: &toml::Table) -> Result<(), ConfigError> {
let mount_dirs = match source.get(CONFIG_MOUNT_DIRS) { let mount_dirs = match source.get(CONFIG_MOUNT_DIRS) {
Some(s) => s, Some(s) => s,
None => return Ok(()), None => return Ok(()),
@ -177,30 +177,32 @@ impl Config {
Ok(()) Ok(())
} }
fn parse_ddns(&mut self, source: &toml::Table) -> Result<(), ConfigError> { fn parse_ddns(&mut self, source: &toml::Table) -> Result<(), ConfigError> {
let ddns = match source.get(CONFIG_DDNS) { let ddns = match source.get(CONFIG_DDNS) {
Some(s) => s, Some(s) => s,
None => return Ok(()), None => return Ok(()),
}; };
let ddns = match ddns { let ddns = match ddns {
&toml::Value::Table(ref a) => a, &toml::Value::Table(ref a) => a,
_ => return Err(ConfigError::DDNSParseError), _ => return Err(ConfigError::DDNSParseError),
}; };
let host = try!(ddns.get(CONFIG_DDNS_HOST).ok_or(ConfigError::DDNSParseError)).as_str(); let host = try!(ddns.get(CONFIG_DDNS_HOST).ok_or(ConfigError::DDNSParseError)).as_str();
let username = try!(ddns.get(CONFIG_DDNS_USERNAME).ok_or(ConfigError::DDNSParseError)).as_str(); let username = try!(ddns.get(CONFIG_DDNS_USERNAME).ok_or(ConfigError::DDNSParseError))
let password = try!(ddns.get(CONFIG_DDNS_PASSWORD).ok_or(ConfigError::DDNSParseError)).as_str(); .as_str();
let password = try!(ddns.get(CONFIG_DDNS_PASSWORD).ok_or(ConfigError::DDNSParseError))
.as_str();
let host = try!(host.ok_or(ConfigError::DDNSParseError)); let host = try!(host.ok_or(ConfigError::DDNSParseError));
let username = try!(username.ok_or(ConfigError::DDNSParseError)); let username = try!(username.ok_or(ConfigError::DDNSParseError));
let password = try!(password.ok_or(ConfigError::DDNSParseError)); let password = try!(password.ok_or(ConfigError::DDNSParseError));
self.ddns = Some(DDNSConfig { self.ddns = Some(DDNSConfig {
host: host.to_owned(), host: host.to_owned(),
username: username.to_owned(), username: username.to_owned(),
password: password.to_owned(), password: password.to_owned(),
}); });
Ok(()) Ok(())
} }
} }

View file

@ -1,6 +1,6 @@
use hyper; use hyper;
use hyper::client::Client; use hyper::client::Client;
use hyper::header::{Authorization, Basic }; use hyper::header::{Authorization, Basic};
use std::io; use std::io;
use std::io::Read; use std::io::Read;
use std::thread; use std::thread;
@ -8,16 +8,16 @@ use std::time;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DDNSConfig { pub struct DDNSConfig {
pub host: String, pub host: String,
pub username: String, pub username: String,
pub password: String, pub password: String,
} }
#[derive(Debug)] #[derive(Debug)]
enum DDNSError { enum DDNSError {
IoError(io::Error), IoError(io::Error),
HyperError(hyper::Error), HyperError(hyper::Error),
UpdateError(hyper::status::StatusCode), UpdateError(hyper::status::StatusCode),
} }
impl From<io::Error> for DDNSError { impl From<io::Error> for DDNSError {
@ -36,43 +36,41 @@ const MY_IP_API_URL: &'static str = "http://api.ipify.org";
const DDNS_UPDATE_URL: &'static str = "http://ydns.io/api/v1/update/"; const DDNS_UPDATE_URL: &'static str = "http://ydns.io/api/v1/update/";
fn get_my_ip() -> Result<String, DDNSError> { fn get_my_ip() -> Result<String, DDNSError> {
let client = Client::new(); let client = Client::new();
let mut res = try!(client.get(MY_IP_API_URL).send()); let mut res = try!(client.get(MY_IP_API_URL).send());
let mut buf = String::new(); let mut buf = String::new();
try!(res.read_to_string(&mut buf)); try!(res.read_to_string(&mut buf));
Ok(buf) Ok(buf)
} }
fn update_my_ip(ip: &String, config: &DDNSConfig) -> Result<(), DDNSError> { fn update_my_ip(ip: &String, config: &DDNSConfig) -> Result<(), DDNSError> {
let client = Client::new(); let client = Client::new();
let url = DDNS_UPDATE_URL; let url = DDNS_UPDATE_URL;
let host = &config.host; let host = &config.host;
let full_url = format!("{}?host={}&ip={}", url, host, ip); let full_url = format!("{}?host={}&ip={}", url, host, ip);
let auth_header = Authorization(Basic { let auth_header = Authorization(Basic {
username: config.username.clone(), username: config.username.clone(),
password: Some(config.password.to_owned()) password: Some(config.password.to_owned()),
}); });
let res = try!(client.get(full_url.as_str()).header(auth_header).send()); let res = try!(client.get(full_url.as_str()).header(auth_header).send());
match res.status { match res.status {
hyper::status::StatusCode::Ok => Ok(()), hyper::status::StatusCode::Ok => Ok(()),
s => Err(DDNSError::UpdateError(s)), s => Err(DDNSError::UpdateError(s)),
} }
} }
pub fn run(config: DDNSConfig) { pub fn run(config: DDNSConfig) {
loop { loop {
let my_ip_res = get_my_ip(); let my_ip_res = get_my_ip();
if let Ok(my_ip) = my_ip_res { if let Ok(my_ip) = my_ip_res {
match update_my_ip(&my_ip, &config) { match update_my_ip(&my_ip, &config) {
Err(e) => println!("Dynamic DNS Error: {:?}", e), Err(e) => println!("Dynamic DNS Error: {:?}", e),
Ok(_) => (), Ok(_) => (),
}; };
} } else {
else println!("Dynamic DNS Error: could not retrieve our own IP address");
{ }
println!("Dynamic DNS Error: could not retrieve our own IP address"); thread::sleep(time::Duration::from_secs(60 * 30));
} }
thread::sleep(time::Duration::from_secs(60 * 30));
}
} }

View file

@ -19,9 +19,9 @@ const INDEX_BUILDING_INSERT_BUFFER_SIZE: usize = 500; // Put 500 insertions in e
const INDEX_LOCK_TIMEOUT: usize = 100; // In milliseconds const INDEX_LOCK_TIMEOUT: usize = 100; // In milliseconds
pub struct Index { pub struct Index {
path: String, path: String,
vfs: Arc<Vfs>, vfs: Arc<Vfs>,
album_art_pattern: Option<Regex>, album_art_pattern: Option<Regex>,
} }
struct SongTags { struct SongTags {
@ -35,15 +35,15 @@ struct SongTags {
impl SongTags { impl SongTags {
fn read(path: &Path) -> Result<SongTags, PError> { fn read(path: &Path) -> Result<SongTags, PError> {
match utils::get_audio_format(path) { match utils::get_audio_format(path) {
Some(AudioFormat::MP3) => SongTags::read_id3(path), Some(AudioFormat::MP3) => SongTags::read_id3(path),
Some(AudioFormat::MPC) => SongTags::read_ape(path), Some(AudioFormat::MPC) => SongTags::read_ape(path),
_ => Err(PError::UnsupportedMetadataFormat), _ => Err(PError::UnsupportedMetadataFormat),
} }
} }
fn read_id3(path: &Path) -> Result<SongTags, PError> { fn read_id3(path: &Path) -> Result<SongTags, PError> {
let tag = try!(Tag::read_from_path(path)); let tag = try!(Tag::read_from_path(path));
let artist = tag.artist().map(|s| s.to_string()); let artist = tag.artist().map(|s| s.to_string());
let album_artist = tag.album_artist().map(|s| s.to_string()); let album_artist = tag.album_artist().map(|s| s.to_string());
@ -63,45 +63,45 @@ impl SongTags {
track_number: track_number, track_number: track_number,
year: year, year: year,
}) })
} }
fn read_ape_string(item: &ape::Item) -> Option<String> { fn read_ape_string(item: &ape::Item) -> Option<String> {
match item.value { match item.value {
ape::ItemValue::Text(ref s) => Some(s.clone()), ape::ItemValue::Text(ref s) => Some(s.clone()),
_ => None, _ => None,
} }
} }
fn read_ape_i32(item: &ape::Item) -> Option<i32> { fn read_ape_i32(item: &ape::Item) -> Option<i32> {
match item.value { match item.value {
ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(), ape::ItemValue::Text(ref s) => s.parse::<i32>().ok(),
_ => None, _ => None,
} }
} }
fn read_ape_track_number(item: &ape::Item) -> Option<u32> { fn read_ape_track_number(item: &ape::Item) -> Option<u32> {
match item.value { match item.value {
ape::ItemValue::Text(ref s) => { ape::ItemValue::Text(ref s) => {
let format = Regex::new(r#"^\d+"#).unwrap(); let format = Regex::new(r#"^\d+"#).unwrap();
if let Some((start, end)) = format.find(s) { if let Some((start, end)) = format.find(s) {
s[start..end].parse().ok() s[start..end].parse().ok()
} else { } else {
None None
} }
}, }
_ => None, _ => None,
} }
} }
fn read_ape(path: &Path) -> Result<SongTags, PError> { fn read_ape(path: &Path) -> Result<SongTags, PError> {
let tag = try!(ape::read(path)); let tag = try!(ape::read(path));
let artist = tag.item("Artist").and_then(SongTags::read_ape_string); let artist = tag.item("Artist").and_then(SongTags::read_ape_string);
let album = tag.item("Album").and_then(SongTags::read_ape_string); let album = tag.item("Album").and_then(SongTags::read_ape_string);
let album_artist = tag.item("Album artist").and_then(SongTags::read_ape_string); let album_artist = tag.item("Album artist").and_then(SongTags::read_ape_string);
let title = tag.item("Title").and_then(SongTags::read_ape_string); let title = tag.item("Title").and_then(SongTags::read_ape_string);
let year = tag.item("Year").and_then(SongTags::read_ape_i32); let year = tag.item("Year").and_then(SongTags::read_ape_i32);
let track_number = tag.item("Track").and_then(SongTags::read_ape_track_number); let track_number = tag.item("Track").and_then(SongTags::read_ape_track_number);
Ok(SongTags { Ok(SongTags {
artist: artist, artist: artist,
album_artist: album_artist, album_artist: album_artist,
album: album, album: album,
@ -109,7 +109,7 @@ impl SongTags {
track_number: track_number, track_number: track_number,
year: year, year: year,
}) })
} }
} }
#[derive(Debug, RustcEncodable)] #[derive(Debug, RustcEncodable)]
@ -140,464 +140,504 @@ pub enum CollectionFile {
} }
fn string_option_to_value(input: Option<String>) -> Value { fn string_option_to_value(input: Option<String>) -> Value {
match input { match input {
Some(s) => Value::String(s), Some(s) => Value::String(s),
None => Value::Null, None => Value::Null,
} }
} }
fn i32_option_to_value(input: Option<i32>) -> Value { fn i32_option_to_value(input: Option<i32>) -> Value {
match input { match input {
Some(s) => Value::Integer(s as i64), Some(s) => Value::Integer(s as i64),
None => Value::Null, None => Value::Null,
} }
} }
fn u32_option_to_value(input: Option<u32>) -> Value { fn u32_option_to_value(input: Option<u32>) -> Value {
match input { match input {
Some(s) => Value::Integer(s as i64), Some(s) => Value::Integer(s as i64),
None => Value::Null, None => Value::Null,
} }
} }
struct IndexBuilder<'db> { struct IndexBuilder<'db> {
queue: Vec<CollectionFile>, queue: Vec<CollectionFile>,
db: &'db Connection, db: &'db Connection,
insert_directory: Statement<'db>, insert_directory: Statement<'db>,
insert_song: Statement<'db>, insert_song: Statement<'db>,
} }
impl<'db> IndexBuilder<'db> { impl<'db> IndexBuilder<'db> {
fn new(db: &Connection) -> IndexBuilder { fn new(db: &Connection) -> IndexBuilder {
let mut queue = Vec::new(); let mut queue = Vec::new();
queue.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE); queue.reserve_exact(INDEX_BUILDING_INSERT_BUFFER_SIZE);
IndexBuilder { IndexBuilder {
queue: queue, queue: queue,
db: db, db: db,
insert_directory: db.prepare("INSERT OR REPLACE INTO directories (path, parent, artwork, year, artist, album) VALUES (?, ?, ?, ?, ?, ?)").unwrap(), insert_directory:
insert_song: db.prepare("INSERT OR REPLACE INTO songs (path, parent, track_number, title, year, album_artist, artist, album, artwork) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").unwrap(), db.prepare("INSERT OR REPLACE INTO directories (path, parent, artwork, year, \
} artist, album) VALUES (?, ?, ?, ?, ?, ?)")
} .unwrap(),
insert_song:
db.prepare("INSERT OR REPLACE INTO songs (path, parent, track_number, title, year, \
album_artist, artist, album, artwork) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
.unwrap(),
}
}
fn get_parent(path: &str) -> Option<String> { fn get_parent(path: &str) -> Option<String> {
let parent_path = Path::new(path); let parent_path = Path::new(path);
if let Some(parent_dir) = parent_path.parent() { if let Some(parent_dir) = parent_path.parent() {
if let Some(parent_path) = parent_dir.to_str() { if let Some(parent_path) = parent_dir.to_str() {
return Some(parent_path.to_owned()); return Some(parent_path.to_owned());
} }
} }
None None
} }
fn flush(&mut self) { fn flush(&mut self) {
self.db.execute("BEGIN TRANSACTION").ok(); self.db.execute("BEGIN TRANSACTION").ok();
while let Some(file) = self.queue.pop() { while let Some(file) = self.queue.pop() {
match file { match file {
// Insert directory // Insert directory
CollectionFile::Directory(directory) => { CollectionFile::Directory(directory) => {
let parent = IndexBuilder::get_parent(directory.path.as_str()); let parent = IndexBuilder::get_parent(directory.path.as_str());
self.insert_directory.reset().ok(); self.insert_directory.reset().ok();
self.insert_directory.bind(1, &Value::String(directory.path)).unwrap(); self.insert_directory.bind(1, &Value::String(directory.path)).unwrap();
self.insert_directory.bind(2, &string_option_to_value(parent)).unwrap(); self.insert_directory.bind(2, &string_option_to_value(parent)).unwrap();
self.insert_directory.bind(3, &string_option_to_value(directory.artwork)).unwrap(); self.insert_directory
self.insert_directory.bind(4, &i32_option_to_value(directory.year)).unwrap(); .bind(3, &string_option_to_value(directory.artwork))
self.insert_directory.bind(5, &string_option_to_value(directory.artist)).unwrap(); .unwrap();
self.insert_directory.bind(6, &string_option_to_value(directory.album)).unwrap(); self.insert_directory.bind(4, &i32_option_to_value(directory.year)).unwrap();
self.insert_directory.next().ok(); self.insert_directory
}, .bind(5, &string_option_to_value(directory.artist))
.unwrap();
self.insert_directory
.bind(6, &string_option_to_value(directory.album))
.unwrap();
self.insert_directory.next().ok();
}
// Insert song // Insert song
CollectionFile::Song(song) => { CollectionFile::Song(song) => {
let parent = IndexBuilder::get_parent(song.path.as_str()); let parent = IndexBuilder::get_parent(song.path.as_str());
self.insert_song.reset().ok(); self.insert_song.reset().ok();
self.insert_song.bind(1, &Value::String(song.path)).unwrap(); self.insert_song.bind(1, &Value::String(song.path)).unwrap();
self.insert_song.bind(2, &string_option_to_value(parent)).unwrap(); self.insert_song.bind(2, &string_option_to_value(parent)).unwrap();
self.insert_song.bind(3, &u32_option_to_value(song.track_number)).unwrap(); self.insert_song.bind(3, &u32_option_to_value(song.track_number)).unwrap();
self.insert_song.bind(4, &string_option_to_value(song.title)).unwrap(); self.insert_song.bind(4, &string_option_to_value(song.title)).unwrap();
self.insert_song.bind(5, &i32_option_to_value(song.year)).unwrap(); self.insert_song.bind(5, &i32_option_to_value(song.year)).unwrap();
self.insert_song.bind(6, &string_option_to_value(song.album_artist)).unwrap(); self.insert_song.bind(6, &string_option_to_value(song.album_artist)).unwrap();
self.insert_song.bind(7, &string_option_to_value(song.artist)).unwrap(); self.insert_song.bind(7, &string_option_to_value(song.artist)).unwrap();
self.insert_song.bind(8, &string_option_to_value(song.album)).unwrap(); self.insert_song.bind(8, &string_option_to_value(song.album)).unwrap();
self.insert_song.bind(9, &string_option_to_value(song.artwork)).unwrap(); self.insert_song.bind(9, &string_option_to_value(song.artwork)).unwrap();
self.insert_song.next().ok(); self.insert_song.next().ok();
} }
} }
} }
self.db.execute("END TRANSACTION").ok(); self.db.execute("END TRANSACTION").ok();
} }
fn push(&mut self, file: CollectionFile) { fn push(&mut self, file: CollectionFile) {
if self.queue.len() == self.queue.capacity() { if self.queue.len() == self.queue.capacity() {
self.flush(); self.flush();
} }
self.queue.push(file); self.queue.push(file);
} }
} }
impl<'db> Drop for IndexBuilder<'db> { impl<'db> Drop for IndexBuilder<'db> {
fn drop(&mut self) { fn drop(&mut self) {
self.flush(); self.flush();
} }
} }
impl Index { impl Index {
pub fn new(path: &Path,
vfs: Arc<Vfs>,
album_art_pattern: &Option<Regex>)
-> Result<Index, PError> {
pub fn new(path: &Path, vfs: Arc<Vfs>, album_art_pattern: &Option<Regex>) -> Result<Index, PError> { let index = Index {
path: path.to_string_lossy().deref().to_string(),
vfs: vfs,
album_art_pattern: album_art_pattern.clone(),
};
let index = Index { if path.exists() {
path: path.to_string_lossy().deref().to_string(), // Migration
vfs: vfs, } else {
album_art_pattern: album_art_pattern.clone(), index.init();
}; }
if path.exists() { Ok(index)
// Migration }
} else {
index.init();
}
Ok(index) fn init(&self) {
}
fn init(&self) { println!("Initializing index database");
println!("Initializing index database"); let db = self.connect();
db.execute("PRAGMA synchronous = NORMAL").unwrap();
let db = self.connect(); db.execute("
db.execute("PRAGMA synchronous = NORMAL").unwrap();
db.execute("
CREATE TABLE version CREATE TABLE version
( id INTEGER PRIMARY KEY NOT NULL ( id INTEGER PRIMARY KEY NOT NULL
, number INTEGER NULL , number \
INTEGER NULL
); );
INSERT INTO version (number) VALUES(1); INSERT INTO version (number) VALUES(1);
CREATE TABLE directories CREATE \
TABLE directories
( id INTEGER PRIMARY KEY NOT NULL ( id INTEGER PRIMARY KEY NOT NULL
, path TEXT NOT NULL , path TEXT NOT \
NULL
, parent TEXT , parent TEXT
, artist TEXT , artist TEXT
, year INTEGER , year INTEGER
, album TEXT , album TEXT
, artwork TEXT \
, artwork TEXT
, UNIQUE(path) , UNIQUE(path)
); );
CREATE TABLE songs CREATE TABLE songs
( id INTEGER PRIMARY KEY NOT NULL ( id \
INTEGER PRIMARY KEY NOT NULL
, path TEXT NOT NULL , path TEXT NOT NULL
, parent TEXT NOT NULL , parent TEXT NOT \
NULL
, track_number INTEGER , track_number INTEGER
, title TEXT , title TEXT
, artist TEXT , artist TEXT
, album_artist TEXT , \
album_artist TEXT
, year INTEGER , year INTEGER
, album TEXT , album TEXT
, artwork TEXT , artwork TEXT
, UNIQUE(path) , \
UNIQUE(path)
); );
").unwrap(); ")
} .unwrap();
}
fn connect(&self) -> Connection { fn connect(&self) -> Connection {
let mut db = sqlite::open(self.path.clone()).unwrap(); let mut db = sqlite::open(self.path.clone()).unwrap();
db.set_busy_timeout(INDEX_LOCK_TIMEOUT).ok(); db.set_busy_timeout(INDEX_LOCK_TIMEOUT).ok();
db db
} }
fn update_index(&self, db: &Connection) { fn update_index(&self, db: &Connection) {
let start = time::Instant::now(); let start = time::Instant::now();
println!("Beginning library index update"); println!("Beginning library index update");
self.clean(db); self.clean(db);
self.populate(db); self.populate(db);
println!("Library index update took {} seconds", start.elapsed().as_secs()); println!("Library index update took {} seconds",
} start.elapsed().as_secs());
}
fn clean(&self, db: &Connection) { fn clean(&self, db: &Connection) {
{ {
let mut select = db.prepare("SELECT path FROM songs").unwrap(); let mut select = db.prepare("SELECT path FROM songs").unwrap();
let mut delete = db.prepare("DELETE FROM songs WHERE path = ?").unwrap(); let mut delete = db.prepare("DELETE FROM songs WHERE path = ?").unwrap();
while let State::Row = select.next().unwrap() { while let State::Row = select.next().unwrap() {
let path_string : String = select.read(0).unwrap(); let path_string: String = select.read(0).unwrap();
let path = Path::new(path_string.as_str()); let path = Path::new(path_string.as_str());
if !path.exists() || self.vfs.real_to_virtual(path).is_err() { if !path.exists() || self.vfs.real_to_virtual(path).is_err() {
delete.reset().ok(); delete.reset().ok();
delete.bind(1, &Value::String(path_string.to_owned())).ok(); delete.bind(1, &Value::String(path_string.to_owned())).ok();
delete.next().ok(); delete.next().ok();
} }
} }
} }
{ {
let mut select = db.prepare("SELECT path FROM directories").unwrap(); let mut select = db.prepare("SELECT path FROM directories").unwrap();
let mut delete = db.prepare("DELETE FROM directories WHERE path = ?").unwrap(); let mut delete = db.prepare("DELETE FROM directories WHERE path = ?").unwrap();
while let State::Row = select.next().unwrap() { while let State::Row = select.next().unwrap() {
let path_string : String = select.read(0).unwrap(); let path_string: String = select.read(0).unwrap();
let path = Path::new(path_string.as_str()); let path = Path::new(path_string.as_str());
if !path.exists() || self.vfs.real_to_virtual(path).is_err() { if !path.exists() || self.vfs.real_to_virtual(path).is_err() {
delete.reset().ok(); delete.reset().ok();
delete.bind(1, &Value::String(path_string.to_owned())).ok(); delete.bind(1, &Value::String(path_string.to_owned())).ok();
delete.next().ok(); delete.next().ok();
} }
} }
} }
} }
fn populate(&self, db: &Connection) { fn populate(&self, db: &Connection) {
let vfs = self.vfs.deref(); let vfs = self.vfs.deref();
let mount_points = vfs.get_mount_points(); let mount_points = vfs.get_mount_points();
let mut builder = IndexBuilder::new(db); let mut builder = IndexBuilder::new(db);
for (_, target) in mount_points { for (_, target) in mount_points {
self.populate_directory(&mut builder, target.as_path()); self.populate_directory(&mut builder, target.as_path());
} }
} }
fn get_artwork(&self, dir: &Path) -> Option<String> { fn get_artwork(&self, dir: &Path) -> Option<String> {
let pattern = match self.album_art_pattern { let pattern = match self.album_art_pattern {
Some(ref p) => p, Some(ref p) => p,
_ => return None, _ => return None,
}; };
if let Ok(dir_content) = fs::read_dir(dir) { if let Ok(dir_content) = fs::read_dir(dir) {
for file in dir_content { for file in dir_content {
if let Ok(file) = file { if let Ok(file) = file {
if let Some(name_string) = file.file_name().to_str() { if let Some(name_string) = file.file_name().to_str() {
if pattern.is_match(name_string) { if pattern.is_match(name_string) {
return file.path().to_str().map(|p| p.to_owned()); return file.path().to_str().map(|p| p.to_owned());
} }
} }
} }
} }
} }
None None
} }
fn populate_directory(&self, builder: &mut IndexBuilder, path: &Path) { fn populate_directory(&self, builder: &mut IndexBuilder, path: &Path) {
// Find artwork // Find artwork
let artwork = self.get_artwork(path); let artwork = self.get_artwork(path);
let path_string = match path.to_str() { let path_string = match path.to_str() {
Some(p) => p, Some(p) => p,
_ => return, _ => return,
}; };
let mut directory_album = None; let mut directory_album = None;
let mut directory_year = None; let mut directory_year = None;
let mut directory_artist = None; let mut directory_artist = None;
let mut inconsistent_directory_album = false; let mut inconsistent_directory_album = false;
let mut inconsistent_directory_year = false; let mut inconsistent_directory_year = false;
let mut inconsistent_directory_artist = false; let mut inconsistent_directory_artist = false;
// Insert content // Insert content
if let Ok(dir_content) = fs::read_dir(path) { if let Ok(dir_content) = fs::read_dir(path) {
for file in dir_content { for file in dir_content {
let file_path = match file { let file_path = match file {
Ok(f) => f.path(), Ok(f) => f.path(),
_ => continue, _ => continue,
}; };
if file_path.is_dir() { if file_path.is_dir() {
self.populate_directory(builder, file_path.as_path()); self.populate_directory(builder, file_path.as_path());
} else { } else {
if let Some(file_path_string) = file_path.to_str() { if let Some(file_path_string) = file_path.to_str() {
if let Ok(tags) = SongTags::read(file_path.as_path()) { if let Ok(tags) = SongTags::read(file_path.as_path()) {
if tags.year.is_some() { if tags.year.is_some() {
inconsistent_directory_year |= directory_year.is_some() && directory_year != tags.year; inconsistent_directory_year |= directory_year.is_some() &&
directory_year = tags.year; directory_year != tags.year;
} directory_year = tags.year;
}
if tags.album.is_some() { if tags.album.is_some() {
inconsistent_directory_album |= directory_album.is_some() && directory_album != tags.album; inconsistent_directory_album |= directory_album.is_some() &&
directory_album = Some(tags.album.as_ref().unwrap().clone()); directory_album != tags.album;
} directory_album = Some(tags.album.as_ref().unwrap().clone());
}
if tags.album_artist.is_some() { if tags.album_artist.is_some() {
inconsistent_directory_artist |= directory_artist.is_some() && directory_artist != tags.album_artist; inconsistent_directory_artist |= directory_artist.is_some() &&
directory_artist = Some(tags.album_artist.as_ref().unwrap().clone()); directory_artist !=
} else if tags.artist.is_some() { tags.album_artist;
inconsistent_directory_artist |= directory_artist.is_some() && directory_artist != tags.artist; directory_artist =
directory_artist = Some(tags.artist.as_ref().unwrap().clone()); Some(tags.album_artist.as_ref().unwrap().clone());
} } else if tags.artist.is_some() {
inconsistent_directory_artist |= directory_artist.is_some() &&
directory_artist != tags.artist;
directory_artist = Some(tags.artist.as_ref().unwrap().clone());
}
let song = Song { let song = Song {
path: file_path_string.to_owned(), path: file_path_string.to_owned(),
track_number: tags.track_number, track_number: tags.track_number,
title: tags.title, title: tags.title,
artist: tags.artist, artist: tags.artist,
album_artist: tags.album_artist, album_artist: tags.album_artist,
album: tags.album, album: tags.album,
year: tags.year, year: tags.year,
artwork: artwork.as_ref().map(|s| s.to_owned()), artwork: artwork.as_ref().map(|s| s.to_owned()),
}; };
builder.push(CollectionFile::Song(song)); builder.push(CollectionFile::Song(song));
} }
} }
} }
} }
} }
// Insert directory // Insert directory
if inconsistent_directory_year { if inconsistent_directory_year {
directory_year = None; directory_year = None;
} }
if inconsistent_directory_album { if inconsistent_directory_album {
directory_album = None; directory_album = None;
} }
if inconsistent_directory_artist { if inconsistent_directory_artist {
directory_artist = None; directory_artist = None;
} }
let directory = Directory { let directory = Directory {
path: path_string.to_owned(), path: path_string.to_owned(),
artwork: artwork, artwork: artwork,
album: directory_album, album: directory_album,
artist: directory_artist, artist: directory_artist,
year: directory_year, year: directory_year,
}; };
builder.push(CollectionFile::Directory(directory)); builder.push(CollectionFile::Directory(directory));
} }
pub fn run(&self) pub fn run(&self) {
{ loop {
loop { {
{ let db = self.connect();
let db = self.connect(); self.update_index(&db);
self.update_index(&db); }
} thread::sleep(time::Duration::from_secs(60 * 20)); // TODO expose in configuration
thread::sleep(time::Duration::from_secs(60 * 20)); // TODO expose in configuration }
} }
}
fn select_songs(&self, select: &mut Statement) -> Vec<Song> { fn select_songs(&self, select: &mut Statement) -> Vec<Song> {
let mut output = Vec::new(); let mut output = Vec::new();
while let State::Row = select.next().unwrap() { while let State::Row = select.next().unwrap() {
let song_path : String = select.read(0).unwrap(); let song_path: String = select.read(0).unwrap();
let track_number : Value = select.read(1).unwrap(); let track_number: Value = select.read(1).unwrap();
let title : Value = select.read(2).unwrap(); let title: Value = select.read(2).unwrap();
let year : Value = select.read(3).unwrap(); let year: Value = select.read(3).unwrap();
let album_artist : Value = select.read(4).unwrap(); let album_artist: Value = select.read(4).unwrap();
let artist : Value = select.read(5).unwrap(); let artist: Value = select.read(5).unwrap();
let album : Value = select.read(6).unwrap(); let album: Value = select.read(6).unwrap();
let artwork : Value = select.read(7).unwrap(); let artwork: Value = select.read(7).unwrap();
let song_path = Path::new(song_path.as_str()); let song_path = Path::new(song_path.as_str());
let song_path = match self.vfs.real_to_virtual(song_path) { let song_path = match self.vfs.real_to_virtual(song_path) {
Ok(p) => p, Ok(p) => p,
_ => continue, _ => continue,
}; };
let artwork = artwork.as_string().map(|p| Path::new(p)).and_then(|p| self.vfs.real_to_virtual(p).ok()); let artwork = artwork.as_string()
.map(|p| Path::new(p))
.and_then(|p| self.vfs.real_to_virtual(p).ok());
let song = Song { let song = Song {
path: song_path.to_str().unwrap().to_owned(), path: song_path.to_str().unwrap().to_owned(),
track_number: track_number.as_integer().map(|n| n as u32), track_number: track_number.as_integer().map(|n| n as u32),
title: title.as_string().map(|s| s.to_owned()), title: title.as_string().map(|s| s.to_owned()),
year: year.as_integer().map(|n| n as i32), year: year.as_integer().map(|n| n as i32),
album_artist: album_artist.as_string().map(|s| s.to_owned()), album_artist: album_artist.as_string().map(|s| s.to_owned()),
artist: artist.as_string().map(|s| s.to_owned()), artist: artist.as_string().map(|s| s.to_owned()),
album: album.as_string().map(|s| s.to_owned()), album: album.as_string().map(|s| s.to_owned()),
artwork: artwork.map(|p| p.to_str().unwrap().to_owned() ), artwork: artwork.map(|p| p.to_str().unwrap().to_owned()),
}; };
output.push(song); output.push(song);
} }
output output
} }
// List sub-directories within a directory // List sub-directories within a directory
fn browse_directories(&self, real_path: &Path) -> Vec<CollectionFile> { fn browse_directories(&self, real_path: &Path) -> Vec<CollectionFile> {
let db = self.connect(); let db = self.connect();
let mut output = Vec::new(); let mut output = Vec::new();
let path_string = real_path.to_string_lossy(); let path_string = real_path.to_string_lossy();
let mut select = db.prepare("SELECT path, artwork, year, artist, album FROM directories WHERE parent = ? ORDER BY path COLLATE NOCASE ASC").unwrap(); let mut select =
select.bind(1, &Value::String(path_string.deref().to_owned())).unwrap(); db.prepare("SELECT path, artwork, year, artist, album FROM directories WHERE \
parent = ? ORDER BY path COLLATE NOCASE ASC")
.unwrap();
select.bind(1, &Value::String(path_string.deref().to_owned())).unwrap();
while let State::Row = select.next().unwrap() { while let State::Row = select.next().unwrap() {
let directory_value : String = select.read(0).unwrap(); let directory_value: String = select.read(0).unwrap();
let artwork_path : Value = select.read(1).unwrap(); let artwork_path: Value = select.read(1).unwrap();
let year : Value = select.read(2).unwrap(); let year: Value = select.read(2).unwrap();
let artist : Value = select.read(3).unwrap(); let artist: Value = select.read(3).unwrap();
let album : Value = select.read(4).unwrap(); let album: Value = select.read(4).unwrap();
let directory_path = Path::new(directory_value.as_str()); let directory_path = Path::new(directory_value.as_str());
let directory_path = match self.vfs.real_to_virtual(directory_path) { let directory_path = match self.vfs.real_to_virtual(directory_path) {
Ok(p) => p, Ok(p) => p,
_ => continue, _ => continue,
}; };
let artwork_path = artwork_path.as_string() let artwork_path = artwork_path.as_string()
.map(|p| Path::new(p)) .map(|p| Path::new(p))
.and_then(|p| self.vfs.real_to_virtual(p).ok()); .and_then(|p| self.vfs.real_to_virtual(p).ok());
let directory = Directory { let directory = Directory {
path: directory_path.to_str().unwrap().to_owned(), path: directory_path.to_str().unwrap().to_owned(),
artwork: artwork_path.map(|p| p.to_str().unwrap().to_owned() ), artwork: artwork_path.map(|p| p.to_str().unwrap().to_owned()),
year: year.as_integer().map(|n| n as i32), year: year.as_integer().map(|n| n as i32),
artist: artist.as_string().map(|s| s.to_owned()), artist: artist.as_string().map(|s| s.to_owned()),
album: album.as_string().map(|s| s.to_owned()), album: album.as_string().map(|s| s.to_owned()),
}; };
output.push(CollectionFile::Directory(directory)); output.push(CollectionFile::Directory(directory));
} }
output output
} }
// List songs within a directory // List songs within a directory
fn browse_songs(&self, real_path: &Path) -> Vec<CollectionFile> { fn browse_songs(&self, real_path: &Path) -> Vec<CollectionFile> {
let db = self.connect(); let db = self.connect();
let path_string = real_path.to_string_lossy(); let path_string = real_path.to_string_lossy();
let mut select = db.prepare("SELECT path, track_number, title, year, album_artist, artist, album, artwork FROM songs WHERE parent = ? ORDER BY path COLLATE NOCASE ASC").unwrap(); let mut select =
select.bind(1, &Value::String(path_string.deref().to_owned())).unwrap(); db.prepare("SELECT path, track_number, title, year, album_artist, artist, album, \
self.select_songs(&mut select).into_iter().map(|s| CollectionFile::Song(s)).collect() artwork FROM songs WHERE parent = ? ORDER BY path COLLATE NOCASE ASC")
} .unwrap();
select.bind(1, &Value::String(path_string.deref().to_owned())).unwrap();
self.select_songs(&mut select).into_iter().map(|s| CollectionFile::Song(s)).collect()
}
pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>, PError> { pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>, PError> {
let mut output = Vec::new(); let mut output = Vec::new();
// Browse top-level // Browse top-level
if virtual_path.components().count() == 0 { if virtual_path.components().count() == 0 {
for (n, _) in self.vfs.get_mount_points() { for (n, _) in self.vfs.get_mount_points() {
let directory = Directory { let directory = Directory {
path: n.to_owned(), path: n.to_owned(),
artwork: None, artwork: None,
year: None, year: None,
artist: None, artist: None,
album: None, album: None,
}; };
output.push(CollectionFile::Directory(directory)); output.push(CollectionFile::Directory(directory));
} }
// Browse sub-directory // Browse sub-directory
} else { } else {
let real_path = try!(self.vfs.virtual_to_real(virtual_path)); let real_path = try!(self.vfs.virtual_to_real(virtual_path));
let directories = self.browse_directories(real_path.as_path()); let directories = self.browse_directories(real_path.as_path());
let songs = self.browse_songs(real_path.as_path()); let songs = self.browse_songs(real_path.as_path());
output.extend(directories); output.extend(directories);
output.extend(songs); output.extend(songs);
} }
Ok(output) Ok(output)
} }
pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>, PError> { pub fn flatten(&self, virtual_path: &Path) -> Result<Vec<Song>, PError> {
let db = self.connect(); let db = self.connect();
let real_path = try!(self.vfs.virtual_to_real(virtual_path)); let real_path = try!(self.vfs.virtual_to_real(virtual_path));
let path_string = real_path.to_string_lossy().into_owned() + "%"; let path_string = real_path.to_string_lossy().into_owned() + "%";
let mut select = db.prepare("SELECT path, track_number, title, year, album_artist, artist, album, artwork FROM songs WHERE path LIKE ? ORDER BY path COLLATE NOCASE ASC").unwrap(); let mut select =
select.bind(1, &Value::String(path_string.deref().to_owned())).unwrap(); db.prepare("SELECT path, track_number, title, year, album_artist, artist, album, \
Ok(self.select_songs(&mut select)) artwork FROM songs WHERE path LIKE ? ORDER BY path COLLATE NOCASE ASC")
} .unwrap();
select.bind(1, &Value::String(path_string.deref().to_owned())).unwrap();
Ok(self.select_songs(&mut select))
}
} }

View file

@ -70,7 +70,8 @@ fn main() {
// Init index // Init index
println!("Starting up index"); println!("Starting up index");
let index_path = path::Path::new(INDEX_FILE_NAME); let index_path = path::Path::new(INDEX_FILE_NAME);
let index = Arc::new(index::Index::new(&index_path, vfs.clone(), &config.album_art_pattern).unwrap()); let index = Arc::new(index::Index::new(&index_path, vfs.clone(), &config.album_art_pattern)
.unwrap());
let index_ref = index.clone(); let index_ref = index.clone();
std::thread::spawn(move || index_ref.run()); std::thread::spawn(move || index_ref.run());
@ -104,7 +105,7 @@ fn main() {
std::thread::spawn(|| { std::thread::spawn(|| {
ddns::run(ddns_config); ddns::run(ddns_config);
}); });
}, }
None => (), None => (),
}; };

View file

@ -14,42 +14,49 @@ use error::*;
const THUMBNAILS_PATH: &'static str = "tmp/thumbnails"; const THUMBNAILS_PATH: &'static str = "tmp/thumbnails";
fn hash(path: &Path, dimension: u32) -> u64 { fn hash(path: &Path, dimension: u32) -> u64 {
let path_string = path.to_string_lossy(); let path_string = path.to_string_lossy();
let hash_input = format!("{}:{}", path_string, dimension.to_string()); let hash_input = format!("{}:{}", path_string, dimension.to_string());
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hash_input.hash(&mut hasher); hash_input.hash(&mut hasher);
hasher.finish() hasher.finish()
} }
pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf, PError> { pub fn get_thumbnail(real_path: &Path, max_dimension: u32) -> Result<PathBuf, PError> {
let mut out_path = PathBuf::new(); let mut out_path = PathBuf::new();
out_path.push(THUMBNAILS_PATH); out_path.push(THUMBNAILS_PATH);
let mut dir_builder = DirBuilder::new(); let mut dir_builder = DirBuilder::new();
dir_builder.recursive(true); dir_builder.recursive(true);
try!(dir_builder.create(out_path.as_path())); try!(dir_builder.create(out_path.as_path()));
let source_image = try!(image::open(real_path)); let source_image = try!(image::open(real_path));
let (source_width, source_height) = source_image.dimensions(); let (source_width, source_height) = source_image.dimensions();
let cropped_dimension = cmp::max(source_width, source_height); let cropped_dimension = cmp::max(source_width, source_height);
let out_dimension = cmp::min(max_dimension, cropped_dimension); let out_dimension = cmp::min(max_dimension, cropped_dimension);
let hash = hash(real_path, out_dimension); let hash = hash(real_path, out_dimension);
out_path.push(format!("{}.png", hash.to_string())); out_path.push(format!("{}.png", hash.to_string()));
if !out_path.exists() { if !out_path.exists() {
let source_aspect_ratio : f32 = source_width as f32 / source_height as f32; let source_aspect_ratio: f32 = source_width as f32 / source_height as f32;
if source_aspect_ratio < 0.8 || source_aspect_ratio > 1.2 { if source_aspect_ratio < 0.8 || source_aspect_ratio > 1.2 {
let mut cropped_image = ImageBuffer::new(cropped_dimension, cropped_dimension); let mut cropped_image = ImageBuffer::new(cropped_dimension, cropped_dimension);
cropped_image.copy_from(&source_image, (cropped_dimension - source_width)/2, (cropped_dimension - source_height)/2); cropped_image.copy_from(&source_image,
let out_image = resize(&cropped_image, out_dimension, out_dimension, FilterType::Lanczos3); (cropped_dimension - source_width) / 2,
try!(out_image.save(out_path.as_path())); (cropped_dimension - source_height) / 2);
} else { let out_image = resize(&cropped_image,
let out_image = resize(&source_image, out_dimension, out_dimension, FilterType::Lanczos3); out_dimension,
try!(out_image.save(out_path.as_path())); out_dimension,
} FilterType::Lanczos3);
} try!(out_image.save(out_path.as_path()));
} else {
let out_image = resize(&source_image,
out_dimension,
out_dimension,
FilterType::Lanczos3);
try!(out_image.save(out_path.as_path()));
}
}
Ok(out_path) Ok(out_path)
} }

View file

@ -1,51 +1,51 @@
use std::path::Path; use std::path::Path;
pub enum AudioFormat { pub enum AudioFormat {
FLAC, FLAC,
MP3, MP3,
MP4, MP4,
MPC, MPC,
OGG, OGG,
} }
pub fn get_audio_format(path: &Path) -> Option<AudioFormat> { pub fn get_audio_format(path: &Path) -> Option<AudioFormat> {
let extension = match path.extension() { let extension = match path.extension() {
Some(e) => e, Some(e) => e,
_ => return None, _ => return None,
}; };
let extension = match extension.to_str() { let extension = match extension.to_str() {
Some(e) => e, Some(e) => e,
_ => return None, _ => return None,
}; };
match extension.to_lowercase().as_str() { match extension.to_lowercase().as_str() {
"flac" => Some(AudioFormat::FLAC), "flac" => Some(AudioFormat::FLAC),
"mp3" => Some(AudioFormat::MP3), "mp3" => Some(AudioFormat::MP3),
"m4a" => Some(AudioFormat::MP4), "m4a" => Some(AudioFormat::MP4),
"mpc" => Some(AudioFormat::MPC), "mpc" => Some(AudioFormat::MPC),
"ogg" => Some(AudioFormat::OGG), "ogg" => Some(AudioFormat::OGG),
_ => None, _ => None,
} }
} }
pub fn is_song(path: &Path) -> bool { pub fn is_song(path: &Path) -> bool {
get_audio_format(path).is_some() get_audio_format(path).is_some()
} }
pub fn is_image(path: &Path) -> bool { pub fn is_image(path: &Path) -> bool {
let extension = match path.extension() { let extension = match path.extension() {
Some(e) => e, Some(e) => e,
_ => return false, _ => return false,
}; };
let extension = match extension.to_str() { let extension = match extension.to_str() {
Some(e) => e, Some(e) => e,
_ => return false, _ => return false,
}; };
match extension.to_lowercase().as_str() { match extension.to_lowercase().as_str() {
"png" => true, "png" => true,
"gif" => true, "gif" => true,
"jpg" => true, "jpg" => true,
"jpeg" => true, "jpeg" => true,
"bmp" => true, "bmp" => true,
_ => false, _ => false,
} }
} }

View file

@ -6,14 +6,12 @@ use error::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VfsConfig { pub struct VfsConfig {
pub mount_points: HashMap<String, PathBuf>, pub mount_points: HashMap<String, PathBuf>,
} }
impl VfsConfig { impl VfsConfig {
pub fn new() -> VfsConfig { pub fn new() -> VfsConfig {
VfsConfig { VfsConfig { mount_points: HashMap::new() }
mount_points: HashMap::new(),
}
} }
} }
@ -43,11 +41,13 @@ impl Vfs {
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) {
Ok(p) => return if p.components().count() == 0 { Ok(p) => {
Ok(target.clone()) return if p.components().count() == 0 {
} else { Ok(target.clone())
Ok(target.join(p)) } else {
}, Ok(target.join(p))
}
}
Err(_) => (), Err(_) => (),
} }
} }