mirror of
https://github.com/agersant/polaris
synced 2024-12-04 18:49:10 +00:00
Implemented /browse
This commit is contained in:
parent
eae6e10ecd
commit
6bf65ac45a
3 changed files with 165 additions and 205 deletions
|
@ -85,7 +85,7 @@ pub fn get_api_handler(collection: Arc<Collection>) -> Mount {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_from_request(request: &Request) -> Result<PathBuf, Utf8Error> {
|
fn path_from_request(request: &Request) -> Result<PathBuf, Utf8Error> {
|
||||||
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());
|
let decoded_path = try!(percent_decode(path_string.as_bytes()).decode_utf8());
|
||||||
Ok(PathBuf::from(decoded_path.deref()))
|
Ok(PathBuf::from(decoded_path.deref()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,13 @@
|
||||||
use std::fs;
|
use core::ops::Deref;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use regex::Regex;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use index::Index;
|
use index::*;
|
||||||
use vfs::*;
|
use vfs::*;
|
||||||
use error::*;
|
use error::*;
|
||||||
use utils::*;
|
|
||||||
|
|
||||||
#[derive(Debug, RustcEncodable)]
|
|
||||||
pub struct Album {
|
|
||||||
title: Option<String>,
|
|
||||||
year: Option<i32>,
|
|
||||||
album_art: Option<String>,
|
|
||||||
artist: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, RustcEncodable)]
|
|
||||||
pub struct Song {
|
|
||||||
path: String,
|
|
||||||
album: Album,
|
|
||||||
track_number: Option<u32>,
|
|
||||||
title: Option<String>,
|
|
||||||
artist: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
@ -42,83 +24,9 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Album {
|
|
||||||
fn read(collection: &Collection, real_path: &Path) -> Result<Album, PError> {
|
|
||||||
|
|
||||||
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<Song, PError> {
|
|
||||||
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<Directory, PError> {
|
|
||||||
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 {
|
pub struct Collection {
|
||||||
vfs: Arc<Vfs>,
|
vfs: Arc<Vfs>,
|
||||||
users: Vec<User>,
|
users: Vec<User>,
|
||||||
album_art_pattern: Option<Regex>,
|
|
||||||
index: Arc<Index>,
|
index: Arc<Index>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,13 +35,11 @@ impl Collection {
|
||||||
Collection {
|
Collection {
|
||||||
vfs: vfs,
|
vfs: vfs,
|
||||||
users: Vec::new(),
|
users: Vec::new(),
|
||||||
album_art_pattern: None,
|
|
||||||
index: index,
|
index: index,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_config(&mut self, config: &Config) -> Result<(), PError> {
|
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();
|
self.users = config.users.to_vec();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -142,98 +48,15 @@ impl Collection {
|
||||||
self.users.iter().any(|u| u.name == username && u.password == password)
|
self.users.iter().any(|u| u.name == username && u.password == password)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn browse(&self, path: &Path) -> Result<Vec<CollectionFile>, PError> {
|
pub fn browse(&self, virtual_path: &Path) -> Result<Vec<CollectionFile>, PError> {
|
||||||
|
self.index.deref().browse(virtual_path)
|
||||||
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<Vec<Song>, 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 flatten(&self, path: &Path) -> Result<Vec<Song>, PError> {
|
pub fn flatten(&self, path: &Path) -> Result<Vec<Song>, PError> {
|
||||||
let real_path = try!(self.vfs.virtual_to_real(path));
|
Err(PError::Unauthorized)
|
||||||
self.flatten_internal(real_path.as_path())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn locate(&self, virtual_path: &Path) -> Result<PathBuf, PError> {
|
pub fn locate(&self, virtual_path: &Path) -> Result<PathBuf, PError> {
|
||||||
self.vfs.virtual_to_real(virtual_path)
|
self.vfs.virtual_to_real(virtual_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_album_art(&self, real_path: &Path) -> Result<Option<PathBuf>, 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
177
src/index.rs
177
src/index.rs
|
@ -51,6 +51,33 @@ impl SongTags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, RustcEncodable)]
|
||||||
|
pub struct Song {
|
||||||
|
path: String,
|
||||||
|
track_number: Option<u32>,
|
||||||
|
title: Option<String>,
|
||||||
|
artist: Option<String>,
|
||||||
|
album_artist: Option<String>,
|
||||||
|
album: Option<String>,
|
||||||
|
year: Option<i32>,
|
||||||
|
artwork: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, RustcEncodable)]
|
||||||
|
pub struct Directory {
|
||||||
|
path: String,
|
||||||
|
artist: Option<String>,
|
||||||
|
album: Option<String>,
|
||||||
|
year: Option<i32>,
|
||||||
|
artwork: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, RustcEncodable)]
|
||||||
|
pub enum CollectionFile {
|
||||||
|
Directory(Directory),
|
||||||
|
Song(Song),
|
||||||
|
}
|
||||||
|
|
||||||
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> {
|
||||||
|
@ -76,7 +103,6 @@ impl Index {
|
||||||
|
|
||||||
let db = self.connect();
|
let db = self.connect();
|
||||||
db.execute("PRAGMA synchronous = NORMAL").unwrap();
|
db.execute("PRAGMA synchronous = NORMAL").unwrap();
|
||||||
db.execute("PRAGMA journal_mode = WAL").unwrap();
|
|
||||||
db.execute("
|
db.execute("
|
||||||
|
|
||||||
CREATE TABLE version
|
CREATE TABLE version
|
||||||
|
@ -87,7 +113,8 @@ impl Index {
|
||||||
|
|
||||||
CREATE TABLE directories
|
CREATE TABLE directories
|
||||||
( id INTEGER PRIMARY KEY NOT NULL
|
( id INTEGER PRIMARY KEY NOT NULL
|
||||||
, path NOT NULL
|
, path TEXT NOT NULL
|
||||||
|
, parent TEXT
|
||||||
, artist TEXT
|
, artist TEXT
|
||||||
, year INTEGER
|
, year INTEGER
|
||||||
, album TEXT
|
, album TEXT
|
||||||
|
@ -97,7 +124,8 @@ impl Index {
|
||||||
|
|
||||||
CREATE TABLE songs
|
CREATE TABLE songs
|
||||||
( id INTEGER PRIMARY KEY NOT NULL
|
( id INTEGER PRIMARY KEY NOT NULL
|
||||||
, path NOT NULL
|
, path TEXT NOT NULL
|
||||||
|
, parent TEXT NOT NULL
|
||||||
, track_number INTEGER
|
, track_number INTEGER
|
||||||
, title TEXT
|
, title TEXT
|
||||||
, artist TEXT
|
, artist TEXT
|
||||||
|
@ -188,6 +216,11 @@ impl Index {
|
||||||
// Find artwork
|
// Find artwork
|
||||||
let artwork = self.get_artwork(path).map_or(sqlite::Value::Null, |t| sqlite::Value::String(t));
|
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_album = None;
|
||||||
let mut directory_year = None;
|
let mut directory_year = None;
|
||||||
let mut directory_artist = None;
|
let mut directory_artist = None;
|
||||||
|
@ -197,13 +230,13 @@ impl Index {
|
||||||
|
|
||||||
// Prepare statements
|
// Prepare statements
|
||||||
let mut insert_directory = db.prepare("
|
let mut insert_directory = db.prepare("
|
||||||
INSERT OR REPLACE INTO directories (path, artwork, year, artist, album)
|
INSERT OR REPLACE INTO directories (path, parent, artwork, year, artist, album)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
").unwrap();
|
").unwrap();
|
||||||
|
|
||||||
let mut insert_song = db.prepare("
|
let mut insert_song = db.prepare("
|
||||||
INSERT OR REPLACE INTO songs (path, track_number, title, year, album_artist, artist, album, artwork)
|
INSERT OR REPLACE INTO songs (path, parent, track_number, title, year, album_artist, artist, album, artwork)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
").unwrap();
|
").unwrap();
|
||||||
|
|
||||||
// Insert content
|
// Insert content
|
||||||
|
@ -239,13 +272,14 @@ impl Index {
|
||||||
|
|
||||||
insert_song.reset().ok();
|
insert_song.reset().ok();
|
||||||
insert_song.bind(1, &sqlite::Value::String(file_path_string.to_owned())).unwrap();
|
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(2, &sqlite::Value::String(path_string.to_owned())).unwrap();
|
||||||
insert_song.bind(3, &tags.title.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).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.year.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.album_artist.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.artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap();
|
insert_song.bind(6, &tags.album_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(7, &tags.artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).unwrap();
|
||||||
insert_song.bind(8, &artwork).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();
|
insert_song.next().ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,20 +297,28 @@ impl Index {
|
||||||
if inconsistent_directory_artist {
|
if inconsistent_directory_artist {
|
||||||
directory_artist = None;
|
directory_artist = None;
|
||||||
}
|
}
|
||||||
if let Some(path_string) = path.to_str() {
|
|
||||||
|
let mut parent : Option<String> = 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.reset().ok();
|
||||||
insert_directory.bind(1, &sqlite::Value::String(path_string.to_owned())).unwrap();
|
insert_directory.bind(1, &sqlite::Value::String(path_string.to_owned())).unwrap();
|
||||||
insert_directory.bind(2, &artwork).unwrap();
|
insert_directory.bind(2, &parent.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t.to_owned()))).unwrap();
|
||||||
insert_directory.bind(3, &directory_year.map_or(sqlite::Value::Null, |t| sqlite::Value::Integer(t as i64))).unwrap();
|
insert_directory.bind(3, &artwork).unwrap();
|
||||||
insert_directory.bind(4, &directory_artist.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).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_album.map_or(sqlite::Value::Null, |t| sqlite::Value::String(t))).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();
|
insert_directory.next().ok();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&self)
|
pub fn run(&self)
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
|
// TODO fix uber-lock
|
||||||
let db = self.connect();
|
let db = self.connect();
|
||||||
if let Err(e) = db.execute("BEGIN TRANSACTION") {
|
if let Err(e) = db.execute("BEGIN TRANSACTION") {
|
||||||
print!("Error while beginning transaction for index update: {}", e);
|
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
|
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<CollectionFile> {
|
||||||
|
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<CollectionFile> {
|
||||||
|
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<Vec<CollectionFile>, 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue