검색결과 리스트
글
[History]
수정 - 기존 코드에 Event 가 누락되는 경우가 발생하여 코드 로직을 변경하였습니다.
안녕하세요?
이번 글에서는 Folder 를 감시하고 있다가, 파일이나 폴더에 변화가 온 것을 감지하는 방법에 대해서 알아보겠습니다.
간단히 방법을 설명드리자면, 핵심 Windows API 는 ReadDirectoryChanges 를 이용하는 것입니다.
예전에 비슷한 내용으로 http://crystalcube.co.kr/entry/JNI-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-Folder-Monitoring 를 통해서 방법을 설명 드렸습니다.
그런데 이 방법은 Folder 변화만 감지할 뿐, 세부적으로 어떤 파일이 어떻게 변경되었는지는 알 수 없습니다.
이번시간에 소개시켜드리는 방법은, 변경된 파일의 정보까지도 얻어올 수 있습니다.
기본적인 jni 빌드 방법은 위에서 언급한 이전글을 참고하시기 바랍니다.
1. FolderWatcher.java - java 에서 직접적으로 호출하게 될 최종 클래스입니다.
2. IFolderChangeListener - 폴더 변경시 호출되는 콜백을 정의한 인터페이스
3. crystalcube_win32_FolderWatcher.h - javah 를 통해서 생성된 C 의 header 파일
4. FolderWatcher.cpp - header 를 구현한 C 파일
이상으로 블로깅을 마칩니다. :)
수정 - 기존 코드에 Event 가 누락되는 경우가 발생하여 코드 로직을 변경하였습니다.
안녕하세요?
이번 글에서는 Folder 를 감시하고 있다가, 파일이나 폴더에 변화가 온 것을 감지하는 방법에 대해서 알아보겠습니다.
간단히 방법을 설명드리자면, 핵심 Windows API 는 ReadDirectoryChanges 를 이용하는 것입니다.
예전에 비슷한 내용으로 http://crystalcube.co.kr/entry/JNI-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%9C-Folder-Monitoring 를 통해서 방법을 설명 드렸습니다.
그런데 이 방법은 Folder 변화만 감지할 뿐, 세부적으로 어떤 파일이 어떻게 변경되었는지는 알 수 없습니다.
이번시간에 소개시켜드리는 방법은, 변경된 파일의 정보까지도 얻어올 수 있습니다.
기본적인 jni 빌드 방법은 위에서 언급한 이전글을 참고하시기 바랍니다.
1. FolderWatcher.java - java 에서 직접적으로 호출하게 될 최종 클래스입니다.
package crystalcube.win32;
import java.util.ArrayList;
public class FolderWatcher implements Runnable {
// Action(For ReadDirectoryChanges)
public final static int FILE_ACTION_ADDED = 0x00000001;
public final static int FILE_ACTION_REMOVED = 0x00000002;
public final static int FILE_ACTION_MODIFIED = 0x00000003;
public final static int FILE_ACTION_RENAMED_OLD_NAME = 0x00000004;
public final static int FILE_ACTION_RENAMED_NEW_NAME = 0x00000005;
// Filter(For ReadDirctoryChanges)
public final static int FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001;
public final static int FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002;
public final static int FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004;
public final static int FILE_NOTIFY_CHANGE_SIZE = 0x00000008;
public final static int FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010;
public final static int FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x00000020;
public final static int FILE_NOTIFY_CHANGE_CREATION = 0x00000040;
public final static int FILE_NOTIFY_CHANGE_SECURITY = 0x00000100;
// enum
public static enum Action { FILE_ACTION_ADDED, FILE_ACTION_REMOVED, FILE_ACTION_MODIFIED, FILE_ACTION_RENAMED_OLD_NAME, FILE_ACTION_RENAMED_NEW_NAME };
// win32 api
private native ArrayList<Pair> ReadDirectoryChangesW(int handle, boolean watchSubtree, int filter);
private native int CreateFile(String folder);
private native boolean CloseHandle(int handle);
// Filter
private final static int ReadDirectoryChangesFilter =
FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_LAST_ACCESS |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY;
// jni
static { System.loadLibrary("FolderWatcher"); }
// listener
private IFolderChangeListener listener = null;
private boolean isWatching = false;
//private int handle = 0;
private volatile Object lockObj = new Object();
private String folderPath = "";
private boolean watchSubfolder = false;
private int filter;
private int hDirectory = -1;
private Thread watchThread = null;
//============
// 생성자
//============
public FolderWatcher(IFolderChangeListener listener) {
this.listener = listener;
}
private static String appendSlash(String path) {
if(path.endsWith("\\") == false) {
return path + "\\";
}
return path;
}
public boolean start(String folderPath, boolean watchSubfolder, int filter) {
synchronized (this.lockObj) {
// 이미 실행중인 경우
if(isWatching == true) { return true; }
// setting
this.folderPath = FolderWatcher.appendSlash(folderPath);
this.watchSubfolder = watchSubfolder;
this.filter = filter;
// Handle 설정
this.hDirectory = CreateFile(this.folderPath);
if(this.hDirectory == -1) { return false; }
// Thread 실행
this.watchThread = new Thread(this);
this.watchThread.setDaemon(true);
this.watchThread.setName("FolderWatcher");
this.watchThread.start();
return true;
}
}
@Override
public void run(){
this.isWatching = true;
while(Thread.currentThread().isInterrupted() == false && this.isWatching == true) {
ArrayList<Pair> result = ReadDirectoryChangesW(this.hDirectory, this.watchSubfolder, this.filter);
// Stop 체크
if(Thread.currentThread().isInterrupted() == true || this.isWatching == false) {
break;
}
for(Pair obj : result) {
OnReadChanges(obj.getFirst(), obj.getSecond());
}
}
// Handle Close
if(this.hDirectory != -1) {
if(CloseHandle(this.hDirectory) == true) {
this.hDirectory = -1;
}
}
}
public boolean stop() {
synchronized (this.lockObj) {
if(this.isWatching == false) { return true; }
//stopWatch();
this.isWatching = false;
this.watchThread.interrupt();
return true;
}
}
private void OnReadChanges(String fileName, int action) {
if(this.listener != null) {
Action a = Action.FILE_ACTION_ADDED;
switch(action) {
case FILE_ACTION_ADDED: a = Action.FILE_ACTION_ADDED; break;
case FILE_ACTION_REMOVED: a = Action.FILE_ACTION_REMOVED; break;
case FILE_ACTION_MODIFIED: a = Action.FILE_ACTION_MODIFIED; break;
case FILE_ACTION_RENAMED_OLD_NAME: a = Action.FILE_ACTION_RENAMED_OLD_NAME; break;
case FILE_ACTION_RENAMED_NEW_NAME: a = Action.FILE_ACTION_RENAMED_NEW_NAME; break;
}
// folder 와 filename 분리
String path = this.folderPath;
String file = fileName;
String fullPath = path + file;
if(fileName.contains("\\") == true) {
int pos = fullPath.lastIndexOf("\\");
path = fullPath.substring(0, pos+1);
file = fullPath.substring(pos+1, fullPath.length());
}
this.listener.folderChanged(this, path, file, fullPath, a);
}
}
@Override
protected void finalize() throws Throwable {
try {
this.stop();
} catch(Exception e) {}
super.finalize();
}
}
2. IFolderChangeListener - 폴더 변경시 호출되는 콜백을 정의한 인터페이스
package crystalcube.win32;
import crystalcube.win32.FolderWatcher.Action;
public interface IFolderChangeListener {
public void folderChanged(Object sender, String fileName, Action action);
}
3. crystalcube_win32_FolderWatcher.h - javah 를 통해서 생성된 C 의 header 파일
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class crystalcube_win32_FolderWatcher */
#ifndef _Included_crystalcube_win32_FolderWatcher
#define _Included_crystalcube_win32_FolderWatcher
#ifdef __cplusplus
extern "C" {
#endif
#undef crystalcube_win32_FolderWatcher_FILE_ACTION_ADDED
#define crystalcube_win32_FolderWatcher_FILE_ACTION_ADDED 1L
#undef crystalcube_win32_FolderWatcher_FILE_ACTION_REMOVED
#define crystalcube_win32_FolderWatcher_FILE_ACTION_REMOVED 2L
#undef crystalcube_win32_FolderWatcher_FILE_ACTION_MODIFIED
#define crystalcube_win32_FolderWatcher_FILE_ACTION_MODIFIED 3L
#undef crystalcube_win32_FolderWatcher_FILE_ACTION_RENAMED_OLD_NAME
#define crystalcube_win32_FolderWatcher_FILE_ACTION_RENAMED_OLD_NAME 4L
#undef crystalcube_win32_FolderWatcher_FILE_ACTION_RENAMED_NEW_NAME
#define crystalcube_win32_FolderWatcher_FILE_ACTION_RENAMED_NEW_NAME 5L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_FILE_NAME
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_FILE_NAME 1L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_DIR_NAME
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_DIR_NAME 2L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_ATTRIBUTES
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_ATTRIBUTES 4L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_SIZE
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_SIZE 8L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_LAST_WRITE
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_LAST_WRITE 16L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_LAST_ACCESS
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_LAST_ACCESS 32L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_CREATION
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_CREATION 64L
#undef crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_SECURITY
#define crystalcube_win32_FolderWatcher_FILE_NOTIFY_CHANGE_SECURITY 256L
#undef crystalcube_win32_FolderWatcher_ReadDirectoryChangesFilter
#define crystalcube_win32_FolderWatcher_ReadDirectoryChangesFilter 383L
/*
* Class: crystalcube_win32_FolderWatcher
* Method: ReadDirectoryChangesW
* Signature: (IZI)Ljava/util/ArrayList;
*/
JNIEXPORT jobject JNICALL Java_crystalcube_win32_FolderWatcher_ReadDirectoryChangesW
(JNIEnv *, jobject, jint, jboolean, jint);
/*
* Class: crystalcube_win32_FolderWatcher
* Method: CreateFile
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_crystalcube_win32_FolderWatcher_CreateFile
(JNIEnv *, jobject, jstring);
/*
* Class: crystalcube_win32_FolderWatcher
* Method: CloseHandle
* Signature: (I)Z
*/
JNIEXPORT jboolean JNICALL Java_crystalcube_win32_FolderWatcher_CloseHandle
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
4. FolderWatcher.cpp - header 를 구현한 C 파일
#include "crystalcube_win32_FolderWatcher.h"
#include <windows.h>
#include <iostream>
using namespace std;
/*
* Class: crystalcube_win32_FolderWatcher
* Method: CreateFile
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_crystalcube_win32_FolderWatcher_CreateFile
(JNIEnv * env, jobject obj, jstring path)
{
const jchar* new_path = env->GetStringChars(path, NULL);
// Directory Handle 생성
HANDLE hDirectory = CreateFile((LPCWSTR)new_path,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
return (int)hDirectory;
}
/*
* Class: crystalcube_win32_FolderWatcher
* Method: CloseHandle
* Signature: (I)Z
*/
JNIEXPORT jboolean JNICALL Java_crystalcube_win32_FolderWatcher_CloseHandle
(JNIEnv * env, jobject obj, jint hDirectory)
{
return CloseHandle((HANDLE)hDirectory);
}
/*
* Class: crystalcube_win32_FolderWatcher
* Method: ReadDirectoryChangesW
* Signature: (IZI)Ljava/util/ArrayList;
*/
JNIEXPORT jobject JNICALL Java_crystalcube_win32_FolderWatcher_ReadDirectoryChangesW
(JNIEnv * env, jobject obj, jint handle, jboolean watchSubtree, jint filter)
{
HANDLE hDirectory = (HANDLE)handle;
// Create java.util.ArrayList
jclass listClass = env->FindClass("java/util/ArrayList");
jmethodID init = env->GetMethodID(listClass, "<init>", "()V");
jobject pairList = env->NewObject(listClass, init);
//set put Method
jmethodID add = env->GetMethodID(listClass, "add", "(Ljava/lang/Object;)Z");
// Create pcloud.win32.Pair
jclass pairClass = env->FindClass("pcloud/win32/Pair");
jmethodID initPair = env->GetMethodID(pairClass, "<init>", "(Ljava/lang/String;I)V");
//jobject jPair;
// Create java.lang.Integer
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID initInteger = env->GetMethodID(integerClass, "<init>", "(I)V");
CONST DWORD cbBuffer = 32*1024;
BYTE pBuffer[cbBuffer];
DWORD bytesReturned;
//LPVOID lpBuffer;
FILE_NOTIFY_INFORMATION* pfni;
// Directory Handle 체크
if(hDirectory == INVALID_HANDLE_VALUE) { OutputDebugStr(L"[FolderWatcher] INVALID HANDLE."); return pairList; }
// 버퍼 할당 & check
//pBuffer = (PBYTE)malloc(cbBuffer);
if(pBuffer == NULL) { OutputDebugStr(L"[FolderWatcher] Memory alloc fail."); return pairList; }
// 함수 호출
BOOL readResult = ReadDirectoryChangesW(hDirectory, pBuffer, cbBuffer, (BOOL)watchSubtree, filter, &bytesReturned, 0, 0);
if(readResult == FALSE) {
OutputDebugStr(L"[FolderWatcher] ERROR_INVALID_FUNCTION - ReadDirectoryChangesW.");
// release memory
//free(pBuffer);
return pairList;
}
// 변경된 내역 가져옴
pfni = (FILE_NOTIFY_INFORMATION*)pBuffer;
do {
//if(pfni->Action = 0x00000001) {
// WaitForFile(pfni->FileName);
//}
// java 용 String 생성
//OutputDebugStr(L"[FolderWatcher] java 용 String 생성.");
jobject jFilePath = env->NewString((jchar*)pfni->FileName, pfni->FileNameLength/2);
// java용 Pair 생성
//OutputDebugStr(L"[FolderWatcher] java용 Pair 생성.");
jobject jPair = env->NewObject(pairClass, initPair, jFilePath, pfni->Action);
// list 에 추가
//OutputDebugStr(L"[FolderWatcher] list 에 추가.");
env->CallObjectMethod(pairList, add, jPair);
//pfni = (FILE_NOTIFY_INFORMATION*)((PBYTE)pfni + pfni->NextEntryOffset);
if(pfni->NextEntryOffset <= 0) {
//OutputDebugStr(L"[FolderWatcher] pfni->NextEntryOffset <= 0.");
pfni = NULL;
} else {
//OutputDebugStr(L"[FolderWatcher] pfni->NextEntryOffset.");
pfni = (FILE_NOTIFY_INFORMATION*)((PBYTE)pfni + pfni->NextEntryOffset);
}
} while(pfni != NULL);
// release memory
//OutputDebugStr(L"[FolderWatcher] release memory.");
//free(pBuffer);
return pairList;
}
코드가 간단하므로 자세한 설명은 생략하도록 하겠습니다.
그럼 사용방법을 알아보죠~
실제 사용 Example
// Written by sw0826.kim@samsung.com
package crystalcube.win32;
import crystalcube.win32.FolderWatcher.Action;
public class WatcherTestMain implements IFolderChangeListener{
public static void main(String[] args)
{
WatcherTestMain test = new WatcherTestMain();
test.test();
}
public void test() {
int ReadDirectoryChangesFilter =
FolderWatcher.FILE_NOTIFY_CHANGE_FILE_NAME |
FolderWatcher.FILE_NOTIFY_CHANGE_DIR_NAME |
FolderWatcher.FILE_NOTIFY_CHANGE_ATTRIBUTES |
FolderWatcher.FILE_NOTIFY_CHANGE_SIZE |
FolderWatcher.FILE_NOTIFY_CHANGE_LAST_WRITE |
FolderWatcher.FILE_NOTIFY_CHANGE_LAST_ACCESS |
FolderWatcher.FILE_NOTIFY_CHANGE_CREATION |
FolderWatcher.FILE_NOTIFY_CHANGE_SECURITY;
FolderWatcher watcher = new FolderWatcher(this);
watcher.start("D:\\woogi", true, ReadDirectoryChangesFilter);
//
// FolderWatcher watcher2 = new FolderWatcher(this);
// watcher2.start("C:\\", false, FolderWatcher.FILTER_CHANGE_DIR_NAME | FolderWatcher.FILTER_CHANGE_FILE_NAME);
try {
Thread.sleep(3000000);
// watcher.stop();
// Thread.sleep(300000);
// //watcher2.stop();
} catch(Exception e) {
e.printStackTrace();
}
}
@Override
public void folderChanged(Object sender, String fileName, Action action) {
System.out.println("changed: " + fileName + " -- " + action.toString());
}
}
이상으로 블로깅을 마칩니다. :)
'Java' 카테고리의 다른 글
Java로 나만의 프로토콜 만들기 (1) | 2011.06.16 |
---|---|
Runtime.getRuntime().exec() 호출시 종료되지 않는 문제 (6) | 2011.06.01 |
[팁] JNI 의 jstring 을 LPCWSTR(wchar_t)로 변환하기 (0) | 2011.05.25 |
JNI 를 사용한 Folder Monitoring (1) | 2011.05.24 |
RECENT COMMENT