mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-21 05:06:29 +00:00
243 lines
6.6 KiB
Rust
243 lines
6.6 KiB
Rust
use std::collections::HashMap;
|
|
use std::io::Read;
|
|
use std::fs::{self, File};
|
|
use std::mem;
|
|
|
|
use file_route::FileRoute;
|
|
use project::Project;
|
|
|
|
/// Represents a file or directory that has been read from the filesystem.
|
|
#[derive(Debug, Clone)]
|
|
pub enum FileItem {
|
|
File {
|
|
contents: String,
|
|
route: FileRoute,
|
|
},
|
|
Directory {
|
|
children: HashMap<String, FileItem>,
|
|
route: FileRoute,
|
|
},
|
|
}
|
|
|
|
impl FileItem {
|
|
pub fn get_route(&self) -> &FileRoute {
|
|
match self {
|
|
FileItem::File { route, .. } => route,
|
|
FileItem::Directory { route, .. } => route,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum FileChange {
|
|
Created(FileRoute),
|
|
Deleted(FileRoute),
|
|
Updated(FileRoute),
|
|
Moved(FileRoute, FileRoute),
|
|
}
|
|
|
|
pub struct VfsSession {
|
|
pub project: Project,
|
|
|
|
/// The in-memory files associated with each partition.
|
|
pub partition_files: HashMap<String, FileItem>,
|
|
}
|
|
|
|
impl VfsSession {
|
|
pub fn new(project: Project) -> VfsSession {
|
|
VfsSession {
|
|
project,
|
|
partition_files: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn read_partitions(&mut self) {
|
|
for partition_name in self.project.partitions.keys() {
|
|
let route = FileRoute {
|
|
partition: partition_name.clone(),
|
|
route: Vec::new(),
|
|
};
|
|
|
|
let file_item = self.read(&route).expect("Couldn't load partitions");
|
|
|
|
self.partition_files.insert(partition_name.clone(), file_item);
|
|
}
|
|
}
|
|
|
|
pub fn handle_change(&mut self, change: &FileChange) -> Option<()> {
|
|
match change {
|
|
FileChange::Created(route) | FileChange::Updated(route) => {
|
|
let new_item = self.read(&route).ok()?;
|
|
self.set_file_item(new_item);
|
|
},
|
|
FileChange::Deleted(route) => {
|
|
self.delete_route(&route);
|
|
},
|
|
FileChange::Moved(from_route, to_route) => {
|
|
let new_item = self.read(&to_route).ok()?;
|
|
self.delete_route(&from_route);
|
|
self.set_file_item(new_item);
|
|
},
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
pub fn get_by_route(&self, route: &FileRoute) -> Option<&FileItem> {
|
|
let partition = self.partition_files.get(&route.partition)?;
|
|
let mut current = partition;
|
|
|
|
for piece in &route.route {
|
|
match current {
|
|
FileItem::File { .. } => return None,
|
|
FileItem::Directory { children, .. } => {
|
|
current = children.get(piece)?;
|
|
},
|
|
}
|
|
}
|
|
|
|
Some(current)
|
|
}
|
|
|
|
pub fn get_by_route_mut(&mut self, route: &FileRoute) -> Option<&mut FileItem> {
|
|
let mut current = self.partition_files.get_mut(&route.partition)?;
|
|
|
|
for piece in &route.route {
|
|
let mut next = match { current } {
|
|
FileItem::File { .. } => return None,
|
|
FileItem::Directory { children, .. } => {
|
|
children.get_mut(piece)?
|
|
},
|
|
};
|
|
|
|
current = next;
|
|
}
|
|
|
|
Some(current)
|
|
}
|
|
|
|
pub fn set_file_item(&mut self, item: FileItem) {
|
|
match self.get_by_route_mut(item.get_route()) {
|
|
Some(existing) => {
|
|
mem::replace(existing, item);
|
|
return;
|
|
},
|
|
None => {},
|
|
}
|
|
|
|
if item.get_route().route.len() > 0 {
|
|
let mut parent_route = item.get_route().clone();
|
|
let child_name = parent_route.route.pop().unwrap();
|
|
|
|
let mut parent_children = HashMap::new();
|
|
parent_children.insert(child_name, item);
|
|
|
|
let parent_item = FileItem::Directory {
|
|
route: parent_route,
|
|
children: parent_children,
|
|
};
|
|
|
|
self.set_file_item(parent_item);
|
|
} else {
|
|
self.partition_files.insert(item.get_route().partition.clone(), item);
|
|
}
|
|
}
|
|
|
|
pub fn delete_route(&mut self, route: &FileRoute) -> Option<()> {
|
|
if route.route.len() == 0 {
|
|
self.partition_files.remove(&route.partition);
|
|
return Some(());
|
|
}
|
|
|
|
let mut current = self.partition_files.get_mut(&route.partition)?;
|
|
|
|
for i in 0..(route.route.len() - 1) {
|
|
let piece = &route.route[i];
|
|
|
|
let mut next = match { current } {
|
|
FileItem::File { .. } => return None,
|
|
FileItem::Directory { children, .. } => {
|
|
children.get_mut(piece)?
|
|
},
|
|
};
|
|
|
|
current = next;
|
|
}
|
|
|
|
match current {
|
|
FileItem::Directory { children, .. } => {
|
|
children.remove(route.route.last().unwrap().as_str());
|
|
},
|
|
_ => {},
|
|
}
|
|
|
|
Some(())
|
|
}
|
|
|
|
fn read(&self, route: &FileRoute) -> Result<FileItem, ()> {
|
|
let partition_path = &self.project.partitions.get(&route.partition)
|
|
.ok_or(())?.path;
|
|
let path = route.to_path_buf(partition_path);
|
|
|
|
let metadata = fs::metadata(path)
|
|
.map_err(|_| ())?;
|
|
|
|
if metadata.is_dir() {
|
|
self.read_directory(route)
|
|
} else if metadata.is_file() {
|
|
self.read_file(route)
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
fn read_file(&self, route: &FileRoute) -> Result<FileItem, ()> {
|
|
let partition_path = &self.project.partitions.get(&route.partition)
|
|
.ok_or(())?.path;
|
|
let path = route.to_path_buf(partition_path);
|
|
|
|
let mut file = File::open(path)
|
|
.map_err(|_| ())?;
|
|
|
|
let mut contents = String::new();
|
|
|
|
file.read_to_string(&mut contents)
|
|
.map_err(|_| ())?;
|
|
|
|
Ok(FileItem::File {
|
|
contents,
|
|
route: route.clone(),
|
|
})
|
|
}
|
|
|
|
fn read_directory(&self, route: &FileRoute) -> Result<FileItem, ()> {
|
|
let partition_path = &self.project.partitions.get(&route.partition)
|
|
.ok_or(())?.path;
|
|
let path = route.to_path_buf(partition_path);
|
|
|
|
let reader = fs::read_dir(path)
|
|
.map_err(|_| ())?;
|
|
|
|
let mut children = HashMap::new();
|
|
|
|
for entry in reader {
|
|
let entry = entry
|
|
.map_err(|_| ())?;
|
|
|
|
let path = entry.path();
|
|
let name = path.file_name().unwrap().to_string_lossy().into_owned();
|
|
|
|
let child_route = route.extended_with(&[&name]);
|
|
|
|
let child_item = self.read(&child_route)?;
|
|
|
|
children.insert(name, child_item);
|
|
}
|
|
|
|
Ok(FileItem::Directory {
|
|
children,
|
|
route: route.clone(),
|
|
})
|
|
}
|
|
}
|