Skip to content

Commit

Permalink
Merge pull request #23 from Nuix/feature/smarter-case-search-traversal
Browse files Browse the repository at this point in the history
Smarter traversal while searching for case.fbi2 files
  • Loading branch information
JuicyDragon authored Mar 18, 2024
2 parents 759f63d + b7877f3 commit ad3943a
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 114 deletions.
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);
}
}
}
60 changes: 60 additions & 0 deletions Java/SuperUtilities/src/test/java/CaseUtilityTests.java
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());
}
}

0 comments on commit ad3943a

Please sign in to comment.