WatchServiceはエキサイティングなアイデアのように聞こえました...残念ながら、チュートリアル/ apiで警告されているほど低レベルであるように見えますが、Swingイベントモデルに実際には適合しません(または、ゼロではない確率で明らかな何かが欠けています
チュートリアルのWatchDirの例のコード(単一のディレクトリのみを処理するために単純化された)を使用すると、基本的には最終的に
- SwingWorkerを拡張する
- コンストラクターで登録作業を行います
- エンドレスループをdoInBackgroundにキーを待機させます
- key.pollEvents()を介して取得されたときに各WatchEventを公開します
削除/作成されたファイルをnewValueとしてpropertyChangeEventsを起動することにより、チャンクを処理します
@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); } } } }
基本的な考え方は、コードを使用して、ぬるぬるした詳細を幸福に認識しないようにすることです。プロパティの変更をリッスンし、fiは必要に応じて任意のモデルを更新します。
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のpublishメソッドは完全には適合していないようです。今のところ、1つのディレクトリだけを監視しているので、問題ありません(間違った方向にあまり遠くまでギャロップしたくありませんでした:)複数のディレクトリを監視しようとする場合(元のWatchDirの例)いくつかのキーとそれらの1つに関連するWatchEventがあります。パスを解決するには、イベントとキーが監視しているディレクトリ[A]の両方が必要ですが、渡すことができるのは1つだけです。おそらく、ロジックの分散が間違っている可能性がありますが
[A]編集済み(@trashgodsのコメントによってトリガーされます)-実際には、イベントと一緒に渡さなければならないキーではなく、変更を報告しているディレクトリです。それに応じて質問を変更しました
参考までに、この質問はOTNスイングフォーラムにクロスポストされています
補遺
WatchKeyのAPIドキュメントを読む:
ウォッチサービスからシグナルキーを取得するスレッドが複数ある場合は、オブジェクトのイベントが処理された後にのみリセットメソッドが呼び出されるように注意する必要があります。
イベントがすべきであることを意味するようです
- WatchKeyを取得したのと同じスレッドで処理されます
- キーがリセットされた後は触れないでください
完全にはわかりませんが、@ Eelsのアドバイスに従うことを決定したディレクトリ(複数)を再帰的に監視するという(将来の)要件と組み合わせると、私が決めたコードがすぐに投稿されます
EDIT は私自身の答えを受け入れました-誰かが合理的な異議を唱えた場合、それを謙虚に元に戻します