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

Last change on this file since 13359 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
RevLine 
[7937]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;
[12814]7import java.nio.file.Files;
[7937]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;
[12825]14import java.util.EnumMap;
[7937]15import java.util.HashMap;
16import java.util.Map;
[12825]17import java.util.Objects;
18import java.util.function.Consumer;
[7937]19
[12649]20import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
[12825]21import org.openstreetmap.josm.data.preferences.sources.SourceType;
[7937]22import org.openstreetmap.josm.tools.CheckParameterUtil;
[12620]23import org.openstreetmap.josm.tools.Logging;
[7937]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;
[9205]32 private Thread thread;
[7937]33
[12825]34 private static final Map<SourceType, Consumer<SourceEntry>> loaderMap = new EnumMap<>(SourceType.class);
35 private final Map<Path, SourceEntry> sourceMap = new HashMap<>();
[7937]36
37 /**
38 * Constructs a new {@code FileWatcher}.
39 */
40 public FileWatcher() {
41 try {
42 watcher = FileSystems.getDefault().newWatchService();
[10615]43 thread = new Thread((Runnable) this::processEvents, "File Watcher");
[7937]44 } catch (IOException e) {
[12620]45 Logging.error(e);
[7937]46 }
47 }
48
49 /**
[9205]50 * Starts the File Watcher thread.
51 */
52 public final void start() {
[11589]53 if (thread != null && !thread.isAlive()) {
[9205]54 thread.start();
55 }
56 }
57
58 /**
[12825]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");
[7937]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
[12825]72 File file = new File(src.url);
[7937]73 // Get parent directory as WatchService allows only to monitor directories, not single files
74 File dir = file.getParentFile();
75 if (dir == null) {
[12825]76 throw new IllegalArgumentException("Resource "+src+" does not have a parent directory");
[7937]77 }
[8510]78 synchronized (this) {
[7937]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);
[12825]82 sourceMap.put(file.toPath(), src);
[7937]83 }
84 }
85
86 /**
[12825]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 /**
[7937]98 * Process all events for the key queued to the watcher.
99 */
100 private void processEvents() {
[12620]101 Logging.debug("File watcher thread started");
[7937]102 while (true) {
103
104 // wait for key to be signaled
105 WatchKey key;
106 try {
107 key = watcher.take();
[11620]108 } catch (InterruptedException ex) {
[11535]109 Thread.currentThread().interrupt();
[7937]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")
[8510]122 WatchEvent<Path> ev = (WatchEvent<Path>) event;
[7937]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)
[8510]129 Path fullPath = ((Path) key.watchable()).resolve(filename);
[7937]130
[12814]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) {
[12827]137 Logging.trace(ex);
[12814]138 continue;
139 }
140
[8510]141 synchronized (this) {
[12825]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);
[7937]150 }
[12620]151 } else if (Logging.isDebugEnabled()) {
152 Logging.debug("Received {0} event for unregistered file: {1}", kind.name(), fullPath);
[7937]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.