SharadxDutta
SharadxDutta

Reputation: 1128

FileNotFound exception even though file is in the place during watch service in java

I have a watch service running on a folder, when I am trying to modify and existing file using evenKind == Modify (basically pasting a same file without removing the current file) I am getting FileNotFoundException (The process cannot access the file because it is being used by another process.)

if (eventKind == StandardWatchEventKinds.ENTRY_MODIFY) {
                
                String newFileChecksum = null;
                
                if (eventPath.toFile().exists()) {
                    newFileChecksum = getFileChecksum(eventPath.toFile());

                }
                        
                if (fileMapper.containsKey(eventPath)) {
                    String existingFileChecksum = fileMapper.get(eventPath);


                if (!existingFileChecksum.equals(newFileChecksum)) {

                        fileMapper.replace(eventPath, existingFileChecksum, newFileChecksum);
                        
                        log.info("listener.filemodified IN");
                        for (DirectoryListener listener : this.listeners) {
                            
                            listener.fileModified(this, eventPath);
                        }
                        log.info("listener.filemodified OUT");
                    } else {
                        log.info("existing checksum");
                        log.debug(String.format(
                                "Checksum for file [%s] has not changed. Skipping plugin processing.",
                                eventPath.getFileName()));
                    }
         
                }
    
            }

In the code when...getFileChecksum() is called

if (eventPath.toFile().exists()) {

        newFileChecksum = getFileChecksum(eventPath.toFile());

}

So ideally, eventPath.toFile().exists() is TRUE, hence code is going inside if but when getFileChecksum() is called, it goes to method...

private synchronized String getFileChecksum(File file) throws IOException, NoSuchAlgorithmException {
        
        MessageDigest md5Digest = MessageDigest.getInstance("MD5");
        
        FileInputStream fis = null;
        
        if(file.exists()) {
            
            try {
                fis = new FileInputStream(file);
            } catch(Exception e) {
                e.printStackTrace();
            }
        } else {
            log.warn("File not detected.");
        }
         
        byte[] byteArray = new byte[1024];
        
        int bytesCount = 0; 
     
        while ((bytesCount = fis.read(byteArray)) != -1) {
            
            md5Digest.update(byteArray, 0, bytesCount);
        };
 
        fis.close();
        
        byte[] bytes = md5Digest.digest();
       
        StringBuilder stringBuilder = new StringBuilder();
        
        for (int i=0; i< bytes.length ;i++) {
            
            stringBuilder.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        }
         
       return stringBuilder.toString();
    }
}

An exception is coming fis = new FileInputStream(file); even if the file is present in the folder.

FileNotFoundException (The process cannot access the file because it is being used by another process.)

I created a RandomAccessFile and a channel to release any LOCK placed on file, but it is not working. Please suggest what could be happening here.

//UPDATE --> This is the infinite while loop that I have,

WHAT IS HAPPENING? WHEN I PUT A FILE 1 create and 2 update are getting called, suppose, when I am deleting the file, 1 delete 1 modify is being called, and IF I PUT THE SAME FILE BACK TO FOLDER, I GET CREATE but before CREATE is finishing, MODIFY IS BEING called. and create is not running instead modify is running.

I fixed this issue by putting Thread.sleep(500) between

WatchKey wk = watchService.take();
Thread.sleep(500)
        for (WatchEvent<?> event : wk.pollEvents()) {

But I dont think I can justify use of sleep here. Please help

WatchService watchService = null; WatchKey watchKey = null;

        while (!this.canceled && (watchKey == null)) {

           watchService = watchService == null
                        ? FileSystems.getDefault().newWatchService() : watchService;
                watchKey = this.directory.register(watchService,
                        StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE,
                        StandardWatchEventKinds.ENTRY_CREATE);
         }
         while (!this.canceled) {

    try {

        WatchKey wk = watchService.take();
        
        for (WatchEvent<?> event : wk.pollEvents()) {

            Kind<?> eventKind = event.kind();
            
            System.out.println("Event kind : " + eventKind);

            Path dir = (Path)wk.watchable();
            Path eventPath = (Path) event.context();

            Path fullPath = dir.resolve(eventPath);
            fireEvent(eventKind, fullPath);
        }

        wk.reset();

    }

Upvotes: 1

Views: 547

Answers (3)

horizon
horizon

Reputation: 503

I have a better approach, use and a while loop on a var isFileReady like this...

var isFileReady = false;

while(!isFile...) {
}

inside while create a try and catch.

try {

FileInputStream fis = new FileInputStream();

isFileReady = true;
} catch () {
catch exception or print file not ready.
}

This will solve your problem.

Upvotes: 1

DuncG
DuncG

Reputation: 15196

The WatchService is verbose and may report multiple ENTRY_MODIFY events for save operation - even when another application is part way through or doing writes repeatedly. Your code is probably acting on a modify event while the other app is still writing and there may be a second ENTRY_MODIFY on its way.

A safer strategy for using the WatchService is to collate the events you receive and only act on the changes when there is a pause. Something like this will ensure that you block on first event but then poll the watch service with small timeout to see if more changes are present before you act on the previous set:

WatchService ws = ...
HashSet<Path> modified = new HashSet<>();

while(appIsRunning) {
    int countNow = modified.size();
    WatchKey k = countNow == 0 ? ws.take() : ws.poll(1, TimeUnit.MILLISECONDS);
    if (k != null) {
        // Loop through k.pollEvents() and put modify file path into modified set:
        // DO NOT CALL fireEvent HERE, save the path instead:
        ...
        if (eventKind == ENTRY_MODIFY)
            modified.add(filePath);
    }
    // Don't act on changes unless no new events:
    if (countNow == modified.size()) {
        // ACT ON modified list here - the watch service did not report new changes
        for (Path filePath : modified) {
           // call fireEvent HERE:
           fireEvent(filePath);
        }

        // reset the list so next watch call is take() not poll(1)
        modified.clear();
    }
}

If you are also looking out for CREATE and DELETE operations with MODIFY you will have to collate and ignore some of the earlier events because the last recorded event type can take precedence over a previously recorded type. For example, if calling take() then poll(1) until nothing new is reported:

  1. Any DELETE then CREATE => you might want to consider as MODIFY
  2. Any CREATE then MODIFY => you might want to consider as CREATE
  3. Any CREATE or MODIFY then a DELETE => treat as DELETE

Your logic would also want to only act when value of modified.size() + created.size() + deleted.size() gets changed between runs.

Upvotes: 1

zeg
zeg

Reputation: 586

let me guess...

modify event gets called when you modify a file. to modify the file you most likely use a seperate tool like notepad that opens and LOCKS the file.

your watcher gets an event that the file gets modified (right now) but you can not modify it again (which fileinputstream wants to do) since it is locked already.

Upvotes: 0

Related Questions