219 lines
5.6 KiB
Rust
219 lines
5.6 KiB
Rust
use std::{
|
|
fs::read_dir,
|
|
mem,
|
|
ops::Deref,
|
|
path::{Path, PathBuf},
|
|
sync::mpsc,
|
|
thread,
|
|
};
|
|
|
|
use egui::{Response, Ui};
|
|
use eyre::{Context, OptionExt, eyre};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub enum Folder {
|
|
NotLoaded {
|
|
name: String,
|
|
path: PathBuf,
|
|
},
|
|
Loading {
|
|
name: String,
|
|
path: PathBuf,
|
|
recv: mpsc::Receiver<LoadedFolder>,
|
|
},
|
|
Loaded(LoadedFolder),
|
|
}
|
|
|
|
pub struct LoadedFolder {
|
|
pub name: String,
|
|
pub path: PathBuf,
|
|
pub child_folders: Vec<Folder>,
|
|
pub child_files: Vec<File>,
|
|
}
|
|
|
|
pub struct File {
|
|
pub name: String,
|
|
pub path: PathBuf,
|
|
}
|
|
|
|
pub struct FolderResponse<'a> {
|
|
inner: Response,
|
|
pub open_file: Option<&'a Path>,
|
|
}
|
|
|
|
impl Deref for FolderResponse<'_> {
|
|
type Target = Response;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
impl LoadedFolder {
|
|
pub fn show<'a>(&'a mut self, ui: &mut Ui) -> FolderResponse<'a> {
|
|
let mut open_file = None;
|
|
let inner = ui
|
|
.collapsing(&self.name, |ui| {
|
|
for folder in &mut self.child_folders {
|
|
open_file = open_file.or(folder.show(ui).open_file);
|
|
}
|
|
|
|
for file in &mut self.child_files {
|
|
if ui.button(&file.name).clicked() {
|
|
open_file = Some(file.path.as_path())
|
|
};
|
|
}
|
|
})
|
|
.header_response;
|
|
|
|
FolderResponse { inner, open_file }
|
|
}
|
|
|
|
fn load(path: PathBuf) -> eyre::Result<Self> {
|
|
let name = path
|
|
.file_name()
|
|
.ok_or_eyre("Path is missing a file-name")?
|
|
.to_string_lossy()
|
|
.to_string();
|
|
|
|
let mut child_folders = vec![];
|
|
let mut child_files = vec![];
|
|
|
|
for entry in read_dir(&path).with_context(|| eyre!("Couldn't read dir {path:?}"))? {
|
|
let entry = entry.with_context(|| eyre!("Couldn't read dir {path:?}"))?;
|
|
let path = entry.path();
|
|
let name = path
|
|
.file_name()
|
|
.ok_or_eyre("Path is missing a file-name")?
|
|
.to_string_lossy()
|
|
.to_string();
|
|
|
|
let file_type = entry.file_type()?;
|
|
|
|
if file_type.is_symlink() {
|
|
log::error!("Symlinks not yet supported, skipping {path:?}");
|
|
continue;
|
|
} else if file_type.is_file() {
|
|
child_files.push(File { name, path });
|
|
} else if file_type.is_dir() {
|
|
child_folders.push(Folder::NotLoaded { name, path });
|
|
}
|
|
}
|
|
|
|
let folder = LoadedFolder {
|
|
name,
|
|
path,
|
|
child_folders,
|
|
child_files,
|
|
};
|
|
|
|
Ok(folder)
|
|
}
|
|
}
|
|
|
|
impl Folder {
|
|
fn load(&mut self, ui: &mut Ui) -> Option<&mut LoadedFolder> {
|
|
if let Folder::NotLoaded { name, path } = self {
|
|
let (tx, rx) = mpsc::channel();
|
|
|
|
{
|
|
let path = path.clone();
|
|
let ctx = ui.ctx().clone();
|
|
thread::spawn(move || match LoadedFolder::load(path) {
|
|
Err(e) => log::error!("Failed to load folder: {e}"),
|
|
Ok(folder) => {
|
|
let _ = tx.send(folder);
|
|
ctx.request_repaint();
|
|
}
|
|
});
|
|
}
|
|
|
|
*self = Folder::Loading {
|
|
name: mem::take(name),
|
|
path: mem::take(path),
|
|
recv: rx,
|
|
};
|
|
}
|
|
|
|
if let Folder::Loading { recv, .. } = self {
|
|
match recv.try_recv() {
|
|
Ok(folder) => *self = Folder::Loaded(folder),
|
|
Err(_) => return None,
|
|
}
|
|
}
|
|
|
|
let Folder::Loaded(folder) = self else {
|
|
unreachable!()
|
|
};
|
|
|
|
Some(folder)
|
|
}
|
|
|
|
pub fn show<'a>(&'a mut self, ui: &mut Ui) -> FolderResponse<'a> {
|
|
self.load(ui);
|
|
|
|
if let Folder::Loaded(folder) = self {
|
|
return folder.show(ui);
|
|
}
|
|
|
|
FolderResponse {
|
|
inner: ui.label(self.name()),
|
|
open_file: None,
|
|
}
|
|
}
|
|
|
|
pub fn path(&self) -> &Path {
|
|
match self {
|
|
Folder::NotLoaded { path, .. } => path,
|
|
Folder::Loading { path, .. } => path,
|
|
Folder::Loaded(folder) => &folder.path,
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
match self {
|
|
Folder::NotLoaded { name, .. } => name,
|
|
Folder::Loading { name, .. } => name,
|
|
Folder::Loaded(folder) => &folder.name,
|
|
}
|
|
}
|
|
|
|
pub fn unload(&mut self) {
|
|
let (name, path) = match self {
|
|
Folder::NotLoaded { .. } => return,
|
|
Folder::Loading { name, path, .. } => (name, path),
|
|
Folder::Loaded(folder) => (&mut folder.name, &mut folder.path),
|
|
};
|
|
|
|
*self = Folder::NotLoaded {
|
|
name: mem::take(name),
|
|
path: mem::take(path),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for Folder {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
self.path().serialize(serializer)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Folder {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
use serde::de::Error;
|
|
let path = PathBuf::deserialize(deserializer)?;
|
|
let name = path
|
|
.file_name()
|
|
.ok_or(D::Error::custom("Path is missing a file-name"))?
|
|
.to_string_lossy()
|
|
.to_string();
|
|
Ok(Folder::NotLoaded { name, path })
|
|
}
|
|
}
|