Index: lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/FileUtil.java (revision fb87f900acda9018b36e3e4fbb3ed04d4ac67805) @@ -0,0 +1,70 @@ +/* +Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + +This program 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; either version 2 of the License, or +(at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +http://www.gnu.org/licenses/gpl.txt +*/ +package org.lamsfoundation.lams.util; + +import java.io.File; + +import org.apache.log4j.Logger; + +/** + * General File Utilities + */ +public class FileUtil { + + private static Logger log = Logger.getLogger(FileUtil.class); + + /** + * Deleting a directory using File.delete() only works if + * the directory is empty. This method deletes a directory and + * all of its contained files. + * + * This method is not transactional - if it fails to delete some + * contained files or directories, it will continue deleting all + * the other files in the directory. If only a partial deletion + * is done, then the files and directories that could not be + * deleted are listed in the log file, and the method returns false. + * + * This method has not been tested in Linux or Unix systems, + * so the behaviour across symbolic links is unknown. + */ + public static boolean deleteDirectory(File directory) { + boolean retValue = true; + + File[] files = directory.listFiles(); + if ( files != null ) { + for ( int i=0; i -1 ? zipFileName.substring(0,dotIndex) : zipFileName; + + String tempSysDirName = System.getProperty( "java.io.tmpdir" ); + if ( tempSysDirName == null ) + throw new ZipFileUtilException("No temporary directory known to the server. [System.getProperty( \"java.io.tmpdir\" ) returns null. ]\n Cannot upload package."); + + String tempDirName = tempSysDirName+File.separator + +prefix+System.currentTimeMillis()+"_"+shortZipName; + + // Set up the directory. Check it doesn't exist and if it does + // try 100 slightly different variations. If I can't find a unique + // one in ten tries, then give up. + File tempDir = new File(tempDirName); + int i = 0; + while ( tempDir.exists() && i < 100 ) { + tempDirName = tempSysDirName+File.separator + +prefix+System.currentTimeMillis()+"_"+i+shortZipName; + tempDir = new File(tempDirName); + } + if ( tempDir.exists() ) + throw new ZipFileUtilException("Temporary filename/directory that we would use to extract files already exists: " + +tempDirName+". Unable to upload Content Package."); + + tempDir.mkdirs(); + return tempDirName; + } + + private static void extractZipFile(InputStream is, String tempDirName) throws ZipFileUtilException { + // got our directory, so write out the input file and expand the zip file + // this is really a hack - write it out temporarily then read it back in again! urg!!!! + ZipInputStream zis = new ZipInputStream(is); + int count; + byte data[] = new byte[ BUFFER_SIZE ]; + ZipEntry entry = null; + BufferedOutputStream dest = null; + String entryName = null; + + try { + + // work through each file, creating a node for each file + while( ( entry = zis.getNextEntry() ) != null ) { + + if( !entry.isDirectory() ) + { + entryName = entry.getName(); + String destName = tempDirName + File.separator + entryName; + + prepareDirectory(destName); + + FileOutputStream fos = new FileOutputStream( destName ); + dest = new BufferedOutputStream( fos, BUFFER_SIZE ); + while( (count = zis.read( data, 0, BUFFER_SIZE ) ) != -1 ) + { + dest.write( data, 0, count ); + } + dest.flush(); + dest.close(); + dest = null; + } + + } + + } catch ( IOException ioe ) { + + log.error("Exception occured processing entries in zip file. Entry was "+entryName,ioe); + throw new ZipFileUtilException("Exception occured processing entries in zip file. Entry was "+entryName,ioe); + + } finally { + + if ( dest != null ) { + try { + dest.close(); + } catch ( IOException ioe2 ) { + log.error("Exception thrown in finally statement, trying to close destination file.",ioe2); + } + } + } + } + + + /** + * @param destName + */ + private static void prepareDirectory(String destName) { + File destNameFile = new File(destName); + String path = destNameFile.getParent(); + File pathDir = new File(path); + pathDir.mkdirs(); + } + + /** Delete a temporary directory. + * Checks that the directory name supplied looks like a temporary directory + * (ie is in the java tmp directory and starts with lamszip_) + * + * @param directoryName + * @return false if directoryName == null or one or more of the files/directories could not be deleted. See + * log for list of not deleted files. + * @throws ZipFileUtilException if the directory name doesn't look like + * a temporary directory created by the helper. + */ + public static boolean deleteDirectory(String directoryName) throws ZipFileUtilException { + if ( directoryName != null ) { + String tempSysDirNamePrefix = System.getProperty( "java.io.tmpdir" ) + +File.separator +prefix; + if ( ! directoryName.startsWith(tempSysDirNamePrefix) ) { + throw new ZipFileUtilException("Invalid directory delete request. Received request to delete directory " + + directoryName + + " but name doesn't start with the temporary directory location (" + + tempSysDirNamePrefix + + "). Not deleting directory"); + } + + return FileUtil.deleteDirectory(new File(directoryName)); + } + return false; + } + + + /** Clean up any old directories in the java tmp directory, where the + * directory name starts with lamszip_ and is older days old + * or older. This has the potential to be a heavy call - it has to + * do complete directory listing and then recursively delete the + * files and directories as needed. + * + * Note: this method has not been tested as it is rather hard to write + * a junit test for! + * + * @param numdays Number of days old that the directory should be to be + * deleted. Must be greater than 0 + * @return number of directories deleted + * @throws ZipFileUtilException if numDays <= 0 + */ + public static int cleanupOldFiles(int numDays) throws ZipFileUtilException { + + // Contract checking + if ( numDays <= 0 ) { + throw new ZipFileUtilException("Invalid cleanupOldFiles call - the parameter numDays is "+ + numDays+". Must be greater than 0. No files have been deleted."); + } + + // calculate comparison date + long newestDateToKeep = System.currentTimeMillis() - ( numDays * numMilliSecondsInADay); + Date date = new Date(newestDateToKeep); + log.info("Deleting all temp zipfile expanded directories before "+date.toString()+" (server time) ("+newestDateToKeep+")"); + + String tempSysDirName = System.getProperty( "java.io.tmpdir" ); + File tempSysDir = new File(tempSysDirName); + File candidates[] = tempSysDir.listFiles(new OldZipDirectoryFilter(newestDateToKeep, log)); + int numDeleted = 0; + if ( candidates != null ) { + for ( int i=0; i < candidates.length; i++) { + if ( FileUtil.deleteDirectory(candidates[i]) ) { + log.info("Directory "+candidates[i].getPath()+" deleted."); + } else { + log.info("Directory "+candidates[i].getPath()+" partially deleted - some directories/files could not be deleted."); + } + numDeleted++; + } + } + return numDeleted; + } + + private static String appendToString(String origString, String newMessage) { + if ( origString != null ) + return origString + " \n" +newMessage; + else + return newMessage; + } + +} + Index: lams_common/src/java/org/lamsfoundation/lams/util/zipfile/ZipFileUtilException.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/zipfile/ZipFileUtilException.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/zipfile/ZipFileUtilException.java (revision fb87f900acda9018b36e3e4fbb3ed04d4ac67805) @@ -0,0 +1,57 @@ +/* +Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + +This program 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; either version 2 of the License, or +(at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +http://www.gnu.org/licenses/gpl.txt +*/ +package org.lamsfoundation.lams.util.zipfile; + +/** + * Exception thrown by ZipFileUtil methods if a serious error occurs. + */ +public class ZipFileUtilException extends Exception { + + /** + * + */ + public ZipFileUtilException() { + super(); + } + + /** + * @param message + */ + public ZipFileUtilException(String message) { + super(message); + } + + /** + * @param cause + */ + public ZipFileUtilException(Throwable cause) { + super(cause); + } + + /** + * @param message + * @param cause + */ + public ZipFileUtilException(String message, Throwable cause) { + super(message, cause); + } + +} Index: lams_common/test/java/org/lamsfoundation/lams/util/zipfile/TestZipFileUtil.java =================================================================== diff -u --- lams_common/test/java/org/lamsfoundation/lams/util/zipfile/TestZipFileUtil.java (revision 0) +++ lams_common/test/java/org/lamsfoundation/lams/util/zipfile/TestZipFileUtil.java (revision fb87f900acda9018b36e3e4fbb3ed04d4ac67805) @@ -0,0 +1,225 @@ +/* +Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + +This program 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; either version 2 of the License, or +(at your option) any later version. + +This program 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 this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA + +http://www.gnu.org/licenses/gpl.txt +*/ +package org.lamsfoundation.lams.util.zipfile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import junit.framework.TestCase; + +import org.lamsfoundation.lams.util.FileUtil; + +/** + * Test that the zip file util will expand a zip file, delete the + * zip file temporary directory and that it won't delete an + * arbitrary directory. + * + * @author Fiona Malikoff + */ +public class TestZipFileUtil extends TestCase { + + // this file must exist in the same package as the test class. + private static String zipFileName = "bof.zip"; + + private String testFilename = "test.txt"; + private byte[] testFileContents = "This is my test file\n".getBytes(); + private String tempSysDirName = System.getProperty( "java.io.tmpdir" ); + + private static InputStream getTestZipFile() throws FileNotFoundException { + URL url = TestZipFileUtil.class.getResource(zipFileName); + if ( url == null ) + fail("Unable to get url of resource "+zipFileName); + + String path = url.getPath(); + File myResource = new File(path); + return new FileInputStream(myResource); + } + + public void testExpandDeleteZip() { + + try { + + InputStream is = getTestZipFile(); + assertTrue("File input stream can be created.", is != null ); + + String tempDirectory = ZipFileUtil.expandZip(is, zipFileName); + + assertTrue("Temporary name returned created", tempDirectory != null); + File dir = new File(tempDirectory); + assertTrue("Temporary directory created", dir.exists()); + String[] filenames = dir.list(); + assertTrue("Temporary directory contains files", filenames != null && filenames.length > 0 ); + + boolean successful = ZipFileUtil.deleteDirectory(tempDirectory); + assertTrue("All files in temporary directory deleted", successful); + dir = new File(tempDirectory); + assertTrue("Temporary name deleted", !dir.exists()); + + } catch (Exception e) { + fail("Exception thrown "+e.getMessage()); + } + } + + /** Tests the public delete directory method - checks with a directory + * with the prefix. (This method should only delete a directory + * if the directoryname starts with the special prefix.) + */ + public void testDeleteDirectoryWithPrefix() { + + try { + String testDirToDelete = ZipFileUtil.prepareTempDirectory("todelete.zip"); + writeFile(testDirToDelete, testFilename, testFileContents); + + ZipFileUtil.deleteDirectory(testDirToDelete); + File dir= new File(testDirToDelete); + assertTrue("Directory deletion suceeded.", ! dir.exists()); + + } catch (Exception e) { + fail("Unexpected exception thrown"+e.getMessage()); + } + + } + + /** Tests the public delete directory method - checks with a directory + * without the prefix. (This method should only delete a directory + * if the directoryname starts with the special prefix.) + */ + public void testDeleteDirectoryWithoutPrefix() { + + String testDirToKeep = tempSysDirName + "KeepDirectory"; + File dir = new File(testDirToKeep); + if ( ! dir.exists() ) { + dir.mkdirs(); + } + + try { + writeFile(testDirToKeep, testFilename, testFileContents); + + ZipFileUtil.deleteDirectory(testDirToKeep); + + } catch (ZipFileUtilException e) { + assertTrue("ZipFileUtilException thrown as expected", true); + } catch (Exception e) { + fail("Unexception thrown"+e.getMessage()); + } + + dir = new File(testDirToKeep); + assertTrue("Directory deletion failed as expected - directory still exists.", dir.exists()); + } + + /** Test the clean up of old files. Create three directories - two with the special + * name and one with a different name. Create three files - two with + * the special name and one with a different name. Of the dir/files + * with the special name, set one file and one directory to be five + * days old (via Calendar). Set one directory to be 4 days old. + * Then run the delete. Only the directory that is four days old should be deleted - + * all the others should be fine. + */ + public void testCleanupOldFiles() { + + Calendar foureDaysAgo = new GregorianCalendar(); + foureDaysAgo.add(Calendar.DAY_OF_MONTH,-4); + long fourDaysAgoMilliseconds = foureDaysAgo.getTimeInMillis(); + + try { + // create the three directories and stick a file in each directory to + // test the recursive delete. + String testDirToDelete = ZipFileUtil.prepareTempDirectory("todelete.zip"); + writeFile(testDirToDelete, testFilename, testFileContents); + File file = new File(testDirToDelete); + file.setLastModified(fourDaysAgoMilliseconds); + + String testDirToKeep1 = ZipFileUtil.prepareTempDirectory("tokeep.zip"); + writeFile(testDirToKeep1, testFilename, testFileContents); + + String testDirToKeep2 = tempSysDirName + "differentDirectory"; + File dir = new File(testDirToKeep2); + if ( ! dir.exists() ) { + dir.mkdirs(); + } + writeFile(testDirToKeep2, testFilename, testFileContents); + + // now create the three files. + String testFileToKeep1 = ZipFileUtil.prefix+"ToKeepFile1.txt"; + writeFile(tempSysDirName, testFileToKeep1, testFileContents); + file = new File(tempSysDirName+File.separator+testFileToKeep1); + file.setLastModified(fourDaysAgoMilliseconds); + String testFileToKeep2 = ZipFileUtil.prefix+"ToKeepFile2.txt"; + writeFile(tempSysDirName, testFileToKeep2, testFileContents); + String testFileToKeep3 = "ToKeepFile3.txt"; + writeFile(tempSysDirName, testFileToKeep3, testFileContents); + + // now sleep (so that the 4 days ago test works) and then delete... + Thread.sleep(5); + int numDeleted = ZipFileUtil.cleanupOldFiles(4); + + // only the "todelete" directory should be deleted + // as the test almost done, be nice and clean up our test files. + assertTrue("One directory deleted ",numDeleted==1); + + file = new File(testDirToDelete); + assertTrue("Directory "+testDirToDelete+" has been deleted ",!file.exists()); + + file = new File(testDirToKeep1); + assertTrue("Directory "+testDirToKeep1+" has not been deleted ",file.exists()); + FileUtil.deleteDirectory(file); + + file = new File(testDirToKeep2); + assertTrue("Directory "+testDirToKeep2+" has not been deleted ",file.exists()); + FileUtil.deleteDirectory(file); + + file = new File(tempSysDirName+File.separator+testFileToKeep1); + assertTrue("File "+testFileToKeep1+" has not been deleted ",file.exists()); + file.delete(); + + file = new File(tempSysDirName+File.separator+testFileToKeep2); + assertTrue("File "+testFileToKeep2+" has not been deleted ",file.exists()); + file.delete(); + + file = new File(tempSysDirName+File.separator+testFileToKeep3); + assertTrue("File "+testFileToKeep3+" has not been deleted ",file.exists()); + file.delete(); + + + } catch (Exception e) { + fail("Exception thrown "+e.getMessage()); + } + } + + private void writeFile(String directoryName, String filename, byte[] testFileContents) throws IOException { + FileOutputStream file = new FileOutputStream(directoryName+File.separator+filename); + try { + file.write(testFileContents); + file.close(); + } catch ( IOException e ) { + if ( file != null ) + file.close(); + throw e; + } + } +} Index: lams_common/test/java/org/lamsfoundation/lams/util/zipfile/bof.zip =================================================================== diff -u Binary files differ