Merge plugin back into main repository (#49)

This commit is contained in:
Lucien Greathouse
2018-04-01 23:22:04 -07:00
committed by GitHub
parent c8f837d726
commit 6fa925a402
55 changed files with 1742 additions and 36 deletions

7
server/src/vfs/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
mod vfs_session;
mod vfs_item;
mod vfs_watcher;
pub use self::vfs_session::*;
pub use self::vfs_item::*;
pub use self::vfs_watcher::*;

View File

@@ -0,0 +1,31 @@
use std::collections::HashMap;
/// A VfsItem represents either a file or directory as it came from the filesystem.
///
/// The interface here is intentionally simplified to make it easier to traverse
/// files that have been read into memory.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type")]
pub enum VfsItem {
File {
route: Vec<String>,
contents: String,
},
Dir {
route: Vec<String>,
children: HashMap<String, VfsItem>,
},
}
impl VfsItem {
pub fn name(&self) -> &String {
self.route().last().unwrap()
}
pub fn route(&self) -> &[String] {
match self {
&VfsItem::File { ref route, .. } => route,
&VfsItem::Dir { ref route, .. } => route,
}
}
}

View File

@@ -0,0 +1,214 @@
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::Instant;
use plugin::PluginChain;
use vfs::VfsItem;
/// Represents a virtual layer over multiple parts of the filesystem.
///
/// Paths in this system are represented as slices of strings, and are always
/// relative to a partition, which is an absolute path into the real filesystem.
pub struct VfsSession {
/// Contains all of the partitions mounted by the Vfs.
///
/// These must be absolute paths!
partitions: HashMap<String, PathBuf>,
/// A chronologically-sorted list of routes that changed since the Vfs was
/// created, along with a timestamp denoting when.
change_history: Vec<VfsChange>,
/// When the Vfs was initialized; used for change tracking.
start_time: Instant,
plugin_chain: &'static PluginChain,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VfsChange {
timestamp: f64,
route: Vec<String>,
}
impl VfsSession {
pub fn new(plugin_chain: &'static PluginChain) -> VfsSession {
VfsSession {
partitions: HashMap::new(),
start_time: Instant::now(),
change_history: Vec::new(),
plugin_chain,
}
}
pub fn get_partitions(&self) -> &HashMap<String, PathBuf> {
&self.partitions
}
pub fn insert_partition<P: Into<PathBuf>>(&mut self, name: &str, path: P) {
let path = path.into();
assert!(path.is_absolute());
self.partitions.insert(name.to_string(), path.into());
}
fn route_to_path(&self, route: &[String]) -> Option<PathBuf> {
let (partition_name, rest) = match route.split_first() {
Some((first, rest)) => (first, rest),
None => return None,
};
let partition = match self.partitions.get(partition_name) {
Some(v) => v,
None => return None,
};
// It's possible that the partition points to a file if `rest` is empty.
// Joining "" onto a path will put a trailing slash on, which causes
// file reads to fail.
let full_path = if rest.is_empty() {
partition.clone()
} else {
let joined = rest.join("/");
let relative = Path::new(&joined);
partition.join(relative)
};
Some(full_path)
}
fn read_dir<P: AsRef<Path>>(&self, route: &[String], path: P) -> Result<VfsItem, ()> {
let path = path.as_ref();
let reader = match fs::read_dir(path) {
Ok(v) => v,
Err(_) => return Err(()),
};
let mut children = HashMap::new();
for entry in reader {
let entry = match entry {
Ok(v) => v,
Err(_) => return Err(()),
};
let path = entry.path();
let name = path.file_name().unwrap().to_string_lossy().into_owned();
let mut child_route = route.iter().cloned().collect::<Vec<_>>();
child_route.push(name.clone());
match self.read_path(&child_route, &path) {
Ok(child_item) => {
children.insert(name, child_item);
},
Err(_) => {},
}
}
Ok(VfsItem::Dir {
route: route.iter().cloned().collect::<Vec<_>>(),
children,
})
}
fn read_file<P: AsRef<Path>>(&self, route: &[String], path: P) -> Result<VfsItem, ()> {
let path = path.as_ref();
let mut file = match File::open(path) {
Ok(v) => v,
Err(_) => return Err(()),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => {},
Err(_) => return Err(()),
}
Ok(VfsItem::File {
route: route.iter().cloned().collect::<Vec<_>>(),
contents,
})
}
fn read_path<P: AsRef<Path>>(&self, route: &[String], path: P) -> Result<VfsItem, ()> {
let path = path.as_ref();
let metadata = match fs::metadata(path) {
Ok(v) => v,
Err(_) => return Err(()),
};
if metadata.is_dir() {
self.read_dir(route, path)
} else if metadata.is_file() {
self.read_file(route, path)
} else {
Err(())
}
}
/// Get the current time, used for logging timestamps for file changes.
pub fn current_time(&self) -> f64 {
let elapsed = self.start_time.elapsed();
elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * 1e-9
}
/// Register a new change to the filesystem at the given timestamp and VFS
/// route.
pub fn add_change(&mut self, timestamp: f64, route: Vec<String>) {
match self.plugin_chain.handle_file_change(&route) {
Some(routes) => {
for route in routes {
self.change_history.push(VfsChange {
timestamp,
route,
});
}
},
None => {}
}
}
/// Collect a list of changes that occured since the given timestamp.
pub fn changes_since(&self, timestamp: f64) -> &[VfsChange] {
let mut marker: Option<usize> = None;
for (index, value) in self.change_history.iter().enumerate().rev() {
if value.timestamp >= timestamp {
marker = Some(index);
} else {
break;
}
}
if let Some(index) = marker {
&self.change_history[index..]
} else {
&self.change_history[..0]
}
}
/// Read an item from the filesystem using the given VFS route.
pub fn read(&self, route: &[String]) -> Result<VfsItem, ()> {
match self.route_to_path(route) {
Some(path) => self.read_path(route, &path),
None => Err(()),
}
}
pub fn write(&self, _route: &[String], _item: VfsItem) -> Result<(), ()> {
unimplemented!()
}
pub fn delete(&self, _route: &[String]) -> Result<(), ()> {
unimplemented!()
}
}

View File

@@ -0,0 +1,108 @@
use std::path::PathBuf;
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
use pathext::path_to_route;
use vfs::VfsSession;
/// An object that registers watchers on the real filesystem and relays those
/// changes to the virtual filesystem layer.
pub struct VfsWatcher {
vfs: Arc<Mutex<VfsSession>>,
}
impl VfsWatcher {
pub fn new(vfs: Arc<Mutex<VfsSession>>) -> VfsWatcher {
VfsWatcher {
vfs,
}
}
fn start_watcher(
vfs: Arc<Mutex<VfsSession>>,
rx: mpsc::Receiver<DebouncedEvent>,
partition_name: String,
root_path: PathBuf,
) {
loop {
let event = rx.recv().unwrap();
let mut vfs = vfs.lock().unwrap();
let current_time = vfs.current_time();
match event {
DebouncedEvent::Write(ref change_path) |
DebouncedEvent::Create(ref change_path) |
DebouncedEvent::Remove(ref change_path) => {
if let Some(mut route) = path_to_route(&root_path, change_path) {
route.insert(0, partition_name.clone());
vfs.add_change(current_time, route);
} else {
eprintln!("Failed to get route from {}", change_path.display());
}
},
DebouncedEvent::Rename(ref from_change, ref to_change) => {
if let Some(mut route) = path_to_route(&root_path, from_change) {
route.insert(0, partition_name.clone());
vfs.add_change(current_time, route);
} else {
eprintln!("Failed to get route from {}", from_change.display());
}
if let Some(mut route) = path_to_route(&root_path, to_change) {
route.insert(0, partition_name.clone());
vfs.add_change(current_time, route);
} else {
eprintln!("Failed to get route from {}", to_change.display());
}
},
_ => {},
}
}
}
pub fn start(self) {
let mut watchers = Vec::new();
// Create an extra scope so that `vfs` gets dropped and unlocked
{
let vfs = self.vfs.lock().unwrap();
for (ref partition_name, ref root_path) in vfs.get_partitions() {
let (tx, rx) = mpsc::channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(1))
.expect("Unable to create watcher! This is a bug in Rojo.");
match watcher.watch(&root_path, RecursiveMode::Recursive) {
Ok(_) => (),
Err(_) => {
panic!("Unable to watch partition {}, with path {}! Make sure that it's a file or directory.", partition_name, root_path.display());
},
}
watchers.push(watcher);
{
let partition_name = partition_name.to_string();
let root_path = root_path.to_path_buf();
let vfs = self.vfs.clone();
thread::spawn(move || {
Self::start_watcher(vfs, rx, partition_name, root_path);
});
}
}
}
loop {
thread::park();
}
}
}