I'm working on a Java process that should efficiently (and recursively) copy files/directories from a source location to a destination location.
To do this, I want to:
- create a lock
- if the destination file does not exist, copy it
- else if the destination file is different, copy it
- else they are the same, so do nothing
- release the lock
To check the contents are equal, I was planning on using the Apache Commons IO FileUtils
method contentsEqual(...)
. To do the copying, I was planning to use the Apache Commons IO FileUtils
method copyFile(...)
.
So, the code I came up with is (this is just for files, directories are handled recursively down to this method for files):
private static void checkAndUpdateFile(File src, File dest) throws IOException {
FileOutputStream out = new FileOutputStream(dest);
FileChannel channel = out.getChannel();
FileLock lock = channel.lock();
if (!dest.exists()) {
FileUtils.copyFile(src, out);
} else if (!FileUtils.contentEquals(src, dest)) {
FileUtils.copyFile(src, out);
}
lock.release();
channel.close();
out.close();
}
This does the file locking (great), and copies the files (super).
However, whenever it copies the files, it sets the copied files' last modified timestamp to the time of the copy. This means that subsequent calls to FileUtils.contentEquals(src, dest)
continue to return false
, so the files are re-copied.
What I'd really like is similar to FileUtils.copyFile(src, dest, true)
, which preserves the file timestamps - and does get true from calling FileUtils.contentEquals(src, dest)
. That would require the lock to be on the File
, not the FileOutputStream
, as otherwise the call to FileUtils.copyFile(src, dest, true)
fails and throws an Exception because the file is locked.
Alternatively, I considered doing what the FileUtils.copyFile(src, dest, true)
method does, which is to call dest.setLastModified(src.lastModified())
. However, this would have to be called after the lock is released, which could cause problems if the same process has been executed more than once simultaneously.
I had also considered the ides of putting the lock on the source file, but that doesn't help as I'd have to put it on a FileInputStream
, and I want to pass a File
to FileUtils.copyFile(src, dest)
.
So:
- Is there any easier way to achieve what I am trying to do?
- Is it possible to put a lock on a File, rather than a derivative of File?
- What would be the best way of resolving this?
- i.e. do I just need to write my own approach method for part of this? (probably
copyFile(...)
)
- i.e. do I just need to write my own approach method for part of this? (probably
So... in the end, I went for the approach of writing my own
copy()
andcompare()
methods to use theFileChannel
objects which have been locked. Below is the solution I came up with - although I expect others might be able to suggest improvements. This was informed by looking at the source code for apache.commons.io classesFileUtils
andIOUtils
.