diff --git a/config/development.yaml b/config/development.yaml index 056844e..7742c55 100644 --- a/config/development.yaml +++ b/config/development.yaml @@ -28,6 +28,7 @@ server: enable: true allow_origins: - "http://localhost:3000" + - "http://localhost:3001" allow_credentials: true max_age: 3600 allow_headers: diff --git a/music-metadata-manager-frontend/bun.lockb b/music-metadata-manager-frontend/bun.lockb index dc8e755..2188bf9 100755 Binary files a/music-metadata-manager-frontend/bun.lockb and b/music-metadata-manager-frontend/bun.lockb differ diff --git a/src/app.rs b/src/app.rs index 46bedb7..12de491 100644 --- a/src/app.rs +++ b/src/app.rs @@ -47,6 +47,7 @@ impl Hooks for App { fn routes(_ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() // controller routes below + .add_route(controllers::fs::routes()) .add_route(controllers::musicbrainz::routes()) .add_route(controllers::music_file::routes()) .add_route(controllers::music_library::routes()) diff --git a/src/controllers/fs.rs b/src/controllers/fs.rs new file mode 100644 index 0000000..861b5d8 --- /dev/null +++ b/src/controllers/fs.rs @@ -0,0 +1,18 @@ +#![allow(clippy::missing_errors_doc)] +#![allow(clippy::unnecessary_struct_initialization)] +#![allow(clippy::unused_async)] +use loco_rs::prelude::*; + +use crate::services::fs::get_readable_writable_dirs; + +pub async fn get_fs_directories() -> Result { + let dirs = get_readable_writable_dirs(std::path::Path::new("/")); + + format::json(dirs) +} + +pub fn routes() -> Routes { + Routes::new() + .prefix("api/fs/") + .add("/directories", get(get_fs_directories)) +} diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index 81f88cb..9ce9f18 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -2,4 +2,5 @@ pub mod auth; pub mod music_library; pub mod music_file; -pub mod musicbrainz; \ No newline at end of file +pub mod musicbrainz; +pub mod fs; \ No newline at end of file diff --git a/src/services/fs.rs b/src/services/fs.rs new file mode 100644 index 0000000..ffcdc8d --- /dev/null +++ b/src/services/fs.rs @@ -0,0 +1,81 @@ +use std::{ + fs::{self, OpenOptions}, + path::Path, +}; + +use serde::Serialize; +use walkdir::{DirEntry, WalkDir}; + +const DEPTH: usize = 5; + +#[derive(Debug, Serialize)] +pub struct Directory { + name: String, + path: String, + parent: Option, +} + +fn is_readable_and_writable(path: &Path) -> bool { + if fs::read_dir(path).is_err() { + return false; + } + + let test_file = path.join(".test_rw_check"); + match OpenOptions::new() + .write(true) + .create_new(true) + .open(&test_file) + { + Ok(_) => { + let _ = fs::remove_file(&test_file); + true + } + Err(_) => false, + } +} + +fn is_hidden(entry: &DirEntry) -> bool { + let file_name_hidden = entry + .file_name() + .to_str() + .map(|s| s.starts_with('.')) + .unwrap_or(false); + + #[cfg(windows)] + { + use std::os::windows::fs::MetadataExt; + use winapi::um::winnt::FILE_ATTRIBUTE_HIDDEN; + + if let Ok(metadata) = entry.metadata() { + let attrs = metadata.file_attributes(); + return file_name_hidden || (attrs & FILE_ATTRIBUTE_HIDDEN != 0); + } + } + + file_name_hidden +} + +pub fn get_readable_writable_dirs(root: &Path) -> Vec { + let mut dirs = Vec::new(); + + for entry in WalkDir::new(root) + .follow_links(false) + .max_depth(DEPTH) + .into_iter() + .filter_entry(|e| !is_hidden(e)) + .filter_map(Result::ok) + .filter(|e| e.file_type().is_dir()) + { + let path = entry.path(); + + if is_readable_and_writable(path) { + dirs.push(Directory { + name: entry.file_name().to_string_lossy().to_string(), + path: path.to_string_lossy().to_string(), + parent: path.parent().map(|p| p.to_string_lossy().to_string()), + }); + } + } + + dirs +} diff --git a/src/services/mod.rs b/src/services/mod.rs index b831afd..fca4d0a 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,2 +1,3 @@ +pub mod fs; pub mod musicbrainz; pub mod suggestion; diff --git a/tests/requests/fs.rs b/tests/requests/fs.rs new file mode 100644 index 0000000..bca6a65 --- /dev/null +++ b/tests/requests/fs.rs @@ -0,0 +1,17 @@ +use music_metadata_manager::app::App; +use loco_rs::testing::prelude::*; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn can_get_fs() { + request::(|request, _ctx| async move { + let res = request.get("/api/fs/").await; + assert_eq!(res.status_code(), 200); + + // you can assert content like this: + // assert_eq!(res.text(), "content"); + }) + .await; +} + diff --git a/tests/requests/mod.rs b/tests/requests/mod.rs index d08edbe..5c94689 100644 --- a/tests/requests/mod.rs +++ b/tests/requests/mod.rs @@ -3,4 +3,5 @@ mod prepare_data; pub mod music_library; pub mod music_file; -pub mod musicbrainz; \ No newline at end of file +pub mod musicbrainz; +pub mod fs; \ No newline at end of file