WatchService звучит как захватывающая идея... к сожалению, это похоже на низкий уровень, как указано в уроке /api plus, не очень вписывается в модель событий Swing (или мне не хватает чего-то очевидного, а не -размерная вероятность
Принимая код из примера WatchDir в учебнике (просто для обработки только одного каталога), я в основном закончил
- расширить SwingWorker
- выполните регистрацию в конструкторе
- положить бесконечный цикл, ожидающий нажатия клавиши doInBackground
- публикуйте каждый WatchEvent при извлечении через key.pollEvents()
-
обрабатывать куски, запустив свойствоChangeEvents с удаленными/созданными файлами как newValue
@SuppressWarnings("unchecked") public class FileWorker extends SwingWorker<Void, WatchEvent<Path>> { public static final String DELETED = "deletedFile"; public static final String CREATED = "createdFile"; private Path directory; private WatchService watcher; public FileWorker(File file) throws IOException { directory = file.toPath(); watcher = FileSystems.getDefault().newWatchService(); directory.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); } @Override protected Void doInBackground() throws Exception { for (;;) { // wait for key to be signalled WatchKey key; try { key = watcher.take(); } catch (InterruptedException x) { return null; } for (WatchEvent<?> event : key.pollEvents()) { WatchEvent.Kind<?> kind = event.kind(); // TBD - provide example of how OVERFLOW event is handled if (kind == OVERFLOW) { continue; } publish((WatchEvent<Path>) event); } // reset key return if directory no longer accessible boolean valid = key.reset(); if (!valid) { break; } } return null; } @Override protected void process(List<WatchEvent<Path>> chunks) { super.process(chunks); for (WatchEvent<Path> event : chunks) { WatchEvent.Kind<?> kind = event.kind(); Path name = event.context(); Path child = directory.resolve(name); File file = child.toFile(); if (StandardWatchEventKinds.ENTRY_DELETE == kind) { firePropertyChange(DELETED, null, file); } else if (StandardWatchEventKinds.ENTRY_CREATE == kind) { firePropertyChange(CREATED, null, file); } } } }
Основная идея состоит в том, чтобы сделать код, блаженно не осведомленным о скользких деталях: он прослушивает изменения свойств и f.i. обновляет произвольные модели по мере необходимости:
String testDir = "D:\\scans\\library";
File directory = new File(testDir);
final DefaultListModel<File> model = new DefaultListModel<File>();
for (File file : directory.listFiles()) {
model.addElement(file);
}
final FileWorker worker = new FileWorker(directory);
PropertyChangeListener l = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (FileWorker.DELETED == evt.getPropertyName()) {
model.removeElement(evt.getNewValue());
} else if (FileWorker.CREATED == evt.getPropertyName()) {
model.addElement((File) evt.getNewValue());
}
}
};
worker.addPropertyChangeListener(l);
JXList list = new JXList(model);
Кажется, работает, но я чувствую себя некомфортно
- Выйдя из себя как агностик потока, я: все примеры, которые я видел до сих пор, блокируют ожидающий поток, используя watcher.take(). Почему они это делают? Ожидалось бы, по крайней мере, некоторое использование watcher.poll() и немного спящего.
- Метод публикации SwingWorker не совсем подходит: на данный момент это нормально, поскольку я смотрю только один каталог (не хотел галопп слишком далеко в неправильном направлении:) При попытке просмотра нескольких каталогов ( как в оригинальном примере WatchDir) есть несколько ключей и WatchEvent относительно одного из них. Чтобы разрешить путь, мне понадобится как событие, так и каталог [A], который просматривает ключ, но могут передавать только один. Скорее всего, неправильное распределение логики, хотя
[A] Отредактировано (вызвано комментарием @trashgods) - это фактически не тот ключ, который мне нужно передать вместе с событием, это каталог, в котором он сообщает об изменениях. Изменен вопрос соответственно
FYI, этот вопрос перекрестно помещен в OTN swing forum
Добавление
Чтение api doc WatchKey:
Если есть несколько потоков, получающих сигнальные ключи от часов следует соблюдать осторожность, чтобы гарантировать, что метод resetвызывается только после обработки событий для объекта.
означает, что события должны
- обрабатывается в том же потоке, который извлекал WatchKey
- не следует трогать после нажатия клавиши reset
Не совсем уверен, но в сочетании с (будущим) требованием рекурсивного просмотра каталогов (более одного) решил следовать совету @Eels, вроде - скоро опубликует код, который я установил на
ИЗМЕНИТЬ просто принял мой собственный ответ - смиренно вернется, что если у кого есть разумные возражения