Skip to content

Commit 68db23b

Browse files
committed
WIP
1 parent b9b91c1 commit 68db23b

File tree

3 files changed

+194
-2
lines changed

3 files changed

+194
-2
lines changed

Cargo.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ authors = [
55
"Félix Saparelli <[email protected]>",
66
"Antti Keränen <[email protected]>",
77
"Jorge Israel Peña <[email protected]>",
8-
"Michael Maurizi <mmaurizi@azavea.com>",
8+
"Michael Maurizi <michael.maurizi@gmail.com>",
99
"Pierre Baillet <[email protected]>",
1010
"ShuYu Wang <[email protected]>",
1111
]
@@ -35,3 +35,9 @@ version = "^0.1"
3535

3636
[target.x86_64-apple-darwin.dependencies.fsevent-sys]
3737
version = "^0.1"
38+
39+
[target.i686-pc-windows-gnu]
40+
dependencies = { winapi = "0.1", kernel32-sys = "0.1" }
41+
42+
[target.x86_64-pc-windows-gnu]
43+
dependencies = { winapi = "0.1", kernel32-sys = "0.1" }

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[macro_use] extern crate log;
22
#[macro_use] extern crate bitflags;
33
#[cfg(target_os="macos")] extern crate fsevent_sys;
4+
#[cfg(target_os="windows")] extern crate winapi;
45
extern crate libc;
56

67
pub use self::op::Op;
@@ -11,10 +12,12 @@ use std::sync::mpsc::Sender;
1112

1213
#[cfg(target_os="macos")] pub use self::fsevent::FsEventWatcher;
1314
#[cfg(target_os="linux")] pub use self::inotify::INotifyWatcher;
15+
#[cfg(target_os="windows")] pub use self::windows::ReadDirectoryChangesWatcher;
1416
pub use self::null::NullWatcher;
1517

1618
#[cfg(target_os="linux")] pub mod inotify;
1719
#[cfg(target_os="macos")] pub mod fsevent;
20+
#[cfg(target_os="windows")] pub mod windows;
1821
pub mod null;
1922

2023
pub mod op {
@@ -53,7 +56,8 @@ pub trait Watcher {
5356

5457
#[cfg(target_os = "linux")] pub type RecommendedWatcher = INotifyWatcher;
5558
#[cfg(target_os = "macos")] pub type RecommendedWatcher = FsEventWatcher;
56-
#[cfg(not(any(target_os = "linux", target_os = "macos")))] pub type RecommendedWatcher = NullWatcher;
59+
#[cfg(target_os = "windows")] pub type RecommendedWatcher = ReadDirectoryChangesWatcher;
60+
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] pub type RecommendedWatcher = NullWatcher;
5761

5862
pub fn new(tx: Sender<Event>) -> Result<RecommendedWatcher, Error> {
5963
Watcher::new(tx)

src/windows.rs

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
extern crate libc;
2+
extern crate kernel32;
3+
extern crate winapi;
4+
5+
use libc::c_void;
6+
7+
use winapi::{HANDLE, INVALID_HANDLE_VALUE, fileapi, winbase, winnt};
8+
use winapi::minwinbase::{OVERLAPPED, LPOVERLAPPED};
9+
use winapi::winerror::ERROR_OPERATION_ABORTED;
10+
11+
use std::collections::HashMap;
12+
use std::mem;
13+
use std::path::{Path, PathBuf};
14+
use std::ptr;
15+
use std::sync::mpsc::{channel, Sender, Receiver};
16+
use std::os::windows::ffi::OsStrExt;
17+
use std::thread;
18+
19+
use super::{Event, Error, op, Op, Watcher};
20+
21+
const BUF_SIZE: u32 = 16384;
22+
23+
enum Action {
24+
Watch(PathBuf),
25+
Unwatch(PathBuf),
26+
Stop
27+
}
28+
29+
// TODO: Does this need repr(c)?
30+
struct ReadDirectoryRequest {
31+
tx: Sender<Event>,
32+
buffer: [u8; BUF_SIZE as usize],
33+
handle: HANDLE
34+
}
35+
36+
struct ReadDirectoryChangesServer {
37+
rx: Receiver<Action>,
38+
tx: Sender<Event>,
39+
watches: HashMap<PathBuf, HANDLE>,
40+
}
41+
42+
impl ReadDirectoryChangesServer {
43+
fn start(event_tx: Sender<Event>) -> Sender<Action> {
44+
let (action_tx, action_rx) = channel();
45+
thread::spawn(move || {
46+
let server = ReadDirectoryChangesServer {
47+
tx: event_tx,
48+
rx: action_rx,
49+
watches: HashMap::new()
50+
};
51+
server.run();
52+
});
53+
action_tx
54+
}
55+
56+
fn run(mut self) {
57+
while let Ok(action) = self.rx.recv() {
58+
match action {
59+
Action::Watch(path) => self.add_watch(path),
60+
Action::Unwatch(path) => self.remove_watch(&path),
61+
Action::Stop => {
62+
63+
}
64+
}
65+
}
66+
}
67+
68+
fn add_watch(&mut self, path: PathBuf) {
69+
let encoded_path: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
70+
let handle;
71+
unsafe {
72+
handle = kernel32::CreateFileW(
73+
encoded_path.as_ptr(),
74+
winnt::FILE_LIST_DIRECTORY,
75+
winnt::FILE_SHARE_READ | winnt::FILE_SHARE_DELETE,
76+
ptr::null_mut(),
77+
fileapi::OPEN_EXISTING,
78+
winbase::FILE_FLAG_BACKUP_SEMANTICS | winbase::FILE_FLAG_OVERLAPPED,
79+
ptr::null_mut());
80+
81+
if handle == INVALID_HANDLE_VALUE {
82+
self.tx.send(Event {
83+
path: None,
84+
// TODO: Call GetLastError for better error info?
85+
op: Err(Error::WatchNotFound)
86+
});
87+
return;
88+
}
89+
}
90+
self.watches.insert(path, handle);
91+
self.read(handle);
92+
}
93+
94+
fn read(&mut self, handle: HANDLE) {
95+
let mut request = Box::new(ReadDirectoryRequest {
96+
tx: self.tx.clone(),
97+
handle: handle,
98+
// TODO: replace with Box<[u8; n]>?
99+
buffer: [0u8; BUF_SIZE as usize]
100+
});
101+
102+
let flags = winnt::FILE_NOTIFY_CHANGE_FILE_NAME
103+
| winnt::FILE_NOTIFY_CHANGE_DIR_NAME
104+
| winnt::FILE_NOTIFY_CHANGE_ATTRIBUTES
105+
| winnt::FILE_NOTIFY_CHANGE_SIZE
106+
| winnt::FILE_NOTIFY_CHANGE_LAST_WRITE
107+
| winnt::FILE_NOTIFY_CHANGE_CREATION
108+
| winnt::FILE_NOTIFY_CHANGE_SECURITY;
109+
110+
let request_p = &mut request as *mut _ as *mut c_void;
111+
112+
unsafe {
113+
let mut overlapped: Box<OVERLAPPED> = Box::new(mem::zeroed());
114+
// When using callback based async requests, we are allowed to use the hEvent member
115+
// for our own purposes
116+
overlapped.hEvent = request_p;
117+
118+
let success = kernel32::ReadDirectoryChangesW(
119+
handle,
120+
request.buffer.as_mut_ptr() as *mut c_void,
121+
BUF_SIZE,
122+
1, // We do want to monitor subdirectories
123+
flags,
124+
&mut 0u32 as *mut u32, // This parameter is not used for async requests
125+
&mut *overlapped as *mut OVERLAPPED,
126+
Some(handle_event));
127+
128+
mem::forget(overlapped);
129+
mem::forget(request);
130+
}
131+
}
132+
133+
fn remove_watch(&mut self, path: &Path) {
134+
if let Some(handle) = self.watches.remove(path) {
135+
unsafe {
136+
// TODO Handle errors?
137+
kernel32::CancelIo(handle);
138+
kernel32::CloseHandle(handle);
139+
}
140+
}
141+
}
142+
}
143+
144+
unsafe extern "system" fn handle_event(errorCode: u32, bytes: u32, overlapped: LPOVERLAPPED) {
145+
if errorCode == ERROR_OPERATION_ABORTED {
146+
return;
147+
}
148+
149+
// TODO: Use Box::from_raw when it is no longer unstable
150+
let overlapped: Box<OVERLAPPED> = mem::transmute(overlapped);
151+
let request: Box<ReadDirectoryRequest> = mem::transmute((*overlapped).hEvent);
152+
}
153+
154+
pub struct ReadDirectoryChangesWatcher {
155+
tx: Sender<Action>
156+
}
157+
158+
impl Watcher for ReadDirectoryChangesWatcher {
159+
fn new(event_tx: Sender<Event>) -> Result<ReadDirectoryChangesWatcher, Error> {
160+
let action_tx = ReadDirectoryChangesServer::start(event_tx);
161+
162+
return Ok(ReadDirectoryChangesWatcher {
163+
tx: action_tx
164+
});
165+
}
166+
167+
fn watch(&mut self, path: &Path) -> Result<(), Error> {
168+
self.tx.send(Action::Watch(path.to_path_buf()));
169+
Ok(())
170+
}
171+
172+
fn unwatch(&mut self, path: &Path) -> Result<(), Error> {
173+
self.tx.send(Action::Unwatch(path.to_path_buf()));
174+
Ok(())
175+
}
176+
}
177+
178+
impl Drop for ReadDirectoryChangesWatcher {
179+
fn drop(&mut self) {
180+
self.tx.send(Action::Stop);
181+
}
182+
}

0 commit comments

Comments
 (0)