~binwiederhier/syncany/syncanyteamproject

« back to all changes in this revision

Viewing changes to SyncanyCore/src/name/pachler/nio/file/impl/WindowsPathWatchService.java

  • Committer: Philipp Heckel
  • Date: 2012-11-28 14:08:35 UTC
  • Revision ID: philipp.heckel@gmail.com-20121128140835-a4z3jjg5cr20dv6p
Team Project Code
Look in the Instructions.txt File!

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2008-2011 Uwe Pachler
 
3
 *
 
4
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 
5
 *
 
6
 * This code is free software; you can redistribute it and/or modify it
 
7
 * under the terms of the GNU General Public License version 2 only, as
 
8
 * published by the Free Software Foundation. This particular file is
 
9
 * subject to the "Classpath" exception as provided in the LICENSE file
 
10
 * that accompanied this code.
 
11
 *
 
12
 * This code is distributed in the hope that it will be useful, but WITHOUT
 
13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 
14
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 
15
 * version 2 for more details (a copy is included in the LICENSE file that
 
16
 * accompanied this code).
 
17
 *
 
18
 * You should have received a copy of the GNU General Public License version
 
19
 * 2 along with this work; if not, write to the Free Software Foundation,
 
20
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 
21
 *
 
22
 */
 
23
 
 
24
package name.pachler.nio.file.impl;
 
25
 
 
26
import name.pachler.nio.file.Path;
 
27
import java.io.File;
 
28
import java.io.IOException;
 
29
import java.util.ArrayList;
 
30
import java.util.HashMap;
 
31
import java.util.LinkedList;
 
32
import java.util.List;
 
33
import java.util.Map;
 
34
import java.util.concurrent.ConcurrentLinkedQueue;
 
35
import java.util.concurrent.SynchronousQueue;
 
36
import java.util.concurrent.TimeUnit;
 
37
import java.util.logging.Level;
 
38
import java.util.logging.Logger;
 
39
 
 
40
import name.pachler.nio.file.ClosedWatchServiceException;
 
41
import name.pachler.nio.file.Path;
 
42
import name.pachler.nio.file.StandardWatchEventKind;
 
43
import name.pachler.nio.file.WatchEvent;
 
44
import name.pachler.nio.file.WatchEvent.Kind;
 
45
import name.pachler.nio.file.WatchEvent.Modifier;
 
46
import name.pachler.nio.file.WatchKey;
 
47
import name.pachler.nio.file.ext.ExtendedWatchEventKind;
 
48
import static name.pachler.nio.file.impl.Windows.*;
 
49
 
 
50
/**
 
51
 *
 
52
 * @author count
 
53
 */
 
54
public class WindowsPathWatchService extends PathWatchService {
 
55
        private static final int DEFAULT_BUFFER_SIZE = 8192;    // 8k by default
 
56
 
 
57
        private static native void initNative();
 
58
 
 
59
        private static volatile int threadCounter = 0;
 
60
        private Map<Path, WatchRecord> pathToWatchRecordMap = new HashMap();
 
61
        private List<WindowsPathWatchThread> startedThreads = new LinkedList<WindowsPathWatchThread>();
 
62
        private native void translateFILE_NOTIFY_INFORMATION(WatchRecord watchRecord, ByteBuffer byteBuffer, int bufferSize);
 
63
 
 
64
        /**
 
65
         * This routine is called from the translateFILE_NOTIFY_INFORMATION native
 
66
         * method and will add events to the given WatchRecord. Note that
 
67
         * the action value recognizes a special value, -1, that indicates that
 
68
         * the queue overflowed.
 
69
         * @param wr
 
70
         * @param action
 
71
         * @param fileName
 
72
         */
 
73
        private void FILE_NOTIFY_INFORMATIONhandler(WatchRecord wr, int action, String fileName){
 
74
                int flags = wr.getFlags();
 
75
                WatchEvent.Kind<Path> kind = null;
 
76
                switch(action){
 
77
                        case FILE_ACTION_RENAMED_NEW_NAME:
 
78
                                if(0 != (flags & FLAG_FILTER_ENTRY_RENAME_TO))
 
79
                                {
 
80
                                        kind = ExtendedWatchEventKind.ENTRY_RENAME_TO;
 
81
                                        break;
 
82
                                }
 
83
                                // intentional fallthrough
 
84
                        case FILE_ACTION_ADDED:
 
85
                                if(0 != (flags & FLAG_FILTER_ENTRY_CREATE))
 
86
                                        kind = StandardWatchEventKind.ENTRY_CREATE;
 
87
                                break;
 
88
                        case FILE_ACTION_RENAMED_OLD_NAME:
 
89
                                if(0 != (flags & FLAG_FILTER_ENTRY_RENAME_FROM))
 
90
                                {
 
91
                                        kind = ExtendedWatchEventKind.ENTRY_RENAME_FROM;
 
92
                                        break;
 
93
                                }
 
94
                                // intentional fallthrough
 
95
                        case FILE_ACTION_REMOVED:
 
96
                                if(0 != (flags & FLAG_FILTER_ENTRY_DELETE))
 
97
                                        kind = StandardWatchEventKind.ENTRY_DELETE;
 
98
                                break;
 
99
                        case FILE_ACTION_MODIFIED:
 
100
                                if(0 != (flags & FLAG_FILTER_ENTRY_MODIFY))
 
101
                                        kind = StandardWatchEventKind.ENTRY_MODIFY;
 
102
                                break;
 
103
                }
 
104
 
 
105
                if(kind == null)
 
106
                        return;
 
107
 
 
108
                WatchEvent<?> e = new PathWatchEvent(kind, new PathImpl(new File(fileName)), 1);
 
109
                addWatchEvent(wr, e);
 
110
        }
 
111
 
 
112
        void addWatchEvent(WatchRecord wr, WatchEvent<?> e){
 
113
                if(wr.addWatchEvent(e)){
 
114
                        pendingWatchKeys.add(wr);
 
115
                }
 
116
        }
 
117
 
 
118
        static {
 
119
                NativeLibLoader.loadLibrary("jpathwatch-native");
 
120
                initNative();
 
121
        }
 
122
 
 
123
        public WindowsPathWatchService(){
 
124
        }
 
125
 
 
126
        private void logLastError() {
 
127
                int lastError = GetLastError();
 
128
                String errorMsg = GetLastError_toString(lastError);
 
129
                String message = "Thread '" + Thread.currentThread().getName() + "': error while reading from watch key: " + errorMsg;
 
130
                Logger.getLogger(getClass().getName()).log(Level.WARNING, message);
 
131
        }
 
132
 
 
133
        private void cancelImpl(WatchRecord wr) {
 
134
                if((wr.getFlags() & FLAG_FILTER_KEY_INVALID)!=0)
 
135
                        addWatchEvent(wr, new VoidWatchEvent(ExtendedWatchEventKind.KEY_INVALID));
 
136
                CancelIo(wr.handle);
 
137
                CloseHandle(wr.handle);
 
138
                CloseHandle(wr.overlapped.getEventHandle());
 
139
                wr.invalidate();
 
140
                wr.thread.eventHandleToWatchRecord.remove(wr.overlapped.getEventHandle());
 
141
                pathToWatchRecordMap.remove(wr.getPath());
 
142
        }
 
143
 
 
144
        private static long openDirectoryHandle(PathImpl pathImpl) {
 
145
                String file = pathImpl.getFile().getAbsolutePath();
 
146
                int shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
 
147
                int flagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
 
148
                return CreateFile(file, FILE_LIST_DIRECTORY, shareMode, null, OPEN_EXISTING, flagsAndAttributes, 0);
 
149
        }
 
150
 
 
151
        private static class WatchRecord extends PathWatchKey{
 
152
 
 
153
                WatchRecord(WindowsPathWatchService pws, Path path, int flags, WindowsPathWatchThread thread){
 
154
                        super(pws, path, flags);
 
155
                        this.thread = thread;
 
156
                }
 
157
 
 
158
                public long handle;
 
159
                public OVERLAPPED overlapped;
 
160
                public ByteBuffer buffer;
 
161
                private WindowsPathWatchThread thread = null;
 
162
 
 
163
                private int getNotifyFilter() {
 
164
                        int flags = getFlags();
 
165
                        int notifyFilter = 0;
 
166
                        if(0 != (flags & FLAG_FILTER_ENTRY_CREATE | FLAG_FILTER_ENTRY_DELETE | FLAG_FILTER_ENTRY_RENAME_FROM | FLAG_FILTER_ENTRY_RENAME_TO))
 
167
                                notifyFilter |= FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME;
 
168
                        if(0 != (flags & FLAG_FILTER_ENTRY_MODIFY))
 
169
                                notifyFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE;
 
170
                        return notifyFilter;
 
171
                }
 
172
 
 
173
                private boolean getWatchSubtree(){
 
174
                        int flags = getFlags();
 
175
                        return 0 != (flags & FLAG_WATCH_SUBTREE);
 
176
                }
 
177
 
 
178
        }
 
179
 
 
180
        private static class Command {
 
181
                static final int TYPE_ADD_WATCHRECORD = 1;
 
182
                static final int TYPE_SHUTDOWN = 2;
 
183
                static final int TYPE_REMOVE_WATCHRECORD = 3;
 
184
                static final int TYPE_MODIFY_WATCHRECORD = 4;
 
185
 
 
186
                private int type;
 
187
                private final WatchRecord wr;
 
188
                private int flags;
 
189
 
 
190
                private Command(int type, WatchRecord wr) {
 
191
                        this(type, wr, 0);
 
192
                }
 
193
                private Command(int type, WatchRecord wr, int flags) {
 
194
                        this.type = type;
 
195
                        this.wr = wr;
 
196
                        this.flags = flags;
 
197
                }
 
198
 
 
199
                private int getType() {
 
200
                        return type;
 
201
                }
 
202
 
 
203
                private WatchRecord getWatchRecord() {
 
204
                        return wr;
 
205
                }
 
206
 
 
207
                private int getFlags(){
 
208
                        return flags;
 
209
                }
 
210
 
 
211
        }
 
212
 
 
213
        private CloseableBlockingQueue<PathWatchKey> pendingWatchKeys = new CloseableBlockingQueue<PathWatchKey>();
 
214
 
 
215
        @Override
 
216
        public synchronized PathWatchKey register(Path path, Kind<?>[] kinds, Modifier[] modifiers) throws IOException {
 
217
                PathImpl pathImpl;
 
218
                try {
 
219
                        pathImpl = (PathImpl)path;
 
220
                }catch(ClassCastException ccx){
 
221
                        throw new IllegalArgumentException("the provided Path was not created by the newPath factory method of name.pachler.nio.file.ext.Bootstrapper");
 
222
                }
 
223
                if(!pathImpl.getFile().isDirectory())
 
224
                        throw new IOException("path "+pathImpl.toString()+" is not a directory");       // JDK 1.7 throws NotDirectoryException in this case, but for now we'll just throw an IOException
 
225
 
 
226
                int flags = makeFlagMask(kinds, modifiers);
 
227
 
 
228
                WatchRecord wr = pathToWatchRecordMap.get(path);
 
229
                boolean commandResult;
 
230
                if(wr == null){
 
231
                        // no WatchRecord for this path yet
 
232
                        long directoryHandle = openDirectoryHandle(pathImpl);
 
233
 
 
234
                        if(directoryHandle == INVALID_HANDLE_VALUE){
 
235
                                int errorCode = GetLastError();
 
236
                                throw new IOException(GetLastError_toString(errorCode));
 
237
                        }
 
238
 
 
239
                        WindowsPathWatchThread currentThread = null;
 
240
                        for(WindowsPathWatchThread t : startedThreads){
 
241
                                if(!t.isFull()){
 
242
                                        currentThread = t;
 
243
                                        break;
 
244
                                }
 
245
                        }
 
246
                        if (currentThread == null)
 
247
                        {
 
248
                                currentThread = new WindowsPathWatchThread();
 
249
                                // make this thread a deamon thread so that if users forget to close
 
250
                                // this WatchService (which instructs the thread to terminate), it will
 
251
                                // at least not prevent the JVM from shutting down (the app would
 
252
                                // appear to 'hang' on shutdown otherwise)
 
253
                                currentThread.setDaemon(true);
 
254
                                currentThread.start();
 
255
                                startedThreads.add(currentThread);
 
256
                        }
 
257
 
 
258
                        wr = new WatchRecord(this, pathImpl, flags, currentThread);
 
259
                        wr.buffer = new ByteBuffer(DEFAULT_BUFFER_SIZE);
 
260
                        wr.overlapped = new OVERLAPPED();
 
261
                        wr.overlapped.setEvent(CreateEvent(null, true, false, null));
 
262
                        wr.handle = directoryHandle;
 
263
 
 
264
                        commandResult = currentThread.executeCommand(new Command(Command.TYPE_ADD_WATCHRECORD, wr));
 
265
                } else {
 
266
                        // a WatchRecord already exists for this path, so modify it
 
267
                        commandResult = wr.thread.executeCommand(new Command(Command.TYPE_MODIFY_WATCHRECORD, wr, flags));
 
268
                }
 
269
 
 
270
                // granted, that's not very inspired, but probably enough for something
 
271
                // that should hardly ever happen anyways.
 
272
                if(!commandResult)
 
273
                        throw new IOException("register() failed, details are in log.");
 
274
 
 
275
                return wr;
 
276
        }
 
277
 
 
278
        @Override
 
279
        synchronized void cancel(PathWatchKey pathWatchKey) {
 
280
                WatchRecord wr = (WatchRecord)pathWatchKey;
 
281
                wr.thread.executeCommand(new Command(Command.TYPE_REMOVE_WATCHRECORD, wr));
 
282
                // if the this WatchRecord was the last in the thread, terminate the thread
 
283
                if(wr.thread.isEmpty()){
 
284
                        wr.thread.executeCommand(new Command(Command.TYPE_SHUTDOWN, wr));
 
285
                        startedThreads.remove(wr.thread);
 
286
                }
 
287
        }
 
288
 
 
289
        @Override
 
290
        public synchronized boolean reset(PathWatchKey pathWatchKey) {
 
291
                WatchRecord wr = (WatchRecord)pathWatchKey;
 
292
 
 
293
                if(wr.hasPendingWatchEvents())
 
294
                        pendingWatchKeys.add(wr);
 
295
                return true;
 
296
        }
 
297
 
 
298
        @Override
 
299
        public synchronized void close() throws IOException {
 
300
                for (WindowsPathWatchThread thread : this.startedThreads) {
 
301
                        thread.close();
 
302
                }
 
303
                pendingWatchKeys.close();
 
304
        }
 
305
 
 
306
        @Override
 
307
        public WatchKey poll() throws InterruptedException, ClosedWatchServiceException {
 
308
                return pendingWatchKeys.poll();
 
309
        }
 
310
 
 
311
        @Override
 
312
        public WatchKey poll(long timeout, TimeUnit unit) throws InterruptedException, ClosedWatchServiceException {
 
313
                return pendingWatchKeys.poll(timeout, unit);
 
314
        }
 
315
 
 
316
        @Override
 
317
        public WatchKey take() throws InterruptedException, ClosedWatchServiceException {
 
318
                return pendingWatchKeys.take();
 
319
        }
 
320
 
 
321
 
 
322
 
 
323
        public class WindowsPathWatchThread extends Thread {
 
324
                private SynchronousQueue<Boolean> commandResultQueue = new SynchronousQueue<Boolean>();
 
325
                private ConcurrentLinkedQueue<Command> commandQueue = new ConcurrentLinkedQueue<Command>();
 
326
                private Map<Long, WatchRecord> eventHandleToWatchRecord = new HashMap<Long, WatchRecord>();
 
327
                private long signallingEvent = CreateEvent(null, true, false, null);
 
328
                WindowsPathWatchThread()
 
329
                {
 
330
                        this.setName(getClass().getSimpleName() + '-' + threadCounter++);
 
331
                }
 
332
 
 
333
                // request to execute command in watcher thread and wait until it has
 
334
                // finished processing it.
 
335
                private synchronized boolean executeCommand(Command command) {
 
336
                        commandQueue.add(command);
 
337
 
 
338
                        // tell thread that there is a new command
 
339
                        SetEvent(signallingEvent);
 
340
 
 
341
                        // wait for thread to finish operation if we need syncing
 
342
                        boolean success = false;
 
343
                        boolean result = false;
 
344
                        while(!success){
 
345
                                try {
 
346
                                        result = commandResultQueue.take();
 
347
                                        success = true;
 
348
                                }catch(InterruptedException ix){
 
349
                                }
 
350
                        }
 
351
                        return result;
 
352
                }
 
353
 
 
354
                public boolean isFull(){
 
355
                        return this.eventHandleToWatchRecord.size() >= MAXIMUM_WAIT_OBJECTS-1; //Allow room for the signaling event handle
 
356
                }
 
357
 
 
358
                public boolean isEmpty(){
 
359
                        return this.eventHandleToWatchRecord.isEmpty();
 
360
                }
 
361
 
 
362
                public synchronized void close() throws IOException {
 
363
                        if(this.isAlive() == false || signallingEvent == 0)
 
364
                                throw new ClosedWatchServiceException();
 
365
                        this.executeCommand(new Command(Command.TYPE_SHUTDOWN, null));
 
366
                }
 
367
                public void run(){
 
368
                        for(;;){
 
369
                                final int result;
 
370
 
 
371
                                long[] handles = new long[eventHandleToWatchRecord.size()+1];
 
372
                                handles[0] = signallingEvent;
 
373
                                int handleIndex = 1;
 
374
                                for(long h : eventHandleToWatchRecord.keySet())
 
375
                                        handles[handleIndex++] = h;
 
376
 
 
377
                                try
 
378
                                {
 
379
                                        result = WaitForMultipleObjects(handles, false, INFINITE);
 
380
                                }
 
381
                                catch(RuntimeException e)
 
382
                                {
 
383
                                        String message = "Thread '" + Thread.currentThread().getName() + "': error while calling WaitForMultipleObjects. Exception: " + e;
 
384
                                        Logger.getLogger(getClass().getName()).log(Level.WARNING, message);
 
385
                                        boolean done = false;
 
386
                                        while(!done){
 
387
                                                try {
 
388
                                                        // indicate that we processed the command
 
389
                                                        commandResultQueue.put(false);
 
390
                                                        done = true;
 
391
                                                } catch (InterruptedException ex) {
 
392
                                                        Logger.getLogger(WindowsPathWatchService.class.getName()).log(Level.SEVERE, null, ex);
 
393
                                                }
 
394
                                        }
 
395
                                        throw e;
 
396
                                }
 
397
 
 
398
                                if(result == WAIT_OBJECT_0){
 
399
                                        ResetEvent(signallingEvent);
 
400
 
 
401
                                        Command cmd = commandQueue.poll();
 
402
                                        boolean success = false;
 
403
                                        WatchRecord watchRecord = null;
 
404
                                        assert(cmd != null);
 
405
                                        try {
 
406
                                                switch(cmd.getType()){
 
407
                                                        case Command.TYPE_SHUTDOWN:
 
408
                                                                // using a duplicate of the watch record to prevent
 
409
                                                                // modifying the list while iterating over it
 
410
                                                                for(WatchRecord wr : new ArrayList<WatchRecord>(eventHandleToWatchRecord.values()))
 
411
                                                                        cancelImpl(wr);
 
412
 
 
413
                                                                eventHandleToWatchRecord.clear();
 
414
                                                                CloseHandle(signallingEvent);
 
415
                                                                signallingEvent = 0;
 
416
                                                                success = true;
 
417
                                                                return;
 
418
                                                        case Command.TYPE_ADD_WATCHRECORD:{
 
419
                                                                watchRecord = cmd.getWatchRecord();
 
420
                                                                eventHandleToWatchRecord.put(watchRecord.overlapped.getEventHandle(),watchRecord);
 
421
                                                                pathToWatchRecordMap.put(watchRecord.getPath(), watchRecord);
 
422
 
 
423
                                                                success = ReadDirectoryChanges(watchRecord.handle, watchRecord.buffer, watchRecord.getWatchSubtree(), watchRecord.getNotifyFilter(), null, watchRecord.overlapped, null);
 
424
                                                        }       break;
 
425
                                                        case Command.TYPE_MODIFY_WATCHRECORD:{
 
426
                                                                // on modification, re-issue read operation for watch record
 
427
                                                                watchRecord = cmd.getWatchRecord();
 
428
 
 
429
                                                                int newFlags = cmd.getFlags();
 
430
                                                                int oldFlags = watchRecord.getFlags();
 
431
                                                                boolean watchSubtreeChanged = ((newFlags ^ oldFlags) & FLAG_WATCH_SUBTREE) != 0;
 
432
                                                                watchRecord.setFlags(newFlags);
 
433
 
 
434
                                                                success = CancelIo(watchRecord.handle);
 
435
 
 
436
                                                                if(success && watchSubtreeChanged){
 
437
                                                                        // close and re-open directory watch if recursion options changed
 
438
                                                                        success = CloseHandle(watchRecord.handle);
 
439
                                                                        addWatchEvent(watchRecord, new VoidWatchEvent(StandardWatchEventKind.OVERFLOW));
 
440
                                                                        if(success){
 
441
                                                                                watchRecord.handle = openDirectoryHandle((PathImpl)watchRecord.getPath());
 
442
                                                                                success = watchRecord.handle != INVALID_HANDLE_VALUE;
 
443
                                                                        }
 
444
 
 
445
                                                                }
 
446
 
 
447
                                                                if(success)
 
448
                                                                        success = ReadDirectoryChanges(watchRecord.handle, watchRecord.buffer, watchRecord.getWatchSubtree(), watchRecord.getNotifyFilter(), null, watchRecord.overlapped, null);
 
449
 
 
450
                                                        }       break;
 
451
                                                        case Command.TYPE_REMOVE_WATCHRECORD:{
 
452
                                                                watchRecord = cmd.getWatchRecord();
 
453
                                                                cancelImpl(watchRecord);
 
454
                                                                success = true;
 
455
                                                        }       break;
 
456
                                                        default:
 
457
                                                                throw new RuntimeException("unhandled command type");
 
458
                                                }
 
459
                                        }finally{
 
460
                                                if(!success)
 
461
                                                {
 
462
                                                        logLastError();
 
463
                                                        cancelImpl(watchRecord);
 
464
                                                }
 
465
                                                boolean done = false;
 
466
                                                while(!done){
 
467
                                                        try {
 
468
                                                                // indicate that we processed the command
 
469
                                                                commandResultQueue.put(success);
 
470
                                                                done = true;
 
471
                                                        } catch (InterruptedException ex) {
 
472
                                                                Logger.getLogger(WindowsPathWatchService.class.getName()).log(Level.SEVERE, null, ex);
 
473
                                                        }
 
474
                                                }
 
475
                                        }
 
476
 
 
477
                                } else if (WAIT_OBJECT_0 < result && result < handles.length){
 
478
                                        int index = result - WAIT_OBJECT_0;
 
479
                                        long h = handles[index];
 
480
                                        WatchRecord wr = eventHandleToWatchRecord.get(h);
 
481
                                        ResetEvent(wr.overlapped.getEventHandle());
 
482
 
 
483
                                        boolean success = true;
 
484
 
 
485
                                        int[] numberOfBytesTransferred = {0};
 
486
                                        if(success)
 
487
                                                success = GetOverlappedResult(wr.overlapped.getEventHandle(), wr.overlapped, numberOfBytesTransferred, true);
 
488
 
 
489
                                        if(success)
 
490
                                        {
 
491
                                                if(numberOfBytesTransferred[0] != 0)
 
492
                                                        translateFILE_NOTIFY_INFORMATION(wr, wr.buffer, numberOfBytesTransferred[0]);
 
493
                                                else
 
494
                                                        addWatchEvent(wr, new VoidWatchEvent(StandardWatchEventKind.OVERFLOW)); // handle queue overflow
 
495
                                        }
 
496
 
 
497
                                        // queue another I/O operation
 
498
                                        if(success)
 
499
                                                success = ReadDirectoryChanges(wr.handle, wr.buffer, wr.getWatchSubtree(), wr.getNotifyFilter(), null, wr.overlapped, null);
 
500
 
 
501
                                        // finally, if something went wrong, cancel watch key
 
502
                                        if(!success){
 
503
                                                // ERROR_ACCESS_DENIED indicates that we can no longer
 
504
                                                // read from the watched directory, probably because
 
505
                                                // it has been deleted or unmounted.
 
506
                                                // Other errors might indicate a real problem and are
 
507
                                                // therefore logged.
 
508
                                                if(GetLastError() != ERROR_ACCESS_DENIED)
 
509
                                                        logLastError();
 
510
                                                cancelImpl(wr);
 
511
                                        }
 
512
                                }
 
513
                        }
 
514
                }
 
515
        }
 
516
 
 
517
        @Override
 
518
        public void finalize() throws Throwable{
 
519
                try {
 
520
                        close();
 
521
                }finally{
 
522
                        super.finalize();
 
523
                }
 
524
        }
 
525
}