-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from Nuix/feature/smarter-case-search-traversal
Smarter traversal while searching for case.fbi2 files
- Loading branch information
Showing
2 changed files
with
195 additions
and
114 deletions.
There are no files selected for viewing
249 changes: 135 additions & 114 deletions
249
Java/SuperUtilities/src/main/java/com/nuix/superutilities/cases/CaseUtility.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,130 +1,151 @@ | ||
package com.nuix.superutilities.cases; | ||
|
||
import com.nuix.superutilities.misc.ZipHelper; | ||
import org.apache.commons.io.FileUtils; | ||
import org.apache.log4j.Logger; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
import org.apache.commons.io.FileUtils; | ||
import org.apache.commons.io.filefilter.RegexFileFilter; | ||
import org.apache.commons.io.filefilter.TrueFileFilter; | ||
import org.apache.log4j.Logger; | ||
|
||
import com.nuix.superutilities.misc.ZipHelper; | ||
|
||
/*** | ||
* Class which provides some additional functionality regarding Nuix cases, mainly finding cases | ||
* present in directories and their sub directories. | ||
* @author Jason Wells | ||
* | ||
*/ | ||
public class CaseUtility { | ||
private static Logger logger = Logger.getLogger(CaseUtility.class); | ||
|
||
protected CaseUtility(){} | ||
|
||
protected static CaseUtility instance = null; | ||
public static CaseUtility getInstance(){ | ||
if(instance == null){ | ||
instance = new CaseUtility(); | ||
} | ||
return instance; | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchDirectory The root directory to search. | ||
* @return A collection of File objects for case directories located. | ||
*/ | ||
public Collection<File> findCaseDirectories(File rootSearchDirectory){ | ||
logger.info("Searching for cases in: "+rootSearchDirectory.getPath()); | ||
return FileUtils | ||
.listFiles(rootSearchDirectory, new RegexFileFilter("case\\.fbi2"), TrueFileFilter.TRUE) | ||
.stream().map(f -> f.getParentFile()).collect(Collectors.toList()); | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchPath The path to the root directory to search. | ||
* @return A collection of File objects for case directories located. | ||
*/ | ||
public Collection<File> findCaseDirectories(String rootSearchPath){ | ||
return findCaseDirectories(new File(rootSearchPath)); | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchDirectory The root directory to search. | ||
* @return A collection of String representing case directories located. | ||
*/ | ||
public Collection<String> findCaseDirectoryPaths(File rootSearchDirectory){ | ||
return findCaseDirectories(rootSearchDirectory) | ||
.stream().map(f -> f.getPath()).collect(Collectors.toList()); | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchPath The root directory to search. | ||
* @return A collection of String representing case directories located. | ||
*/ | ||
public Collection<String> findCaseDirectoryPaths(String rootSearchPath){ | ||
return findCaseDirectoryPaths(new File(rootSearchPath)); | ||
} | ||
|
||
/*** | ||
* Scans specified root directory and sub-directories for cases, returning {@link CaseInfo} objects | ||
* for each case located. | ||
* @param rootSearchDirectory The root directory to search in | ||
* @return Case info objects for each case found | ||
*/ | ||
public List<CaseInfo> findCaseInformation(File rootSearchDirectory){ | ||
List<CaseInfo> result = null; | ||
result = findCaseDirectories(rootSearchDirectory).stream().map(d -> new CaseInfo(d)).collect(Collectors.toList()); | ||
return result; | ||
} | ||
|
||
/*** | ||
* Scans specified root directory and sub-directories for cases, returning {@link CaseInfo} objects | ||
* for each case located. | ||
* @param rootSearchPath The root directory to search in | ||
* @return Case info objects for each case found | ||
*/ | ||
public List<CaseInfo> findCaseInformation(String rootSearchPath){ | ||
return findCaseInformation(new File(rootSearchPath)); | ||
} | ||
|
||
/*** | ||
* Archives a Nuix case into a Zip file, optionally deleting the case once completed. Based on: | ||
* https://stackoverflow.com/questions/23318383/compress-directory-into-a-zipfile-with-commons-io | ||
* @param nuixCaseDirectory Directory of the Nuix case | ||
* @param archiveFile The Zip file to archive the case into | ||
* @param deleteCaseOnCompletion Whether to delete the case upon completion | ||
* @param compressionLevel The compression level (0-9) with 0 being no compression and 9 being full compression, values outside | ||
* range will be clamped into range. | ||
* @throws IOException Thrown if there are issues creating the archive or deleting the directory. | ||
*/ | ||
public void archiveCase(String nuixCaseDirectory, String archiveFile, boolean deleteCaseOnCompletion, int compressionLevel) throws IOException { | ||
archiveCase(new File(nuixCaseDirectory),new File(archiveFile),deleteCaseOnCompletion,compressionLevel); | ||
} | ||
|
||
/*** | ||
* Archives a Nuix case into a Zip file, optionally deleting the case once completed. Based on: | ||
* https://stackoverflow.com/questions/23318383/compress-directory-into-a-zipfile-with-commons-io | ||
* @param nuixCaseDirectory Directory of the Nuix case | ||
* @param archiveFile The Zip file to archive the case into | ||
* @param deleteCaseOnCompletion Whether to delete the case upon completion | ||
* @param compressionLevel The compression level (0-9) with 0 being no compression and 9 being full compression, values outside | ||
* range will be clamped into range. | ||
* @throws IOException Thrown if there are issues creating the archive or deleting the directory. | ||
*/ | ||
public void archiveCase(File nuixCaseDirectory, File archiveFile, boolean deleteCaseOnCompletion, int compressionLevel) throws IOException { | ||
logger.info("Backing up case at " + nuixCaseDirectory.getAbsolutePath() + " to " + archiveFile.getAbsolutePath()); | ||
archiveFile.getParentFile().mkdirs(); | ||
ZipHelper.compressDirectoryToZipFile(nuixCaseDirectory.getAbsolutePath(), archiveFile.getAbsolutePath(), compressionLevel); | ||
if(deleteCaseOnCompletion && archiveFile.exists()) { | ||
logger.info("Deleting now archived case " + nuixCaseDirectory.getAbsolutePath()); | ||
FileUtils.deleteDirectory(nuixCaseDirectory); | ||
} | ||
} | ||
private static Logger logger = Logger.getLogger(CaseUtility.class); | ||
|
||
protected CaseUtility() { | ||
} | ||
|
||
protected static CaseUtility instance = null; | ||
|
||
public static CaseUtility getInstance() { | ||
if (instance == null) { | ||
instance = new CaseUtility(); | ||
} | ||
return instance; | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchDirectory The root directory to search. | ||
* @return A collection of File objects for case directories located. | ||
*/ | ||
public Collection<File> findCaseDirectories(File rootSearchDirectory) { | ||
logger.info("Searching for cases in: " + rootSearchDirectory.getPath()); | ||
if (!rootSearchDirectory.exists()) { | ||
logger.info("Directory does not exist: " + rootSearchDirectory.getPath()); | ||
return List.of(); | ||
} else { | ||
File[] children = rootSearchDirectory.listFiles(); | ||
for (int i = 0; i < children.length; i++) { | ||
File child = children[i]; | ||
if(child.isFile() && child.getName().equalsIgnoreCase("case.fbi2")) { | ||
return List.of(child); | ||
} | ||
} | ||
|
||
List<File> result = new ArrayList<>(); | ||
for (int i = 0; i < children.length; i++) { | ||
File child = children[i]; | ||
if(child.isDirectory()) { | ||
Collection<File> foundFiles = findCaseDirectories(child); | ||
if(!foundFiles.isEmpty()) { | ||
result.addAll(foundFiles); | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchPath The path to the root directory to search. | ||
* @return A collection of File objects for case directories located. | ||
*/ | ||
public Collection<File> findCaseDirectories(String rootSearchPath) { | ||
return findCaseDirectories(new File(rootSearchPath)); | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchDirectory The root directory to search. | ||
* @return A collection of String representing case directories located. | ||
*/ | ||
public Collection<String> findCaseDirectoryPaths(File rootSearchDirectory) { | ||
return findCaseDirectories(rootSearchDirectory) | ||
.stream().map(f -> f.getPath()).collect(Collectors.toList()); | ||
} | ||
|
||
/*** | ||
* Searches for case directories in a given directory and sub-directories. | ||
* @param rootSearchPath The root directory to search. | ||
* @return A collection of String representing case directories located. | ||
*/ | ||
public Collection<String> findCaseDirectoryPaths(String rootSearchPath) { | ||
return findCaseDirectoryPaths(new File(rootSearchPath)); | ||
} | ||
|
||
/*** | ||
* Scans specified root directory and sub-directories for cases, returning {@link CaseInfo} objects | ||
* for each case located. | ||
* @param rootSearchDirectory The root directory to search in | ||
* @return Case info objects for each case found | ||
*/ | ||
public List<CaseInfo> findCaseInformation(File rootSearchDirectory) { | ||
List<CaseInfo> result = null; | ||
result = findCaseDirectories(rootSearchDirectory).stream().map(d -> new CaseInfo(d)).collect(Collectors.toList()); | ||
return result; | ||
} | ||
|
||
/*** | ||
* Scans specified root directory and sub-directories for cases, returning {@link CaseInfo} objects | ||
* for each case located. | ||
* @param rootSearchPath The root directory to search in | ||
* @return Case info objects for each case found | ||
*/ | ||
public List<CaseInfo> findCaseInformation(String rootSearchPath) { | ||
return findCaseInformation(new File(rootSearchPath)); | ||
} | ||
|
||
/*** | ||
* Archives a Nuix case into a Zip file, optionally deleting the case once completed. Based on: | ||
* https://stackoverflow.com/questions/23318383/compress-directory-into-a-zipfile-with-commons-io | ||
* @param nuixCaseDirectory Directory of the Nuix case | ||
* @param archiveFile The Zip file to archive the case into | ||
* @param deleteCaseOnCompletion Whether to delete the case upon completion | ||
* @param compressionLevel The compression level (0-9) with 0 being no compression and 9 being full compression, values outside | ||
* range will be clamped into range. | ||
* @throws IOException Thrown if there are issues creating the archive or deleting the directory. | ||
*/ | ||
public void archiveCase(String nuixCaseDirectory, String archiveFile, boolean deleteCaseOnCompletion, int compressionLevel) throws IOException { | ||
archiveCase(new File(nuixCaseDirectory), new File(archiveFile), deleteCaseOnCompletion, compressionLevel); | ||
} | ||
|
||
/*** | ||
* Archives a Nuix case into a Zip file, optionally deleting the case once completed. Based on: | ||
* https://stackoverflow.com/questions/23318383/compress-directory-into-a-zipfile-with-commons-io | ||
* @param nuixCaseDirectory Directory of the Nuix case | ||
* @param archiveFile The Zip file to archive the case into | ||
* @param deleteCaseOnCompletion Whether to delete the case upon completion | ||
* @param compressionLevel The compression level (0-9) with 0 being no compression and 9 being full compression, values outside | ||
* range will be clamped into range. | ||
* @throws IOException Thrown if there are issues creating the archive or deleting the directory. | ||
*/ | ||
public void archiveCase(File nuixCaseDirectory, File archiveFile, boolean deleteCaseOnCompletion, int compressionLevel) throws IOException { | ||
logger.info("Backing up case at " + nuixCaseDirectory.getAbsolutePath() + " to " + archiveFile.getAbsolutePath()); | ||
archiveFile.getParentFile().mkdirs(); | ||
ZipHelper.compressDirectoryToZipFile(nuixCaseDirectory.getAbsolutePath(), archiveFile.getAbsolutePath(), compressionLevel); | ||
if (deleteCaseOnCompletion && archiveFile.exists()) { | ||
logger.info("Deleting now archived case " + nuixCaseDirectory.getAbsolutePath()); | ||
FileUtils.deleteDirectory(nuixCaseDirectory); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import com.nuix.superutilities.cases.CaseUtility; | ||
import org.apache.commons.io.FileUtils; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.io.File; | ||
import java.util.Collection; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
||
public class CaseUtilityTests extends TestFoundation { | ||
@Test | ||
public void testCaseSearch() throws Exception { | ||
File exampleCaseFbi2 = new File(testDataDirectory, "ExampleCase.fbi2"); | ||
File testDir = new File(testDataDirectory, "CaseSearchTest"); | ||
if (!testDir.exists()) { | ||
testDir.mkdirs(); | ||
// TestData/Case1234 | ||
File fakeCaseRoot = new File(testDir, "Case1234"); | ||
fakeCaseRoot.mkdirs(); | ||
|
||
// TestData/Case1234/case.fbi2 | ||
FileUtils.copyFile(exampleCaseFbi2, new File(fakeCaseRoot, "case.fbi2")); | ||
|
||
// TestData/Case1234/Sub-Directory | ||
File fakeCaseInner = new File(fakeCaseRoot, "Sub-Directory"); | ||
fakeCaseInner.mkdirs(); | ||
|
||
// TestData/Case1234/Sub-Directory/case.fbi2 | ||
FileUtils.copyFile(exampleCaseFbi2, new File(fakeCaseInner, "case.fbi2")); | ||
|
||
// TestData/Case5678 | ||
File fakeCaseRoot2 = new File(testDir, "Case5678"); | ||
fakeCaseRoot2.mkdirs(); | ||
|
||
// TestData/Case5678/case.fbi2 | ||
FileUtils.copyFile(exampleCaseFbi2, new File(fakeCaseRoot2, "case.fbi2")); | ||
|
||
// TestData/Case5678/Sub-Directory | ||
File fakeCaseInner2 = new File(fakeCaseRoot2, "Sub-Directory"); | ||
fakeCaseInner2.mkdirs(); | ||
|
||
// TestData/Case5678/Sub-Directory/case.fbi2 | ||
FileUtils.copyFile(exampleCaseFbi2, new File(fakeCaseInner2, "case.fbi2")); | ||
} | ||
|
||
// Test data should look like: | ||
// TestData/Case1234/case.fbi2 | ||
// TestData/Case1234/Sub-Directory/case.fbi2 | ||
// TestData/Case5678/case.fbi2 | ||
// TestData/Case5678/Sub-Directory/case.fbi2 | ||
// The deeper case.fbi2 files should NOT be found since traversal logic should halt | ||
// searching deeper once it finds each upper case.fbi2 file. The early traversal exit logic | ||
// will hopefully help address a situation where file system stored binaries are in case directory | ||
// which, without early exit logic, will do a large amount of unnecessary traversal when the case | ||
// fbi2 file had been found much higher up. | ||
|
||
Collection<File> caseDirs = CaseUtility.getInstance().findCaseDirectories(testDir); | ||
assertEquals(2, caseDirs.size()); | ||
} | ||
} |