mirror of
https://github.com/house-of-vanity/mus-fuse.git
synced 2025-07-06 13:14:08 +00:00
Ititial commit. It works somehow.
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
43
.vscode/launch.json
vendored
Normal file
43
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"env": {"RUST_BACKTRACE": "1"},
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'musfuse'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"run",
|
||||
],
|
||||
},
|
||||
"args": [
|
||||
"mnt"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"postDebugTask": "Umount FUSE",
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'musfuse'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=musfuse",
|
||||
"--package=musfuse"
|
||||
],
|
||||
"filter": {
|
||||
"name": "musfuse",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
22
.vscode/tasks.json
vendored
Normal file
22
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cargo",
|
||||
"subcommand": "build",
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "Umount FUSE",
|
||||
"type": "shell",
|
||||
"command": "fusermount -u mnt",
|
||||
"group": "build",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "musfuse"
|
||||
version = "0.1.0"
|
||||
authors = ["AB <ultradesu@hexor.ru>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
reqwest = { version = "0.10", features = ["json", "blocking"] }
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
percent-encoding = "*"
|
||||
fuse = "*"
|
||||
time = "0.1"
|
||||
libc = "*"
|
||||
rustc-serialize = "*"
|
244
src/main.rs
Normal file
244
src/main.rs
Normal file
@ -0,0 +1,244 @@
|
||||
// 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<String>,
|
||||
pub name: Option<String>,
|
||||
pub artist: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub genre: Option<String>,
|
||||
pub year: Option<i32>,
|
||||
pub format: Option<String>,
|
||||
pub filetype: Option<String>,
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
const API_URL: &str = "https://mus.hexor.ru";
|
||||
|
||||
fn get_basename(path: Option<&String>) -> Option<String> {
|
||||
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<Vec<Track>, Box<dyn std::error::Error>> {
|
||||
let resp = reqwest::get(format!("{}/songs", API_URL).as_str())
|
||||
.await?
|
||||
.json::<Vec<Track>>()
|
||||
.await?;
|
||||
println!("Found {} tracks.", resp.len());
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
struct JsonFilesystem {
|
||||
tree: Vec<Track>,
|
||||
attrs: BTreeMap<u64, FileAttr>,
|
||||
inodes: BTreeMap<String, u64>,
|
||||
buffer_data: Vec<u8>,
|
||||
buffer_name: String,
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
impl JsonFilesystem {
|
||||
fn new(tree: &Vec<Track>) -> 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<u8> = 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: {} <MOUNTPOINT>", env::args().nth(0).unwrap());
|
||||
return;
|
||||
}
|
||||
};
|
||||
fuse::mount(fs, &mountpoint, &[]).expect("Couldn't mount filesystem");
|
||||
}
|
Reference in New Issue
Block a user