diff --git a/src/api.rs b/src/api.rs index 0658307..a8826b3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -85,7 +85,7 @@ pub fn get_api_handler(collection: Arc) -> Mount { } fn path_from_request(request: &Request) -> Result { - let path_string = request.url.path().join("/"); + let path_string = request.url.path().join("\\"); let decoded_path = try!(percent_decode(path_string.as_bytes()).decode_utf8()); Ok(PathBuf::from(decoded_path.deref())) } diff --git a/src/collection.rs b/src/collection.rs index 16f8dc7..2eb32da 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -1,31 +1,13 @@ -use std::fs; +use core::ops::Deref; use std::path::Path; use std::path::PathBuf; -use regex::Regex; use std::sync::Arc; use config::Config; -use index::Index; +use index::*; use vfs::*; use error::*; -use utils::*; -#[derive(Debug, RustcEncodable)] -pub struct Album { - title: Option, - year: Option, - album_art: Option, - artist: Option, -} - -#[derive(Debug, RustcEncodable)] -pub struct Song { - path: String, - album: Album, - track_number: Option, - title: Option, - artist: Option, -} #[derive(Clone, Debug)] pub struct User { @@ -42,83 +24,9 @@ impl User { } } -impl Album { - fn read(collection: &Collection, real_path: &Path) -> Result { - - let album_art = collection.get_album_art(real_path).unwrap_or(None); - let album_art = match album_art { - Some(p) => Some(try!(collection.vfs.real_to_virtual(p.as_path()))), - None => None, - }; - let album_art = match album_art { - None => None, - Some(a) => a.to_str().map(|p| p.to_string()), - }; - - Ok(Album { - album_art: album_art, - title: None, - year: None, - artist: None, - }) - } -} - -impl Song { - fn read(collection: &Collection, path: &Path) -> Result { - let virtual_path = try!(collection.vfs.real_to_virtual(path)); - let path_string = try!(virtual_path.to_str().ok_or(PError::PathDecoding)); - - let album = try!(Album::read(collection, path)); - - Ok(Song { - path: path_string.to_string(), - album: album, - artist: None, - title: None, - track_number: None, - }) - } - - -} - -#[derive(Debug, RustcEncodable)] -pub struct Directory { - path: String, - name: String, - album: Album, -} - -impl Directory { - fn read(collection: &Collection, path: &Path) -> Result { - let virtual_path = try!(collection.vfs.real_to_virtual(path)); - let path_string = try!(virtual_path.to_str().ok_or(PError::PathDecoding)); - - let name = virtual_path.iter().last().unwrap(); - let name = name.to_str().unwrap(); - let name = name.to_string(); - - let album = try!(Album::read(collection, path)); - - Ok(Directory { - path: path_string.to_string(), - name: name, - album: album, - }) - } -} - -#[derive(Debug, RustcEncodable)] -pub enum CollectionFile { - Directory(Directory), - Song(Song), -} - pub struct Collection { vfs: Arc, users: Vec, - album_art_pattern: Option, index: Arc, } @@ -127,13 +35,11 @@ impl Collection { Collection { vfs: vfs, users: Vec::new(), - album_art_pattern: None, index: index, } } pub fn load_config(&mut self, config: &Config) -> Result<(), PError> { - self.album_art_pattern = config.album_art_pattern.clone(); self.users = config.users.to_vec(); Ok(()) } @@ -142,98 +48,15 @@ impl Collection { self.users.iter().any(|u| u.name == username && u.password == password) } - pub fn browse(&self, path: &Path) -> Result, PError> { - - let mut out = vec![]; - - if path.components().count() == 0 { - let mount_points = self.vfs.get_mount_points(); - for (_, target) in mount_points { - let directory = try!(Directory::read(self, target.as_path())); - out.push(CollectionFile::Directory(directory)); - } - } else { - let full_path = try!(self.vfs.virtual_to_real(path)); - for file in try!(fs::read_dir(full_path)) { - let file = try!(file); - let file_meta = try!(file.metadata()); - let file_path = file.path(); - let file_path = file_path.as_path(); - if file_meta.is_file() { - if is_song(file_path) { - let song = try!(Song::read(self, file_path)); - out.push(CollectionFile::Song(song)); - } - } else if file_meta.is_dir() { - let directory = try!(Directory::read(self, file_path)); - out.push(CollectionFile::Directory(directory)); - } - } - } - - Ok(out) - } - - fn flatten_internal(&self, path: &Path) -> Result, PError> { - let files = try!(fs::read_dir(path)); - files.fold(Ok(vec![]), |acc, file| { - let mut acc = try!(acc); - let file: fs::DirEntry = try!(file); - let file_meta = try!(file.metadata()); - let file_path = file.path(); - let file_path = file_path.as_path(); - if file_meta.is_file() { - if is_song(file_path) { - let song = try!(Song::read(self, file_path)); - acc.push(song); - } - } else { - let mut explore_content = try!(self.flatten_internal(file_path)); - acc.append(&mut explore_content); - } - Ok(acc) - }) + pub fn browse(&self, virtual_path: &Path) -> Result, PError> { + self.index.deref().browse(virtual_path) } pub fn flatten(&self, path: &Path) -> Result, PError> { - let real_path = try!(self.vfs.virtual_to_real(path)); - self.flatten_internal(real_path.as_path()) + Err(PError::Unauthorized) } pub fn locate(&self, virtual_path: &Path) -> Result { self.vfs.virtual_to_real(virtual_path) } - - fn get_album_art(&self, real_path: &Path) -> Result, PError> { - let pattern = match self.album_art_pattern { - Some(ref p) => p, - None => return Ok(None), - }; - - let mut real_dir = real_path; - if real_dir.is_file() { - real_dir = try!(real_dir.parent().ok_or(PError::AlbumArtSearchError)); - } - assert!(real_dir.is_dir()); - - let mut files = try!(fs::read_dir(real_dir)); - let album_art = files.find(|dir_entry| { - let file = match *dir_entry { - Err(_) => return false, - Ok(ref r) => r, - }; - let file_name = file.file_name(); - let file_name = match file_name.to_str() { - None => return false, - Some(r) => r, - }; - pattern.is_match(file_name) - }); - - match album_art { - Some(Err(_)) => Err(PError::AlbumArtSearchError), - Some(Ok(a)) => Ok(Some(a.path())), - None => Ok(None), - } - } } diff --git a/src/index.rs b/src/index.rs index b568628..ed7a90e 100644 --- a/src/index.rs +++ b/src/index.rs @@ -51,6 +51,33 @@ impl SongTags { } } +#[derive(Debug, RustcEncodable)] +pub struct Song { + path: String, + track_number: Option, + title: Option, + artist: Option, + album_artist: Option, + album: Option, + year: Option, + artwork: Option, +} + +#[derive(Debug, RustcEncodable)] +pub struct Directory { + path: String, + artist: Option, + album: Option, + year: Option, + artwork: Option, +} + +#[derive(Debug, RustcEncodable)] +pub enum CollectionFile { + Directory(Directory), + Song(Song), +} + impl Index { pub fn new(path: &Path, vfs: Arc, album_art_pattern: &Option) -> Result { @@ -76,7 +103,6 @@ impl Index { let db = self.connect(); db.execute("PRAGMA synchronous = NORMAL").unwrap(); - db.execute("PRAGMA journal_mode = WAL").unwrap(); db.execute(" CREATE TABLE version @@ -87,7 +113,8 @@ impl Index { CREATE TABLE directories ( id INTEGER PRIMARY KEY NOT NULL - , path NOT NULL + , path TEXT NOT NULL + , parent TEXT , artist TEXT , year INTEGER , album TEXT @@ -97,7 +124,8 @@ impl Index { CREATE TABLE songs ( id INTEGER PRIMARY KEY NOT NULL - , path NOT NULL + , path TEXT NOT NULL + , parent TEXT NOT NULL , track_number INTEGER , title TEXT , artist TEXT @@ -187,6 +215,11 @@ impl Index { // Find artwork let artwork = self.get_artwork(path).map_or(sqlite::Value::Null, |t| sqlite::Value::String(t)); + + let path_string = match path.to_str() { + Some(p) => p, + _ => return, + }; let mut directory_album = None; let mut directory_year = None; @@ -197,13 +230,13 @@ impl Index { // Prepare statements let mut insert_directory = db.prepare(" - INSERT OR REPLACE INTO directories (path, artwork, year, artist, album) - VALUES (?, ?, ?, ?, ?) + INSERT OR REPLACE INTO directories (path, parent, artwork, year, artist, album) + VALUES (?, ?, ?, ?, ?, ?) ").unwrap(); let mut insert_song = db.prepare(" - INSERT OR REPLACE INTO songs (path, track_number, title, year, album_artist, artist, album, artwork) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + INSERT OR REPLACE INTO songs (path, parent, track_number, title, year, album_artist, artist, album, artwork) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ").unwrap(); // Insert content @@ -239,13 +272,14 @@ impl Index { insert_song.reset().ok(); insert_song.bind(1, &sqlite::Value::String(file_path_string.to_owned())).unwrap(); - insert_song.bind(2, &tags.track_number.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap(); - insert_song.bind(3, &tags.title.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); - insert_song.bind(4, &tags.year.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap(); - insert_song.bind(5, &tags.album_artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); - insert_song.bind(6, &tags.artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); - insert_song.bind(7, &tags.album.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); - insert_song.bind(8, &artwork).unwrap(); + insert_song.bind(2, &sqlite::Value::String(path_string.to_owned())).unwrap(); + insert_song.bind(3, &tags.track_number.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap(); + insert_song.bind(4, &tags.title.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); + insert_song.bind(5, &tags.year.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap(); + insert_song.bind(6, &tags.album_artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); + insert_song.bind(7, &tags.artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); + insert_song.bind(8, &tags.album.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); + insert_song.bind(9, &artwork).unwrap(); insert_song.next().ok(); } } @@ -263,20 +297,28 @@ impl Index { if inconsistent_directory_artist { directory_artist = None; } - if let Some(path_string) = path.to_str() { - insert_directory.reset().ok(); - insert_directory.bind(1, &sqlite::Value::String(path_string.to_owned())).unwrap(); - insert_directory.bind(2, &artwork).unwrap(); - insert_directory.bind(3, &directory_year.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap(); - insert_directory.bind(4, &directory_artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); - insert_directory.bind(5, &directory_album.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); - insert_directory.next().ok(); + + let mut parent : Option = None; + if let Some(parent_dir) = path.parent() { + if let Some(parent_path) = parent_dir.to_str() { + parent = Some(parent_path.to_owned()); + } } + + insert_directory.reset().ok(); + insert_directory.bind(1, &sqlite::Value::String(path_string.to_owned())).unwrap(); + insert_directory.bind(2, &parent.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t.to_owned()))).unwrap(); + insert_directory.bind(3, &artwork).unwrap(); + insert_directory.bind(4, &directory_year.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap(); + insert_directory.bind(5, &directory_artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); + insert_directory.bind(6, &directory_album.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap(); + insert_directory.next().ok(); } pub fn run(&self) { loop { + // TODO fix uber-lock let db = self.connect(); if let Err(e) = db.execute("BEGIN TRANSACTION") { print!("Error while beginning transaction for index update: {}", e); @@ -289,4 +331,99 @@ impl Index { thread::sleep(time::Duration::from_secs(60 * 20)); // TODO expose in configuration } } + + // List sub-directories within a directory + fn browse_directories(&self, real_path: &Path) -> Vec { + let db = self.connect(); + let mut output = Vec::new(); + + let path_string = real_path.to_string_lossy(); + let mut cursor = db.prepare("SELECT path, artwork, year, artist, album FROM directories WHERE parent = ?").unwrap().cursor(); + cursor.bind(&[sqlite::Value::String(path_string.deref().to_owned())]).unwrap(); + + while let Ok(Some(row)) = cursor.next() { + let directory_path = Path::new(row[0].as_string().unwrap()); + let directory_path = match self.vfs.real_to_virtual(directory_path) { + Ok(p) => p, + _ => continue, + }; + let artwork_path = row[1].as_string() + .map(|p| Path::new(p)) + .and_then(|p| self.vfs.real_to_virtual(p).ok()); + + let directory = Directory { + path: directory_path.to_str().unwrap().to_owned(), + artwork: artwork_path.map(|p| p.to_str().unwrap().to_owned() ), + year: row[2].as_integer().map(|n| n as i32), + artist: row[3].as_string().map(|s| s.to_owned()), + album: row[4].as_string().map(|s| s.to_owned()), + }; + output.push(CollectionFile::Directory(directory)); + } + output + } + + // List songs within a directory + fn browse_songs(&self, real_path: &Path) -> Vec { + let db = self.connect(); + let mut output = Vec::new(); + + let path_string = real_path.to_string_lossy(); + let mut cursor = db.prepare("SELECT path, track_number, title, year, album_artist, artist, album, artwork FROM songs WHERE parent = ?").unwrap().cursor(); + cursor.bind(&[sqlite::Value::String(path_string.deref().to_owned())]).unwrap(); + + while let Some(row) = cursor.next().unwrap() { + + let song_path = Path::new(row[0].as_string().unwrap()); + let song_path = match self.vfs.real_to_virtual(song_path) { + Ok(p) => p, + _ => continue, + }; + + let artwork_path = row[7].as_string().map(|p| Path::new(p)).and_then(|p| self.vfs.real_to_virtual(p).ok()); + + let song = Song { + path: song_path.to_str().unwrap().to_owned(), + track_number: row[1].as_integer().map(|n| n as u32), + title: row[2].as_string().map(|s| s.to_owned()), + year: row[3].as_integer().map(|n| n as i32), + album_artist: row[4].as_string().map(|s| s.to_owned()), + artist: row[5].as_string().map(|s| s.to_owned()), + album: row[6].as_string().map(|s| s.to_owned()), + artwork: artwork_path.map(|p| p.to_str().unwrap().to_owned() ), + }; + output.push(CollectionFile::Song(song)); + } + + output + } + + pub fn browse(&self, virtual_path: &Path) -> Result, PError> { + + let mut output = Vec::new(); + + // Browse top-level + if virtual_path.components().count() == 0 { + for (n, _) in self.vfs.get_mount_points() { + let directory = Directory { + path: n.to_owned(), + artwork: None, + year: None, + artist: None, + album: None, + }; + output.push(CollectionFile::Directory(directory)); + } + + // Browse sub-directory + } else { + let real_path = try!(self.vfs.virtual_to_real(virtual_path)); + let directories = self.browse_directories(real_path.as_path()); + let songs = self.browse_songs(real_path.as_path()); + output.extend(directories); + output.extend(songs); + } + + Ok(output) + } }