diff --git a/app/press/Compressor.java b/app/press/Compressor.java index 9f5b1a7..64cb30d 100644 --- a/app/press/Compressor.java +++ b/app/press/Compressor.java @@ -278,11 +278,29 @@ protected static VirtualFile getCompressedFile(FileCompressor compressor, } } + // + // We create a temp file to which the output will be written to first, + // and then rename it to the target file name (because compression can + // take a while) + // If the temp file is already being written by another thread, this + // method will block until it is complete and then return null + // + File tmp = getTmpOutputFile(file); + if (tmp == null) { + PressLogger.trace("Compressed file was generated by another thread"); + } else { + writeCompressedFile(compressor, componentFiles, file, tmp); + } + + return file; + } + + private static void writeCompressedFile(FileCompressor compressor, + List componentFiles, VirtualFile file, File tmp) { Writer out = null; try { - // Compress the component files and write the output to a temporary - // file - File tmp = createTempFile(file); + // Compress the component files and write the output to the + // temporary file out = new BufferedWriter(new FileWriter(tmp)); // Add the last modified dates of each component file to the start @@ -300,7 +318,7 @@ protected static VirtualFile getCompressedFile(FileCompressor compressor, .getRealFile().getName(), (timeAfter - timeStart)); // Once the compressed output has been written to the temporary - // file, move it to the cache directory. + // file, rename it to overwrite the original file. String msg = "Output written to temporary file\n%s\n"; msg += "Moving from tmp path to final path:\n%s"; String tmpPath = tmp.getAbsolutePath(); @@ -326,8 +344,46 @@ protected static VirtualFile getCompressedFile(FileCompressor compressor, } } } + } - return file; + private static File getTmpOutputFile(VirtualFile file) { + String origPath = file.getRealFile().getAbsolutePath(); + File tmp = new File(origPath + ".tmp"); + + // If the temp file already exists + if (tmp.exists()) { + long tmpLastModified = tmp.lastModified(); + long now = System.currentTimeMillis(); + + // If the temp file is older than the destination file, or if it is + // older than the allowed compression time, it must be a remnant of + // a previous server crash so we can overwrite it + if (tmpLastModified < file.lastModified()) { + return tmp; + } + if (now - tmpLastModified > PluginConfig.maxCompressionTimeMillis) { + return tmp; + } + + // Otherwise it must be currently being written by another thread, + // so wait for it to finish + while (tmp.exists()) { + if (System.currentTimeMillis() - now > PluginConfig.maxCompressionTimeMillis) { + throw new PressException("Timeout waiting for compressed file to be generated"); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + } + + // Return null to indicate that the file was already generated by + // another thread + return null; + } + + return tmp; } public static List clearCache(String compressedDir, String extension) { @@ -554,20 +610,6 @@ private static VirtualFile getVirtualFile(String filePath) { return VirtualFile.open(Play.getFile(filePath)); } - /** - * Creates a temporary version of a file that will later be copied over the - * original - */ - private static File createTempFile(VirtualFile original) { - String origPath = original.getRealFile().getAbsolutePath(); - File tmpFile = new File(origPath + ".tmp"); - for (int i = 1; tmpFile.exists(); i++) { - tmpFile = new File(origPath + ".tmp"); - } - - return tmpFile; - } - protected static class FileInfo { String fileName; boolean compress; diff --git a/app/press/PluginConfig.java b/app/press/PluginConfig.java index 816782e..dced9e6 100644 --- a/app/press/PluginConfig.java +++ b/app/press/PluginConfig.java @@ -25,6 +25,10 @@ public static class DefaultConfig { // less than a second) public static final String compressionKeyStorageTime = "2mn"; + // The maximum amount of time in milli-seconds allowed for compression + // to occur before a timeout exception is thrown. + public static final int maxCompressionTimeMillis = 60000; + public static class js { // The directory where source javascript files are read from public static final String srcDir = "/public/javascripts/"; @@ -56,6 +60,7 @@ public static class css { public static CachingStrategy cache; public static boolean cacheClearEnabled; public static String compressionKeyStorageTime; + public static int maxCompressionTimeMillis; public static class js { public static String srcDir = DefaultConfig.js.srcDir; @@ -90,6 +95,8 @@ public static void readConfig() { enabled = ConfigHelper.getBoolean("press.enabled", DefaultConfig.enabled); compressionKeyStorageTime = ConfigHelper.getString("press.key.lifetime", DefaultConfig.compressionKeyStorageTime); + maxCompressionTimeMillis = ConfigHelper.getInt("press.compression.maxTimeMillis", + DefaultConfig.maxCompressionTimeMillis); css.srcDir = ConfigHelper.getString("press.css.sourceDir", DefaultConfig.css.srcDir); css.compressedDir = ConfigHelper.getString("press.css.outputDir", diff --git a/documentation/manual/home.textile b/documentation/manual/home.textile index 2af5958..81dbd16 100644 --- a/documentation/manual/home.textile +++ b/documentation/manual/home.textile @@ -148,6 +148,12 @@ When the **#{press.compressed-script}** or **#{press.compressed-stylesheet}** ta **press.key.lifetime=2mn** +h3. __press.compression.maxTimeMillis__ + +The maximum amount of time in milli-seconds that compression is allowed to take before a timeout exception is thrown. +**press.compression.maxTimeMillis=60000** + + h3. __press.js.sourceDir__ The source directory for javascript files, relative to the application root