Skip to content

Commit

Permalink
feat(filebrowser): add file browser
Browse files Browse the repository at this point in the history
  • Loading branch information
ravenclaw900 committed Oct 24, 2021
1 parent c255bf4 commit c0ea2de
Show file tree
Hide file tree
Showing 23 changed files with 606 additions and 217 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
node_modules/
build/
.vscode/
src/backend/public
dist
dietpi-dashboard
.DS_Store
target/
Expand Down
7 changes: 7 additions & 0 deletions src/backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ futures = "0.3.17"
nanoserde = "0.1.29"
pty-process = "0.1.0"
heim = "0.0.11"
infer = { version="0.5.0", default-features = false }

# Heim needs a patch to run on ARM64
[patch.crates-io]
Expand Down
1 change: 1 addition & 0 deletions src/backend/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![warn(clippy::pedantic)]
#![warn(clippy::cognitive_complexity)]
use simple_logger::SimpleLogger;
use warp::Filter;

Expand Down
98 changes: 98 additions & 0 deletions src/backend/src/sockets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,101 @@ async fn service_handler(
}
}

async fn browser_refresh(
socket_send: &mut SplitSink<warp::ws::WebSocket, warp::ws::Message>,
path: &str,
) {
let dir_path = path.rsplit_once('/').unwrap().0;
let _send = socket_send
.send(Message::text(SerJson::serialize_json(
&types::BrowserList {
contents: systemdata::browser_dir(std::path::Path::new(dir_path)),
currentpath: dir_path.to_string(),
},
)))
.await;
}

async fn browser_handler(
socket_send: &mut SplitSink<warp::ws::WebSocket, warp::ws::Message>,
quit: &Arc<AtomicBool>,
data_recv: &mut Receiver<types::Request>,
) {
// Get initial listing of $HOME
let _send = socket_send
.send(Message::text(SerJson::serialize_json(
&types::BrowserList {
contents: systemdata::browser_dir(std::path::Path::new(
&std::env::var_os("HOME").unwrap(),
)),
currentpath: std::env::var("HOME").unwrap(),
},
)))
.await;
loop {
if quit.load(Relaxed) {
quit.store(false, Relaxed);
break;
}
match data_recv.try_recv() {
Err(_) => {}
Ok(data) => match data.cmd.as_str() {
"cd" => {
let _send = socket_send
.send(Message::text(SerJson::serialize_json(
&types::BrowserList {
contents: systemdata::browser_dir(std::path::Path::new(
&data.args[0],
)),
currentpath: data.args[0].clone(),
},
)))
.await;
}
"open" => {
let _send = socket_send
.send(Message::text(SerJson::serialize_json(
&types::BrowserFileData {
data: std::fs::read_to_string(std::path::Path::new(&data.args[0]))
.unwrap(),
currentpath: data.args[0].clone(),
},
)))
.await;
}
"save" => {
std::fs::write(std::path::Path::new(&data.args[0]), &data.args[1]).unwrap();
}
"copy" => {
std::fs::copy(&data.args[0], format!("{} {}", &data.args[0], 2)).unwrap();
browser_refresh(socket_send, &data.args[0]).await;
}
"rm" => {
std::fs::remove_file(&data.args[0]).unwrap();
browser_refresh(socket_send, &data.args[0]).await;
}
"rmdir" => {
std::fs::remove_dir_all(&data.args[0]).unwrap();
browser_refresh(socket_send, &data.args[0]).await;
}
"mkdir" => {
std::fs::create_dir(&data.args[0]).unwrap();
browser_refresh(socket_send, &data.args[0]).await;
}
"mkfile" => {
std::fs::write(&data.args[0], "").unwrap();
browser_refresh(socket_send, &data.args[0]).await;
}
"rename" => {
std::fs::rename(&data.args[0], &data.args[1]).unwrap();
browser_refresh(socket_send, &data.args[0]).await;
}
_ => {}
},
}
}
}

pub async fn socket_handler(socket: warp::ws::WebSocket) {
let (mut socket_send, mut socket_recv) = socket.split();
let (data_send, mut data_recv) = broadcast::channel(1);
Expand Down Expand Up @@ -222,6 +317,9 @@ pub async fn socket_handler(socket: warp::ws::WebSocket) {
"/service" => {
service_handler(&mut socket_send, &quit_clone, &mut data_recv).await;
}
"/browser" => {
browser_handler(&mut socket_send, &quit_clone, &mut data_recv).await;
}
_ => {}
}
}
Expand Down
77 changes: 77 additions & 0 deletions src/backend/src/systemdata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use heim::{
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::fs;
use std::io::Read;
use std::process::Command;
use std::str::from_utf8;
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
Expand Down Expand Up @@ -331,3 +332,79 @@ pub fn global() -> types::GlobalData {
fs::read_to_string("/run/dietpi/.update_available").unwrap_or_else(|_| String::new());
types::GlobalData { update }
}

pub fn browser_dir(path: &std::path::Path) -> Vec<types::BrowserDirData> {
let dir = fs::read_dir(path).unwrap();
let mut file_list = Vec::new();
for file in dir {
let file = file.unwrap();
// Resolve all symlinks
let path = fs::canonicalize(file.path()).unwrap();
let metadata = fs::metadata(&path).unwrap();
let maintype;
let subtype;
let prettytype;
if metadata.is_dir() {
maintype = "dir".to_string();
subtype = String::new();
prettytype = "Directory".to_string();
} else {
let buf;
if let Ok(val) = fs::read(path) {
buf = val
} else {
log::error!("Could not read directory");
return vec![types::BrowserDirData {
path: "/".to_string(),
name: "ERROR".to_string(),
maintype: "dir".to_string(),
subtype: String::new(),
prettytype: "Could not read directory".to_string(),
size: 0,
}];
}
if let Some(infertype) = infer::get(&buf) {
subtype = infertype.mime_type().split_once('/').unwrap().1.to_string();
maintype = {
if infer::is_app(&buf) {
"application"
} else if infer::is_archive(&buf) {
"archive"
} else if infer::is_audio(&buf) {
"audio"
} else if infer::is_image(&buf) {
"image"
} else if infer::is_video(&buf) {
"video"
} else {
"unknown"
}
}
.to_string();
prettytype = format!(
"{} {}{} File",
subtype.to_uppercase(),
maintype.chars().next().unwrap().to_uppercase(),
&maintype[1..]
);
} else if from_utf8(&buf).is_err() {
maintype = "unknown".to_string();
subtype = "unknown".to_string();
prettytype = "Binary file".to_string();
} else {
maintype = "text".to_string();
subtype = "plain".to_string();
prettytype = "Plain Text File".to_string();
}
}
file_list.push(types::BrowserDirData {
path: file.path().into_os_string().into_string().unwrap(),
name: file.file_name().into_string().unwrap(),
maintype,
subtype,
prettytype,
size: metadata.len(),
});
}
file_list
}
14 changes: 5 additions & 9 deletions src/backend/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use futures::{SinkExt, StreamExt};
use nanoserde::DeJson;
use pty_process::Command;
use std::io::{Read, Write};
use std::ops::{Deref, DerefMut};
use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
Arc,
Expand Down Expand Up @@ -38,19 +37,16 @@ pub async fn term_handler(socket: warp::ws::WebSocket) {
}
if data.is_text() && data.to_str().unwrap().get(..4) == Some("size") {
let json: TTYSize = DeJson::deserialize_json(&data.to_str().unwrap()[4..]).unwrap();
lock.deref()
(*lock)
.resize_pty(&pty_process::Size::new(json.rows, json.cols))
.unwrap();
continue;
}
lock.deref().pty().write_all(data.as_bytes()).unwrap();
(*lock).pty().write_all(data.as_bytes()).unwrap();
}
stop_thread_write.store(true, Relaxed);
// Stop reader
cmd_write
.read()
.await
.deref()
(*cmd_write.read().await)
.pty()
.write_all("exit".as_bytes())
.unwrap();
Expand All @@ -60,7 +56,7 @@ pub async fn term_handler(socket: warp::ws::WebSocket) {
loop {
let mut data = [0; 1024];
let lock = cmd_read.read().await;
match lock.deref().pty().read(&mut data) {
match (*lock).pty().read(&mut data) {
Ok(_) => {}
Err(_) => break,
};
Expand All @@ -77,7 +73,7 @@ pub async fn term_handler(socket: warp::ws::WebSocket) {
tokio::try_join!(pty_writer, pty_reader).unwrap();

// Process should be safe to kill after exiting
cmd.write().await.deref_mut().kill().unwrap();
(*cmd.write().await).kill().unwrap();

log::info!("Closed terminal");
}
22 changes: 22 additions & 0 deletions src/backend/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,25 @@ pub struct ServiceList {
pub struct GlobalData {
pub update: String,
}

#[derive(SerJson, Debug)]
pub struct BrowserDirData {
pub path: String,
pub name: String,
pub subtype: String,
pub maintype: String,
pub prettytype: String,
pub size: u64,
}

#[derive(SerJson)]
pub struct BrowserFileData {
pub data: String,
pub currentpath: String,
}

#[derive(SerJson)]
pub struct BrowserList {
pub contents: Vec<BrowserDirData>,
pub currentpath: String,
}
Binary file modified src/frontend/.yarn/install-state.gz
Binary file not shown.
2 changes: 1 addition & 1 deletion src/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DietPi Dashboard</title>
</head>
Expand Down
Loading

0 comments on commit c0ea2de

Please sign in to comment.