WatchService и SwingWorker: как это правильно сделать?

WatchService звучал как захватывающая идея ... к сожалению, он кажется таким же низкоуровневым, как предупреждалось в учебнике/api плюс не очень вписывается в модель событий Swing (или я упускаю что-то очевидное, вероятность не нулевая

Взяв код из примера WatchDir в учебнике (просто переработанный для обработки только одного каталога), Я в основном закончил тем, что

  • расширил SwingWorker
  • сделал регистрационные вещи в конструкторе
  • поместил бесконечный цикл ожидания ключа в doInBackground
  • опубликовал каждое WatchEvent, когда оно было получено через key. pollEvents()
  • обрабатываем куски, запуская propertyChangeEvents с удаленными/созданными файлами в качестве newValue

    @SuppressWarnings("unchecked")
    public class FileWorker extends SwingWorker> {
    
     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 (;;) {
     // ожидание сигнала о нажатии клавиши
     WatchKey ключ;
     try {
     key = watcher.take();
     } catch (InterruptedException x) {
     return null;
     }
    
     for (WatchEvent> event : key.pollEvents()) {
     WatchEvent.Kind> kind = event.kind();
     // TBD - пример обработки события OVERFLOW
     if (kind == OVERFLOW) {
     продолжаем;
     }
     publish((WatchEvent) event);
     }
    
     // сброс ключа возвращается, если каталог больше не доступен
     boolean valid = key.reset();
     if (!valid) {
     break;
     }
     }
     return null;
     }
    
     @Override
     protected void process(List> chunks) {
     super.process(chunks);
     for (WatchEvent 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 model = new DefaultListModel();
    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) - на самом деле мне нужно передавать не ключ вместе с событием, а директорию, в которой он сообщает об изменениях. Соответственно изменил вопрос

К вашему сведению, этот вопрос перепостнут на форум OTN swing

Дополнение

Читаем api doc WatchKey:

Если есть несколько потоков, извлекающих сигнальные ключи из службы WatchKey, то следует позаботиться о том, чтобы в них не было ошибок. тогда необходимо позаботиться о том, чтобы метод сброса был вызывался только после обработки событий для объекта.

похоже, подразумевается, что события должны

  1. обрабатываться в том же потоке, который извлек WatchKey
  2. не должны быть тронуты после сброса ключа

Не совсем уверен, но в сочетании с (будущим) требованием рекурсивного просмотра каталогов (более одного) решил последовать совету @Eels, вроде как - скоро выложу код, на котором остановился

EDIT только что принял свой собственный ответ - смиренно верну его, если у кого-то есть разумные возражения

11
задан Roman C 6 March 2016 в 11:45
поделиться