Browse Source

Artist listing now works

master
Skia 1 year ago
parent
commit
b2afbaeef1
7 changed files with 501 additions and 17 deletions
  1. +2
    -0
      .gitignore
  2. +2
    -1
      Cargo.toml
  3. +57
    -0
      src/artist.rs
  4. +90
    -16
      src/main.rs
  5. +280
    -0
      src/old_main.rs
  6. +56
    -0
      src/song.rs
  7. +14
    -0
      src/subsonicfs.rs

+ 2
- 0
.gitignore View File

@@ -11,3 +11,5 @@
# Generated by Cargo
/target/
/Cargo.lock

/mnt

+ 2
- 1
Cargo.toml View File

@@ -2,10 +2,11 @@
name = "subsonicfs"
version = "0.1.0"
authors = ["Skia <skia@libskia.so>"]
edition = "2018"

[dependencies]
fuse = "0.3.1"
sunk = { git = "https://github.com/Hyask/sunk" }
sunk = { path = "/home/skia/workspace/sunk/" }
env_logger = "0.6.0"
time = "0.1.40"
libc = "0.2.43"

+ 57
- 0
src/artist.rs View File

@@ -0,0 +1,57 @@
extern crate sunk;
extern crate fuse;

use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyDirectory};

use time::Timespec;

type Artist = sunk::Artist;


const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 }; // 2013-10-08 08:56

//pub struct Artist {
//pub name: String,
//pub id: String,
//}

impl Artist {
pub fn new_from_id(client: & sunk::Client, id: &String) -> Artist {
let a = sunk::Artist::get(&client, id).unwrap();
Artist {
id: a.id,
name: a.name,
}
}

[>
fn new_from_ino(client: & sunk::Client, ino: u64) -> Artist {
let id = (ino & !ARTIST_ID) as usize;
Artist::new_from_id(&client, id)
}
*/

pub fn get_ino(&self) -> u64 {
12
}

pub fn get_attr(&self) -> FileAttr {
FileAttr {
ino: self.get_ino(),
size: 0,
blocks: 0,
atime: CREATE_TIME,
mtime: CREATE_TIME,
ctime: CREATE_TIME,
crtime: CREATE_TIME,
kind: FileType::RegularFile,
perm: 0o755,
nlink: 2,
uid: 501,
gid: 20,
rdev: 0,
flags: 0,
}
}
}


+ 90
- 16
src/main.rs View File

@@ -11,9 +11,10 @@ use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, Reply
use std::collections::HashMap;

extern crate sunk;
use sunk::{Artist, Streamable, ListType};
use sunk::{Artist, Streamable, ListType, Album};

const ARTIST_ID: u64 = 1 << 31;
const ALBUM_ID: u64 = 1 << 32;

const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; // 1 second

@@ -59,9 +60,12 @@ struct SubsonicFS<'subfs> {
pub client: sunk::Client,
pub artists: Vec<Artist>,
pub artists_name_to_index: HashMap<String, usize>, // key is the name
pub artists_by_ino: HashMap<u64, &'subfs Artist>, // key is the inode
pub albums: Vec<Album>,
pub albums_name_to_index: HashMap<String, usize>, // key is the name
pub artist_ino_to_album_ino_list: HashMap<u64, Vec<u64>>,
}


impl<'subfs> SubsonicFS<'subfs> {
fn new(name: &str, client: sunk::Client) -> SubsonicFS {
SubsonicFS {
@@ -69,16 +73,35 @@ impl<'subfs> SubsonicFS<'subfs> {
client: client,
artists: Vec::new(),
artists_name_to_index: HashMap::new(),
artists_by_ino: HashMap::new(),
albums: Vec::new(),
albums_name_to_index: HashMap::new(),
artist_ino_to_album_ino_list: HashMap::new(),
}
}

fn add_new_artist(&mut self, artist: Artist) {
fn add_new_artist(&mut self, mut artist: Artist) {
artist.name = artist.name.replace("/", "-");
self.artists.push(artist);
let name = self.artists.last().unwrap().name.clone();
let artist = &self.artists.last().unwrap();
let name = artist.name.clone();
self.artists_name_to_index.insert(name, self.artists.len() - 1);
// println!("artist list: {:?}", self.artists);
// println!("artist name to index: {:?}", self.artists_name_to_index);
// TODO
// self.build_album_list(artist);
}

fn add_new_album(&mut self, album: Album) {
self.albums.push(album);
let album = &self.albums.last().unwrap();
let name = album.name.clone();
self.albums_name_to_index.insert(name, self.albums.len() - 1);
// let artist_name = &album.artist.unwrap();
// match self.get_artist_by_name(artist_name) {
// Some(artist) => {
// let artist_ino = self.get_artist_ino(artist);
// // self.artist_ino_to_album_ino_list.insert(artist_ino, )
// },
// None => println!("Oops, no artist found for this album: {:#?}", album)
// }
}

fn build_artist_list(&mut self) {
@@ -88,10 +111,25 @@ impl<'subfs> SubsonicFS<'subfs> {
}
}

fn build_album_list(&mut self, artist: &Artist) {
let album_list = artist.albums(&self.client).unwrap();
for a in album_list {
self.add_new_album(a);
}
}

fn get_albums_for_artist(&self, artist: &Artist) -> Option<Vec<Album>> {
Some(artist.albums(&self.client).unwrap())
}

fn get_artist_list(&self) -> & Vec<Artist> {
&self.artists
}

fn get_album_list(&self) -> & Vec<Album> {
&self.albums
}

fn get_artist_by_name(&self, name: &str) -> Option<&Artist> {
println!("name: {}", name);
match self.artists_name_to_index.get(name) {
@@ -100,15 +138,34 @@ impl<'subfs> SubsonicFS<'subfs> {
}
}

fn get_artist_by_ino(&self, ino: u64) -> Option<&Artist> {
println!("ino: {}", ino);
let index = ino - ARTIST_ID - 1;
self.artists.get(index as usize)
}

fn get_artist_index(&self, artist: &Artist) -> usize {
*self.artists_name_to_index.get(&artist.name).unwrap()
}

fn get_album_index(&self, album: &Album) -> usize {
*self.albums_name_to_index.get(&album.name).unwrap()
}

pub fn get_artist_ino(&self, artist: &Artist) -> u64 {
let ino = ARTIST_ID + 1 + (*self.artists_name_to_index.get(&artist.name).unwrap() as u64);
let ino = ARTIST_ID + 1 + self.get_artist_index(artist) as u64;
ino
}

pub fn get_album_ino(&self, album: &Album) -> u64 {
let ino = ALBUM_ID + 1 + self.get_album_index(album) as u64;
ino
}

pub fn get_artist_attr(&self, artist: &Artist) -> FileAttr {
FileAttr {
ino: self.get_artist_ino(&artist),
size: 0,
size: artist.album_count as u64,
blocks: 0,
atime: CREATE_TIME,
mtime: CREATE_TIME,
@@ -202,9 +259,27 @@ impl<'subfs> Filesystem for SubsonicFS<'subfs> {
let a = &self.get_artist_list()[i];
entries.push((self.get_artist_ino(&a), FileType::Directory, &a.name));
}
println!("entries: {:?}", entries);
// println!("entries: {:?}", entries);
}
_ => {
if (ino & ARTIST_ID) == ARTIST_ID { // This is an artist folder
entries = vec![
(ino, FileType::Directory, "."),
(ARTIST_ID, FileType::Directory, ".."),
];
let artist = &self.get_artist_by_ino(ino).unwrap();
let albums = &self.get_albums_for_artist(&artist).unwrap();
for al in albums {
println!("album: {:?}", al);
let i = self.get_album_index(&al);
let a = &self.get_album_list()[i];

entries.push((self.get_album_ino(&a), FileType::Directory, &a.name));
}
} else {
entries = vec![];
}
}
_ => entries = vec![],
}


@@ -213,6 +288,7 @@ impl<'subfs> Filesystem for SubsonicFS<'subfs> {
// it.
let to_skip = if offset == 0 { offset } else { offset + 1 } as usize;
for (i, entry) in entries.into_iter().enumerate().skip(to_skip) {
// println!("entry: {:?}", entry);
reply.add(entry.0, i as i64, entry.1, entry.2);
}
reply.ok();
@@ -227,12 +303,9 @@ fn main() {
.map(|o| o.as_ref())
.collect::<Vec<&OsStr>>();

let site = "http://127.0.0.1:80/";
let site = "https://festival.libskia.so";
let username = "skia";
let password = "skia";
// let site = "http://demo.subsonic.org/";
// let username = "guest4";
// let password = "guest";
let password = "plop4000";

let client = sunk::Client::new(site, username, password).unwrap();
//let song = Song::new_from_id(&client, 1);
@@ -241,6 +314,7 @@ fn main() {

fs.build_artist_list();
println!("{:?}", fs.get_artist_list());
println!("{:?}", fs.get_artist_list().len());

// println!("client: {:#?}", client);
// let an_artist = sunk::Artist::get(&client, 1);

+ 280
- 0
src/old_main.rs View File

@@ -0,0 +1,280 @@
extern crate env_logger;
extern crate fuse;
extern crate libc;
extern crate time;

use std::env;
use std::ffi::OsStr;
use libc::{ENOENT,EOF};
use time::Timespec;
use fuse::{FileType, FileAttr, Filesystem, Request, ReplyData, ReplyEntry, ReplyAttr, ReplyDirectory};
use std::collections::HashMap;

extern crate sunk;
use sunk::{Artist, Streamable, ListType};

//mod artist;
//use artist::Artist;

const SONG_ID: u64 = 1 << 63;
const ALBUM_ID: u64 = 1 << 62;
const ARTIST_ID: u64 = 1 << 61;


const TTL: Timespec = Timespec { sec: 1, nsec: 0 }; // 1 second

const CREATE_TIME: Timespec = Timespec { sec: 1381237736, nsec: 0 }; // 2013-10-08 08:56




fn get_dir_attr(ino: u64) -> FileAttr {
FileAttr {
ino: ino,
size: 0,
blocks: 0,
atime: CREATE_TIME,
mtime: CREATE_TIME,
ctime: CREATE_TIME,
crtime: CREATE_TIME,
kind: FileType::Directory,
perm: 0o755,
nlink: 2,
uid: 501,
gid: 20,
rdev: 0,
flags: 0,
}
}

const SUBFS_DIR_ATTR: FileAttr = FileAttr {
ino: 1,
size: 0,
blocks: 0,
atime: CREATE_TIME,
mtime: CREATE_TIME,
ctime: CREATE_TIME,
crtime: CREATE_TIME,
kind: FileType::Directory,
perm: 0o755,
nlink: 2,
uid: 501,
gid: 20,
rdev: 0,
flags: 0,
};

const HELLO_TXT_CONTENT: &'static str = "Hello World!\n";

const SUBFS_TXT_ATTR: FileAttr = FileAttr {
ino: 2,
size: 13,
blocks: 1,
atime: CREATE_TIME,
mtime: CREATE_TIME,
ctime: CREATE_TIME,
crtime: CREATE_TIME,
kind: FileType::RegularFile,
perm: 0o644,
nlink: 1,
uid: 501,
gid: 20,
rdev: 0,
flags: 0,
};


struct SubsonicFS<'subfs> {
pub name: &'subfs str,
pub client: sunk::Client,
pub artists: Vec<Artist>,
pub artists_by_name: HashMap<&'subfs str, &'subfs Artist>, // key is the name
pub artists_by_ino: HashMap<u64, &'subfs Artist>, // key is the inode
}

impl<'subfs> SubsonicFS<'subfs> {
fn new(name: &str, client: sunk::Client) -> SubsonicFS {
SubsonicFS {
name: name,
client: client,
artists: Vec::new(),
artists_by_name: HashMap::new(),
artists_by_ino: HashMap::new(),
}
}

fn get_artist_by_id(&self, id: &String) -> Artist {
Artist::new_from_id(&self.client, id)
}

fn add_new_artist(&mut self, Artist) {

}

fn build_artist_list(&mut self) {
let artist_list = sunk::Artist::list(&self.client, )
}

fn get_artist_list(&self) -> Vec<Artist> {
self.artists
//if self.artists.len() < 1 {
//self.artists = vec![
//Artist::new_from_id(&self.client, &String::from("AR1")), // Lordi
//];
//self.artists
//} else {
//self.artists
//}
}

fn get_artist_by_name(&self, name: &str) -> Option<Artist> {
if name == "Lordi" {
return Some(Artist::new_from_id(&self.client, &String::from("AR1")));
}
return None;
}
}

impl<'subfs> Filesystem for SubsonicFS<'subfs> {
fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
println!("lookup");
if parent == 1 {
match name.to_str() {
Some("hello.txt") => reply.entry(&TTL, &SUBFS_TXT_ATTR, 0),
//Some("Man Skin Boots") => {
//println!("{} ---- {:#?}", &self.song, name);
//reply.entry(&TTL, &self.song.get_attr(), 0);
//},
Some("Artists") => reply.entry(&TTL, &get_dir_attr(ARTIST_ID), 0),
Some("Albums") => reply.entry(&TTL, &get_dir_attr(ALBUM_ID), 0),
_ => reply.error(ENOENT),
}
} else if parent == ARTIST_ID {
let a = self.get_artist_by_name(&name.to_str().unwrap());
match a {
Some(artist) => reply.entry(&TTL, &artist.get_attr(), 0),
_ => reply.error(ENOENT),
}
} else {
reply.error(ENOENT);
}
}

fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
println!("getattr");
//println!("===> {} - {}", ino, self.song.get_ino());
if ino == 1 { reply.attr(&TTL, &SUBFS_DIR_ATTR) }
else if ino == 2 { reply.attr(&TTL, &SUBFS_TXT_ATTR) }
//else if ino == self.song.get_ino() {
//println!("{}", ino);
//reply.attr(&TTL, &self.song.get_attr()) }
//else if ino == ARTIST_ID { reply.attr(&TTL, &get_dir_attr(ARTIST_ID)) }
//else if ino == ALBUM_ID { reply.attr(&TTL, &SUBFS_DIR_ATTR) }
//else if ino == SONG_ID { reply.attr(&TTL, &SUBFS_DIR_ATTR) }
//else if ino & ARTIST_ID == ARTIST_ID { reply.attr(&TTL, &get_dir_attr(ino)) }
else { reply.error(ENOENT) };
}

fn read(&mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, _size: u32, reply: ReplyData) {
println!("read");
if ino == 2 {
reply.data(&HELLO_TXT_CONTENT.as_bytes()[offset as usize..]);
//} else if ino == self.song.get_ino() {
//println!("{}", self.song);
//self.song.set_max_bit_rate(128);
//let size;
//if offset as usize + _size as usize > self.song.size as usize {
//size = self.song.size as usize - offset as usize;
//} else {
//size = _size as usize;
//}
//println!("offset: {}, size: {}, _size: {}, song.size: {}", offset, size, _size, self.song.size);
//if offset as usize >= self.song.size as usize {
//reply.error(EOF);
//} else {
//reply.data(&self.song.stream(&self.client).unwrap()[offset as usize..offset as usize + size as usize]);
//}
} else {
reply.error(ENOENT);
}
}

fn readdir(&mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) {
println!("readdir");
let artists = &self.get_artist_list();
let mut entries;
match ino {
1 => {
entries = vec![
(1, FileType::Directory, "."),
(1, FileType::Directory, ".."),
(ARTIST_ID, FileType::Directory, "Artists"),
(ALBUM_ID, FileType::Directory, "Albums"),
(SONG_ID, FileType::Directory, "Songs"),
(2, FileType::RegularFile, "hello.txt"),
//(self.song.get_ino(), FileType::RegularFile, &self.song.title),
];
}
ARTIST_ID => {
entries = vec![
(ARTIST_ID, FileType::Directory, "."),
(ARTIST_ID, FileType::Directory, ".."),
];
for a in artists {
entries.push((a.get_ino(), FileType::Directory, &a.name))
}
}
_ => entries = vec![],
}


// Offset of 0 means no offset.
// Non-zero offset means the passed offset has already been seen, and we should start after
// it.
let to_skip = if offset == 0 { offset } else { offset + 1 } as usize;
for (i, entry) in entries.into_iter().enumerate().skip(to_skip) {
reply.add(entry.0, i as i64, entry.1, entry.2);
}
reply.ok();
}
}

fn main() {
env_logger::init();
let mountpoint = env::args_os().nth(1).unwrap();
let options = ["-o", "ro", "-o", "fsname=subsonicfs"]
.iter()
.map(|o| o.as_ref())
.collect::<Vec<&OsStr>>();

let site = "http://127.0.0.1:5000/";
let username = "skia";
let password = "skia";
// let site = "http://demo.subsonic.org/";
// let username = "guest4";
// let password = "guest";

let client = sunk::Client::new(site, username, password).unwrap();
//let song = Song::new_from_id(&client, 1);

let fs = SubsonicFS::new("Subsonic FS", client);

// println!("client: {:#?}", client);
// let an_artist = sunk::Artist::get(&client, 1);
// // let artist_info = an_artist.info(&client);
// // let artists_albums = an_artist.albums(&client);
// println!("artist: {:#?}", an_artist);
// // println!("artist_info: {:#?}", artist_info);
// // println!("artists_albums: {:#?}", artists_albums);

// let an_album = sunk::Album::get(&client, 1);
// println!("album: {:#?}", an_album);

// let a_song = sunk::song::Song::get(&client, 1);
// println!("song: {:#?}", a_song);

// println!("ARTIST_ID: {}", ARTIST_ID);


fuse::mount(fs, &mountpoint, &options).unwrap();
}

+ 56
- 0
src/song.rs View File

@@ -0,0 +1,56 @@
extern crate sunk;
type Song = sunk::song::Song;

trait SubFSFile {
type File;
fn get_ino_from_id(id: usize) -> u64;
fn get_id_from_ino(ino: u64) -> usize;
fn new_from_id(client: & sunk::Client, id: usize) -> Self::File;
fn new_from_ino(client: & sunk::Client, ino: u64) -> Self::File;
fn get_ino(&self) -> u64;
fn get_attr(&self) -> FileAttr;
}

impl SubFSFile for Song {
type File = Song;

fn get_ino_from_id(id: usize) -> u64 {
id as u64 | SONG_ID
}
fn get_id_from_ino(ino: u64) -> usize {
ino as usize & !SONG_ID as usize
}

fn new_from_id(client: & sunk::Client, id: usize) -> Song {
Song::get(&client, id as u64).unwrap()
}

fn new_from_ino(client: & sunk::Client, ino: u64) -> Song {
let id = Song::get_id_from_ino(ino);
Song::new_from_id(&client, id)
}

fn get_ino(&self) -> u64 {
Song::get_ino_from_id(self.id as usize)
}

fn get_attr(&self) -> FileAttr {
FileAttr {
ino: self.get_ino(),
size: self.size,
blocks: 0,
atime: CREATE_TIME,
mtime: CREATE_TIME,
ctime: CREATE_TIME,
crtime: CREATE_TIME,
kind: FileType::RegularFile,
perm: 0o755,
nlink: 2,
uid: 501,
gid: 20,
rdev: 0,
flags: 0,
}
}
}


+ 14
- 0
src/subsonicfs.rs View File

@@ -0,0 +1,14 @@
extern crate fuse;

use fuse::{File, FileAttr};

trait SubFSFile {
type File;
fn get_ino_from_id(id: usize) -> u64;
fn get_id_from_ino(ino: u64) -> usize;
fn new_from_id(client: & sunk::Client, id: usize) -> Self::File;
fn get_ino(&self) -> u64;
fn get_attr(&self) -> FileAttr;
}



Loading…
Cancel
Save