由於最近剛好有實作在UI上面顯示Tree結構,而Tree是依據指定目錄的檔案結構做呈現,當我們在目錄內放進其他檔案後,UI上的Tree結構需要做相對應的即時更新,因此在這邊就利用了Java WatchService來實作,透過WatchService您可以先register您要監控的目錄,同時assign要監控的相對應動作,如新增、刪除等,最後就是做相對應的回饋! 而在這裡呈現的範例捨棄UI改用文字做簡單的顯示,主要專注在WatchService的使用上做說明。
#主程式 - DocumentMonitorSystem.java
先列出主程式的code
#建立WatchService - DocumentWatchService.java
首先透過主程式register一個目錄做監控後,隨即呼叫enableService(); 此時啟用執行緒開始WatchService的服務,當下程式會停在watchService.take();這一行!
另外這裡要特別提到的是ExtendedWatchEventModifier.FILE_TREE這個參數,它可以解決access denied的error。對照下面的檔案結構,當WatchService監控了A目錄,它只會針對這層目錄下的內容發生監控的event,若A目錄下的B目錄有檔案的增減還得另外再register這B目錄,如此類推! 可是當您在Windows欲刪除A目錄時,若B目錄被register,這時候Windows就會跳出access denied的error! 因此在register時指定這個參數,只要註冊A目錄後就可以了,其他下層目錄都會發生新增與刪除的event,也不會跳出access denied的error!
A folder
-- B folder
-- BB file
-- C file
PS. 相關說明請見Stack Overflow,這樣看來它只會發生在Windows且是乎是Java的bug
再來要說明的是register當下
#變數dirMapPath會記錄被註冊目錄對應的List,List紀錄新增檔案的Path
#變數idMapWatchKey會記錄當下ID編號對應的WatchKey,註冊產生的key都是唯一的
+執行unregister -- 選擇3
這邊只要呼叫WatchKey的cancel();即可,以後呼叫isValid()就會是false
+列出Register目錄下的Document -- 選擇1
若要取得當時註冊的Path,可以透過WatchKey呼叫watchable();取得
在此呼叫traceFolder做遞迴印出現在的檔案結構,若有標註*表示透過WatchService監控所增加的檔案
+新增、刪除檔案 in 監控的目錄
新增目錄或檔案,此時List<WatchEvent<?>> lists = key.pollEvents();會回傳一組lists,進入迴圈後在WatchEvent可以得到event's kind,另event's context會得到檔案相對路徑,目錄裡還有檔案的話會依序進行追蹤產生相對應event!
刪除目錄或檔案,這比較單純,它並不會依序追蹤被你刪的內容有哪些,如刪除A目錄,若目錄下還有很多層目錄,這裡WatchEvent也僅有一筆事件,那就是得知A目錄被刪除了而已!
比較特別的是若您是更新檔案名稱或移動檔案,此時會先產生delete event,再來是create event,並沒有單純的modify event。而若您有註冊ENTRY_MODIFY,這時會產生三筆event,對照前面所述,最後會產生modify event,由於當您更新檔案,它所存在的目錄修改日期就會進行更新,因此它並不是針對檔案本身,而是檔案所在的目錄! 從這裡可以看出,我們無從得知這筆delete event與create event是否有關係,我們只單純的知道一個檔案被刪掉,另一個檔案被產生出來!
最後DEMO如下:
#列出目前的Document分佈
#手動刪除aaaa目錄
此時不會產生刪除bbbb.txt檔案的event
#修改檔名from cccc.txt to cccc222.txt
#主程式 - DocumentMonitorSystem.java
先列出主程式的code
public static void main(String[] args) {
// TODO Auto-generated method stub
DocumentWatchService service = DocumentWatchService.getInstance();
Scanner input = new Scanner(System.in);
boolean processing = true;
System.out.println("*****Welcone to Document Monitor System*****");
JFileChooser fc = new JFileChooser("D:\\workspace\\Java program");
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
while(processing) {
System.out.println("Please enter ID:");
System.out.println("(1)List Current Documents (2)Register (3)Unregister (4)Shut down");
int select = input.nextInt();
switch(select) {
case 1:{
service.listCurrentDocuments();
break;
}
case 2:{
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
if(selectedFile.isDirectory()) {
service.register(selectedFile);
service.listCurrentRegisters();
}
}
break;
}
case 3:{
if(service.listCurrentRegisters()) {
System.out.println("Please enter ID to unregister:");
int id = input.nextInt();
service.unregister(id);
}else
System.out.println("A valid registration does not exist.");
break;
}
case 4:{
service.closeService();
input.close();
processing = false;
System.out.println("Shut down...");
break;
}
default:{
System.out.println("Option '"+select+"' dose not exist");
}
}
System.out.println();
}
}
這邊透過menu的選擇讓我們可以動態新增register或採取unregister哪個bind的目錄,同時可以列出目前register目錄的檔案結構做呈現,之中會再區分哪些是透過WatchService所監控到的檔案做標記!#建立WatchService - DocumentWatchService.java
public class DocumentWatchService {
private WatchService watchService = null;
private static DocumentWatchService INSTANCE = null;
private Map<Path, List<Path>> dirMapPath = new HashMap<>();
private Map<Integer, WatchKey> idMapWatchKey = new TreeMap<>();
private boolean enabled = false;
private DocumentWatchService() {
}
public static DocumentWatchService getInstance() {
if(INSTANCE == null) {
INSTANCE = new DocumentWatchService();
}
return INSTANCE;
}
public void register(File dir) {
try {
if(watchService == null) {
watchService = FileSystems.getDefault().newWatchService();
enableService();
}
Path path = Paths.get(dir.getAbsolutePath());
if(!dirMapPath.containsKey(path)) {
WatchKey watchkey = path.register(watchService, new Kind[] {ENTRY_CREATE, ENTRY_DELETE}, ExtendedWatchEventModifier.FILE_TREE);
dirMapPath.put(path, new ArrayList<>());
idMapWatchKey.put(dirMapPath.size(), watchkey);
}else{
System.out.println("The directory you selected has been monitored");
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void unregister(int id) {
WatchKey key = idMapWatchKey.get(id);
if(key != null) {
key.cancel();
}else {
System.out.println("The ID you entered does not exist");
}
}
public boolean listCurrentRegisters() {
boolean exist = false;
System.out.println("==========List Current Registers==========");
for(Integer id : idMapWatchKey.keySet()) {
WatchKey key = idMapWatchKey.get(id);
String status = key.isValid() ? "processing" : "stop";
Path path = (Path) key.watchable();
System.out.println("("+id+")"+path.toAbsolutePath()+"("+status+")");
if(key.isValid()) {
exist = true;
}
}
return exist;
}
public void listCurrentDocuments() {
System.out.println("==========List Current Documents==========");
for(Integer id : idMapWatchKey.keySet()) {
WatchKey key = idMapWatchKey.get(id);
Path path = (Path) key.watchable();
System.out.println("("+id+")"+path.toAbsolutePath());
File dir = path.toFile();
List<Path> mapPaths = dirMapPath.get(path);
traceFolder(dir, mapPaths, "");
}
if(dirMapPath.size() == 0) {
System.out.println("No registration exist");
}
}
private void traceFolder(File f, List paths, String tab) {
if(f.isFile()) {
return;
}else {
for(File file : f.listFiles()) {
String selfAdd = paths.contains(file.toPath()) ? "(*)" : "";
System.out.println(tab + file.getName() + selfAdd);
traceFolder(file, paths, tab + " ");
}
}
}
private void enableService() {
if(!enabled) {
new Thread("Documents Watch Service") {
@SuppressWarnings("unchecked")
@Override
public void run() {
while (true) {
try {
WatchKey key = watchService.take();
Path dir = (Path)key.watchable();
List<WatchEvent<?>> lists = key.pollEvents();
System.out.println();
System.out.println();
System.out.println("***********Document Watch Service***********");
for (WatchEvent<?> watchEvent : lists) {
WatchEvent<Path> watchEventPath = (WatchEvent<Path>) watchEvent;
Kind<Path> kind = (Kind<Path>) watchEvent.kind();
String relativePath = watchEventPath.context().toString();
Path self = dir.resolve(relativePath);
File file = new File(relativePath);
System.out.println("***Kind: " + kind);
System.out.println("***Watch Directory: " + dir);
System.out.println("***RelativePath: " + relativePath);
System.out.println("***Filename: " + file.getName());
boolean hidden = file.isHidden();
if (!hidden) {
List pathList = dirMapPath.get(dir);
if(ENTRY_CREATE == kind) {
pathList.add(self);
}else if(ENTRY_DELETE == kind) {
pathList.remove(self);
}
}
}
System.out.println("********************************************");
key.reset();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClosedWatchServiceException e) {
//e.printStackTrace();
break;
}
}
}
}.start();
}
enabled = true;
}
public void closeService() {
if(watchService != null) {
try {
watchService.close();
watchService = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
+新增register -- 選擇2首先透過主程式register一個目錄做監控後,隨即呼叫enableService(); 此時啟用執行緒開始WatchService的服務,當下程式會停在watchService.take();這一行!
另外這裡要特別提到的是ExtendedWatchEventModifier.FILE_TREE這個參數,它可以解決access denied的error。對照下面的檔案結構,當WatchService監控了A目錄,它只會針對這層目錄下的內容發生監控的event,若A目錄下的B目錄有檔案的增減還得另外再register這B目錄,如此類推! 可是當您在Windows欲刪除A目錄時,若B目錄被register,這時候Windows就會跳出access denied的error! 因此在register時指定這個參數,只要註冊A目錄後就可以了,其他下層目錄都會發生新增與刪除的event,也不會跳出access denied的error!
A folder
-- B folder
-- BB file
-- C file
PS. 相關說明請見Stack Overflow,這樣看來它只會發生在Windows且是乎是Java的bug
再來要說明的是register當下
#變數dirMapPath會記錄被註冊目錄對應的List,List紀錄新增檔案的Path
#變數idMapWatchKey會記錄當下ID編號對應的WatchKey,註冊產生的key都是唯一的
+執行unregister -- 選擇3
這邊只要呼叫WatchKey的cancel();即可,以後呼叫isValid()就會是false
+列出Register目錄下的Document -- 選擇1
若要取得當時註冊的Path,可以透過WatchKey呼叫watchable();取得
在此呼叫traceFolder做遞迴印出現在的檔案結構,若有標註*表示透過WatchService監控所增加的檔案
+新增、刪除檔案 in 監控的目錄
新增目錄或檔案,此時List<WatchEvent<?>> lists = key.pollEvents();會回傳一組lists,進入迴圈後在WatchEvent可以得到event's kind,另event's context會得到檔案相對路徑,目錄裡還有檔案的話會依序進行追蹤產生相對應event!
刪除目錄或檔案,這比較單純,它並不會依序追蹤被你刪的內容有哪些,如刪除A目錄,若目錄下還有很多層目錄,這裡WatchEvent也僅有一筆事件,那就是得知A目錄被刪除了而已!
比較特別的是若您是更新檔案名稱或移動檔案,此時會先產生delete event,再來是create event,並沒有單純的modify event。而若您有註冊ENTRY_MODIFY,這時會產生三筆event,對照前面所述,最後會產生modify event,由於當您更新檔案,它所存在的目錄修改日期就會進行更新,因此它並不是針對檔案本身,而是檔案所在的目錄! 從這裡可以看出,我們無從得知這筆delete event與create event是否有關係,我們只單純的知道一個檔案被刪掉,另一個檔案被產生出來!
最後DEMO如下:
#選擇register的目錄
#列出目前被監控的目錄&手動新增檔案至目錄內#列出目前的Document分佈
#手動刪除aaaa目錄
此時不會產生刪除bbbb.txt檔案的event
#修改檔名from cccc.txt to cccc222.txt
留言
張貼留言