// Fuse staff #[cfg(target_family = "unix")] extern crate fuse; extern crate libc; extern crate time; #[cfg(target_family = "unix")] use fuse::{ FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request, }; #[cfg(target_family = "unix")] use libc::ENOENT; #[cfg(target_family = "unix")] use std::collections::BTreeMap; #[cfg(target_family = "unix")] use std::env; #[cfg(target_family = "unix")] use std::ffi::OsStr; #[cfg(target_family = "unix")] use time::Timespec; // Download lib staff use percent_encoding::percent_decode_str; use serde::Deserialize; use std::collections::HashMap; use std::fs; use std::path::Path; #[derive(Default, Debug, Clone, PartialEq, Deserialize)] pub struct Track { pub id: Option, pub name: Option, pub artist: Option, pub album: Option, pub genre: Option, pub year: Option, pub format: Option, pub filetype: Option, pub path: Option, } const API_URL: &str = "https://mus.hexor.ru"; fn get_basename(path: Option<&String>) -> Option { let base = match percent_decode_str(path.unwrap().as_str()).decode_utf8() { Ok(path) => { let remote_name = path.into_owned(); let basename = Path::new(&remote_name).file_name(); match basename { Some(name) => Some(name.to_os_string().into_string().unwrap()), None => None, } } Err(_) => None, }; base } #[tokio::main] async fn get_tracks() -> Result, Box> { let resp = reqwest::get(format!("{}/songs", API_URL).as_str()) .await? .json::>() .await?; println!("Found {} tracks.", resp.len()); Ok(resp) } #[cfg(target_family = "unix")] struct JsonFilesystem { tree: Vec, attrs: BTreeMap, inodes: BTreeMap, buffer_data: Vec, buffer_name: String, } #[cfg(target_family = "unix")] impl JsonFilesystem { fn new(tree: &Vec) -> JsonFilesystem { let mut attrs = BTreeMap::new(); let mut inodes = BTreeMap::new(); let ts = time::now().to_timespec(); let attr = FileAttr { ino: 1, size: 0, blocks: 0, atime: ts, mtime: ts, ctime: ts, crtime: ts, kind: FileType::Directory, perm: 0o755, nlink: 0, uid: 0, gid: 0, rdev: 0, flags: 0, }; attrs.insert(1, attr); inodes.insert("/".to_string(), 1); for (i, track) in tree.iter().enumerate() { let basename = get_basename(track.path.as_ref()).unwrap().to_string(); let attr = FileAttr { ino: i as u64 + 2, size: 1024 * 1024 * 1024 as u64, blocks: 0, atime: ts, mtime: ts, ctime: ts, crtime: ts, kind: FileType::RegularFile, perm: 0o644, nlink: 0, uid: 0, gid: 0, rdev: 0, flags: 0, }; attrs.insert(attr.ino, attr); inodes.insert(basename.clone(), attr.ino); } JsonFilesystem { tree: tree.clone(), attrs: attrs, inodes: inodes, buffer_data: Vec::new(), buffer_name: "".to_string(), } } } #[cfg(target_family = "unix")] impl Filesystem for JsonFilesystem { fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { println!("getattr(ino={})", ino); match self.attrs.get(&ino) { Some(attr) => { let ttl = Timespec::new(1, 0); reply.attr(&ttl, attr); } None => reply.error(ENOENT), }; } fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) { println!("lookup(parent={}, name={})", parent, name.to_str().unwrap()); let inode = match self.inodes.get(name.to_str().unwrap()) { Some(inode) => inode, None => { reply.error(ENOENT); return; } }; match self.attrs.get(inode) { Some(attr) => { let ttl = Timespec::new(1, 0); reply.entry(&ttl, attr, 0); } None => reply.error(ENOENT), }; } fn read( &mut self, _req: &Request, ino: u64, fh: u64, offset: i64, size: u32, reply: ReplyData, ) { println!( "read(ino={}, fh={}, offset={}, size={})", ino, fh, offset, size ); //let mus = fs::read("/home/ab/Downloads/Mizuki.mp3").unwrap(); let url = &self.tree[(ino - 2) as usize].path.as_ref().unwrap(); let full_url = format!("{}/{}", API_URL, url); let mut full_track: Vec = Vec::new(); if self.buffer_name == full_url { full_track = self.buffer_data.clone(); println!("Hit cache!"); } else { let resp = reqwest::blocking::get(full_url.as_str()).unwrap(); let test = resp.bytes().unwrap(); full_track = test.to_vec().clone(); self.buffer_data = full_track.clone(); self.buffer_name = full_url; println!("Miss cache!"); } let mut chunk_end = size as usize + offset as usize; if chunk_end >= full_track.len() { chunk_end = full_track.len() - 1; } if offset as usize >= full_track.len() { reply.data(&full_track[(full_track.len() - 1) as usize..chunk_end as usize]); } else { reply.data(&full_track[offset as usize..chunk_end as usize]); } println!("Len: {}, chunk end {}", full_track.len(), chunk_end); return } fn readdir( &mut self, _req: &Request, ino: u64, fh: u64, offset: i64, mut reply: ReplyDirectory, ) { println!("readdir(ino={}, fh={}, offset={})", ino, fh, offset); if ino == 1 { if offset == 0 { reply.add(1, 0, FileType::Directory, "."); reply.add(1, 1, FileType::Directory, ".."); } for (i, (key, &inode)) in self.inodes.iter().enumerate().skip(offset as usize) { if inode == 1 { continue; } reply.add(inode, (i + 1) as i64, FileType::RegularFile, key); } reply.ok(); } else { reply.error(ENOENT); } } } fn main() { let lib = get_tracks().unwrap(); let fs = JsonFilesystem::new(&lib); let mountpoint = match env::args().nth(1) { Some(path) => path, None => { println!("Usage: {} ", env::args().nth(0).unwrap()); return; } }; fuse::mount(fs, &mountpoint, &[]).expect("Couldn't mount filesystem"); }