source: josm/trunk/src/org/openstreetmap/josm/io/FileWatcher.java@ 13197

Last change on this file since 13197 was 13173, checked in by Don-vip, 6 years ago

see #15310 - remove most of deprecated APIs

  • Property svn:eol-style set to native
File size: 6.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.io.File;
5import java.io.IOException;
6import java.nio.file.FileSystems;
7import java.nio.file.Files;
8import java.nio.file.Path;
9import java.nio.file.StandardWatchEventKinds;
10import java.nio.file.WatchEvent;
11import java.nio.file.WatchEvent.Kind;
12import java.nio.file.WatchKey;
13import java.nio.file.WatchService;
14import java.util.EnumMap;
15import java.util.HashMap;
16import java.util.Map;
17import java.util.Objects;
18import java.util.function.Consumer;
19
20import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
21import org.openstreetmap.josm.data.preferences.sources.SourceType;
22import org.openstreetmap.josm.tools.CheckParameterUtil;
23import org.openstreetmap.josm.tools.Logging;
24
25/**
26 * Background thread that monitors certain files and perform relevant actions when they change.
27 * @since 7185
28 */
29public class FileWatcher {
30
31 private WatchService watcher;
32 private Thread thread;
33
34 private static final Map<SourceType, Consumer<SourceEntry>> loaderMap = new EnumMap<>(SourceType.class);
35 private final Map<Path, SourceEntry> sourceMap = new HashMap<>();
36
37 /**
38 * Constructs a new {@code FileWatcher}.
39 */
40 public FileWatcher() {
41 try {
42 watcher = FileSystems.getDefault().newWatchService();
43 thread = new Thread((Runnable) this::processEvents, "File Watcher");
44 } catch (IOException e) {
45 Logging.error(e);
46 }
47 }
48
49 /**
50 * Starts the File Watcher thread.
51 */
52 public final void start() {
53 if (thread != null && !thread.isAlive()) {
54 thread.start();
55 }
56 }
57
58 /**
59 * Registers a source for local file changes, allowing dynamic reloading.
60 * @param src The source to watch
61 * @throws IllegalArgumentException if {@code rule} is null or if it does not provide a local file
62 * @throws IllegalStateException if the watcher service failed to start
63 * @throws IOException if an I/O error occurs
64 * @since 12825
65 */
66 public void registerSource(SourceEntry src) throws IOException {
67 CheckParameterUtil.ensureParameterNotNull(src, "src");
68 if (watcher == null) {
69 throw new IllegalStateException("File watcher is not available");
70 }
71 // Get local file, as this method is only called for local style sources
72 File file = new File(src.url);
73 // Get parent directory as WatchService allows only to monitor directories, not single files
74 File dir = file.getParentFile();
75 if (dir == null) {
76 throw new IllegalArgumentException("Resource "+src+" does not have a parent directory");
77 }
78 synchronized (this) {
79 // Register directory. Can be called several times for a same directory without problem
80 // (it returns the same key so it should not send events several times)
81 dir.toPath().register(watcher, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE);
82 sourceMap.put(file.toPath(), src);
83 }
84 }
85
86 /**
87 * Registers a source loader, allowing dynamic reloading when an entry changes.
88 * @param type the source type for which the loader operates
89 * @param loader the loader in charge of reloading any source of given type when it changes
90 * @return the previous loader registered for this source type, if any
91 * @since 12825
92 */
93 public static Consumer<SourceEntry> registerLoader(SourceType type, Consumer<SourceEntry> loader) {
94 return loaderMap.put(Objects.requireNonNull(type, "type"), Objects.requireNonNull(loader, "loader"));
95 }
96
97 /**
98 * Process all events for the key queued to the watcher.
99 */
100 private void processEvents() {
101 Logging.debug("File watcher thread started");
102 while (true) {
103
104 // wait for key to be signaled
105 WatchKey key;
106 try {
107 key = watcher.take();
108 } catch (InterruptedException ex) {
109 Thread.currentThread().interrupt();
110 return;
111 }
112
113 for (WatchEvent<?> event: key.pollEvents()) {
114 Kind<?> kind = event.kind();
115
116 if (StandardWatchEventKinds.OVERFLOW.equals(kind)) {
117 continue;
118 }
119
120 // The filename is the context of the event.
121 @SuppressWarnings("unchecked")
122 WatchEvent<Path> ev = (WatchEvent<Path>) event;
123 Path filename = ev.context();
124 if (filename == null) {
125 continue;
126 }
127
128 // Only way to get full path (http://stackoverflow.com/a/7802029/2257172)
129 Path fullPath = ((Path) key.watchable()).resolve(filename);
130
131 try {
132 // Some filesystems fire two events when a file is modified. Skip first event (file is empty)
133 if (Files.size(fullPath) == 0) {
134 continue;
135 }
136 } catch (IOException ex) {
137 Logging.trace(ex);
138 continue;
139 }
140
141 synchronized (this) {
142 SourceEntry source = sourceMap.get(fullPath);
143 if (source != null) {
144 Consumer<SourceEntry> loader = loaderMap.get(source.type);
145 if (loader != null) {
146 Logging.info("Source "+source.getDisplayString()+" has been modified. Reloading it...");
147 loader.accept(source);
148 } else {
149 Logging.warn("Received {0} event for unregistered source type: {1}", kind.name(), source.type);
150 }
151 } else if (Logging.isDebugEnabled()) {
152 Logging.debug("Received {0} event for unregistered file: {1}", kind.name(), fullPath);
153 }
154 }
155 }
156
157 // Reset the key -- this step is critical to receive
158 // further watch events. If the key is no longer valid, the directory
159 // is inaccessible so exit the loop.
160 if (!key.reset()) {
161 break;
162 }
163 }
164 }
165}
Note: See TracBrowser for help on using the repository browser.