diff --git a/lesson-25-x-terrain/core/shaders/quad.frag b/lesson-25-x-terrain/core/shaders/quad.frag index dbc71c2..181aa6c 100644 --- a/lesson-25-x-terrain/core/shaders/quad.frag +++ b/lesson-25-x-terrain/core/shaders/quad.frag @@ -8,6 +8,5 @@ out vec4 Color; void main() { - Color = IN.Color; } \ No newline at end of file diff --git a/lesson-25-x-terrain/src/main.rs b/lesson-25-x-terrain/src/main.rs index fed7052..b34b2b3 100644 --- a/lesson-25-x-terrain/src/main.rs +++ b/lesson-25-x-terrain/src/main.rs @@ -20,7 +20,8 @@ fn run() -> Result<(), failure::Error> { "core", 0, FileSystem::from_rel_path(env!("CARGO_MANIFEST_DIR"), "core") - .with_write(), + .with_write() + .with_watch(), ); let res = resources.resource("shaders/quad.frag"); @@ -31,8 +32,7 @@ fn run() -> Result<(), failure::Error> { resources.notify_changes_synced(p); } - let mut v = String::new(); - ::std::io::stdin().read_line(&mut v).unwrap(); + ::std::thread::sleep_ms(500); } Ok(()) diff --git a/lib/resources/src/backend/filesystem.rs b/lib/resources/src/backend/filesystem.rs index 51605f6..31e791b 100644 --- a/lib/resources/src/backend/filesystem.rs +++ b/lib/resources/src/backend/filesystem.rs @@ -1,48 +1,129 @@ -use crate::backend::{Backend, BackendSyncPoint}; +use crate::backend::{Backend, BackendSyncPoint, Modification}; use std::path::{Path, PathBuf}; use std::{fs, io}; +use std::collections::VecDeque; use crate::{Error, ResourcePath}; use std::sync::Mutex; #[cfg(feature = "backend_filesystem_watch")] mod watch_impl { + use std::collections::VecDeque; use std::path::{Path, PathBuf}; - use std::sync::mpsc::{channel, Receiver}; - use std::time::Duration; + use std::sync::mpsc::{channel, Receiver, TryRecvError}; + use std::time::{Duration, Instant}; use notify::{RecommendedWatcher, Watcher as NotifyWatcher, RecursiveMode, DebouncedEvent}; + use crate::backend::{BackendSyncPoint, Modification}; + use crate::{ResourcePathBuf}; pub struct Watcher { root_path: PathBuf, - watcher: RecommendedWatcher, + _watcher: RecommendedWatcher, receiver: Receiver, + outdated_at: Option, } impl Watcher { pub fn new(root_path: &Path) -> Option { let (tx, rx) = channel(); - let mut watcher: RecommendedWatcher = NotifyWatcher::new(tx, Duration::from_secs(2)) - .map_err(|e| error!("faled to create watcher for {:?}", root_path)) + let mut watcher: RecommendedWatcher = NotifyWatcher::new(tx, Duration::from_millis(50)) + .map_err(|e| error!("failed to create watcher for {:?}, {:?}", root_path, e)) .ok()?; watcher.watch(root_path, RecursiveMode::Recursive).ok()?; Some(Watcher { root_path: root_path.into(), - watcher, + _watcher: watcher, receiver: rx, + outdated_at: None, }) } + + pub fn notify_changes_synced(&mut self, point: BackendSyncPoint) { + if let Some(last_outdated) = self.outdated_at { + if point.instant == last_outdated { + self.outdated_at = None; + } + } + } + + pub fn new_changes(&mut self, queue: &mut VecDeque) -> Option { + let mut something_outdated = false; + + loop { + match self.receiver.try_recv() { + Ok(event) => { + match event { + DebouncedEvent::Create(path) => { + if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) { + queue.push_back(Modification::Create(resource_path)); + something_outdated = true; + } else { + warn!("unrecognised resource path {:?} for {} event", path, "Create") + } + }, + DebouncedEvent::Write(path) => { + if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) { + queue.push_back(Modification::Write(resource_path)); + something_outdated = true; + } else { + warn!("unrecognised resource path {:?} for {} event", path, "Write") + } + }, + DebouncedEvent::Remove(path) => { + if let Some(resource_path) = ResourcePathBuf::from_filesystem_path(&self.root_path, &path) { + queue.push_back(Modification::Remove(resource_path)); + something_outdated = true; + } else { + warn!("unrecognised resource path {:?} for {} event", path, "Remove") + } + }, + DebouncedEvent::Rename(from_path, to_path) => { + match (ResourcePathBuf::from_filesystem_path(&self.root_path, &from_path), ResourcePathBuf::from_filesystem_path(&self.root_path, &to_path)) { + (Some(from), Some(to)) => { + queue.push_back(Modification::Rename { from, to }); + something_outdated = true; + }, + (None, Some(_)) => warn!("unrecognised resource path {:?} for {} event", from_path, "Rename"), + (Some(_), None) => warn!("unrecognised resource path {:?} for {} event", to_path, "Rename"), + (None, None) => warn!("unrecognised resource paths {:?} and {:?} for Rename event", from_path, to_path), + } + }, + _ => (), + } + }, + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => { + error!("filesystem watcher disconnected"); + break; + }, + } + } + + if something_outdated { + let outdated_at = Instant::now(); + + self.outdated_at = Some(outdated_at); + + Some(BackendSyncPoint { instant: outdated_at }) + } else { + None + } + } } } #[cfg(not(feature = "backend_filesystem_watch"))] mod watch_impl { - use std::path::{Path}; + use std::collections::VecDeque; + use crate::backend::{BackendSyncPoint, Modification}; pub struct Watcher {} impl Watcher { - pub fn new(_root_path: &Path) -> Option { + pub fn notify_changes_synced(&mut self, _point: BackendSyncPoint) {} + + pub fn new_changes(&mut self, _queue: &mut VecDeque) -> Option { None } } @@ -91,10 +172,18 @@ impl Backend for FileSystem { resource_name_to_path(&self.root_path, path).exists() } - fn notify_changes_synced(&mut self, _point: BackendSyncPoint) {} + fn notify_changes_synced(&mut self, point: BackendSyncPoint) { + if let Some(ref mut watch) = self.watch { + watch.lock().unwrap().notify_changes_synced(point); + } + } - fn new_changes(&mut self) -> Option { - None + fn new_changes(&mut self, queue: &mut VecDeque) -> Option { + if let Some(ref mut watch) = self.watch { + watch.lock().unwrap().new_changes(queue) + } else { + None + } } fn read_into(&mut self, path: &ResourcePath, mut output: &mut io::Write) -> Result<(), Error> { diff --git a/lib/resources/src/backend/mod.rs b/lib/resources/src/backend/mod.rs index 6c220c0..404861a 100644 --- a/lib/resources/src/backend/mod.rs +++ b/lib/resources/src/backend/mod.rs @@ -1,4 +1,5 @@ -use crate::path::ResourcePath; +use crate::path::{ResourcePath, ResourcePathBuf}; +use std::collections::VecDeque; use std::io; use std::time::Instant; use crate::Error; @@ -20,7 +21,15 @@ pub use self::filesystem::FileSystem; #[derive(Eq, PartialEq, Copy, Clone, Debug)] pub struct BackendSyncPoint { - instant: Instant, + pub (crate) instant: Instant, +} + +#[derive(Eq, PartialEq,Clone, Debug)] +pub enum Modification { + Create(ResourcePathBuf), + Write(ResourcePathBuf), + Remove(ResourcePathBuf), + Rename { from: ResourcePathBuf, to: ResourcePathBuf }, } impl BackendSyncPoint { @@ -36,7 +45,7 @@ pub trait Backend: Send + Sync { fn exists(&self, path: &ResourcePath) -> bool; fn notify_changes_synced(&mut self, point: BackendSyncPoint); - fn new_changes(&mut self) -> Option; + fn new_changes(&mut self, queue: &mut VecDeque) -> Option; fn read_into(&mut self, path: &ResourcePath, output: &mut io::Write) -> Result<(), Error>; fn read_vec(&mut self, path: &ResourcePath) -> Result, Error> { diff --git a/lib/resources/src/path.rs b/lib/resources/src/path.rs index 76c69c0..f90d1a5 100644 --- a/lib/resources/src/path.rs +++ b/lib/resources/src/path.rs @@ -147,7 +147,11 @@ impl ResourcePathBuf { let mut path = ResourcePathBuf { inner: String::with_capacity(relative_dir.as_os_str().len() + 32) }; for part in relative_dir.components() { - path = path.join(unsanitize_path_component(part.as_os_str()).as_ref()); + if let Some(unsanitized_part) = unsanitize_path_component(part.as_os_str()) { + path = path.join(unsanitized_part.as_ref()); + } else { + return None; + } } Some(path) @@ -217,25 +221,25 @@ fn check_for_sanitize_fix(previous_len: usize, remainder: &[u8]) -> Option { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", remainder, b"_"), + fix: FixOutput::Triple(b"+r", remainder, b"+"), }) } (b'o', b'n', Some(b'.'), _) => { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", &remainder[..3], b"_"), + fix: FixOutput::Triple(b"+r", &remainder[..3], b"+"), }) } (b'o', b'm', Some(b'1'...b'9'), None) => { return Some(FixSolution { problematic_sequence_len: 4, - fix: FixOutput::Triple(b"_r", remainder, b"_"), + fix: FixOutput::Triple(b"+r", remainder, b"+"), }) } (b'o', b'm', Some(b'1'...b'9'), Some(b'.')) => { return Some(FixSolution { problematic_sequence_len: 4, - fix: FixOutput::Triple(b"_r", &remainder[..4], b"_"), + fix: FixOutput::Triple(b"+r", &remainder[..4], b"+"), }) } _ => (), @@ -250,13 +254,13 @@ fn check_for_sanitize_fix(previous_len: usize, remainder: &[u8]) -> Option { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", remainder, b"_"), + fix: FixOutput::Triple(b"+r", remainder, b"+"), }) } (b'r', b'n', Some(b'.')) => { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", &remainder[..3], b"_"), + fix: FixOutput::Triple(b"+r", &remainder[..3], b"+"), }) } _ => (), @@ -271,13 +275,13 @@ fn check_for_sanitize_fix(previous_len: usize, remainder: &[u8]) -> Option { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", remainder, b"_"), + fix: FixOutput::Triple(b"+r", remainder, b"+"), }) } (b'u', b'x', Some(b'.')) => { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", &remainder[..3], b"_"), + fix: FixOutput::Triple(b"+r", &remainder[..3], b"+"), }) } _ => (), @@ -292,13 +296,13 @@ fn check_for_sanitize_fix(previous_len: usize, remainder: &[u8]) -> Option { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", remainder, b"_"), + fix: FixOutput::Triple(b"+r", remainder, b"+"), }) } (b'u', b'l', Some(b'.')) => { return Some(FixSolution { problematic_sequence_len: 3, - fix: FixOutput::Triple(b"_r", &remainder[..3], b"_"), + fix: FixOutput::Triple(b"+r", &remainder[..3], b"+"), }) } _ => (), @@ -314,13 +318,13 @@ fn check_for_sanitize_fix(previous_len: usize, remainder: &[u8]) -> Option { return Some(FixSolution { problematic_sequence_len: 4, - fix: FixOutput::Triple(b"_r", remainder, b"_"), + fix: FixOutput::Triple(b"+r", remainder, b"+"), }) } (b'p', b't', b'1'...b'9', Some(b'.')) => { return Some(FixSolution { problematic_sequence_len: 4, - fix: FixOutput::Triple(b"_r", &remainder[..4], b"_"), + fix: FixOutput::Triple(b"+r", &remainder[..4], b"+"), }) } _ => (), @@ -333,88 +337,88 @@ fn check_for_sanitize_fix(previous_len: usize, remainder: &[u8]) -> Option Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_b_"), + fix: FixOutput::Single(b"+b+"), }), - b'_' => Some(FixSolution { + b'+' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"__"), + fix: FixOutput::Single(b"++"), }), b'<' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_lt_"), + fix: FixOutput::Single(b"+lt+"), }), b'>' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_gt_"), + fix: FixOutput::Single(b"+gt+"), }), b':' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_c_"), + fix: FixOutput::Single(b"+c+"), }), b'\"' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_q_"), + fix: FixOutput::Single(b"+q+"), }), b'/' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_sl_"), + fix: FixOutput::Single(b"+sl+"), }), b'|' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_p_"), + fix: FixOutput::Single(b"+p+"), }), b'?' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_m_"), + fix: FixOutput::Single(b"+m+"), }), b'*' => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_a_"), + fix: FixOutput::Single(b"+a+"), }), i @ 1..=31 => Some(FixSolution { problematic_sequence_len: 1, fix: match i { - 1 => FixOutput::Single(b"_i1_"), - 2 => FixOutput::Single(b"_i2_"), - 3 => FixOutput::Single(b"_i3_"), - 4 => FixOutput::Single(b"_i4_"), - 5 => FixOutput::Single(b"_i5_"), - 6 => FixOutput::Single(b"_i6_"), - 7 => FixOutput::Single(b"_i7_"), - 8 => FixOutput::Single(b"_i8_"), - 9 => FixOutput::Single(b"_i9_"), - 10 => FixOutput::Single(b"_i10_"), - 11 => FixOutput::Single(b"_i11_"), - 12 => FixOutput::Single(b"_i12_"), - 13 => FixOutput::Single(b"_i13_"), - 14 => FixOutput::Single(b"_i14_"), - 15 => FixOutput::Single(b"_i15_"), - 16 => FixOutput::Single(b"_i16_"), - 17 => FixOutput::Single(b"_i17_"), - 18 => FixOutput::Single(b"_i18_"), - 19 => FixOutput::Single(b"_i19_"), - 20 => FixOutput::Single(b"_i20_"), - 21 => FixOutput::Single(b"_i21_"), - 22 => FixOutput::Single(b"_i22_"), - 23 => FixOutput::Single(b"_i23_"), - 24 => FixOutput::Single(b"_i24_"), - 25 => FixOutput::Single(b"_i25_"), - 26 => FixOutput::Single(b"_i26_"), - 27 => FixOutput::Single(b"_i27_"), - 28 => FixOutput::Single(b"_i28_"), - 29 => FixOutput::Single(b"_i29_"), - 30 => FixOutput::Single(b"_i30_"), - 31 => FixOutput::Single(b"_i31_"), + 1 => FixOutput::Single(b"+i1+"), + 2 => FixOutput::Single(b"+i2+"), + 3 => FixOutput::Single(b"+i3+"), + 4 => FixOutput::Single(b"+i4+"), + 5 => FixOutput::Single(b"+i5+"), + 6 => FixOutput::Single(b"+i6+"), + 7 => FixOutput::Single(b"+i7+"), + 8 => FixOutput::Single(b"+i8+"), + 9 => FixOutput::Single(b"+i9+"), + 10 => FixOutput::Single(b"+i10+"), + 11 => FixOutput::Single(b"+i11+"), + 12 => FixOutput::Single(b"+i12+"), + 13 => FixOutput::Single(b"+i13+"), + 14 => FixOutput::Single(b"+i14+"), + 15 => FixOutput::Single(b"+i15+"), + 16 => FixOutput::Single(b"+i16+"), + 17 => FixOutput::Single(b"+i17+"), + 18 => FixOutput::Single(b"+i18+"), + 19 => FixOutput::Single(b"+i19+"), + 20 => FixOutput::Single(b"+i20+"), + 21 => FixOutput::Single(b"+i21+"), + 22 => FixOutput::Single(b"+i22+"), + 23 => FixOutput::Single(b"+i23+"), + 24 => FixOutput::Single(b"+i24+"), + 25 => FixOutput::Single(b"+i25+"), + 26 => FixOutput::Single(b"+i26+"), + 27 => FixOutput::Single(b"+i27+"), + 28 => FixOutput::Single(b"+i28+"), + 29 => FixOutput::Single(b"+i29+"), + 30 => FixOutput::Single(b"+i30+"), + 31 => FixOutput::Single(b"+i31+"), _ => unreachable!("should be in range 1 - 31"), }, }), b'.' if remainder.len() == 1 => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_d_"), + fix: FixOutput::Single(b"+d+"), }), b' ' if remainder.len() == 1 => Some(FixSolution { problematic_sequence_len: 1, - fix: FixOutput::Single(b"_s_"), + fix: FixOutput::Single(b"+s+"), }), _ => None, } @@ -523,7 +527,7 @@ pub fn sanitize_path_component(component: &str) -> Cow { use std::ffi::OsStr; -pub fn unsanitize_path_component(component: &OsStr) -> Cow { +pub fn unsanitize_path_component(component: &OsStr) -> Option> { #[derive(Copy, Clone)] enum FixState { Underscore, @@ -538,7 +542,7 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { let part = component.to_string_lossy(); if part.len() == 0 { - return part; + return Some(part); } let state = { @@ -548,7 +552,7 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { let mut position = 0; loop { - if bytes[position] == b'_' { + if bytes[position] == b'+' { let mut ok_data = Vec::with_capacity(bytes_len); ok_data.extend(bytes.iter().take(position)); break UnsanitizeState::Fixed { bytes: ok_data, state: FixState::Underscore, position: position + 1 }; @@ -561,7 +565,7 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { }; match state { - UnsanitizeState::ReuseSameString => return part, + UnsanitizeState::ReuseSameString => return Some(part), UnsanitizeState::Fixed { mut bytes, mut state, mut position } => { let src_bytes = part.as_ref().as_bytes(); let src_bytes_len = src_bytes.len(); @@ -572,21 +576,20 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { let remaining_len = src_bytes_len - position; if remaining_len == 0 { - bytes.push(b'_'); // invalid file - break; + return None; } let next_char = src_bytes[position]; - if remaining_len > 0 && next_char == b'_' { - bytes.push(b'_'); + if remaining_len > 0 && next_char == b'+' { + bytes.push(b'+'); position += 1; state = FixState::Scan; - } else if remaining_len > 4 && next_char == b'r' && src_bytes[position + 4] == b'_' { + } else if remaining_len > 4 && next_char == b'r' && src_bytes[position + 4] == b'+' { bytes.extend_from_slice(&src_bytes[position + 1..position + 4]); position += 5; state = FixState::Scan; - } else if remaining_len > 5 && next_char == b'r' && src_bytes[position + 5] == b'_' { + } else if remaining_len > 5 && next_char == b'r' && src_bytes[position + 5] == b'+' { bytes.extend_from_slice(&src_bytes[position + 1..position + 5]); position += 6; state = FixState::Scan; @@ -595,46 +598,42 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { let next_char3 = src_bytes[position + 2]; match (next_char2, next_char3) { - (b'1', b'_') => bytes.push(1), - (b'2', b'_') => bytes.push(2), - (b'3', b'_') => bytes.push(3), - (b'4', b'_') => bytes.push(4), - (b'5', b'_') => bytes.push(5), - (b'6', b'_') => bytes.push(6), - (b'7', b'_') => bytes.push(7), - (b'8', b'_') => bytes.push(8), - (b'9', b'_') => bytes.push(9), + (b'1', b'+') => bytes.push(1), + (b'2', b'+') => bytes.push(2), + (b'3', b'+') => bytes.push(3), + (b'4', b'+') => bytes.push(4), + (b'5', b'+') => bytes.push(5), + (b'6', b'+') => bytes.push(6), + (b'7', b'+') => bytes.push(7), + (b'8', b'+') => bytes.push(8), + (b'9', b'+') => bytes.push(9), _ => if remaining_len > 3 { let next_char4 = src_bytes[position + 3]; match (next_char2, next_char3, next_char4) { - (b'1', b'0', b'_') => bytes.push(10), - (b'1', b'1', b'_') => bytes.push(11), - (b'1', b'2', b'_') => bytes.push(12), - (b'1', b'3', b'_') => bytes.push(13), - (b'1', b'4', b'_') => bytes.push(14), - (b'1', b'5', b'_') => bytes.push(15), - (b'1', b'6', b'_') => bytes.push(16), - (b'1', b'7', b'_') => bytes.push(17), - (b'1', b'8', b'_') => bytes.push(18), - (b'1', b'9', b'_') => bytes.push(19), - (b'2', b'0', b'_') => bytes.push(20), - (b'2', b'1', b'_') => bytes.push(21), - (b'2', b'2', b'_') => bytes.push(22), - (b'2', b'3', b'_') => bytes.push(23), - (b'2', b'4', b'_') => bytes.push(24), - (b'2', b'5', b'_') => bytes.push(25), - (b'2', b'6', b'_') => bytes.push(26), - (b'2', b'7', b'_') => bytes.push(27), - (b'2', b'8', b'_') => bytes.push(28), - (b'2', b'9', b'_') => bytes.push(29), - (b'3', b'0', b'_') => bytes.push(30), - (b'3', b'1', b'_') => bytes.push(31), - _ => { - bytes.push(b'_'); - bytes.extend_from_slice(&src_bytes[position..]); // invalid file - break; - }, + (b'1', b'0', b'+') => bytes.push(10), + (b'1', b'1', b'+') => bytes.push(11), + (b'1', b'2', b'+') => bytes.push(12), + (b'1', b'3', b'+') => bytes.push(13), + (b'1', b'4', b'+') => bytes.push(14), + (b'1', b'5', b'+') => bytes.push(15), + (b'1', b'6', b'+') => bytes.push(16), + (b'1', b'7', b'+') => bytes.push(17), + (b'1', b'8', b'+') => bytes.push(18), + (b'1', b'9', b'+') => bytes.push(19), + (b'2', b'0', b'+') => bytes.push(20), + (b'2', b'1', b'+') => bytes.push(21), + (b'2', b'2', b'+') => bytes.push(22), + (b'2', b'3', b'+') => bytes.push(23), + (b'2', b'4', b'+') => bytes.push(24), + (b'2', b'5', b'+') => bytes.push(25), + (b'2', b'6', b'+') => bytes.push(26), + (b'2', b'7', b'+') => bytes.push(27), + (b'2', b'8', b'+') => bytes.push(28), + (b'2', b'9', b'+') => bytes.push(29), + (b'3', b'0', b'+') => bytes.push(30), + (b'3', b'1', b'+') => bytes.push(31), + _ => return None, } position += 1; @@ -647,25 +646,22 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { let next_char2 = src_bytes[position + 1]; match (next_char, next_char2) { - (b'd', b'_') => bytes.push(b'.'), - (b'b', b'_') => bytes.push(b'\\'), - (b'c', b'_') => bytes.push(b':'), - (b'q', b'_') => bytes.push(b'\"'), - (b'p', b'_') => bytes.push(b'|'), - (b'm', b'_') => bytes.push(b'?'), - (b'a', b'_') => bytes.push(b'*'), - (b's', b'_') => bytes.push(b' '), + (b'd', b'+') => bytes.push(b'.'), + (b'b', b'+') => bytes.push(b'\\'), + (b'c', b'+') => bytes.push(b':'), + (b'q', b'+') => bytes.push(b'\"'), + (b'p', b'+') => bytes.push(b'|'), + (b'm', b'+') => bytes.push(b'?'), + (b'a', b'+') => bytes.push(b'*'), + (b's', b'+') => bytes.push(b' '), _ => if remaining_len > 2 { let next_char3 = src_bytes[position + 2]; match (next_char, next_char2, next_char3) { - (b'l', b't', b'_') => bytes.push(b'<'), - (b'g', b't', b'_') => bytes.push(b'>'), - (b's', b'l', b'_') => bytes.push(b'/'), - _ => { - bytes.push(b'_'); - bytes.extend_from_slice(&src_bytes[position..]); // invalid file - }, + (b'l', b't', b'+') => bytes.push(b'<'), + (b'g', b't', b'+') => bytes.push(b'>'), + (b's', b'l', b'+') => bytes.push(b'/'), + _ => return None, } position += 1; @@ -674,11 +670,7 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { position += 2; state = FixState::Scan; - } else { - bytes.push(b'_'); - bytes.extend_from_slice(&src_bytes[position..]); // invalid file - break; - } + } else { return None } }, FixState::Scan => { if position == src_bytes_len { @@ -687,7 +679,7 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { let next_char = src_bytes[position]; - if next_char == b'_' { + if next_char == b'+' { state = FixState::Underscore; } else { bytes.push(next_char); @@ -698,7 +690,7 @@ pub fn unsanitize_path_component(component: &OsStr) -> Cow { } } - Cow::from(String::from_utf8(bytes).expect("bytes already undergone lossy conversion to utf8")) + Some(Cow::from(String::from_utf8(bytes).expect("bytes already undergone lossy conversion to utf8"))) } } } @@ -718,54 +710,57 @@ mod normalize_path_tests { // this is not valid path, but not a concern of this function check("", ""); - // _ is the start of the escape sequence, so this escapes the escape sequence - check("__", "_"); - check("____", "__"); + // + is the start of the escape sequence, so this escapes the escape sequence + check("++", "+"); + check("++++", "++"); // kill path traversing - check("_d_", "."); - check("._d_", ".."); + check("+d+", "."); + check(".+d+", ".."); // simple unsanitized names check("hello world", "hello world"); check("hello-world", "hello-world"); - check("hello+world", "hello+world"); + check("hello_world", "hello_world"); + + // underscore handling + assert_eq!("quad+.vert", unsanitize_path_component(&OsString::from("quad+.vert")).as_ref()); } #[test] fn test_windows() { - check("_b_", "\\"); - check("_b__b_", "\\\\"); + check("+b+", "\\"); + check("+b++b+", "\\\\"); - check("_lt_", "<"); - check("_lt__lt_", "<<"); + check("+lt+", "<"); + check("+lt++lt+", "<<"); - check("_gt_", ">"); - check("_gt__gt_", ">>"); + check("+gt+", ">"); + check("+gt++gt+", ">>"); - check("_c_", ":"); - check("_c__c_", "::"); + check("+c+", ":"); + check("+c++c+", "::"); - check("_q_", "\""); - check("_q__q_", "\"\""); + check("+q+", "\""); + check("+q++q+", "\"\""); - check("_sl_", "/"); - check("_sl__sl_", "//"); + check("+sl+", "/"); + check("+sl++sl+", "//"); - check("_p_", "|"); - check("_p__p_", "||"); + check("+p+", "|"); + check("+p++p+", "||"); - check("_m_", "?"); - check("_m__m_", "??"); + check("+m+", "?"); + check("+m++m+", "??"); - check("_a_", "*"); - check("_a__a_", "**"); + check("+a+", "*"); + check("+a++a+", "**"); for i in 1u8..=31 { let mut output = String::new(); - output.push_str("_i"); + output.push_str("+i"); output.push_str(&format!("{}", i)); - output.push('_'); + output.push('+'); let mut input = String::new(); input.push(i as char); @@ -773,12 +768,12 @@ mod normalize_path_tests { check(&output, &input); let mut output = String::new(); - output.push_str("_i"); + output.push_str("+i"); output.push_str(&format!("{}", i)); - output.push('_'); - output.push_str("_i"); + output.push('+'); + output.push_str("+i"); output.push_str(&format!("{}", i)); - output.push('_'); + output.push('+'); let mut input = String::new(); input.push(i as char); @@ -787,12 +782,12 @@ mod normalize_path_tests { check(&output, &input); } - check("hello_s_", "hello "); - check("hello_d_", "hello."); - check("hello _s_", "hello "); - check("hello._d_", "hello.."); - check(" hello _s_", " hello "); - check(".hello._d_", ".hello.."); + check("hello+s+", "hello "); + check("hello+d+", "hello."); + check("hello +s+", "hello "); + check("hello.+d+", "hello.."); + check(" hello +s+", " hello "); + check(".hello.+d+", ".hello.."); for reserved_name in &[ "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", @@ -800,35 +795,35 @@ mod normalize_path_tests { ] { let seq = format!("{}", &reserved_name); check( - &format!("_r{}_", seq), + &format!("+r{}+", seq), &seq ); let seq = format!("{}", reserved_name.to_lowercase()); check( - &format!("_r{}_", seq), + &format!("+r{}+", seq), &seq ); let seq = format!("{}", title_case(reserved_name)); check( - &format!("_r{}_", seq), + &format!("+r{}+", seq), &seq ); let seq = format!("{}", &reserved_name); let input = format!("{}.txt", &seq); - let output = format!("_r{}_.txt", &seq); + let output = format!("+r{}+.txt", &seq); check(&output, &input); let seq = format!("{}", &reserved_name); let input = format!("{}.", &seq); - let output = format!("_r{}__d_", &seq); + let output = format!("+r{}++d+", &seq); check(&output, &input); let seq = format!("{}", &reserved_name); let input = format!("{}.a", &seq); - let output = format!("_r{}_.a", &seq); + let output = format!("+r{}+.a", &seq); check(&output, &input); let seq = format!("{}", &reserved_name); diff --git a/lib/resources/src/shared/mod.rs b/lib/resources/src/shared/mod.rs index 088d065..4dc826d 100644 --- a/lib/resources/src/shared/mod.rs +++ b/lib/resources/src/shared/mod.rs @@ -1,8 +1,7 @@ -use crate::backend::{Backend, BackendSyncPoint}; +use crate::backend::{Backend, BackendSyncPoint, Modification}; use crate::path::{ResourcePath, ResourcePathBuf}; use slab::Slab; -use std::collections::BTreeMap; -use std::collections::HashMap; +use std::collections::{HashMap, BTreeMap, VecDeque}; use std::hash::BuildHasherDefault; use std::time::Instant; use twox_hash::XxHash; @@ -38,7 +37,7 @@ pub struct UserKey { user_id: usize, } -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Copy, Clone)] pub enum InternalSyncPoint { Backend { backend_hash: u64, @@ -54,6 +53,8 @@ pub struct SharedResources { path_resource_ids: HashMap>, backends: BTreeMap>, outdated_at: Option, + + modification_queue: VecDeque, } fn backend_hash(id: &str) -> u64 { @@ -70,6 +71,8 @@ impl SharedResources { path_resource_ids: HashMap::default(), backends: BTreeMap::new(), outdated_at: None, + + modification_queue: VecDeque::new(), } } @@ -77,15 +80,80 @@ impl SharedResources { if let Some(instant) = self.outdated_at { return Some(InternalSyncPoint::Everything { time: instant }); } + + let mut new_change_point = None; + let mut mod_queue = ::std::mem::replace(&mut self.modification_queue, VecDeque::new()); + for (key, backend) in self.backends.iter_mut() { - if let Some(sync_point) = backend.new_changes() { - return Some(InternalSyncPoint::Backend { + mod_queue.clear(); + if let Some(sync_point) = backend.new_changes(&mut mod_queue) { + new_change_point = Some(InternalSyncPoint::Backend { backend_hash: backend_hash(&key.id), sync_point, }); + + break; } } - None + + if let Some(InternalSyncPoint::Backend { backend_hash: bh, sync_point }) = new_change_point { + let mut some_resource_is_modified = false; + + while let Some(modification) = mod_queue.pop_front() { + match modification { + Modification::Create(p) => { + if let Some(resource_id) = self.path_resource_ids.get(&p) { + if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) { + meta.everyone_should_reload(sync_point.instant); + some_resource_is_modified = true; + } + } + }, + Modification::Write(p) => { + if let Some(resource_id) = self.path_resource_ids.get(&p) { + if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) { + meta.everyone_should_reload(sync_point.instant); + some_resource_is_modified = true; + } + } + }, + Modification::Remove(p) => { + if let Some(resource_id) = self.path_resource_ids.get(&p) { + if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) { + meta.everyone_should_reload(sync_point.instant); + some_resource_is_modified = true; + } + } + }, + Modification::Rename { from, to } => { + if let (Some(resource_id), Some(resource_id_to)) = (self.path_resource_ids.get(&from), self.path_resource_ids.get(&to)) { + if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) { + meta.everyone_should_reload(sync_point.instant); + some_resource_is_modified = true; + } + if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id_to) { + meta.everyone_should_reload(sync_point.instant); + some_resource_is_modified = true; + } + } + }, + } + } + + if let false = some_resource_is_modified { + for (key, backend) in self.backends.iter_mut() { + if backend_hash(&key.id) == bh { + backend.notify_changes_synced(sync_point); + break; + } + } + new_change_point = None; + } + } + + ::std::mem::replace(&mut self.modification_queue, mod_queue); + + new_change_point } pub fn notify_changes_synced(&mut self, sync_point: InternalSyncPoint) { @@ -100,6 +168,7 @@ impl SharedResources { for (key, backend) in self.backends.iter_mut() { if backend_hash(&key.id) == bh { backend.notify_changes_synced(sp); + break; } } }