From 75ca414ef15248389611a05dfeb85faa269cddb0 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 28 Jul 2025 03:38:54 +0200 Subject: [PATCH] Get fs directories --- config/development.yaml | 1 + music-metadata-manager-frontend/bun.lockb | Bin 225942 -> 225958 bytes src/app.rs | 1 + src/controllers/fs.rs | 18 +++++ src/controllers/mod.rs | 3 +- src/services/fs.rs | 81 ++++++++++++++++++++++ src/services/mod.rs | 1 + tests/requests/fs.rs | 17 +++++ tests/requests/mod.rs | 3 +- 9 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/controllers/fs.rs create mode 100644 src/services/fs.rs create mode 100644 tests/requests/fs.rs 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 dc8e7559d33955af5358f7228e7701b2deb679da..2188bf95a1dcd66ebc63a326a44c4f7eff205323 100755 GIT binary patch delta 2631 zcmYM$3s6->0LJmXdoS-tzChEELIhDc-OIffL_`Tu)Jia%eB>}fLg54%ppsS>Q>-ze za@3Pnf@z{8D5wEq8jX(}A3dzbw>9|6S4lQ$h4tUdq3+CY&N=(-p53#%=fLVq0h=xb zyfe`hyeO?K&9o~`_O$e`8u<^NSKW>UoNK`WoiiO)DHuVGB2Z^zYxQPr$=XFZ=*1P zORG`lW0VC__NGzhYm_~pw|dB)G2MuAxv%Qe--x<%{=88ZV3Z9r=3q{sK08oGHb!|$ zvnimEn--xMbM$yG=^C@02HUU`_1J-3P!m)KH7O0K!8&||ji^KwKE!&x!%OydE#$NW zGcleqjl)Q zk&}?4i`%C3SPy_ScKOw4|7qA zIy^)d0q}zf5*<9}ef*8PxF?04;Ul}d&T;V*&f)^jLv5@lF%fA<$0SU~G^kCs3ab%` zaHvf-0AYy0eTxwPKrkQ^BrH(-&I^9<_uw4};3QB#<|7Bh*3zyH^=LpiQ3M7d8ft&q z;XoRsN5s{drAM=v~dVI$t ze2-+}(Ma*o*ZRx8u`QJC!6EELEB0d_-ltv&i`dTd-o!$>T7Y7+e%@a;^vxp6MksEP zze481JhNUCAp5%>ru;Ro=Oc-sgrGk{Q9@Y?$FWGo6PSUCNXI0!F@{j;`Qj<8gqbnf zaF1*~*Xm2CzI=H|MPITX$db(>Sj?s1CFG+jkP-5m*_an7uLS$NGqCe|PUEzJGR)*M zWjeJO&C0H!(u2J6Z^vaPjQ=*;CQX?(5}QqYM)D9zdRt?YwPkmRJF#gthd5;y+eQ*6#P1}*v{}1z|iBU5wN{o;g$tp;}^a+wwj^gSxq^ zkvK&;Nner+V-J0J?OaDk*R36*nk0y1^U&9qW!&i(VYJ~H_K@@@Dcv(Acj#^NmaaBV zae^e2bp-4{r<=QJ6dWqIzDLhj^n3_qKuB5x^7k6&b=l-_geC2-&6eCU>ZT>oOVoc(~?YoUW8L0YlsHMwm`=vDeR*Ntan^pL; zJ=OfCdgTw!9v(X28>1thTYcBnT=jWIww}FzXEupdi_>g+Qk0Bl&$`!Jh*??1InlWV zMTsjaQaT>nt5(&)PGC2yPl+07`Rw^C&*Z)lOfpQ1Rf+oZ;zLpyadK6}$h@!JojTO^ zn@YleDy93y&l&CI&kb>R>Wpz})`FapVm7o`bokDxQBUbNqGX7d8j(Yb)w>Or(Okr9 z!!)a&!o?$|%s4HU5el6fsx>;fulBo+)7N)$L%U|x?D~q%HoSM-;NW1H8rJzp3}V5H zvs6zaI=1b4YKp6sHV&%UtnQuOm^?)GuZaA-nMqZ@-JM6@5k)!q^KuHcF(t)0g#}qJ gWE2FbEI(-a7kzx#=Kufz delta 2646 zcmYk;2T&AO0LJmVcSk!p&`42HA#}mR8jXU)j+g|nB-Y7b0mTt4cPQ4w?r22gs2hz6 zDhd(-u>=&$m{EzNqcKrO#gYt)y`fGtBP9QqTixu;kA3^T_xA0+x4Xx#yXFmd&1(|$ z&I=NkBSWO!A!?nQ+RjYf za`k*Bwa$aOJcBNl9Vb#NJt?QsS)SNkt!qqqqFQHG>%!$|Y{^O!XAxEq<1F;%sSGe3 zR@jty7h&(WhgQWXK`Hj44EwMja!L-O2wSifxyZvdY)8Igb``$%EQ)iGj2?{K6TQ(N z0}wCwWTEiJRpuuGk+?&47qz&D`*?tdc!bAzf~RSb&9S$q;6_3eZE~ z9rsy}2E4&rp;N}Wi6-_N9Nfe$+{T{>;v~TcK`6oyjtE4;f+)0ud>%WXBRZio8jSL0 z865K2bcH)S(HQw$Pyq@JI>pl@%=Quv%CHae!5xkfkPqxABp?xkFcg6ZLNMe59Eva` zGVTg)AdmerESJYviP0Fy3V)6-kbpq^#e~l^i4pcQG&qZ2P>d3kVlT?D5BqTd2k{fi zQGr7^3^y*q54BvxJ?xNoi=Eho0u*95itr=$V4IW9Lzhb-4;!&bnbk=6MJ=Yd1WU0D z^361Iegoty{10P1#|u0bN`$)zG*6<!h$HYLwj^UM|3jiB6Xc9e1b0MicirE-4Tr# z^gygZDRLJrBC5F16F7Y7B>3M#vF_x8w-Ct70PL| zXdNq+^W-*Ugp`;@F#FGCOsQ4d@(TO`DxECAECDhq&nDmaV4A zAfxdMzp- z@+K;5yKu#jSAL;da)iW}B>Tv))V41SyS3zZ5`U7fQ}61Rq`!TmC3R|_vX|A1N^9?D zXo>t)l(S&3PG52=r{K*ZE&7;9&cfo{E=$i8Rh`n37&WoDb{kRITYp1KhLOlw@En>G zKA>b%u$H8g$Ypi54gc=TwwM3VlBKdwGST_)KKsvKv$oNq%|u=#bLLfFnBY}aq$Tp# zQx5b^R{slicOFJ-$!WDug_8Ml$F1D=i>C}4D7?NRb8PaPA{R2P=2>>|(@LGz-3yOkwGkHW0UkfbmSRcGu=ZG+)3?6YvHvZq z{1C|7Sv6_qy6V(rKJQQ9`1ZSz!q;A!ONG-`o$+F3t zS#ajnwa*4C@7f6YMkr09gs;*uLio9e$y|xmrVNb`7P**?P2q%c=B^$uzEpqx(Si3T zRJPO1MQx^Bi4buCANtImGXB5LmM%4q;`-W8sC|^uiNd>laHMFR6TJ73qxExj@AwZe pEyHRXGkJ29H6?vU%5+6JB-$uP4~agefWwaF4b#d^hsEZKe*r_$sg(c# 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