diff --git a/netx/net/sourceforge/jnlp/util/FileUtils.java b/netx/net/sourceforge/jnlp/util/FileUtils.java --- a/netx/net/sourceforge/jnlp/util/FileUtils.java +++ b/netx/net/sourceforge/jnlp/util/FileUtils.java @@ -30,8 +30,12 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.RandomAccessFile; import java.io.Writer; +import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; @@ -587,4 +591,27 @@ public final class FileUtils { public static String loadFileAsString(File f, String encoding) throws IOException { return getContentOfStream(new FileInputStream(f), encoding); } + + public static byte[] getFileMD5Sum(final File file, final String algorithm) throws NoSuchAlgorithmException, + FileNotFoundException, IOException { + final MessageDigest md5; + InputStream is = null; + DigestInputStream dis = null; + try { + md5 = MessageDigest.getInstance(algorithm); + is = new FileInputStream(file); + dis = new DigestInputStream(is, md5); + + md5.update(getContentOfStream(dis).getBytes()); + } finally { + if (is != null) { + is.close(); + } + if (dis != null) { + dis.close(); + } + } + + return md5.digest(); + } } diff --git a/netx/net/sourceforge/jnlp/util/MD5SumWatcher.java b/netx/net/sourceforge/jnlp/util/MD5SumWatcher.java new file mode 100644 --- /dev/null +++ b/netx/net/sourceforge/jnlp/util/MD5SumWatcher.java @@ -0,0 +1,104 @@ +/*Copyright (C) 2014 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.util; + +import static net.sourceforge.jnlp.runtime.Translator.R; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +import net.sourceforge.jnlp.util.logging.OutputController; + +public class MD5SumWatcher { + + private final File watchedFile; + private byte[] md5sum; + + /** + * Create a new MD5SumWatcher instance + * @param watchedFile the file to watch + */ + public MD5SumWatcher(final File watchedFile) { + this.watchedFile = watchedFile; + try { + this.md5sum = getSum(); + } catch (final IOException ioe) { + OutputController.getLogger().log(ioe); + this.md5sum = null; + } + } + + /** + * Get the current MD5 sum of the watched file + * @return a byte array of the MD5 sum + * @throws FileNotFoundException if the watched file does not exist + * @throws IOException if the file cannot be read + */ + public byte[] getSum() throws FileNotFoundException, IOException { + update(); + return md5sum; + } + + /** + * Detect if the file's MD5 has changed and track its new sum if so + * @return if the file's MD5 has changed since the last update + * @throws FileNotFoundException if the watched file does not exist + * @throws IOException if the file cannot be read + */ + public boolean update() throws FileNotFoundException, IOException { + byte[] newSum; + try { + newSum = FileUtils.getFileMD5Sum(watchedFile, "MD5"); + } catch (final NoSuchAlgorithmException e) { + // There definitely should be an MD5 algorithm, but if not, all we can do is fail. + // This really, really is not expected to happen, so rethrow as RuntimeException + // to avoid having to check for NoSuchAlgorithmExceptions all the time + OutputController.getLogger().log(e); + throw new RuntimeException(e); + } + final boolean changed = !Arrays.equals(newSum, md5sum); + md5sum = newSum; + return changed; + } + +} diff --git a/tests/netx/unit/net/sourceforge/jnlp/util/MD5SumWatcherTest.java b/tests/netx/unit/net/sourceforge/jnlp/util/MD5SumWatcherTest.java new file mode 100644 --- /dev/null +++ b/tests/netx/unit/net/sourceforge/jnlp/util/MD5SumWatcherTest.java @@ -0,0 +1,111 @@ +/*Copyright (C) 2014 Red Hat, Inc. + +This file is part of IcedTea. + +IcedTea is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 2. + +IcedTea is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with IcedTea; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. + */ + +package net.sourceforge.jnlp.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Arrays; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class MD5SumWatcherTest { + + private File file; + private MD5SumWatcher watcher; + + @Before + public void createNewFile() throws Exception { + file = File.createTempFile("md5sumwatchertest", "tmp"); + file.deleteOnExit(); + watcher = new MD5SumWatcher(file); + } + + @After + public void deleteTempFile() throws Exception { + file.delete(); + } + + @Test + public void testNonExistentFile() { + file.delete(); + file.mkdirs(); + watcher = new MD5SumWatcher(file); + boolean gotException = false; + try { + watcher.update(); + } catch (final Exception e) { + gotException = true; + assertTrue("Should have received FileNotFoundException", e instanceof FileNotFoundException); + } + assertTrue("Should have received FileNotFoundException", gotException); + } + + @Test + public void testNoFileChangeGivesSameMd5() throws Exception { + byte[] sum = watcher.getSum(); + byte[] sum2 = watcher.getSum(); + assertTrue("MD5 sums should be the same. first: " + Arrays.toString(sum) + ", second: " + Arrays.toString(sum2), + Arrays.equals(sum, sum2)); + } + + @Test + public void testSavingToFileChangesMd5() throws Exception { + byte[] original = watcher.getSum(); + FileUtils.saveFile("some test content\n", file); + byte[] changed = watcher.getSum(); + assertFalse("MD5 sum should have changed, but was constant as " + Arrays.toString(original), + Arrays.equals(original, changed)); + } + + @Test + public void testUnchangedContentUpdate() throws Exception { + assertFalse("update() should return false", watcher.update()); + } + + @Test + public void testChangedContentUpdate() throws Exception { + FileUtils.saveFile("some test content\n", file); + final boolean changed = watcher.update(); + assertTrue("update() should return true", changed); + } + +}