From 581955ea958086f554796c833e3f876b7473ae00 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 4 Apr 2018 12:38:20 -0400 Subject: [PATCH 001/100] Health monitor can create database --- .../CoordinationService.java | 3 +- .../org/sleuthkit/autopsy/core/Installer.java | 1 + .../healthmonitor/HealthMonitorException.java | 21 + .../autopsy/healthmonitor/Installer.java | 53 ++ .../healthmonitor/ServicesHealthMonitor.java | 508 ++++++++++++++++++ .../autopsy/healthmonitor/TimingMetric.java | 46 ++ 6 files changed, 631 insertions(+), 1 deletion(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java diff --git a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java index 9b2afff6b4..d7ef8c4750 100644 --- a/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java +++ b/Core/src/org/sleuthkit/autopsy/coordinationservice/CoordinationService.java @@ -457,7 +457,8 @@ public final class CoordinationService { CASES("cases"), MANIFESTS("manifests"), CONFIG("config"), - CENTRAL_REPO("centralRepository"); + CENTRAL_REPO("centralRepository"), + HEALTH_MONITOR("healthMonitor"); private final String displayName; diff --git a/Core/src/org/sleuthkit/autopsy/core/Installer.java b/Core/src/org/sleuthkit/autopsy/core/Installer.java index 83250f719d..63ac880171 100644 --- a/Core/src/org/sleuthkit/autopsy/core/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/core/Installer.java @@ -216,6 +216,7 @@ public class Installer extends ModuleInstall { packageInstallers.add(org.sleuthkit.autopsy.datamodel.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.ingest.Installer.getDefault()); packageInstallers.add(org.sleuthkit.autopsy.centralrepository.eventlisteners.Installer.getDefault()); + packageInstallers.add(org.sleuthkit.autopsy.healthmonitor.Installer.getDefault()); } /** diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java new file mode 100644 index 0000000000..26c39ef5b5 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java @@ -0,0 +1,21 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.healthmonitor; + +/** + * + */ +class HealthMonitorException extends Exception { + private static final long serialVersionUID = 1L; + + HealthMonitorException(String message) { + super(message); + } + + HealthMonitorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java new file mode 100644 index 0000000000..537f3593ae --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -0,0 +1,53 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import java.util.logging.Level; +import org.openide.modules.ModuleInstall; +import org.sleuthkit.autopsy.coreutils.Logger; + +public class Installer extends ModuleInstall { + + private static final Logger logger = Logger.getLogger(Installer.class.getName()); + private static final long serialVersionUID = 1L; + + private static Installer instance; + + public synchronized static Installer getDefault() { + if (instance == null) { + instance = new Installer(); + } + return instance; + } + + private Installer() { + super(); + } + + @Override + public void restored() { + try { + ServicesHealthMonitor.startUp(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error starting health services monitor", ex); + } + } + + @Override + public boolean closing() { + //platform about to close + ServicesHealthMonitor.close(); + + return true; + } + + @Override + public void uninstalled() { + //module is being unloaded + ServicesHealthMonitor.close(); + + } +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java new file mode 100644 index 0000000000..67f096f77c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -0,0 +1,508 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import org.apache.commons.dbcp2.BasicDataSource; +import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; +import org.sleuthkit.autopsy.coordinationservice.CoordinationService; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.core.UserPreferencesException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; + +/** + * + */ +public class ServicesHealthMonitor { + + private final static Logger logger = Logger.getLogger(ServicesHealthMonitor.class.getName()); + private final static String DATABASE_NAME = "ServicesHealthMonitor"; + private final static String MODULE_NAME = "ServicesHealthMonitor"; + private final static String IS_ENABLED_KEY = "is_enabled"; + private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes + public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION + = new CaseDbSchemaVersionNumber(1, 0); + + private static final AtomicBoolean isEnabled = new AtomicBoolean(false); + private static ServicesHealthMonitor instance; + + private ScheduledThreadPoolExecutor periodicTasksExecutor; + private Map timingInfoMap; + private static final int CONN_POOL_SIZE = 5; + private BasicDataSource connectionPool = null; + + private ServicesHealthMonitor() throws HealthMonitorException { + System.out.println("\nCreating ServicesHealthMonitor"); + + // Create the map to collect timing metrics + timingInfoMap = new HashMap<>(); + + if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { + if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ + isEnabled.set(true); + activateMonitor(); + return; + } + } + isEnabled.set(false); + } + + private synchronized void activateMonitor() throws HealthMonitorException { + // Set up database (if needed) + System.out.println(" Setting up database..."); + if (!UserPreferences.getIsMultiUserModeEnabled()) { + throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); + } + + CoordinationService.Lock lock = getExclusiveDbLock(); + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + try { + // Check if the database exists + if (! databaseExists()) { + + // If not, create a new one + createDatabase(); + initializeDatabaseSchema(); + } + + // Any database upgrades would happen here + + } finally { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } + + // Prepare metric storage + System.out.println(" Clearing hash map..."); + timingInfoMap = new HashMap<>(); + + // Start the timer + System.out.println(" Starting the timer..."); + if(periodicTasksExecutor != null) { + // Make sure the previous executor (if it exists) has been stopped + periodicTasksExecutor.shutdown(); + } + periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); + periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); + + } + + private synchronized void deactivateMonitor() throws HealthMonitorException { + // Clear out the collected data + System.out.println(" Clearing hash map..."); + timingInfoMap.clear(); + + // Stop the timer + System.out.println(" Stopping the timer..."); + if(periodicTasksExecutor != null) { + periodicTasksExecutor.shutdown(); + } + + // Shut down the connection pool + shutdownConnections(); + } + + synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { + if (instance == null) { + instance = new ServicesHealthMonitor(); + } + return instance; + } + + static synchronized void startUp() throws HealthMonitorException { + System.out.println("\nServicesHealthMonitor starting up"); + getInstance(); + } + + static synchronized void setEnabled(boolean enabled) throws HealthMonitorException { + System.out.println("\nServicesHealthMonitor setting enabled to " + enabled + "(previous: " + isEnabled.get() + ")"); + if(enabled == isEnabled.get()) { + // The setting has not changed, so do nothing + return; + } + + if(enabled) { + ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true"); + isEnabled.set(true); + getInstance().activateMonitor(); + } else { + ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false"); + isEnabled.set(false); + getInstance().deactivateMonitor(); + } + } + + public static TimingMetric getTimingMetric(String name) { + if(isEnabled.get()) { + return new TimingMetric(name); + } + return null; + } + + public static void submitTimingMetric(TimingMetric metric) { + if(isEnabled.get() && (metric != null)) { + metric.stopTiming(); + try { + getInstance().addTimingMetric(metric); + } catch (HealthMonitorException ex) { + // We don't want calling methods to have to check for exceptions, so just log it + logger.log(Level.SEVERE, "Error accessing services health monitor", ex); + } + } + } + + private void addTimingMetric(TimingMetric metric) { + try{ + synchronized(this) { + // There's a small check-then-act situation here where isEnabled + // may have changed before reaching this code, but it doesn't cause + // any errors to load a few extra entries into the map after disabling + // the monitor (they will be deleted if the monitor is re-enabled). + if(timingInfoMap.containsKey(metric.getName())) { + timingInfoMap.get(metric.getName()).addMetric(metric); + } else { + timingInfoMap.put(metric.getName(), new TimingInfo(metric)); + } + } + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error adding timing metric", ex); + } + } + + // Make private once testing is done + void writeCurrentStateToDatabase() { + System.out.println("\nwriteCurrentStateToDatabase"); + + Map timingMapCopy; + synchronized(this) { + if(! isEnabled.get()) { + return; + } + + // Make a shallow copy of the map. The map should be small - one entry + // per metric type. + timingMapCopy = new HashMap<>(timingInfoMap); + timingInfoMap.clear(); + } + + for(String name:timingMapCopy.keySet()){ + TimingInfo info = timingMapCopy.get(name); + long timestamp = System.currentTimeMillis(); + System.out.println(" Name: " + name + "\tTimestamp: " + timestamp + "\tAverage: " + info.getAverage() + + "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); + } + } + + synchronized void clearCurrentState() { + timingInfoMap.clear(); + } + + static synchronized void close() { + if(isEnabled.get()) { + // Stop the timer + + // Write current data + try { + getInstance().writeCurrentStateToDatabase(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error writing final metric data to database", ex); + } + + // Shutdown connection pool + try { + getInstance().shutdownConnections(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error shutting down connection pool", ex); + } + + } + } + + synchronized void printCurrentState() { + System.out.println("\nTiming Info Map:"); + for(String name:timingInfoMap.keySet()) { + System.out.print(name + "\t"); + timingInfoMap.get(name).print(); + } + } + + // Change to private after testing + boolean databaseExists() throws HealthMonitorException { + + System.out.println("\nChecking database existence"); + + try { + // Use the same database settings as the case + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + ResultSet rs = null; + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; + System.out.println(" query: " + createCommand); + rs = statement.executeQuery(createCommand); + if(rs.next()) { + System.out.println(" Exists!"); + return true; + } + } finally { + if(rs != null) { + rs.close(); + } + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + throw new HealthMonitorException("Failed check for health monitor database", ex); + } + System.out.println(" Does not exist"); + return false; + } + + private void createDatabase() throws HealthMonitorException { + try { + System.out.println("\nCreating database " + DATABASE_NAME); + // Use the same database settings as the case + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String createCommand = "CREATE DATABASE \"" + DATABASE_NAME + "\" OWNER \"" + db.getUserName() + "\""; //NON-NLS + statement.execute(createCommand); + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + throw new HealthMonitorException("Failed to delete health monitor database", ex); + } + } + + + /** + * Delete the current health monitor database (for testing only) + * Make private after test + */ + void deleteDatabase() { + try { + // Use the same database settings as the case + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + Class.forName("org.postgresql.Driver"); //NON-NLS + try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + Statement statement = connection.createStatement();) { + String deleteCommand = "DROP DATABASE \"" + DATABASE_NAME + "\""; //NON-NLS + statement.execute(deleteCommand); + } + } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { + logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); + } + } + + /** + * Setup a connection pool for db connections. + * + */ + private void setupConnectionPool() throws HealthMonitorException { + try { + CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); + + connectionPool = new BasicDataSource(); + //connectionPool.setUsername(db.getUserName()); + //connectionPool.setPassword(db.getPassword()); + connectionPool.setDriverClassName("org.postgresql.Driver"); + + StringBuilder connectionURL = new StringBuilder(); + connectionURL.append("jdbc:postgresql://"); + connectionURL.append(db.getHost()); + connectionURL.append(":"); + connectionURL.append(db.getPort()); + connectionURL.append("/"); + connectionURL.append(DATABASE_NAME); + + connectionPool.setUrl(connectionURL.toString()); + connectionPool.setUsername(db.getUserName()); + connectionPool.setPassword(db.getPassword()); + + // tweak pool configuration + connectionPool.setInitialSize(5); // start with 5 connections + connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections + connectionPool.setValidationQuery("SELECT version()"); + } catch (UserPreferencesException ex) { + throw new HealthMonitorException("Error loading database configuration", ex); + } + } + + public void shutdownConnections() throws HealthMonitorException { + try { + synchronized(this) { + if(connectionPool != null){ + connectionPool.close(); + connectionPool = null; // force it to be re-created on next connect() + } + } + } catch (SQLException ex) { + throw new HealthMonitorException("Failed to close existing database connections.", ex); // NON-NLS + } + } + + private Connection connect() throws HealthMonitorException { + synchronized (this) { + if (connectionPool == null) { + setupConnectionPool(); + } + } + + try { + return connectionPool.getConnection(); + } catch (SQLException ex) { + throw new HealthMonitorException("Error getting connection from connection pool.", ex); // NON-NLS + } + } + + private void initializeDatabaseSchema() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + try (Statement statement = conn.createStatement()) { + StringBuilder createTimingTable = new StringBuilder(); + createTimingTable.append("CREATE TABLE IF NOT EXISTS timingData ("); + createTimingTable.append("id SERIAL PRIMARY KEY,"); + createTimingTable.append("name text NOT NULL,"); + createTimingTable.append("timestamp bigint NOT NULL,"); + createTimingTable.append("count bigint NOT NULL,"); + createTimingTable.append("average int NOT NULL,"); + createTimingTable.append("max int NOT NULL,"); + createTimingTable.append("min int NOT NULL"); + createTimingTable.append(")"); + statement.execute(createTimingTable.toString()); + + StringBuilder createDbInfoTable = new StringBuilder(); + createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info ("); + createDbInfoTable.append("id SERIAL PRIMARY KEY NOT NULL,"); + createDbInfoTable.append("name text NOT NULL,"); + createDbInfoTable.append("value text NOT NULL"); + createDbInfoTable.append(")"); + statement.execute(createDbInfoTable.toString()); + + statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); + statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')"); + + } catch (SQLException ex) { + throw new HealthMonitorException("Error initializing database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + + private final class DatabaseWriteTask implements Runnable { + + /** + * Write current metric data to the database + */ + @Override + public void run() { + try { + System.out.println("\nTimer up - writing to DB"); + getInstance().writeCurrentStateToDatabase(); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Unexpected exception in DatabaseWriteTask", ex); //NON-NLS + } + } + } + + private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ + try { + String databaseNodeName = DATABASE_NAME; + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); + + if(lock != null){ + return lock; + } + throw new HealthMonitorException("Error acquiring database lock"); + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + throw new HealthMonitorException("Error acquiring database lock"); + } + } + + private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{ + try { + String databaseNodeName = DATABASE_NAME; + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetSharedLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); + + if(lock != null){ + return lock; + } + throw new HealthMonitorException("Error acquiring database lock"); + } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ + throw new HealthMonitorException("Error acquiring database lock"); + } + } + + private class TimingInfo { + private long count; + private long sum; + private long max; + private long min; + + TimingInfo(TimingMetric metric) throws HealthMonitorException { + count = 1; + sum = metric.getDuration(); + max = metric.getDuration(); + min = metric.getDuration(); + } + + void addMetric(TimingMetric metric) throws HealthMonitorException { + count++; + sum += metric.getDuration(); + + if(max < metric.getDuration()) { + max = metric.getDuration(); + } + + if(min > metric.getDuration()) { + min = metric.getDuration(); + } + } + + long getAverage() { + return sum / count; + } + + long getMax() { + return max; + } + + long getMin() { + return min; + } + + void print() { + System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java new file mode 100644 index 0000000000..431e585710 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -0,0 +1,46 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.healthmonitor; + +/** + * + */ +public class TimingMetric { + + private final String name; + private final long startingTimestamp; + private Long duration; + + TimingMetric(String name) { + this.name = name; + this.startingTimestamp = System.nanoTime(); + this.duration = null; + } + + /** + * Record how long the metric was running. + */ + void stopTiming() { + long endingTimestamp = System.nanoTime(); + this.duration = endingTimestamp - startingTimestamp; + } + + /** + * Get the name of metric + * @return name + */ + String getName() { + return name; + } + + long getDuration() throws HealthMonitorException { + if (duration != null) { + return duration; + } else { + throw new HealthMonitorException("getDuration() called before stopTiming()"); + } + } +} From 94107ef1dca4e82f013b26a8af25252fdc2ef608 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 4 Apr 2018 14:33:16 -0400 Subject: [PATCH 002/100] Added metrics to Keyword Search --- Core/nbproject/project.xml | 1 + .../healthmonitor/ServicesHealthMonitor.java | 71 +++++++++++++++++-- .../autopsy/keywordsearch/Ingester.java | 4 ++ .../autopsy/keywordsearch/Server.java | 4 ++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 0159ec9880..5dabff9f5d 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -327,6 +327,7 @@ org.sleuthkit.autopsy.events org.sleuthkit.autopsy.filesearch org.sleuthkit.autopsy.guiutils + org.sleuthkit.autopsy.healthmonitor org.sleuthkit.autopsy.ingest org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 67f096f77c..0bdf9d5e79 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -82,8 +82,11 @@ public class ServicesHealthMonitor { if (! databaseExists()) { // If not, create a new one + System.out.println(" No database exists - setting up new one"); createDatabase(); initializeDatabaseSchema(); + } else { + System.out.println(" Database already exists"); } // Any database upgrades would happen here @@ -194,7 +197,7 @@ public class ServicesHealthMonitor { } // Make private once testing is done - void writeCurrentStateToDatabase() { + void writeCurrentStateToDatabase() throws HealthMonitorException { System.out.println("\nwriteCurrentStateToDatabase"); Map timingMapCopy; @@ -208,13 +211,65 @@ public class ServicesHealthMonitor { timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } - + + // Check if there's anything to report + if(timingMapCopy.keySet().isEmpty()) { + System.out.println("No timing data to save"); + return; + } + + // Debug for(String name:timingMapCopy.keySet()){ TimingInfo info = timingMapCopy.get(name); long timestamp = System.currentTimeMillis(); System.out.println(" Name: " + name + "\tTimestamp: " + timestamp + "\tAverage: " + info.getAverage() + "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); } + + CoordinationService.Lock lock = getSharedDbLock(); + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + + try { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + + //"INSERT INTO db_info (name, value) VALUES (?, ?)" + String addTimingInfoSql = "INSERT INTO timing_data (name, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?)"; + try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { + + for(String name:timingMapCopy.keySet()) { + TimingInfo info = timingMapCopy.get(name); + + statement.setString(1, name); + statement.setLong(2, System.currentTimeMillis()); + statement.setLong(3, info.getCount()); + statement.setLong(4, info.getAverage()); + statement.setLong(5, info.getMax()); + statement.setLong(6, info.getMin()); + + statement.execute(); + } + + } catch (SQLException ex) { + throw new HealthMonitorException("Error saving metric data to database", ex); + } finally { + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } finally { + try { + lock.release(); + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); + } + } } synchronized void clearCurrentState() { @@ -386,14 +441,14 @@ public class ServicesHealthMonitor { try (Statement statement = conn.createStatement()) { StringBuilder createTimingTable = new StringBuilder(); - createTimingTable.append("CREATE TABLE IF NOT EXISTS timingData ("); + createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); createTimingTable.append("id SERIAL PRIMARY KEY,"); createTimingTable.append("name text NOT NULL,"); createTimingTable.append("timestamp bigint NOT NULL,"); createTimingTable.append("count bigint NOT NULL,"); - createTimingTable.append("average int NOT NULL,"); - createTimingTable.append("max int NOT NULL,"); - createTimingTable.append("min int NOT NULL"); + createTimingTable.append("average bigint NOT NULL,"); + createTimingTable.append("max bigint NOT NULL,"); + createTimingTable.append("min bigint NOT NULL"); createTimingTable.append(")"); statement.execute(createTimingTable.toString()); @@ -501,6 +556,10 @@ public class ServicesHealthMonitor { return min; } + long getCount() { + return count; + } + void print() { System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index c7f4c07f6a..679a646995 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,6 +27,8 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; import org.sleuthkit.datamodel.AbstractFile; @@ -235,7 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr index chunk"); solrServer.addDocument(updateDoc); + ServicesHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 416f2b394f..3b4e494480 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,6 +70,8 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -773,7 +775,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr connectivity check"); connectToSolrServer(currentSolrServer); + ServicesHealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); From f289685f254ddecadddb6a225cd877128ed8f346 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 10:24:56 -0400 Subject: [PATCH 003/100] Cleanup and improved documentation --- .../healthmonitor/HealthMonitorException.java | 21 +- .../autopsy/healthmonitor/Installer.java | 19 +- .../healthmonitor/ServicesHealthMonitor.java | 334 +++++++++++++----- .../autopsy/healthmonitor/TimingMetric.java | 27 +- 4 files changed, 304 insertions(+), 97 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java index 26c39ef5b5..6df918acaa 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorException.java @@ -1,12 +1,25 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.healthmonitor; /** - * + * Exception used internally by the Services Health Monitor */ class HealthMonitorException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index 537f3593ae..b77ba067d7 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.healthmonitor; diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 0bdf9d5e79..ab2510ca59 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.healthmonitor; @@ -19,7 +32,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import org.apache.commons.dbcp2.BasicDataSource; -import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; @@ -29,7 +41,11 @@ import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; /** - * + * Class for recording data on the health of the system. + * + * For timing data: + * Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object + * Modules will call submitTimingMetric() with the obtained TimingMetric object to log it */ public class ServicesHealthMonitor { @@ -45,16 +61,17 @@ public class ServicesHealthMonitor { private static ServicesHealthMonitor instance; private ScheduledThreadPoolExecutor periodicTasksExecutor; - private Map timingInfoMap; - private static final int CONN_POOL_SIZE = 5; + private final Map timingInfoMap; + private static final int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; private ServicesHealthMonitor() throws HealthMonitorException { - System.out.println("\nCreating ServicesHealthMonitor"); - // Create the map to collect timing metrics + // Create the map to collect timing metrics. The map will exist regardless + // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); + // Read from module settings to determine if the module is enabled if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ isEnabled.set(true); @@ -65,13 +82,32 @@ public class ServicesHealthMonitor { isEnabled.set(false); } + /** + * Get the instance of the ServicesHealthMonitor + * @return the instance + * @throws HealthMonitorException + */ + synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { + if (instance == null) { + instance = new ServicesHealthMonitor(); + } + return instance; + } + + /** + * Activate the health monitor. + * Creates/initialized the database (if needed), clears any existing metrics + * out of the maps, and sets up the timer for writing to the database. + * @throws HealthMonitorException + */ private synchronized void activateMonitor() throws HealthMonitorException { - // Set up database (if needed) - System.out.println(" Setting up database..."); + + logger.log(Level.INFO, "Activating Servies Health Monitor"); + if (!UserPreferences.getIsMultiUserModeEnabled()) { throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); } - + // Set up database (if needed) CoordinationService.Lock lock = getExclusiveDbLock(); if(lock == null) { throw new HealthMonitorException("Error getting database lock"); @@ -85,8 +121,6 @@ public class ServicesHealthMonitor { System.out.println(" No database exists - setting up new one"); createDatabase(); initializeDatabaseSchema(); - } else { - System.out.println(" Database already exists"); } // Any database upgrades would happen here @@ -99,50 +133,69 @@ public class ServicesHealthMonitor { } } - // Prepare metric storage - System.out.println(" Clearing hash map..."); - timingInfoMap = new HashMap<>(); + // Clear out any old data + timingInfoMap.clear(); - // Start the timer - System.out.println(" Starting the timer..."); + // Start the timer for database writes + startTimer(); + } + + /** + * Deactivate the health monitor. + * This should only be used when disabling the monitor, not when Autopsy is closing. + * Clears out any metrics that haven't been written, stops the database write timer, + * and shuts down the connection pool. + * @throws HealthMonitorException + */ + private synchronized void deactivateMonitor() throws HealthMonitorException { + + logger.log(Level.INFO, "Deactivating Servies Health Monitor"); + + // Clear out the collected data + timingInfoMap.clear(); + + // Stop the timer + stopTimer(); + + // Shut down the connection pool + shutdownConnections(); + } + + /** + * Start the ScheduledThreadPoolExecutor that will handle the database writes. + */ + private synchronized void startTimer() { if(periodicTasksExecutor != null) { // Make sure the previous executor (if it exists) has been stopped periodicTasksExecutor.shutdown(); } periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); - } - private synchronized void deactivateMonitor() throws HealthMonitorException { - // Clear out the collected data - System.out.println(" Clearing hash map..."); - timingInfoMap.clear(); - - // Stop the timer - System.out.println(" Stopping the timer..."); + /** + * Stop the ScheduledThreadPoolExecutor to prevent further database writes. + */ + private synchronized void stopTimer() { if(periodicTasksExecutor != null) { periodicTasksExecutor.shutdown(); } - - // Shut down the connection pool - shutdownConnections(); } - - synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { - if (instance == null) { - instance = new ServicesHealthMonitor(); - } - return instance; - } - + + /** + * Called from the installer to set up the Health Monitor instance at startup. + * @throws HealthMonitorException + */ static synchronized void startUp() throws HealthMonitorException { - System.out.println("\nServicesHealthMonitor starting up"); getInstance(); } + /** + * Enabled/disable the health monitor. + * @param enabled true to enable the monitor, false to disable it + * @throws HealthMonitorException + */ static synchronized void setEnabled(boolean enabled) throws HealthMonitorException { - System.out.println("\nServicesHealthMonitor setting enabled to " + enabled + "(previous: " + isEnabled.get() + ")"); if(enabled == isEnabled.get()) { // The setting has not changed, so do nothing return; @@ -159,6 +212,16 @@ public class ServicesHealthMonitor { } } + /** + * Get a metric that will measure the time to execute a section of code. + * Call this before the section of code to be timed and then + * submit it afterward using submitTimingMetric(). + * This method is safe to call regardless of whether the Services Health + * Monitor is enabled. + * @param name A short but descriptive name describing the code being timed. + * This name will appear in the UI. + * @return The TimingMetric object + */ public static TimingMetric getTimingMetric(String name) { if(isEnabled.get()) { return new TimingMetric(name); @@ -166,6 +229,13 @@ public class ServicesHealthMonitor { return null; } + /** + * Submit the metric that was previously obtained through getTimingMetric(). + * Call this immediately after the section of code being timed. + * This method is safe to call regardless of whether the Services Health + * Monitor is enabled. + * @param metric The TimingMetric object obtained from getTimingMetric() + */ public static void submitTimingMetric(TimingMetric metric) { if(isEnabled.get() && (metric != null)) { metric.stopTiming(); @@ -173,52 +243,63 @@ public class ServicesHealthMonitor { getInstance().addTimingMetric(metric); } catch (HealthMonitorException ex) { // We don't want calling methods to have to check for exceptions, so just log it - logger.log(Level.SEVERE, "Error accessing services health monitor", ex); + logger.log(Level.SEVERE, "Error adding timing metric", ex); } } } - private void addTimingMetric(TimingMetric metric) { - try{ - synchronized(this) { - // There's a small check-then-act situation here where isEnabled - // may have changed before reaching this code, but it doesn't cause - // any errors to load a few extra entries into the map after disabling - // the monitor (they will be deleted if the monitor is re-enabled). - if(timingInfoMap.containsKey(metric.getName())) { - timingInfoMap.get(metric.getName()).addMetric(metric); - } else { - timingInfoMap.put(metric.getName(), new TimingInfo(metric)); - } + /** + * Add the timing metric data to the map. + * @param metric The metric to add. stopTiming() should already have been called. + */ + private void addTimingMetric(TimingMetric metric) throws HealthMonitorException { + + // Do as little as possible within the synchronized block to minimize + // blocking with multiple threads. + synchronized(this) { + // There's a small check-then-act situation here where isEnabled + // may have changed before reaching this code. This is fine - + // the map still exists and any extra data added after the monitor + // is disabled will be deleted if the monitor is re-enabled. This + // seems preferable to doing another check on isEnabled within + // the synchronized block. + if(timingInfoMap.containsKey(metric.getName())) { + timingInfoMap.get(metric.getName()).addMetric(metric); + } else { + timingInfoMap.put(metric.getName(), new TimingInfo(metric)); } - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error adding timing metric", ex); } } - // Make private once testing is done + // TODO: Make private once testing is done + /** + * Write the collected metrics to the database. + * @throws HealthMonitorException + */ void writeCurrentStateToDatabase() throws HealthMonitorException { - System.out.println("\nwriteCurrentStateToDatabase"); + logger.log(Level.INFO, "Writing health monitor metrics to database"); Map timingMapCopy; + + // Do as little as possible within the synchronized block since it will + // block threads attempting to record metrics. synchronized(this) { if(! isEnabled.get()) { return; } - // Make a shallow copy of the map. The map should be small - one entry - // per metric type. + // Make a shallow copy of the timing map. The map should be small - one entry + // per metric name. timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } - // Check if there's anything to report + // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { - System.out.println("No timing data to save"); return; } - // Debug + // TODO: Debug for(String name:timingMapCopy.keySet()){ TimingInfo info = timingMapCopy.get(name); long timestamp = System.currentTimeMillis(); @@ -226,6 +307,7 @@ public class ServicesHealthMonitor { "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); } + // Write to the database CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { throw new HealthMonitorException("Error getting database lock"); @@ -237,7 +319,7 @@ public class ServicesHealthMonitor { throw new HealthMonitorException("Error getting database connection"); } - //"INSERT INTO db_info (name, value) VALUES (?, ?)" + // Add timing metrics to the database String addTimingInfoSql = "INSERT INTO timing_data (name, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { @@ -272,13 +354,23 @@ public class ServicesHealthMonitor { } } + // TODO: debug synchronized void clearCurrentState() { timingInfoMap.clear(); } + /** + * Call during application closing - attempts to log any remaining entries. + */ static synchronized void close() { if(isEnabled.get()) { + // Stop the timer + try { + getInstance().stopTimer(); + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error shutting down timer", ex); + } // Write current data try { @@ -293,10 +385,10 @@ public class ServicesHealthMonitor { } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error shutting down connection pool", ex); } - } } + // TODO: debug synchronized void printCurrentState() { System.out.println("\nTiming Info Map:"); for(String name:timingInfoMap.keySet()) { @@ -305,11 +397,14 @@ public class ServicesHealthMonitor { } } - // Change to private after testing - boolean databaseExists() throws HealthMonitorException { - - System.out.println("\nChecking database existence"); - + // TODO: Change to private after testing + /** + * Check whether the health monitor database exists. + * Does not check the schema. + * @return true if the database exists, false otherwise + * @throws HealthMonitorException + */ + boolean databaseExists() throws HealthMonitorException { try { // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); @@ -321,7 +416,7 @@ public class ServicesHealthMonitor { System.out.println(" query: " + createCommand); rs = statement.executeQuery(createCommand); if(rs.next()) { - System.out.println(" Exists!"); + logger.log(Level.INFO, "Existing Services Health Monitor database found"); return true; } } finally { @@ -332,13 +427,15 @@ public class ServicesHealthMonitor { } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { throw new HealthMonitorException("Failed check for health monitor database", ex); } - System.out.println(" Does not exist"); return false; } + /** + * Create a new health monitor database. + * @throws HealthMonitorException + */ private void createDatabase() throws HealthMonitorException { try { - System.out.println("\nCreating database " + DATABASE_NAME); // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); Class.forName("org.postgresql.Driver"); //NON-NLS @@ -347,12 +444,13 @@ public class ServicesHealthMonitor { String createCommand = "CREATE DATABASE \"" + DATABASE_NAME + "\" OWNER \"" + db.getUserName() + "\""; //NON-NLS statement.execute(createCommand); } + logger.log(Level.INFO, "Created new health monitor database " + DATABASE_NAME); } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { throw new HealthMonitorException("Failed to delete health monitor database", ex); } } - + // TODO: At least make private /** * Delete the current health monitor database (for testing only) * Make private after test @@ -371,18 +469,16 @@ public class ServicesHealthMonitor { logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); } } - + /** * Setup a connection pool for db connections. - * + * @throws HealthMonitorException */ private void setupConnectionPool() throws HealthMonitorException { try { CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); connectionPool = new BasicDataSource(); - //connectionPool.setUsername(db.getUserName()); - //connectionPool.setPassword(db.getPassword()); connectionPool.setDriverClassName("org.postgresql.Driver"); StringBuilder connectionURL = new StringBuilder(); @@ -398,7 +494,7 @@ public class ServicesHealthMonitor { connectionPool.setPassword(db.getPassword()); // tweak pool configuration - connectionPool.setInitialSize(5); // start with 5 connections + connectionPool.setInitialSize(2); // start with 2 connections connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections connectionPool.setValidationQuery("SELECT version()"); } catch (UserPreferencesException ex) { @@ -406,7 +502,11 @@ public class ServicesHealthMonitor { } } - public void shutdownConnections() throws HealthMonitorException { + /** + * Shut down the connection pool + * @throws HealthMonitorException + */ + private void shutdownConnections() throws HealthMonitorException { try { synchronized(this) { if(connectionPool != null){ @@ -419,6 +519,12 @@ public class ServicesHealthMonitor { } } + /** + * Get a database connection. + * Sets up the connection pool if needed. + * @return The Connection object + * @throws HealthMonitorException + */ private Connection connect() throws HealthMonitorException { synchronized (this) { if (connectionPool == null) { @@ -433,12 +539,17 @@ public class ServicesHealthMonitor { } } + /** + * Initialize the database. + * @throws HealthMonitorException + */ private void initializeDatabaseSchema() throws HealthMonitorException { Connection conn = connect(); if(conn == null) { throw new HealthMonitorException("Error getting database connection"); } + // TODO: transaction try (Statement statement = conn.createStatement()) { StringBuilder createTimingTable = new StringBuilder(); createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); @@ -474,6 +585,10 @@ public class ServicesHealthMonitor { } } + /** + * The task called by the ScheduledThreadPoolExecutor to handle + * the database writes. + */ private final class DatabaseWriteTask implements Runnable { /** @@ -482,14 +597,19 @@ public class ServicesHealthMonitor { @Override public void run() { try { - System.out.println("\nTimer up - writing to DB"); getInstance().writeCurrentStateToDatabase(); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Unexpected exception in DatabaseWriteTask", ex); //NON-NLS + } catch (HealthMonitorException ex) { + logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS } } } + /** + * Get an exclusive lock for the health monitor database. + * Acquire this before creating, initializing, or updating the database schema. + * @return The lock + * @throws HealthMonitorException + */ private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ try { String databaseNodeName = DATABASE_NAME; @@ -504,6 +624,12 @@ public class ServicesHealthMonitor { } } + /** + * Get an shared lock for the health monitor database. + * Acquire this before database reads or writes. + * @return The lock + * @throws HealthMonitorException + */ private CoordinationService.Lock getSharedDbLock() throws HealthMonitorException{ try { String databaseNodeName = DATABASE_NAME; @@ -518,11 +644,19 @@ public class ServicesHealthMonitor { } } + /** + * Internal class for collecting timing metrics. + * Instead of storing each TimingMetric, we only store the min and max + * seen and the number of metrics and total duration to compute the average + * later. + * One TimingInfo instance should be created per metric name, and + * additional timing metrics will be added to it. + */ private class TimingInfo { - private long count; - private long sum; - private long max; - private long min; + private long count; // Number of metrics collected + private long sum; // Sum of the durations collected (nanoseconds) + private long max; // Maximum value found (nanoseconds) + private long min; // Minimum value found (nanoseconds) TimingInfo(TimingMetric metric) throws HealthMonitorException { count = 1; @@ -531,35 +665,63 @@ public class ServicesHealthMonitor { min = metric.getDuration(); } + /** + * Add a new TimingMetric to an existing TimingInfo object. + * This is called in a synchronized block for almost all new + * TimingMetric objects, so do as little processing here as possible. + * @param metric The new metric + * @throws HealthMonitorException Will be thrown if the metric hasn't been stopped + */ void addMetric(TimingMetric metric) throws HealthMonitorException { + + // Keep track of needed info to calculate the average count++; sum += metric.getDuration(); + // Check if this is the longest duration seen if(max < metric.getDuration()) { max = metric.getDuration(); } + // Check if this is the lowest duration seen if(min > metric.getDuration()) { min = metric.getDuration(); } } + /** + * Get the average duration + * @return average duration (nanoseconds) + */ long getAverage() { return sum / count; } + /** + * Get the maximum duration + * @return maximum duration (nanoseconds) + */ long getMax() { return max; } + /** + * Get the minimum duration + * @return minimum duration (nanoseconds) + */ long getMin() { return min; } + /** + * Get the total number of metrics collected + * @return number of metrics collected + */ long getCount() { return count; } + // TODO: debug void print() { System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java index 431e585710..d94dfd7161 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -1,12 +1,25 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.healthmonitor; /** - * + * Used to calculate and report timing metrics. */ public class TimingMetric { @@ -36,6 +49,12 @@ public class TimingMetric { return name; } + /** + * Get the duration of the metric. Will throw an exception if the + * metric has not been stopped. + * @return how long the metric was running (nanoseconds) + * @throws HealthMonitorException + */ long getDuration() throws HealthMonitorException { if (duration != null) { return duration; From 7176c6edf1ee844681687048e0d52afcd4ca228c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 5 Apr 2018 12:05:11 -0400 Subject: [PATCH 004/100] 3610 initial swapping of jtables for outlineviews on AutoIngestDashboard --- Experimental/nbproject/project.xml | 16 + .../autoingest/AutoIngestDashboard.form | 58 +- .../autoingest/AutoIngestDashboard.java | 862 +++++++++--------- .../autoingest/AutoIngestJobsPanel.form | 18 + .../autoingest/AutoIngestJobsPanel.java | 173 ++++ .../autoingest/AutoIngestNode.java | 134 +++ .../experimental/autoingest/Bundle.properties | 3 - 7 files changed, 767 insertions(+), 497 deletions(-) create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml index dbd5a31c4c..54e7d881b2 100644 --- a/Experimental/nbproject/project.xml +++ b/Experimental/nbproject/project.xml @@ -33,6 +33,14 @@ 1.49.1 + + org.netbeans.swing.outline + + + + 1.34.1 + + org.openide.awt @@ -49,6 +57,14 @@ 7.41.1 + + org.openide.explorer + + + + 6.62.1 + + org.openide.filesystems diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 9d2a343f56..68a1bb656d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -73,7 +73,7 @@ - + @@ -98,27 +98,19 @@ + + + + + + + + - - - - - - - - - - - - - - - - @@ -126,22 +118,6 @@ - - - - - - - - - - - - - - - - @@ -149,22 +125,6 @@ - - - - - - - - - - - - - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 3389a64a03..fb5abdc686 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -35,18 +35,25 @@ import java.beans.PropertyChangeListener; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import javax.swing.JPanel; import javax.swing.JTable; +import javax.swing.ListSelectionModel; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; import javax.swing.table.TableColumn; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer; @@ -82,11 +89,14 @@ final class AutoIngestDashboard extends JPanel implements Observer { private static final int COMPLETED_TIME_COL_MAX_WIDTH = 2000; private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName()); - private final DefaultTableModel pendingTableModel; - private final DefaultTableModel runningTableModel; - private final DefaultTableModel completedTableModel; + // private final DefaultTableModel pendingTableModel; +// private final DefaultTableModel runningTableModel; +// private final DefaultTableModel completedTableModel; private AutoIngestMonitor autoIngestMonitor; - + private AutoIngestJobsPanel pendingJobsPanel; + private AutoIngestJobsPanel runningJobsPanel; + private AutoIngestJobsPanel finishedJobsPanel; + /** * Maintain a mapping of each service to it's last status update. */ @@ -115,36 +125,53 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ private AutoIngestDashboard() { this.statusByService = new ConcurrentHashMap<>(); - - pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - - runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); - - completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); +// pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); +// runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); +// completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); - initPendingJobsTable(); - initRunningJobsTable(); - initCompletedJobsTable(); - + // initPendingJobsTable(); +// initRunningJobsTable(); +// initCompletedJobsTable(); + pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.PENDING_JOB); + pendingJobsPanel.setSize(pendingScrollPane.getSize()); + pendingScrollPane.add(pendingJobsPanel); + pendingScrollPane.setViewportView(pendingJobsPanel); + pendingJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { + System.out.println("SELECTION HAPPENED PENDING WJS-TODO"); + }); + runningJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.RUNNING_JOB); + runningJobsPanel.setSize(runningScrollPane.getSize()); + runningScrollPane.add(runningJobsPanel); + runningScrollPane.setViewportView(runningJobsPanel); + runningJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { + System.out.println("SELECTION HAPPENED RUNNING WJS-TODO"); + }); + finishedJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.COMPLETED_JOB); + finishedJobsPanel.setSize(completedScrollPane.getSize()); + completedScrollPane.add(finishedJobsPanel); + completedScrollPane.setViewportView(finishedJobsPanel); + finishedJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { + System.out.println("SELECTION HAPPENED COMPLETED WJS-TODO"); + }); /* * Must set this flag, otherwise pop up menus don't close properly. */ UIManager.put("PopupMenu.consumeEventOnClose", false); } - + /** * Update status of the services on the dashboard */ private void displayServicesStatus() { - tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", - statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), - statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), - statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", + statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), statusByService.get(ServicesMonitor.Service.MESSAGING.toString()))); String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0 @@ -162,7 +189,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ private void setServicesStatusMessage() { new SwingWorker() { - + @Override protected Void doInBackground() throws Exception { statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE)); @@ -202,233 +229,231 @@ final class AutoIngestDashboard extends JPanel implements Observer { }.execute(); } - /** - * Sets up the JTable that presents a view of the pending jobs queue for an - * auto ingest cluster. - */ - private void initPendingJobsTable() { - /* - * Remove some of the jobs table model columns from the JTable. This - * does not remove the columns from the model, just from this table. - */ - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - - /* - * Set up a column to display the cases associated with the jobs. - */ - TableColumn column; - column = pendingTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the data sources associated with the jobs. - */ - column = pendingTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the create times of the jobs. - */ - column = pendingTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); - column.setCellRenderer(new LongDateCellRenderer()); - column.setMinWidth(TIME_COL_MIN_WIDTH); - column.setMaxWidth(TIME_COL_MAX_WIDTH); - column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - column.setWidth(TIME_COL_PREFERRED_WIDTH); - - column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()); - column.setCellRenderer(new PrioritizedIconCellRenderer()); - column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); - column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); - column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); - /* - * Allow sorting when a column header is clicked. - */ - pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel)); - - /* - * Create a row selection listener to enable/disable the Prioritize - * button. - */ - pendingTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { - if (e.getValueIsAdjusting()) { - return; - } - int row = pendingTable.getSelectedRow(); - - boolean enablePrioritizeButtons = false; - boolean enableDeprioritizeButtons = false; - if (row >= 0 && row < pendingTable.getRowCount()) { - enablePrioritizeButtons = true; - enableDeprioritizeButtons = (Integer) pendingTableModel.getValueAt(row, JobsTableModelColumns.PRIORITY.ordinal()) > 0; - } - this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); - this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); - this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); - this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); - }); - } - - /** - * Sets up the JTable that presents a view of the running jobs list for an - * auto ingest cluster. - */ - private void initRunningJobsTable() { - /* - * Remove some of the jobs table model columns from the JTable. This - * does not remove the columns from the model, just from this table. - */ - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); - /* - * Set up a column to display the cases associated with the jobs. - */ - TableColumn column; - column = runningTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the image folders associated with the - * jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(GENERIC_COL_MAX_WIDTH); - column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the host names of the cluster nodes - * processing the jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()); - column.setMinWidth(NAME_COL_MIN_WIDTH); - column.setMaxWidth(NAME_COL_MAX_WIDTH); - column.setPreferredWidth(NAME_COL_PREFERRED_WIDTH); - column.setWidth(NAME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the ingest activities associated with the - * jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()); - column.setMinWidth(STAGE_COL_MIN_WIDTH); - column.setMaxWidth(STAGE_COL_MAX_WIDTH); - column.setPreferredWidth(STAGE_COL_PREFERRED_WIDTH); - column.setWidth(STAGE_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the ingest activity times associated with - * the jobs. - */ - column = runningTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()); - column.setCellRenderer(new DurationCellRenderer()); - column.setMinWidth(GENERIC_COL_MIN_WIDTH); - column.setMaxWidth(STAGE_TIME_COL_MAX_WIDTH); - column.setPreferredWidth(STAGE_TIME_COL_MIN_WIDTH); - column.setWidth(STAGE_TIME_COL_MIN_WIDTH); - - /* - * Prevent sorting when a column header is clicked. - */ - runningTable.setAutoCreateRowSorter(false); - } - - /** - * Sets up the JTable that presents a view of the completed jobs list for an - * auto ingest cluster. - */ - private void initCompletedJobsTable() { - /* - * Remove some of the jobs table model columns from the JTable. This - * does not remove the columns from the model, just from this table. - */ - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); - completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); - /* - * Set up a column to display the cases associated with the jobs. - */ - TableColumn column; - column = completedTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); - column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); - column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); - column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the image folders associated with the - * jobs. - */ - column = completedTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); - column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); - column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); - column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the create times of the jobs. - */ - column = completedTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); - column.setCellRenderer(new LongDateCellRenderer()); - column.setMinWidth(TIME_COL_MIN_WIDTH); - column.setMaxWidth(TIME_COL_MAX_WIDTH); - column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - column.setWidth(TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the completed times of the jobs. - */ - column = completedTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()); - column.setCellRenderer(new LongDateCellRenderer()); - column.setMinWidth(TIME_COL_MIN_WIDTH); - column.setMaxWidth(TIME_COL_MAX_WIDTH); - column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); - column.setWidth(TIME_COL_PREFERRED_WIDTH); - - /* - * Set up a column to display the statuses of the jobs, with a cell - * renderer that will choose an icon to represent the job status. - */ - column = completedTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()); - column.setCellRenderer(new StatusIconCellRenderer()); - column.setMinWidth(STATUS_COL_MIN_WIDTH); - column.setMaxWidth(STATUS_COL_MAX_WIDTH); - column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); - column.setWidth(STATUS_COL_PREFERRED_WIDTH); - /* - * Allow sorting when a column header is clicked. - */ - completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel)); - } - +// /** +// * Sets up the JTable that presents a view of the pending jobs queue for an +// * auto ingest cluster. +// */ +// private void initPendingJobsTable() { +// /* +// * Remove some of the jobs table model columns from the JTable. This +// * does not remove the columns from the model, just from this table. +// */ +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); +// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); +// +// /* +// * Set up a column to display the cases associated with the jobs. +// */ +// TableColumn column; +// column = pendingTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); +// column.setMinWidth(GENERIC_COL_MIN_WIDTH); +// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); +// column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); +// column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the data sources associated with the jobs. +// */ +// column = pendingTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); +// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); +// column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); +// column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the create times of the jobs. +// */ +// column = pendingTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); +// column.setCellRenderer(new LongDateCellRenderer()); +// column.setMinWidth(TIME_COL_MIN_WIDTH); +// column.setMaxWidth(TIME_COL_MAX_WIDTH); +// column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); +// column.setWidth(TIME_COL_PREFERRED_WIDTH); +// +// column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()); +// column.setCellRenderer(new PrioritizedIconCellRenderer()); +// column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); +// column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); +// column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); +// /* +// * Allow sorting when a column header is clicked. +// */ +// pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel)); +// +// /* +// * Create a row selection listener to enable/disable the Prioritize +// * button. +// */ +// pendingTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { +// if (e.getValueIsAdjusting()) { +// return; +// } +// int row = pendingTable.getSelectedRow(); +// +// boolean enablePrioritizeButtons = false; +// boolean enableDeprioritizeButtons = false; +// if (row >= 0 && row < pendingTable.getRowCount()) { +// enablePrioritizeButtons = true; +// enableDeprioritizeButtons = (Integer) pendingTableModel.getValueAt(row, JobsTableModelColumns.PRIORITY.ordinal()) > 0; +// } +// this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); +// this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); +// this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); +// this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); +// }); +// } +// /** +// * Sets up the JTable that presents a view of the running jobs list for an +// * auto ingest cluster. +// */ +// private void initRunningJobsTable() { +// /* +// * Remove some of the jobs table model columns from the JTable. This +// * does not remove the columns from the model, just from this table. +// */ +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); +// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); +// /* +// * Set up a column to display the cases associated with the jobs. +// */ +// TableColumn column; +// column = runningTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); +// column.setMinWidth(GENERIC_COL_MIN_WIDTH); +// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); +// column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); +// column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the image folders associated with the +// * jobs. +// */ +// column = runningTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); +// column.setMinWidth(GENERIC_COL_MIN_WIDTH); +// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); +// column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); +// column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the host names of the cluster nodes +// * processing the jobs. +// */ +// column = runningTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()); +// column.setMinWidth(NAME_COL_MIN_WIDTH); +// column.setMaxWidth(NAME_COL_MAX_WIDTH); +// column.setPreferredWidth(NAME_COL_PREFERRED_WIDTH); +// column.setWidth(NAME_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the ingest activities associated with the +// * jobs. +// */ +// column = runningTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()); +// column.setMinWidth(STAGE_COL_MIN_WIDTH); +// column.setMaxWidth(STAGE_COL_MAX_WIDTH); +// column.setPreferredWidth(STAGE_COL_PREFERRED_WIDTH); +// column.setWidth(STAGE_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the ingest activity times associated with +// * the jobs. +// */ +// column = runningTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()); +// column.setCellRenderer(new DurationCellRenderer()); +// column.setMinWidth(GENERIC_COL_MIN_WIDTH); +// column.setMaxWidth(STAGE_TIME_COL_MAX_WIDTH); +// column.setPreferredWidth(STAGE_TIME_COL_MIN_WIDTH); +// column.setWidth(STAGE_TIME_COL_MIN_WIDTH); +// +// /* +// * Prevent sorting when a column header is clicked. +// */ +// runningTable.setAutoCreateRowSorter(false); +// } +// /** +// * Sets up the JTable that presents a view of the completed jobs list for an +// * auto ingest cluster. +// */ +// private void initCompletedJobsTable() { +// /* +// * Remove some of the jobs table model columns from the JTable. This +// * does not remove the columns from the model, just from this table. +// */ +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); +// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); +// /* +// * Set up a column to display the cases associated with the jobs. +// */ +// TableColumn column; +// column = completedTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); +// column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); +// column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); +// column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); +// column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the image folders associated with the +// * jobs. +// */ +// column = completedTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); +// column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); +// column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); +// column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); +// column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the create times of the jobs. +// */ +// column = completedTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); +// column.setCellRenderer(new LongDateCellRenderer()); +// column.setMinWidth(TIME_COL_MIN_WIDTH); +// column.setMaxWidth(TIME_COL_MAX_WIDTH); +// column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); +// column.setWidth(TIME_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the completed times of the jobs. +// */ +// column = completedTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()); +// column.setCellRenderer(new LongDateCellRenderer()); +// column.setMinWidth(TIME_COL_MIN_WIDTH); +// column.setMaxWidth(TIME_COL_MAX_WIDTH); +// column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); +// column.setWidth(TIME_COL_PREFERRED_WIDTH); +// +// /* +// * Set up a column to display the statuses of the jobs, with a cell +// * renderer that will choose an icon to represent the job status. +// */ +// column = completedTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()); +// column.setCellRenderer(new StatusIconCellRenderer()); +// column.setMinWidth(STATUS_COL_MIN_WIDTH); +// column.setMaxWidth(STATUS_COL_MAX_WIDTH); +// column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); +// column.setWidth(STATUS_COL_PREFERRED_WIDTH); +// /* +// * Allow sorting when a column header is clicked. +// */ +// completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel)); +// } +// /** * Starts up the auto ingest monitor and adds this panel as an observer, * subscribes to services monitor events and starts a task to populate the @@ -437,10 +462,10 @@ final class AutoIngestDashboard extends JPanel implements Observer { private void startUp() throws AutoIngestMonitor.AutoIngestMonitorException { PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> { - + String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString(); String status = evt.getNewValue().toString(); - + if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) { status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); LOGGER.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS @@ -450,23 +475,23 @@ final class AutoIngestDashboard extends JPanel implements Observer { } else { LOGGER.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS } - + // if the status update is for an existing service who's status hasn't changed - do nothing. if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) { return; } - + statusByService.put(serviceDisplayName, status); displayServicesStatus(); }; - + // Subscribe to all multi-user services in order to display their status Set servicesList = new HashSet<>(); servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()); - servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); + servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); servicesList.add(ServicesMonitor.Service.MESSAGING.toString()); ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener); - + autoIngestMonitor = new AutoIngestMonitor(); autoIngestMonitor.addObserver(this); autoIngestMonitor.startUp(); @@ -474,7 +499,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override public void update(Observable observable, Object arg) { - EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); + // EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); } /** @@ -484,53 +509,53 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param jobsSnapshot The jobs snapshot. */ private void refreshTables(JobsSnapshot jobsSnapshot) { - List pendingJobs = jobsSnapshot.getPendingJobs(); - List runningJobs = jobsSnapshot.getRunningJobs(); - List completedJobs = jobsSnapshot.getCompletedJobs(); - pendingJobs.sort(new AutoIngestJob.PriorityComparator()); - runningJobs.sort(new AutoIngestJob.DataSourceFileNameComparator()); - completedJobs.sort(new AutoIngestJob.CompletedDateDescendingComparator()); - refreshTable(pendingJobs, pendingTable, pendingTableModel); - refreshTable(runningJobs, runningTable, runningTableModel); - refreshTable(completedJobs, completedTable, completedTableModel); - } - - /** - * Reloads the table model for an auto ingest jobs table and refreshes the - * JTable that uses the model. - * - * @param jobs The list of auto ingest jobs. - * @param tableModel The table model. - * @param comparator An optional comparator (may be null) for sorting the - * table model. - */ - private void refreshTable(List jobs, JTable table, DefaultTableModel tableModel) { - try { - Path currentRow = getSelectedEntry(table, tableModel); - tableModel.setRowCount(0); - for (AutoIngestJob job : jobs) { - AutoIngestJob.StageDetails status = job.getProcessingStageDetails(); - tableModel.addRow(new Object[]{ - job.getManifest().getCaseName(), // CASE - job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME - job.getManifest().getDateFileCreated(), // CREATED_TIME - job.getProcessingStageStartDate(), // STARTED_TIME - job.getCompletedDate(), // COMPLETED_TIME - status.getDescription(), // STAGE - job.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, // STATUS - ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME - job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH - job.getManifest().getFilePath(), // MANIFEST_FILE_PATH - job.getPriority(), // PRIORITY - job - }); - } - setSelectedEntry(table, tableModel, currentRow); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Error refreshing table " + table.toString(), ex); - } + pendingJobsPanel.refresh(jobsSnapshot); + runningJobsPanel.refresh(jobsSnapshot); + finishedJobsPanel.refresh(jobsSnapshot); + // List runningJobs = jobsSnapshot.getRunningJobs(); + // List completedJobs = jobsSnapshot.getCompletedJobs(); + // runningJobs.sort(new AutoIngestJob.DataSourceFileNameComparator()); + // completedJobs.sort(new AutoIngestJob.CompletedDateDescendingComparator()); + // refreshTable(pendingJobs, pendingTable, pendingTableModel); +// refreshTable(runningJobs, runningTable, runningTableModel); + // refreshTable(completedJobs, completedTable, completedTableModel); } +// /** +// * Reloads the table model for an auto ingest jobs table and refreshes the +// * JTable that uses the model. +// * +// * @param jobs The list of auto ingest jobs. +// * @param tableModel The table model. +// * @param comparator An optional comparator (may be null) for sorting the +// * table model. +// */ +// private void refreshTable(List jobs, JTable table, DefaultTableModel tableModel) { +// try { +// Path currentRow = getSelectedEntry(table, tableModel); +// tableModel.setRowCount(0); +// for (AutoIngestJob job : jobs) { +// AutoIngestJob.StageDetails status = job.getProcessingStageDetails(); +// tableModel.addRow(new Object[]{ +// job.getManifest().getCaseName(), // CASE +// job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME +// job.getManifest().getDateFileCreated(), // CREATED_TIME +// job.getProcessingStageStartDate(), // STARTED_TIME +// job.getCompletedDate(), // COMPLETED_TIME +// status.getDescription(), // STAGE +// job.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, // STATUS +// ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME +// job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH +// job.getManifest().getFilePath(), // MANIFEST_FILE_PATH +// job.getPriority(), // PRIORITY +// job +// }); +// } +// setSelectedEntry(table, tableModel, currentRow); +// } catch (Exception ex) { +// LOGGER.log(Level.SEVERE, "Error refreshing table " + table.toString(), ex); +// } +// } /** * Gets a path representing the current selection in a table. * @@ -629,7 +654,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { JOB.getColumnHeader() }; }; - /** * A task that refreshes the UI components on this panel to reflect a * snapshot of the pending, running and completed auto ingest jobs lists of @@ -698,11 +722,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { jButton1 = new javax.swing.JButton(); pendingScrollPane = new javax.swing.JScrollPane(); - pendingTable = new javax.swing.JTable(); runningScrollPane = new javax.swing.JScrollPane(); - runningTable = new javax.swing.JTable(); completedScrollPane = new javax.swing.JScrollPane(); - completedTable = new javax.swing.JTable(); lbPending = new javax.swing.JLabel(); lbRunning = new javax.swing.JLabel(); lbCompleted = new javax.swing.JLabel(); @@ -717,56 +738,10 @@ final class AutoIngestDashboard extends JPanel implements Observer { org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N - pendingTable.setModel(pendingTableModel); - pendingTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.pendingTable.toolTipText")); // NOI18N - pendingTable.setRowHeight(20); - pendingTable.setSelectionModel(new DefaultListSelectionModel() { - private static final long serialVersionUID = 1L; - @Override - public void setSelectionInterval(int index0, int index1) { - if (index0 == pendingTable.getSelectedRow()) { - pendingTable.clearSelection(); - } else { - super.setSelectionInterval(index0, index1); - } - } - }); - pendingTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - pendingScrollPane.setViewportView(pendingTable); - - runningTable.setModel(runningTableModel); - runningTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.runningTable.toolTipText")); // NOI18N - runningTable.setRowHeight(20); - runningTable.setSelectionModel(new DefaultListSelectionModel() { - private static final long serialVersionUID = 1L; - @Override - public void setSelectionInterval(int index0, int index1) { - if (index0 == runningTable.getSelectedRow()) { - runningTable.clearSelection(); - } else { - super.setSelectionInterval(index0, index1); - } - } - }); - runningTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - runningScrollPane.setViewportView(runningTable); - - completedTable.setModel(completedTableModel); - completedTable.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.completedTable.toolTipText")); // NOI18N - completedTable.setRowHeight(20); - completedTable.setSelectionModel(new DefaultListSelectionModel() { - private static final long serialVersionUID = 1L; - @Override - public void setSelectionInterval(int index0, int index1) { - if (index0 == completedTable.getSelectedRow()) { - completedTable.clearSelection(); - } else { - super.setSelectionInterval(index0, index1); - } - } - }); - completedTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); - completedScrollPane.setViewportView(completedTable); + pendingScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + pendingScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + pendingScrollPane.setOpaque(false); + pendingScrollPane.setPreferredSize(new java.awt.Dimension(2, 215)); lbPending.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(lbPending, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.lbPending.text")); // NOI18N @@ -843,7 +818,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(pendingScrollPane) + .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() @@ -883,7 +858,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(1, 1, 1) - .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lbRunning) .addGap(1, 1, 1) @@ -920,38 +895,38 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."}) private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.prioritizeJob(job); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath()); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } +// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { +// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); +// AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); +// JobsSnapshot jobsSnapshot; +// try { +// jobsSnapshot = autoIngestMonitor.prioritizeJob(job); +// refreshTables(jobsSnapshot); +// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { +// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath()); +// LOGGER.log(Level.SEVERE, errorMessage, ex); +// MessageNotifyUtil.Message.error(errorMessage); +// } +// setCursor(Cursor.getDefaultCursor()); +// } }//GEN-LAST:event_prioritizeJobButtonActionPerformed @Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."}) private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } +// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { +// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); +// String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); +// JobsSnapshot jobsSnapshot; +// try { +// jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName); +// refreshTables(jobsSnapshot); +// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { +// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName); +// LOGGER.log(Level.SEVERE, errorMessage, ex); +// MessageNotifyUtil.Message.error(errorMessage); +// } +// setCursor(Cursor.getDefaultCursor()); +// } }//GEN-LAST:event_prioritizeCaseButtonActionPerformed private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed @@ -960,44 +935,43 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."}) private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.deprioritizeJob(job); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath()); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } +// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { +// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); +// AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); +// JobsSnapshot jobsSnapshot; +// try { +// jobsSnapshot = autoIngestMonitor.deprioritizeJob(job); +// refreshTables(jobsSnapshot); +// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { +// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath()); +// LOGGER.log(Level.SEVERE, errorMessage, ex); +// MessageNotifyUtil.Message.error(errorMessage); +// } +// setCursor(Cursor.getDefaultCursor()); +// } }//GEN-LAST:event_deprioritizeJobButtonActionPerformed @Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."}) private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed - if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } +// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { +// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); +// String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); +// JobsSnapshot jobsSnapshot; +// try { +// jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName); +// refreshTables(jobsSnapshot); +// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { +// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName); +// LOGGER.log(Level.SEVERE, errorMessage, ex); +// MessageNotifyUtil.Message.error(errorMessage); +// } +// setCursor(Cursor.getDefaultCursor()); +// } }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton clusterMetricsButton; private javax.swing.JScrollPane completedScrollPane; - private javax.swing.JTable completedTable; private javax.swing.JButton deprioritizeCaseButton; private javax.swing.JButton deprioritizeJobButton; private javax.swing.JButton jButton1; @@ -1006,42 +980,40 @@ final class AutoIngestDashboard extends JPanel implements Observer { private javax.swing.JLabel lbRunning; private javax.swing.JLabel lbServicesStatus; private javax.swing.JScrollPane pendingScrollPane; - private javax.swing.JTable pendingTable; private javax.swing.JButton prioritizeCaseButton; private javax.swing.JButton prioritizeJobButton; private javax.swing.JButton refreshButton; private javax.swing.JScrollPane runningScrollPane; - private javax.swing.JTable runningTable; private javax.swing.JTextField tbServicesStatusMessage; // End of variables declaration//GEN-END:variables - private class AutoIngestTableModel extends DefaultTableModel { - - private static final long serialVersionUID = 1L; - - private AutoIngestTableModel(String[] headers, int i) { - super(headers, i); - } - - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - @Override - public Class getColumnClass(int columnIndex) { - if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) { - return Integer.class; - } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal() - || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal() - || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal() - || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) { - return Date.class; - } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) { - return Boolean.class; - } else { - return super.getColumnClass(columnIndex); - } - } - } +// private class AutoIngestTableModel extends DefaultTableModel { +// +// private static final long serialVersionUID = 1L; +// +// private AutoIngestTableModel(String[] headers, int i) { +// super(headers, i); +// } +// +// @Override +// public boolean isCellEditable(int row, int column) { +// return false; +// } +// +// @Override +// public Class getColumnClass(int columnIndex) { +// if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) { +// return Integer.class; +// } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal() +// || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal() +// || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal() +// || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) { +// return Date.class; +// } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) { +// return Boolean.class; +// } else { +// return super.getColumnClass(columnIndex); +// } +// } +// } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form new file mode 100644 index 0000000000..bde4db4324 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.form @@ -0,0 +1,18 @@ + + +
+ + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java new file mode 100644 index 0000000000..e37f419802 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -0,0 +1,173 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionListener; +import javax.swing.SwingWorker; +import org.netbeans.swing.outline.DefaultOutlineModel; +import org.netbeans.swing.outline.Outline; +import org.openide.explorer.ExplorerManager; +import org.openide.util.Exceptions; +import org.sleuthkit.autopsy.datamodel.EmptyNode; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.AutoIngestJobType; + +/** + * + * @author wschaefer + */ +final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider { + + private static final long serialVersionUID = 1L; + private final org.openide.explorer.view.OutlineView outlineView; + private final Outline outline; + private ExplorerManager explorerManager; + private JobListWorker jobListWorker; + private final AutoIngestJobType type; + + /** + * Creates new form PendingJobsPanel + */ + AutoIngestJobsPanel(AutoIngestJobType jobType) { + initComponents(); + type = jobType; + outlineView = new org.openide.explorer.view.OutlineView(); + outline = outlineView.getOutline(); + customize(); + } + + void customize() { + + switch (type) { + case PENDING_JOB: + outlineView.setPropertyColumns(Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), + Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), + Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text()); + break; + case RUNNING_JOB: + outlineView.setPropertyColumns("Stage", "Stage"); + break; + case COMPLETED_JOB: + outlineView.setPropertyColumns(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), + "Date Started", "Date Started", + "Date Completed", "Date Completed", + "Status", "Status"); + break; + default: + } + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestNode_col1_text()); + outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + outline.setRootVisible(false); + outline.setColumnSorted(0, false, 1); + if (null == explorerManager) { + explorerManager = new ExplorerManager(); + + } + outline.setRowSelectionAllowed(false); + + // outlineView.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + add(outlineView, java.awt.BorderLayout.CENTER); + + } + + @Override + public void setSize(Dimension d) { + super.setSize(d); + outlineView.setMaximumSize(new Dimension(400, 100)); + outline.setPreferredScrollableViewportSize(new Dimension(400, 100)); + } + + void addListSelectionListener(ListSelectionListener listener) { + outline.getSelectionModel().addListSelectionListener(listener); + } + + @Override + public ExplorerManager getExplorerManager() { + return explorerManager; + } + + void refresh(AutoIngestMonitor.JobsSnapshot jobsSnapshot) { + if (jobListWorker == null || jobListWorker.isDone()) { + outline.setRowSelectionAllowed(false); +// EmptyNode emptyNode = new EmptyNode("Refreshing..."); +// explorerManager.setRootContext(emptyNode); + jobListWorker = new JobListWorker(jobsSnapshot, type); + jobListWorker.execute(); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new java.awt.BorderLayout()); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + /** + * Swingworker to fetch the updated List of cases in a background thread + */ + private class JobListWorker extends SwingWorker, Void> { + + private final AutoIngestMonitor.JobsSnapshot jobsSnapshot; + private final AutoIngestJobType jobType; + + JobListWorker(AutoIngestMonitor.JobsSnapshot snapshot, AutoIngestJobType type) { + jobsSnapshot = snapshot; + jobType = type; + } + + @Override + protected List doInBackground() throws Exception { + List jobs; + switch (jobType) { + case PENDING_JOB: + jobs = jobsSnapshot.getPendingJobs(); + break; + case RUNNING_JOB: + jobs = jobsSnapshot.getRunningJobs(); + break; + case COMPLETED_JOB: + jobs = jobsSnapshot.getCompletedJobs(); + break; + default: + jobs = new ArrayList<>(); + + } + jobs.sort(new AutoIngestJob.PriorityComparator()); + return jobs; + } + + @Override + protected void done() { + try { + List jobs = get(); + EventQueue.invokeLater(() -> { + AutoIngestNode autoIngestNode = new AutoIngestNode(jobs, jobType); + explorerManager.setRootContext(autoIngestNode); + outline.setRowSelectionAllowed(true); + }); + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java new file mode 100644 index 0000000000..e436ab03bc --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java @@ -0,0 +1,134 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; + +final class AutoIngestNode extends AbstractNode { + + @Messages({ + "AutoIngestNode.col1.text=Case Name", + "AutoIngestNode.col2.text=File Name", + "AutoIngestNode.col3.text=Date Created", + "AutoIngestNode.col4.text=Priority" + }) + + AutoIngestNode(List jobs, AutoIngestJobType type) { + super(Children.create(new AutoIngestNodeChildren(jobs, type), true)); + } + + static class AutoIngestNodeChildren extends ChildFactory { + + AutoIngestJobType autoIngestJobType; + private final List jobs; + + AutoIngestNodeChildren(List jobList, AutoIngestJobType type) { + this.jobs = jobList; + autoIngestJobType = type; + } + + @Override + protected boolean createKeys(List list) { + if (jobs != null && jobs.size() > 0) { + list.addAll(jobs); + } + return true; + } + + @Override + protected Node createNodeForKey(AutoIngestJob key) { + return new PendingJobNode(key, autoIngestJobType); + } + + } + + /** + * A node which represents a single multi user case. + */ + static final class PendingJobNode extends AbstractNode { + + private final AutoIngestJob autoIngestJob; + private final String caseName; + private final Path fileName; + private final Date dateCreated; + private final int priority; + private final AutoIngestJobType jobType; + + PendingJobNode(AutoIngestJob job, AutoIngestJobType type) { + super(Children.LEAF); + jobType = type; + autoIngestJob = job; + caseName = autoIngestJob.getManifest().getCaseName(); + fileName = autoIngestJob.getManifest().getDataSourcePath().getFileName(); + dateCreated = autoIngestJob.getManifest().getDateFileCreated(); + priority = autoIngestJob.getPriority(); + super.setName(caseName); + setName(caseName); + setDisplayName(caseName); + } + + @Override + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), caseName)); + switch (jobType) { + case PENDING_JOB: + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), fileName.toString())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), dateCreated.toString())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Integer.toString(priority))); + break; + case RUNNING_JOB: + AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); + ss.put(new NodeProperty<>("Stage", "Stage", "Stage", status.getDescription())); + break; + case COMPLETED_JOB: + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), dateCreated.toString())); + ss.put(new NodeProperty<>("Date Started", "Date Started", "Date Started", autoIngestJob.getProcessingStageStartDate())); + ss.put(new NodeProperty<>("Date Completed - Prop6", "Date Completed", "Date Completed - Prop6", autoIngestJob.getCompletedDate())); + ss.put(new NodeProperty<>("Status", "Status", "Status", autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); + break; + default: + } +// AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); + // ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), caseName)); +// ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), fileName.toString())); +// ss.put(new NodeProperty<>("Host Name - Prop3", "Host Name - Prop3", "Host Name - Prop3", autoIngestJob.getProcessingHostName())); +// ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), dateCreated.toString())); +// ss.put(new NodeProperty<>("Date Started - Prop5", "Date Started - Prop5", "Date Started - Prop5", autoIngestJob.getProcessingStageStartDate())); +// ss.put(new NodeProperty<>("Date Completed - Prop6", "Date Completed - Prop6", "Date Completed - Prop6", autoIngestJob.getCompletedDate())); +// ss.put(new NodeProperty<>("Stage - Prop7", "Stage - Prop7", "Stage - Prop7", status.getDescription())); +// ss.put(new NodeProperty<>("Status - Prop8", "Status - Prop8", "Status - Prop8", autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); +// ss.put(new NodeProperty<>("Stage Time - Prop9", "Stage Time - Prop9", "Stage Time - Prop9", (Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime()))); +// ss.put(new NodeProperty<>("Case Directory - Prop10", "Case Directory - Prop10", "Case Directory - Prop10", autoIngestJob.getCaseDirectoryPath())); +// ss.put(new NodeProperty<>("Manifest Path - Prop11", "Manifest Path - Prop11", "Manifest Path - Prop11", autoIngestJob.getManifest().getFilePath())); +// ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Integer.toString(priority))); + return s; + } + + } + + enum AutoIngestJobType { + PENDING_JOB, //NON-NLS + RUNNING_JOB, //NON-NLS + COMPLETED_JOB //NON-NLS + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index 633d069cc8..a9a500a5b9 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -10,9 +10,6 @@ AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime=Job Completed AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage=Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.Status=Status AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath= Manifest File Path -AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first -AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it -AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime=Time in Stage AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder=Case AutoIngestDashboard.JobsTableModel.ColumnHeader.Job=Job From b6ccca3e57a92ca2109c765160553b281c65825f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 5 Apr 2018 14:08:29 -0400 Subject: [PATCH 005/100] 3610 - re-enable buttons on Auto Ingest Dashboard with outlineViews --- .../autoingest/AutoIngestDashboard.java | 620 ++++-------------- .../autoingest/AutoIngestJobsPanel.java | 53 +- .../autoingest/AutoIngestNode.java | 96 +-- 3 files changed, 195 insertions(+), 574 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index fb5abdc686..43d0d791d4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -19,45 +19,26 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Cursor; -import java.awt.EventQueue; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Instant; -import java.util.Date; -import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.logging.Level; -import javax.swing.DefaultListSelectionModel; import java.awt.Color; +import java.awt.EventQueue; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import javax.swing.JPanel; -import javax.swing.JTable; -import javax.swing.ListSelectionModel; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.table.DefaultTableModel; -import org.netbeans.swing.outline.Outline; -import org.openide.explorer.ExplorerManager; -import javax.swing.table.TableColumn; -import org.netbeans.swing.outline.DefaultOutlineModel; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.datamodel.EmptyNode; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; -import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; -import org.sleuthkit.autopsy.guiutils.LongDateCellRenderer; -import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; /** * A dashboard for monitoring an automated ingest cluster. @@ -89,9 +70,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { private static final int COMPLETED_TIME_COL_MAX_WIDTH = 2000; private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName()); - // private final DefaultTableModel pendingTableModel; -// private final DefaultTableModel runningTableModel; -// private final DefaultTableModel completedTableModel; private AutoIngestMonitor autoIngestMonitor; private AutoIngestJobsPanel pendingJobsPanel; private AutoIngestJobsPanel runningJobsPanel; @@ -126,37 +104,53 @@ final class AutoIngestDashboard extends JPanel implements Observer { private AutoIngestDashboard() { this.statusByService = new ConcurrentHashMap<>(); -// pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); -// runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); -// completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); - // initPendingJobsTable(); -// initRunningJobsTable(); -// initCompletedJobsTable(); pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.PENDING_JOB); pendingJobsPanel.setSize(pendingScrollPane.getSize()); pendingScrollPane.add(pendingJobsPanel); pendingScrollPane.setViewportView(pendingJobsPanel); pendingJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { - System.out.println("SELECTION HAPPENED PENDING WJS-TODO"); + if (e.getValueIsAdjusting()) { + return; + } + AutoIngestJob job = this.pendingJobsPanel.getSelectedAutoIngestJob(); + + boolean enablePrioritizeButtons = false; + boolean enableDeprioritizeButtons = false; + if (job != null) { + enablePrioritizeButtons = true; + enableDeprioritizeButtons = job.getPriority() > 0; + } + this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); + this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); + this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); + this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); }); runningJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.RUNNING_JOB); runningJobsPanel.setSize(runningScrollPane.getSize()); runningScrollPane.add(runningJobsPanel); runningScrollPane.setViewportView(runningJobsPanel); runningJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { - System.out.println("SELECTION HAPPENED RUNNING WJS-TODO"); + boolean enabled = false; + this.prioritizeJobButton.setEnabled(enabled); + this.prioritizeCaseButton.setEnabled(enabled); + this.deprioritizeJobButton.setEnabled(enabled); + this.deprioritizeCaseButton.setEnabled(enabled); }); finishedJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.COMPLETED_JOB); finishedJobsPanel.setSize(completedScrollPane.getSize()); completedScrollPane.add(finishedJobsPanel); completedScrollPane.setViewportView(finishedJobsPanel); finishedJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { - System.out.println("SELECTION HAPPENED COMPLETED WJS-TODO"); + boolean enabled = false; + this.prioritizeJobButton.setEnabled(enabled); + this.prioritizeCaseButton.setEnabled(enabled); + this.deprioritizeJobButton.setEnabled(enabled); + this.deprioritizeCaseButton.setEnabled(enabled); }); /* * Must set this flag, otherwise pop up menus don't close properly. @@ -229,231 +223,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { }.execute(); } -// /** -// * Sets up the JTable that presents a view of the pending jobs queue for an -// * auto ingest cluster. -// */ -// private void initPendingJobsTable() { -// /* -// * Remove some of the jobs table model columns from the JTable. This -// * does not remove the columns from the model, just from this table. -// */ -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); -// pendingTable.removeColumn(pendingTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); -// -// /* -// * Set up a column to display the cases associated with the jobs. -// */ -// TableColumn column; -// column = pendingTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); -// column.setMinWidth(GENERIC_COL_MIN_WIDTH); -// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); -// column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); -// column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the data sources associated with the jobs. -// */ -// column = pendingTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); -// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); -// column.setPreferredWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); -// column.setWidth(PENDING_TABLE_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the create times of the jobs. -// */ -// column = pendingTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); -// column.setCellRenderer(new LongDateCellRenderer()); -// column.setMinWidth(TIME_COL_MIN_WIDTH); -// column.setMaxWidth(TIME_COL_MAX_WIDTH); -// column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); -// column.setWidth(TIME_COL_PREFERRED_WIDTH); -// -// column = pendingTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader()); -// column.setCellRenderer(new PrioritizedIconCellRenderer()); -// column.setMaxWidth(PRIORITY_COLUMN_MAX_WIDTH); -// column.setPreferredWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); -// column.setWidth(PRIORITY_COLUMN_PREFERRED_WIDTH); -// /* -// * Allow sorting when a column header is clicked. -// */ -// pendingTable.setRowSorter(new AutoIngestRowSorter<>(pendingTableModel)); -// -// /* -// * Create a row selection listener to enable/disable the Prioritize -// * button. -// */ -// pendingTable.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { -// if (e.getValueIsAdjusting()) { -// return; -// } -// int row = pendingTable.getSelectedRow(); -// -// boolean enablePrioritizeButtons = false; -// boolean enableDeprioritizeButtons = false; -// if (row >= 0 && row < pendingTable.getRowCount()) { -// enablePrioritizeButtons = true; -// enableDeprioritizeButtons = (Integer) pendingTableModel.getValueAt(row, JobsTableModelColumns.PRIORITY.ordinal()) > 0; -// } -// this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); -// this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); -// this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); -// this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); -// }); -// } -// /** -// * Sets up the JTable that presents a view of the running jobs list for an -// * auto ingest cluster. -// */ -// private void initRunningJobsTable() { -// /* -// * Remove some of the jobs table model columns from the JTable. This -// * does not remove the columns from the model, just from this table. -// */ -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); -// runningTable.removeColumn(runningTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); -// /* -// * Set up a column to display the cases associated with the jobs. -// */ -// TableColumn column; -// column = runningTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); -// column.setMinWidth(GENERIC_COL_MIN_WIDTH); -// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); -// column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); -// column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the image folders associated with the -// * jobs. -// */ -// column = runningTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); -// column.setMinWidth(GENERIC_COL_MIN_WIDTH); -// column.setMaxWidth(GENERIC_COL_MAX_WIDTH); -// column.setPreferredWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); -// column.setWidth(RUNNING_TABLE_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the host names of the cluster nodes -// * processing the jobs. -// */ -// column = runningTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader()); -// column.setMinWidth(NAME_COL_MIN_WIDTH); -// column.setMaxWidth(NAME_COL_MAX_WIDTH); -// column.setPreferredWidth(NAME_COL_PREFERRED_WIDTH); -// column.setWidth(NAME_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the ingest activities associated with the -// * jobs. -// */ -// column = runningTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader()); -// column.setMinWidth(STAGE_COL_MIN_WIDTH); -// column.setMaxWidth(STAGE_COL_MAX_WIDTH); -// column.setPreferredWidth(STAGE_COL_PREFERRED_WIDTH); -// column.setWidth(STAGE_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the ingest activity times associated with -// * the jobs. -// */ -// column = runningTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader()); -// column.setCellRenderer(new DurationCellRenderer()); -// column.setMinWidth(GENERIC_COL_MIN_WIDTH); -// column.setMaxWidth(STAGE_TIME_COL_MAX_WIDTH); -// column.setPreferredWidth(STAGE_TIME_COL_MIN_WIDTH); -// column.setWidth(STAGE_TIME_COL_MIN_WIDTH); -// -// /* -// * Prevent sorting when a column header is clicked. -// */ -// runningTable.setAutoCreateRowSorter(false); -// } -// /** -// * Sets up the JTable that presents a view of the completed jobs list for an -// * auto ingest cluster. -// */ -// private void initCompletedJobsTable() { -// /* -// * Remove some of the jobs table model columns from the JTable. This -// * does not remove the columns from the model, just from this table. -// */ -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STARTED_TIME.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.STAGE_TIME.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.HOST_NAME.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.CASE_DIRECTORY_PATH.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.MANIFEST_FILE_PATH.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.JOB.getColumnHeader())); -// completedTable.removeColumn(completedTable.getColumn(JobsTableModelColumns.PRIORITY.getColumnHeader())); -// /* -// * Set up a column to display the cases associated with the jobs. -// */ -// TableColumn column; -// column = completedTable.getColumn(JobsTableModelColumns.CASE.getColumnHeader()); -// column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); -// column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); -// column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); -// column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the image folders associated with the -// * jobs. -// */ -// column = completedTable.getColumn(JobsTableModelColumns.DATA_SOURCE.getColumnHeader()); -// column.setMinWidth(COMPLETED_TIME_COL_MIN_WIDTH); -// column.setMaxWidth(COMPLETED_TIME_COL_MAX_WIDTH); -// column.setPreferredWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); -// column.setWidth(COMPLETED_TIME_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the create times of the jobs. -// */ -// column = completedTable.getColumn(JobsTableModelColumns.CREATED_TIME.getColumnHeader()); -// column.setCellRenderer(new LongDateCellRenderer()); -// column.setMinWidth(TIME_COL_MIN_WIDTH); -// column.setMaxWidth(TIME_COL_MAX_WIDTH); -// column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); -// column.setWidth(TIME_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the completed times of the jobs. -// */ -// column = completedTable.getColumn(JobsTableModelColumns.COMPLETED_TIME.getColumnHeader()); -// column.setCellRenderer(new LongDateCellRenderer()); -// column.setMinWidth(TIME_COL_MIN_WIDTH); -// column.setMaxWidth(TIME_COL_MAX_WIDTH); -// column.setPreferredWidth(TIME_COL_PREFERRED_WIDTH); -// column.setWidth(TIME_COL_PREFERRED_WIDTH); -// -// /* -// * Set up a column to display the statuses of the jobs, with a cell -// * renderer that will choose an icon to represent the job status. -// */ -// column = completedTable.getColumn(JobsTableModelColumns.STATUS.getColumnHeader()); -// column.setCellRenderer(new StatusIconCellRenderer()); -// column.setMinWidth(STATUS_COL_MIN_WIDTH); -// column.setMaxWidth(STATUS_COL_MAX_WIDTH); -// column.setPreferredWidth(STATUS_COL_PREFERRED_WIDTH); -// column.setWidth(STATUS_COL_PREFERRED_WIDTH); -// /* -// * Allow sorting when a column header is clicked. -// */ -// completedTable.setRowSorter(new AutoIngestRowSorter<>(completedTableModel)); -// } -// /** * Starts up the auto ingest monitor and adds this panel as an observer, * subscribes to services monitor events and starts a task to populate the @@ -499,7 +268,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override public void update(Observable observable, Object arg) { - // EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); + EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); } /** @@ -512,172 +281,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { pendingJobsPanel.refresh(jobsSnapshot); runningJobsPanel.refresh(jobsSnapshot); finishedJobsPanel.refresh(jobsSnapshot); - // List runningJobs = jobsSnapshot.getRunningJobs(); - // List completedJobs = jobsSnapshot.getCompletedJobs(); - // runningJobs.sort(new AutoIngestJob.DataSourceFileNameComparator()); - // completedJobs.sort(new AutoIngestJob.CompletedDateDescendingComparator()); - // refreshTable(pendingJobs, pendingTable, pendingTableModel); -// refreshTable(runningJobs, runningTable, runningTableModel); - // refreshTable(completedJobs, completedTable, completedTableModel); - } - -// /** -// * Reloads the table model for an auto ingest jobs table and refreshes the -// * JTable that uses the model. -// * -// * @param jobs The list of auto ingest jobs. -// * @param tableModel The table model. -// * @param comparator An optional comparator (may be null) for sorting the -// * table model. -// */ -// private void refreshTable(List jobs, JTable table, DefaultTableModel tableModel) { -// try { -// Path currentRow = getSelectedEntry(table, tableModel); -// tableModel.setRowCount(0); -// for (AutoIngestJob job : jobs) { -// AutoIngestJob.StageDetails status = job.getProcessingStageDetails(); -// tableModel.addRow(new Object[]{ -// job.getManifest().getCaseName(), // CASE -// job.getManifest().getDataSourcePath().getFileName(), job.getProcessingHostName(), // HOST_NAME -// job.getManifest().getDateFileCreated(), // CREATED_TIME -// job.getProcessingStageStartDate(), // STARTED_TIME -// job.getCompletedDate(), // COMPLETED_TIME -// status.getDescription(), // STAGE -// job.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK, // STATUS -// ((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())), // STAGE_TIME -// job.getCaseDirectoryPath(), // CASE_DIRECTORY_PATH -// job.getManifest().getFilePath(), // MANIFEST_FILE_PATH -// job.getPriority(), // PRIORITY -// job -// }); -// } -// setSelectedEntry(table, tableModel, currentRow); -// } catch (Exception ex) { -// LOGGER.log(Level.SEVERE, "Error refreshing table " + table.toString(), ex); -// } -// } - /** - * Gets a path representing the current selection in a table. - * - * @param table The table. - * @param tableModel The table model of the table. - * - * @return A path representing the current selection, or null if there is no - * selection. - */ - Path getSelectedEntry(JTable table, DefaultTableModel tableModel) { - try { - int currentlySelectedRow = table.getSelectedRow(); - if (currentlySelectedRow >= 0 && currentlySelectedRow < table.getRowCount()) { - return Paths.get(tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.CASE.ordinal()).toString(), - tableModel.getValueAt(currentlySelectedRow, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); - } - } catch (Exception ignored) { - return null; - } - return null; - } - - /** - * Sets the selection of the table to the passed-in path's item, if that - * item exists in the table. If it does not, clears the table selection. - * - * @param table The table. - * @param tableModel The table model of the table. - * @param path The path of the item to set - */ - void setSelectedEntry(JTable table, DefaultTableModel tableModel, Path path) { - if (path != null) { - try { - for (int row = 0; row < table.getRowCount(); ++row) { - Path temp = Paths.get(tableModel.getValueAt(row, JobsTableModelColumns.CASE.ordinal()).toString(), - tableModel.getValueAt(row, JobsTableModelColumns.DATA_SOURCE.ordinal()).toString()); - if (temp.compareTo(path) == 0) { // found it - table.setRowSelectionInterval(row, row); - return; - } - } - } catch (Exception ignored) { - table.clearSelection(); - } - } - table.clearSelection(); - } - - /* - * This enum is used in conjunction with the DefaultTableModel class to - * provide table models for the JTables used to display a view of the - * pending jobs queue, running jobs list, and completed jobs list for an - * auto ingest cluster. The enum allows the columns of the table model to be - * described by either an enum ordinal or a column header string. - */ - private enum JobsTableModelColumns { - @Messages({"AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority=Prioritized"}) - - CASE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Case")), - DATA_SOURCE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ImageFolder")), - HOST_NAME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.HostName")), - CREATED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CreatedTime")), - STARTED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StartedTime")), - COMPLETED_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CompletedTime")), - STAGE(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Stage")), - STAGE_TIME(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.StageTime")), - STATUS(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Status")), - CASE_DIRECTORY_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.CaseFolder")), - MANIFEST_FILE_PATH(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.ManifestFilePath")), - PRIORITY(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Priority")), - JOB(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.JobsTableModel.ColumnHeader.Job")); - - private final String header; - - private JobsTableModelColumns(String header) { - this.header = header; - } - - private String getColumnHeader() { - return header; - } - - private static final String[] headers = { - CASE.getColumnHeader(), - DATA_SOURCE.getColumnHeader(), - HOST_NAME.getColumnHeader(), - CREATED_TIME.getColumnHeader(), - STARTED_TIME.getColumnHeader(), - COMPLETED_TIME.getColumnHeader(), - STAGE.getColumnHeader(), - STATUS.getColumnHeader(), - STAGE_TIME.getColumnHeader(), - CASE_DIRECTORY_PATH.getColumnHeader(), - MANIFEST_FILE_PATH.getColumnHeader(), - PRIORITY.getColumnHeader(), - JOB.getColumnHeader() - }; - }; - /** - * A task that refreshes the UI components on this panel to reflect a - * snapshot of the pending, running and completed auto ingest jobs lists of - * an auto ingest cluster. - */ - private class RefreshComponentsTask implements Runnable { - - private final JobsSnapshot jobsSnapshot; - - /** - * Constructs a task that refreshes the UI components on this panel to - * reflect a snapshot of the pending, running and completed auto ingest - * jobs lists of an auto ingest cluster. - * - * @param jobsSnapshot The jobs snapshot. - */ - RefreshComponentsTask(JobsSnapshot jobsSnapshot) { - this.jobsSnapshot = jobsSnapshot; - } - - @Override - public void run() { - refreshTables(jobsSnapshot); - } } /** @@ -895,38 +498,39 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."}) private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed -// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { -// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); -// JobsSnapshot jobsSnapshot; -// try { -// jobsSnapshot = autoIngestMonitor.prioritizeJob(job); -// refreshTables(jobsSnapshot); -// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { -// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath()); -// LOGGER.log(Level.SEVERE, errorMessage, ex); -// MessageNotifyUtil.Message.error(errorMessage); -// } -// setCursor(Cursor.getDefaultCursor()); -// } + AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); + if (job != null) { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + JobsSnapshot jobsSnapshot; + try { + jobsSnapshot = autoIngestMonitor.prioritizeJob(job); + refreshTables(jobsSnapshot); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath()); + LOGGER.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } + setCursor(Cursor.getDefaultCursor()); + } }//GEN-LAST:event_prioritizeJobButtonActionPerformed @Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."}) private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed -// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { -// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); -// JobsSnapshot jobsSnapshot; -// try { -// jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName); -// refreshTables(jobsSnapshot); -// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { -// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName); -// LOGGER.log(Level.SEVERE, errorMessage, ex); -// MessageNotifyUtil.Message.error(errorMessage); -// } -// setCursor(Cursor.getDefaultCursor()); -// } + AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); + if (job != null) { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + String caseName = job.getManifest().getCaseName(); + JobsSnapshot jobsSnapshot; + try { + jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName); + refreshTables(jobsSnapshot); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName); + LOGGER.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } + setCursor(Cursor.getDefaultCursor()); + } }//GEN-LAST:event_prioritizeCaseButtonActionPerformed private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed @@ -935,38 +539,39 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."}) private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed -// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { -// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// AutoIngestJob job = (AutoIngestJob) (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.JOB.ordinal())); -// JobsSnapshot jobsSnapshot; -// try { -// jobsSnapshot = autoIngestMonitor.deprioritizeJob(job); -// refreshTables(jobsSnapshot); -// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { -// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath()); -// LOGGER.log(Level.SEVERE, errorMessage, ex); -// MessageNotifyUtil.Message.error(errorMessage); -// } -// setCursor(Cursor.getDefaultCursor()); -// } + AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); + if (job != null) { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + JobsSnapshot jobsSnapshot; + try { + jobsSnapshot = autoIngestMonitor.deprioritizeJob(job); + refreshTables(jobsSnapshot); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath()); + LOGGER.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } + setCursor(Cursor.getDefaultCursor()); + } }//GEN-LAST:event_deprioritizeJobButtonActionPerformed @Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."}) private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed -// if (pendingTableModel.getRowCount() > 0 && pendingTable.getSelectedRow() >= 0) { -// setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); -// String caseName = (pendingTableModel.getValueAt(pendingTable.getSelectedRow(), JobsTableModelColumns.CASE.ordinal())).toString(); -// JobsSnapshot jobsSnapshot; -// try { -// jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName); -// refreshTables(jobsSnapshot); -// } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { -// String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName); -// LOGGER.log(Level.SEVERE, errorMessage, ex); -// MessageNotifyUtil.Message.error(errorMessage); -// } -// setCursor(Cursor.getDefaultCursor()); -// } + AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); + if (job != null) { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + String caseName = job.getManifest().getCaseName(); + JobsSnapshot jobsSnapshot; + try { + jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName); + refreshTables(jobsSnapshot); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName); + LOGGER.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } + setCursor(Cursor.getDefaultCursor()); + } }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables @@ -986,34 +591,29 @@ final class AutoIngestDashboard extends JPanel implements Observer { private javax.swing.JScrollPane runningScrollPane; private javax.swing.JTextField tbServicesStatusMessage; // End of variables declaration//GEN-END:variables + /** + * A task that refreshes the UI components on this panel to reflect a + * snapshot of the pending, running and completed auto ingest jobs lists of + * an auto ingest cluster. + */ + private class RefreshComponentsTask implements Runnable { -// private class AutoIngestTableModel extends DefaultTableModel { -// -// private static final long serialVersionUID = 1L; -// -// private AutoIngestTableModel(String[] headers, int i) { -// super(headers, i); -// } -// -// @Override -// public boolean isCellEditable(int row, int column) { -// return false; -// } -// -// @Override -// public Class getColumnClass(int columnIndex) { -// if (columnIndex == JobsTableModelColumns.PRIORITY.ordinal()) { -// return Integer.class; -// } else if (columnIndex == JobsTableModelColumns.CREATED_TIME.ordinal() -// || columnIndex == JobsTableModelColumns.COMPLETED_TIME.ordinal() -// || columnIndex == JobsTableModelColumns.STARTED_TIME.ordinal() -// || columnIndex == JobsTableModelColumns.STAGE_TIME.ordinal()) { -// return Date.class; -// } else if (columnIndex == JobsTableModelColumns.STATUS.ordinal()) { -// return Boolean.class; -// } else { -// return super.getColumnClass(columnIndex); -// } -// } -// } + private final JobsSnapshot jobsSnapshot; + + /** + * Constructs a task that refreshes the UI components on this panel to + * reflect a snapshot of the pending, running and completed auto ingest + * jobs lists of an auto ingest cluster. + * + * @param jobsSnapshot The jobs snapshot. + */ + RefreshComponentsTask(JobsSnapshot jobsSnapshot) { + this.jobsSnapshot = jobsSnapshot; + } + + @Override + public void run() { + refreshTables(jobsSnapshot); + } + } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index e37f419802..791b487b98 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -1,11 +1,23 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.EventQueue; import java.util.ArrayList; @@ -17,9 +29,11 @@ import javax.swing.SwingWorker; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; +import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.AutoIngestJobType; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.JobNode; /** * @@ -49,22 +63,25 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa switch (type) { case PENDING_JOB: - outlineView.setPropertyColumns(Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), - Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), - Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text()); + outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), + Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), + Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text()); break; case RUNNING_JOB: - outlineView.setPropertyColumns("Stage", "Stage"); + outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), + Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), + Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), + Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text()); break; case COMPLETED_JOB: - outlineView.setPropertyColumns(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), - "Date Started", "Date Started", - "Date Completed", "Date Completed", - "Status", "Status"); + outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), + Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), + Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), + Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text()); break; default: } - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestNode_col1_text()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestNode_caseName_text()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); outline.setRootVisible(false); outline.setColumnSorted(0, false, 1); @@ -73,8 +90,6 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa } outline.setRowSelectionAllowed(false); - - // outlineView.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); add(outlineView, java.awt.BorderLayout.CENTER); } @@ -117,7 +132,13 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents - + AutoIngestJob getSelectedAutoIngestJob() { + Node[] selectedRows = explorerManager.getSelectedNodes(); + if (selectedRows.length == 1) { + return ((JobNode) selectedRows[0]).getAutoIngestJob(); + } + return null; + } // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java index e436ab03bc..4d01e4243d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java @@ -1,12 +1,24 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.nio.file.Path; -import java.util.ArrayList; +import java.time.Instant; import java.util.Date; import java.util.List; import org.openide.nodes.AbstractNode; @@ -21,10 +33,15 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; final class AutoIngestNode extends AbstractNode { @Messages({ - "AutoIngestNode.col1.text=Case Name", - "AutoIngestNode.col2.text=File Name", - "AutoIngestNode.col3.text=Date Created", - "AutoIngestNode.col4.text=Priority" + "AutoIngestNode.caseName.text=Case Name", + "AutoIngestNode.dataSource.text=Data Source", + "AutoIngestNode.hostName.text=Host Name", + "AutoIngestNode.stage.text=Stage", + "AutoIngestNode.stageTime.text=Time in Stage", + "AutoIngestNode.jobCreated.text=Job Created", + "AutoIngestNode.jobCompleted.text=Job Completed", + "AutoIngestNode.priority.text=Priority", + "AutoIngestNode.status.text=Status" }) AutoIngestNode(List jobs, AutoIngestJobType type) { @@ -33,7 +50,7 @@ final class AutoIngestNode extends AbstractNode { static class AutoIngestNodeChildren extends ChildFactory { - AutoIngestJobType autoIngestJobType; + private final AutoIngestJobType autoIngestJobType; private final List jobs; AutoIngestNodeChildren(List jobList, AutoIngestJobType type) { @@ -51,7 +68,7 @@ final class AutoIngestNode extends AbstractNode { @Override protected Node createNodeForKey(AutoIngestJob key) { - return new PendingJobNode(key, autoIngestJobType); + return new JobNode(key, autoIngestJobType); } } @@ -59,26 +76,22 @@ final class AutoIngestNode extends AbstractNode { /** * A node which represents a single multi user case. */ - static final class PendingJobNode extends AbstractNode { + static final class JobNode extends AbstractNode { private final AutoIngestJob autoIngestJob; - private final String caseName; - private final Path fileName; - private final Date dateCreated; - private final int priority; private final AutoIngestJobType jobType; - PendingJobNode(AutoIngestJob job, AutoIngestJobType type) { + JobNode(AutoIngestJob job, AutoIngestJobType type) { super(Children.LEAF); jobType = type; autoIngestJob = job; - caseName = autoIngestJob.getManifest().getCaseName(); - fileName = autoIngestJob.getManifest().getDataSourcePath().getFileName(); - dateCreated = autoIngestJob.getManifest().getDateFileCreated(); - priority = autoIngestJob.getPriority(); - super.setName(caseName); - setName(caseName); - setDisplayName(caseName); + super.setName(autoIngestJob.getManifest().getCaseName()); + setName(autoIngestJob.getManifest().getCaseName()); + setDisplayName(autoIngestJob.getManifest().getCaseName()); + } + + AutoIngestJob getAutoIngestJob(){ + return autoIngestJob; } @Override @@ -89,41 +102,28 @@ final class AutoIngestNode extends AbstractNode { ss = Sheet.createPropertiesSet(); s.put(ss); } - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), caseName)); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), autoIngestJob.getManifest().getCaseName())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); switch (jobType) { - case PENDING_JOB: - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), fileName.toString())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), dateCreated.toString())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Integer.toString(priority))); + case PENDING_JOB: + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), autoIngestJob.getPriority())); break; case RUNNING_JOB: AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); - ss.put(new NodeProperty<>("Stage", "Stage", "Stage", status.getDescription())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(),autoIngestJob.getProcessingHostName())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), status.getDescription())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), (Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime()))); break; case COMPLETED_JOB: - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), dateCreated.toString())); - ss.put(new NodeProperty<>("Date Started", "Date Started", "Date Started", autoIngestJob.getProcessingStageStartDate())); - ss.put(new NodeProperty<>("Date Completed - Prop6", "Date Completed", "Date Completed - Prop6", autoIngestJob.getCompletedDate())); - ss.put(new NodeProperty<>("Status", "Status", "Status", autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), autoIngestJob.getCompletedDate())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); break; default: } -// AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); - // ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), Bundle.AutoIngestNode_col1_text(), caseName)); -// ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), Bundle.AutoIngestNode_col2_text(), fileName.toString())); -// ss.put(new NodeProperty<>("Host Name - Prop3", "Host Name - Prop3", "Host Name - Prop3", autoIngestJob.getProcessingHostName())); -// ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), Bundle.AutoIngestNode_col3_text(), dateCreated.toString())); -// ss.put(new NodeProperty<>("Date Started - Prop5", "Date Started - Prop5", "Date Started - Prop5", autoIngestJob.getProcessingStageStartDate())); -// ss.put(new NodeProperty<>("Date Completed - Prop6", "Date Completed - Prop6", "Date Completed - Prop6", autoIngestJob.getCompletedDate())); -// ss.put(new NodeProperty<>("Stage - Prop7", "Stage - Prop7", "Stage - Prop7", status.getDescription())); -// ss.put(new NodeProperty<>("Status - Prop8", "Status - Prop8", "Status - Prop8", autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); -// ss.put(new NodeProperty<>("Stage Time - Prop9", "Stage Time - Prop9", "Stage Time - Prop9", (Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime()))); -// ss.put(new NodeProperty<>("Case Directory - Prop10", "Case Directory - Prop10", "Case Directory - Prop10", autoIngestJob.getCaseDirectoryPath())); -// ss.put(new NodeProperty<>("Manifest Path - Prop11", "Manifest Path - Prop11", "Manifest Path - Prop11", autoIngestJob.getManifest().getFilePath())); -// ss.put(new NodeProperty<>(Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Bundle.AutoIngestNode_col4_text(), Integer.toString(priority))); return s; } - } enum AutoIngestJobType { From 3d6dd20d90b138f43c2b69e255d7d85868f769bd Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 14:11:06 -0400 Subject: [PATCH 006/100] Improved schema initialization. Added property change listener. Cleanup. --- .../HealthMonitorCaseEventListener.java | 53 +++++ .../autopsy/healthmonitor/Installer.java | 20 +- .../healthmonitor/ServicesHealthMonitor.java | 182 ++++++++++++------ 3 files changed, 177 insertions(+), 78 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java new file mode 100644 index 0000000000..5c8a6f675d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java @@ -0,0 +1,53 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.healthmonitor; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.sleuthkit.autopsy.casemodule.Case; + +/** + * Listener for case events + */ +final class HealthMonitorCaseEventListener implements PropertyChangeListener { + + private final ExecutorService jobProcessingExecutor; + private static final String CASE_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; + + HealthMonitorCaseEventListener() { + jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build()); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + + switch (Case.Events.valueOf(evt.getPropertyName())) { + + case CURRENT_CASE: + if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { + // When a case is closed, write the current metrics to the database + jobProcessingExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); + } + break; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index b77ba067d7..5d294aca31 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -20,11 +20,13 @@ package org.sleuthkit.autopsy.healthmonitor; import java.util.logging.Level; import org.openide.modules.ModuleInstall; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; public class Installer extends ModuleInstall { private static final Logger logger = Logger.getLogger(Installer.class.getName()); + private final HealthMonitorCaseEventListener pcl = new HealthMonitorCaseEventListener(); private static final long serialVersionUID = 1L; private static Installer instance; @@ -42,25 +44,13 @@ public class Installer extends ModuleInstall { @Override public void restored() { + + Case.addPropertyChangeListener(pcl); + try { ServicesHealthMonitor.startUp(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error starting health services monitor", ex); } } - - @Override - public boolean closing() { - //platform about to close - ServicesHealthMonitor.close(); - - return true; - } - - @Override - public void uninstalled() { - //module is being unloaded - ServicesHealthMonitor.close(); - - } } \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index ab2510ca59..18fe6ba0ca 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -53,7 +53,7 @@ public class ServicesHealthMonitor { private final static String DATABASE_NAME = "ServicesHealthMonitor"; private final static String MODULE_NAME = "ServicesHealthMonitor"; private final static String IS_ENABLED_KEY = "is_enabled"; - private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes + private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 0); @@ -75,7 +75,14 @@ public class ServicesHealthMonitor { if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ isEnabled.set(true); - activateMonitor(); + try { + activateMonitor(); + } catch (HealthMonitorException ex) { + // If we failed to activate it, then disable the monitor + logger.log(Level.SEVERE, "Health monitor activation failed - disabling health monitor"); + setEnabled(false); + throw ex; + } return; } } @@ -118,12 +125,12 @@ public class ServicesHealthMonitor { if (! databaseExists()) { // If not, create a new one - System.out.println(" No database exists - setting up new one"); createDatabase(); - initializeDatabaseSchema(); } - // Any database upgrades would happen here + if( ! databaseIsInitialized()) { + initializeDatabaseSchema(); + } } finally { try { @@ -202,9 +209,11 @@ public class ServicesHealthMonitor { } if(enabled) { + getInstance().activateMonitor(); + + // If activateMonitor fails, we won't update either of these ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "true"); isEnabled.set(true); - getInstance().activateMonitor(); } else { ModuleSettings.setConfigSetting(MODULE_NAME, IS_ENABLED_KEY, "false"); isEnabled.set(false); @@ -271,14 +280,12 @@ public class ServicesHealthMonitor { } } - // TODO: Make private once testing is done /** * Write the collected metrics to the database. * @throws HealthMonitorException */ - void writeCurrentStateToDatabase() throws HealthMonitorException { - logger.log(Level.INFO, "Writing health monitor metrics to database"); - + private void writeCurrentStateToDatabase() throws HealthMonitorException { + Map timingMapCopy; // Do as little as possible within the synchronized block since it will @@ -293,20 +300,13 @@ public class ServicesHealthMonitor { timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } + logger.log(Level.INFO, "Writing health monitor metrics to database"); // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { return; } - // TODO: Debug - for(String name:timingMapCopy.keySet()){ - TimingInfo info = timingMapCopy.get(name); - long timestamp = System.currentTimeMillis(); - System.out.println(" Name: " + name + "\tTimestamp: " + timestamp + "\tAverage: " + info.getAverage() + - "\tMax: " + info.getMax() + "\tMin: " + info.getMin()); - } - // Write to the database CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { @@ -354,11 +354,6 @@ public class ServicesHealthMonitor { } } - // TODO: debug - synchronized void clearCurrentState() { - timingInfoMap.clear(); - } - /** * Call during application closing - attempts to log any remaining entries. */ @@ -388,23 +383,13 @@ public class ServicesHealthMonitor { } } - // TODO: debug - synchronized void printCurrentState() { - System.out.println("\nTiming Info Map:"); - for(String name:timingInfoMap.keySet()) { - System.out.print(name + "\t"); - timingInfoMap.get(name).print(); - } - } - - // TODO: Change to private after testing /** * Check whether the health monitor database exists. * Does not check the schema. * @return true if the database exists, false otherwise * @throws HealthMonitorException */ - boolean databaseExists() throws HealthMonitorException { + private boolean databaseExists() throws HealthMonitorException { try { // Use the same database settings as the case CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); @@ -449,26 +434,6 @@ public class ServicesHealthMonitor { throw new HealthMonitorException("Failed to delete health monitor database", ex); } } - - // TODO: At least make private - /** - * Delete the current health monitor database (for testing only) - * Make private after test - */ - void deleteDatabase() { - try { - // Use the same database settings as the case - CaseDbConnectionInfo db = UserPreferences.getDatabaseConnectionInfo(); - Class.forName("org.postgresql.Driver"); //NON-NLS - try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS - Statement statement = connection.createStatement();) { - String deleteCommand = "DROP DATABASE \"" + DATABASE_NAME + "\""; //NON-NLS - statement.execute(deleteCommand); - } - } catch (UserPreferencesException | ClassNotFoundException | SQLException ex) { - logger.log(Level.SEVERE, "Failed to delete health monitor database", ex); - } - } /** * Setup a connection pool for db connections. @@ -494,7 +459,7 @@ public class ServicesHealthMonitor { connectionPool.setPassword(db.getPassword()); // tweak pool configuration - connectionPool.setInitialSize(2); // start with 2 connections + connectionPool.setInitialSize(3); // start with 3 connections connectionPool.setMaxIdle(CONN_POOL_SIZE); // max of 10 idle connections connectionPool.setValidationQuery("SELECT version()"); } catch (UserPreferencesException ex) { @@ -539,6 +504,95 @@ public class ServicesHealthMonitor { } } + /** + * Test whether the database schema has been initialized. + * We do this by looking for the version number. + * @return True if it has been initialized, false otherwise. + * @throws HealthMonitorException + */ + private boolean databaseIsInitialized() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + ResultSet resultSet = null; + + try (Statement statement = conn.createStatement()) { + resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); + return resultSet.next(); + } catch (SQLException ex) { + // This likely just means that the db_info table does not exist + return false; + } finally { + if(resultSet != null) { + try { + resultSet.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing result set", ex); + } + } + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + + /** + * Get the current schema version + * @return the current schema version + * @throws HealthMonitorException + */ + private CaseDbSchemaVersionNumber getVersion() throws HealthMonitorException { + Connection conn = connect(); + if(conn == null) { + throw new HealthMonitorException("Error getting database connection"); + } + ResultSet resultSet = null; + + try (Statement statement = conn.createStatement()) { + int minorVersion = 0; + int majorVersion = 0; + resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_MINOR_VERSION'"); + if (resultSet.next()) { + String minorVersionStr = resultSet.getString("value"); + try { + minorVersion = Integer.parseInt(minorVersionStr); + } catch (NumberFormatException ex) { + throw new HealthMonitorException("Bad value for schema minor version (" + minorVersionStr + ") - database is corrupt"); + } + } + + resultSet = statement.executeQuery("SELECT value FROM db_info WHERE name='SCHEMA_VERSION'"); + if (resultSet.next()) { + String majorVersionStr = resultSet.getString("value"); + try { + majorVersion = Integer.parseInt(majorVersionStr); + } catch (NumberFormatException ex) { + throw new HealthMonitorException("Bad value for schema version (" + majorVersionStr + ") - database is corrupt"); + } + } + + return new CaseDbSchemaVersionNumber(majorVersion, minorVersion); + } catch (SQLException ex) { + throw new HealthMonitorException("Error initializing database", ex); + } finally { + if(resultSet != null) { + try { + resultSet.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing result set", ex); + } + } + try { + conn.close(); + } catch (SQLException ex) { + logger.log(Level.SEVERE, "Error closing Connection.", ex); + } + } + } + /** * Initialize the database. * @throws HealthMonitorException @@ -549,8 +603,9 @@ public class ServicesHealthMonitor { throw new HealthMonitorException("Error getting database connection"); } - // TODO: transaction try (Statement statement = conn.createStatement()) { + conn.setAutoCommit(false); + StringBuilder createTimingTable = new StringBuilder(); createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); createTimingTable.append("id SERIAL PRIMARY KEY,"); @@ -574,13 +629,19 @@ public class ServicesHealthMonitor { statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')"); + conn.commit(); } catch (SQLException ex) { + try { + conn.rollback(); + } catch (SQLException ex2) { + logger.log(Level.SEVERE, "Rollback error"); + } throw new HealthMonitorException("Error initializing database", ex); } finally { try { conn.close(); } catch (SQLException ex) { - logger.log(Level.SEVERE, "Error closing Connection.", ex); + logger.log(Level.SEVERE, "Error closing connection.", ex); } } } @@ -589,7 +650,7 @@ public class ServicesHealthMonitor { * The task called by the ScheduledThreadPoolExecutor to handle * the database writes. */ - private final class DatabaseWriteTask implements Runnable { + static final class DatabaseWriteTask implements Runnable { /** * Write current metric data to the database @@ -720,10 +781,5 @@ public class ServicesHealthMonitor { long getCount() { return count; } - - // TODO: debug - void print() { - System.out.println("count: " + count + "\tsum: " + sum + "\tmax: " + max + "\tmin: " + min); - } } } From f21feb0d3afe606941c30b848e92f19182f34137 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 14:15:02 -0400 Subject: [PATCH 007/100] Modified metric names. --- .../src/org/sleuthkit/autopsy/keywordsearch/Ingester.java | 2 +- .../src/org/sleuthkit/autopsy/keywordsearch/Server.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index 679a646995..f956513eca 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -237,7 +237,7 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr index chunk"); + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); ServicesHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 3b4e494480..5748524ada 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -775,7 +775,7 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("solr connectivity check"); + TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); ServicesHealthMonitor.submitTimingMetric(metric); From fc53bff61aeaff82b2d29817111104c52777dbd3 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 5 Apr 2018 14:17:43 -0400 Subject: [PATCH 008/100] Remove unused method --- .../healthmonitor/ServicesHealthMonitor.java | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index 18fe6ba0ca..ebed5d5f61 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -354,35 +354,6 @@ public class ServicesHealthMonitor { } } - /** - * Call during application closing - attempts to log any remaining entries. - */ - static synchronized void close() { - if(isEnabled.get()) { - - // Stop the timer - try { - getInstance().stopTimer(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error shutting down timer", ex); - } - - // Write current data - try { - getInstance().writeCurrentStateToDatabase(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error writing final metric data to database", ex); - } - - // Shutdown connection pool - try { - getInstance().shutdownConnections(); - } catch (HealthMonitorException ex) { - logger.log(Level.SEVERE, "Error shutting down connection pool", ex); - } - } - } - /** * Check whether the health monitor database exists. * Does not check the schema. From 9cdc521379319a344d47e4caf42761c3f651f990 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 5 Apr 2018 14:57:48 -0400 Subject: [PATCH 009/100] 3610 display stage time as human readable string again --- .../guiutils/DurationCellRenderer.java | 56 ++++++++++--------- .../autoingest/AutoIngestNode.java | 39 ++++++++----- 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java index 6aab063952..5e8dfbcdea 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.guiutils; -import java.awt.Color; import java.awt.Component; import java.time.Duration; import javax.swing.JTable; @@ -41,36 +40,39 @@ public class DurationCellRenderer extends GrayableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value instanceof Long) { { - Duration d = Duration.ofMillis((long) value); - if (d.isNegative()) { - d = Duration.ofMillis(-(long) value); - } - - String result; - long days = d.toDays(); - long hours = d.minusDays(days).toHours(); - long minutes = d.minusDays(days).minusHours(hours).toMinutes(); - long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds(); - - if (minutes > 0) { - if (hours > 0) { - if (days > 0) { - result = days + " d " + hours + " h " + minutes + " m " + seconds + " s"; - } else { - result = hours + " h " + minutes + " m " + seconds + " s"; - } - } else { - result = minutes + " m " + seconds + " s"; - } - } else { - result = seconds + " s"; - } - - setText(result); + setText(DurationCellRenderer.longToDurationString((long)value)); } } grayCellIfTableNotEnabled(table, isSelected); return this; } + public static String longToDurationString(long duration) { + Duration d = Duration.ofMillis(duration); + if (d.isNegative()) { + d = Duration.ofMillis(-duration); + } + + String result; + long days = d.toDays(); + long hours = d.minusDays(days).toHours(); + long minutes = d.minusDays(days).minusHours(hours).toMinutes(); + long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds(); + + if (minutes > 0) { + if (hours > 0) { + if (days > 0) { + result = days + " d " + hours + " h " + minutes + " m " + seconds + " s"; + } else { + result = hours + " h " + minutes + " m " + seconds + " s"; + } + } else { + result = minutes + " m " + seconds + " s"; + } + } else { + result = seconds + " s"; + } + return result; + } + } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java index 4d01e4243d..4169eaca09 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java @@ -28,6 +28,7 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; final class AutoIngestNode extends AbstractNode { @@ -41,7 +42,7 @@ final class AutoIngestNode extends AbstractNode { "AutoIngestNode.jobCreated.text=Job Created", "AutoIngestNode.jobCompleted.text=Job Completed", "AutoIngestNode.priority.text=Priority", - "AutoIngestNode.status.text=Status" + "AutoIngestNode.status.text=Status" }) AutoIngestNode(List jobs, AutoIngestJobType type) { @@ -89,8 +90,8 @@ final class AutoIngestNode extends AbstractNode { setName(autoIngestJob.getManifest().getCaseName()); setDisplayName(autoIngestJob.getManifest().getCaseName()); } - - AutoIngestJob getAutoIngestJob(){ + + AutoIngestJob getAutoIngestJob() { return autoIngestJob; } @@ -102,23 +103,33 @@ final class AutoIngestNode extends AbstractNode { ss = Sheet.createPropertiesSet(); s.put(ss); } - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), autoIngestJob.getManifest().getCaseName())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), + autoIngestJob.getManifest().getCaseName())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), + autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); switch (jobType) { - case PENDING_JOB: - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), autoIngestJob.getPriority())); + case PENDING_JOB: + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), + autoIngestJob.getManifest().getDateFileCreated())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), + autoIngestJob.getPriority())); break; case RUNNING_JOB: AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(),autoIngestJob.getProcessingHostName())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), status.getDescription())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), (Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime()))); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), + autoIngestJob.getProcessingHostName())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), + status.getDescription())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), + DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())))); break; case COMPLETED_JOB: - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), autoIngestJob.getCompletedDate())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), + autoIngestJob.getManifest().getDateFileCreated())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), + autoIngestJob.getCompletedDate())); + ss.put(new NodeProperty<>(Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), + autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); break; default: } From d5f1064e8855b7523e5e63a16dfeb5d11aa3e6a4 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 6 Apr 2018 13:55:49 -0400 Subject: [PATCH 010/100] Added host name to timing metric --- .../healthmonitor/ServicesHealthMonitor.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index ebed5d5f61..d628f2e8b9 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -301,6 +301,15 @@ public class ServicesHealthMonitor { timingInfoMap.clear(); } logger.log(Level.INFO, "Writing health monitor metrics to database"); + + String hostName; + try { + hostName = java.net.InetAddress.getLocalHost().getHostName(); + } catch (java.net.UnknownHostException ex) { + // Write it to the database but log a warning + logger.log(Level.WARNING, "Unable to look up host name"); + hostName = "unknown"; + } // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { @@ -320,18 +329,19 @@ public class ServicesHealthMonitor { } // Add timing metrics to the database - String addTimingInfoSql = "INSERT INTO timing_data (name, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?)"; + String addTimingInfoSql = "INSERT INTO timing_data (name, host, timestamp, count, average, max, min) VALUES (?, ?, ?, ?, ?, ?, ?)"; try (PreparedStatement statement = conn.prepareStatement(addTimingInfoSql)) { for(String name:timingMapCopy.keySet()) { TimingInfo info = timingMapCopy.get(name); statement.setString(1, name); - statement.setLong(2, System.currentTimeMillis()); - statement.setLong(3, info.getCount()); - statement.setLong(4, info.getAverage()); - statement.setLong(5, info.getMax()); - statement.setLong(6, info.getMin()); + statement.setString(2, hostName); + statement.setLong(3, System.currentTimeMillis()); + statement.setLong(4, info.getCount()); + statement.setLong(5, info.getAverage()); + statement.setLong(6, info.getMax()); + statement.setLong(7, info.getMin()); statement.execute(); } @@ -581,6 +591,7 @@ public class ServicesHealthMonitor { createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); createTimingTable.append("id SERIAL PRIMARY KEY,"); createTimingTable.append("name text NOT NULL,"); + createTimingTable.append("host text NOT NULL,"); createTimingTable.append("timestamp bigint NOT NULL,"); createTimingTable.append("count bigint NOT NULL,"); createTimingTable.append("average bigint NOT NULL,"); From 3f20808ed4f3be6870d69d84aee2721d03562f48 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 6 Apr 2018 14:48:52 -0400 Subject: [PATCH 011/100] 3610 refresh no longer causes table contents to blink with asynchronous= false --- .../autoingest/AutoIngestDashboard.java | 4 +- .../autoingest/AutoIngestJobsPanel.java | 72 +++---------------- .../autoingest/AutoIngestNode.java | 28 ++++++-- 3 files changed, 35 insertions(+), 69 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 43d0d791d4..4fb2e1e989 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -268,7 +268,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override public void update(Observable observable, Object arg) { - EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); + EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); } /** @@ -282,7 +282,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { runningJobsPanel.refresh(jobsSnapshot); finishedJobsPanel.refresh(jobsSnapshot); } - + /** * Exception type thrown when there is an error completing an auto ingest * dashboard operation. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 791b487b98..3bb22237c5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -20,17 +20,12 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Dimension; import java.awt.EventQueue; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutionException; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; -import javax.swing.SwingWorker; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.AutoIngestJobType; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.JobNode; @@ -45,7 +40,6 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa private final org.openide.explorer.view.OutlineView outlineView; private final Outline outline; private ExplorerManager explorerManager; - private JobListWorker jobListWorker; private final AutoIngestJobType type; /** @@ -65,7 +59,8 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa case PENDING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), - Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text()); + Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), + "Time Since Created", "Time Since Created"); break; case RUNNING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), @@ -91,7 +86,8 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa } outline.setRowSelectionAllowed(false); add(outlineView, java.awt.BorderLayout.CENTER); - + EmptyNode emptyNode = new EmptyNode("Please wait..."); + explorerManager.setRootContext(emptyNode); } @Override @@ -111,12 +107,13 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa } void refresh(AutoIngestMonitor.JobsSnapshot jobsSnapshot) { - if (jobListWorker == null || jobListWorker.isDone()) { + synchronized (this) { outline.setRowSelectionAllowed(false); -// EmptyNode emptyNode = new EmptyNode("Refreshing..."); -// explorerManager.setRootContext(emptyNode); - jobListWorker = new JobListWorker(jobsSnapshot, type); - jobListWorker.execute(); + EventQueue.invokeLater(() -> { + AutoIngestNode autoIngestNode = new AutoIngestNode(jobsSnapshot, type); + explorerManager.setRootContext(autoIngestNode); + outline.setRowSelectionAllowed(true); + }); } } @@ -141,54 +138,5 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa } // Variables declaration - do not modify//GEN-BEGIN:variables // End of variables declaration//GEN-END:variables - /** - * Swingworker to fetch the updated List of cases in a background thread - */ - private class JobListWorker extends SwingWorker, Void> { - - private final AutoIngestMonitor.JobsSnapshot jobsSnapshot; - private final AutoIngestJobType jobType; - - JobListWorker(AutoIngestMonitor.JobsSnapshot snapshot, AutoIngestJobType type) { - jobsSnapshot = snapshot; - jobType = type; - } - - @Override - protected List doInBackground() throws Exception { - List jobs; - switch (jobType) { - case PENDING_JOB: - jobs = jobsSnapshot.getPendingJobs(); - break; - case RUNNING_JOB: - jobs = jobsSnapshot.getRunningJobs(); - break; - case COMPLETED_JOB: - jobs = jobsSnapshot.getCompletedJobs(); - break; - default: - jobs = new ArrayList<>(); - - } - jobs.sort(new AutoIngestJob.PriorityComparator()); - return jobs; - } - - @Override - protected void done() { - try { - List jobs = get(); - EventQueue.invokeLater(() -> { - AutoIngestNode autoIngestNode = new AutoIngestNode(jobs, jobType); - explorerManager.setRootContext(autoIngestNode); - outline.setRowSelectionAllowed(true); - }); - } catch (InterruptedException | ExecutionException ex) { - Exceptions.printStackTrace(ex); - } - - } - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java index 4169eaca09..869da413c2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.time.Instant; +import java.util.ArrayList; import java.util.Date; import java.util.List; import org.openide.nodes.AbstractNode; @@ -28,6 +29,7 @@ import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; @@ -45,22 +47,36 @@ final class AutoIngestNode extends AbstractNode { "AutoIngestNode.status.text=Status" }) - AutoIngestNode(List jobs, AutoIngestJobType type) { - super(Children.create(new AutoIngestNodeChildren(jobs, type), true)); + AutoIngestNode(JobsSnapshot snapshot, AutoIngestJobType type) { + super(Children.create(new AutoIngestNodeChildren(snapshot, type), false)); } static class AutoIngestNodeChildren extends ChildFactory { private final AutoIngestJobType autoIngestJobType; - private final List jobs; + private final JobsSnapshot jobsSnapshot; - AutoIngestNodeChildren(List jobList, AutoIngestJobType type) { - this.jobs = jobList; + AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobType type) { + jobsSnapshot = snapshot; autoIngestJobType = type; } @Override protected boolean createKeys(List list) { + List jobs; + switch (autoIngestJobType) { + case PENDING_JOB: + jobs = jobsSnapshot.getPendingJobs(); + break; + case RUNNING_JOB: + jobs = jobsSnapshot.getRunningJobs(); + break; + case COMPLETED_JOB: + jobs = jobsSnapshot.getCompletedJobs(); + break; + default: + jobs = new ArrayList<>(); + } if (jobs != null && jobs.size() > 0) { list.addAll(jobs); } @@ -113,6 +129,8 @@ final class AutoIngestNode extends AbstractNode { autoIngestJob.getManifest().getDateFileCreated())); ss.put(new NodeProperty<>(Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), autoIngestJob.getPriority())); + ss.put(new NodeProperty<>("Time Since Created", "Time Since Created", "Time Since Created", + DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (autoIngestJob.getManifest().getDateFileCreated().getTime())))); break; case RUNNING_JOB: AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); From 4a2b4197b27ef494cb697dab83f7626386f617f3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 9 Apr 2018 13:38:06 -0400 Subject: [PATCH 012/100] 3610 row selection, resize, icons --- .../autoingest/AutoIngestDashboard.form | 65 ++++++++-------- .../autoingest/AutoIngestDashboard.java | 76 +++++++------------ .../autoingest/AutoIngestJobsPanel.java | 44 ++++++++--- .../autoingest/AutoIngestNode.java | 2 - 4 files changed, 92 insertions(+), 95 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 68a1bb656d..0bdc79f34f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -26,36 +26,31 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -65,24 +60,24 @@ - + - + - - + + - - + + - - - + + + @@ -91,7 +86,7 @@ - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 4fb2e1e989..d0923b63ba 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -33,6 +33,7 @@ import javax.swing.JPanel; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; +import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; @@ -46,29 +47,6 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnaps final class AutoIngestDashboard extends JPanel implements Observer { private static final long serialVersionUID = 1L; - private static final int GENERIC_COL_MIN_WIDTH = 30; - private static final int GENERIC_COL_MAX_WIDTH = 2000; - private static final int PENDING_TABLE_COL_PREFERRED_WIDTH = 280; - private static final int RUNNING_TABLE_COL_PREFERRED_WIDTH = 175; - private static final int PRIORITY_COLUMN_PREFERRED_WIDTH = 60; - private static final int PRIORITY_COLUMN_MAX_WIDTH = 150; - private static final int STAGE_TIME_COL_MIN_WIDTH = 250; - private static final int STAGE_TIME_COL_MAX_WIDTH = 450; - private static final int TIME_COL_MIN_WIDTH = 30; - private static final int TIME_COL_MAX_WIDTH = 250; - private static final int TIME_COL_PREFERRED_WIDTH = 140; - private static final int NAME_COL_MIN_WIDTH = 100; - private static final int NAME_COL_MAX_WIDTH = 250; - private static final int NAME_COL_PREFERRED_WIDTH = 140; - private static final int STAGE_COL_MIN_WIDTH = 70; - private static final int STAGE_COL_MAX_WIDTH = 2000; - private static final int STAGE_COL_PREFERRED_WIDTH = 300; - private static final int STATUS_COL_MIN_WIDTH = 55; - private static final int STATUS_COL_MAX_WIDTH = 250; - private static final int STATUS_COL_PREFERRED_WIDTH = 55; - private static final int COMPLETED_TIME_COL_MIN_WIDTH = 30; - private static final int COMPLETED_TIME_COL_MAX_WIDTH = 2000; - private static final int COMPLETED_TIME_COL_PREFERRED_WIDTH = 280; private static final Logger LOGGER = Logger.getLogger(AutoIngestDashboard.class.getName()); private AutoIngestMonitor autoIngestMonitor; private AutoIngestJobsPanel pendingJobsPanel; @@ -155,6 +133,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { /* * Must set this flag, otherwise pop up menus don't close properly. */ + UIManager.put("PopupMenu.consumeEventOnClose", false); } @@ -278,9 +257,11 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param jobsSnapshot The jobs snapshot. */ private void refreshTables(JobsSnapshot jobsSnapshot) { +// Node[] selectedPending = pendingJobsPanel.getSelectedNodes(); pendingJobsPanel.refresh(jobsSnapshot); - runningJobsPanel.refresh(jobsSnapshot); - finishedJobsPanel.refresh(jobsSnapshot); +// pendingJobsPanel.setSelectedNodes(selectedPending); +// runningJobsPanel.refresh(jobsSnapshot); +// finishedJobsPanel.refresh(jobsSnapshot); } /** @@ -425,27 +406,24 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addComponent(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(prioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deprioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(prioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deprioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(clusterMetricsButton)) - .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(lbServicesStatus) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.PREFERRED_SIZE, 861, javax.swing.GroupLayout.PREFERRED_SIZE))) - .addGap(0, 0, Short.MAX_VALUE))) + .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(prioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deprioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(prioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deprioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(clusterMetricsButton)) + .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(lbServicesStatus) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 861, Short.MAX_VALUE))) .addContainerGap()) ); @@ -461,15 +439,15 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(lbPending, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(1, 1, 1) - .addComponent(pendingScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lbRunning) .addGap(1, 1, 1) - .addComponent(runningScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 133, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(runningScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 133, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(lbCompleted) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(completedScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(completedScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 179, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(refreshButton) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 3bb22237c5..ba3b379023 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -20,12 +20,16 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Dimension; import java.awt.EventQueue; +import java.beans.PropertyVetoException; +import java.util.Enumeration; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableColumn; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; +import org.openide.util.Exceptions; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.AutoIngestJobType; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.JobNode; @@ -59,27 +63,32 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa case PENDING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), - Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), - "Time Since Created", "Time Since Created"); + Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text()); + outline.setColumnSorted(3, false, 1); + outline.setColumnSorted(0, true, 2); break; case RUNNING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text()); + outline.setColumnSorted(0, true, 1); break; case COMPLETED_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text()); + outline.setColumnSorted(3, false, 1); break; default: } ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestNode_caseName_text()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); outline.setRootVisible(false); - outline.setColumnSorted(0, false, 1); + + outline.getColumnModel().getColumn(0).setPreferredWidth(160); + outline.getColumnModel().getColumn(1).setPreferredWidth(260); if (null == explorerManager) { explorerManager = new ExplorerManager(); @@ -108,12 +117,29 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa void refresh(AutoIngestMonitor.JobsSnapshot jobsSnapshot) { synchronized (this) { - outline.setRowSelectionAllowed(false); + // outline.setRowSelectionAllowed(false); + Node[] selectedNodes = explorerManager.getSelectedNodes(); + AutoIngestNode autoIngestNode = new AutoIngestNode(jobsSnapshot, type); + explorerManager.setRootContext(autoIngestNode); + outline.setRowSelectionAllowed(true); EventQueue.invokeLater(() -> { - AutoIngestNode autoIngestNode = new AutoIngestNode(jobsSnapshot, type); - explorerManager.setRootContext(autoIngestNode); - outline.setRowSelectionAllowed(true); - }); + setSelectedNodes(selectedNodes); + System.out.println("SUCESS: " + selectedNodes.length); + + }); + } + } + + Node[] getSelectedNodes() { + return explorerManager.getSelectedNodes(); + } + + void setSelectedNodes(Node[] selectedRows) { + try { + explorerManager.setSelectedNodes(selectedRows); + } catch (PropertyVetoException ignore) { + System.out.println("Unable to set selected Rows: " + ignore.toString()); + //Unable to select previously selected node } } @@ -130,7 +156,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa }// //GEN-END:initComponents AutoIngestJob getSelectedAutoIngestJob() { - Node[] selectedRows = explorerManager.getSelectedNodes(); + Node[] selectedRows = getSelectedNodes(); if (selectedRows.length == 1) { return ((JobNode) selectedRows[0]).getAutoIngestJob(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java index 869da413c2..e6a4d19440 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java @@ -129,8 +129,6 @@ final class AutoIngestNode extends AbstractNode { autoIngestJob.getManifest().getDateFileCreated())); ss.put(new NodeProperty<>(Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), autoIngestJob.getPriority())); - ss.put(new NodeProperty<>("Time Since Created", "Time Since Created", "Time Since Created", - DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (autoIngestJob.getManifest().getDateFileCreated().getTime())))); break; case RUNNING_JOB: AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); From 1a437d8686376ce710cca77d93876a0cec64a7d0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 9 Apr 2018 13:50:49 -0400 Subject: [PATCH 013/100] Cleanup addressing PR review comments. --- .../HealthMonitorCaseEventListener.java | 8 +- .../healthmonitor/ServicesHealthMonitor.java | 74 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java index 5c8a6f675d..f6c5583692 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java @@ -30,11 +30,11 @@ import org.sleuthkit.autopsy.casemodule.Case; */ final class HealthMonitorCaseEventListener implements PropertyChangeListener { - private final ExecutorService jobProcessingExecutor; - private static final String CASE_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; + private final ExecutorService healthMonitorExecutor; + private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; HealthMonitorCaseEventListener() { - jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(CASE_EVENT_THREAD_NAME).build()); + healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); } @Override @@ -45,7 +45,7 @@ final class HealthMonitorCaseEventListener implements PropertyChangeListener { case CURRENT_CASE: if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { // When a case is closed, write the current metrics to the database - jobProcessingExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); + healthMonitorExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); } break; } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index d628f2e8b9..c73fa55106 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -60,10 +60,11 @@ public class ServicesHealthMonitor { private static final AtomicBoolean isEnabled = new AtomicBoolean(false); private static ServicesHealthMonitor instance; - private ScheduledThreadPoolExecutor periodicTasksExecutor; + private ScheduledThreadPoolExecutor healthMonitorOutputTimer; private final Map timingInfoMap; private static final int CONN_POOL_SIZE = 10; private BasicDataSource connectionPool = null; + private String hostName; private ServicesHealthMonitor() throws HealthMonitorException { @@ -71,6 +72,15 @@ public class ServicesHealthMonitor { // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); + // Get the host name + try { + hostName = java.net.InetAddress.getLocalHost().getHostName(); + } catch (java.net.UnknownHostException ex) { + // Continue on but log a warning + logger.log(Level.WARNING, "Unable to look up host name"); + hostName = "unknown"; + } + // Read from module settings to determine if the module is enabled if (ModuleSettings.settingExists(MODULE_NAME, IS_ENABLED_KEY)) { if(ModuleSettings.getConfigSetting(MODULE_NAME, IS_ENABLED_KEY).equals("true")){ @@ -172,20 +182,20 @@ public class ServicesHealthMonitor { * Start the ScheduledThreadPoolExecutor that will handle the database writes. */ private synchronized void startTimer() { - if(periodicTasksExecutor != null) { + if(healthMonitorOutputTimer != null) { // Make sure the previous executor (if it exists) has been stopped - periodicTasksExecutor.shutdown(); + healthMonitorOutputTimer.shutdown(); } - periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); - periodicTasksExecutor.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); + healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); + healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); } /** * Stop the ScheduledThreadPoolExecutor to prevent further database writes. */ private synchronized void stopTimer() { - if(periodicTasksExecutor != null) { - periodicTasksExecutor.shutdown(); + if(healthMonitorOutputTimer != null) { + healthMonitorOutputTimer.shutdown(); } } @@ -301,15 +311,6 @@ public class ServicesHealthMonitor { timingInfoMap.clear(); } logger.log(Level.INFO, "Writing health monitor metrics to database"); - - String hostName; - try { - hostName = java.net.InetAddress.getLocalHost().getHostName(); - } catch (java.net.UnknownHostException ex) { - // Write it to the database but log a warning - logger.log(Level.WARNING, "Unable to look up host name"); - hostName = "unknown"; - } // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { @@ -587,26 +588,26 @@ public class ServicesHealthMonitor { try (Statement statement = conn.createStatement()) { conn.setAutoCommit(false); - StringBuilder createTimingTable = new StringBuilder(); - createTimingTable.append("CREATE TABLE IF NOT EXISTS timing_data ("); - createTimingTable.append("id SERIAL PRIMARY KEY,"); - createTimingTable.append("name text NOT NULL,"); - createTimingTable.append("host text NOT NULL,"); - createTimingTable.append("timestamp bigint NOT NULL,"); - createTimingTable.append("count bigint NOT NULL,"); - createTimingTable.append("average bigint NOT NULL,"); - createTimingTable.append("max bigint NOT NULL,"); - createTimingTable.append("min bigint NOT NULL"); - createTimingTable.append(")"); - statement.execute(createTimingTable.toString()); + String createTimingTable = + "CREATE TABLE IF NOT EXISTS timing_data (" + + "id SERIAL PRIMARY KEY," + + "name text NOT NULL," + + "host text NOT NULL," + + "timestamp bigint NOT NULL," + + "count bigint NOT NULL," + + "average bigint NOT NULL," + + "max bigint NOT NULL," + + "min bigint NOT NULL" + + ")"; + statement.execute(createTimingTable); - StringBuilder createDbInfoTable = new StringBuilder(); - createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info ("); - createDbInfoTable.append("id SERIAL PRIMARY KEY NOT NULL,"); - createDbInfoTable.append("name text NOT NULL,"); - createDbInfoTable.append("value text NOT NULL"); - createDbInfoTable.append(")"); - statement.execute(createDbInfoTable.toString()); + String createDbInfoTable = + "CREATE TABLE IF NOT EXISTS db_info (" + + "id SERIAL PRIMARY KEY NOT NULL," + + "name text NOT NULL," + + "value text NOT NULL" + + ")"; + statement.execute(createDbInfoTable); statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() + "')"); statement.execute("INSERT INTO db_info (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() + "')"); @@ -655,8 +656,7 @@ public class ServicesHealthMonitor { */ private CoordinationService.Lock getExclusiveDbLock() throws HealthMonitorException{ try { - String databaseNodeName = DATABASE_NAME; - CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, databaseNodeName, 5, TimeUnit.MINUTES); + CoordinationService.Lock lock = CoordinationService.getInstance().tryGetExclusiveLock(CoordinationService.CategoryNode.HEALTH_MONITOR, DATABASE_NAME, 5, TimeUnit.MINUTES); if(lock != null){ return lock; From 08298e5dcec87bc66626e9939cbcd56841da0ff2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 9 Apr 2018 16:14:32 -0400 Subject: [PATCH 014/100] 3610 retain selection through refreshes of explorer view for aid 2.0 --- .../autoingest/AutoIngestDashboard.java | 6 ++---- .../autoingest/AutoIngestJobsPanel.java | 14 ++++---------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index d0923b63ba..c3347adb32 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -257,11 +257,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param jobsSnapshot The jobs snapshot. */ private void refreshTables(JobsSnapshot jobsSnapshot) { -// Node[] selectedPending = pendingJobsPanel.getSelectedNodes(); pendingJobsPanel.refresh(jobsSnapshot); -// pendingJobsPanel.setSelectedNodes(selectedPending); -// runningJobsPanel.refresh(jobsSnapshot); -// finishedJobsPanel.refresh(jobsSnapshot); + runningJobsPanel.refresh(jobsSnapshot); + finishedJobsPanel.refresh(jobsSnapshot); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index ba3b379023..ad8a92f97d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -19,17 +19,13 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Dimension; -import java.awt.EventQueue; import java.beans.PropertyVetoException; -import java.util.Enumeration; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; -import javax.swing.table.TableColumn; import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; -import org.openide.util.Exceptions; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.AutoIngestJobType; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.JobNode; @@ -117,16 +113,14 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa void refresh(AutoIngestMonitor.JobsSnapshot jobsSnapshot) { synchronized (this) { - // outline.setRowSelectionAllowed(false); + outline.setRowSelectionAllowed(false); Node[] selectedNodes = explorerManager.getSelectedNodes(); AutoIngestNode autoIngestNode = new AutoIngestNode(jobsSnapshot, type); explorerManager.setRootContext(autoIngestNode); outline.setRowSelectionAllowed(true); - EventQueue.invokeLater(() -> { - setSelectedNodes(selectedNodes); - System.out.println("SUCESS: " + selectedNodes.length); - - }); + if (selectedNodes.length > 0) { + setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); + } } } From 8d7cc2550fabef3a8a9f75d4cfddd4cd4eea0b72 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 9 Apr 2018 16:36:18 -0400 Subject: [PATCH 015/100] 3610 some clean up of various auto ingest outline view changes --- .../guiutils/DurationCellRenderer.java | 12 ++++++-- .../autoingest/AutoIngestDashboard.java | 28 +++++++++++-------- .../autoingest/AutoIngestJobsPanel.java | 20 ++++--------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java index 5e8dfbcdea..9ab4ec281b 100644 --- a/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java +++ b/Core/src/org/sleuthkit/autopsy/guiutils/DurationCellRenderer.java @@ -40,13 +40,21 @@ public class DurationCellRenderer extends GrayableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value instanceof Long) { { - setText(DurationCellRenderer.longToDurationString((long)value)); + setText(DurationCellRenderer.longToDurationString((long) value)); } } grayCellIfTableNotEnabled(table, isSelected); return this; } + /** + * Convert a duration represented by a long to a human readable string with + * with days, hours, minutes, and seconds components. + * + * @param duration - the representation of the duration in long form + * + * @return - the representation of the duration in String form. + */ public static String longToDurationString(long duration) { Duration d = Duration.ofMillis(duration); if (d.isNegative()) { @@ -58,7 +66,7 @@ public class DurationCellRenderer extends GrayableCellRenderer { long hours = d.minusDays(days).toHours(); long minutes = d.minusDays(days).minusHours(hours).toMinutes(); long seconds = d.minusDays(days).minusHours(hours).minusMinutes(minutes).getSeconds(); - + if (minutes > 0) { if (hours > 0) { if (days > 0) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index c3347adb32..3d4a78f1ff 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -33,7 +33,6 @@ import javax.swing.JPanel; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; -import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; @@ -51,7 +50,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { private AutoIngestMonitor autoIngestMonitor; private AutoIngestJobsPanel pendingJobsPanel; private AutoIngestJobsPanel runningJobsPanel; - private AutoIngestJobsPanel finishedJobsPanel; + private AutoIngestJobsPanel completedJobsPanel; /** * Maintain a mapping of each service to it's last status update. @@ -79,6 +78,10 @@ final class AutoIngestDashboard extends JPanel implements Observer { /** * Constructs a panel for monitoring an automated ingest cluster. */ + @Messages({"AutoIngestDashboard.pendingTable.toolTipText=The Pending table displays the order upcoming Jobs will be processed with the top of the list first", + "AutoIngestDashboard.runningTable.toolTipText=The Running table displays the currently running Job and information about it", + "AutoIngestDashboard.completedTable.toolTipText=The Completed table shows all Jobs that have been processed already"}) + private AutoIngestDashboard() { this.statusByService = new ConcurrentHashMap<>(); @@ -108,6 +111,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); }); + pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable.toolTipText()); runningJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.RUNNING_JOB); runningJobsPanel.setSize(runningScrollPane.getSize()); runningScrollPane.add(runningJobsPanel); @@ -119,21 +123,23 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeJobButton.setEnabled(enabled); this.deprioritizeCaseButton.setEnabled(enabled); }); - finishedJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.COMPLETED_JOB); - finishedJobsPanel.setSize(completedScrollPane.getSize()); - completedScrollPane.add(finishedJobsPanel); - completedScrollPane.setViewportView(finishedJobsPanel); - finishedJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { + runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable.toolTipText()); + completedJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.COMPLETED_JOB); + completedJobsPanel.setSize(completedScrollPane.getSize()); + completedScrollPane.add(completedJobsPanel); + completedScrollPane.setViewportView(completedJobsPanel); + completedJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { boolean enabled = false; this.prioritizeJobButton.setEnabled(enabled); this.prioritizeCaseButton.setEnabled(enabled); this.deprioritizeJobButton.setEnabled(enabled); this.deprioritizeCaseButton.setEnabled(enabled); }); + completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable.toolTipText()); /* * Must set this flag, otherwise pop up menus don't close properly. */ - + UIManager.put("PopupMenu.consumeEventOnClose", false); } @@ -247,7 +253,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override public void update(Observable observable, Object arg) { - EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); + EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); } /** @@ -259,9 +265,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { private void refreshTables(JobsSnapshot jobsSnapshot) { pendingJobsPanel.refresh(jobsSnapshot); runningJobsPanel.refresh(jobsSnapshot); - finishedJobsPanel.refresh(jobsSnapshot); + completedJobsPanel.refresh(jobsSnapshot); } - + /** * Exception type thrown when there is an error completing an auto ingest * dashboard operation. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index ad8a92f97d..4ad3305045 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -119,24 +119,16 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa explorerManager.setRootContext(autoIngestNode); outline.setRowSelectionAllowed(true); if (selectedNodes.length > 0) { - setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); + try { + explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); + } catch (PropertyVetoException ignore) { + //Unable to select previously selected node + } + } } } - Node[] getSelectedNodes() { - return explorerManager.getSelectedNodes(); - } - - void setSelectedNodes(Node[] selectedRows) { - try { - explorerManager.setSelectedNodes(selectedRows); - } catch (PropertyVetoException ignore) { - System.out.println("Unable to set selected Rows: " + ignore.toString()); - //Unable to select previously selected node - } - } - /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always From 14d271bb28e3314eed4ceb7f9b5b6e4de896fc54 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 9 Apr 2018 17:04:51 -0400 Subject: [PATCH 016/100] 3610 add comments to clarifiy new methods and classes for outlineView aid2.0 --- .../autoingest/AutoIngestDashboard.java | 6 +- ...ngestNode.java => AutoIngestJobsNode.java} | 61 ++++++++++++++----- .../autoingest/AutoIngestJobsPanel.java | 50 ++++++++++----- 3 files changed, 86 insertions(+), 31 deletions(-) rename Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/{AutoIngestNode.java => AutoIngestJobsNode.java} (78%) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 3d4a78f1ff..99e4deb600 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -90,7 +90,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); - pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.PENDING_JOB); + pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobType.PENDING_JOB); pendingJobsPanel.setSize(pendingScrollPane.getSize()); pendingScrollPane.add(pendingJobsPanel); pendingScrollPane.setViewportView(pendingJobsPanel); @@ -112,7 +112,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); }); pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable.toolTipText()); - runningJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.RUNNING_JOB); + runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobType.RUNNING_JOB); runningJobsPanel.setSize(runningScrollPane.getSize()); runningScrollPane.add(runningJobsPanel); runningScrollPane.setViewportView(runningJobsPanel); @@ -124,7 +124,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeCaseButton.setEnabled(enabled); }); runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable.toolTipText()); - completedJobsPanel = new AutoIngestJobsPanel(AutoIngestNode.AutoIngestJobType.COMPLETED_JOB); + completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobType.COMPLETED_JOB); completedJobsPanel.setSize(completedScrollPane.getSize()); completedScrollPane.add(completedJobsPanel); completedScrollPane.setViewportView(completedJobsPanel); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java similarity index 78% rename from Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java rename to Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index e6a4d19440..c71f0dcd55 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -33,7 +33,11 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnaps import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; -final class AutoIngestNode extends AbstractNode { +/** + * A node which represents all AutoIngestJobs of a given AutoIngestJobStatus. + * Each job with the specified status will have a child node representing it. + */ +final class AutoIngestJobsNode extends AbstractNode { @Messages({ "AutoIngestNode.caseName.text=Case Name", @@ -47,24 +51,37 @@ final class AutoIngestNode extends AbstractNode { "AutoIngestNode.status.text=Status" }) - AutoIngestNode(JobsSnapshot snapshot, AutoIngestJobType type) { - super(Children.create(new AutoIngestNodeChildren(snapshot, type), false)); + /** + * Construct a new AutoIngestJobsNode. + */ + AutoIngestJobsNode(JobsSnapshot snapshot, AutoIngestJobStatus status) { + super(Children.create(new AutoIngestNodeChildren(snapshot, status), false)); } + /** + * A ChildFactory for generating JobNodes. + */ static class AutoIngestNodeChildren extends ChildFactory { - private final AutoIngestJobType autoIngestJobType; + private final AutoIngestJobStatus autoIngestJobStatus; private final JobsSnapshot jobsSnapshot; - AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobType type) { + /** + * Create children nodes for the AutoIngestJobsNode which will each + * represent a single AutoIngestJob + * + * @param snapshot the snapshot which contains the AutoIngestJobs + * @param status the status of the jobs being displayed + */ + AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status) { jobsSnapshot = snapshot; - autoIngestJobType = type; + autoIngestJobStatus = status; } @Override protected boolean createKeys(List list) { List jobs; - switch (autoIngestJobType) { + switch (autoIngestJobStatus) { case PENDING_JOB: jobs = jobsSnapshot.getPendingJobs(); break; @@ -85,28 +102,40 @@ final class AutoIngestNode extends AbstractNode { @Override protected Node createNodeForKey(AutoIngestJob key) { - return new JobNode(key, autoIngestJobType); + return new JobNode(key, autoIngestJobStatus); } } /** - * A node which represents a single multi user case. + * A node which represents a single auto ingest job. */ static final class JobNode extends AbstractNode { private final AutoIngestJob autoIngestJob; - private final AutoIngestJobType jobType; + private final AutoIngestJobStatus jobStatus; - JobNode(AutoIngestJob job, AutoIngestJobType type) { + /** + * Construct a new JobNode to represent an AutoIngestJob and its status. + * + * @param job - the AutoIngestJob being represented by this node + * @param status - the current status of the AutoIngestJob being + * represented + */ + JobNode(AutoIngestJob job, AutoIngestJobStatus status) { super(Children.LEAF); - jobType = type; + jobStatus = status; autoIngestJob = job; super.setName(autoIngestJob.getManifest().getCaseName()); setName(autoIngestJob.getManifest().getCaseName()); setDisplayName(autoIngestJob.getManifest().getCaseName()); } + /** + * Get the AutoIngestJob which this node represents. + * + * @return autoIngestJob + */ AutoIngestJob getAutoIngestJob() { return autoIngestJob; } @@ -123,7 +152,7 @@ final class AutoIngestNode extends AbstractNode { autoIngestJob.getManifest().getCaseName())); ss.put(new NodeProperty<>(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); - switch (jobType) { + switch (jobStatus) { case PENDING_JOB: ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); @@ -153,7 +182,11 @@ final class AutoIngestNode extends AbstractNode { } } - enum AutoIngestJobType { + /** + * An enumeration used to indicate the current status of an auto ingest job + * node. + */ + enum AutoIngestJobStatus { PENDING_JOB, //NON-NLS RUNNING_JOB, //NON-NLS COMPLETED_JOB //NON-NLS diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 4ad3305045..40c306ddd2 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -27,12 +27,11 @@ import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; import org.sleuthkit.autopsy.datamodel.EmptyNode; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.AutoIngestJobType; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNode.JobNode; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; /** - * - * @author wschaefer + * A panel which displays an outline view with all jobs for a specified status. */ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider { @@ -40,22 +39,28 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa private final org.openide.explorer.view.OutlineView outlineView; private final Outline outline; private ExplorerManager explorerManager; - private final AutoIngestJobType type; + private final AutoIngestJobStatus status; /** - * Creates new form PendingJobsPanel + * Creates a new AutoIngestJobsPanel of the specified jobStatus + * + * @param jobStatus the status of the jbos to be displayed on this panel */ - AutoIngestJobsPanel(AutoIngestJobType jobType) { + AutoIngestJobsPanel(AutoIngestJobStatus jobStatus) { initComponents(); - type = jobType; + status = jobStatus; outlineView = new org.openide.explorer.view.OutlineView(); outline = outlineView.getOutline(); customize(); } + /** + * Set up the AutoIngestJobsPanel's so that its outlineView is displaying + * the correct columns for the specified AutoIngestJobStatus + */ void customize() { - switch (type) { + switch (status) { case PENDING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), @@ -102,6 +107,12 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa outline.setPreferredScrollableViewportSize(new Dimension(400, 100)); } + /** + * Add a list selection listener to the selection model of the outline being + * used in this panel. + * + * @param listener the ListSelectionListener to add + */ void addListSelectionListener(ListSelectionListener listener) { outline.getSelectionModel().addListSelectionListener(listener); } @@ -111,20 +122,26 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa return explorerManager; } + /** + * Update the contents of this AutoIngestJobsPanel while retaining currently + * selected node. + * + * @param jobsSnapshot - the JobsSnapshot which will provide the new + * contents + */ void refresh(AutoIngestMonitor.JobsSnapshot jobsSnapshot) { synchronized (this) { outline.setRowSelectionAllowed(false); Node[] selectedNodes = explorerManager.getSelectedNodes(); - AutoIngestNode autoIngestNode = new AutoIngestNode(jobsSnapshot, type); + AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); explorerManager.setRootContext(autoIngestNode); outline.setRowSelectionAllowed(true); if (selectedNodes.length > 0) { try { - explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); - } catch (PropertyVetoException ignore) { + explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); + } catch (PropertyVetoException ignore) { //Unable to select previously selected node } - } } } @@ -141,8 +158,13 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa setLayout(new java.awt.BorderLayout()); }// //GEN-END:initComponents + /** + * Get the AutoIngestJob for the currently selected node of this panel. + * + * @return AutoIngestJob which is currently selected in this panel + */ AutoIngestJob getSelectedAutoIngestJob() { - Node[] selectedRows = getSelectedNodes(); + Node[] selectedRows = explorerManager.getSelectedNodes(); if (selectedRows.length == 1) { return ((JobNode) selectedRows[0]).getAutoIngestJob(); } From 30a364c4175156c0436173f1c58964f2f16f574e Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 10 Apr 2018 07:29:51 -0400 Subject: [PATCH 017/100] Minor cleanup from PR review --- .../autopsy/healthmonitor/ServicesHealthMonitor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java index c73fa55106..33855df091 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java @@ -310,13 +310,14 @@ public class ServicesHealthMonitor { timingMapCopy = new HashMap<>(timingInfoMap); timingInfoMap.clear(); } - logger.log(Level.INFO, "Writing health monitor metrics to database"); // Check if there's anything to report (right now we only have the timing map) if(timingMapCopy.keySet().isEmpty()) { return; } + logger.log(Level.INFO, "Writing health monitor metrics to database"); + // Write to the database CoordinationService.Lock lock = getSharedDbLock(); if(lock == null) { @@ -380,7 +381,6 @@ public class ServicesHealthMonitor { try (Connection connection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS Statement statement = connection.createStatement();) { String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; - System.out.println(" query: " + createCommand); rs = statement.executeQuery(createCommand); if(rs.next()) { logger.log(Level.INFO, "Existing Services Health Monitor database found"); From 34ee030bd88a71ca80855253183efc38c33eb708 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 10 Apr 2018 17:09:11 -0400 Subject: [PATCH 018/100] 3610 add context menu options for priority changing to AID2.0 --- .../autoingest/AutoIngestDashboard.java | 132 ++++---------- .../AutoIngestDashboardTopComponent.java | 21 ++- .../autoingest/AutoIngestJobsNode.java | 47 ++++- .../autoingest/AutoIngestJobsPanel.java | 4 +- .../autoingest/PrioritizationAction.java | 162 ++++++++++++++++++ 5 files changed, 252 insertions(+), 114 deletions(-) create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 99e4deb600..ece26b0b71 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -37,8 +37,6 @@ import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; /** * A dashboard for monitoring an automated ingest cluster. @@ -90,7 +88,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); - pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobType.PENDING_JOB); + pendingJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.PENDING_JOB); pendingJobsPanel.setSize(pendingScrollPane.getSize()); pendingScrollPane.add(pendingJobsPanel); pendingScrollPane.setViewportView(pendingJobsPanel); @@ -111,8 +109,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); }); - pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable.toolTipText()); - runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobType.RUNNING_JOB); + pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable_toolTipText()); + runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.RUNNING_JOB); runningJobsPanel.setSize(runningScrollPane.getSize()); runningScrollPane.add(runningJobsPanel); runningScrollPane.setViewportView(runningJobsPanel); @@ -123,8 +121,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeJobButton.setEnabled(enabled); this.deprioritizeCaseButton.setEnabled(enabled); }); - runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable.toolTipText()); - completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobType.COMPLETED_JOB); + runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable_toolTipText()); + completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.COMPLETED_JOB); completedJobsPanel.setSize(completedScrollPane.getSize()); completedScrollPane.add(completedJobsPanel); completedScrollPane.setViewportView(completedJobsPanel); @@ -135,7 +133,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { this.deprioritizeJobButton.setEnabled(enabled); this.deprioritizeCaseButton.setEnabled(enabled); }); - completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable.toolTipText()); + completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable_toolTipText()); /* * Must set this flag, otherwise pop up menus don't close properly. */ @@ -143,6 +141,14 @@ final class AutoIngestDashboard extends JPanel implements Observer { UIManager.put("PopupMenu.consumeEventOnClose", false); } + AutoIngestMonitor getMonitor() { + return autoIngestMonitor; + } + + AutoIngestJobsPanel getPendingJobsPanel() { + return pendingJobsPanel; + } + /** * Update status of the services on the dashboard */ @@ -253,7 +259,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override public void update(Observable observable, Object arg) { - EventQueue.invokeLater(new RefreshComponentsTask((JobsSnapshot) arg)); + EventQueue.invokeLater(() -> { + refreshTables(); + }); } /** @@ -262,10 +270,10 @@ final class AutoIngestDashboard extends JPanel implements Observer { * * @param jobsSnapshot The jobs snapshot. */ - private void refreshTables(JobsSnapshot jobsSnapshot) { - pendingJobsPanel.refresh(jobsSnapshot); - runningJobsPanel.refresh(jobsSnapshot); - completedJobsPanel.refresh(jobsSnapshot); + private void refreshTables() { + pendingJobsPanel.refresh(autoIngestMonitor); + runningJobsPanel.refresh(autoIngestMonitor); + completedJobsPanel.refresh(autoIngestMonitor); } /** @@ -473,46 +481,13 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ private void refreshButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshButtonActionPerformed setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - JobsSnapshot jobsSnapshot = autoIngestMonitor.refreshJobsSnapshot(); - refreshTables(jobsSnapshot); + refreshTables(); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_refreshButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."}) - private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed - AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); - if (job != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.prioritizeJob(job); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), job.getManifest().getFilePath()); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } - }//GEN-LAST:event_prioritizeJobButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."}) private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed - AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); - if (job != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = job.getManifest().getCaseName(); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.prioritizeCase(caseName); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), caseName); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } + new PrioritizationAction.PrioritizeCaseAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_prioritizeCaseButtonActionPerformed private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed @@ -521,41 +496,19 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."}) private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed - AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); - if (job != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.deprioritizeJob(job); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), job.getManifest().getFilePath()); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } + new PrioritizationAction.DeprioritizeJobAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_deprioritizeJobButtonActionPerformed @Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."}) private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed - AutoIngestJob job = pendingJobsPanel.getSelectedAutoIngestJob(); - if (job != null) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - String caseName = job.getManifest().getCaseName(); - JobsSnapshot jobsSnapshot; - try { - jobsSnapshot = autoIngestMonitor.deprioritizeCase(caseName); - refreshTables(jobsSnapshot); - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), caseName); - LOGGER.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } - setCursor(Cursor.getDefaultCursor()); - } + new PrioritizationAction.DeprioritizeCaseAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed + @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."}) + private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed + new PrioritizationAction.PrioritizeJobAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); + }//GEN-LAST:event_prioritizeJobButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton clusterMetricsButton; private javax.swing.JScrollPane completedScrollPane; @@ -573,29 +526,4 @@ final class AutoIngestDashboard extends JPanel implements Observer { private javax.swing.JScrollPane runningScrollPane; private javax.swing.JTextField tbServicesStatusMessage; // End of variables declaration//GEN-END:variables - /** - * A task that refreshes the UI components on this panel to reflect a - * snapshot of the pending, running and completed auto ingest jobs lists of - * an auto ingest cluster. - */ - private class RefreshComponentsTask implements Runnable { - - private final JobsSnapshot jobsSnapshot; - - /** - * Constructs a task that refreshes the UI components on this panel to - * reflect a snapshot of the pending, running and completed auto ingest - * jobs lists of an auto ingest cluster. - * - * @param jobsSnapshot The jobs snapshot. - */ - RefreshComponentsTask(JobsSnapshot jobsSnapshot) { - this.jobsSnapshot = jobsSnapshot; - } - - @Override - public void run() { - refreshTables(jobsSnapshot); - } - } } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 83bad29a99..54c7848b4f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.Component; import java.util.List; import java.util.logging.Level; import java.util.stream.Collectors; @@ -74,7 +75,7 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { AutoIngestDashboard dashboard = AutoIngestDashboard.createDashboard(); tc.add(dashboard); dashboard.setSize(dashboard.getPreferredSize()); - + tc.open(); } tc.toFront(); @@ -104,6 +105,24 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { setName(Bundle.CTL_AutoIngestDashboardTopComponent()); } + AutoIngestMonitor getAutoIngestMonitor() { + for (Component comp : getComponents()) { + if (comp instanceof AutoIngestDashboard) { + return ((AutoIngestDashboard) comp).getMonitor(); + } + } + return null; + } + + AutoIngestJobsPanel getPendingJobsPanel() { + for (Component comp : getComponents()) { + if (comp instanceof AutoIngestDashboard) { + return ((AutoIngestDashboard) comp).getPendingJobsPanel(); + } + } + return null; + } + @Override public List availableModes(List modes) { /* diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index c71f0dcd55..cf6e4c7f29 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -18,6 +18,11 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.awt.Cursor; +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; import java.util.Date; @@ -28,8 +33,9 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.NodeProperty; -import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; @@ -54,8 +60,8 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. */ - AutoIngestJobsNode(JobsSnapshot snapshot, AutoIngestJobStatus status) { - super(Children.create(new AutoIngestNodeChildren(snapshot, status), false)); + AutoIngestJobsNode(AutoIngestMonitor autoIngestMonitor, AutoIngestJobStatus status) { + super(Children.create(new AutoIngestNodeChildren(autoIngestMonitor, status), false)); } /** @@ -64,7 +70,7 @@ final class AutoIngestJobsNode extends AbstractNode { static class AutoIngestNodeChildren extends ChildFactory { private final AutoIngestJobStatus autoIngestJobStatus; - private final JobsSnapshot jobsSnapshot; + private final AutoIngestMonitor autoIngestMonitor; /** * Create children nodes for the AutoIngestJobsNode which will each @@ -73,8 +79,8 @@ final class AutoIngestJobsNode extends AbstractNode { * @param snapshot the snapshot which contains the AutoIngestJobs * @param status the status of the jobs being displayed */ - AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status) { - jobsSnapshot = snapshot; + AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status) { + autoIngestMonitor = monitor; autoIngestJobStatus = status; } @@ -83,13 +89,13 @@ final class AutoIngestJobsNode extends AbstractNode { List jobs; switch (autoIngestJobStatus) { case PENDING_JOB: - jobs = jobsSnapshot.getPendingJobs(); + jobs = autoIngestMonitor.refreshJobsSnapshot().getPendingJobs(); break; case RUNNING_JOB: - jobs = jobsSnapshot.getRunningJobs(); + jobs = autoIngestMonitor.refreshJobsSnapshot().getRunningJobs(); break; case COMPLETED_JOB: - jobs = jobsSnapshot.getCompletedJobs(); + jobs = autoIngestMonitor.refreshJobsSnapshot().getCompletedJobs(); break; default: jobs = new ArrayList<>(); @@ -180,6 +186,29 @@ final class AutoIngestJobsNode extends AbstractNode { } return s; } + + @Override + public Action[] getActions(boolean context) { + List actions = new ArrayList<>(); + switch (jobStatus) { + case PENDING_JOB: + actions.add(new PrioritizationAction.PrioritizeJobAction(autoIngestJob)); + actions.add(new PrioritizationAction.PrioritizeCaseAction(autoIngestJob)); + PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(autoIngestJob); + deprioritizeJobAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(deprioritizeJobAction); + PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(autoIngestJob); + deprioritizeCaseAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(deprioritizeCaseAction); + break; + case RUNNING_JOB: + break; + case COMPLETED_JOB: + break; + default: + } + return actions.toArray(new Action[actions.size()]); + } } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 40c306ddd2..ecb829c288 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -129,11 +129,11 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * @param jobsSnapshot - the JobsSnapshot which will provide the new * contents */ - void refresh(AutoIngestMonitor.JobsSnapshot jobsSnapshot) { + void refresh(AutoIngestMonitor autoIngestMonitor) { synchronized (this) { outline.setRowSelectionAllowed(false); Node[] selectedNodes = explorerManager.getSelectedNodes(); - AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); + AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(autoIngestMonitor, status); explorerManager.setRootContext(autoIngestNode); outline.setRowSelectionAllowed(true); if (selectedNodes.length > 0) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java new file mode 100644 index 0000000000..ce6e03071e --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -0,0 +1,162 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.Cursor; +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +abstract class PrioritizationAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + private final AutoIngestJob job; + + PrioritizationAction(AutoIngestJob selectedJob, String title) { + super(title); + job = selectedJob; + } + + protected abstract void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException; + + protected abstract String getErrorMessage(); + + protected AutoIngestJob getJob() { + return job; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (job != null) { + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc != null) { + tc.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EventQueue.invokeLater(() -> { + try { + AutoIngestMonitor monitor = tc.getAutoIngestMonitor(); + AutoIngestJobsPanel pendingPanel = tc.getPendingJobsPanel(); + if (monitor != null && pendingPanel != null) { + modifyPriority(monitor, pendingPanel); + } + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = getErrorMessage(); + // LOGGER.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } finally { + tc.getPendingJobsPanel().setCursor(Cursor.getDefaultCursor()); + } + }); + } + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + + static final class PrioritizeJobAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + PrioritizeJobAction(AutoIngestJob selectedJob) { + super(selectedJob, "Prioritize Job"); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.prioritizeJob(getJob()); + panel.refresh(monitor); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), getJob().getManifest().getFilePath()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + static final class DeprioritizeJobAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + DeprioritizeJobAction(AutoIngestJob selectedJob) { + super(selectedJob, "Deprioritize Job"); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.deprioritizeJob(getJob()); + panel.refresh(monitor); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), getJob().getManifest().getFilePath()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + static final class PrioritizeCaseAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + PrioritizeCaseAction(AutoIngestJob selectedJob) { + super(selectedJob, "Prioritize Case"); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.prioritizeCase(getJob().getManifest().getCaseName()); + panel.refresh(monitor); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), getJob().getManifest().getCaseName()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + static final class DeprioritizeCaseAction extends PrioritizationAction { + + private static final long serialVersionUID = 1L; + + DeprioritizeCaseAction(AutoIngestJob selectedJob) { + super(selectedJob, "Deprioritize Case"); + } + + @Override + protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + monitor.deprioritizeCase(getJob().getManifest().getCaseName()); + panel.refresh(monitor); + } + + @Override + protected String getErrorMessage() { + return String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), getJob().getManifest().getCaseName()); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } +} From a4d602933f30cb3e71f6d4bba8a38177758d4620 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 10 Apr 2018 17:37:09 -0400 Subject: [PATCH 019/100] 3610 change arguement back to being JobsSnapshot for refreshing --- .../autoingest/AutoIngestDashboard.java | 6 ++--- .../autoingest/AutoIngestJobsNode.java | 23 ++++++++----------- .../autoingest/AutoIngestJobsPanel.java | 5 ++-- .../autoingest/PrioritizationAction.java | 8 +++---- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index ece26b0b71..823f94c0a4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -271,9 +271,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { * @param jobsSnapshot The jobs snapshot. */ private void refreshTables() { - pendingJobsPanel.refresh(autoIngestMonitor); - runningJobsPanel.refresh(autoIngestMonitor); - completedJobsPanel.refresh(autoIngestMonitor); + pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); + completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); } /** diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index cf6e4c7f29..ed99ac80fa 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -18,10 +18,6 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; -import java.awt.Cursor; -import java.awt.EventQueue; -import java.awt.event.ActionEvent; -import javax.swing.AbstractAction; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; @@ -33,9 +29,8 @@ import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle.Messages; -import org.openide.windows.WindowManager; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.NodeProperty; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; import org.sleuthkit.autopsy.guiutils.DurationCellRenderer; import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; @@ -60,8 +55,8 @@ final class AutoIngestJobsNode extends AbstractNode { /** * Construct a new AutoIngestJobsNode. */ - AutoIngestJobsNode(AutoIngestMonitor autoIngestMonitor, AutoIngestJobStatus status) { - super(Children.create(new AutoIngestNodeChildren(autoIngestMonitor, status), false)); + AutoIngestJobsNode(JobsSnapshot jobsSnapshot, AutoIngestJobStatus status) { + super(Children.create(new AutoIngestNodeChildren(jobsSnapshot, status), false)); } /** @@ -70,7 +65,7 @@ final class AutoIngestJobsNode extends AbstractNode { static class AutoIngestNodeChildren extends ChildFactory { private final AutoIngestJobStatus autoIngestJobStatus; - private final AutoIngestMonitor autoIngestMonitor; + private final JobsSnapshot jobsSnapshot; /** * Create children nodes for the AutoIngestJobsNode which will each @@ -79,8 +74,8 @@ final class AutoIngestJobsNode extends AbstractNode { * @param snapshot the snapshot which contains the AutoIngestJobs * @param status the status of the jobs being displayed */ - AutoIngestNodeChildren(AutoIngestMonitor monitor, AutoIngestJobStatus status) { - autoIngestMonitor = monitor; + AutoIngestNodeChildren(JobsSnapshot snapshot, AutoIngestJobStatus status) { + jobsSnapshot = snapshot; autoIngestJobStatus = status; } @@ -89,13 +84,13 @@ final class AutoIngestJobsNode extends AbstractNode { List jobs; switch (autoIngestJobStatus) { case PENDING_JOB: - jobs = autoIngestMonitor.refreshJobsSnapshot().getPendingJobs(); + jobs = jobsSnapshot.getPendingJobs(); break; case RUNNING_JOB: - jobs = autoIngestMonitor.refreshJobsSnapshot().getRunningJobs(); + jobs = jobsSnapshot.getRunningJobs(); break; case COMPLETED_JOB: - jobs = autoIngestMonitor.refreshJobsSnapshot().getCompletedJobs(); + jobs = jobsSnapshot.getCompletedJobs(); break; default: jobs = new ArrayList<>(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index ecb829c288..5186d8797b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -29,6 +29,7 @@ import org.openide.nodes.Node; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; +import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnapshot; /** * A panel which displays an outline view with all jobs for a specified status. @@ -129,11 +130,11 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * @param jobsSnapshot - the JobsSnapshot which will provide the new * contents */ - void refresh(AutoIngestMonitor autoIngestMonitor) { + void refresh(JobsSnapshot jobsSnapshot) { synchronized (this) { outline.setRowSelectionAllowed(false); Node[] selectedNodes = explorerManager.getSelectedNodes(); - AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(autoIngestMonitor, status); + AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); explorerManager.setRootContext(autoIngestNode); outline.setRowSelectionAllowed(true); if (selectedNodes.length > 0) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index ce6e03071e..bbf7bf4ffb 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -71,7 +71,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.prioritizeJob(getJob()); - panel.refresh(monitor); + panel.refresh(monitor.getJobsSnapshot()); } @Override @@ -96,7 +96,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.deprioritizeJob(getJob()); - panel.refresh(monitor); + panel.refresh(monitor.getJobsSnapshot()); } @Override @@ -121,7 +121,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.prioritizeCase(getJob().getManifest().getCaseName()); - panel.refresh(monitor); + panel.refresh(monitor.getJobsSnapshot()); } @Override @@ -146,7 +146,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.deprioritizeCase(getJob().getManifest().getCaseName()); - panel.refresh(monitor); + panel.refresh(monitor.getJobsSnapshot()); } @Override From 3bdbdbae6a18fb5dab5f0366aa0c25e5a59feaf5 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 10 Apr 2018 17:52:32 -0400 Subject: [PATCH 020/100] 3610 add comments to PrioritizeAction and its subclasses --- .../autoingest/PrioritizationAction.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index bbf7bf4ffb..7d9012dcf6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -12,20 +12,52 @@ import javax.swing.AbstractAction; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +/** + * Abstract actions which are for the modification of AutoIngestJob or Case + * priority. + */ abstract class PrioritizationAction extends AbstractAction { private static final long serialVersionUID = 1L; private final AutoIngestJob job; + /** + * Construct a new Prioritization action for the selected job + * + * @param selectedJob The job which will be used to determine what has it's + * priority modified + * @param title - the string to represent the action in menus + */ PrioritizationAction(AutoIngestJob selectedJob, String title) { super(title); job = selectedJob; } + /** + * The implementation specific method which modifies job or case priority + * + * @param monitor - the AutoIngestMonitor which can be accessed to change + * the job or case priority + * @param panel - the AutoIngestJobsPanel which will need to be updated + * after the priority is modified + * + * @throws + * org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestMonitorException + */ protected abstract void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException; + /** + * Get the implementation specific error message for if modifyPriority fails + * + * @return the error message for the current implementation + */ protected abstract String getErrorMessage(); + /** + * Gets the job this action is constructed for + * + * @return job - the AutoIngestJob + */ protected AutoIngestJob getJob() { return job; } @@ -60,10 +92,18 @@ abstract class PrioritizationAction extends AbstractAction { return super.clone(); //To change body of generated methods, choose Tools | Templates. } + /** + * Action to prioritize the specified AutoIngestJob + */ static final class PrioritizeJobAction extends PrioritizationAction { private static final long serialVersionUID = 1L; + /** + * Construct a new PrioritizeJobAction + * + * @param selectedJob - the AutoIngestJob to be prioritized + */ PrioritizeJobAction(AutoIngestJob selectedJob) { super(selectedJob, "Prioritize Job"); } @@ -85,10 +125,18 @@ abstract class PrioritizationAction extends AbstractAction { } } + /** + * Action to deprioritize the specified AutoIngestJob + */ static final class DeprioritizeJobAction extends PrioritizationAction { private static final long serialVersionUID = 1L; + /** + * Construct a new DeprioritizeJobAction + * + * @param selectedJob - the AutoIngestJob to be deprioritized + */ DeprioritizeJobAction(AutoIngestJob selectedJob) { super(selectedJob, "Deprioritize Job"); } @@ -110,10 +158,20 @@ abstract class PrioritizationAction extends AbstractAction { } } + /** + * Action to prioritize all jobs for the case which the specified + * AutoIngestJob is a part of. + */ static final class PrioritizeCaseAction extends PrioritizationAction { private static final long serialVersionUID = 1L; + /** + * Construct a new PrioritizeCaseAction + * + * @param selectedJob - the AutoIngestJob which should have it's case + * prioritized + */ PrioritizeCaseAction(AutoIngestJob selectedJob) { super(selectedJob, "Prioritize Case"); } @@ -135,10 +193,20 @@ abstract class PrioritizationAction extends AbstractAction { } } + /** + * Action to deprioritize all jobs for the case which the specified + * AutoIngestJob is a part of. + */ static final class DeprioritizeCaseAction extends PrioritizationAction { private static final long serialVersionUID = 1L; + /** + * Construct a new DeprioritizeCaseAction + * + * @param selectedJob - the AutoIngestJob which should have it's case + * deprioritized + */ DeprioritizeCaseAction(AutoIngestJob selectedJob) { super(selectedJob, "Deprioritize Case"); } From 205f5b5bbe6dd47edecf2c4e00010e1ef86aeafb Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 10 Apr 2018 18:10:03 -0400 Subject: [PATCH 021/100] 3610 move bundle messages to where they are used --- .../autoingest/AutoIngestDashboard.java | 5 --- .../AutoIngestDashboardTopComponent.java | 14 ++++++ .../autoingest/PrioritizationAction.java | 44 ++++++++++++++----- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 823f94c0a4..e5f150948d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -485,7 +485,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_refreshButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.casePrioritization=Failed to prioritize case \"%s\"."}) private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed new PrioritizationAction.PrioritizeCaseAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_prioritizeCaseButtonActionPerformed @@ -493,18 +492,14 @@ final class AutoIngestDashboard extends JPanel implements Observer { private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed new AutoIngestMetricsDialog(this.getTopLevelAncestor()); }//GEN-LAST:event_clusterMetricsButtonActionPerformed - - @Messages({"AutoIngestDashboard.errorMessage.jobDeprioritization=Failed to deprioritize job \"%s\"."}) private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed new PrioritizationAction.DeprioritizeJobAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_deprioritizeJobButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.caseDeprioritization=Failed to deprioritize case \"%s\"."}) private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed new PrioritizationAction.DeprioritizeCaseAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed - @Messages({"AutoIngestDashboard.errorMessage.jobPrioritization=Failed to prioritize job \"%s\"."}) private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed new PrioritizationAction.PrioritizeJobAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); }//GEN-LAST:event_prioritizeJobButtonActionPerformed diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 54c7848b4f..6b96c13609 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -105,6 +105,13 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { setName(Bundle.CTL_AutoIngestDashboardTopComponent()); } + /** + * Get the AutoIngestMonitor from the current AutoIngestDashboard if there + * is one. + * + * @return the current AutoIngestMonitor or null if there is no + * AutoIngestDashboard + */ AutoIngestMonitor getAutoIngestMonitor() { for (Component comp : getComponents()) { if (comp instanceof AutoIngestDashboard) { @@ -114,6 +121,13 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { return null; } + /** + * Get the pending jobs panel from the current AutoIngestDashboard if there + * is one. + * + * @return the AutoIngestJobsPanel which contains the pending AutoIngestJobs + * or null if there is no AutoIngestDashboard + */ AutoIngestJobsPanel getPendingJobsPanel() { for (Component comp : getComponents()) { if (comp instanceof AutoIngestDashboard) { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index 7d9012dcf6..ba461077a6 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.experimental.autoingest; @@ -9,6 +22,7 @@ import java.awt.Cursor; import java.awt.EventQueue; import java.awt.event.ActionEvent; import javax.swing.AbstractAction; +import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; @@ -95,6 +109,8 @@ abstract class PrioritizationAction extends AbstractAction { /** * Action to prioritize the specified AutoIngestJob */ + @Messages({"PrioritizationAction.prioritizeJobAction.title=Prioritize Job", + "PrioritizationAction.prioritizeJobAction.error=Failed to prioritize job \"%s\"."}) static final class PrioritizeJobAction extends PrioritizationAction { private static final long serialVersionUID = 1L; @@ -105,7 +121,7 @@ abstract class PrioritizationAction extends AbstractAction { * @param selectedJob - the AutoIngestJob to be prioritized */ PrioritizeJobAction(AutoIngestJob selectedJob) { - super(selectedJob, "Prioritize Job"); + super(selectedJob, Bundle.PrioritizationAction_prioritizeJobAction_title()); } @Override @@ -116,7 +132,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected String getErrorMessage() { - return String.format(Bundle.AutoIngestDashboard_errorMessage_jobPrioritization(), getJob().getManifest().getFilePath()); + return String.format(Bundle.PrioritizationAction_prioritizeJobAction_error(), getJob().getManifest().getFilePath()); } @Override @@ -128,6 +144,8 @@ abstract class PrioritizationAction extends AbstractAction { /** * Action to deprioritize the specified AutoIngestJob */ + @Messages({"PrioritizationAction.deprioritizeJobAction.title=Deprioritize Job", + "PrioritizationAction.deprioritizeJobAction.error=Failed to deprioritize job \"%s\"."}) static final class DeprioritizeJobAction extends PrioritizationAction { private static final long serialVersionUID = 1L; @@ -138,7 +156,7 @@ abstract class PrioritizationAction extends AbstractAction { * @param selectedJob - the AutoIngestJob to be deprioritized */ DeprioritizeJobAction(AutoIngestJob selectedJob) { - super(selectedJob, "Deprioritize Job"); + super(selectedJob, Bundle.PrioritizationAction_deprioritizeJobAction_title()); } @Override @@ -149,7 +167,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected String getErrorMessage() { - return String.format(Bundle.AutoIngestDashboard_errorMessage_jobDeprioritization(), getJob().getManifest().getFilePath()); + return String.format(Bundle.PrioritizationAction_deprioritizeJobAction_error(), getJob().getManifest().getFilePath()); } @Override @@ -162,6 +180,8 @@ abstract class PrioritizationAction extends AbstractAction { * Action to prioritize all jobs for the case which the specified * AutoIngestJob is a part of. */ + @Messages({"PrioritizationAction.prioritizeCaseAction.title=Prioritize Case", + "PrioritizationAction.prioritizeCaseAction.error==Failed to prioritize case \"%s\"."}) static final class PrioritizeCaseAction extends PrioritizationAction { private static final long serialVersionUID = 1L; @@ -173,7 +193,7 @@ abstract class PrioritizationAction extends AbstractAction { * prioritized */ PrioritizeCaseAction(AutoIngestJob selectedJob) { - super(selectedJob, "Prioritize Case"); + super(selectedJob, Bundle.PrioritizationAction_prioritizeCaseAction_title()); } @Override @@ -184,7 +204,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected String getErrorMessage() { - return String.format(Bundle.AutoIngestDashboard_errorMessage_casePrioritization(), getJob().getManifest().getCaseName()); + return String.format(Bundle.PrioritizationAction_prioritizeCaseAction_error(), getJob().getManifest().getCaseName()); } @Override @@ -197,6 +217,8 @@ abstract class PrioritizationAction extends AbstractAction { * Action to deprioritize all jobs for the case which the specified * AutoIngestJob is a part of. */ + @Messages({"PrioritizationAction.deprioritizeCaseAction.title=Deprioritize Case", + "PrioritizationAction.deprioritizeCaseAction.error=Failed to deprioritize case \"%s\"."}) static final class DeprioritizeCaseAction extends PrioritizationAction { private static final long serialVersionUID = 1L; @@ -208,7 +230,7 @@ abstract class PrioritizationAction extends AbstractAction { * deprioritized */ DeprioritizeCaseAction(AutoIngestJob selectedJob) { - super(selectedJob, "Deprioritize Case"); + super(selectedJob, Bundle.PrioritizationAction_deprioritizeCaseAction_title()); } @Override @@ -219,7 +241,7 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected String getErrorMessage() { - return String.format(Bundle.AutoIngestDashboard_errorMessage_caseDeprioritization(), getJob().getManifest().getCaseName()); + return String.format(Bundle.PrioritizationAction_deprioritizeCaseAction_error(), getJob().getManifest().getCaseName()); } @Override From 85dff9aee439cd427099f10bbb0b6f66eea86100 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 10 Apr 2018 18:21:50 -0400 Subject: [PATCH 022/100] 3610 add logger usage to new PrioritizationAction class --- .../experimental/autoingest/PrioritizationAction.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index ba461077a6..14e2fed384 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -21,9 +21,11 @@ package org.sleuthkit.autopsy.experimental.autoingest; import java.awt.Cursor; import java.awt.EventQueue; import java.awt.event.ActionEvent; +import java.util.logging.Level; import javax.swing.AbstractAction; import org.openide.util.NbBundle.Messages; import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; /** @@ -33,6 +35,7 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; abstract class PrioritizationAction extends AbstractAction { private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(PrioritizationAction.class.getName()); private final AutoIngestJob job; /** @@ -88,10 +91,11 @@ abstract class PrioritizationAction extends AbstractAction { AutoIngestJobsPanel pendingPanel = tc.getPendingJobsPanel(); if (monitor != null && pendingPanel != null) { modifyPriority(monitor, pendingPanel); + pendingPanel.refresh(monitor.getJobsSnapshot()); } } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { String errorMessage = getErrorMessage(); - // LOGGER.log(Level.SEVERE, errorMessage, ex); + logger.log(Level.SEVERE, errorMessage, ex); MessageNotifyUtil.Message.error(errorMessage); } finally { tc.getPendingJobsPanel().setCursor(Cursor.getDefaultCursor()); @@ -127,7 +131,6 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.prioritizeJob(getJob()); - panel.refresh(monitor.getJobsSnapshot()); } @Override @@ -162,7 +165,6 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.deprioritizeJob(getJob()); - panel.refresh(monitor.getJobsSnapshot()); } @Override @@ -199,7 +201,6 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.prioritizeCase(getJob().getManifest().getCaseName()); - panel.refresh(monitor.getJobsSnapshot()); } @Override @@ -236,7 +237,6 @@ abstract class PrioritizationAction extends AbstractAction { @Override protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.deprioritizeCase(getJob().getManifest().getCaseName()); - panel.refresh(monitor.getJobsSnapshot()); } @Override From 2ba0df6f8c136845641cff43eeb64e72b83dafa5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 11 Apr 2018 14:34:50 -0400 Subject: [PATCH 023/100] Code cleanup and renaming to address PR review comments. --- ...itor.java => EnterpriseHealthMonitor.java} | 114 ++++++++++-------- .../HealthMonitorCaseEventListener.java | 53 -------- .../autopsy/healthmonitor/Installer.java | 5 +- .../autopsy/keywordsearch/Ingester.java | 6 +- .../autopsy/keywordsearch/Server.java | 6 +- 5 files changed, 73 insertions(+), 111 deletions(-) rename Core/src/org/sleuthkit/autopsy/healthmonitor/{ServicesHealthMonitor.java => EnterpriseHealthMonitor.java} (89%) delete mode 100644 Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java similarity index 89% rename from Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java rename to Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 33855df091..35bc6b6bfa 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/ServicesHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -19,6 +19,8 @@ package org.sleuthkit.autopsy.healthmonitor; import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; @@ -27,16 +29,21 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import org.apache.commons.dbcp2.BasicDataSource; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.ThreadUtils; import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; @@ -47,18 +54,21 @@ import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; * Modules will call getTimingMetric() before the code to be timed to get a TimingMetric object * Modules will call submitTimingMetric() with the obtained TimingMetric object to log it */ -public class ServicesHealthMonitor { +public final class EnterpriseHealthMonitor implements PropertyChangeListener { - private final static Logger logger = Logger.getLogger(ServicesHealthMonitor.class.getName()); - private final static String DATABASE_NAME = "ServicesHealthMonitor"; - private final static String MODULE_NAME = "ServicesHealthMonitor"; + private final static Logger logger = Logger.getLogger(EnterpriseHealthMonitor.class.getName()); + private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; + private final static String MODULE_NAME = "EnterpriseHealthMonitor"; private final static String IS_ENABLED_KEY = "is_enabled"; private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 0); private static final AtomicBoolean isEnabled = new AtomicBoolean(false); - private static ServicesHealthMonitor instance; + private static EnterpriseHealthMonitor instance; + + private final ExecutorService healthMonitorExecutor; + private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; private ScheduledThreadPoolExecutor healthMonitorOutputTimer; private final Map timingInfoMap; @@ -66,19 +76,22 @@ public class ServicesHealthMonitor { private BasicDataSource connectionPool = null; private String hostName; - private ServicesHealthMonitor() throws HealthMonitorException { + private EnterpriseHealthMonitor() throws HealthMonitorException { // Create the map to collect timing metrics. The map will exist regardless // of whether the monitor is enabled. timingInfoMap = new HashMap<>(); + // Set up the executor to handle case events + healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); + // Get the host name try { hostName = java.net.InetAddress.getLocalHost().getHostName(); } catch (java.net.UnknownHostException ex) { - // Continue on but log a warning - logger.log(Level.WARNING, "Unable to look up host name"); - hostName = "unknown"; + // Continue on, but log the error and generate a UUID to use for this session + hostName = UUID.randomUUID().toString(); + logger.log(Level.SEVERE, "Unable to look up host name - falling back to UUID " + hostName, ex); } // Read from module settings to determine if the module is enabled @@ -100,13 +113,14 @@ public class ServicesHealthMonitor { } /** - * Get the instance of the ServicesHealthMonitor + * Get the instance of the EnterpriseHealthMonitor * @return the instance * @throws HealthMonitorException */ - synchronized static ServicesHealthMonitor getInstance() throws HealthMonitorException { + synchronized static EnterpriseHealthMonitor getInstance() throws HealthMonitorException { if (instance == null) { - instance = new ServicesHealthMonitor(); + instance = new EnterpriseHealthMonitor(); + Case.addPropertyChangeListener(instance); } return instance; } @@ -122,15 +136,15 @@ public class ServicesHealthMonitor { logger.log(Level.INFO, "Activating Servies Health Monitor"); if (!UserPreferences.getIsMultiUserModeEnabled()) { - throw new HealthMonitorException("Multi user mode is not enabled - can not activate services health monitor"); - } - // Set up database (if needed) - CoordinationService.Lock lock = getExclusiveDbLock(); - if(lock == null) { - throw new HealthMonitorException("Error getting database lock"); + throw new HealthMonitorException("Multi user mode is not enabled - can not activate health monitor"); } - try { + // Set up database (if needed) + try (CoordinationService.Lock lock = getExclusiveDbLock()) { + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + // Check if the database exists if (! databaseExists()) { @@ -142,12 +156,8 @@ public class ServicesHealthMonitor { initializeDatabaseSchema(); } - } finally { - try { - lock.release(); - } catch (CoordinationService.CoordinationServiceException ex) { - throw new HealthMonitorException("Error releasing database lock", ex); - } + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); } // Clear out any old data @@ -182,10 +192,9 @@ public class ServicesHealthMonitor { * Start the ScheduledThreadPoolExecutor that will handle the database writes. */ private synchronized void startTimer() { - if(healthMonitorOutputTimer != null) { - // Make sure the previous executor (if it exists) has been stopped - healthMonitorOutputTimer.shutdown(); - } + // Make sure the previous executor (if it exists) has been stopped + stopTimer(); + healthMonitorOutputTimer = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("health_monitor_timer").build()); healthMonitorOutputTimer.scheduleWithFixedDelay(new DatabaseWriteTask(), DATABASE_WRITE_INTERVAL, DATABASE_WRITE_INTERVAL, TimeUnit.MINUTES); } @@ -195,7 +204,7 @@ public class ServicesHealthMonitor { */ private synchronized void stopTimer() { if(healthMonitorOutputTimer != null) { - healthMonitorOutputTimer.shutdown(); + ThreadUtils.shutDownTaskExecutor(healthMonitorOutputTimer); } } @@ -203,7 +212,7 @@ public class ServicesHealthMonitor { * Called from the installer to set up the Health Monitor instance at startup. * @throws HealthMonitorException */ - static synchronized void startUp() throws HealthMonitorException { + static synchronized void startUpIfEnabled() throws HealthMonitorException { getInstance(); } @@ -235,7 +244,7 @@ public class ServicesHealthMonitor { * Get a metric that will measure the time to execute a section of code. * Call this before the section of code to be timed and then * submit it afterward using submitTimingMetric(). - * This method is safe to call regardless of whether the Services Health + * This method is safe to call regardless of whether the Enterprise Health * Monitor is enabled. * @param name A short but descriptive name describing the code being timed. * This name will appear in the UI. @@ -251,7 +260,7 @@ public class ServicesHealthMonitor { /** * Submit the metric that was previously obtained through getTimingMetric(). * Call this immediately after the section of code being timed. - * This method is safe to call regardless of whether the Services Health + * This method is safe to call regardless of whether the Enterprise Health * Monitor is enabled. * @param metric The TimingMetric object obtained from getTimingMetric() */ @@ -318,13 +327,12 @@ public class ServicesHealthMonitor { logger.log(Level.INFO, "Writing health monitor metrics to database"); - // Write to the database - CoordinationService.Lock lock = getSharedDbLock(); - if(lock == null) { - throw new HealthMonitorException("Error getting database lock"); - } - - try { + // Write to the database + try (CoordinationService.Lock lock = getSharedDbLock()) { + if(lock == null) { + throw new HealthMonitorException("Error getting database lock"); + } + Connection conn = connect(); if(conn == null) { throw new HealthMonitorException("Error getting database connection"); @@ -357,12 +365,8 @@ public class ServicesHealthMonitor { logger.log(Level.SEVERE, "Error closing Connection.", ex); } } - } finally { - try { - lock.release(); - } catch (CoordinationService.CoordinationServiceException ex) { - throw new HealthMonitorException("Error releasing database lock", ex); - } + } catch (CoordinationService.CoordinationServiceException ex) { + throw new HealthMonitorException("Error releasing database lock", ex); } } @@ -383,7 +387,7 @@ public class ServicesHealthMonitor { String createCommand = "SELECT 1 AS result FROM pg_database WHERE datname='" + DATABASE_NAME + "'"; rs = statement.executeQuery(createCommand); if(rs.next()) { - logger.log(Level.INFO, "Existing Services Health Monitor database found"); + logger.log(Level.INFO, "Existing Enterprise Health Monitor database found"); return true; } } finally { @@ -648,6 +652,20 @@ public class ServicesHealthMonitor { } } + @Override + public void propertyChange(PropertyChangeEvent evt) { + + switch (Case.Events.valueOf(evt.getPropertyName())) { + + case CURRENT_CASE: + if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { + // When a case is closed, write the current metrics to the database + healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask()); + } + break; + } + } + /** * Get an exclusive lock for the health monitor database. * Acquire this before creating, initializing, or updating the database schema. @@ -663,7 +681,7 @@ public class ServicesHealthMonitor { } throw new HealthMonitorException("Error acquiring database lock"); } catch (InterruptedException | CoordinationService.CoordinationServiceException ex){ - throw new HealthMonitorException("Error acquiring database lock"); + throw new HealthMonitorException("Error acquiring database lock", ex); } } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java deleted file mode 100644 index f6c5583692..0000000000 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/HealthMonitorCaseEventListener.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Autopsy Forensic Browser - * - * Copyright 2018 Basis Technology Corp. - * Contact: carrier sleuthkit org - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.sleuthkit.autopsy.healthmonitor; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import org.sleuthkit.autopsy.casemodule.Case; - -/** - * Listener for case events - */ -final class HealthMonitorCaseEventListener implements PropertyChangeListener { - - private final ExecutorService healthMonitorExecutor; - private static final String HEALTH_MONITOR_EVENT_THREAD_NAME = "Health-Monitor-Event-Listener-%d"; - - HealthMonitorCaseEventListener() { - healthMonitorExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(HEALTH_MONITOR_EVENT_THREAD_NAME).build()); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - - switch (Case.Events.valueOf(evt.getPropertyName())) { - - case CURRENT_CASE: - if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { - // When a case is closed, write the current metrics to the database - healthMonitorExecutor.submit(new ServicesHealthMonitor.DatabaseWriteTask()); - } - break; - } - } -} diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java index 5d294aca31..61ea5a5244 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/Installer.java @@ -26,7 +26,6 @@ import org.sleuthkit.autopsy.coreutils.Logger; public class Installer extends ModuleInstall { private static final Logger logger = Logger.getLogger(Installer.class.getName()); - private final HealthMonitorCaseEventListener pcl = new HealthMonitorCaseEventListener(); private static final long serialVersionUID = 1L; private static Installer instance; @@ -45,10 +44,8 @@ public class Installer extends ModuleInstall { @Override public void restored() { - Case.addPropertyChangeListener(pcl); - try { - ServicesHealthMonitor.startUp(); + EnterpriseHealthMonitor.startUpIfEnabled(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error starting health services monitor", ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index f956513eca..b9c4541c7b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -27,7 +27,7 @@ import org.apache.solr.common.SolrInputDocument; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.keywordsearch.Chunker.Chunk; @@ -237,9 +237,9 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Index chunk"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); - ServicesHealthMonitor.submitTimingMetric(metric); + EnterpriseHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 5748524ada..eea4d2914e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -70,7 +70,7 @@ import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PlatformUtil; -import org.sleuthkit.autopsy.healthmonitor.ServicesHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchServiceException; import org.sleuthkit.datamodel.Content; @@ -775,9 +775,9 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = ServicesHealthMonitor.getTimingMetric("Solr: Connectivity check"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); - ServicesHealthMonitor.submitTimingMetric(metric); + EnterpriseHealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); From e8c88eb86f9c5173e085f8696f737b73583b5eb9 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 11 Apr 2018 14:54:08 -0400 Subject: [PATCH 024/100] Remove pushing selectoin to result viewers from DataResultPanel --- .../AbstractDataResultViewer.java | 8 +- .../corecomponents/DataResultPanel.java | 82 +++++++++---------- .../corecomponents/DataResultViewerTable.java | 6 +- .../DataResultViewerThumbnail.java | 20 ++--- 4 files changed, 54 insertions(+), 62 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 26d58d6eff..38577b55ba 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -38,7 +38,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider { private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); - protected transient ExplorerManager em; + protected transient ExplorerManager explorerManager; /** * This constructor is intended to allow an AbstractDataResultViewer to use @@ -51,7 +51,7 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView * @param explorerManager */ AbstractDataResultViewer(ExplorerManager explorerManager) { - this.em = explorerManager; + this.explorerManager = explorerManager; } /** @@ -91,13 +91,13 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView @Override public ExplorerManager getExplorerManager() { - return this.em; + return this.explorerManager; } @Override public void setSelectedNodes(Node[] selected) { try { - this.em.setSelectedNodes(selected); + this.explorerManager.setSelectedNodes(selected); } catch (PropertyVetoException ex) { logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 9751bb024a..d3fd551d54 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -74,7 +74,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C private final List resultViewers = new ArrayList<>(); private boolean isMain; private ExplorerManager explorerManager; - private ExplorerManagerNodeSelectionListener emNodeSelectionListener; + private ExplorerManagerListener emNodeSelectionListener; private Node rootNode; private final RootNodeListener rootNodeListener = new RootNodeListener(); private boolean listeningToTabbedPane; @@ -172,12 +172,6 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C setTitle(title); } - private DataResultPanel(boolean isMain, DataContent contentView) { - this.isMain = isMain; - this.contentView = contentView; - initComponents(); - } - /** * Constructs a DataResultPanel with the a custom DataContent. * @@ -189,6 +183,18 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C this(false, customContentView); } + /** + * Constructs a DataResultPanel with a given content view. + * + * @param isMain + * @param contentView + */ + private DataResultPanel(boolean isMain, DataContent contentView) { + this.isMain = isMain; + this.contentView = contentView; + initComponents(); + } + /** * Gets the preferred identifier for this panel in the window system. * @@ -279,7 +285,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * selections to be made in all of the result viewers. */ explorerManager = ExplorerManager.find(this); - emNodeSelectionListener = new ExplorerManagerNodeSelectionListener(); + emNodeSelectionListener = new ExplorerManagerListener(); explorerManager.addPropertyChangeListener(emNodeSelectionListener); } @@ -528,47 +534,33 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Responds to node selection change events from the explorer manager. + * Responds to node selection change events from the explorer manager of + * this panel's parent top component. The selected nodes are passed to the + * content view. This is how the results view and the content view are kept + * in sync. It is therefore required that all of the result viewers in this + * panel use the explorer manager of the parent top component. This supports + * this way of passing the selection to the content view, plus the exposure + * of the selection to through the actions global context, which is needed + * for multiple selection. */ - private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + private class ExplorerManagerListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { - try { - Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { - return; - } - - /* - * Only interested in node selection events. - */ - if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { - setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - try { - if (contentView != null) { - Node[] selectedNodes = explorerManager.getSelectedNodes(); - - /* - * Pass the selected nodes to all of the result viewers - * sharing this explorer manager. - */ - resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); - - /* - * Passing null signals that either multiple nodes are - * selected, or no nodes are selected. This is important - * to the content view, since content views only work - * for a single node.. - */ - if (1 == selectedNodes.length) { - contentView.setNode(selectedNodes[0]); - } else { - contentView.setNode(null); - } - } - } finally { - setCursor(null); + if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES) && contentView != null) { + /* + * Pass a single node selection in a result viewer to the + * content view. Note that passing null to the content view + * signals that either multiple nodes are selected, or a + * previous selection has been cleared. This is important to the + * content view, since its child content viewers only work for a + * single node. + */ + Node[] selectedNodes = explorerManager.getSelectedNodes(); + if (1 == selectedNodes.length) { + contentView.setNode(selectedNodes[0]); + } else { + contentView.setNode(null); } } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index fe21a4ca55..0a81b78f94 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -224,11 +224,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { if (hasChildren) { currentRoot = selectedNode; - em.setRootContext(currentRoot); + explorerManager.setRootContext(currentRoot); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); // make empty node + explorerManager.setRootContext(emptyNode); // make empty node outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); /* @@ -314,7 +314,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Node childNode = childNodes[i]; if (selectedChildInfo.matches(childNode)) { try { - em.setSelectedNodes(new Node[]{childNode}); + explorerManager.setSelectedNodes(new Node[]{childNode}); } catch (PropertyVetoException ex) { logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 6d5cd54f5b..8eb8d53929 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -108,7 +108,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { private void initialize() { initComponents(); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - em.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); + explorerManager.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), @@ -297,7 +297,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (thumbSize != newIconSize) { thumbSize = newIconSize; - Node root = em.getRootContext(); + Node root = explorerManager.getRootContext(); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); @@ -307,13 +307,13 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { // update even though the new and old Node values are identical. This in turn // will cause the entire view to update completely. After this we // immediately set the node back to the current child by calling switchPage(). - em.setExploredContext(root); + explorerManager.setExploredContext(root); switchPage(); } }//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed - List> childProperties = ResultViewerPersistence.getAllChildProperties(em.getRootContext(), 100); + List> childProperties = ResultViewerPersistence.getAllChildProperties(explorerManager.getRootContext(), 100); SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); @@ -395,12 +395,12 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); - em.setRootContext(root); + explorerManager.setRootContext(root); } else { tfn = null; tvc = null; Node emptyNode = new AbstractNode(Children.LEAF); - em.setRootContext(emptyNode); + explorerManager.setRootContext(emptyNode); iconView.setBackground(Color.BLACK); } } finally { @@ -488,9 +488,9 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); progress.start(); progress.switchToIndeterminate(); - Node root = em.getRootContext(); + Node root = explorerManager.getRootContext(); Node pageNode = root.getChildren().getNodeAt(curPage - 1); - em.setExploredContext(pageNode); + explorerManager.setExploredContext(pageNode); curPageImages = pageNode.getChildren().getNodesCount(); return null; } @@ -618,7 +618,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } }); - em.setExploredContext(pageNode); + explorerManager.setExploredContext(pageNode); } updateControls(); @@ -647,7 +647,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - Node[] selectedNodes = em.getSelectedNodes(); + Node[] selectedNodes = explorerManager.getSelectedNodes(); if (selectedNodes.length == 1) { AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); if (af == null) { From eb898c5fcecba7323ec0805a86d302642a3b8f29 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 11 Apr 2018 15:48:29 -0400 Subject: [PATCH 025/100] 3610 Priority column now displays yes or no for prioritized --- .../AutoIngestDashboardTopComponent.form | 2 +- .../AutoIngestDashboardTopComponent.java | 26 ++--------- .../autoingest/AutoIngestJobsNode.java | 44 ++++++++++--------- .../autoingest/AutoIngestJobsPanel.java | 26 +++++------ .../autoingest/PrioritizationAction.java | 43 +++++++++--------- 5 files changed, 61 insertions(+), 80 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form index 63887aceb0..5f3eab1a5f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.form @@ -25,4 +25,4 @@
- \ No newline at end of file + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 6b96c13609..9a5553519c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -106,32 +106,14 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { } /** - * Get the AutoIngestMonitor from the current AutoIngestDashboard if there - * is one. + * Get the current AutoIngestDashboard if there is one. * - * @return the current AutoIngestMonitor or null if there is no - * AutoIngestDashboard + * @return the current AutoIngestDashboard or null if there is not one */ - AutoIngestMonitor getAutoIngestMonitor() { + AutoIngestDashboard getAutoIngestDashboard() { for (Component comp : getComponents()) { if (comp instanceof AutoIngestDashboard) { - return ((AutoIngestDashboard) comp).getMonitor(); - } - } - return null; - } - - /** - * Get the pending jobs panel from the current AutoIngestDashboard if there - * is one. - * - * @return the AutoIngestJobsPanel which contains the pending AutoIngestJobs - * or null if there is no AutoIngestDashboard - */ - AutoIngestJobsPanel getPendingJobsPanel() { - for (Component comp : getComponents()) { - if (comp instanceof AutoIngestDashboard) { - return ((AutoIngestDashboard) comp).getPendingJobsPanel(); + return (AutoIngestDashboard) comp; } } return null; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index ed99ac80fa..0e5e94159c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -41,15 +41,15 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; final class AutoIngestJobsNode extends AbstractNode { @Messages({ - "AutoIngestNode.caseName.text=Case Name", - "AutoIngestNode.dataSource.text=Data Source", - "AutoIngestNode.hostName.text=Host Name", - "AutoIngestNode.stage.text=Stage", - "AutoIngestNode.stageTime.text=Time in Stage", - "AutoIngestNode.jobCreated.text=Job Created", - "AutoIngestNode.jobCompleted.text=Job Completed", - "AutoIngestNode.priority.text=Priority", - "AutoIngestNode.status.text=Status" + "AutoIngestJobsNode.caseName.text=Case Name", + "AutoIngestJobsNode.dataSource.text=Data Source", + "AutoIngestJobsNode.hostName.text=Host Name", + "AutoIngestJobsNode.stage.text=Stage", + "AutoIngestJobsNode.stageTime.text=Time in Stage", + "AutoIngestJobsNode.jobCreated.text=Job Created", + "AutoIngestJobsNode.jobCompleted.text=Job Completed", + "AutoIngestJobsNode.priority.text=Prioritized", + "AutoIngestJobsNode.status.text=Status" }) /** @@ -85,6 +85,7 @@ final class AutoIngestJobsNode extends AbstractNode { switch (autoIngestJobStatus) { case PENDING_JOB: jobs = jobsSnapshot.getPendingJobs(); + jobs.sort(new AutoIngestJob.PriorityComparator()); break; case RUNNING_JOB: jobs = jobsSnapshot.getRunningJobs(); @@ -142,6 +143,9 @@ final class AutoIngestJobsNode extends AbstractNode { } @Override + @Messages({"AutoIngestJobsNode.prioritized.true=Yes", + "AutoIngestJobsNode.prioritized.false=No" + }) protected Sheet createSheet() { Sheet s = super.createSheet(); Sheet.Set ss = s.get(Sheet.PROPERTIES); @@ -149,32 +153,32 @@ final class AutoIngestJobsNode extends AbstractNode { ss = Sheet.createPropertiesSet(); s.put(ss); } - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), Bundle.AutoIngestNode_caseName_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), Bundle.AutoIngestJobsNode_caseName_text(), autoIngestJob.getManifest().getCaseName())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), autoIngestJob.getManifest().getDataSourcePath().getFileName().toString())); switch (jobStatus) { case PENDING_JOB: - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text(), - autoIngestJob.getPriority())); + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text(), + autoIngestJob.getPriority() > 0 ? Bundle.AutoIngestJobsNode_prioritized_true() : Bundle.AutoIngestJobsNode_prioritized_false())); break; case RUNNING_JOB: AutoIngestJob.StageDetails status = autoIngestJob.getProcessingStageDetails(); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), autoIngestJob.getProcessingHostName())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), status.getDescription())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text(), DurationCellRenderer.longToDurationString((Date.from(Instant.now()).getTime()) - (status.getStartDate().getTime())))); break; case COMPLETED_JOB: - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), autoIngestJob.getManifest().getDateFileCreated())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), autoIngestJob.getCompletedDate())); - ss.put(new NodeProperty<>(Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text(), + ss.put(new NodeProperty<>(Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text(), autoIngestJob.getErrorsOccurred() ? StatusIconCellRenderer.Status.WARNING : StatusIconCellRenderer.Status.OK)); break; default: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 5186d8797b..e21d8f569c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -63,29 +63,27 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa switch (status) { case PENDING_JOB: - outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), - Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), - Bundle.AutoIngestNode_priority_text(), Bundle.AutoIngestNode_priority_text()); - outline.setColumnSorted(3, false, 1); - outline.setColumnSorted(0, true, 2); + outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), + Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text()); break; case RUNNING_JOB: - outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), - Bundle.AutoIngestNode_hostName_text(), Bundle.AutoIngestNode_hostName_text(), - Bundle.AutoIngestNode_stage_text(), Bundle.AutoIngestNode_stage_text(), - Bundle.AutoIngestNode_stageTime_text(), Bundle.AutoIngestNode_stageTime_text()); + outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), + Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), + Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text()); outline.setColumnSorted(0, true, 1); break; case COMPLETED_JOB: - outlineView.setPropertyColumns(Bundle.AutoIngestNode_dataSource_text(), Bundle.AutoIngestNode_dataSource_text(), - Bundle.AutoIngestNode_jobCreated_text(), Bundle.AutoIngestNode_jobCreated_text(), - Bundle.AutoIngestNode_jobCompleted_text(), Bundle.AutoIngestNode_jobCompleted_text(), - Bundle.AutoIngestNode_status_text(), Bundle.AutoIngestNode_status_text()); + outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), + Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), + Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), + Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text()); outline.setColumnSorted(3, false, 1); break; default: } - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestNode_caseName_text()); + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestJobsNode_caseName_text()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); outline.setRootVisible(false); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java index 14e2fed384..d50a85f906 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PrioritizationAction.java @@ -55,13 +55,11 @@ abstract class PrioritizationAction extends AbstractAction { * * @param monitor - the AutoIngestMonitor which can be accessed to change * the job or case priority - * @param panel - the AutoIngestJobsPanel which will need to be updated - * after the priority is modified * * @throws * org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestMonitorException */ - protected abstract void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException; + protected abstract void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException; /** * Get the implementation specific error message for if modifyPriority fails @@ -84,23 +82,22 @@ abstract class PrioritizationAction extends AbstractAction { if (job != null) { final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); if (tc != null) { - tc.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - EventQueue.invokeLater(() -> { - try { - AutoIngestMonitor monitor = tc.getAutoIngestMonitor(); - AutoIngestJobsPanel pendingPanel = tc.getPendingJobsPanel(); - if (monitor != null && pendingPanel != null) { - modifyPriority(monitor, pendingPanel); - pendingPanel.refresh(monitor.getJobsSnapshot()); + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + dashboard.getPendingJobsPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + EventQueue.invokeLater(() -> { + try { + modifyPriority(dashboard.getMonitor()); + dashboard.getPendingJobsPanel().refresh(dashboard.getMonitor().getJobsSnapshot()); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + String errorMessage = getErrorMessage(); + logger.log(Level.SEVERE, errorMessage, ex); + MessageNotifyUtil.Message.error(errorMessage); + } finally { + dashboard.getPendingJobsPanel().setCursor(Cursor.getDefaultCursor()); } - } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { - String errorMessage = getErrorMessage(); - logger.log(Level.SEVERE, errorMessage, ex); - MessageNotifyUtil.Message.error(errorMessage); - } finally { - tc.getPendingJobsPanel().setCursor(Cursor.getDefaultCursor()); - } - }); + }); + } } } } @@ -129,7 +126,7 @@ abstract class PrioritizationAction extends AbstractAction { } @Override - protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.prioritizeJob(getJob()); } @@ -163,7 +160,7 @@ abstract class PrioritizationAction extends AbstractAction { } @Override - protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.deprioritizeJob(getJob()); } @@ -199,7 +196,7 @@ abstract class PrioritizationAction extends AbstractAction { } @Override - protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.prioritizeCase(getJob().getManifest().getCaseName()); } @@ -235,7 +232,7 @@ abstract class PrioritizationAction extends AbstractAction { } @Override - protected void modifyPriority(AutoIngestMonitor monitor, AutoIngestJobsPanel panel) throws AutoIngestMonitor.AutoIngestMonitorException { + protected void modifyPriority(AutoIngestMonitor monitor) throws AutoIngestMonitor.AutoIngestMonitorException { monitor.deprioritizeCase(getJob().getManifest().getCaseName()); } From 472a0d1104446c4d8d7f47dcf9a7c0a77b42b153 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 11 Apr 2018 16:44:01 -0400 Subject: [PATCH 026/100] 3610 clean up of resizing and sorting columns in outline view AID2.0 --- .../autoingest/AutoIngestJobsPanel.java | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index e21d8f569c..4cb237f1a8 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -26,6 +26,7 @@ import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.Outline; import org.openide.explorer.ExplorerManager; import org.openide.nodes.Node; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.AutoIngestJobStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJobsNode.JobNode; @@ -37,6 +38,10 @@ import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.JobsSnaps final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerManager.Provider { private static final long serialVersionUID = 1L; + private static final int INITIAL_CASENAME_WIDTH = 170; + private static final int INITIAL_DATASOURCE_WIDTH = 270; + private static final int INITIAL_PRIORITIZED_WIDTH = 20; + private static final int INITIAL_STATUS_WIDTH = 20; private final org.openide.explorer.view.OutlineView outlineView; private final Outline outline; private ExplorerManager explorerManager; @@ -59,46 +64,76 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * Set up the AutoIngestJobsPanel's so that its outlineView is displaying * the correct columns for the specified AutoIngestJobStatus */ + @Messages({"AutoIngestJobsPanel.waitNode.text=Please wait..."}) void customize() { - + ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestJobsNode_caseName_text()); + int indexOfColumn; switch (status) { case PENDING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text()); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_priority_text()); + if (indexOfColumn != -1) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_PRIORITIZED_WIDTH); + } break; case RUNNING_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_hostName_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text()); - outline.setColumnSorted(0, true, 1); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); + if (indexOfColumn != -1) { + outline.setColumnSorted(indexOfColumn, true, 1); + } break; case COMPLETED_JOB: outlineView.setPropertyColumns(Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_dataSource_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text()); - outline.setColumnSorted(3, false, 1); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_jobCompleted_text()); + if (indexOfColumn != -1) { + outline.setColumnSorted(indexOfColumn, false, 1); + } + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_status_text()); + if (indexOfColumn != -1) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_STATUS_WIDTH); + } break; default: } - ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestJobsNode_caseName_text()); outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); outline.setRootVisible(false); - outline.getColumnModel().getColumn(0).setPreferredWidth(160); - outline.getColumnModel().getColumn(1).setPreferredWidth(260); + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); + if (indexOfColumn != -1) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_CASENAME_WIDTH); + } + indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_dataSource_text()); + if (indexOfColumn != -1) { + outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_DATASOURCE_WIDTH); + } if (null == explorerManager) { explorerManager = new ExplorerManager(); } outline.setRowSelectionAllowed(false); add(outlineView, java.awt.BorderLayout.CENTER); - EmptyNode emptyNode = new EmptyNode("Please wait..."); + EmptyNode emptyNode = new EmptyNode(Bundle.AutoIngestJobsPanel_waitNode_text()); explorerManager.setRootContext(emptyNode); } + private int getColumnIndexByName(String columnName) { + for (int index = 0; index < outline.getColumnModel().getColumnCount(); index++) { + if (outline.getColumnModel().getColumn(index).getHeaderValue().toString().equals(columnName)) { + return index; + } + } + return -1; + } + @Override public void setSize(Dimension d) { super.setSize(d); From 253b656387f625ea66b1258938d933e674846bb6 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 12 Apr 2018 10:18:43 -0400 Subject: [PATCH 027/100] Add ExplorerManager.find to AbstractDatResultViewer --- .../AbstractDataResultViewer.java | 9 ++++++--- .../corecomponents/DataResultViewerTable.java | 6 +++--- .../DataResultViewerThumbnail.java | 17 +++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 38577b55ba..e02493ee31 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,7 @@ import org.sleuthkit.autopsy.coreutils.Logger; abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider { private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); - protected transient ExplorerManager explorerManager; + private transient ExplorerManager explorerManager; /** * This constructor is intended to allow an AbstractDataResultViewer to use @@ -91,13 +91,16 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView @Override public ExplorerManager getExplorerManager() { + if (this.explorerManager == null) { + this.explorerManager = ExplorerManager.find(this); + } return this.explorerManager; } @Override public void setSelectedNodes(Node[] selected) { try { - this.explorerManager.setSelectedNodes(selected); + this.getExplorerManager().setSelectedNodes(selected); } catch (PropertyVetoException ex) { logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 0a81b78f94..2c71d288f8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -224,11 +224,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { if (hasChildren) { currentRoot = selectedNode; - explorerManager.setRootContext(currentRoot); + this.getExplorerManager().setRootContext(currentRoot); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); - explorerManager.setRootContext(emptyNode); // make empty node + this.getExplorerManager().setRootContext(emptyNode); // make empty node outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); /* @@ -314,7 +314,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Node childNode = childNodes[i]; if (selectedChildInfo.matches(childNode)) { try { - explorerManager.setSelectedNodes(new Node[]{childNode}); + this.getExplorerManager().setSelectedNodes(new Node[]{childNode}); } catch (PropertyVetoException ex) { logger.log(Level.SEVERE, "Failed to select node specified by selected child info", ex); } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 8eb8d53929..ef1f0c68f3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -108,7 +108,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { private void initialize() { initComponents(); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - explorerManager.addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); + this.getExplorerManager().addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), @@ -297,7 +297,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (thumbSize != newIconSize) { thumbSize = newIconSize; - Node root = explorerManager.getRootContext(); + Node root = this.getExplorerManager().getRootContext(); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); @@ -307,13 +307,13 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { // update even though the new and old Node values are identical. This in turn // will cause the entire view to update completely. After this we // immediately set the node back to the current child by calling switchPage(). - explorerManager.setExploredContext(root); + this.getExplorerManager().setExploredContext(root); switchPage(); } }//GEN-LAST:event_thumbnailSizeComboBoxActionPerformed private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed - List> childProperties = ResultViewerPersistence.getAllChildProperties(explorerManager.getRootContext(), 100); + List> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100); SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); @@ -395,12 +395,12 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); - explorerManager.setRootContext(root); + this.getExplorerManager().setRootContext(root); } else { tfn = null; tvc = null; Node emptyNode = new AbstractNode(Children.LEAF); - explorerManager.setRootContext(emptyNode); + this.getExplorerManager().setRootContext(emptyNode); iconView.setBackground(Color.BLACK); } } finally { @@ -488,6 +488,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.genThumbs")); progress.start(); progress.switchToIndeterminate(); + ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager(); Node root = explorerManager.getRootContext(); Node pageNode = root.getChildren().getNodeAt(curPage - 1); explorerManager.setExploredContext(pageNode); @@ -618,7 +619,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { } }); - explorerManager.setExploredContext(pageNode); + DataResultViewerThumbnail.this.getExplorerManager().setExploredContext(pageNode); } updateControls(); @@ -647,7 +648,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (evt.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - Node[] selectedNodes = explorerManager.getSelectedNodes(); + Node[] selectedNodes = DataResultViewerThumbnail.this.getExplorerManager().getSelectedNodes(); if (selectedNodes.length == 1) { AbstractFile af = selectedNodes[0].getLookup().lookup(AbstractFile.class); if (af == null) { From 044932d8bcc1b034b667580a76082311d483d45a Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 12 Apr 2018 12:23:12 -0400 Subject: [PATCH 028/100] Updated 'createFileIngestModule()' to check object type. --- .../ingestmodule/IngestModuleFactory.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java index 6ef03ae00d..2f732ac60f 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java @@ -68,7 +68,17 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter { @Override public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { - return new IngestModule((IngestSettings) settings); + if (settings instanceof IngestSettings) { + return new IngestModule((IngestSettings) settings); + } + /* + * Compatibility check for older versions. + */ + if (settings instanceof NoIngestModuleIngestJobSettings) { + return new IngestModule(new IngestSettings()); + } + + throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings"); } @Override From e51fdc1658de617330ad1a814e27d403a422db6e Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 12 Apr 2018 12:25:47 -0400 Subject: [PATCH 029/100] 3610 add initial version of admin mode to enable context menu file is userdir/adminAccess --- .../autoingest/AutoIngestJobsNode.java | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 0e5e94159c..7edf074e1a 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -18,11 +18,13 @@ */ package org.sleuthkit.autopsy.experimental.autoingest; +import java.io.File; import javax.swing.Action; import java.time.Instant; import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.openide.modules.Places; import org.openide.nodes.AbstractNode; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; @@ -189,22 +191,27 @@ final class AutoIngestJobsNode extends AbstractNode { @Override public Action[] getActions(boolean context) { List actions = new ArrayList<>(); - switch (jobStatus) { - case PENDING_JOB: - actions.add(new PrioritizationAction.PrioritizeJobAction(autoIngestJob)); - actions.add(new PrioritizationAction.PrioritizeCaseAction(autoIngestJob)); - PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(autoIngestJob); - deprioritizeJobAction.setEnabled(autoIngestJob.getPriority() > 0); - actions.add(deprioritizeJobAction); - PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(autoIngestJob); - deprioritizeCaseAction.setEnabled(autoIngestJob.getPriority() > 0); - actions.add(deprioritizeCaseAction); - break; - case RUNNING_JOB: - break; - case COMPLETED_JOB: - break; - default: + //WJS-TODO make this a publically accessible setting like isAdminModeEnabled + //Or at minimum make the file name a static variable + File f = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "adminAccess"); + if (f.exists()) { + switch (jobStatus) { + case PENDING_JOB: + actions.add(new PrioritizationAction.PrioritizeJobAction(autoIngestJob)); + actions.add(new PrioritizationAction.PrioritizeCaseAction(autoIngestJob)); + PrioritizationAction.DeprioritizeJobAction deprioritizeJobAction = new PrioritizationAction.DeprioritizeJobAction(autoIngestJob); + deprioritizeJobAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(deprioritizeJobAction); + PrioritizationAction.DeprioritizeCaseAction deprioritizeCaseAction = new PrioritizationAction.DeprioritizeCaseAction(autoIngestJob); + deprioritizeCaseAction.setEnabled(autoIngestJob.getPriority() > 0); + actions.add(deprioritizeCaseAction); + break; + case RUNNING_JOB: + break; + case COMPLETED_JOB: + break; + default: + } } return actions.toArray(new Action[actions.size()]); } From 79ac8165f28a97ff82054261112d0ea659fe34cc Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 12 Apr 2018 12:27:17 -0400 Subject: [PATCH 030/100] 3610 remove buttons which have context actions from aid2.0 --- .../autoingest/AutoIngestDashboard.form | 89 ++----------- .../autoingest/AutoIngestDashboard.java | 126 ++---------------- .../experimental/autoingest/Bundle.properties | 8 -- 3 files changed, 26 insertions(+), 197 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form index 0bdc79f34f..214254e032 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.form @@ -31,27 +31,24 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -80,11 +77,7 @@ - - - - @@ -188,34 +181,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -226,33 +191,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index e5f150948d..fa5b9037d4 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.swing.JPanel; import javax.swing.SwingWorker; import javax.swing.UIManager; -import javax.swing.event.ListSelectionEvent; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.core.ServicesMonitor; @@ -92,47 +91,16 @@ final class AutoIngestDashboard extends JPanel implements Observer { pendingJobsPanel.setSize(pendingScrollPane.getSize()); pendingScrollPane.add(pendingJobsPanel); pendingScrollPane.setViewportView(pendingJobsPanel); - pendingJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { - if (e.getValueIsAdjusting()) { - return; - } - AutoIngestJob job = this.pendingJobsPanel.getSelectedAutoIngestJob(); - - boolean enablePrioritizeButtons = false; - boolean enableDeprioritizeButtons = false; - if (job != null) { - enablePrioritizeButtons = true; - enableDeprioritizeButtons = job.getPriority() > 0; - } - this.prioritizeJobButton.setEnabled(enablePrioritizeButtons); - this.prioritizeCaseButton.setEnabled(enablePrioritizeButtons); - this.deprioritizeJobButton.setEnabled(enableDeprioritizeButtons); - this.deprioritizeCaseButton.setEnabled(enableDeprioritizeButtons); - }); pendingJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_pendingTable_toolTipText()); runningJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.RUNNING_JOB); runningJobsPanel.setSize(runningScrollPane.getSize()); runningScrollPane.add(runningJobsPanel); runningScrollPane.setViewportView(runningJobsPanel); - runningJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { - boolean enabled = false; - this.prioritizeJobButton.setEnabled(enabled); - this.prioritizeCaseButton.setEnabled(enabled); - this.deprioritizeJobButton.setEnabled(enabled); - this.deprioritizeCaseButton.setEnabled(enabled); - }); runningJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_runningTable_toolTipText()); completedJobsPanel = new AutoIngestJobsPanel(AutoIngestJobsNode.AutoIngestJobStatus.COMPLETED_JOB); completedJobsPanel.setSize(completedScrollPane.getSize()); completedScrollPane.add(completedJobsPanel); completedScrollPane.setViewportView(completedJobsPanel); - completedJobsPanel.addListSelectionListener((ListSelectionEvent e) -> { - boolean enabled = false; - this.prioritizeJobButton.setEnabled(enabled); - this.prioritizeCaseButton.setEnabled(enabled); - this.deprioritizeJobButton.setEnabled(enabled); - this.deprioritizeCaseButton.setEnabled(enabled); - }); completedJobsPanel.setToolTipText(Bundle.AutoIngestDashboard_completedTable_toolTipText()); /* * Must set this flag, otherwise pop up menus don't close properly. @@ -326,11 +294,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { refreshButton = new javax.swing.JButton(); lbServicesStatus = new javax.swing.JLabel(); tbServicesStatusMessage = new javax.swing.JTextField(); - prioritizeJobButton = new javax.swing.JButton(); - prioritizeCaseButton = new javax.swing.JButton(); clusterMetricsButton = new javax.swing.JButton(); - deprioritizeJobButton = new javax.swing.JButton(); - deprioritizeCaseButton = new javax.swing.JButton(); org.openide.awt.Mnemonics.setLocalizedText(jButton1, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.jButton1.text")); // NOI18N @@ -364,24 +328,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { tbServicesStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.text")); // NOI18N tbServicesStatusMessage.setBorder(null); - org.openide.awt.Mnemonics.setLocalizedText(prioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.text")); // NOI18N - prioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeJobButton.toolTipText")); // NOI18N - prioritizeJobButton.setEnabled(false); - prioritizeJobButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - prioritizeJobButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(prioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.text")); // NOI18N - prioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.prioritizeCaseButton.toolTipText")); // NOI18N - prioritizeCaseButton.setEnabled(false); - prioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - prioritizeCaseButtonActionPerformed(evt); - } - }); - org.openide.awt.Mnemonics.setLocalizedText(clusterMetricsButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.clusterMetricsButton.text")); // NOI18N clusterMetricsButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -389,24 +335,6 @@ final class AutoIngestDashboard extends JPanel implements Observer { } }); - org.openide.awt.Mnemonics.setLocalizedText(deprioritizeJobButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.text")); // NOI18N - deprioritizeJobButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeJobButton.toolTipText")); // NOI18N - deprioritizeJobButton.setEnabled(false); - deprioritizeJobButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - deprioritizeJobButtonActionPerformed(evt); - } - }); - - org.openide.awt.Mnemonics.setLocalizedText(deprioritizeCaseButton, org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.text")); // NOI18N - deprioritizeCaseButton.setToolTipText(org.openide.util.NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.deprioritizeCaseButton.toolTipText")); // NOI18N - deprioritizeCaseButton.setEnabled(false); - deprioritizeCaseButton.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - deprioritizeCaseButtonActionPerformed(evt); - } - }); - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -417,29 +345,24 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addComponent(pendingScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(runningScrollPane, javax.swing.GroupLayout.Alignment.LEADING) .addComponent(completedScrollPane, javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(prioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deprioritizeJobButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(prioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(deprioritizeCaseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 127, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(clusterMetricsButton)) - .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() .addComponent(lbServicesStatus) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 861, Short.MAX_VALUE))) + .addComponent(tbServicesStatusMessage, javax.swing.GroupLayout.DEFAULT_SIZE, 861, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(lbPending, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbCompleted, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lbRunning, javax.swing.GroupLayout.Alignment.LEADING)) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(refreshButton, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(clusterMetricsButton))) .addContainerGap()) ); - layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, prioritizeCaseButton, prioritizeJobButton, refreshButton}); + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {clusterMetricsButton, refreshButton}); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -463,11 +386,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(refreshButton) - .addComponent(prioritizeJobButton) - .addComponent(prioritizeCaseButton) - .addComponent(clusterMetricsButton) - .addComponent(deprioritizeJobButton) - .addComponent(deprioritizeCaseButton)) + .addComponent(clusterMetricsButton)) .addContainerGap()) ); }// //GEN-END:initComponents @@ -485,38 +404,19 @@ final class AutoIngestDashboard extends JPanel implements Observer { setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); }//GEN-LAST:event_refreshButtonActionPerformed - private void prioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeCaseButtonActionPerformed - new PrioritizationAction.PrioritizeCaseAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); - }//GEN-LAST:event_prioritizeCaseButtonActionPerformed - private void clusterMetricsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_clusterMetricsButtonActionPerformed new AutoIngestMetricsDialog(this.getTopLevelAncestor()); }//GEN-LAST:event_clusterMetricsButtonActionPerformed - private void deprioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeJobButtonActionPerformed - new PrioritizationAction.DeprioritizeJobAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); - }//GEN-LAST:event_deprioritizeJobButtonActionPerformed - - private void deprioritizeCaseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deprioritizeCaseButtonActionPerformed - new PrioritizationAction.DeprioritizeCaseAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); - }//GEN-LAST:event_deprioritizeCaseButtonActionPerformed - - private void prioritizeJobButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prioritizeJobButtonActionPerformed - new PrioritizationAction.PrioritizeJobAction(pendingJobsPanel.getSelectedAutoIngestJob()).actionPerformed(evt); - }//GEN-LAST:event_prioritizeJobButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton clusterMetricsButton; private javax.swing.JScrollPane completedScrollPane; - private javax.swing.JButton deprioritizeCaseButton; - private javax.swing.JButton deprioritizeJobButton; private javax.swing.JButton jButton1; private javax.swing.JLabel lbCompleted; private javax.swing.JLabel lbPending; private javax.swing.JLabel lbRunning; private javax.swing.JLabel lbServicesStatus; private javax.swing.JScrollPane pendingScrollPane; - private javax.swing.JButton prioritizeCaseButton; - private javax.swing.JButton prioritizeJobButton; private javax.swing.JButton refreshButton; private javax.swing.JScrollPane runningScrollPane; private javax.swing.JTextField tbServicesStatusMessage; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties index a9a500a5b9..6194815f69 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/Bundle.properties @@ -219,10 +219,6 @@ FileExporterSettingsPanel.SaveTooltip_1=Save the current rule AutoIngestDashboard.refreshButton.toolTipText=Refresh displayed tables AutoIngestDashboard.refreshButton.text=&Refresh AutoIngestDashboard.jButton1.text=jButton1 -AutoIngestDashboard.prioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue. -AutoIngestDashboard.prioritizeJobButton.text=Prioritize &Job -AutoIngestDashboard.prioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue. -AutoIngestDashboard.prioritizeCaseButton.text=Prioritize &Case AutoIngestMetricsDialog.reportTextArea.text= AutoIngestDashboard.clusterMetricsButton.text=Auto Ingest &Metrics AutoIngestMetricsDialog.metricsButton.text=Generate Metrics Report @@ -233,10 +229,6 @@ ArchiveFilePanel.browseButton.text=Browse ArchiveFilePanel.pathTextField.text= ArchiveFilePanel.errorLabel.text=Error Label AutoIngestMetricsDialog.startingDataLabel.text=Starting Date: -AutoIngestDashboard.deprioritizeJobButton.toolTipText=Move the selected job to the top of the Pending queue. -AutoIngestDashboard.deprioritizeJobButton.text=Deprioritize J&ob -AutoIngestDashboard.deprioritizeCaseButton.text=Deprioritize C&ase -AutoIngestDashboard.deprioritizeCaseButton.toolTipText=Move all images associated with a case to top of Pending queue. AutoIngestControlPanel.bnDeprioritizeCase.text=Deprioritize Case AutoIngestControlPanel.bnDeprioritizeJob.text=Deprioritize Job AutoIngestControlPanel.bnPrioritizeCase.text=Prioritize Case From 5fd9dbd62101b9c168457c6842389239ebf10724 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 12 Apr 2018 12:53:38 -0400 Subject: [PATCH 031/100] 3709: 1st test for embedded files --- Core/build.xml | 1 + .../autopsy/ingest/EmbeddedFileTest.java | 189 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java diff --git a/Core/build.xml b/Core/build.xml index 90b588bbc3..d3e04083f8 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -85,6 +85,7 @@ + diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java new file mode 100755 index 0000000000..b3380944a0 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -0,0 +1,189 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.ingest; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import junit.framework.Test; +import org.apache.commons.io.FileUtils; +import org.netbeans.junit.NbModuleSuite; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; +import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner; +import org.sleuthkit.autopsy.testutils.IngestJobRunner; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +public class EmbeddedFileTest extends NbTestCase { + + private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "EmbeddedFileTest"); + private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString()); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + private static final int DEEP_FOLDER_COUNT = 25; + private static int deepFolderTested = 0; + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EmbeddedFileTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + public EmbeddedFileTest(String name) { + super(name); + } + + @Override + public void setUp() { + // Delete the test directory, if it exists + if (CASE_DIRECTORY_PATH.toFile().exists()) { + try { + FileUtils.deleteDirectory(CASE_DIRECTORY_PATH.toFile()); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + assertFalse("Unable to delete existing test directory", CASE_DIRECTORY_PATH.toFile().exists()); + + // Create the test directory + CASE_DIRECTORY_PATH.toFile().mkdirs(); + assertTrue("Unable to create test directory", CASE_DIRECTORY_PATH.toFile().exists()); + + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, CASE_DIRECTORY_PATH.toString(), new CaseDetails("EmbeddedFileTest")); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + assertTrue(CASE_DIR.exists()); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + try { + DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, IMAGE_PATH); + List dataSourceContent = callBack.getDataSourceContent(); + assertEquals(1, dataSourceContent.size()); + List errorMessages = callBack.getErrorMessages(); + assertEquals(0, errorMessages.size()); + } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + + } + } + + @Override + public void tearDown() { + try { + Case.closeCurrentCase(); + //Seems like we need some time to close the case. + try { + Thread.sleep(2000); + } catch (Exception ex) { + + } + + FileUtils.deleteDirectory(CASE_DIR); + } catch (CaseActionException | IOException ex) { + Exceptions.printStackTrace(ex); + } + assertFalse(CASE_DIR.exists()); + } + + public void testEncription() { + final int numOfFiles = 11; + try { + Case openCase = Case.getOpenCase(); + runIngestJob(openCase.getDataSources()); + + List results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%password%'"); + assertEquals(numOfFiles, results.size()); + int passwdProtectedZips = 0; + int nonPasswdProcted = 0; + for (AbstractFile file : results) { + //.zip file has artifact TSK_ENCRYPTION_DETECTED + if (file.getNameExtension().equalsIgnoreCase("zip")) { + ArrayList artifacts = file.getAllArtifacts(); + for (BlackboardArtifact artifact : artifacts) { + assertEquals(1, artifacts.size()); + assertEquals(artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()); + passwdProtectedZips++; + } + } else { + assertTrue(file.getAllArtifacts().size() == 0); + nonPasswdProcted++; + } + + } + //Make sure 2 password prected zip files has been tested + assertEquals(2, passwdProtectedZips); + //No other files has artifact TSK_ENCRYPTION_DETECTED + assertEquals(numOfFiles - 2, nonPasswdProcted); + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + private void runIngestJob(List datasources) { + IngestModuleTemplate embeddedTemplate = getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()); + IngestModuleTemplate hashLookupTemplate = getIngestModuleTemplate(new HashLookupModuleFactory()); + + ArrayList templates = new ArrayList<>(); + templates.add(embeddedTemplate); + templates.add(hashLookupTemplate); + IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + try { + List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); + for (IngestModuleError err : errs) { + System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); + } + assertEquals(0, errs.size()); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + private IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { + IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); + template.setEnabled(true); + return template; + } +} From 79221c2f80af54d12774e87d15634affa5e0d5ec Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 12 Apr 2018 13:00:47 -0400 Subject: [PATCH 032/100] 3709: cleanup --- .../src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index b3380944a0..be44a34c3c 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -54,9 +54,7 @@ public class EmbeddedFileTest extends NbTestCase { private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "EmbeddedFileTest"); private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString()); private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); - private static final int DEEP_FOLDER_COUNT = 25; - private static int deepFolderTested = 0; - + public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EmbeddedFileTest.class). clusters(".*"). @@ -152,7 +150,7 @@ public class EmbeddedFileTest extends NbTestCase { //Make sure 2 password prected zip files has been tested assertEquals(2, passwdProtectedZips); //No other files has artifact TSK_ENCRYPTION_DETECTED - assertEquals(numOfFiles - 2, nonPasswdProcted); + assertEquals(numOfFiles - passwdProtectedZips, nonPasswdProcted); } catch (NoCurrentCaseException | TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); From ab1c424e0d363929c81b35b80d33e0d98ae982f3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 12 Apr 2018 15:13:41 -0400 Subject: [PATCH 033/100] 3610 change invalid index of -1 for columns to be a Static variable --- .../autoingest/AutoIngestJobsPanel.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 4cb237f1a8..6e72f91d17 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -42,6 +42,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa private static final int INITIAL_DATASOURCE_WIDTH = 270; private static final int INITIAL_PRIORITIZED_WIDTH = 20; private static final int INITIAL_STATUS_WIDTH = 20; + private static final int INVALID_INDEX = -1; private final org.openide.explorer.view.OutlineView outlineView; private final Outline outline; private ExplorerManager explorerManager; @@ -74,7 +75,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_jobCreated_text(), Bundle.AutoIngestJobsNode_priority_text(), Bundle.AutoIngestJobsNode_priority_text()); indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_priority_text()); - if (indexOfColumn != -1) { + if (indexOfColumn != INVALID_INDEX) { outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_PRIORITIZED_WIDTH); } break; @@ -84,7 +85,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stage_text(), Bundle.AutoIngestJobsNode_stageTime_text(), Bundle.AutoIngestJobsNode_stageTime_text()); indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); - if (indexOfColumn != -1) { + if (indexOfColumn != INVALID_INDEX) { outline.setColumnSorted(indexOfColumn, true, 1); } break; @@ -94,11 +95,11 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_jobCompleted_text(), Bundle.AutoIngestJobsNode_status_text(), Bundle.AutoIngestJobsNode_status_text()); indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_jobCompleted_text()); - if (indexOfColumn != -1) { + if (indexOfColumn != INVALID_INDEX) { outline.setColumnSorted(indexOfColumn, false, 1); } indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_status_text()); - if (indexOfColumn != -1) { + if (indexOfColumn != INVALID_INDEX) { outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_STATUS_WIDTH); } break; @@ -108,11 +109,11 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa outline.setRootVisible(false); indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_caseName_text()); - if (indexOfColumn != -1) { + if (indexOfColumn != INVALID_INDEX) { outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_CASENAME_WIDTH); } indexOfColumn = getColumnIndexByName(Bundle.AutoIngestJobsNode_dataSource_text()); - if (indexOfColumn != -1) { + if (indexOfColumn != INVALID_INDEX) { outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_DATASOURCE_WIDTH); } if (null == explorerManager) { @@ -131,7 +132,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa return index; } } - return -1; + return INVALID_INDEX; } @Override From 78b6a06e58734db398eb3913a8caf1e4d6578d03 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 12 Apr 2018 23:48:59 -0400 Subject: [PATCH 034/100] Fix the remembering selection and add outputs as files instead of eport --- .../volatilityDSP/MemoryDSInputPanel.java | 12 ++--- .../volatilityDSP/VolatilityProcessor.java | 50 ++++++++----------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index f96a9cc801..f006687c3e 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -53,7 +53,6 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private final PluginListTableModel tableModel = new PluginListTableModel(); private final List PluginListNames = new ArrayList<>(); private final Map pluginListStates = new HashMap<>(); // is set by listeners when users select and deselect items - private final Boolean isEnabled = true; /** * Creates new MemoryDSInputPanel panel for user input @@ -157,8 +156,10 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { PluginListNames.add(plugin); if (allEnabled) { pluginListStates.put(plugin, true); + } else if ((pluginMap.containsKey(plugin) && pluginMap.get(plugin).equals("false"))) { + pluginListStates.put(plugin, false); } else { - pluginListStates.put(plugin, pluginMap.containsKey(plugin)); + pluginListStates.put(plugin, true); } } tableModel.fireTableDataChanged(); @@ -327,15 +328,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { List getPluginsToRun() { List enabledPlugins = new ArrayList<>(); - Map pluginMap = new HashMap<>(); + Map pluginSettingsToSave = new HashMap<>(); for (String plugin : PluginListNames) { if (pluginListStates.get(plugin)) { enabledPlugins.add(plugin); - pluginMap.put(plugin, ""); } + pluginSettingsToSave.put(plugin, pluginListStates.get(plugin).toString()); } - - ModuleSettings.setConfigSettings(this.contextName, pluginMap); + ModuleSettings.setConfigSettings(this.contextName, pluginSettingsToSave); // @@ Could return keys of set return enabledPlugins; } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index 50246f5255..59293de566 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -29,7 +29,7 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.modules.InstalledFileLocator; -import org.openide.util.Lookup; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -41,13 +41,13 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; -import org.sleuthkit.autopsy.keywordsearchservice.KeywordSearchService; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Image; -import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData.EncodingType; import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM; /** @@ -70,6 +70,7 @@ class VolatilityProcessor { private String moduleOutputPath; private FileManager fileManager; private volatile boolean isCancelled; + private Content outputVirtDir; /** * Constructs a processor that runs Volatility on a given memory image file @@ -117,6 +118,13 @@ class VolatilityProcessor { fileManager = currentCase.getServices().getFileManager(); + try { + // make a virtual directory to store the reports + outputVirtDir = currentCase.getSleuthkitCase().addVirtualDirectory(dataSource.getId(), "ModuleOutput"); + } catch (TskCoreException ex) { + throw new VolatilityProcessorException("Error creating virtual directory", ex); + } + /* * Make an output folder unique to this data source. */ @@ -129,6 +137,7 @@ class VolatilityProcessor { runVolatilityPlugin("imageinfo"); //NON-NLS } + progressMonitor.setIndeterminate(false); progressMonitor.setProgressMax(pluginsToRun.size()); for (int i = 0; i < pluginsToRun.size(); i++) { @@ -186,14 +195,15 @@ class VolatilityProcessor { commandLine.add(pluginToRun); - String outputFile = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS + String outputFileAsString = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS ProcessBuilder processBuilder = new ProcessBuilder(commandLine); /* * Add an environment variable to force Volatility to run with the same * permissions Autopsy uses. */ processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS - processBuilder.redirectOutput(new File(outputFile)); + File outputFile = new File(outputFileAsString); + processBuilder.redirectOutput(outputFile); processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS processBuilder.directory(new File(memoryImage.getParent())); @@ -210,32 +220,16 @@ class VolatilityProcessor { if (isCancelled) { return; } - - /* - * Add the plugin output file to the case as a report. - */ + try { - Report report = currentCase.getSleuthkitCase().addReport(outputFile, VOLATILITY, VOLATILITY + " " + pluginToRun + " Plugin"); //NON-NLS - try { - KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class); - if (searchService != null) { - searchService.index(report); - } else { - errorMsgs.add(Bundle.VolatilityProcessor_exceptionMessage_searchServiceNotFound(pluginToRun)); - /* - * Log the exception as well as add it to the error - * messages, to ensure that the stack trace is not lost. - */ - logger.log(Level.WARNING, Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun)); - } - } catch (TskCoreException ex) { - throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorIndexingOutput(pluginToRun), ex); - } + String relativePath = new File(currentCase.getCaseDirectory()).toURI().relativize(new File(outputFileAsString).toURI()).getPath(); + fileManager.addDerivedFile(pluginToRun, relativePath, outputFile.length(), 0, 0, 0, 0, true, outputVirtDir, null, null, null, null, EncodingType.NONE); } catch (TskCoreException ex) { - throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_errorAddingOutput(pluginToRun), ex); + errorMsgs.add("Error adding " + pluginToRun + " volatility report as a file"); + logger.log(Level.WARNING, "Error adding report as derived file", ex); } - - createArtifactsFromPluginOutput(pluginToRun, new File(outputFile)); + + createArtifactsFromPluginOutput(pluginToRun, new File(outputFileAsString)); } /** From e36607acccc5772088132ffc6a8b9dff51db05bd Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 13 Apr 2018 08:20:02 -0400 Subject: [PATCH 035/100] Moved solr metrics further down. --- .../src/org/sleuthkit/autopsy/keywordsearch/Ingester.java | 2 -- .../src/org/sleuthkit/autopsy/keywordsearch/Server.java | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java index b9c4541c7b..4e41a09aa9 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java @@ -237,9 +237,7 @@ class Ingester { try { //TODO: consider timeout thread, or vary socket timeout based on size of indexed content - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); solrServer.addDocument(updateDoc); - EnterpriseHealthMonitor.submitTimingMetric(metric); uncommitedIngests = true; } catch (KeywordSearchModuleException | NoOpenCoreException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index eea4d2914e..556eca08e4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -710,7 +710,9 @@ public class Server { if (null == currentCore) { throw new NoOpenCoreException(); } + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Index chunk"); currentCore.addDocument(doc); + EnterpriseHealthMonitor.submitTimingMetric(metric); } finally { currentCoreLock.readLock().unlock(); } @@ -775,9 +777,7 @@ public class Server { IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); connectToSolrServer(currentSolrServer); - EnterpriseHealthMonitor.submitTimingMetric(metric); } catch (SolrServerException | IOException ex) { throw new KeywordSearchModuleException(NbBundle.getMessage(Server.class, "Server.connect.exception.msg", ex.getLocalizedMessage()), ex); @@ -1319,11 +1319,13 @@ public class Server { * @throws IOException */ void connectToSolrServer(HttpSolrServer solrServer) throws SolrServerException, IOException { + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Solr: Connectivity check"); CoreAdminRequest statusRequest = new CoreAdminRequest(); statusRequest.setCoreName( null ); statusRequest.setAction( CoreAdminParams.CoreAdminAction.STATUS ); statusRequest.setIndexInfoNeeded(false); statusRequest.process(solrServer); + EnterpriseHealthMonitor.submitTimingMetric(metric); } /** From c40149cb9d8ec0f8f113aeddb75a0f7a076120b3 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Fri, 13 Apr 2018 10:28:54 -0400 Subject: [PATCH 036/100] 3709: update Google doc ID --- Core/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/build.xml b/Core/build.xml index d3e04083f8..b5b571d137 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -85,7 +85,7 @@ - + From edd853a1e278e830420e476572c8b07dad29937d Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 13 Apr 2018 12:39:39 -0400 Subject: [PATCH 037/100] 3610 add dummy actions Running and Completed jobs related actions --- .../autoingest/AutoIngestAdminActions.java | 155 ++++++++++++++++++ .../autoingest/AutoIngestDashboard.java | 2 +- .../autoingest/AutoIngestJobsNode.java | 12 +- 3 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java new file mode 100644 index 0000000000..15374f22ee --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestAdminActions.java @@ -0,0 +1,155 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.experimental.autoingest; + +import java.awt.event.ActionEvent; +import javax.swing.AbstractAction; +import org.openide.util.NbBundle; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; + +final class AutoIngestAdminActions { + + @NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"}) + static final class ProgressDialogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ProgressDialogAction() { + super(Bundle.AutoIngestAdminActions_progressDialogAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3734 + final AutoIngestDashboardTopComponent tc = (AutoIngestDashboardTopComponent) WindowManager.getDefault().findTopComponent(AutoIngestDashboardTopComponent.PREFERRED_ID); + if (tc != null) { + AutoIngestDashboard dashboard = tc.getAutoIngestDashboard(); + if (dashboard != null) { + new IngestProgressSnapshotDialog(dashboard.getTopLevelAncestor(), true); + } + } + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.cancelJobAction.title=Cancel Job"}) + static final class CancelJobAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + CancelJobAction() { + super(Bundle.AutoIngestAdminActions_cancelJobAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3738 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.cancelModuleAction.title=Cancel Module"}) + static final class CancelModuleAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + CancelModuleAction() { + super(Bundle.AutoIngestAdminActions_cancelModuleAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3738 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.reprocessJobAction.title=Reprocess Job"}) + static final class ReprocessJobAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ReprocessJobAction() { + super(Bundle.AutoIngestAdminActions_reprocessJobAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3739 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.deleteCaseAction.title=Delete Case"}) + static final class DeleteCaseAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + DeleteCaseAction() { + super(Bundle.AutoIngestAdminActions_deleteCaseAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA-3740 + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } + + @NbBundle.Messages({"AutoIngestAdminActions.showCaseLogAction.title=Show Case Log"}) + static final class ShowCaseLogAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + ShowCaseLogAction() { + super(Bundle.AutoIngestAdminActions_showCaseLogAction_title()); + } + + @Override + public void actionPerformed(ActionEvent e) { + //TODO JIRA- + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); //To change body of generated methods, choose Tools | Templates. + } + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index fa5b9037d4..aca3e1cca8 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -238,7 +238,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { * * @param jobsSnapshot The jobs snapshot. */ - private void refreshTables() { + void refreshTables() { pendingJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); runningJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); completedJobsPanel.refresh(autoIngestMonitor.getJobsSnapshot()); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java index 7edf074e1a..b623a2281f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsNode.java @@ -41,6 +41,8 @@ import org.sleuthkit.autopsy.guiutils.StatusIconCellRenderer; * Each job with the specified status will have a child node representing it. */ final class AutoIngestJobsNode extends AbstractNode { + private final static String ADMIN_ACCESS_FILE_NAME = "adminAccess"; + private final static String ADMIN_ACCESS_FILE_PATH = Places.getUserDirectory().getAbsolutePath() + File.separator + ADMIN_ACCESS_FILE_NAME; @Messages({ "AutoIngestJobsNode.caseName.text=Case Name", @@ -191,9 +193,7 @@ final class AutoIngestJobsNode extends AbstractNode { @Override public Action[] getActions(boolean context) { List actions = new ArrayList<>(); - //WJS-TODO make this a publically accessible setting like isAdminModeEnabled - //Or at minimum make the file name a static variable - File f = new File(Places.getUserDirectory().getAbsolutePath() + File.separator + "adminAccess"); + File f = new File(ADMIN_ACCESS_FILE_PATH); if (f.exists()) { switch (jobStatus) { case PENDING_JOB: @@ -207,8 +207,14 @@ final class AutoIngestJobsNode extends AbstractNode { actions.add(deprioritizeCaseAction); break; case RUNNING_JOB: + actions.add(new AutoIngestAdminActions.ProgressDialogAction()); + actions.add(new AutoIngestAdminActions.CancelJobAction()); + actions.add(new AutoIngestAdminActions.CancelModuleAction()); break; case COMPLETED_JOB: + actions.add(new AutoIngestAdminActions.ReprocessJobAction()); + actions.add(new AutoIngestAdminActions.DeleteCaseAction()); + actions.add(new AutoIngestAdminActions.ShowCaseLogAction()); break; default: } From da854f684bdbc87ee3df1ec7368bd75b22c044a9 Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Fri, 6 Apr 2018 09:06:00 -0400 Subject: [PATCH 038/100] made changes in lal and localdisk for macos --- Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java | 3 ++- Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java | 4 ++-- .../org/sleuthkit/autopsy/timeline/PromptDialogManager.java | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java index 54f2a358b5..9394234e4d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Installer.java @@ -76,7 +76,8 @@ public class Installer extends ModuleInstall { private void setLookAndFeel() { if (System.getProperty("os.name").toLowerCase().contains("mac")) { //NON-NLS - setOSXLookAndFeel(); + setUnixLookAndFeel(); + setModuleSettings("false"); }else if (System.getProperty("os.name").toLowerCase().contains("nux")){ setUnixLookAndFeel(); } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java index b715d8ce35..7d66dc4e3c 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java @@ -358,7 +358,7 @@ public class PlatformUtil { File[] files = dev.listFiles(); for (File f : files) { String name = f.getName(); - if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 3) { //NON-NLS + if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 5) { //NON-NLS String path = "/dev/" + name; //NON-NLS if (canReadDrive(path)) { try { @@ -401,7 +401,7 @@ public class PlatformUtil { File[] files = dev.listFiles(); for (File f : files) { String name = f.getName(); - if ((name.contains("hd") || name.contains("sd")) && f.canRead() && name.length() == 4) { //NON-NLS + if ((name.contains("hd") || name.contains("sd") || name.contains("disk")) && f.canRead() && name.length() <= 7) { //NON-NLS String path = "/dev/" + name; //NON-NLS if (canReadDrive(path)) { drives.add(new LocalDisk(path, path, f.getTotalSpace())); diff --git a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java index a379fc7d8d..cc58dfd80a 100644 --- a/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java +++ b/Core/src/org/sleuthkit/autopsy/timeline/PromptDialogManager.java @@ -225,7 +225,7 @@ public final class PromptDialogManager { @NbBundle.Messages({ "PromptDialogManager.showTimeLineDisabledMessage.contentText=" - + "Timeline functionality is not available for Linux yet." + + "Timeline functionality is not available yet." + " Timeline will be disabled. ", "PromptDialogManager.showTimeLineDisabledMessage.headerText="}) static void showTimeLineDisabledMessage() { From 35e6a2a73f5eea1117bc4d5665d8123eb87ac32d Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 13 Apr 2018 14:44:07 -0400 Subject: [PATCH 039/100] Add warnings for boundary characters in regex searches. Don't add .* if a boundary character is present. --- .../DropdownSingleTermSearchPanel.java | 15 +++++++++++++++ .../keywordsearch/GlobalEditListPanel.java | 19 ++++++++++++++++++- .../autopsy/keywordsearch/RegexQuery.java | 9 ++++++--- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index 512d62238a..7422f95b9f 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.swing.JMenuItem; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; /** @@ -135,8 +136,22 @@ public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { * * @return The keyword list. */ + @NbBundle.Messages({"DropdownSingleTermSearchPanel.warning.title=Warning", + "DropdownSingleTermSearchPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"}) @Override List getKeywordLists() { + + if (regexRadioButton.isSelected()) { + if((keywordTextField.getText() != null) && + (keywordTextField.getText().startsWith("^") || + (keywordTextField.getText().endsWith("$") && ! keywordTextField.getText().endsWith("\\$")))) { + + KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "DropdownSingleTermSearchPanel.warning.title"), + NbBundle.getMessage(this.getClass(), "DropdownSingleTermSearchPanel.warning.text"), + KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN); + } + } + List keywords = new ArrayList<>(); keywords.add(new Keyword(keywordTextField.getText(), !regexRadioButton.isSelected(), exactRadioButton.isSelected())); List keywordLists = new ArrayList<>(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java index 1b55a2926c..da7cf3c459 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/GlobalEditListPanel.java @@ -125,7 +125,10 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis } } - @NbBundle.Messages("GlobalEditListPanel.editKeyword.title=Edit Keyword") + @NbBundle.Messages({"GlobalEditListPanel.editKeyword.title=Edit Keyword", + "GlobalEditListPanel.warning.title=Warning", + "GlobalEditListPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"}) + /** * Adds keywords to a keyword list, returns true if at least one keyword was * successfully added and no duplicates were found. @@ -151,6 +154,7 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis dupeCount = 0; badCount = 0; keywordsToRedisplay = ""; + boolean displayedBoundaryWarning = false; if (!dialog.getKeywords().isEmpty()) { @@ -164,6 +168,19 @@ class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionLis dupeCount++; continue; } + + // Check if it is a regex and starts or ends with a boundary character + if (( ! displayedBoundaryWarning) && dialog.isKeywordRegex()) { + if(newWord.startsWith("^") || + (newWord.endsWith("$") && ! newWord.endsWith("\\$"))) { + + KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.title"), + NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.text"), + KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN); + // Only display the warning once + displayedBoundaryWarning = true; + } + } //check if valid boolean valid = true; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java index 1ddd06680e..62be15c2c8 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/RegexQuery.java @@ -170,10 +170,13 @@ final class RegexQuery implements KeywordSearchQuery { */ // We construct the query by surrounding it with slashes (to indicate it is // a regular expression search) and .* as anchors (if the query doesn't - // already have them). + // already have them). We do not add .* if there is a boundary character. + boolean skipWildcardPrefix = queryStringContainsWildcardPrefix || getQueryString().startsWith("^"); + boolean skipWildcardSuffix = queryStringContainsWildcardSuffix || + (getQueryString().endsWith("$") && ( ! getQueryString().endsWith("\\$"))); solrQuery.setQuery((field == null ? Server.Schema.CONTENT_STR.toString() : field) + ":/" - + (queryStringContainsWildcardPrefix ? "" : ".*") + getQueryString() - + (queryStringContainsWildcardSuffix ? "" : ".*") + "/"); + + (skipWildcardPrefix ? "" : ".*") + getQueryString() + + (skipWildcardSuffix ? "" : ".*") + "/"); // Set the fields we want to have returned by the query. solrQuery.setFields(Server.Schema.CONTENT_STR.toString(), Server.Schema.ID.toString(), Server.Schema.CHUNK_SIZE.toString()); From 2b4f93c54302539d28265eb25c799dd264432386 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 13 Apr 2018 15:17:33 -0400 Subject: [PATCH 040/100] Updated doxygen --- docs/doxygen-user/adHocKeywordSearch.dox | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/doxygen-user/adHocKeywordSearch.dox b/docs/doxygen-user/adHocKeywordSearch.dox index ec7c1ab689..f8e4881fb4 100644 --- a/docs/doxygen-user/adHocKeywordSearch.dox +++ b/docs/doxygen-user/adHocKeywordSearch.dox @@ -39,6 +39,11 @@ Substring match should be used where the search term is just part of a word, or Regex match can be used to search for a specific pattern. Regular expressions are supported using Lucene Regex Syntax which is documented here: https://www.elastic.co/guide/en/elasticsearch/reference/1.6/query-dsl-regexp-query.html#regexp-syntax. Wildcards are automatically added to the beginning and end of the regular expressions to ensure all matches are found. Additionally, the resulting hits are split on common token separator boundaries (e.g. space, newline, colon, exclamation point etc.) to make the resulting keyword hit more amenable to highlighting. +Note: Since Autopsy 4.4, boundary characters ('^' and '$') no longer work as word boundaries. Previously a search for "^[0-9]{5}$" would return all five +digit strings surrounded by some type of non-word characters. For example, "The number 12345 is.." would contain a match, while "123456789 people" would not. This was because the regex +was compared against each "word" in the document. In newer versions, the text is not split into words internally so this type of search no longer works. To get similar results, replace the +boundary characters with the specific characters that should represent a word break. For example, "^[0-9]{5}$" could become "[ \.\-\,][0-9]{5}[ \.\-\,]". + There is some validation on the regex but it's best to test on a sample image to make sure your regexes are correct and working as expected. One simple way to test is by creating a sample text file that your expression should match, ingesting it as a \ref ds_log "Logical File Set" and then running the regex query. > In the year 1885 in an article titled Current Notes, the quick brown fox first jumped over the lazy dog. From 3107d81926ee5045df0d67dcb284c9a3775e00b3 Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Thu, 12 Apr 2018 14:27:22 -0400 Subject: [PATCH 041/100] unix-setup --- unix_setup.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/unix_setup.sh b/unix_setup.sh index 93a98ae1bc..197038a8d2 100755 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -6,8 +6,9 @@ TSK_VERSION=4.6.0 # Verify PhotoRec was installed photorec_filepath=/usr/bin/photorec -if [ -f "$photorec_filepath" ]; then - echo "$photorec_filepath found" +photorec_osx_filepath=/usr/local/bin/photorec +if [ -f "$photorec_filepath" ] || [ -f "$photorec_osx_filepath" ]; then + echo "photorec found" else echo "ERROR: Photorec not found, please install the testdisk package" exit 1 @@ -28,6 +29,7 @@ fi # Verify Sleuth Kit Java was installed sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar; +sleuthkit_osx_jar_filepath=/usr/local/share/java/sleuthkit-$TSK_VERSION.jar; ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-postgresql-$TSK_VERSION.jar; if [ -f "$sleuthkit_jar_filepath" ]; then echo "$sleuthkit_jar_filepath found" From 6ed2c5b1bdfb615bf2ceac3e8311121c60732641 Mon Sep 17 00:00:00 2001 From: rishwanth1995 Date: Thu, 12 Apr 2018 16:08:57 -0400 Subject: [PATCH 042/100] modified unix-setup script for osx --- unix_setup.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/unix_setup.sh b/unix_setup.sh index 197038a8d2..a1e2f6215b 100755 --- a/unix_setup.sh +++ b/unix_setup.sh @@ -28,8 +28,18 @@ else fi # Verify Sleuth Kit Java was installed -sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar; -sleuthkit_osx_jar_filepath=/usr/local/share/java/sleuthkit-$TSK_VERSION.jar; + + +if [ -f "/usr/share/java/sleuthkit-$TSK_VERSION.jar" ]; then + sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar +elif [ -f "/usr/local/share/java/sleuthkit-$TSK_VERSION.jar" ]; then + sleuthkit_jar_filepath=/usr/local/share/java/sleuthkit-$TSK_VERSION.jar +else + echo "sleuthkit.jar file not found" + echo "exiting .." + exit 1 +fi + ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-postgresql-$TSK_VERSION.jar; if [ -f "$sleuthkit_jar_filepath" ]; then echo "$sleuthkit_jar_filepath found" From 27f49273e68f028f5c200297f5c6f7ae50e810c3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 13 Apr 2018 17:20:35 -0400 Subject: [PATCH 043/100] 3723 move auto ingest monitor start up of AWT onto a new thread --- .../autoingest/AutoIngestDashboard.java | 9 ++++++++- .../autoingest/AutoIngestJobsPanel.java | 20 +++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index aca3e1cca8..482742d7de 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -222,7 +222,14 @@ final class AutoIngestDashboard extends JPanel implements Observer { autoIngestMonitor = new AutoIngestMonitor(); autoIngestMonitor.addObserver(this); - autoIngestMonitor.startUp(); + new Thread(() -> { + try { + autoIngestMonitor.startUp(); + } + catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + LOGGER.log(Level.SEVERE, "Unable to start up Auto Ingest Monitor", ex); + } + }).start(); } @Override diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java index 6e72f91d17..843089f948 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestJobsPanel.java @@ -65,9 +65,15 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa * Set up the AutoIngestJobsPanel's so that its outlineView is displaying * the correct columns for the specified AutoIngestJobStatus */ - @Messages({"AutoIngestJobsPanel.waitNode.text=Please wait..."}) + @Messages({"AutoIngestJobsPanel.waitNode.text=Please Wait..."}) void customize() { ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(Bundle.AutoIngestJobsNode_caseName_text()); + outline.setRowSelectionAllowed(false); //rows will be made selectable after table has been populated + outline.setFocusable(false); //table will be made focusable after table has been populated + if (null == explorerManager) { + explorerManager = new ExplorerManager(); + } + explorerManager.setRootContext(new EmptyNode(Bundle.AutoIngestJobsPanel_waitNode_text())); int indexOfColumn; switch (status) { case PENDING_JOB: @@ -116,14 +122,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa if (indexOfColumn != INVALID_INDEX) { outline.getColumnModel().getColumn(indexOfColumn).setPreferredWidth(INITIAL_DATASOURCE_WIDTH); } - if (null == explorerManager) { - explorerManager = new ExplorerManager(); - - } - outline.setRowSelectionAllowed(false); add(outlineView, java.awt.BorderLayout.CENTER); - EmptyNode emptyNode = new EmptyNode(Bundle.AutoIngestJobsPanel_waitNode_text()); - explorerManager.setRootContext(emptyNode); } private int getColumnIndexByName(String columnName) { @@ -170,14 +169,15 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa Node[] selectedNodes = explorerManager.getSelectedNodes(); AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); explorerManager.setRootContext(autoIngestNode); - outline.setRowSelectionAllowed(true); - if (selectedNodes.length > 0) { + outline.setRowSelectionAllowed(true); + if (selectedNodes.length > 0 && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored try { explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); } catch (PropertyVetoException ignore) { //Unable to select previously selected node } } + outline.setFocusable(true); } } From b59177c618b2c2c6d9ca41a986bfe74d66d163ba Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Fri, 13 Apr 2018 17:42:57 -0400 Subject: [PATCH 044/100] 3709: 1st test for embedded files --- Core/build.xml | 2 +- .../autopsy/ingest/EmbeddedFileTest.java | 43 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Core/build.xml b/Core/build.xml index b5b571d137..c1d33db889 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -85,7 +85,7 @@ - + diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index be44a34c3c..bdbc15aa4a 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -54,6 +54,9 @@ public class EmbeddedFileTest extends NbTestCase { private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), "EmbeddedFileTest"); private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString()); private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(),"embedded.vhd"); + public static final String HASH_VALUE = "098f6bcd4621d373cade4e832627b4f6"; + private static final int DEEP_FOLDER_COUNT = 25; + private Case openCase; public static Test suite() { NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EmbeddedFileTest.class). @@ -102,6 +105,14 @@ public class EmbeddedFileTest extends NbTestCase { Assert.fail(ex); } + + try { + openCase = Case.getOpenCase(); + runIngestJob(openCase.getDataSources()); + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } } @Override @@ -123,35 +134,31 @@ public class EmbeddedFileTest extends NbTestCase { } public void testEncription() { - final int numOfFiles = 11; try { - Case openCase = Case.getOpenCase(); - runIngestJob(openCase.getDataSources()); - - List results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%password%'"); - assertEquals(numOfFiles, results.size()); + List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'"); + String protectedName1 = "password_protected.zip"; + String protectedName2 = "level1_protected.zip"; + String protectedName3 = "42.zip"; + assertEquals(2207, results.size()); int passwdProtectedZips = 0; - int nonPasswdProcted = 0; for (AbstractFile file : results) { //.zip file has artifact TSK_ENCRYPTION_DETECTED - if (file.getNameExtension().equalsIgnoreCase("zip")) { + if (file.getName().equalsIgnoreCase(protectedName1) || file.getName().equalsIgnoreCase(protectedName2) || file.getName().equalsIgnoreCase(protectedName3)){ ArrayList artifacts = file.getAllArtifacts(); + assertEquals(1, artifacts.size()); for (BlackboardArtifact artifact : artifacts) { - assertEquals(1, artifacts.size()); assertEquals(artifact.getArtifactTypeID(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()); passwdProtectedZips++; } - } else { - assertTrue(file.getAllArtifacts().size() == 0); - nonPasswdProcted++; + } else {//No other files has artifact defined + assertEquals(0, file.getAllArtifacts().size()); } - + + } - //Make sure 2 password prected zip files has been tested - assertEquals(2, passwdProtectedZips); - //No other files has artifact TSK_ENCRYPTION_DETECTED - assertEquals(numOfFiles - passwdProtectedZips, nonPasswdProcted); - } catch (NoCurrentCaseException | TskCoreException ex) { + //Make sure 3 password protected zip files has been tested: password_protected.zip, level1_protected.zip and 42.zip that we download for bomb testing. + assertEquals(3, passwdProtectedZips); + } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); } From 1f178b2363b6799df5a7fc20b06ebf81b9553f83 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sat, 14 Apr 2018 22:54:44 -0400 Subject: [PATCH 045/100] Allow user to specify profile --- .../volatilityDSP/AddMemoryImageTask.java | 7 +- .../volatilityDSP/Bundle.properties | 6 +- .../volatilityDSP/MemoryDSInputPanel.form | 62 ++++----- .../volatilityDSP/MemoryDSInputPanel.java | 119 +++++++++++------- .../volatilityDSP/MemoryDSProcessor.java | 7 +- .../volatilityDSP/VolatilityProcessor.java | 34 +++-- 6 files changed, 141 insertions(+), 94 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java index 7490c3e7b1..e5f67909da 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/AddMemoryImageTask.java @@ -49,6 +49,7 @@ final class AddMemoryImageTask implements Runnable { private final DataSourceProcessorCallback callback; private volatile VolatilityProcessor volatilityProcessor; private volatile boolean isCancelled; + private final String profile; // empty for autodetect /** * Constructs a runnable that adds a memory image to a case database. @@ -57,6 +58,7 @@ final class AddMemoryImageTask implements Runnable { * associated with the data source that is intended * to be unique across multiple cases (e.g., a UUID). * @param memoryImagePath Path to the memory image file. + * @param profile Volatility profile to run or empty string to autodetect * @param pluginsToRun The Volatility plugins to run. * @param timeZone The time zone to use when processing dates and * times for the image, obtained from @@ -65,9 +67,10 @@ final class AddMemoryImageTask implements Runnable { * during processing. * @param callback Callback to call when processing is done. */ - AddMemoryImageTask(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddMemoryImageTask(String deviceId, String memoryImagePath, String profile, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { this.deviceId = deviceId; this.memoryImagePath = memoryImagePath; + this.profile = profile; this.pluginsToRun = pluginsToRun; this.timeZone = timeZone; this.callback = callback; @@ -94,7 +97,7 @@ final class AddMemoryImageTask implements Runnable { try { Image dataSource = addImageToCase(); dataSources.add(dataSource); - volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, pluginsToRun, progressMonitor); + volatilityProcessor = new VolatilityProcessor(memoryImagePath, dataSource, profile, pluginsToRun, progressMonitor); volatilityProcessor.run(); } catch (NoCurrentCaseException | TskCoreException | VolatilityProcessor.VolatilityProcessorException ex) { criticalErrorOccurred = true; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties index 007b78dc5a..1908725bcb 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties @@ -3,11 +3,11 @@ # and open the template in the editor. MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName=Browse for a memory image file: -MemoryDSInputPanel.PluginsToRunLabel.text=Available plugins to run: -MemoryDSInputPanel.volExecutableLabel.text=Version of Volatility to Run: +MemoryDSInputPanel.PluginsToRunLabel.text=Plugins to run: MemoryDSInputPanel.pathLabel.text=Browse for a memory image file: MemoryDSInputPanel.pathTextField.text= MemoryDSInputPanel.errorLabel.text=Error Label MemoryDSInputPanel.browseButton.text=Browse MemoryDSImputPanel.pathTextField.text= -MemoryDSInputPanel.timeZoneLabel.text=Please select the input timezone: \ No newline at end of file +MemoryDSInputPanel.timeZoneLabel.text=Please select the input timezone: +MemoryDSInputPanel.jLabel1.text=Profile: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form index 123f9b308b..a05b8261a0 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form @@ -32,9 +32,9 @@ - + @@ -43,8 +43,8 @@ - + @@ -66,17 +66,17 @@ - + - - + + - + - + @@ -139,27 +139,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -174,7 +153,7 @@ - + @@ -189,5 +168,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index f006687c3e..48244cb896 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -26,7 +26,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SimpleTimeZone; +import java.util.SortedSet; import java.util.TimeZone; +import java.util.TreeSet; import javax.swing.JFileChooser; import javax.swing.JPanel; import javax.swing.JTable; @@ -54,6 +56,20 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private final List PluginListNames = new ArrayList<>(); private final Map pluginListStates = new HashMap<>(); // is set by listeners when users select and deselect items + private final SortedSet profileList = new TreeSet<>(Arrays.asList( + "VistaSP0x64", "VistaSP0x86", "VistaSP1x64", "VistaSP1x86", + "VistaSP2x64", "VistaSP2x86", "Win10x64", "Win10x64_10586", + "Win10x64_14393", "Win10x86", "Win10x86_10586", "Win10x86_14393", + "Win2003SP0x86", "Win2003SP1x64", "Win2003SP1x86", "Win2003SP2x64", + "Win2003SP2x86", "Win2008R2SP0x64", "Win2008R2SP1x64", "Win2008R2SP1x64_23418", + "Win2008SP1x64", "Win2008SP1x86", "Win2008SP2x64", "Win2008SP2x86", + "Win2012R2x64", "Win2012R2x64_18340", "Win2012x64", "Win2016x64_14393", + "Win7SP0x64", "Win7SP0x86", "Win7SP1x64", "Win7SP1x64_23418", "Win7SP1x86_23418", + "Win81U1x64", "Win81U1x86", "Win8SP0x64", "Win8SP0x86", "Win8SP1x64", + "Win8SP1x64_18340", "Win8SP1x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP2x86", + "WinXPSP3x86")); + private final String AUTODETECT_PROFILE = "Auto Detect"; + /** * Creates new MemoryDSInputPanel panel for user input */ @@ -81,7 +97,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { instance.postInit(); instance.customizePluginListTable(); instance.createTimeZoneList(); - instance.createVolatilityVersionList(); + instance.populateProfileCombobox(); instance.createPluginList(); return instance; @@ -94,14 +110,14 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } private void customizePluginListTable() { - PluginList.setModel(tableModel); - PluginList.setTableHeader(null); - PluginList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + pluginTable.setModel(tableModel); + pluginTable.setTableHeader(null); + pluginTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); final int width = listsScrollPane.getPreferredSize().width; - PluginList.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + pluginTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); TableColumn column; - for (int i = 0; i < PluginList.getColumnCount(); i++) { - column = PluginList.getColumnModel().getColumn(i); + for (int i = 0; i < pluginTable.getColumnCount(); i++) { + column = pluginTable.getColumnModel().getColumn(i); if (i == 0) { column.setPreferredWidth(((int) (width * 0.07))); } else { @@ -137,11 +153,12 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { timeZoneComboBox.setSelectedItem(formatted); } - private void createVolatilityVersionList() { - - volExecutableComboBox.addItem("2.6"); - volExecutableComboBox.addItem("2.5"); - + + private void populateProfileCombobox() { + profileComboBox.addItem(AUTODETECT_PROFILE); + profileList.forEach((profile) -> { + profileComboBox.addItem(profile); + }); } private void createPluginList() { @@ -182,17 +199,17 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { errorLabel = new javax.swing.JLabel(); timeZoneLabel = new javax.swing.JLabel(); timeZoneComboBox = new javax.swing.JComboBox<>(); - volExecutableLabel = new javax.swing.JLabel(); - volExecutableComboBox = new javax.swing.JComboBox<>(); PluginsToRunLabel = new javax.swing.JLabel(); listsScrollPane = new javax.swing.JScrollPane(); - PluginList = new javax.swing.JTable(); + pluginTable = new javax.swing.JTable(); + jLabel1 = new javax.swing.JLabel(); + profileComboBox = new javax.swing.JComboBox<>(); - org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.pathLabel.text")); // NOI18N - pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathTextField.text")); // NOI18N + pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.pathTextField.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.browseButton.text")); // NOI18N browseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { browseButtonActionPerformed(evt); @@ -200,24 +217,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { }); errorLabel.setForeground(new java.awt.Color(255, 0, 0)); - org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.errorLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.errorLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.timeZoneLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.timeZoneLabel.text")); // NOI18N timeZoneComboBox.setMaximumRowCount(30); - org.openide.awt.Mnemonics.setLocalizedText(volExecutableLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.volExecutableLabel.text")); // NOI18N - - volExecutableComboBox.setEnabled(false); - volExecutableComboBox.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - volExecutableComboBoxActionPerformed(evt); - } - }); - org.openide.awt.Mnemonics.setLocalizedText(PluginsToRunLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.PluginsToRunLabel.text")); // NOI18N - PluginList.setModel(new javax.swing.table.DefaultTableModel( + pluginTable.setModel(new javax.swing.table.DefaultTableModel( new Object [][] { {}, {}, @@ -228,7 +236,16 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } )); - listsScrollPane.setViewportView(PluginList); + listsScrollPane.setViewportView(pluginTable); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.jLabel1.text")); // NOI18N + + profileComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + profileComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + profileComboBoxActionPerformed(evt); + } + }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); @@ -245,15 +262,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) .addGap(0, 163, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(errorLabel) - .addComponent(volExecutableLabel) - .addComponent(PluginsToRunLabel)) + .addComponent(PluginsToRunLabel) + .addComponent(jLabel1)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -270,15 +287,15 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(errorLabel) - .addGap(18, 18, 18) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(volExecutableLabel) - .addComponent(volExecutableComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jLabel1) + .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(PluginsToRunLabel) - .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 132, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(30, Short.MAX_VALUE)) + .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(59, Short.MAX_VALUE)) ); pathLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName")); // NOI18N @@ -299,23 +316,23 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { } }//GEN-LAST:event_browseButtonActionPerformed - private void volExecutableComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_volExecutableComboBoxActionPerformed + private void profileComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_profileComboBoxActionPerformed // TODO add your handling code here: - }//GEN-LAST:event_volExecutableComboBoxActionPerformed + }//GEN-LAST:event_profileComboBoxActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTable PluginList; private javax.swing.JLabel PluginsToRunLabel; private javax.swing.JButton browseButton; private javax.swing.JLabel errorLabel; private javax.swing.ButtonGroup infileTypeButtonGroup; + private javax.swing.JLabel jLabel1; private javax.swing.JScrollPane listsScrollPane; private javax.swing.JLabel pathLabel; private javax.swing.JTextField pathTextField; + private javax.swing.JTable pluginTable; + private javax.swing.JComboBox profileComboBox; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; - private javax.swing.JComboBox volExecutableComboBox; - private javax.swing.JLabel volExecutableLabel; // End of variables declaration//GEN-END:variables /** * Get the path of the user selected image. @@ -326,6 +343,18 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { return pathTextField.getText(); } + /** + * + * @return Profile or empty string if auto detect + */ + String getProfile() { + String profile = (String)profileComboBox.getSelectedItem(); + if (profile.equals(AUTODETECT_PROFILE)) { + return ""; + } + return profile; + } + List getPluginsToRun() { List enabledPlugins = new ArrayList<>(); Map pluginSettingsToSave = new HashMap<>(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java index 9791ad1f09..dc05cb8376 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSProcessor.java @@ -117,7 +117,7 @@ public class MemoryDSProcessor implements DataSourceProcessor { @Override public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { configPanel.storeSettings(); - run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback); + run(UUID.randomUUID().toString(), configPanel.getImageFilePath(), configPanel.getProfile(), configPanel.getPluginsToRun(), configPanel.getTimeZone(), progressMonitor, callback); } /** @@ -131,6 +131,7 @@ public class MemoryDSProcessor implements DataSourceProcessor { * associated with the data source that is intended * to be unique across multiple cases (e.g., a UUID). * @param memoryImagePath Path to the memory image file. + * @param profile Volatility profile to run or empty string to autodetect * @param pluginsToRun The Volatility plugins to run. * @param timeZone The time zone to use when processing dates and * times for the image, obtained from @@ -139,8 +140,8 @@ public class MemoryDSProcessor implements DataSourceProcessor { * processing. * @param callback Callback to call when processing is done. */ - private void run(String deviceId, String memoryImagePath, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, pluginsToRun, timeZone, progressMonitor, callback); + private void run(String deviceId, String memoryImagePath, String profile, List pluginsToRun, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + addImageTask = new AddMemoryImageTask(deviceId, memoryImagePath, profile, pluginsToRun, timeZone, progressMonitor, callback); new Thread(addImageTask).start(); } diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index 59293de566..ec85fc21e5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -71,6 +71,7 @@ class VolatilityProcessor { private FileManager fileManager; private volatile boolean isCancelled; private Content outputVirtDir; + private String profile; /** * Constructs a processor that runs Volatility on a given memory image file @@ -78,11 +79,13 @@ class VolatilityProcessor { * * @param memoryImagePath Path to memory image file. * @param dataSource The memory image data source. + * @param profile Volatility profile to run or empty string to autodetect * @param plugInToRuns Volatility plugins to run. * @param progressMonitor Progress monitor for reporting progress during * processing. */ - VolatilityProcessor(String memoryImagePath, Image dataSource, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) { + VolatilityProcessor(String memoryImagePath, Image dataSource, String profile, List plugInToRun, DataSourceProcessorProgressMonitor progressMonitor) { + this.profile = profile; this.memoryImagePath = memoryImagePath; this.pluginsToRun = plugInToRun; this.dataSource = dataSource; @@ -133,11 +136,16 @@ class VolatilityProcessor { File directory = new File(String.valueOf(moduleOutputPath)); if (!directory.exists()) { directory.mkdirs(); + + } + + // if they did not specify a profile, then run imageinfo to get one + if (profile.isEmpty() ) { progressMonitor.setProgressText(Bundle.VolatilityProcessor_progressMessage_runningImageInfo("imageinfo")); //NON-NLS runVolatilityPlugin("imageinfo"); //NON-NLS + profile = getProfileFromImageInfoOutput(); } - progressMonitor.setIndeterminate(false); progressMonitor.setProgressMax(pluginsToRun.size()); for (int i = 0; i < pluginsToRun.size(); i++) { @@ -186,13 +194,9 @@ class VolatilityProcessor { commandLine.add("\"" + executableFile + "\""); //NON-NLS File memoryImage = new File(memoryImagePath); commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS - - File imageInfoOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS - if (imageInfoOutputFile.exists()) { - String memoryProfile = parseImageInfoOutput(imageInfoOutputFile); - commandLine.add("--profile=" + memoryProfile); //NON-NLS + if (profile.isEmpty() == false) { + commandLine.add("--profile=" + profile); //NON-NLS } - commandLine.add(pluginToRun); String outputFileAsString = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS @@ -258,12 +262,18 @@ class VolatilityProcessor { @NbBundle.Messages({ "VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info" }) - private String parseImageInfoOutput(File imageOutputFile) throws VolatilityProcessorException { + private String getProfileFromImageInfoOutput() throws VolatilityProcessorException { + File imageOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) { String fileRead = br.readLine(); - String[] profileLine = fileRead.split(":"); //NON-NLS - String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS - return memProfile[0].replaceAll("\\s+", ""); //NON-NLS + if (fileRead != null) { + String[] profileLine = fileRead.split(":"); //NON-NLS + String[] memProfile = profileLine[1].split(",|\\("); //NON-NLS + return memProfile[0].replaceAll("\\s+", ""); //NON-NLS + } + else { + throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo()); + } } catch (IOException ex) { throw new VolatilityProcessorException(Bundle.VolatilityProcessor_exceptionMessage_failedToParseImageInfo(), ex); } From c899b2b5945717df993bae9e66d69004ba85ea2c Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sat, 14 Apr 2018 23:34:29 -0400 Subject: [PATCH 046/100] make interesting file same priority as hash and keyword hits --- .../autopsy/corecomponents/DataContentViewerArtifact.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java index 48e84e6d79..f1a3fe59b9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataContentViewerArtifact.java @@ -474,7 +474,8 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat // file and not more details about the artifact if ((artifact == null) || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()) - || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())) { + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) + || (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())) { return 3; } else { return 6; From de5a5ec5894ad60dfeedf49b03269d5d7d607e66 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sat, 14 Apr 2018 23:41:07 -0400 Subject: [PATCH 047/100] revert to original API --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 65e6c5ab42..11f3fa90fd 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -1543,8 +1543,8 @@ public class Case { * @throws TskCoreException if there is a problem adding the report to the * case database. */ - public Report addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { - return addReport(localPath, srcModuleName, reportName, null); + public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { + addReport(localPath, srcModuleName, reportName, null); } /** From b77f0007a8c24a3831ac67e4fec1c56c4ec265fe Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 15 Apr 2018 00:00:25 -0400 Subject: [PATCH 048/100] Fix bundle name --- .../experimental/volatilityDSP/MemoryDSInputPanel.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index 48244cb896..18ef95f36b 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -205,11 +205,11 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { jLabel1 = new javax.swing.JLabel(); profileComboBox = new javax.swing.JComboBox<>(); - org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.pathLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N - pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.pathTextField.text")); // NOI18N + pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathTextField.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.browseButton.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N browseButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { browseButtonActionPerformed(evt); @@ -217,9 +217,9 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { }); errorLabel.setForeground(new java.awt.Color(255, 0, 0)); - org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.errorLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.errorLabel.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "RawDSInputPanel.timeZoneLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.timeZoneLabel.text")); // NOI18N timeZoneComboBox.setMaximumRowCount(30); From ee096bba59501ed30910da8b36567317812fc180 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 15 Apr 2018 00:11:27 -0400 Subject: [PATCH 049/100] cleanup old RAW references --- .../volatilityDSP/MemoryDSInputPanel.form | 16 +++++----------- .../volatilityDSP/MemoryDSInputPanel.java | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form index a05b8261a0..1b52f24aea 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form @@ -85,7 +85,7 @@ - + @@ -97,14 +97,14 @@ - + - + @@ -117,14 +117,14 @@ - + - + @@ -178,12 +178,6 @@ - - - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index 18ef95f36b..7d7427bc21 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -240,7 +240,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.jLabel1.text")); // NOI18N - profileComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + profileComboBox.setModel(new javax.swing.DefaultComboBoxModel<>()); profileComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { profileComboBoxActionPerformed(evt); From cf7970cb4294f9a744f24dfcaca737a0318838fd Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 15 Apr 2018 00:51:05 -0400 Subject: [PATCH 050/100] Codacy messages and more bundle cleanup --- .../volatilityDSP/Bundle.properties | 3 +- .../volatilityDSP/MemoryDSInputPanel.form | 29 ++++++++++--------- .../volatilityDSP/MemoryDSInputPanel.java | 22 ++++++++++---- .../volatilityDSP/VolatilityProcessor.java | 1 - 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties index 1908725bcb..4f6bc6b9b9 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties @@ -8,6 +8,5 @@ MemoryDSInputPanel.pathLabel.text=Browse for a memory image file: MemoryDSInputPanel.pathTextField.text= MemoryDSInputPanel.errorLabel.text=Error Label MemoryDSInputPanel.browseButton.text=Browse -MemoryDSImputPanel.pathTextField.text= -MemoryDSInputPanel.timeZoneLabel.text=Please select the input timezone: +MemoryDSInputPanel.timeZoneLabel.text=Timezone: MemoryDSInputPanel.jLabel1.text=Profile: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form index 1b52f24aea..3529fe8920 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form @@ -28,13 +28,15 @@ - - + + - - + + + + @@ -76,7 +78,7 @@ - + @@ -85,7 +87,7 @@ - + @@ -97,14 +99,17 @@ - + + + + - + @@ -117,14 +122,14 @@ - + - + @@ -176,10 +181,6 @@ - - - - diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index 7d7427bc21..beaa46fc36 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.PathValidator; +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives final class MemoryDSInputPanel extends JPanel implements DocumentListener { private static final long serialVersionUID = 1L; //default @@ -68,7 +69,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { "Win81U1x64", "Win81U1x86", "Win8SP0x64", "Win8SP0x86", "Win8SP1x64", "Win8SP1x64_18340", "Win8SP1x86", "WinXPSP1x64", "WinXPSP2x64", "WinXPSP2x86", "WinXPSP3x86")); - private final String AUTODETECT_PROFILE = "Auto Detect"; + private final static String AUTODETECT_PROFILE = "Auto Detect"; /** * Creates new MemoryDSInputPanel panel for user input @@ -208,6 +209,11 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N pathTextField.setText(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathTextField.text")); // NOI18N + pathTextField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + pathTextFieldActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.browseButton.text")); // NOI18N browseButton.addActionListener(new java.awt.event.ActionListener() { @@ -240,7 +246,6 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.jLabel1.text")); // NOI18N - profileComboBox.setModel(new javax.swing.DefaultComboBoxModel<>()); profileComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { profileComboBoxActionPerformed(evt); @@ -259,12 +264,13 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(pathLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 218, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 168, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 248, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(profileComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, 243, Short.MAX_VALUE) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.Alignment.LEADING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))) .addGap(0, 163, Short.MAX_VALUE)) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -295,7 +301,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(PluginsToRunLabel) .addComponent(listsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addContainerGap(59, Short.MAX_VALUE)) + .addContainerGap(73, Short.MAX_VALUE)) ); pathLabel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.AccessibleContext.accessibleName")); // NOI18N @@ -320,6 +326,10 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { // TODO add your handling code here: }//GEN-LAST:event_profileComboBoxActionPerformed + private void pathTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pathTextFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_pathTextFieldActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel PluginsToRunLabel; private javax.swing.JButton browseButton; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index ec85fc21e5..1d23367d65 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; import org.openide.modules.InstalledFileLocator; -import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; From 47564a167eda4affc0dd31eb8c518827d58ae755 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 16 Apr 2018 14:06:36 -0400 Subject: [PATCH 051/100] Switch to saving nanoseconds to milliseconds (as double precision) in health monitor --- .../EnterpriseHealthMonitor.java | 30 +++++++++---------- .../autopsy/healthmonitor/TimingMetric.java | 8 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 35bc6b6bfa..c1c09a3337 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -349,9 +349,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { statement.setString(2, hostName); statement.setLong(3, System.currentTimeMillis()); statement.setLong(4, info.getCount()); - statement.setLong(5, info.getAverage()); - statement.setLong(6, info.getMax()); - statement.setLong(7, info.getMin()); + statement.setDouble(5, info.getAverage()); + statement.setDouble(6, info.getMax()); + statement.setDouble(7, info.getMin()); statement.execute(); } @@ -599,9 +599,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { "host text NOT NULL," + "timestamp bigint NOT NULL," + "count bigint NOT NULL," + - "average bigint NOT NULL," + - "max bigint NOT NULL," + - "min bigint NOT NULL" + + "average double precision NOT NULL," + + "max double precision NOT NULL," + + "min double precision NOT NULL" + ")"; statement.execute(createTimingTable); @@ -715,9 +715,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { */ private class TimingInfo { private long count; // Number of metrics collected - private long sum; // Sum of the durations collected (nanoseconds) - private long max; // Maximum value found (nanoseconds) - private long min; // Minimum value found (nanoseconds) + private double sum; // Sum of the durations collected (nanoseconds) + private double max; // Maximum value found (nanoseconds) + private double min; // Minimum value found (nanoseconds) TimingInfo(TimingMetric metric) throws HealthMonitorException { count = 1; @@ -752,25 +752,25 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { /** * Get the average duration - * @return average duration (nanoseconds) + * @return average duration (milliseconds) */ - long getAverage() { + double getAverage() { return sum / count; } /** * Get the maximum duration - * @return maximum duration (nanoseconds) + * @return maximum duration (milliseconds) */ - long getMax() { + double getMax() { return max; } /** * Get the minimum duration - * @return minimum duration (nanoseconds) + * @return minimum duration (milliseconds) */ - long getMin() { + double getMin() { return min; } diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java index d94dfd7161..384073d461 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -25,7 +25,7 @@ public class TimingMetric { private final String name; private final long startingTimestamp; - private Long duration; + private Double duration; TimingMetric(String name) { this.name = name; @@ -38,7 +38,7 @@ public class TimingMetric { */ void stopTiming() { long endingTimestamp = System.nanoTime(); - this.duration = endingTimestamp - startingTimestamp; + this.duration = (double)(endingTimestamp - startingTimestamp) / 1000000; } /** @@ -52,10 +52,10 @@ public class TimingMetric { /** * Get the duration of the metric. Will throw an exception if the * metric has not been stopped. - * @return how long the metric was running (nanoseconds) + * @return how long the metric was running (milliseconds) * @throws HealthMonitorException */ - long getDuration() throws HealthMonitorException { + double getDuration() throws HealthMonitorException { if (duration != null) { return duration; } else { From 6b005512c1102d2eac83458323721daa37e704fb Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 16 Apr 2018 16:14:20 -0400 Subject: [PATCH 052/100] Add missing case_properties.png --- docs/doxygen-user/images/case_properties.png | Bin 0 -> 26951 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/doxygen-user/images/case_properties.png diff --git a/docs/doxygen-user/images/case_properties.png b/docs/doxygen-user/images/case_properties.png new file mode 100644 index 0000000000000000000000000000000000000000..75784e1cc6f50249861cc5bdfefc2358f5236953 GIT binary patch literal 26951 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 Date: Tue, 17 Apr 2018 09:14:44 -0400 Subject: [PATCH 053/100] made build-zip the same for all platform --- build.xml | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/build.xml b/build.xml index 9a8b8bd507..0995b07e10 100644 --- a/build.xml +++ b/build.xml @@ -91,19 +91,10 @@ - - - - - - - - - - - - - + + + + From f89dca903c41f96c9ef3b7ce6fc168086b32112c Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 17 Apr 2018 10:10:10 -0400 Subject: [PATCH 054/100] Adding new public method to handle "time per X" metrics --- .../EnterpriseHealthMonitor.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index c1c09a3337..cd50553126 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -276,6 +276,27 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } + /** + * Submit the metric that was previously obtained through getTimingMetric(), + * incorporating a count that the time should be divided by. + * Call this immediately after the section of code being timed. + * This method is safe to call regardless of whether the Enterprise Health + * Monitor is enabled. + * @param metric The TimingMetric object obtained from getTimingMetric() + * @param count The number to divide the time by + */ + public static void submitNormalizedTimingMetric(TimingMetric metric, long count) { + if(isEnabled.get() && (metric != null)) { + metric.stopTiming(); + try { + getInstance().addTimingMetric(metric); // TODO - make new method using count + } catch (HealthMonitorException ex) { + // We don't want calling methods to have to check for exceptions, so just log it + logger.log(Level.SEVERE, "Error adding timing metric", ex); + } + } + } + /** * Add the timing metric data to the map. * @param metric The metric to add. stopTiming() should already have been called. From dfe2a308249be6c52cbea510ec6c97a792ec06de Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 18 Apr 2018 08:24:51 -0400 Subject: [PATCH 055/100] Make core result viewers into service providers again --- .../AbstractDataResultViewer.java | 21 +- .../corecomponents/DataResultPanel.java | 18 +- .../corecomponents/DataResultViewerTable.java | 388 ++++++++++-------- .../DataResultViewerThumbnail.java | 9 +- 4 files changed, 220 insertions(+), 216 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index e02493ee31..9dd387e0ae 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -40,6 +40,9 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); private transient ExplorerManager explorerManager; + AbstractDataResultViewer() { + } + /** * This constructor is intended to allow an AbstractDataResultViewer to use * an ExplorerManager provided by a TopComponent, allowing Node selections @@ -54,28 +57,10 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView this.explorerManager = explorerManager; } - /** - * This constructor can be used by AbstractDataResultViewers that do not - * need to make Node selections available to Actions via the action global - * context lookup. - */ - public AbstractDataResultViewer() { - this(new ExplorerManager()); - } - @Override public void clearComponent() { } - public Node getSelectedNode() { - Node result = null; - Node[] selectedNodes = this.getExplorerManager().getSelectedNodes(); - if (selectedNodes.length > 0) { - result = selectedNodes[0]; - } - return result; - } - @Override public void expandNode(Node n) { } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index d3fd551d54..c15b47447a 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -292,23 +292,13 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C /* * Load the child result viewers into the tabbed pane. */ - if (0 == dataResultTabbedPanel.getTabCount()) { - /* - * TODO (JIRA-2658): Fix the DataResultViewer extension point. When - * this is done, restore the implementation of DataResultViewerTable - * and DataREsultViewerThumbnail as DataResultViewer service - * providers. - */ - addResultViewer(new DataResultViewerTable(this.explorerManager)); - addResultViewer(new DataResultViewerThumbnail(this.explorerManager)); - for (DataResultViewer factory : Lookup.getDefault().lookupAll(DataResultViewer.class)) { - DataResultViewer resultViewer; + if (dataResultTabbedPanel.getTabCount() == 0) { + for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) { if (isMain) { - resultViewer = factory; + addResultViewer(resultViewer); } else { - resultViewer = factory.createInstance(); + addResultViewer(resultViewer.createInstance()); } - addResultViewer(resultViewer); } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 2c71d288f8..8738884251 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012 - 2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -60,151 +60,142 @@ import org.openide.nodes.Node; import org.openide.nodes.Node.Property; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; /** - * A tabular viewer for the results view. - * - * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, - * restore implementation of DataResultViewerTable as a DataResultViewer service - * provider. + * A tabular results viewer that displays the children of a given root node + * using an OutlineView. */ -//@ServiceProvider(service = DataResultViewer.class) -public class DataResultViewerTable extends AbstractDataResultViewer { +@ServiceProvider(service = DataResultViewer.class) +public final class DataResultViewerTable extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; - private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); @NbBundle.Messages("DataResultViewerTable.firstColLbl=Name") static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl(); - private static final Color TAGGED_COLOR = new Color(255, 255, 195); - + static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195); + private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); private final String title; - - /** - * The properties map: - * - * stored value of column index -> property at that index - * - * We move around stored values instead of directly using the column indices - * in order to not override settings for a column that may not appear in the - * current table view due to its collection of its children's properties. - */ + private Outline outline; + private TableListener outlineViewListener; + private Node currentRootNode; + private final Map columnMap = new HashMap<>(); private final Map> propertiesMap = new TreeMap<>(); /** - * Stores references to the actual table column objects, keyed by column - * name, so that we can check there visibility later in - * storeColumnVisibility(). + * Constructs a tabular results viewer that displays the children of a given + * root node using an OutlineView. */ - private final Map columnMap = new HashMap<>(); - - private Node currentRoot; - - /* - * Convience reference to internal Outline. - */ - private Outline outline; + public DataResultViewerTable() { + super(); + this.title = Bundle.DataResultViewerTable_title(); + initialize(); + } /** - * Listener for table model event and mouse clicks. - */ - private final TableListener tableListener; - - /** - * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions, and the default title. + * Constructs a tabular results viewer that displays the children of a given + * root node using an OutlineView, with a given explorer manager. * - * @param explorerManager allow for explorer manager sharing + * @param explorerManager The explorer manager. */ public DataResultViewerTable(ExplorerManager explorerManager) { this(explorerManager, Bundle.DataResultViewerTable_title()); } /** - * Creates a DataResultViewerTable object that is compatible with node - * multiple selection actions, and a custom title. + * Constructs a tabular results viewer that displays the children of a given + * root node using an OutlineView, with a given explorer manager, and a + * custom title. * - * @param explorerManager allow for explorer manager sharing + * @param explorerManager The explorer manager. * @param title The custom title. */ public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); this.title = title; - + initialize(); + } + + /* + * Initializes this tabular results viewer. + */ + private void initialize() { + /* + * Execute the code generated by the GUI builder. + */ initComponents(); - + + /* + * Configure the child OutlineView (explorer view) component. + */ outlineView.setAllowedDragActions(DnDConstants.ACTION_NONE); outline = outlineView.getOutline(); outline.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - outline.setRootVisible(false); // don't show the root node + outline.setRootVisible(false); outline.setDragEnabled(false); outline.setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); - // add a listener so that when columns are moved, the new order is stored - tableListener = new TableListener(); - outline.getColumnModel().addColumnModelListener(tableListener); - // the listener also moves columns back if user tries to move the first column out of place - outline.getTableHeader().addMouseListener(tableListener); + + /* + * Add a table listener to the child OutlineView (explorer view) to + * persist the order of the table columns when a column is moved. + */ + outlineViewListener = new TableListener(); + outline.getColumnModel().addColumnModelListener(outlineViewListener); + + /* + * Add a mouse listener to the child OutlineView (explorer view) to make + * sure the first column of the table is kept in place. + */ + outline.getTableHeader().addMouseListener(outlineViewListener); } /** - * Creates a DataResultViewerTable object that is NOT compatible with node - * multiple selection actions. - */ - public DataResultViewerTable() { - this(new ExplorerManager(),Bundle.DataResultViewerTable_title()); - } - - - /** - * Expand node + * Creates a new instance of a tabular results viewer that displays the + * children of a given root node using an OutlineView. This method exists to + * make it possible to use the default service provider instance of this + * class in the "main" results view of the application, while using distinct + * instances in other places in the UI. * - * @param n Node to expand + * @return A new instance of a tabular results viewer, */ @Override - public void expandNode(Node n) { - super.expandNode(n); - - outlineView.expandNode(n); + public DataResultViewer createInstance() { + return new DataResultViewerTable(); } /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. + * Gets the title of this tabular results viewer. */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) - ); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - private org.openide.explorer.view.OutlineView outlineView; - // End of variables declaration//GEN-END:variables - @Override - public boolean isSupported(Node selectedNode) { + @NbBundle.Messages("DataResultViewerTable.title=Table") + public String getTitle() { + return title; + } + + /** + * Indicates whether a given node is supported as a root node for this + * tabular viewer. + * + * @param candidateRootNode The candidate root node. + * + * @return + */ + @Override + public boolean isSupported(Node candidateRootNode) { return true; } + /** + * Sets the current root node of this tabular results viewer. + * + * @param rootNode The node to set as the current root node, possibly null. + */ @Override @ThreadConfined(type = ThreadConfined.ThreadType.AWT) - public void setNode(Node selectedNode) { - + public void setNode(Node rootNode) { /* * The quick filter must be reset because when determining column width, * ETable.getRowCount is called, and the documentation states that quick @@ -213,30 +204,30 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * model." */ outline.unsetQuickFilter(); - // change the cursor to "waiting cursor" for this operation + this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - boolean hasChildren = false; - if (selectedNode != null) { - // @@@ This just did a DB round trip to get the count and the results were not saved... - hasChildren = selectedNode.getChildren().getNodesCount() > 0; - } - - if (hasChildren) { - currentRoot = selectedNode; - this.getExplorerManager().setRootContext(currentRoot); + /* + * If the given node is not null and has children, set it as the + * root context of the child OutlineView, otherwise make an + * "empty"node the root context. + * + * IMPORTANT NOTE: This is the first of many times where a + * getChildren call on the current root node causes all of the + * children of the root node to be created and defeats lazy child + * node creation, if it is enabled. It also likely leads to many + * case database round trips. + */ + if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { + currentRootNode = rootNode; + this.getExplorerManager().setRootContext(currentRootNode); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); - this.getExplorerManager().setRootContext(emptyNode); // make empty node + this.getExplorerManager().setRootContext(emptyNode); outline.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); - - /* - * Since we are modifying the columns, we don't want to listen - * to added/removed events as un-hide/hide. - */ - tableListener.listenToVisibilityChanges(false); - outlineView.setPropertyColumns(); // set the empty property header + outlineViewListener.listenToVisibilityChanges(false); + outlineView.setPropertyColumns(); } } finally { this.setCursor(null); @@ -244,17 +235,18 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Create Column Headers based on the Content represented by the Nodes in - * the table. Load persisted column order, sorting and visibility. + * Sets up the Outline view of this tabular results viewer by creating + * column headers based on the children of the current root node. The + * persisted column order, sorting and visibility is used. */ private void setupTable() { /* * Since we are modifying the columns, we don't want to listen to * added/removed events as un-hide/hide, until the table setup is done. */ - tableListener.listenToVisibilityChanges(false); + outlineViewListener.listenToVisibilityChanges(false); - /** + /* * OutlineView makes the first column be the result of * node.getDisplayName with the icon. This duplicates our first column, * which is the file name, etc. So, pop that property off the list, but @@ -286,7 +278,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { setColumnWidths(); - //Load column sorting information from preferences file and apply it to columns. + /* + * Load column sorting information from preferences file and apply it to + * columns. + */ loadColumnSorting(); /* @@ -298,7 +293,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ populateColumnMap(); - //Load column visibility information from preferences file and apply it to columns. + /* + * Load column visibility information from preferences file and apply it + * to columns. + */ loadColumnVisibility(); /* @@ -306,10 +304,10 @@ public class DataResultViewerTable extends AbstractDataResultViewer { * it. */ SwingUtilities.invokeLater(() -> { - if (currentRoot instanceof TableFilterNode) { - NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRoot).getChildNodeSelectionInfo(); + if (currentRootNode instanceof TableFilterNode) { + NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRootNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { - Node[] childNodes = currentRoot.getChildren().getNodes(true); + Node[] childNodes = currentRootNode.getChildren().getNodes(true); for (int i = 0; i < childNodes.length; ++i) { Node childNode = childNodes[i]; if (selectedChildInfo.matches(childNode)) { @@ -321,17 +319,21 @@ public class DataResultViewerTable extends AbstractDataResultViewer { break; } } - ((TableFilterNode) currentRoot).setChildNodeSelectionInfo(null); + ((TableFilterNode) currentRootNode).setChildNodeSelectionInfo(null); } } }); - //the table setup is done, so any added/removed events can now be treated as un-hide/hide. - tableListener.listenToVisibilityChanges(true); + /* + * The table setup is done, so any added/removed events can now be + * treated as un-hide/hide. + */ + outlineViewListener.listenToVisibilityChanges(true); } /* - * Populate the map with references to the column objects for use when + * Populates the column map for the child OutlineView of this tabular + * results viewer with references to the column objects for use when * loading/storing the visibility info. */ private void populateColumnMap() { @@ -348,8 +350,12 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } } + /* + * Sets the column widths for the child OutlineView of this tabular results + * viewer. + */ private void setColumnWidths() { - if (currentRoot.getChildren().getNodesCount() != 0) { + if (currentRootNode.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); if (graphics != null) { final FontMetrics metrics = graphics.getFontMetrics(); @@ -385,8 +391,11 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } } + /* + * Sets up the columns for the child OutlineView of this tabular results + * viewer with respect to column names and visisbility. + */ synchronized private void assignColumns(List> props) { - // Get the columns setup with respect to names and sortability String[] propStrings = new String[props.size() * 2]; for (int i = 0; i < props.size(); i++) { final Property prop = props.get(i); @@ -399,29 +408,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer { propStrings[2 * i] = prop.getName(); propStrings[2 * i + 1] = prop.getDisplayName(); } - outlineView.setPropertyColumns(propStrings); } /** - * Store the current column visibility information into a preference file. + * Persists the current column visibility information for the child + * OutlineView of this tabular results viewer using a preferences file. */ private synchronized void storeColumnVisibility() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (currentRootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRoot; + if (currentRootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) currentRootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); - - //store hidden state for (Map.Entry entry : columnMap.entrySet()) { - String columnName = entry.getKey(); final String columnHiddenKey = ResultViewerPersistence.getColumnHiddenKey(tfn, columnName); final TableColumn column = entry.getValue(); - boolean columnHidden = columnModel.isColumnHidden(column); if (columnHidden) { preferences.putBoolean(columnHiddenKey, true); @@ -433,16 +438,16 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Store the current column order information into a preference file. + * Persists the current column ordering for the child OutlineView of this + * tabular results viewer using a preferences file. */ private synchronized void storeColumnOrder() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (currentRootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRoot; + if (currentRootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) currentRootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - // Store the current order of the columns into settings for (Map.Entry> entry : propertiesMap.entrySet()) { preferences.putInt(ResultViewerPersistence.getColumnPositionKey(tfn, entry.getValue().getName()), entry.getKey()); @@ -451,20 +456,19 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Store the current column sorting information into a preference file. + * Persists the current column sorting information using a preferences file. */ private synchronized void storeColumnSorting() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (currentRootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRoot instanceof TableFilterNode) { - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + if (currentRootNode instanceof TableFilterNode) { + final TableFilterNode tfn = ((TableFilterNode) currentRootNode); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry entry : columnMap.entrySet()) { ETableColumn etc = entry.getValue(); String columnName = entry.getKey(); - //store sort rank and order final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, columnName); final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, columnName); @@ -482,47 +486,43 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Reads and applies the column sorting information persisted to the - * preferences file. Must be called after loadColumnOrder() since it depends - * on propertiesMap being initialized, and after assignColumns since it - * cannot set the sort on columns that have not been added to the table. + * preferences file. Must be called after loadColumnOrder, since it depends + * on the properties map being initialized, and after assignColumns, since + * it cannot set the sort on columns that have not been added to the table. */ private synchronized void loadColumnSorting() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (currentRootNode == null || propertiesMap.isEmpty()) { return; } - - if (currentRoot instanceof TableFilterNode) { - final TableFilterNode tfn = (TableFilterNode) currentRoot; - + if (currentRootNode instanceof TableFilterNode) { + final TableFilterNode tfn = (TableFilterNode) currentRootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); //organize property sorting information, sorted by rank TreeSet sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); propertiesMap.entrySet().stream().forEach(entry -> { final String propName = entry.getValue().getName(); //if the sort rank is undefined, it will be defaulted to 0 => unsorted. - Integer sortRank = preferences.getInt(ResultViewerPersistence.getColumnSortRankKey(tfn, propName), 0); //default to true => ascending Boolean sortOrder = preferences.getBoolean(ResultViewerPersistence.getColumnSortOrderKey(tfn, propName), true); - sortInfos.add(new ColumnSortInfo(entry.getKey(), sortRank, sortOrder)); }); - //apply sort information in rank order. sortInfos.forEach(sortInfo -> outline.setColumnSorted(sortInfo.modelIndex, sortInfo.order, sortInfo.rank)); } } + /** + * Reads and applies the column visibility information persisted to the + * preferences file. + */ private synchronized void loadColumnVisibility() { - if (currentRoot == null || propertiesMap.isEmpty()) { + if (currentRootNode == null || propertiesMap.isEmpty()) { return; } - - if (currentRoot instanceof TableFilterNode) { - + if (currentRootNode instanceof TableFilterNode) { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + final TableFilterNode tfn = ((TableFilterNode) currentRootNode); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry> entry : propertiesMap.entrySet()) { final String propName = entry.getValue().getName(); @@ -535,7 +535,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { /** * Gets a list of child properties (columns) in the order persisted in the - * preference file. Also initialized the propertiesMap with the column + * preference file. Also initialized the properties map with the column * order. * * @return a List> of the properties in the persisted @@ -543,14 +543,14 @@ public class DataResultViewerTable extends AbstractDataResultViewer { */ private synchronized List> loadColumnOrder() { - List> props = ResultViewerPersistence.getAllChildProperties(currentRoot, 100); + List> props = ResultViewerPersistence.getAllChildProperties(currentRootNode, 100); // If node is not table filter node, use default order for columns - if (!(currentRoot instanceof TableFilterNode)) { + if (!(currentRootNode instanceof TableFilterNode)) { return props; } - final TableFilterNode tfn = ((TableFilterNode) currentRoot); + final TableFilterNode tfn = ((TableFilterNode) currentRootNode); propertiesMap.clear(); /* @@ -587,24 +587,25 @@ public class DataResultViewerTable extends AbstractDataResultViewer { return new ArrayList<>(propertiesMap.values()); } + /** + * Expands a given child node of the current root node. + * + * @param node Node to expand + */ @Override - @NbBundle.Messages("DataResultViewerTable.title=Table") - public String getTitle() { - return title; - } - - @Override - public DataResultViewer createInstance() { - return new DataResultViewerTable(); + public void expandNode(Node node) { + outlineView.expandNode(node); } + /** + * Frees the resources that have been allocated by this tabular results + * viewer, in preparation for permanently disposing of it. + */ @Override public void clearComponent() { this.outlineView.removeAll(); this.outlineView = null; - super.clearComponent(); - } /** @@ -775,8 +776,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); // only override the color if a node is not selected - if (currentRoot != null && !isSelected) { - Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); + if (currentRootNode != null && !isSelected) { + Node node = currentRootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row)); boolean tagFound = false; if (node != null) { Node.PropertySet[] propSets = node.getPropertySets(); @@ -796,10 +797,37 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } //if the node does have associated tags, set its background color if (tagFound) { - component.setBackground(TAGGED_COLOR); + component.setBackground(TAGGED_ROW_COLOR); } } return component; } - } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + outlineView = new OutlineView(DataResultViewerTable.FIRST_COLUMN_LABEL); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 691, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(outlineView, javax.swing.GroupLayout.DEFAULT_SIZE, 366, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + private org.openide.explorer.view.OutlineView outlineView; + // End of variables declaration//GEN-END:variables + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index ef1f0c68f3..14b7c22b6b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -50,6 +50,7 @@ import org.openide.nodes.NodeMemberEvent; import org.openide.nodes.NodeReorderEvent; import org.openide.util.NbBundle; import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import static org.sleuthkit.autopsy.corecomponents.Bundle.*; import org.sleuthkit.autopsy.corecomponents.ResultViewerPersistence.SortCriterion; @@ -69,8 +70,8 @@ import org.sleuthkit.datamodel.TskCoreException; * restore implementation of DataResultViewerTable as a DataResultViewer service * provider. */ -//@ServiceProvider(service = DataResultViewer.class) -final class DataResultViewerThumbnail extends AbstractDataResultViewer { +@ServiceProvider(service = DataResultViewer.class) +public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); @@ -88,7 +89,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { * * @param explorerManager The shared ExplorerManager for the result viewers. */ - DataResultViewerThumbnail(ExplorerManager explorerManager) { + public DataResultViewerThumbnail(ExplorerManager explorerManager) { super(explorerManager); initialize(); } @@ -97,7 +98,7 @@ final class DataResultViewerThumbnail extends AbstractDataResultViewer { * Constructs a thumbnail viewer for the results view, with paging support, * that is NOT compatible with node multiple selection actions. */ - DataResultViewerThumbnail() { + public DataResultViewerThumbnail() { initialize(); } From b6fe29d039550528e490d404922c9a234c51e1b6 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Wed, 18 Apr 2018 09:55:29 -0400 Subject: [PATCH 056/100] Add normalized metric to time hash lookup --- .../EnterpriseHealthMonitor.java | 4 +++- .../autopsy/healthmonitor/TimingMetric.java | 19 +++++++++++++++++++ .../hashdatabase/HashDbIngestModule.java | 4 ++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index cd50553126..1b3ea2ab52 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -283,12 +283,14 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * This method is safe to call regardless of whether the Enterprise Health * Monitor is enabled. * @param metric The TimingMetric object obtained from getTimingMetric() - * @param count The number to divide the time by + * @param count The number to divide the time by (a zero here will be treated as a one) */ public static void submitNormalizedTimingMetric(TimingMetric metric, long count) { if(isEnabled.get() && (metric != null)) { metric.stopTiming(); try { + System.out.println("### duration: " + metric.getDuration() + " count: " + count); + metric.normalize(count); getInstance().addTimingMetric(metric); // TODO - make new method using count } catch (HealthMonitorException ex) { // We don't want calling methods to have to check for exceptions, so just log it diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java index 384073d461..4423ff24f4 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -62,4 +62,23 @@ public class TimingMetric { throw new HealthMonitorException("getDuration() called before stopTiming()"); } } + + /** + * Normalize the metric by dividing the time by the given counter. + * If the counter is zero, it will be treated the same way as if the + * counter were one. + * @param count Value to divide the duration by + * @throws HealthMonitorException + */ + void normalize(long count) throws HealthMonitorException { + if (duration != null) { + if(count < 0) { + throw new HealthMonitorException("normalize() called with negative count (" + count + ")"); + } else if(count > 1) { // Small optimization to prevent dividing by one + duration = duration / count; + } // If count = 0, do nothing + } else { + throw new HealthMonitorException("normalize() called before stopTiming()"); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index 92e2989e64..aaa721cc0d 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -33,6 +33,8 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.healthmonitor.EnterpriseHealthMonitor; +import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; @@ -182,8 +184,10 @@ public class HashDbIngestModule implements FileIngestModule { String md5Hash = file.getMd5Hash(); if (md5Hash == null || md5Hash.isEmpty()) { try { + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk: Hash Calculation (time per byte)"); long calcstart = System.currentTimeMillis(); md5Hash = HashUtility.calculateMd5Hash(file); + EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize()); file.setMd5Hash(md5Hash); long delta = (System.currentTimeMillis() - calcstart); totals.totalCalctime.addAndGet(delta); From 3d67c8d2cf2f22d40f954c9b9da94222cf701034 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 19 Apr 2018 23:55:56 -0400 Subject: [PATCH 057/100] Added password detection for Office documents. --- .../EncryptionDetectionFileIngestModule.java | 195 +++++++++++++----- .../EncryptionDetectionModuleFactory.java | 5 +- 2 files changed, 144 insertions(+), 56 deletions(-) mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java mode change 100644 => 100755 Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java old mode 100644 new mode 100755 index 7b48606ea0..89cc99230e --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -23,6 +23,12 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.logging.Level; +import org.apache.tika.exception.EncryptedDocumentException; +import org.apache.tika.exception.TikaException; +import org.apache.tika.metadata.Metadata; +import org.apache.tika.parser.AutoDetectParser; +import org.apache.tika.parser.ParseContext; +import org.apache.tika.sax.BodyContentHandler; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; @@ -41,9 +47,11 @@ import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; /** - * File ingest module to detect encryption. + * File ingest module to detect encryption and password protection. */ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { @@ -73,9 +81,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /** * Create a EncryptionDetectionFileIngestModule object that will detect - * files that are encrypted and create blackboard artifacts as appropriate. - * The supplied EncryptionDetectionIngestJobSettings object is used to - * configure the module. + * files that are either encrypted or password protected and create + * blackboard artifacts as appropriate. The supplied + * EncryptionDetectionIngestJobSettings object is used to configure the + * module. */ EncryptionDetectionFileIngestModule(EncryptionDetectionIngestJobSettings settings) { minimumEntropy = settings.getMinimumEntropy(); @@ -101,13 +110,37 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter public IngestModule.ProcessResult process(AbstractFile file) { try { - if (isFileEncrypted(file)) { - return flagFile(file); + /* + * Qualify the file type. + */ + if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) + && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR) + && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) { + /* + * Qualify the file against hash databases. + */ + if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + /* + * Qualify the MIME type. + */ + String mimeType = fileTypeDetector.getMIMEType(file); + if (mimeType.equals("application/octet-stream")) { + if (isFileEncryptionSuspected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + } + } else { + if (isFilePasswordProtected(file)) { + return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); + } + } + } } - } catch (ReadContentInputStreamException ex) { + } catch (ReadContentInputStreamException | SAXException | TikaException ex) { logger.log(Level.WARNING, String.format("Unable to read file '%s'", file.getParentPath() + file.getName()), ex); return IngestModule.ProcessResult.ERROR; - } catch (IOException | TskCoreException ex) { + } catch (IOException ex) { logger.log(Level.SEVERE, String.format("Unable to process file '%s'", file.getParentPath() + file.getName()), ex); return IngestModule.ProcessResult.ERROR; } @@ -138,14 +171,15 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /** * Create a blackboard artifact. * - * @param The file to be processed. + * @param file The file to be processed. + * @param artifactType The type of artifact to create. * * @return 'OK' if the file was processed successfully, or 'ERROR' if there * was a problem. */ - private IngestModule.ProcessResult flagFile(AbstractFile file) { + private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) { try { - BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + BlackboardArtifact artifact = file.newArtifact(artifactType); try { /* @@ -159,17 +193,19 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter /* * Send an event to update the view with the new result. */ - services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact))); + services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), artifactType, Collections.singletonList(artifact))); /* * Make an ingest inbox message. */ StringBuilder detailsSb = new StringBuilder(); - detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()).append("
\n"); - detailsSb.append("Entropy: ").append(calculatedEntropy); + detailsSb.append("File: ").append(file.getParentPath()).append(file.getName()); + if (artifactType.equals(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED)) { + detailsSb.append("
\n").append("Entropy: ").append(calculatedEntropy); + } services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), - "Encryption Detected Match: " + file.getName(), + artifactType.getDisplayName() + " Match: " + file.getName(), detailsSb.toString(), file.getName(), artifact)); @@ -182,16 +218,86 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter } /** - * This method checks if the AbstractFile input is encrypted. Initial - * qualifications require that it be an actual file that is not known, meets - * file size requirements, and has a MIME type of - * 'application/octet-stream'. + * This method checks if the AbstractFile input is password protected. * * @param file AbstractFile to be checked. * - * @return True if the AbstractFile is encrypted. + * @return True if the file is password protected. + * + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. + * @throws SAXException If there was an issue parsing the + * file with Tika. + * @throws TikaException If there was an issue parsing the + * file with Tika. */ - private boolean isFileEncrypted(AbstractFile file) throws ReadContentInputStreamException, IOException, TskCoreException { + private boolean isFilePasswordProtected(AbstractFile file) throws ReadContentInputStreamException, IOException, SAXException, TikaException { + + boolean passwordProtected = false; + + switch (file.getMIMEType()) { + case "application/x-ooxml-protected": + /* + * Office Open XML files that are password protected can be + * determined so simply by checking the MIME type. + */ + passwordProtected = true; + break; + + case "application/msword": + case "application/vnd.ms-excel": + case "application/vnd.ms-powerpoint": + /* + * A file of one of these types will be determined to be + * password protected or not by attempting to parse it via Tika. + */ + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(file); + bin = new BufferedInputStream(in); + ContentHandler handler = new BodyContentHandler(-1); + Metadata metadata = new Metadata(); + metadata.add(Metadata.RESOURCE_NAME_KEY, file.getName()); + AutoDetectParser parser = new AutoDetectParser(); + parser.parse(bin, handler, metadata, new ParseContext()); + } catch (EncryptedDocumentException ex) { + /* + * Office OLE2 file is determined to be password protected. + */ + passwordProtected = true; + } finally { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } + } + + return passwordProtected; + } + + /** + * This method checks if the AbstractFile input is encrypted. It must meet + * file size requirements before its entropy is calculated. If the entropy + * result meets the minimum entropy value set, the file will be considered + * to be possibly encrypted. + * + * @param file AbstractFile to be checked. + * + * @return True if encryption is suspected. + * + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. + */ + private boolean isFileEncryptionSuspected(AbstractFile file) throws ReadContentInputStreamException, IOException { /* * Criteria for the checks in this method are partially based on * http://www.forensicswiki.org/wiki/TrueCrypt#Detection @@ -200,55 +306,36 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter boolean possiblyEncrypted = false; /* - * Qualify the file type. + * Qualify the size. */ - if (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR) - && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL_DIR) - && (!file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK) || slackFilesAllowed)) { - /* - * Qualify the file against hash databases. - */ - if (!file.getKnown().equals(TskData.FileKnown.KNOWN)) { + long contentSize = file.getSize(); + if (contentSize >= minimumFileSize) { + if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) { /* - * Qualify the size. + * Qualify the entropy. */ - long contentSize = file.getSize(); - if (contentSize >= minimumFileSize) { - if (!fileSizeMultipleEnforced || (contentSize % FILE_SIZE_MODULUS) == 0) { - /* - * Qualify the MIME type. - */ - String mimeType = fileTypeDetector.getMIMEType(file); - if (mimeType.equals("application/octet-stream")) { - possiblyEncrypted = true; - } - } + calculatedEntropy = calculateEntropy(file); + if (calculatedEntropy >= minimumEntropy) { + possiblyEncrypted = true; } } } - if (possiblyEncrypted) { - calculatedEntropy = calculateEntropy(file); - if (calculatedEntropy >= minimumEntropy) { - return true; - } - } - - return false; + return possiblyEncrypted; } /** * Calculate the entropy of the file. The result is used to qualify the file - * as an encrypted file. + * as possibly encrypted. * * @param file The file to be calculated against. * * @return The entropy of the file. * - * @throws IOException If there is a failure closing or reading from the - * InputStream. + * @throws ReadContentInputStreamException If there is a failure reading + * from the InputStream. + * @throws IOException If there is a failure closing or + * reading from the InputStream. */ private double calculateEntropy(AbstractFile file) throws ReadContentInputStreamException, IOException { /* diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java old mode 100644 new mode 100755 index 27549f648f..7798d5be51 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,8 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** - * A factory that creates file ingest modules that detect encryption. + * A factory that creates file ingest modules that detect encryption and + * password protection. */ @ServiceProvider(service = IngestModuleFactory.class) @Messages({ From 7c334f0eaf38afb8700c1c2fa0432ff7c025c288 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 20 Apr 2018 10:06:14 -0400 Subject: [PATCH 058/100] Docs and claen up for result view infrastructure changes --- .../communications/MessageBrowser.java | 2 +- .../contentviewers/MessageContentViewer.java | 2 +- .../AbstractDataResultViewer.java | 35 +- .../autopsy/corecomponents/Bundle.properties | 6 +- .../corecomponents/Bundle_ja.properties | 6 +- .../corecomponents/DataResultPanel.form | 22 +- .../corecomponents/DataResultPanel.java | 606 ++++++++++-------- .../DataResultTopComponent.form | 2 +- .../DataResultTopComponent.java | 35 +- .../corecomponents/DataResultViewerTable.java | 10 +- .../DataResultViewerThumbnail.java | 33 +- 11 files changed, 414 insertions(+), 345 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java index 7b90357993..dca1770463 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java +++ b/Core/src/org/sleuthkit/autopsy/communications/MessageBrowser.java @@ -128,7 +128,7 @@ public final class MessageBrowser extends JPanel implements ExplorerManager.Prov public void propertyChange(PropertyChangeEvent pce) { if (pce.getPropertyName().equals(ExplorerManager.PROP_SELECTED_NODES)) { final Node[] selectedNodes = MessageBrowser.this.tableEM.getSelectedNodes(); - messagesResultPanel.setNumMatches(0); + messagesResultPanel.setNumberOfChildNodes(0); messagesResultPanel.setNode(null); messagesResultPanel.setPath(""); if (selectedNodes.length > 0) { diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java index ce2721e647..591f824de4 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/MessageContentViewer.java @@ -98,7 +98,7 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont @NbBundle.Messages("MessageContentViewer.AtrachmentsPanel.title=Attachments") public MessageContentViewer() { initComponents(); - drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", Node.EMPTY, 0, null); + drp = DataResultPanel.createInstanceUninitialized(Bundle.MessageContentViewer_AtrachmentsPanel_title(), "", new TableFilterNode(Node.EMPTY, false), 0, null); attachmentsScrollPane.setViewportView(drp); msgbodyTabbedPane.setEnabledAt(ATTM_TAB_INDEX, true); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 9dd387e0ae..715973aa7d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -30,26 +30,34 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; /** - * This class provides a default implementation of selected methods of the - * DataResultViewer interface. Derived classes will be Swing JPanel objects. - * Additionally, the ExplorerManager.Provider interface is implemented to supply - * an ExplorerManager to derived classes and their child components. + * Provides a JPanel base class that provides a default implementation of the + * ExplorerManager.Provider interface and selected methods of + * theDataResultViewer interface. The ExplorerManager.Provider interface is + * implemented to supply an explorer manager to subclasses and their child + * components. The explorer manager is expected to be the explorer manager of a + * top component that exposes a lookup maintained by the explorer manager to the + * actions global context. This connects the nodes displayed in the result + * viewer to the actions global context. The explorer manager may be either + * supplied during construction or discovered at runtime. */ abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider { private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); private transient ExplorerManager explorerManager; - AbstractDataResultViewer() { - } - /** - * This constructor is intended to allow an AbstractDataResultViewer to use - * an ExplorerManager provided by a TopComponent, allowing Node selections - * to be available to Actions via the action global context lookup when the - * TopComponent has focus. The ExplorerManager must be present when the - * object is constructed so that its child components can discover it using - * the ExplorerManager.find() method. + * Constructs a JPanel base class instance that provides a default + * implementation of selected methods of the DataResultViewer and + * ExplorerManager.Provider interfaces. The explorer manager of this viewer + * will be discovered at runtime. + */ + AbstractDataResultViewer() { + } + + /** + * Constructs a JPanel base class instance that provides a default + * implementation of selected methods of the DataResultViewer and + * ExplorerManager.Provider interfaces. * * @param explorerManager */ @@ -95,4 +103,5 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView @Override public void setContentViewer(DataContent contentViewer) { } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index 245fe1744d..c2a9d9845f 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -62,9 +62,6 @@ DataResultViewerThumbnail.filePathLabel.text=\ \ \ DataResultViewerThumbnail.goToPageLabel.text=Go to Page: DataResultViewerThumbnail.goToPageField.text= AdvancedConfigurationDialog.cancelButton.text=Cancel -DataResultPanel.directoryTablePath.text=directoryPath -DataResultPanel.numberMatchLabel.text=0 -DataResultPanel.matchLabel.text=Results DataContentViewerArtifact.waitText=Retrieving and preparing data, please wait... DataContentViewerArtifact.errorText=Error retrieving result DataContentViewerArtifact.title=Results @@ -181,3 +178,6 @@ AutopsyOptionsPanel.agencyLogoPreview.text=
resultViewers = new ArrayList<>(); - private boolean isMain; - private ExplorerManager explorerManager; - private ExplorerManagerListener emNodeSelectionListener; - private Node rootNode; - private final RootNodeListener rootNodeListener = new RootNodeListener(); - private boolean listeningToTabbedPane; + private final boolean isMain; + private final List resultViewers; + private final ExplorerManagerListener explorerManagerListener; + private final RootNodeListener rootNodeListener; private DataContent contentView; + private ExplorerManager explorerManager; + private Node currentRootNode; + private boolean listeningToTabbedPane; /** - * Constructs and opens a DataResultPanel with the given initial data, and - * the default DataContent. + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains instances of the result viewers (DataResultViewer) provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). The result view panel will push single node selections + * from its child result viewers to the "main" content view that is normally + * docked into the lower right hand side of the main application window. * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * - * @return A DataResultPanel instance. - */ - public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches) { - DataResultPanel resultPanel = new DataResultPanel(title, false); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); - resultPanel.open(); - return resultPanel; - } - - /** - * Constructs and opens a DataResultPanel with the given initial data, and a - * custom DataContent. - * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param customContentView A content view to use in place of the default - * content view. - * - * @return A DataResultPanel instance. - */ - public static DataResultPanel createInstance(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { - DataResultPanel resultPanel = new DataResultPanel(title, customContentView); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); - resultPanel.open(); - return resultPanel; - } - - /** - * Constructs a DataResultPanel with the given initial data, and a custom - * DataContent. The panel is NOT opened; the client of this method must call - * open on the panel that is returned. - * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes - * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param customContentView A content view to use in place of the default - * content view. - * - * @return A DataResultPanel instance. - */ - public static DataResultPanel createInstanceUninitialized(String title, String pathText, Node rootNode, int totalMatches, DataContent customContentView) { - DataResultPanel resultPanel = new DataResultPanel(title, customContentView); - createInstanceCommon(title, pathText, rootNode, totalMatches, resultPanel); - return resultPanel; - } - - /** - * Executes code common to all of the DataSreultPanel factory methods. - * - * @param title The title for the panel. - * @param pathText Descriptive text about the source of the nodes + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes * displayed. - * @param rootNode The new root node. - * @param totalMatches Cardinality of root node's children - * @param resultViewPanel A content view to use in place of the default - * content view. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * + * @return A result view panel. */ - private static void createInstanceCommon(String title, String pathText, Node rootNode, int totalMatches, DataResultPanel resultViewPanel) { + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), null); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains a given collection of result viewers (DataResultViewer) instead + * of the result viewers provided by the results viewer extension point. The + * result view panel will push single node selections from its child result + * viewers to the "main" content view that is normally docked into the lower + * right hand side of the main application window.. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results viewer + * extension point. + * + * @return A result view panel. + */ + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, Collection viewers) { + DataResultPanel resultPanel = new DataResultPanel(title, false, viewers, null); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates and opens a Swing JPanel with a JTabbedPane child component that + * contains instances of the result viewers (DataResultViewer) provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). The result view panel will push single node selections + * from its child result viewers to the supplied custom content view. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param customContentView A custom content view to use instead of the + * "main" content view that is normally docked into + * the lower right hand side of the main + * application window. + * + * @return A result view panel. + */ + public static DataResultPanel createInstance(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + resultPanel.open(); + return resultPanel; + } + + /** + * Creates, but does not open, a Swing JPanel with a JTabbedPane child + * component that contains instances of the result viewers + * (DataResultViewer) provided by the result viewer extension point (service + * providers that implement DataResultViewer). The result view panel will + * push single node selections from its child result viewers to the supplied + * custom content view. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param customContentView A content view to use in place of the default + * content view. + * + * @return A result view panel. + */ + public static DataResultPanel createInstanceUninitialized(String title, String description, Node currentRootNode, int childNodeCount, DataContent customContentView) { + DataResultPanel resultPanel = new DataResultPanel(title, false, Collections.emptyList(), customContentView); + createInstanceCommon(title, description, currentRootNode, childNodeCount, resultPanel); + return resultPanel; + } + + /** + * Executes code common to all of the result view panel factory methods. + * + * @param title The title for the result view panel. + * @param description Descriptive text about the source of the nodes + * displayed. + * @param currentRootNode The current root (parent) node for the nodes + * displayed. May be changed by calling setNode. + * @param childNodeCount The cardinality of the root node's children. + * @param resultViewPanel A new results view panel. + */ + private static void createInstanceCommon(String title, String description, Node currentRootNode, int childNodeCount, DataResultPanel resultViewPanel) { resultViewPanel.setTitle(title); resultViewPanel.setName(title); - resultViewPanel.setNumMatches(totalMatches); - resultViewPanel.setNode(rootNode); - resultViewPanel.setPath(pathText); + resultViewPanel.setNumberOfChildNodes(childNodeCount); + resultViewPanel.setNode(currentRootNode); + resultViewPanel.setPath(description); } /** - * Constructs a DataResultPanel with the default DataContent + * Constructs a Swing JPanel with a JTabbedPane child component that + * contains a collection of result viewers that is either supplied or + * provided by the result viewer extension point. * - * @param title The title for the panel. - * @param isMain True if the DataResultPanel being constructed is the "main" - * DataResultPanel. + * @param title The title of the result view panel. + * @param isMain Whether or not the result view panel is the + * "main" instance of the panel that resides in the + * "main" results view (DataResultTopComponent) + * that is normally docked into the upper right + * hand side of the main application window. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results + * viewer extension point, may be empty. + * @param customContentView A custom content view to use instead of the + * "main" content view that is normally docked into + * the lower right hand side of the main + * application window, may be null. */ - DataResultPanel(String title, boolean isMain) { - this(isMain, Lookup.getDefault().lookup(DataContent.class)); - setTitle(title); - } - - /** - * Constructs a DataResultPanel with the a custom DataContent. - * - * @param title The title for the panel. - * @param customContentView A content view to use in place of the default - * content view. - */ - DataResultPanel(String title, DataContent customContentView) { - this(false, customContentView); - } - - /** - * Constructs a DataResultPanel with a given content view. - * - * @param isMain - * @param contentView - */ - private DataResultPanel(boolean isMain, DataContent contentView) { + DataResultPanel(String title, boolean isMain, Collection viewers, DataContent customContentView) { + this.setTitle(title); this.isMain = isMain; - this.contentView = contentView; + if (customContentView == null) { + this.contentView = Lookup.getDefault().lookup(DataContent.class); + } else { + this.contentView = customContentView; + } + this.resultViewers = new ArrayList<>(viewers); + this.explorerManagerListener = new ExplorerManagerListener(); + this.rootNodeListener = new RootNodeListener(); initComponents(); } /** - * Gets the preferred identifier for this panel in the window system. + * Gets the preferred identifier for this result view panel in the window + * system. * * @return The preferred identifier. */ @@ -206,19 +254,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Gets whether or not this panel is the "main" panel used to view the child - * nodes of a node selected in the tree view (DirectoryTreeTopComponent) - * that is normally docked into the left hand side of the main window. - * - * @return True or false. - */ - @Override - public boolean isMain() { - return this.isMain; - } - - /** - * Sets the title of this panel. + * Sets the title of this result view panel. * * @param title The title. */ @@ -229,27 +265,27 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C /** * Sets the descriptive text about the source of the nodes displayed in this - * panel. + * result view panel. * - * @param pathText The text to display. + * @param description The text to display. */ @Override - public void setPath(String pathText) { - this.directoryTablePath.setText(pathText); + public void setPath(String description) { + this.descriptionLabel.setText(description); } /** - * Adds a result viewer to this panel. + * Adds a results viewer to this result view panel. * - * @param resultViewer The result viewer. + * @param resultViewer The results viewer. */ public void addResultViewer(DataResultViewer resultViewer) { resultViewers.add(resultViewer); - dataResultTabbedPanel.addTab(resultViewer.getTitle(), resultViewer.getComponent()); + resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent()); } /** - * Gets the result viewers for this panel. + * Gets the result viewers for this result view panel. * * @return A list of result viewers. */ @@ -259,8 +295,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Sets the content view for this panel. Needs to be called before the first - * call to open. + * Sets the content view for this result view panel. Needs to be called + * before the first call to open. * * @param customContentView A content view to use in place of the default * content view. @@ -270,57 +306,60 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /** - * Initializes this panel. Intended to be called by a parent top component + * Opens this result view panel. Should be called by a parent top component * when the top component is opened. */ public void open() { - if (null == explorerManager) { - /* - * Get an explorer manager to pass to the child result viewers. If - * the application components are put together as expected, this - * will be an explorer manager owned by a parent top component, and - * placed by the top component in the look up that is proxied as the - * action global context when the top component has focus. The - * sharing of this explorer manager enables the same child node - * selections to be made in all of the result viewers. - */ - explorerManager = ExplorerManager.find(this); - emNodeSelectionListener = new ExplorerManagerListener(); - explorerManager.addPropertyChangeListener(emNodeSelectionListener); + /* + * The parent top component is expected to be an explorer manager + * provider that exposes a lookup maintained by its explorer manager to + * the actions global context. The child result view panel will then + * find the parent top component's explorer manager at runtime, so that + * it can act as an explorer manager provider for its child result + * viewers. This connects the nodes displayed in the result viewers to + * the actions global context. + */ + if (this.explorerManager == null) { + this.explorerManager = ExplorerManager.find(this); + this.explorerManager.addPropertyChangeListener(this.explorerManagerListener); } /* - * Load the child result viewers into the tabbed pane. + * Load either the supplied result viewers or the result viewers + * provided by the result viewer extension point into the tabbed pane. + * If loading from the extension point and distinct result viewer + * instances MUST be created if this is not the "main" result view. */ - if (dataResultTabbedPanel.getTabCount() == 0) { - for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) { - if (isMain) { - addResultViewer(resultViewer); - } else { - addResultViewer(resultViewer.createInstance()); + if (this.resultViewerTabs.getTabCount() == 0) { + if (this.resultViewers.isEmpty()) { + for (DataResultViewer resultViewer : Lookup.getDefault().lookupAll(DataResultViewer.class)) { + if (this.isMain) { + this.resultViewers.add(resultViewer); + } else { + this.resultViewers.add(resultViewer.createInstance()); + } } } - } - - if (isMain && null == rootNode) { - setNode(rootNode); + this.resultViewers.forEach((resultViewer) -> resultViewerTabs.addTab(resultViewer.getTitle(), resultViewer.getComponent())); } this.setVisible(true); } /** - * Sets the root node for this panel. The child nodes of the root node will - * be displayed in the result viewers. For the "main" panel, the root node - * is the currently selected node in the tree view docked into the left side - * of the main application window. + * Sets the current root node for this result view panel. The child nodes of + * the current root node will be displayed in the child result viewers. For + * the "main" panel, the root node is the currently selected node in the + * application tree view docked into the left side of the main application + * window. * - * @param rootNode The root node for this panel. + * @param rootNode The root node for this panel, may be null if the panel is + * to be reset. */ @Override public void setNode(Node rootNode) { - if (this.rootNode != null) { - this.rootNode.removeNodeListener(rootNodeListener); + if (this.currentRootNode != null) { + this.currentRootNode.removeNodeListener(rootNodeListener); } /* @@ -329,53 +368,54 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * construction. */ if (listeningToTabbedPane == false) { - dataResultTabbedPanel.addChangeListener(this); + resultViewerTabs.addChangeListener(this); listeningToTabbedPane = true; } - this.rootNode = rootNode; - if (this.rootNode != null) { + this.currentRootNode = rootNode; + if (this.currentRootNode != null) { rootNodeListener.reset(); - this.rootNode.addNodeListener(rootNodeListener); + this.currentRootNode.addNodeListener(rootNodeListener); } - resetTabs(this.rootNode); - setupTabs(this.rootNode); + this.resultViewers.forEach((viewer) -> { + viewer.resetComponent(); + }); + setupTabs(this.currentRootNode); - if (null != this.rootNode) { - int childrenCount = this.rootNode.getChildren().getNodesCount(); - this.numberMatchLabel.setText(Integer.toString(childrenCount)); + if (this.currentRootNode != null) { + int childrenCount = this.currentRootNode.getChildren().getNodesCount(); + this.numberOfChildNodesLabel.setText(Integer.toString(childrenCount)); } - this.numberMatchLabel.setVisible(true); + this.numberOfChildNodesLabel.setVisible(true); } /** - * Gets the root node of this panel. For the "main" panel, the root node is - * the currently selected node in the tree view docked into the left side of - * the main application window. + * Gets the root node of this result view panel. For the "main" panel, the + * root node is the currently selected node in the application tree view + * docked into the left side of the main application window. * * @return The root node. */ public Node getRootNode() { - return rootNode; + return currentRootNode; } /** - * Set number of child nodes displayed for the current root node. + * Sets the label text that displays the number of the child nodes displayed + * by this result view panel's result viewers. * - * @param numberOfChildNodes + * @param numberOfChildNodes The number of child nodes. */ - public void setNumMatches(Integer numberOfChildNodes) { - if (this.numberMatchLabel != null) { - this.numberMatchLabel.setText(Integer.toString(numberOfChildNodes)); - } + public void setNumberOfChildNodes(Integer numberOfChildNodes) { + this.numberOfChildNodesLabel.setText(Integer.toString(numberOfChildNodes)); } /** - * Sets the children of the root node that should be currently selected in - * this panel's result viewers. + * Selects the given child nodes of the root node in this panel's result + * viewers. * - * @param selectedNodes The nodes to be selected. + * @param selectedNodes The child nodes to be selected. */ public void setSelectedNodes(Node[] selectedNodes) { this.resultViewers.forEach((viewer) -> viewer.setSelectedNodes(selectedNodes)); @@ -392,11 +432,11 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * Enable or disable the result viewer tabs based on whether or not the * corresponding results viewer supports display of the selected node. */ - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); i++) { + for (int i = 0; i < resultViewerTabs.getTabCount(); i++) { if (resultViewers.get(i).isSupported(selectedNode)) { - dataResultTabbedPanel.setEnabledAt(i, true); + resultViewerTabs.setEnabledAt(i, true); } else { - dataResultTabbedPanel.setEnabledAt(i, false); + resultViewerTabs.setEnabledAt(i, false); } } @@ -411,17 +451,17 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C NodeSelectionInfo selectedChildInfo = ((TableFilterNode) selectedNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { for (int i = 0; i < resultViewers.size(); ++i) { - if (resultViewers.get(i) instanceof DataResultViewerTable && dataResultTabbedPanel.isEnabledAt(i)) { + if (resultViewers.get(i) instanceof DataResultViewerTable && resultViewerTabs.isEnabledAt(i)) { tabToSelect = i; } } } }; - if (NO_TAB_SELECTED == tabToSelect) { - tabToSelect = dataResultTabbedPanel.getSelectedIndex(); - if ((NO_TAB_SELECTED == tabToSelect) || (!dataResultTabbedPanel.isEnabledAt(tabToSelect))) { - for (int i = 0; i < dataResultTabbedPanel.getTabCount(); ++i) { - if (dataResultTabbedPanel.isEnabledAt(i)) { + if (tabToSelect == NO_TAB_SELECTED) { + tabToSelect = resultViewerTabs.getSelectedIndex(); + if ((tabToSelect == NO_TAB_SELECTED) || (!resultViewerTabs.isEnabledAt(tabToSelect))) { + for (int i = 0; i < resultViewerTabs.getTabCount(); ++i) { + if (resultViewerTabs.isEnabledAt(i)) { tabToSelect = i; break; } @@ -430,27 +470,15 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } /* - * If there is a tab to sele3ct, do so, and push the selected node to - * the corresponding result viewer. + * If there is a tab to select, do so, and push the selected node to the + * corresponding result viewer. */ - if (NO_TAB_SELECTED != tabToSelect) { - dataResultTabbedPanel.setSelectedIndex(tabToSelect); + if (tabToSelect != NO_TAB_SELECTED) { + resultViewerTabs.setSelectedIndex(tabToSelect); resultViewers.get(tabToSelect).setNode(selectedNode); } } - /** - * Resets the state of the child result viewers, based on a selected root - * node. - * - * @param unusedSelectedNode The selected node. - */ - public void resetTabs(Node unusedSelectedNode) { - this.resultViewers.forEach((viewer) -> { - viewer.resetComponent(); - }); - } - /** * Responds to a tab selection changed event by setting the root node of the * corresponding result viewer. @@ -461,57 +489,34 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C public void stateChanged(ChangeEvent event) { JTabbedPane pane = (JTabbedPane) event.getSource(); int currentTab = pane.getSelectedIndex(); - if (-1 != currentTab) { + if (currentTab != DataResultPanel.NO_TAB_SELECTED) { DataResultViewer currentViewer = this.resultViewers.get(currentTab); this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); try { - currentViewer.setNode(rootNode); + currentViewer.setNode(currentRootNode); } finally { - this.setCursor(null); + this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } } } /** - * Indicates whether or not this panel can be closed at the time of the - * call. - * - * @return True or false. - */ - public boolean canClose() { - /* - * If this is the "main" panel, only allow it to be closed when no case - * is open or no there are no data sources in the current case. - */ - Case openCase; - try { - openCase = Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { - return true; - } - return (!this.isMain) || openCase.hasData() == false; - } - - /** - * Closes down the component. Intended to be called by the parent top + * Closes this reult view panel. Intended to be called by the parent top * component when it is closed. */ void close() { - if (null != explorerManager && null != emNodeSelectionListener) { - explorerManager.removePropertyChangeListener(emNodeSelectionListener); + if (explorerManager != null && explorerManagerListener != null) { + explorerManager.removePropertyChangeListener(explorerManagerListener); explorerManager = null; } this.resultViewers.forEach((viewer) -> viewer.setNode(null)); - if (!this.isMain) { + if (!this.isMain) { // RJCTODO: What? this.resultViewers.forEach(DataResultViewer::clearComponent); - this.directoryTablePath.removeAll(); - this.directoryTablePath = null; - this.numberMatchLabel.removeAll(); - this.numberMatchLabel = null; + this.descriptionLabel.removeAll(); + this.numberOfChildNodesLabel.removeAll(); this.matchLabel.removeAll(); - this.matchLabel = null; this.setLayout(null); this.removeAll(); this.setVisible(false); @@ -521,6 +526,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C @Override public ExplorerManager getExplorerManager() { return explorerManager; + } /** @@ -560,6 +566,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * Responds to changes in the root node due to asynchronous child node * creation. */ + // RJCTODO: Why do we need this? private class RootNodeListener implements NodeListener { private volatile boolean waitingForData = true; @@ -605,8 +612,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C * */ private void updateMatches() { - if (rootNode != null && rootNode.getChildren() != null) { - setNumMatches(rootNode.getChildren().getNodesCount()); + if (currentRootNode != null && currentRootNode.getChildren() != null) { + setNumMatches(currentRootNode.getChildren().getNodesCount()); } } @@ -637,52 +644,93 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C // //GEN-BEGIN:initComponents private void initComponents() { - directoryTablePath = new javax.swing.JLabel(); - numberMatchLabel = new javax.swing.JLabel(); + descriptionLabel = new javax.swing.JLabel(); + numberOfChildNodesLabel = new javax.swing.JLabel(); matchLabel = new javax.swing.JLabel(); - dataResultTabbedPanel = new javax.swing.JTabbedPane(); + resultViewerTabs = new javax.swing.JTabbedPane(); setMinimumSize(new java.awt.Dimension(0, 5)); setPreferredSize(new java.awt.Dimension(5, 5)); - org.openide.awt.Mnemonics.setLocalizedText(directoryTablePath, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.directoryTablePath.text")); // NOI18N - directoryTablePath.setMinimumSize(new java.awt.Dimension(5, 14)); + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.descriptionLabel.text")); // NOI18N + descriptionLabel.setMinimumSize(new java.awt.Dimension(5, 14)); - org.openide.awt.Mnemonics.setLocalizedText(numberMatchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberMatchLabel.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(numberOfChildNodesLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.numberOfChildNodesLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(matchLabel, org.openide.util.NbBundle.getMessage(DataResultPanel.class, "DataResultPanel.matchLabel.text")); // NOI18N - dataResultTabbedPanel.setMinimumSize(new java.awt.Dimension(0, 5)); + resultViewerTabs.setMinimumSize(new java.awt.Dimension(0, 5)); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(numberMatchLabel) + .addComponent(numberOfChildNodesLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(matchLabel)) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(numberMatchLabel) + .addComponent(numberOfChildNodesLabel) .addComponent(matchLabel)) - .addComponent(directoryTablePath, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(descriptionLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addGap(0, 0, 0) - .addComponent(dataResultTabbedPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addComponent(resultViewerTabs, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); }// //GEN-END:initComponents // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JTabbedPane dataResultTabbedPanel; - private javax.swing.JLabel directoryTablePath; + private javax.swing.JLabel descriptionLabel; private javax.swing.JLabel matchLabel; - private javax.swing.JLabel numberMatchLabel; + private javax.swing.JLabel numberOfChildNodesLabel; + private javax.swing.JTabbedPane resultViewerTabs; // End of variables declaration//GEN-END:variables + /** + * Gets whether or not this result view panel is the "main" result view + * panel used to view the child nodes of a node selected in the application + * tree view (DirectoryTreeTopComponent) that is normally docked into the + * left hand side of the main window. + * + * @return True or false. + * + * @Deprecated This method has no valid use case. + */ + @Deprecated + @Override + public boolean isMain() { + return this.isMain; + } + + /** + * Sets the label text that displays the number of the child nodes displayed + * by this result view panel's result viewers. + * + * @param numberOfChildNodes The number of child nodes. + * + * @deprecated Use setNumberOfChildNodes instead. + */ + @Deprecated + public void setNumMatches(Integer numberOfChildNodes) { + this.setNumberOfChildNodes(numberOfChildNodes); + } + + /** + * Resets the state of this results panel. + * + * @param unusedSelectedNode Unused. + * + * @deprecated Use setNode(null) instead. + */ + @Deprecated + public void resetTabs(Node unusedSelectedNode) { + this.setNode(null); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form index cb4669a51e..f0f49dcff7 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form @@ -34,4 +34,4 @@ - + \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index 4a5d265a2c..aec2cb2316 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -78,7 +78,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult, */ public DataResultTopComponent(boolean isMain, String title) { associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); - this.dataResultPanel = new DataResultPanel(title, isMain); + this.dataResultPanel = new DataResultPanel(title, isMain, Collections.emptyList(), null); initComponents(); customizeComponent(isMain, title); } @@ -96,7 +96,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult, DataResultTopComponent(String name, String mode, DataContentTopComponent customContentViewer) { associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); this.customModeName = mode; - dataResultPanel = new DataResultPanel(name, customContentViewer); + this.dataResultPanel = new DataResultPanel(name, false, Collections.emptyList(), customContentViewer); initComponents(); customizeComponent(isMain, name); } @@ -129,7 +129,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult, * uninitialized instance */ public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) { - newDataResult.setNumMatches(totalMatches); + newDataResult.setNumberOfChildNodes(totalMatches); newDataResult.open(); // open it first so the component can be initialized @@ -357,17 +357,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult, return (!this.isMain) || openCase.hasData() == false; } - /** - * Resets the tabs based on the selected Node. If the selected node is null - * or not supported, disable that tab as well. - * - * @param selectedNode the selected content Node - */ - public void resetTabs(Node selectedNode) { - - dataResultPanel.resetTabs(selectedNode); - } - public void setSelectedNodes(Node[] selected) { dataResultPanel.setSelectedNodes(selected); } @@ -376,7 +365,21 @@ public class DataResultTopComponent extends TopComponent implements DataResult, return dataResultPanel.getRootNode(); } - void setNumMatches(int matches) { - this.dataResultPanel.setNumMatches(matches); + + void setNumberOfChildNodes(int matches) { + this.dataResultPanel.setNumberOfChildNodes(matches); } + + /** + * Resets the tabs based on the selected Node. If the selected node is null + * or not supported, disable that tab as well. + * + * @param selectedNode the selected content Node + * @deprecated Use setNode instead. + */ + @Deprecated + public void resetTabs(Node selectedNode) { + dataResultPanel.setNode(selectedNode); + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 8738884251..42aa2b727d 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -119,6 +119,12 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { initialize(); } +// @Override +// public void addNotify() { +// super.addNotify(); +// initialize(); +// } + /* * Initializes this tabular results viewer. */ @@ -802,8 +808,8 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { } return component; } - } - + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index 14b7c22b6b..fd4727ca7b 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -82,6 +82,16 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private final PageUpdater pageUpdater = new PageUpdater(); private TableFilterNode tfn; private ThumbnailViewChildren tvc; + private NodeSelectionListener selectionListener; + + /** + * Constructs a thumbnail viewer for the results view, with paging support, + * that is NOT compatible with node multiple selection actions. + */ + public DataResultViewerThumbnail() { + super(); + initialize(); + } /** * Constructs a thumbnail viewer for the results view, with paging support, @@ -94,14 +104,6 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { initialize(); } - /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is NOT compatible with node multiple selection actions. - */ - public DataResultViewerThumbnail() { - initialize(); - } - @NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails" @@ -109,7 +111,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private void initialize() { initComponents(); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - this.getExplorerManager().addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); +// this.getExplorerManager().addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), @@ -301,8 +303,6 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { Node root = this.getExplorerManager().getRootContext(); ((ThumbnailViewChildren) root.getChildren()).setThumbsSize(thumbSize); - - // Temporarily set the explored context to the root, instead of a child node. // This is a workaround hack to convince org.openide.explorer.ExplorerManager to // update even though the new and old Node values are identical. This in turn @@ -380,6 +380,9 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public void setNode(Node givenNode) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + if (selectionListener == null) { + this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup + } if (tvc != null) { tvc.cancelLoadingThumbnails(); } @@ -391,7 +394,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - tvc = new ThumbnailViewChildren(givenNode,thumbSize); + tvc = new ThumbnailViewChildren(givenNode, thumbSize); final Node root = new AbstractNode(tvc); pageUpdater.setRoot(root); @@ -506,8 +509,8 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { try { get(); } catch (InterruptedException | ExecutionException ex) { - NotifyDescriptor d = - new NotifyDescriptor.Message( + NotifyDescriptor d + = new NotifyDescriptor.Message( NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.switchPage.done.errMsg", ex.getMessage()), NotifyDescriptor.ERROR_MESSAGE); @@ -642,7 +645,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { } } - private class ExplorerManagerNodeSelectionListener implements PropertyChangeListener { + private class NodeSelectionListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { From 45dc03758ef4cc08992a96098528dc2dde7b94e7 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 20 Apr 2018 13:55:36 -0400 Subject: [PATCH 059/100] 3744 intial documentation of quick search feature added to doxygen --- docs/doxygen-user/case_management.dox | 2 +- docs/doxygen-user/communications.dox | 2 ++ docs/doxygen-user/quick_search.dox | 25 +++++++++++++++++++++++++ docs/doxygen-user/quick_start_guide.dox | 2 ++ docs/doxygen-user/timeline.dox | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 docs/doxygen-user/quick_search.dox diff --git a/docs/doxygen-user/case_management.dox b/docs/doxygen-user/case_management.dox index 56d6b81be5..a8b550b989 100644 --- a/docs/doxygen-user/case_management.dox +++ b/docs/doxygen-user/case_management.dox @@ -34,7 +34,7 @@ To open a case, either: "Open Recent Case" will always bring up a screen allowing you to select one of the recently opened cases. "Open Case" will do one of two things; - If multi-user cases are not enabled, it will bring up a file chooser that can be used to browse to the ".aut" file in the case directory of the desired case -- If multi-user case are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The following shows the multi-user case selection screen: +- If multi-user case are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The multi-user case selection screen has a \ref quick_search feature which can be used to quickly find a case in the table. The following shows the multi-user case selection screen: \image html multi_user_case_select.png diff --git a/docs/doxygen-user/communications.dox b/docs/doxygen-user/communications.dox index 008ce70b64..2080f910e5 100644 --- a/docs/doxygen-user/communications.dox +++ b/docs/doxygen-user/communications.dox @@ -20,6 +20,8 @@ The middle column displays each account, its device and type, and the number of Selecting an account in the middle column will bring up the messages for that account in the right hand column. Here data about each message is displayed in the top section, and the messages itself can be seen in the bottom section (if applicable). +The middle column and the right hand column both have a \ref quick_search feature which can be used to quickly find a visible item in their section's table. + \image html cvt_messages.png */ \ No newline at end of file diff --git a/docs/doxygen-user/quick_search.dox b/docs/doxygen-user/quick_search.dox new file mode 100644 index 0000000000..ebb815d9c4 --- /dev/null +++ b/docs/doxygen-user/quick_search.dox @@ -0,0 +1,25 @@ +/*! \page quick_search Quick Search + +Where it can be used +======== + +- The tree view (at the left of the main window) +- The table view (in the results listing) +- The open multi user case panel +- The Auto Ingest Dashboard’s three job information panels +- The Timeline tool’s table view +- The Communication tool’s browse panel +- The Communication tool’s message panel + +How to use it +======= + +In order to use the search you need to select any item in the area you wish to search, and start typing what you are searching for. If the area you have selected is a search-able area a search field will appear at the bottom of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. + + +Configuration +====== +By default the search will match against the data in all fields which are currently visible in that area. By default the search will also ignore case. If you want to change either of these default behaviors you can click the spyglass with down area icon and configure which columns will be searched as well as if the search should ignore case. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field. + + +*/ diff --git a/docs/doxygen-user/quick_start_guide.dox b/docs/doxygen-user/quick_start_guide.dox index 98e23da660..e1bece678b 100644 --- a/docs/doxygen-user/quick_start_guide.dox +++ b/docs/doxygen-user/quick_start_guide.dox @@ -59,6 +59,8 @@ If you are viewing files from the Views and Results nodes, you can right-click o If you want to search for single keywords, then you can use the search box in the upper right of the program. The results will be shown in a table in the upper right. +The tree on the left as well as the table on the right have a \ref quick_search feature which can be used to quickly find a visible node. + You can tag (bookmark) arbitrary files so that you can more quickly find them later or so that you can include them specifically in a report. \subsection s2a Ingest Inbox diff --git a/docs/doxygen-user/timeline.dox b/docs/doxygen-user/timeline.dox index 349124bcdf..f992c57d26 100644 --- a/docs/doxygen-user/timeline.dox +++ b/docs/doxygen-user/timeline.dox @@ -69,6 +69,7 @@ The __Counts View__ shows a stacked bar chart. Use this type of graph to show ho The __Details View__ shows individual or groups of related events. Date/time is represented horizontally along the x-axis, but the vertical axis does not represent any specific units. You would use this interface to answer questions about what specific events happened in a given time frame or what events occurred before or after a given event. You would generally use this type of interface after using the Counts View to identify a period of time that you wanted details on. There can be a lot of details in this view and we have introduced zooming concepts, as described in the next section, to help with this. +The table on the bottom left hand side of the panel has a \ref quick_search feature which can be used to quickly find a node in the table. Visualization settings ---------------------- From 8d57aa90173d3eb5c1f60844b3149e6a416546d7 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 20 Apr 2018 14:36:40 -0400 Subject: [PATCH 060/100] 3744 change formating and add screen shots for Quick Search documentation --- .../images/quick_search_configuration.png | Bin 0 -> 50888 bytes .../images/quick_search_result.png | Bin 0 -> 16844 bytes docs/doxygen-user/main.dox | 1 + docs/doxygen-user/quick_search.dox | 32 ++++++++---------- 4 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 docs/doxygen-user/images/quick_search_configuration.png create mode 100644 docs/doxygen-user/images/quick_search_result.png diff --git a/docs/doxygen-user/images/quick_search_configuration.png b/docs/doxygen-user/images/quick_search_configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..986d667c0ca63ec506f89280bf24510e51f3ef07 GIT binary patch literal 50888 zcmc$`1yoy6w=SBtKU64Ch2m|YxNFf=kW#EzDHdAX-L)+(?i9B|a7&TkkW!rB1PfLy zXb5hB9C`|o*o-0{x6dyEXSH*2r8_S$RBZ+_pLE1_>x707SU-T;9>Jt76TqKKE*c6iK;;AU>%hrBES{@82Z1W0$<9BJ0O!{oU+KDl zKoo6%K9{;23Qa+vPD4f6=USeStywZ(`Y}Jgosr;vRiz!EOq}+0k-tO}!mu`Rk zvgcU+Am_66LzNh%XLR3xzY>T>-|n0cxOe$Z^aJL+mPz55{KbXpD6NOp%d9$!W~s8d zA%=9XaCYhMCKG?wly+^;5ygpBRaHX3XQcdXqW}Ehk8~Fd*us2b{H$PUSP$&@`(wV9 z0Nb!7iqPG0U}+C@7zFysvesa4f8r}+J;@|zU9e*(KRokn_7#gUW3H%*h5tYPl=;|` z{mkA!H)5dB@Xed|5o`EkT)|Gi7Jm|ZDP1&`F!O{<;NYb{1wrx8oKo+KTy+(?v3*{M zo34#!e%j&03{_KB-hsx>ULV_Gj_hWRLH{kBAEqgp=i(VTD>)77u;PHKH8C$Y>56z$k~M1Z5?&O8A- zk81zhuNXUIB9G*D-IA)*ZhK{AB?%c-UlthgE-oCme=tuZ94CHt58Dc+U_e({P<}nk z&d#>T^a?V!va&ku)oJ-9G~g|?io2Che7$!vv#TQp8LC|>PDo9nU97nIK4|}BJa*P2 znZDU*p=7U+pUgjHM>29K+|liKjvd%b^>Dv$ zIdb9bjhc>p92^T<<(d*=Z>LtuwoHO4nFzlFDd&7}&3^bJKO%k}v02_oOH0c>bvM(S zcA4vE%HXCmd7_~eY`o&9%$i`_(Y^NO&Lu6Hj9Aa~e#0n#8!xFwXc2uHhxd}Bc*be- zn59eGh0_SR5dOOf&9j#RjV0@N36|CQ^ zSTRe?PPYd0VM-!{dMY(6XQwxu&wH7GrzEcWkVvR-k@`79VYtf@v9_bonY$wJ(YrW` z2Hv*FXmquD8Y8o@Y=p~pwnywMWiIJMwQ>imsk8W1jxcH}Dt9-xi~jlZ{-lJSl!}{-UIpa9BQtK{8esJ ze@Nd9d`ob0zDQI14rk4T5MEu_*j(lfd0tDdmc@s}A}y{Ivp6j?Ql)0*==fZoG=En` zZY3V(F+5PE!gY`S| z&j(|R2=`2dDXH&{sXek&L0|4PlRebj(TJ%rKaF%Ps z{OM%f%>z{OZ%?}~o}2R7GcySZt`E&zN2Ttf-t5y^h%{e zofiS8l+8`*Y4VIpjIjv{YwYlzDNEH<-99iaIAbf!CPrzQl=gJIw#6*h{l=Ih|LtceNEfhJCz2DVbP%c>&A_ColO zPXQ|0Y$SXleT4__fQPpB&h_?V{bXd9ALQI~!JRk})7bev@57YMD{Tk+Tli0)(av42 zfs2wrnya)OQHCyUzhp9tomtbwjSyA8GrnYgeoj7BI6LK8dE~XlY`*Q2lJvP+q+KbJ zS~tZaEWTS`w^J@CU!~K=eyB4=pCU?`o+g{u4)q(`;V~bup}^3jj;|1N*O%`L9=f^2 ze9&!JA6eWZXL4}O@QPt*18b=9X@U+!F_QVtJR`G)B=-BNa`YEX$w&>y0*zki-n&n8 zKj`N>DqpCG@vQZDpNqVuPFj?q5fVuiUvjj+Wsl4lZ-($nVyl$&-zTb9G;*sZ-1dKx zLMuVLgOt;n5AvWPi855+Z46nui4ye)_4~&|j~Rq#WbOJirDV&JXuYy&VQoqJ%120bLg!KM@Klk9d-CPsQmPsj7` zp7fF@kL;GJ8SYGfnW<}yzFqIPz`PGTagJfjPA0&J;@vxeTK&BAvOSFt| zjXGN9#4)`^`@vo0iaGr+uh0y2S+#RvPJzVf>r8Fj4vOcja#{hsDA)R=_4<>&kd3ZT z68C~9?zq0u)YaOXVUK%GGVBS*@2qa7GR@NQ7Is1~PoKJ-Ys+f|RHC>QPN}!lJuara z`-M`2!$UVD74@I7>a;P-9!hr?KHY4WUcBek)yXQ~^|+~rUM;#wB_dkBvn#^x#ayQ9 zv~~gS`MF|Oa-gJ`<(Ez?Sg9~mA!~%QzUzt_G%8Z_$%;e6oy^wH!&#IFmDev9fDi@RE2AGP$=N5xs! z_YXfJZ7jDI0gQ^xoBIP(FAR~_DxyKluZlfL@pWG90HU3*RV;M-IgFcal3^{0pQUMG zS>@tfN&Wk`#?;vC@{*=TsBU$6(O=cGLO4kt%;JwWK66YD z*<$O1Iw|FO6`vemLA|nFG_lagbst#ftkSeDc;sF2v@`Opd#yegSw0Wz>QVg_wr%IYm#I~+)x#{Bf@!|Wyt1WLi z`-yXDN6pHRug~nf`|Ce2S{%){3R~L0`Shs^J9A81$7)yMbZL8L_4Z(Yn-nAR=TBJu z614Tf!Q?}X=+C|nUI9BNQLQ2vg=q3*32&jEUi|gJms&*j*fJZ8S2FGEekEOWYPAr5 zv>7j}bfD+ucxm?&590C9?H%Ig#XPZA%?^PFgNZNa2LtAh5yT;U75Ly{%dZ|AAC1TR zyFaUm#C9|JE5z@Vt;$vtcO>WJ60{uXmFW8AUBXiE{)9$i{8Q$?m?aB|y-(uIoH7cn zdl@9Z9xJgIN@igFp(#K;pRkr!5pbrq`?6#iJ~$R2?3SF_RDd{$#wZ35OS-Z&`h}HG zFP2x-BBEqYJK|UBmUj)4d+Cd2oiMQkTt9I#t1HuQmETK_XkfM8_HAZNhRv6|8Qsa! z88R#ird{S!Po4h`u@0&4bEZ+Jg@iPQG4c} zf;d~AemO!7HG^4;`}OZ#lcpsX-}+hjsr37CylrzHeWZiR`9bK0T9{K^S5ecq`iVN6 zh-ZN3TWdb9z;kRfad3 zd4|h9c73vu8aEt&CmgHwFtDKkmXXCNa?=3&`*?8HMQN)MCS8Bda=3B6-jBN&yUtTBF<<3pv}GWuFTm_GS_hw8aa1YZaIH z<8@m|HT@K6mlng{*7TDjq4llYhRHd$&CT@94w|kjn)RMCGs@Uk{Yoi5pSoy9#_Q6~ z7H9^(<9!cEAV0ydHLe}2>fxBl8xbme>_aG4vKzic-@YOkRmRCIqB&##Obh}4L?`@W zsahv7^ksWRei3Tf_1ERYrPx$?a;GQ0&#BHMu03(_RxX9>JHj9BX2OrL1#n#EF~|NE zTb6ST;a~}IE)8=A9}MZ745YM^bL~Y@cZTm7|IQ9xghr7a&R^_CtIU&~pXKTE;di4E zNDGnqnI@B5Bl@mc=d^W?)TqJ23;UC%%oKIXBF=6yiW{f&Tu14$= z2p+I@-o`V{d^x|)0s z?ZDE`Nq`Yn=ZL<)KV&P+)~~!js=H?3D(^Hm@_4@K#qC|qrHL;rGSjhz0u$7m$3Boy zLx@|>1_zt}x%1dab%BG|>Bf9iR21PTtK9QUiRYUyheFojp1Xjs#2MPdL7Iy($Dv-% zN1I}Ofb5Xz^k7V<8%H9053!nvEGHZ&cm|imCxM?^jPnu&GIlUv?ZC_)8^0rIuYi%e zMpEKMM4%3RD#Ve#!x)5-#_Kcn_}A7UA@r(h#aDN1?n(gPcCxtd$*cum0H(5F4s|RO zaJUNb@;-jFfa8dkua31Foz^=?Y z$r8711ht9l)Z6#XoY_;77igSZ8j}|rYtwM5gURpWbY>FgIQ9`9hx0_mrZ1}J)km%u zS=|ZlPFUF1X`nB2%W*=Ql)u6);-#_Zi__6cH37mb?Be%9@L0|nlmB6$MYRQ=q~E9C z2DA3%OW?{d&NHRAOESfS|!C?I&)EV?V&?MpC6u^{s_AG>?vMf20@a zGENHN-miLTRvc8H{H`IYu(P5MSrPx!$XoW1jf*S(8I{?lPWzIS6|(7G;ah9@$Ry)% zMf$j+%SN#d{_9O9(L4q}zQ1q%oKs2ib%o4oKY@qa;2q@0_ZgkHkav)L9&Rpf{qIRU z?B6ffUwCy)&axo}>EKvqEyY7&-Y6bEsF$T^s%9Uz+Vj;Mp%Jh3Em!itmRA?@GZ*e2 zh~#qHD=2Sy(YP`n7YBPaR&(efRKLfNbkLT)Et`W_$qOdzDyjJ$lhMG^*kZwnz4nBA zh&CAp#t1|nU?lkH0zN%UfUR3-zSi;RHfYI6LO-b>Y@f&&?0@w+(8qaY3i-Vt9;*uZ zKRblXQHN)E4$mV>@vDPb#IaSg7sP!F&!UUp!OOehs<}E~!dYX}IZr>dgO7!{}a z!4jhM^*EBo(k7iDmZ~jjHvPfwKchQCHWO_oADPbK*F42S@o0`xyvEl5Ai2^c3hV_r zt&po$mp6I`ss7;$(LHT4Sz*;|oQdL*I-2~fELu#h(9_m`cjSDL8^sPz98j_-u=&oF zy{YK7BBdHVj#7QuH27G6ePYN#e82cag~ylK%a}uoFwA$ZSz>ged3*o#ZHnT_DB@)I ze(j7i(hN)>bFmcLh8D#8ZjSVv$8ZMVc1K{Res8&pj0kWwx_M>PjPTxEG+)3OE}+P7 zV*{C0PE2frk2>`@42h5rLI}ziU5r#>bs>fUp4&E*$H>N61S`%RN1U<~INfkQjAYvO z$5dJn2|DP47VoL5q%zEjD!q{}vc8>L)r^V}sUe`cAsFpj-UDmP^-Kx23E8mF-!>nh zUef6w;rHlgmYLede`!Sg`9S_ntUIc_n~!(Uwko=mCk`ficT z1i?6ER*UeG?A=_a6tfTFb5_j@*qJC2{L&sH>|( zAdt6j<(X-6-Hp94C>4JtL4_@7Xp-35<5Vpl!ze*_UD~MV8Amnj+*M5{jw3-e`PJ7Q zODwwLNwcmpUpZdm31;_lt=}=$f8cMJc{OI1$oq%Dge>W#oAGOlX#@Oal3$1tO-4`I z6K3m|&>sVPE0s|#HLwoBn8p2>fiw)8E5_WFKG(X6nfy1;0*EUJ;==C_)|(A#evhH& zQu3!@`69zU=(c*c`JRrb3d@EzUnXjW4s^jLw|>n#hG+K~2^$alMPC8fW$YrY2)qAe zmiU6^qEBeBj~gBx85znIDO8)$hemB{{Pr)cnD8T2O4(hT6p_D~Se}C9t*SPIV0Jxu zJpgLllQK{c!^3No&{?h(sTB>Ol~2-b=ySUDkeVK`-4~d68ERwYxkcL)@k47#Gq!`T zqA37wzIoZads=myml@j!QI$SSE!uh_|0#*9qWj?1@fo0JoJ5q;~=j5j?yv?*FG_oNOHTXr;_@ifKMd|l#AI^*aa z*U@D}=vhqBkw`ND%7?TyWFlo>|9540%5j5^#GZdQ3u{(|RG#~b!IYww+@M9&+OVQ@ z@YCLY`i$l`n#qL>&Pmo@&pLb|OYHVT&<`69XPtdCyUvi}l(UuFeEW{%>KCONnycn} z>5E1N<`M5`Ns+u=3ikXiX%7r_6RMHCc9))&=bhzXo|`1)EllPq*gvqlLMf<}7sX_x+#SR1<+GD{AE1LZ|m|oaowanh} zT-S|7X(HBpuLsrgbiePYnaaTSTMAu7b<@z_+ zX2tm;ts#ya&n(tww;wY6TU0Eh-Yy3IdE<&Q8FB*Z+vZ5PA>yukBci-wVuas?-gUXz zxx8nQu4BflEoCtrv*LJ6o74;_L1r+eJZ9v=wjgI_%1Exee~|2~_x_^mU>OP*O_+$JWGlwCH~%z) zA$g7GZ0vo$sr%)}53q_81EpeM6!YIXFCn_s zC&FtcbhpJb+Wmm5JAzlNNy#U~=MxfwqmExU)AW;%_uH-d}(oFIb$_e(sfo z*+*L?aeli&My9=KDSBwcf)kqLYNnU$;7UKct87E7`XPD1MR$1@Rd3#Ni^h!{v#TZ2 zZbtnKQWsz~eJnBV!!4ww&wqshYlQG5KC1V>%_@dChGM$;7AKQWl61-APjwReiZUMK z!Nh$q;(QNTO!+qqr0H@rWdVCv@r!<@Dh56A`rj{CIW2XP(~0J)U}?^Ui>?2Ly?!3- zeM8@#`ulyadvq0E@;CSdT7KD?b$;z{b)z?n82)^DpRcq}BPd*WL!_**kR5B$EmiGR z@^7_#%%AnWcs=GEw=x*>x2li6Jw19po7j>%G7zYKe<7Gj9y+|Y=R9^5^l|3*vHn=k zKP*g2;aC3Dt6Wm1Upwb_JnNA!?^7hNZ9%Ca`RDpawlpR`t%*Yn27}jtHfz01*huDn zZhaN>RBvH^6a-3U?8IXUe)#f+jpC&!$!$mL>LlyJILaQXT=zfsgVZ{^x;Vbo`W-Vd zd7Wfg`fSJUi%B1t*ze05qVb3pqHjkvWIm2s4zNZ@KUSnWL z6O0;a)^C_dF2!p%h1c|4fNVHlVL>1Tu9-(yK>X5uxLR=YYVv7C?GYki+2cY@mRZ1? zUi!||@!TL}^WC$^wyRxM)DC}c0ghAknggG3=sbfr7I~G36EbR=RETc$;hjB<4~KV9 zu1R2{PkkjEhAqEP_S^uT;UiR=FoABv!r|BtW0ye5aG<#{BrU;l4Z3PH-sn3uSgjU4 zvLsGTTy%WiKiO2}-Xe2rH0O!~@v1{?_ZJCPI_EtY_eK8(A~VGFNbGVWqeLxcJV^wL z#xkezdJ{TF_%3FrjtN~#Vtj8KFM%HFs()zxjmJlEq=*F&E+h5rx9aKE47J=^2!qUN zi6u1_6^4|V$8K2Mn&CTM`aEOa@D6wB^RGhoK5|%Nq6^D zNBJ3Y@ce*jyX8o!5dr?Oo*S>)a4@0-&l=pEIt8mF(UpE2?IG=Izn)Z$Wri{BdbQc8C#`(|32$ z=B4plxFaOl$y5+I--Ye{$C4oW3l!bDI0+?UmvX|O(YMxpU^3eOfH`c7c%Vj<#WtuB z7O`0d#{p<6s%E_15=BaAX*qnA1wJ|&miKHvzc4Z3=}syvzz+!?&f=@Q8!q2Q>6>lJ z$na~Ln(^i^ugJ-%%Ti-`z_wAjj!0I(L;A=tT9bC+Yasr#p=lOkdi*Jm7%jOQxNLTm z*c`qXNbTn^`n8eq{MRaz=bHR!kYX3hoZg@xJNF!88)YV4z~DV*;hr`l#7csWeUW&6 z@|-93u=CY=oIb?K8zENyO3R<{=}ibD46(bv7(+*Vau8$9qGF~@0pcHhfU8|zmp0-B z?@!PB96`}hzZ?Sa)6@ozmq?Du_?jH?n^|1Cd)UeqFcFdW+;GA(T)FQAmHB#Ipd(m8 zeqH?#%X>N^qVIqHdBgH-QJW|#_&S(g;v6GwVFR0S$F?<^erJRE?m18}ogPYOjaKs< z63?F$PZ8W`n+`v%cFa!_bT1OCa9*19ub{9Q@7cCSlsX7hHNCxU0XbdnQqyVvqGRue zk7Ev2V61=RLEUBA$5dU<4Y)CZ@;hILTQp9^jD9`~uu9sS&IfSgVQc;Kci=JA_t z3WURa5AMT-HN=dT%O+dxes{A)$=%%jUI!V1hr*ilX~39KiPMsOX0NwEF(c@`yu@z% zU9gjVJY&WX)w+=<@$!xBhH@QkZM(CH#bWet3z>_Qcr}@h)lpB$8{=pOC({B_>3oW9 zYQwWb9^%1Z@k@8<124`@qlR{8Sg!j~mzpLIc8e3r5dyASqYRl&47svdJJijU?cqrc z`dOO2Y5ZK@txwq@`a00v{%}*1p;BH=9oIL>s|Lsj{C%}#^qEs z`yWpu1kCrhw~!laKB1#v|C-_N&r^Hd1wbI7$dwCsVW?xj0 z@t&R$(T{UPuY+B;v*wQWrjDBw4+kO4N4+UtY+~99)sgx1%TUzOj$9>R+^J%59_KYl zj_5A%Y451?x5oJ764}qhvFz9r$>n@9U{owb)Qpa71e|v!jIjm@G?B|^WF3s7wmqv! z^fwlnJIhb%?40YVfyIB@L1ob}{9@}iQN$^6i;v0gK!9TV*w;fu z?p)2cDX2CSx=oRCn)l?~axTKDgpBY0se$*Z9sDO_IQGUxP}+J1VfV?wNv|DtFwN*a z=%e_vi6mHqLL*jb94335(CYBQ%e3~D4 zof)}@n`>OKJcZl@{p@o`i-4J=F%hGxB~mM+MvGXmxU8&&a}(=uM01*U$Z(@F!03FvjPS@%d!YH6ZRf$pXWwam#@+%!SBXzrJ< z+8(HHFKqwp13+|x zm%!~{)UxLbRhc&OYxbXQaEq4O>sFB2iyhu2+Mbv26`NFM|ISad50ia-cjV;XQd$@! zTz7uAIzFYpN!l+qOnvX(`6hy}S-+ZDa;6ILx=1=WnFo7N_4hD?Krv);awKg`zMjg8 zP9Hwpo^!qga_;g(XPN1U5zB?Nq}@Capno1okTKFjLYt^ zey0g9J3Pzr*Y#ovAIIREe=z+co1C27RSNnODEJI&`PT!{V2FPrerkn6@~sT+lK_L6 z1EwPY52EA-0zI@?yGUdNR>AP%;$l!x5a1Fq*3vODZf|Vle7uNs@C~d229j2Ut?0tx z?R>W(2-KbNj$0l#y}vrB;|H9P`y*D4@>SB<`JeU>60Qw|=Ww*Afi z^a~t# zd8Xl!I&U-FH-e_^*QTe4Zy&V&brqj=TUgHrIQCVNQ$*9#&@s_VXn8Dn@im`!>MwX` zi~OnOCw(FU#TdHQU_L8s=j!S^f0em9!)HKTKhyKA_7%|213iR)v$a>e)kU9hhlPM7 zyiswLeaMika=#OJkvczeedCC_er`0}Iy??D8H<@@#*f(zWYY zrMx*N6RJRu+(HS&nYAZ>YQz8yKfgEbCo{h)b1d$i?&_#xGstQktVq!CG5xhV%A@b( z#y~HZhssgy4QHbrinE9J&{o z?-1@4_O0C8_PO*vF*{jRW;ayr+qZARA|fhJ>IxDDQIOxGJkw1%zfSY=%*@EFpz^}B z+GE!Rt6_zuB@QoauYw$xr9a+LP3c3bGa~yuopa7cUu8Vq*DPs^L0oz$61An2$bGuZ z2KH(tq4vcHWqIwiUS*@6S-E&jQ8H$3rO~JdO+vj%R91eP>yDHcDMo*P(U||J<_hR( z_V*O^VvILNAUBMki>nz`Qj}MHXwZg=q1WPZi12pmeY*bzQrIRaB?7#XZv#5z2B&{g zX><&nI&>{E+p7sC?Ppy;tIeR0-3)`}z4pC(dkuAK`VuJa zjcC8y&@#7DU4Ni%d;DF1h$I(1j_n>V)5p~wRx^FJE6tEH#M42^S)RoN zCC9-f2Eo{z%UO7!+j_uY7wHr3UtaIDmIOWfB`kZS*zFpdmXf8EFZAFrUeZkuV{_$W zeMH5aeT@ONBPj(i7th;8P*+gLw@2@y#I%A< z=NrgC?^Pc#GV=2At=2rrb*>a9#XfpE7V;zky_uq(SR*rblSRA2>_t)$y@Ft|*n_LI zA`zth%!yYbp~-n6zO_B28d9V_ME2A+;&W;{Nu#NNi5x1p*3Jd>7mssDf(`# zB(V%2XEp~LEIF5h7LSghzE}|z2sF8DTn5;cEzGta9xG&qxdb*Niyeg_6Mlxm_Ul7u9E`5K7nJ$ z6v(>-W(}0IV>2|g`=q^na`I%MO@V?z;$WfmWm_8)MGFE)Bp~MR0~ki2M6AA)x+C^W zsn{VtUea`i2s{#S{4Uhg(AfLaIlsd(RFph4_L}syd*f{Ezeaz`E)OlVK^5eYNNPgX zT$Y%@a7^%^O#zNAEG$ea-+g8RaQmeljU)j(`5TWGhimfZcQeLTWH1H=1+#T>)P}FA zE~M=VxY$2!%n9*itR4_EI-!zYqo!;C&|DY zXYPUT|9_bG9oOIpJY}^iMc&cH)h_Po-#gv06}zq3FWsm!k4KOWP4_eV*f4*a>l5_C zFLzn8yqLfCPE3W4fnol*^_tykrprJgTc$Sf9{HQcVMZ{$Ia58bkjk!ZUljEvka>Pn z-q9=J#ZfoByQQfqa=gonF74GhcbQ^c^V9*p{FRy3F1Rts;^^MLX+N@nK4pjW1 zKzI7^keKi#i{u4Kp|iqsq&!nJmDJdMi?c%8yi z-}75_YyAqIpe-Sn-1hi~pa`Kp#nGyDgS^l;AhZYtWzv9hmmnpO7X1qzuEr4r3vX_D;^KCFo)6sg0 zsID%xePo9Uk%tybJP$g$pWt?=vO8&?XOqxBjcV|8*e`#ttrTgnLIFR#`ZVJBP{(0O z4wpah?1%n8YAY(&g?o$ z1m7tEATJGCZ8d&CvV@&Y6RC+lN6tZ}*jf`ESEw z6CWzf?PJXX9>5W%m%eu+GAfEOmV!w}N?5qs12-q_bL8sbft#s!vE|+kUH|&^tIc?+ zZlinIhNPF}{#$R`X21I$h6ZkiE?t4g#i}=2wQC7Gcx-Vz&%zN}-cBDqXuoxRgRb~x zihnyZV8m(vg1AlwcaN~gKx%qw0^4U41;76AUx(c!TH5_^OsC7Z;uQ;RHZkYm0Rt^# z=7X%5$M-Th^pcYJwy=1)w)^DK_Fi#O&8KGxQlZdDIx+uM$$5c-?U9i`p*~*S%G{is z&vVPj-d;-<0ALU9@px2BixsPnjqmV1c{CXGB6%>aIwX8cpP;P28;>q_?8ZPo}nrw?{|Cs&v6#J>@ zD!Qp24F|1%y89RoxP^4(U&Z&M$GWB@&iq zfp8h%Y;@7P^l419fic*9U^m744d-1i0O#L}k!MZVmsU#VA2s>y0elcR9?amB2KB9t zdcoXXLoOF3T&MS4=nOyq08~^pA!snomr8%f>t$C|2la0!U@kM?$ z%}2Kb&61?_-uT^xw-rI$;&yp+&dTG3_Ib;N3x}W5>eku=+27pEm2cl%Jf+QUtG@(_ zaXVGQ-lZsNE-V~EY?ZF8W+_bPQC}^T*G~KD;cBI#qBjx6oww)8ndW2iqOc$5K-OdK z;F5=4kGw{>RHo0U)BwkZV1a7BQFDk04n!T@W?Uw9DHX^?+rWh1x#y*Rdb%x0T5n?x z^GI*)6D)lFMC99N&Ny!7&h!}(F%Uq@dEg~q7oLiwJ^@7Oe+6j)|7DOi`r{9zr7CbY zHl+{z_Jp{+#ssxGIYTpZB^vRXjtXWN&3AP;63{ETuQH0{Pxc$e8C@>UyA{9x=aeX{ z%bm-x#C9%!yu&BWgQP^ydYRLsunhe3LlX?)o%SHqP0Wq-^X%jIE{dg%=I_NA=Fq4f z0F9?5)ATZ^#(h3(Un$#*c~_WSQXy@c#o|xym((;hrlV<>#?lhRM`2kHQ^1_J-~2Gm z5i@=1Co}HSH^*L6NYvgt^Vs2Y<-?322vX}PmLo3dVWAA@S(N&sX4yTdyrb$Wanz2$Xt}e*Y%mr32^?mHBr9-5|Geh^in>^a%nmb~E zxW~)>hPF^?LBmb?c|5w4xnkl0c~`T0q9ICzF%)_8$dI=~85^$=nY{abiI+?UzEPLi zz5DB?6%e%B(wd7$#Y8gR_$sfU5d8|+ti?!6i*EG5WlH-rG&atAE=5*Uw6o;{DqX2= zl{pp-FUBor_v^1$93E-UxH*lv^)pb;l}K>>LudA^4Od&DTJvSvh7N9L1#YZhD|9HN zFgv>)4KOwvPtUrhCIh!hmeCn;8)xgcnb$NVf7;67YM(rL5-oi+Ii_R85Z>`P*76G> ze8j}n($mw}_eIzn&wNWmNXw5}95uO)uvEi}qQntqQ)iDQO7!)=A%|R>^GLZYQ^2)RAWj?q9GZ&(w_fL@wIY8K9d^@x$ zv9pu~;#V+Bm9)@Q%~*7KZhH0OOpm@b;y-{M5`dqL$YRpJfReY|#caN0r2A*PCcto= zhzAc6S>Zk@f6qbAoc(OzJ&U28u;ybme^;U_;QRx{paTa05SDLtbaW`$Wt}I>!0E)M zOppHq_Icd%g-Fh`ywqa)a|@{QHHkf7ypYps(DG)BDnEUfV^)9q$-~UzzhIlZ4{&qn z@LJ@Ah+d5i9jsIWiz2U`9jdJ&mhpf73&I~`22m}<-^pfl^o-(yqB2ii3%7smL%dHT zYk%R20e%j2Oq@HQNczsOTYrNUdTs5bg;L&9@;Ke4MryYdc;1ieaQ_SMu|3w&vlOea zL1Xu(YoHjujdJ(icGZ6&+8HZrv;D7NUuTExq2a2IWSqy8!p)}ecMTC&`%x4Qhr*Tr zg2^$b-_mAFc*BmGpCgK>?T{QeHs?2oyWiuOvRw zA?|NqwG8)i+(-M666SxH;aAG%iPI&-T|S}@n;oidp$1oIUG<0ep_T!y5B2ei`ch4R zpkT(RmYRx+%|@c#$m}=ED#etqu;hk10e?Bm;Ei$)h4 zIyz-~Ze0u>GamDPfCm3674A+!AQed^|CeaI0bTqnYX*%Siuf5yF2AVpQq-?`d4T9l zY-vg4gxpgrO+Y@eSHF9cl|1G3$&Q|hNE2JK^@mwsVCT|RyTbsi-t)^R^mkDr64akqgMM}57A1^NX=K_y#}fa0LOgD zLlIDH5{Hm9eSDWhX121Qi!r)}*Bycc6W#KXv{A5^;j@*GcD}#rTRgh^n6jKQ#GJmvIiRgW&*#mI|BLLDMHk}7hzYln>9Z@J z{Nc(DCaG_)ODf+%@TV%<6up)`&3qW_3|}5hVyNo7RrN=*7*h^G*4L4yYQk;lJ?c^M zMMfWZGnoN11(PK8_#g2##BL`Y#z)InW**pBxhsayz?-$>dm*sKfGWGy5doXLijWxjP(Q)BwzM z$iNC*=Jd8@tL%s-PVIvM4d~;CzrdG1omk!5Gx9+AvpM><5+{5@2-;`ha8C&Ob4H{J z=S=T3k0i$(%1{5AG^6jZ9Fj5?pApPPo`W2gvNc&zb;1IJv<9KvqC+UOC%GN21C$ zCGw5{AHsac3+wuPi4}wUTOsHn7klMCt%S?DRI+f9q5tT2Kw*XbE94zxH;+y+O-H+g z@oxVo(J~BGSGOvltQ|AaSJ?)(0SPXhSA!|W3_a(R_z=5qPYkum|1``WyaKo;j~mK$ z|EG1-CU$oAWHASb({58-++yWdG9(g7#UpW4w?xJBj^9YzRDVvxhlO@`y}?gXSg_J; zbEzp(C8B79<(~nylJdKpPJG=Tr3?ktT=dJc*$OFGTNy1Mp_|*6Oa&^I{drAZL{u!IZZ5()YvtAjbVTslza%Rz`bzZ zb4;0iUGJ>FMgQ`+CPEK#DM5 zU@+9vj30+HD-}s)^kU|cZ&~l-r(aY~O&b)sdozCyc($3?6=!cJX)FmcWmu7W9T`yt z_4{(QzLg%>M}36mB9DqMOpG*3l8Da@QZ9Ulb@BUbZk;7NTYX5X&7vzGai!kI9`rMP zuSd;)Z^mWtaAJI~CO03$9B`muZA9_MJ!JI-fayLpwGO?J(@0OjkUY7HOC62Tm!rR^ z$yt+@RBa|HOSG|F-?S-Ol-q6Bt2bDr5@~%u5>xbr5+?Gt&F6Cw)p&nbJ74<)expDH zmZV|1=8ZyzR20fB#64%uid5jeBoI0#i*6lOqAAcHtEj+rf`n&?thVgebk*d zxAJ;Rjq^UI3s^3n`X)#LYAK8!C;T)^G9rBCORu$^T217YE~(sGVoP`21AZ_gisk)p6dM}I3T-KiJo!i59f_$<7uKm9W><+siIp1wiABoGx|3;}cO3FHbN;pSv?!n>Fm8!ivW@dLlPtpBFgE1-W z+(WZsn|hKR`u*RaD=LEE;nZ67pH!Qp-v4I^Yl>g{?@Hu=zrc3VYMsvE6UGk)r4m3E zar~Lvdw{XNAAi^4d-^2Fz~`KmVrU~F|ft-KkPm6zc`$r zWRS=O@>%QZ>fms<|8fr7KHHy46)p!Io?K{Sj(zn-@4ykrcL9F<rEgPEU{)VK+X5_;Om_~j6hJ-A zIbKhHtJk*B05B9o5w>n(VgiMl0D7kX&O$H{o1}lrUmZaF6nOQn(%e)6kc1Igdfv%- z9BHlPM)lvQJL|Y8*RB1JqGF?hBCUX+bayBzQqm2I(v5U03IY;>bV@S_NDhrbhak+* z-3$!PkORMa(4EJ<&wI{!_kPds`D6Rp0`dUEv+i}R?{zH|u1y#9J9+aTf<2FZAPqNJ zjFaWH_iz6`md(Qawh{xzGmHCYSyb!Wa1;{!JyB=X?|_jqc}LE6wJCTp(ekiVTf z$2G9!xXTQ)vjLrwP2XVeB1o)TaJmS2VA!k$lwm3%9hjA`ghYwCdmJp^k5wg}dQdle z9?S6Bi`-Z|P85MLGi@s&?(U%~;`Z*2XKz{>4za5#|XNku0oiyRabMkIQYTSV%TCK0Px zTp9kF#>r`-!(Z^GxeIhQfv4Vb)zmOj$5yq4n`UrRZs8VLIy205mr#}9-0oJ;O@%k| z@b^nPg^;x7*N2$qRdqh-3pYJ6UfObXGB>(P*Pc2zpC#P@O{AzG4!9Mb5G=RyO*i>r zVa$;Hs|%x3ZYR#u^H?5IvI$gRFbK9FOVQ{Wt{Nw{P&eEFSe*6ZxSwPx3o&lNR`>c92Ltw)Erhi-AM>#Q28 z>B$ybgl-X6^CV4GlSi-Nvp=`UxNV6G39iJw|LInkGjH>w=!ciq*b%U?Dk2^47m-Az zKG#{#DAu0J)&4rVG&n5FSQGcOgIe?8_PcV4`Lm5v_PWOj zC)i2?I&`fVUdMJ6jbF%ursY{ZP)oD8#O}GFk6>es=){S)yZm;N=qOq#^D{^gyWi1z z^Q*1k+EqpIsPJ%IWR^tRqJ@WwDP1}jca#D1m{Mj?&aj+Z$h7`XceYZE25uqnoFIMS zP>m9k&|%IO6x7L^zi_61W1z06lDIuz(7>2*e6v)V?8v?T^YYS zz{_rn{#h!~q$8nJ#$9`GJU=1r(RTbx!SewDU^OU05)s*Cyi_gawomAFj=9>X`RWHUbYqwC1V zJsk4Y21mY}={7CaB}|;}9MD$Lkd4+vgVAhBRz+_ehNC{7!@<73NW{XmQAlc>_!CeQ zr&qmHDUzxc#Kf%EJFpqxLr+XT zv`S6py0$^@d`Xgo+cI@C&>I&ITAdEmDH{-7ruDlf)FLN7A$f|)0SjNb^r!7DUj;o{ z+N`|nztv3w9nz|1j+i<2`!oJC_=`xF6ezYZK&IbUrX39kA?TW4zsxo3mhF;|Y}j;X5g90( zi7TA)Gs4o8GI<;GJ=qc5(cSGbN^VyQC&WROyk!OS+Q2Qlp=vT>qP~I+o8fwzdS&}i zYuB9Io)TY`o>SqagpY40FhysSG4^!eE8~Ve2qp$&db)doV8$fzvZ@6nkMtW2m{G9G zlt=X&n!?3U)_h{vIpi?X{6zU&;1cXJ+#J+MZ+?er6Mj)++M_@ zGi71mEEB!FZP%izt*ve2RwqT9C_=)?!I%z}6*IO{*U{_B(69JklQWmEl2*O3DOVvQ z+0|WpiMbt_rHEpaH+CEj zcu}N4MH~o49wX{kQ|?A^*FCBT&sE{FQkoyHYO8pr!pLbG&JXXV7-bpm(W?q1tolbW za}u$ffQpyRkvlUl_!0V4)%h+1yaUk-W`{x61*y6{i zl2WP^uWy`NfELN>Pv_~r)1;|abV&-!rF|uPG}Bxoa#85X!h+66 z?&-#wyP zNjksQE-;f-DF#GR`VUcvf=} z6vR7EJ8$cpj)Xos9Rueg_p7Qm=2{%A<3=ZV=6^*x^@B1_N2WNBfc^S1*sp!Xrj0T3k`jY%H!_rU_K4a2~y zm{9u_bXRlk6Qdu!v5*NUK!E%GQFmqh58c&{QrN$R$$($00ju)*mh@jL4mxcZi@zR@ z*Q~yk9mrMB_hm>Ptj`f#QFFiTo|@)pg*~>$eBs^p@#JK}+pNGS(9n=}@J+oWtPybA z4=&g{BxEF)U|8QFi&R{{^KjvxsF$=Op5J(Xmntbvf#xC)!0c`L=N4CVBFVX=4n2xi|P#i*bsZL3ud!D1D0(c3wsVa^)F z&_34rL+$Z${|qO}D5j)R*qp+ToUiK^vnZJH2bPxmTr zry&umAq$%L9B_>^0nA8d3&57(cPE#pdOS5u{%s&}%yfuFwtQESV{zhnlf(>()le=N zeM&!ctyowiRgj1vU|gT2`hti?wAo@XbK;0+IZucpr=wn^?}*rOTLI#(MXOFrKDi>s z)-Kzp2A3v?%Ogx%fT7#<-n^eDn^yaol-gmbPzhsu}FA3YE6)(JXdfl zS(C74finkl8Ukf$KrP8P6w1y1F=Y6fPVF>`ZUUvSR+`*Obu3?$HzLp};(1Y`myZ)!^#adiIWj^8T z+WO>;m!pZW*5nvX^l4NIZc&gOq8Y8ThBx_bh7&jMD0 z{TAyo{FW||b`-5%a1r&KIGvyFu_H7iRF7XzmV&{Xi4Ror@y~ z|1J1zIXXuURY=x<{Lad>+}pkkGhuhNiU;`)9~+7in`@V@c?9!Y9gG*sh2DNTj6Qcl z9Bq`_;emd`qdLfHRdIHI@sr!>c}GAbaD_={60r`yDHZ%I`}xdKK$ovN`O2hbewe{Z zOA&As>+1BCexcpuQmN^QH9^rhD)>1O0WaIJo*XC4FqV!JAL0uw~AU2Mr{e6PNvN>N!5_3>DUMXl3&@ z6a){UisTNUN@~ui2mQ(qR4t8jXZCETfp70ctd$ykz>EJ#CXtKy5PT z3aY21lKw;@ue6!Ry-BP7-ASY3?wKjeGEI4^@h3X_3a+j->F%ly-su&+n-() zM74n?b=}t%t@35`wLdEv`D^vo>1?c~7Fl!@dGaP%5Gh8!Ws1F2Y2vd)_CDe0W--of zO(dD9 z#6sCR_61^tF}Bqu(-g%a^XF*4b`^6AWxLOQ3x-R~M!J%O(mMaa%a;3Riu*J`K!piQl*_oN@CFXs=2$K0N zMMhOqGX_@d;Nt(?+JQ8o^*FzA4p5?Ayw5wi3>LF)A96F`(I%nl@hBYI*zvi#?H1FQ zcQ&jf5EfbW38Rk#dbAN>u<3Dpbu-Tz&x3k5V1Z&l7ZJ-8oNsb-GoqzP{m> zCNp2j-t1jqn|lQ8BoFw`;^B3Xlp_J5`lVx+F$10Fjxxi?zj%7qHb+n*%l%*haPrIe~}f<_o>6j}WXHp|E0f@g^wC_&b*3o7=N1NS}ag!h>&R`F%Im?ctOI@+|@C zacFI+kq=D2{Tfq?FTV%R5aJ|R4B3vY;2t8_qeuAh+diJ0IhdKs(8N|I{?Y(%X1s_SzVOfc|~sOwEOTst`)rfV^7C#yU~;GqWb@tb?@J$ zfPO^4;vdc+MgDK4(P5htqR=YzJ%0PB$3(2>XL7OlEq37j`cxv&9|6;Kxj`+?ZuM4? zf@qgMv|sG65=LiqzD*2&5k{|)wofU_lgi&w3>;91wmHqeSq01{5&y*d_kT4KG>oeK z#<{L3YbTo8uIWayoe}c?yXDdKt1?7CL#}BFrtpQ~X|=R;ebXo2E6<@+O{;GYtd)6ZOB?5?oZ2^a?F8j-vHT;jyey^j}cX zIVT{V*~Yx@f6e5?_Kz?^WJ%5EO&{$ioF&Lc_ze*s-w)!;Z)_XddI`NORuwbs z!Zz>aOzt@_P=(kWppnuReUgSLs@K>qfb|>Q6?$O<%*`j(Y64A9WyLWB-k~y1C(BP& zbQNT8?(EKpdFphFYnt1EwI!)4VPKF&BC4$Z!C7KSR$`9eQu!$!c1Y_{=L49++K+sS zO;@yt9n-6+Zr?xQMbsA8UwGp;C~Ej7JORf4IQjL+0S zgji4F18&Eje@S_~mZ+Z}WttWdmMCFgGeM5+vqm$+DiS5Czz|SqZ&~!wiH1apwgDqR zyL^C;v4gc9>5_87qV>|I4P1Z|H@Kk3hix2zvUcAge79{uH{^2Nm50hlNi zl~#$+mx12eD5xBG#UM8N(eIIbZ!c{=JKNZGV~qngMeOnn`QXxm0%+tk#Q(w44dT3< zi;{L|OeNhSk+myH(fwl_HAo)Tr78X79h(2p+t*x^eX@8PV{yH2!6H|gzv|*g;UxQ( zM^dm7vV1Z~LC`8?n#8w$Zt%hH8agQR%FB=W!(dhxuOLNgWS#0y-SX1rnq%9aHFefn z!6#AYvggfql+C%BOMDgpEGIJ`~{R)Ro7Yl4-VioMEK1Q4#WDvzxQvBOe_5>d~F*+ z$Ac=>6r)>ifXGcqR1r|1k0x67O<%tJg_)6&Uh6IiXY$ufs9eZfcX0z9cO`)mtvOAD zHLhZ&;ekqeb%Y|7TA8Q?=E(U8cMM{{>1#z6`-O!oM`ES*hd6O6=9atD<#f+2^$qvI zkwx!^@!t^}hx^uwoYM(JUEif%F`KQJY|E{VF&&^@;dHaP-Qq6DHbZN8azO6s;CTqS zGhWhjC7LzcHw|n7ucnQli9t{YXvUouqAFpH{{vs2BInOF02m$O5?i9MMhBj8{c&}9 zZvjgITh_7H%E}5fdDOtnH1)qfB9lvlhLL=;I2ex4Qv=`>! ze`?kn?(Qd19nRFuEibWD1dZnVraDb5$JjPh{6Eqh1w?+58$0(nkRQEq#a~yJ4e!dQ zl#kdPX3AN^qrWEh2$jJXr?BPb0dkoL6q>Gy*{!og<>x_27q+&TM8bCe%EXX`aCDGe zUY6iItVELPqE64d*mPjaT-tr`yyQtcuTcwrJJzcZr90n~r^aZ-mXjErzQthJ5CD?b zM4V%@JPImv{@1FHj})QOfJ`9G6HhZL%1~r%#}8OXuXn&Mm}q?$B`*GA-@u4b z_&}hasfy<|2*F^eOp!J-?JReAd@DRdsc(?JX8zQ_T5}qtlw0Zxq~oO&MVm-7Y@-vS zHWjtn5}0(eDVx~zNgH*rmUc&AX{W*$0uOlqfB^cSJf68pCF2yIbl$z=Pyh0&Lov&y z0@ay575GMGVIznzMN`gIX+>A+C+FRNZS5(T8}ndQDpOvuy?c1H*Hx;>rQI*ico!7~ zewqjdcbww3vy^#;O_kc9GkF0|l=rg3@^3BnSG_fCB$hhx&1X79(Mp2&n zDHuht+sHpXEkGe2KEEQPE8STaHP7g}&C6CMd;|LU`|Y_h3?yF1UkUkNosuQ7#LOo( zX+&7Z;;y~Odk+3MR~cYEAaoL9O&}H4e~S^SS|U#Q>wuJ7E28oG#e!m=SL}`RwYZLv z6ZmNW#DDVR#*weDQq&i%O4ZHq{0HQ3awxeiQiQCOTiR=RUhBw8oHBKlI zX2PiAcXI@P@_uf_-gd#G2qOJM2T6=iqtLm9c1l!v>5IsntV-3w37`(9$n6w6_%}eq zRsPUqmoJpVeICB$99hX3f5L2l(DCJNU!AUvCqJ`#Ho`^6Emzv=iHZTIfoWeJ(zG{y zQJ-+L>#AdCFM_D+bTRhK;%9bJjcYTe1xM4}3*DWG12>>;?@%-lJnl&a4(HE~radhu zU{Yxy;K;HNxa1o6)q2p(s83vH;x+IoF;$QnXj(}&T-tpyW(4J1VKXF>+TeR%1XlEg zrTB`x{rdGQRp;1eUtb>|^x{y3QG3GT;$jb&odJ<8wYD)0*x+(laa874 zbM>Fyb)Q#DfGmc+$gcxP5L~TxbHdvSmz%%!NtHke+$Gz@z5yZ`zqij}dLj!G^(d%m z{MT($gUrF%=!N}bbcgRJBeJvGA2^yYbgTxQ(1x~X8;aesdC=nuJ^VxuzC4je@ixyh z{FC-+uBTt4+nfInqfvg5(VW4$6ykp|0*kFQPq2dWcOh8+u9EhXpH4vj%d4u@j$`B; zO2AvEQGSA0??*=RgvF31aqD0}r~MBw!!*KcSI+#4^{iDl(ey;9X=GaKrvf!H=B&3o z4ckMs+?k(23&o1_y@eWZNBzLMb|*sxK|@)@{Vq3ePAPw=GL>=Q`i|ZsX+1sZ%gp|? z-OW>%V40^*g!#zaaC6GZCmzO^DSmG$MB{P2-fNq9&w{z*2J8hNa?&O>SF{L!LyrCB zk;5ocjS?4#|*a2sFLj!9fkK)M7ihs;q zs`Q_b9=_mx-H2YZkO@ zNEbiU+?w~1tpN+G0{>^cf5Z5gdbFZj#I5L}sEjJ3~+AI~vt{<=yNooL)PaPvXH zhDnu8Qhj^HWX>Fi`apH5=wLe&+JASz`J-k|_dFPQxx{AEW#3Y2@la9w%w(F)}u|{fy60-NsuKm@jLrO(9Pf4A^|uYA;Haa@HFczzVsw1 zi;360X0LoEbopDDm<-|e(H~nT7dyWsp{2eJxU%H@aiVwA(*JzC1r{8B{`?twzYBP< z=i-h3ebPQ)I1PB1RA{By+TsgoG7P8uKy-7f>bi%Pq9Pj)kGi@#kJYe1B6B`EXWoV3 z3drw^q72U({S0xa(h+_LN5Qgm~%hU~WN-E0kTMmd(c62_%`4^bQrg@{rknOb@qZRhYnv0bOM8yeamq8@xiz40;Ox&bqq??y^sk4wuZMPXJUNIHIGy0_`(9s&{YZ-Lx!B-`D zzZl9Kg%rvFX*IpL7_mphMw{R?J}E3pGf#;RL`u$bDC>#KMo{>Gmel5_ug;vAzj^}L@y_pupyCvwQ{Ou%jlz?=FU6~y1D4) zL2Ml8`et!9_R#$g4c&=&J(XAx?exC9I$gfb;a+VqitfWNN@Ci)e(=jyhMNTizm!4Z zNcze8g{Icu8PW9ZtNy$QvgxNjmPE71C~v zkv&D{IOTuBa|Uf`x;=IFInCD{Tzv$mrqOMtyfwPgtC9IS&(SU!BpoK2HZPSCoju&| zvpc!`qHA38O{1?$&pnkZ_rJe-_Q>un_(KT9@}{vNL1bBi^VuxQ*5+pA4Gy`&EIFDj zwkTHK;y|D4h^$j66~-nv*46uU1}BpJX*L)@l+H&+kfD?nD=v`g03a0`g8uCid?Nd* zTJ*Xd_=zzX_gE2jt(ponpx?aJc{wt{WPj|YNq~=YD3AXHnm(;j7lHVo^t^v>Jnv+v zVaSLMbvz~F1qe=}A&ac~#zGo)S)Utj%M1W%^Zww4hL321-lbMl9({r}>(Ybc)eVnK zrFSL1#yr*M86B_y2qkO4fc#5=QlV=xHUur0b716g2jr@}F!XQ7;IQfT$ zRRx-8-dJFf+WTb8mXk(zamw`>X*1#*`61ehm8;d*ufLUBA)d7cW`z@EoBh%AjD!<5 zCmhixMK?GFw4l)}o`&N4qQ(`^#iNRxaLd|=`Wx2XSnf8TBcF$hYga_jK!}1*t1$RV5G^OuM8+ z5gnh-jdqWmc)6=<**hPo)5>NHC(%C75e&Ab1nEaGL0XBO4<;MVD(*fzerC4qrdAdP zIv>MNUa4lUcrv$q!*|3=E#5po&Bh4HfAp9EB5-^zexyUwVucWeJVx5Of+ z>fux%bP8%VPlHwDVnKu(a8C^l>X`jWN(q*NKP2EkP<>mQ0Q^<7!huMS20`r8|AT^2 z&|$FiPsOEm==+KXNHDNx?^J&Tt9u{+fg7tBZOq{^VILTQ>aVBImgBeoY{9*bTJWTQ zHG0@lU7P&paa(9bZdht{)s263))bGEckfgHX0clqSOkndDpvVX>U*3s~FW%c*Gp<-4hky*C!sQ^Q954axf1ReB zAO~#kd-EW^?_Ua#G_j5o19=Al+jdYZ{RK+5B==eoR9I$g$#BxIhfF}2LG-^*^(mmf z^aC8eZH)Qk--5$yPqSXV)%V3_T4p|aB8yBRdiRz#8f(Ye?*?+@|18@>r}C$458Gd| zJywz(m5i?SG9KlMtSvV?;m|dfBO6~yJX}~+n1wKTSVgPO*+GZr_ky^b7#j;X-qYdO z6szOTi71g8H{1HdSdW{*24VGJ3-}05u45$~b|-d_#9-oa3UQFL^MF`kM~N})|BQ^6 zfTvN9y!;fXtcY0SVb`N8HW9N|98LMF;P~ZG$&8nU44@?<7f&C%aTL<{LSXnh{w=!Y zGAq!DUB1{Kdr266OLah$gH+G=N`-;w=$+S5ATCmjT$|!T8;}3gzE40gItjS7MA++Q z_Ky(d&8xyTaF1e4qlfD`axOVUQF}b(i0)Plf4M4n=lqmuQzA3JVf1xFtm{3yFGjMh zW%5c;#j88J!|y7^G@_3|t||SY%L!y7ZZ3`Fo#q59>5i|f7XQM|^}6iREtYSYE^FZp zJ4PZ?-z5eUiij|8h^ehU6a7VRRgQy_7Jzz6U`tQ z1vENSQ&WQ_&y{~F7x#PO?x8kXZ%S(cd1E}SSKxqPK9GsMIm#xfs#PEqdDC$2=!bWA z4im4yDHo(kg6Q$J?Mr4Ozh<+9LBb)`%S8J&y|H;{e7$ex;F%*KXo`cx5Pu>I(%1Urt_5dii5nWDiQ3 z0r5RR#`fP=Xh4bj?+>E?cQv@5X*Q~AY5Q?t@&$|PJ0IM#j&}aParsIEXFY3S>+J=q zE9fw21ik%nVDh!i`ZJGjK$a=~MWE*P^sHy~TIzO2pe|tY^3y;7yFXzazS)$mpBDU> zx0Go5C85f0^>a(7(lyn&De!A9Jo?~vZohCGwUOtUUwFoQSDDdZoAjpICeokH%1^NOMk@UHe9ffjrK?E8D^! z`g68LqzhD`&^1aAAtk|DTsHQ)EP-as`Bk65_ZZ3Qzkpq6PA@wj^E-AS+HW<{G%e(B z_WJgas@1aV(6ko`DHV=lheirQJ^g3S*lo6zcgwA)vQHU1hqcNVOnmYHocHf{G6Q1$Lk$$a|^YW={F~kilxeAN*$>sD|Z(- zB|h8a#TyXX2TG2!dAmnSv!>W!=SG8R&>5xdAO;q72~y_PkP8p41CsKt>Qz00Qkhh4^hAh7*u9OJ)GoVxLZw!CP@UW4kivQXHr z`w!M$F!@kl)Q4fu6B#tB)9?q3xFsT*0084rr=KjsS_U*-KR1)8w5arS%R<4x zjH^r?yPc%MMBPlyy{x<1yssd-pxa|tw#TXOr=Z~o7`z%z@;x(2I=W|03FI_>lh-1+ zN~wMt7J(Hl%A5R)hrLe_U)in+oPV+OGsvny|A?*gQQ~(rfktm&u?KKHcqn&Z2<%B_ zOMT`e-Gfzr6u1sm8OX!8O$)%_U(9xa{6+VtlcEoCAzZeunr7|d)*1gaeo;d0f22nEd(-oA4yJ0S$ZQcoE?k zAD8LAQU%3t@ILXw2;z(WzR+;ZKeSc-=aEhkI)LQCo);Hm`sT@uSa=py+`(#vm;Fl@l5*izbBeS5Q# zWpt$=Ubm7vGBo9Wu}=MOMeO6;_^OR7dl*~6W3eEAUpF8LOwuIT?>&VYgf;B;Q~k{u z7Ll~Z!v?uCJ|BhHPBCG@Y!eAx`OAYFe^e|$7%J-LVzF4c_NCaY2U;EWE4JVVy4ttg z`f(g>mp=3i@$&|o(8CQjZ@GT6!KPL^pS31GAR4!gon&Kh+Nb5^N1l$%haAS_`J~3A zbqqR?D9%|-Nop!s%JTmGd+Xs!XCXa(RIIH7!xi=n3sd}iJ=kFUa&Env&;~M#!P3{m z+~>&!?pnn7lGD@Cfz*kT$4!?^J7V25G&D?1OaM6j1GGY4dg2S*@z}{`2un(|l55Fh z5CkkM*Vc>=jJi=dwlV#;9(e*^?qFIL>F?It{1AhnX*-FL_)Pgni*9VIx-F|kr$Ojb z>>V={Y{V`wQdZ>~LH!mLsdRS1!ZdEKLQeDe9@Eo=Z&>1b~T9~W>H{8wszSBTo$ zl}CIw<;IDxQphO_A3tQwyO>n0!Duw`ZzCg;ee#<7>*>?^lW2X7W<$p46nQ18ecVM?DuI0d0@iB?>wY0yEQyx)1{+#0nF zK?6$d-s>u8_R|btZm83{NZod=rs6K2I25d_W4V9n=LKnCy`q_Or6|qcHTdbe&`0~x zU25PA$j?`j#k^WP1k5G*1vki0GI%dUQc_YRQ$PFS;hj5&8me$?PY@g$8p^9e_wQqN zRoHJRj zJJ=RJpuPMgGM$c|o*yi?{8{*eGcq!^fkb*_`E(HOl1ebxU`;W9gZI3NM?^lFA%e$Amb&B>=I<|A9$y`t|V zf;DImC1317DvG>p|11%mDuy77?0X9EBoVGV!X^}SgFk}#EUiy1f>QeDi_WGals^h4 z0=Mr+))p=v0o%vsZq~E+hhEH7t*??;y%@pnF9X*Tb!=9k$jIIQaKqVEC4|%`RB^fP z0J*9Tt@5~T$PwrA#|hIV+M@FJ$z+}hnqGih%f89UhHxalj%HHAJT^_V$aAlKFnj|~ z7VI@M}h_ z{Xx9N6{WwB;u|`o_?&Ae40vn>@?MAPMB#DtXwle}aWyFMC$mLL+m4(8r|19HN@Xa( ztKhB%f`GRpt>>(ztlf4*0wKIN0tTm z{;5UkR?4oMkjviE0j7q6&+a8!QBin*YoeWJ;uRDMozJWB(AO8~vH#YLp|N8uUf)8H zCSlq8{^`*&q*?#o_FB8ZTI&tz2xVpE|EL>V$chi;(*E(_3@+!a&#rONLl#jus4XUVe%DU_HqGO7N4% zu%U@~vOjY78oetfY^vyW7b-^U+`oStm5`87Ud~5V5YT=ln)7$f8-$>X_YM0!WWGAX zg_9jrBUc~Y$9*gXI?kVgz_T9$=INrO!5UB+8e8H%@HXJ%!eSV{R29g(gW(!FV=D{a z-CE1d1s#hbWFgpX)4!53_qs9zt}rTM>~3nd`+N)1{fuSaW$-509|H#vA_SLRuo#X{zQ1su4HOy|5E`xGYR zNCe)>bPhU%{Xj^_Cyq2UY?yfKW1%3$CUeblfL>+U*qI^)?{bd270Mu(3|u_X>F{9; z^TF!tI?o;9FZ&DPi95#5a{~RDcXWtTb)esFK2Ao-)UzyaX~MI79pBS#uvZM;nQPOxxwAeZT&^Gy86J{lIxI~^tOHhxyx`rBDcGzOmA zkhKqI0G|V{B(FoM@MvYcPVZo+X9;XuZ;Pe4^X(n)8tioUlQC)*&C=^_pT^zP+h2_| z1*17|$^79g0?12bJl40tECT#Wg8=Ja%S+g0!aAS9cT?reHRu{KZ2LHj4%8UHDc+Sn#@o@$q7#NzB^b*%AE(h3tDD+ zB@4ofoHOS4hVk;8RqV|xCv6Ge^hPpk5|?j$2(f!mh#m|uCB(k_?UL3{{Exrzhz{%z z@gSG64a=m@?4z8e{tNHfrR9IsJJ z0Zh_)&>VIo@kZCy_-E>mU8;H2RHhs zr`AWF^wbPg8aMdkpk`_sC%EHs8$5Po^DCL|t9vf)bjegW2p4D%rVM*L&0Vx*r6F*Y zwJ7dUwR)fMBG+h+7?m5}K4M+oOMjkY$gF1dR`V+K-0)}^#r293%b|NquG8{ENAISU zmTtXKl51+qiDZGbE;^Dk>)moX9@QZ45to*QDRLAuuPOG}t)i?*OwTfA>MCsPn=vc6 zy$~y68fms>=VhbE&pyri<&*D%$0TJll0K2IW1rQY2wjQ?yh@^1EIih~c0LN)UX84A z16JYe>t66-&jSnvW>(TyQX==Do*RDQ_Ou~}U%o$&pz^2>FUA>F?y<80OUR%0=hVck zcC|Ha4RvI@$p@6)i1(F|)6%Vx8RSkWpKC09H|SDFIdR^pc|Q0)f1QS0&DeIlBjavx z2YH1A+f2x4;2p;0l1jc_5bG{tR%UA^rCw<7bpb-yOskaI&>>ArYjj0?1n z#kHsZVaaD<(KCH%!+JBXW^-i;bxTduIG{Trr#50wJZOenWog_Klv0D*`80L&r@Dk{ zgigW9rT2R9y*z!!Uwl3ry^ixYsiYLM;oEOk!yHI z^wt|KcaksEw8Lxo={>g8&e{^araFzFK6nvwQ~DJ(jr6+nCCYkcm&w;#+HQO;<^|KS zBX1s!>}B<5F)`E?v53BOoQ?C7@Z4F36)%~e_K-=q+x4Yp39)1>>|xoHD_Ol&H$Ba) z`hGgmAGS5S*tYVN*>afd@(}p%;5(mE8o2T+g+wH%D{44zv3 zaC&hXbB>v_3rouC>l8?#s#Ij>tuCGt!&kgZ;{T42jDfr=;pQBl=A|cGNL%a4~4!K_qu=kqp{qrS5H+s3WzYkgO=YqrTy94oit_L#@3OCQZQYXv5fTp@a}eYsyLIZD9#&N%H; zJOTUq7+<|q7I#bxdjj9J(+PZ3>vDVCIX8PXYA`!(QrsOiAwPx$PvR%5a*{iOuxVX5R z`=1fPp4(c?V!B)11syPS^J_IG%t~OezZ0e(Ci>K=&7C88UmZ~-e5T_LSPJ9TBJcdX zF+-$_hO9tZW)~&Dha+Dx#*q89Hc_jj>joy;!uetC^$LnAg;whG+rIM89r#-xXfIM;QF6_(A z3Uj=kKv;S$XfPe9O1d@fXRjK*TbpyOCwrqUW~_H-#H68WZO{Rg8!LlT9|POkG&`Uw z+I=%qX0k8 zEiM6*7bMMH#>dn?%l&F62Z!ajhU-jIAAc)B8s=U!l74h0a7uJnYg|~6{h>?jPH|UD zXka8|#nNSs8c~T3W~bXsEQ?!NcUc@!wVM2s2~0vRp7TM3Id6xaiJ)yKGMF8P#hqh> zqt+nqdABGxxC2Lu#!2qqaaYes>1CQXw-k^+<9yxfPHVK1uAeY4_>% zgZdU;)0_7quXh^?6;8jnfAgK&3~9bPe%?iTZa){%g}9~XBA%O56oQ*p`j_Zf!dI>@ zE$%qZQ?E$Q&*@X>YzGf+H?&;2F@1gYcA(sq^S&a4LNKS5xs1GvMs{TnyfK$wN>DAC zo0KM1EFQNppMAlZxWZ9}>6>}FMTQe{?(mhhMP}GJsU#yBE*AvjzTyk3om7)J>*7q& zLMq%~4qi?*ja#_h!>2F}1<>GlUHkg%gPN+{r$Ny2HOT(33=RGz9HOKW9aH)F+! zl;BE2=y7E{VJ9&ofulSUDBx-t3pjs4TxlIGhQ(qga7HzO&tdvhk{9HEGrk6Q@ z?0xG_!4#M1xDXkuyZBaeJw+bR?Z8kg^3vpU-j%cOoTlIJoDKOL+nXi59`<=g7(7MOb!HTF>b&qVeeO&O6YyR}DczCwrUBxSa9-I~sHyDV9d zF6i88ta#Cz;+lQ+YBYy4-jYY5n_geCIG}3-t_UXY?M)2f&Eylt;K5!>nC@~O|!tI`Ob9*9qH~9>tiBt%)eBUJyZap9BW&gnEpAdZWhU28* z9uNPm?t+!sXR*fFgGu}uT`3ggLn&)|3EJzBx-=Y?BOacLls(C$LIY^<0dscLp{L4l%jvRK5_(~lp^%F1wXaFS0c`i#)uGs!77XI$vj zQ}Sw}y>Py~Q1D=Hts$6#aO0|C5C)kzpSq()Ue4r0n6uhI!NjZHS+;#%!FRbWmOEJY z++6cTd#~lX(J$;b9IsY=5Wc14v=kV9Vd?U^fWYS96M{x@9oZr5c87AiIqa(7)+ko@ zo%Pn%R;lP7@<(nlEU8CV7%6;rS{_~KyM9T5NXhRQ$*SMi{E?lH`j$--_qy<^DK)QC zuFK*_p8s%6W=Fop#XCB=f}8x4HTZ*97mor&n*eu z@l;$sYb+9X-5Y-X)Tu18WV4ws@phXeWMr4^y;h=S(&OUdfZ61)GU0X!!i}6v~NPYG1=K+anK)&J)_- zNzAl9?vaWtyd2z)1rcuVrC2YLo5?@>YUl4WrJ`6kGz?1J(#;|=0@OtkX1@X)4S#cz zzNfwUF{v1Qq3+rj`{%DC<(2Q-e(cA=;C5db^)$!cF6Ee@s850G(S}WOEiIcdx8l33 zHWqWK0vE*ROrcp?gXUJ&D9_a(P7VE4F>F70yJ!p8qcT5Mr6!l>ZV8Kk>L_aIynfNWCL6Vg=`KO~UsCAmvS>r!)KuAdYMsMyI!B+rUu@t= zj>D!oL7|3rAo)xiIv2gpHvplb-%>) zzKh)7Ps~-%9~#zXyxgOd=%biVG(()(t>oBPKiYSmAIYEHEE@feQsK)m9cLfPE>lM( zf;h|1Vp!&_i|@RkgjRgcr<9e^^L%Id>pb|FS9FvpDt66r5@lY>E1Tf(KX)q@x#l~hU&dI^S!NOv_I9S$|A&(6n zMxjsyIZjrg(a~j&OT&QMybb@H|F|hM^8n-Q0>Sb2e)8nWT%34VKqo>y*RUJ4c1x?f z*w0W3noct()zbYb2=gYmN-YN#Uf6^jkk)Cx*iv9>gn4dmKSMsmlZWo^kE^X3%&wtCcYey=rcCkV&v%+_1pV8JbZBF1sEqXG&HJFxD8F;}1AvrY-G+QUXS z_p%m>9V&fWD_(ptEhgu+6+3iCUsAQPSrObEWET)%K!&~v4=)BFD2Y|2&2($@m4GQ| zc5d>O?ZS4U6uIXa=#L*i4i672WdOfJq3_Z1AxlCn=}@T~bPrvCapX~Szm(b|BI)o6 zTpP|^m)z4)V$NsAL|5VM^v4<)ir2y(;=Rue95yq~7|ZwVP~<1t^s`0XAydsTqF+?p z#cYnWw@59Ih=E_bdtwLfgihIwXx@a`MMk@PfkkCO3APu$2?Ev3Pjne~pd&rCNv_M6 zoET+J_m@2!g={qRpF@I8;;P`Vxr_5wJo1v zvDG);u+0oyO1{AT%IQE?-;L)IO!6$9e+EG^gjs$^MKhXkPt`s0=@~IS8eNs~fums! z#vJ?mn>8aP7Fr4luj}-qSszI_I5-#@ra$#0F*ad&&&Jr*+1c6EWmP(M0%^e&yN&jH zo7$oJ_^l)Ll{2yEJ5NJpM;9|Ak^ir!E02fri~7AuDwUM@iI62r#m|x~q3k;|lO@Gu zsTd(K7-J}9$s|jI!q}&2kY&su+q77Ik##0CmPz(y?8eUf%=CWV`{&H_x#zj(o^$TG z=bYz!`w#gVujhgpcEr236%<5y5w6lMS9i`m!v&~v^05%1eg8A(*a((NNsB$dY|P6` zn#Wl7cU&XBn|;J|Avmd-M52NqMto1)tr|S-#FK(5pV?e2kS+AkDWW!k-S6DJTuI%~ zb#OB}-aT$^*d$Kzo;DWUMk-rIe`cUe{rRC{TPNyUo$R0!b%p-uY)$?(|5)qp5Zep$ zvI;G6CDmFoyKSlV6Hz&(o-Vt)f)c~F0{BzmfXuBR6b1fFM|#|>FDmjjY-*t^IoALh z*)=fWUGZxe2-&jt+}<8s+aHhbqA<#Zy<=#uWG5~th<+ltu1M`9R9fP0$oFRlB&)Bl zX1sE^=mf80l^K@VdGIWsZ4FOea~w&3g$g~oRamf)lY+Rgs4{r4J57@z_*Png%I+Tx zhAGL({ny@()kSG#i+yXRYi>QTO~fC(BLDlLhsTe1stD9PhEa_MiyvQccmIAApt$6ge*yN2On#U z*qw#SH;U_oViuHD$Yio9^OO;WE^_XiAv;_vvDBWWqphvIo$g&SQsMT>SZW+D_RC8i zA}1~)GD_Hui{q<;1W_K3986fKGe-eyAyj-D=^8#2v(XXqv(WDEZ0pPcSx$GMmoN8C zc(?kN|C|?CXf+$ZB=7Xnc?p*itrZ3R`6>er$fH&kXE1iO^}J zh0lJhk|Y}lfM5{|_3M9MO&OFUv-AA@%)%?>rRh1+*wHL^RS#>dmeI{j*(YBQ50u`` z-RO9ubp?UJ+tFec%Y*P#0xn3OXOu?_0JMd6Lbl&RW;9a5S$Y1K3rGvbJf=1fl^%iJ zq$$g(r7roGTGC1tp!7G)YQ={?a$NogLkDts?(h@r132%Gd^$d8j{wOUs*ltb@zx8N zIF5745Sroj&9k1?eYYZIZhWSuuh5{dF=WSVgDpk&}u^hZ^^<#8y}x!=5FM3T$DRBSs`h-`2-A0xiTHL4M;jg(Xmeou z8PalDd7F;f+yF_^7{OV@7q4$@933`4sF~FyZ?fXqp;{xzIO->RoruH*t;OE-c<^AE zwpW{gABqT|r+|{`zPYKNJJ~NP!)?lxk5e4o&DKck6e6HJJ!7lb{*$y^DXrtVwhi;e zvQ|qoU7ieCp17SE8v?9Gch%aW5N46prwPy0DWF*otrB*h6cFm0CLCYWvI;G;2w3Pi zFvWqF=;h-h1nelm`3~4)a>;p~$;6pj@!xF>f)6^2Q^^ORO~^uj=Tj}mr@ltX#%qWU3_B#>+I;5;3PJV#^HlHrqbyp@zqrDrT%M z=*dDM+6&F0MN_6>W<0hDvkU+yd6j^Yy9e7}fp(OZ^ix^_a+YE*I54mpvTfyMkOj0V3q$1xHFtD7mX3~#3t=a?Zs_ORT3d+qYKk!sH%W^$Yq_x5 zrNEN+fy%@ZvL}{&!b@0#Oxd2V6(bA<6z^t>X^bUehA1x9lPjzNymA(iYi&^n3r|}OGR0}X$3LqN*^n( zD3u_ydl}%)%KO@LbpK%fJD1EDTk$B9qv2lPG3M8V@4yMWl>@$`f2Atk?Mi2lljYiYWZ$~C2xNHM^_(0lq;eR0svasUh#T-xI| zrJ#3lcTT+VMr|QIwB%;ke`nvn_@zoNbu5qkD}`!tTgL)<9XM=I`jOK-$uxqv`l2H18r`Ke&n8c9%(Fobq6PLLqJ>2pC^0gT_WVCGA8 z2tjwIrWb~!XLR4=gq`#P!r(5S%UQQ)5=Yz5~)e?~i0L)&Lgll{qK-ZuUO! zNs5Z6_2j{O{iZ*441Kue30uyKw~?yboRq-cB_!HYO5@p7QAuh29u8fh#Z`&uzl?6v7KD~R@lSUNf zR&6;xbM`4G_bo5eOBf=Uwro+Yq5p2}U~v*ZjJ>QYM{7j+>9LpF{@(9+4j(o=Q*fM9 z*kLMPZq0ONseQkfu7}1gmf5y0Oy|?|obK^U&6+X^$2mY{!29UMht5PQN4)p2&6LW2 zUDn`vhMuS{q|IptY$^=Qk$+OYT_RrdY>Z^ono&kqG)WWS*nt~`@6s9U%1vP8q8YTk zue=ez>NX<)#|{`0QB7VjJ^!6Q9nA@py@VA*PMHelSH|Kh&-i%Xa2ZW)d*FlW0OpPP8@)&cQv`Q4fz~%AsfJ~2?g=-jE_FCNb^yma^UvKL87_CxaOIWSg{QMx*$syoGcJ;;?GO`<{KA)FT`GHnth)Fk@}VT@5IvjszD}Ut7P1 zG3Cj=OtdcyaF}cF>3#3(G?~>)M4vOS^j^7h&ir=l`dn#c89xf-=SN zU5FWTY=GgiGv$t!6!&!ajyu76z15+h{_Ta83L&XgM=w*M4L6`YgR7Aoeuf$*0r$o` zms8ITxWS9DTeohZZSuER53kH%0ty`F3f*1Ji&RmwT)EAkm0dH$J^}k<*QiV-OC2o^k9T_?Rvm uY6*xmF1TImRWv^UiY#ZJET(6ZSNF9&&nAq2?faMgznPJ>VTt~|$o~aI&4CC2 literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/images/quick_search_result.png b/docs/doxygen-user/images/quick_search_result.png new file mode 100644 index 0000000000000000000000000000000000000000..c11d8ffa74670df75b01c3cabf42f9f8a933dd6c GIT binary patch literal 16844 zcmdtKXH=70*EUL(CPfiZdJzy5klwrWrhtM}1%wcKFQHcvDT;!0kS+oWgx--Ry+cBk z5-Fh*z)-$B=-&Hz_TJwa=ZrJnKkpCVPO`Gvob#I3oNF#3A809)64MZ4VPTP~s@&JX z!omiAaA*i|fOk&ybZ!BEuw8YO<*`co=~jUkc-C^7a#&bjVo6S(;RCOUoK&8;VquZB zVScbX9Sbb6u;iYp-j~z!GDW7XyV7@h=kIXbntQ`rB8p}+Gfw!X{>|k(>}^pQ?eqAL z#gkVl}SAVPW`tAK4|0_hMc)_;_ zDVXGN>gUO>GGV>mcL|u;l9-Wl?Z>{fge@v?3IVTeBhwofdeNz31c_EXc+9uh9cJrQ zkwlSE-@r{PNeIERla>jy(vB=Ma^@;iIZi&%!LHs41`!KWPc_nXhuy!&&l($a~ zsal?i$`vb+vbjFmC`bV1=wksRc^M&A$DJ8s{E!BRA^MWWhataP`1?n1eL`+#qH>-* zfNYiDVetOB$lVIGlpHxO&Z*ewiOp(x2H!Bc1Ks7FyEmh0R(!AF`l3)UEZ#~ESq`Hu zGGcgQduO6VFv#}0po(3<;uZAp=LKGc^Fk?3VP~5F)Goookmu2AEO&qp`m3G6wD8$a zL?1U~d0_Yelt)T-Y#I%oey=HsnXa?V9D#et;;S)t_>qSFjt0xQcBi#*1D+9l{K!gY z@7uQow`kh($crWMAO~c7=0&-OTM?N@4)EkA1HayDov~71PSQEZ`riAjH`&hn*z`@- zHi7uKZ9DD0WA^2RI(lR=Zi=zNUVF~oVYT%eHEevk;bfIN+sU8?U1%}9+a95@Q^I@d zInpc)@w8J?{_M=_a1^s-=Ze|hb|hHFA9S$0TtprnYg?l_efHTMx<#MyWFwp4Os0ZI zcj|2jurEUo(5c^_CntTF-3=6DJ|Jl)h4h?Fj5S)@S-p;--hgBGe4|#%7qCuoc>o!{ zdtDBl1NxpoWlQaF*J*LdSc%&mDkCJUJB#V+Sw+O77ag4wC4=ZkJqo22HFcG*`w2JH z^S+e679ZoU7$qx!O~g<pL|gZ^NiI>JjBTL1~ToblU@;S zP&q2OqjVl$o+cvQ`emM3_LZmZI^`biFZF3ajz8IPlbL?7w9t)?HQKt>)?+rg6+|AU zuoGJ0$n1XConAM(P1um_lUIAA(_~`I2GiAvA=9~6y}b1g^wIIGBg;~s{e2%wdQG1J zlcvh@3O+2}Db%35uK8eKjl$X`z0NPMM}7;H#KO!M56S>>L7TnrQTcMMf1(*GoFi349KoH&8_-@KRGaQV$9OZfmclq2T~O0$t#CW}JC8fY8r1zi{Iyd(n%%s-9 zrMxo7me-MO)J*apAj!R@I|QEKm)1z7I^I9QmmPyO`;5n?kV7NT#24*RH{Iv`06mT4A0J#1VJ(!=@L=` zc1Z}6%v7BYz31Lm`RKT?44bmbe~5rlUZo#RkNc5{3casXlZ3_jYgw+P3uL)#likPJlJM|lHxpOjlRp@-eqNFeFvE(C}eP6#qQDo zH@$c|O#3_#ri{-Y594L|AeEqJS>R{KBv5#o>Lvr(Y8ddaZP76rxdGNDCgc||L}rzq zRTgK*^j8(19;{D%`N$GNrGqYzwNeVG$}n2A0+mJ;M4Y5$pe=&%hdwoqN(;v&v5ot? zxa_Wt6?u)8=G}y4=T!OE9Hx{PJ+?_J`hxTu3FpQA(D2E_uJ@$fEd06I&*9x=ZD@3~ zY6||d4kItKRd=Qz>PQRRq$iJ&St6)p}P{6Imncr1ix~7%UhJZZ>ZkAB|6;Omqm zDEnwqXMLx!WxwzyX$XiQ#L=R2^vT7(_2dI%ERY8rHKI2mCp(=P-YZFCMfy?4RYeW2 z#W+P9Xl)o94FqK?7_|f}-=aItvY4Y65Iw^F$1MrZ=e)KW_Ma5C5b0$Fkp%lx2)2PV z!GbrH?+J+_IyCxq(37%O3EGew{^zHMGCzN0^VV;-7L_*E+KlAc!z=BltFIhr4oZKP zcC8W-+O1?fSkd$kG4jP?PfJhV)Ep^aF7li^9pjdga$oeU^v!)3ZEM{0ya!=guv!#- z;4nA*W}g^s9yTh?6_@n90X{JtTGw!TpZC4v((~C~b%BXSlpg7a(?Y?SE!mpV`jHtE z5q~tYlXk4S>9qIt9Nox4}*GhP*H^J zY01grkL^CogBhtV6#WHCIf@h-OKQmuHM@Urb!o{G@}=dMVicaTu)r8Og)h`~d9bm# z*d7Uk%4qCmGoGq2(4U`eo%rgeVV@Sz3uzMC{?>;*JoZ5c~ShdyEVW^fWXK zbab^&3+85XQ-^ahKa0#xKZ?RAbu_+Zo$d|DY=t(tJiofNva{LPbna_5S2H`}H#5_K zRE|Z$8cMSlk6dNEN=i`T*O?#TW!S$)7h=Hv*8ZUI=X>3*+)IJL#C?{kMxk*b1{F>I zryac;LSlKbr#({K+=r5Wt<+n`i7`p!#<&cUYHrijJL^%2;c5dp?7XXxT8WYhcSkJ@+KcjmU+SEsUVrAs7+ zDtvu4J3keh9p9V1QBfgTzd1=mY6Xqr#2y)r?hWZ2W*j^_35)956IYc+#*eUZr@k~m zuReJ}9=$FphoWkc)*KJGX5ut3g48z)sD8dI4sWdFC1+6gT^c%D+ovVx5aAZ3f}&_b z711uBz**&yzBkRIwybhyI9}V;tevF+r|Us`)Nu$`8^&u3dV(;sI)AEJn8mlB)Bf(4 zoYBb;22+cdTdd$3>%ND&oTM7j_A4;!wV6P`(X}jglm^VmJcT;OEhVv}zBEB|?#0YZ z!xfT{cVsP)`QuZE>Z9q(%rV9=xMX5ZnC=(Qc?)!1cYB;6z5^k&rLAeZBjjr?(yre} z61b2aq5q-f^&vFLHiNi=+Yp`QwQcYdHv>74PAi*k9dAr|?US#NilHb~6snxjIOVH; ze~9uj8EDS4lF>U|pnKsO6w#7BO#pTLbh-h(6R+>RT`=ps*byz@hloRn#nWlEe>m;k zAV@6L*qMLWu=q2YHh&;hF&&}dBC3e=-oAY(hol7uq{gX*NKSyH=k@Nw%s6stjq5x} zy-UK51A)nxUSS@>%{L!1?PSUMz%(HA4daxFFCONWD6djGz4V00f5h4~&Zc|t*TX}(2##mi)54pP2lr_+-=L0>#$ z56)ccVZHqN;3 zA-$uw0mVy44FUkd?-yq&P8+fJx$Tc$s5Ly>FgJwiN>f2CSKnZnFsx-kG@(Z3Ht%8WVwZUBQc76ZT^rne!bU@z!9C zYl5t~Sl&gi+r_pvkAwAeAmqLY8Sn*6m(*syJ1qo^CyO#?i)JU^sYM|Gsi;78w2#tm zysEgAy`GZfW8@ZIG`RW=&$>19zK2#*+@`hu0^`AC^zuMNTKtHdfDTz)70r{> zg{zh?M`uGgpF#!9yp9$knhqPbPTOQo+JNq5zS|X}i0@-NvEAXmceSs$0rgFf3rhBZ z9TbFeXH#tW)S0S5wf`yRN({RzexHh}>@e6e3q>-*(CkYnBP!~(IbAb{ZkU5ZZ70f} z_cWfLZfP41YDidq@v%_c)Kj3zSyt!ph-*p%6MBZ7gYuMpv>7g0- z2?N7Fba?8VzCytW@$y4{T59N0kv+XLOj$cpS(mPmx|Xn$KAuRvRHGCG;Q0XVbO*vi z0}mndb11g;c)Q_{7HmGpNSwO-Z2{1`u5^u*Fc}8b(k=9mLPo?!bi_fY8 z&{Y{;--&ujwj&w(7jc_>k0!`MuU!H<9yRSMl=AH`zQ_2qWN7GNJziiTr)Yq;@AQek zX+VGs3`8JLjQa}%$n#{)IzQu|laY~{)w|hPT3R|eQIz7tlH*u{06QZ9SS=UE;Qr;| z547>B`U7JA#2VG_HtdIS>Z9_j)5YqVvjTs=6BfuTcjsd-k*C;(M1oBr9m( z$ZoL9>P~Z2<-{!WUZ>-VE@Z%M%A=n&k0_#YGOMCr&^2s(3v7S>If;?|5mP%sQsKj3 zkeR+ec#8m-tMoFtJ9#4 zg3CrpAUd#iAG1ce&d%)N7v`WCj_%4-wMCos612~XO^jLQ<8|Mg784=jm&Czf5AH#vz^45ROk`agr=z9(NGh-z@%tePWRUl-JgsBv|m z1K_Rf0lFS$;d`a-ngjwik+}4hj2iE{=!Qs-CRG_ye(MWyz{|IIfatD+o3M~!!yBpV zwr{NR*rl>)n}t#IZQkXxBR_XB5{aXGfc6fAA5F(ZR=e_W(s#K=55p$HV35pH%%(2m zd6!&)QoDf3Gx(@`3Etbjl|a1N{Ut%boChcP>=ycKa<2VC+*$X z*x;WJ4G|c12{s-B41P8w(vnw7G6rMHT)#)L!ss0LFMW`@iF{6?iLq*Iz^X&V`~@;# zki4EMR|g&8(Cqwt0o+>oqr}S9)i$1098T#jjp(cQ(Y3p}5bXjfX}ASlR!zMAjraTxN0{b2W?6teUMl>aDOfdf7ePv;A`WVAAvm`b^oH+j_PJU#nkrA#P z^=2+D0kE#SqrePqn0@8x>#vC%ZtTDZjI?S1W27g;8(b%xI#+?6cv5hRQ$ei;buIZ& z{yup>)By`VDMCWf@1womLjdfBEMP4NY==cx3HI-R+}r@Uso9!Q19L|SU?fEOH;jG= zY~r{70^~4^J^f#B_fJUvp9SDmkNyMhwk&Rs+r^*mhHx3Xl?YWTl^qPcGQlR^ z0rHkd?o2T(5-QG!iGZj}iIW!LB&6@H~87K zFTdsBtG9T!fY#uqdr0iwi0u#n#t-3+5q~R|xR%r-C%WCMnLal^ zYkK1FG0q;5Xr;fP{rU~C+gdb}*t!b`pzocp~z)bc(ELOG=hOH{+c?HpvCNF2F zr}M2fuDI6ysp~&Pfoy0m2Bzh@D>Bm4k59FI&6nHAWl%q|T~5|L8_PGL6IyBFnt!x9 zt}r|3VwFL!YMW_$9nN@?(}~_IMbTWjD&_RjarZZhH;Wwq#)TKlnvF3+9DhRje2OnQ z`3OMU9J;(d)xX<3$G!-v6LG3ct{-+sQ(|-jQD283(NRT8Z9nmM zh@h=|PflFs>Tuh;poDextDg%iK87UUiE!xR zOBGp|3yRdfq5~*3zG@K$oi!j>d(0eErMu0LdrGY|gX!qOUnHJ)*7IQdy`YyIAMY@0+Pc))~`-3zyH{+VrKl7UA>jX5-Bw_rSbJXAHs<0aUJ< z89cG*Obc>GOs@s~yCYce>{ncO@-ZRnDrx17mOwnF_=h`C`|MhRZO>mmK0eu4CNCiS zG1D5VJgIL*p8Q%qIA;taq&%s>V+#MzcL8GewLh@;cCdXrFeSqO@CH2qWTg4I%!nKt z_qb92LB>zak+p!ISNY4&m%Elw=?p#zZj{sXA#xxDe0>iY^4p73!kqU2J`>*}81-kE z7nZKFV8M_f3<0P>xo4pmKr$F6EC!&%ePmcFtj)R?e-C?wW*It|Eb0({rC)Lfh2bamcx#cx`N5*8oPK+YGjDe2|*v zN-5?ne)2UV5S%n~5pKmqKAZk=;qtHlm&;d#iHVpb*)SVK09}0o7X4?m6(@nHZFVQa z!C3MF#*(uR-~W@3|I8U41u(nM-a;xuG?i^#s<;#r^Wb!2Lo*1fqX&@hUjTe;7!%(V z`(0>MJ+%@9zvM&QQJ3NTYLj+lxhGulK+w;;C1}y+phn?j{h^;_V@hT*AER! zq4&vCzt8+L)}F;^khk0)Ik>V!1_ot}wov^Z<$_LL|HbpuU!lsv*fD_vhSayxH6;P2 zIg$$pm>K7z)soewoT`v@4ItZv!E6G;lp0YJCPhp%^|x5s6ESVco3_vYHknHg%3U2qA0l%K zhNXc|wJuU2dzq#!~c!a02qWXjurC#s|AY(FSuXBCqqX&>4w=!G*L)c`}pdp z{fW=S04sSU_~^M|UW6xNgUN?SK>dGRM4g@->V$1YtWS835^l)+tX?bo@)E|m4mFmX zjLuU2Cei>uH4y`!xLwj)RTaSf7rPCc5u4bY71Gx9c~&s&TvO+8Ap2Ls)bo-!Lj<+F;N|HgR4V&GDYe; zs^kmx@aD@l3hHGLWDUauD0wb1tm$O82RP897;bE1yvZQkBs>C#)YjGI7okVH<4riU zQYAe$zMh@h^ET`c+k?sPT>+3{sK3S=d>LTxB}R%ECHa_n+USRVD!sYn%II{K<~dvQ z9Jv!Ne-Yw;OBG_@#|1*ctcy@kxnfljPjE<9MS( z#Fy@kUH$6teY1Wmc9S18$jNwNfM*6fsR1++bSydNJn5t`nrV3~AL z=&6kCG1TQvqbTf$J>YOPs1dnD?}vN7YpIg9t0+@;*?t}QEuO7gm0}aea*g(rQ@V4j zqJf<{eff%N531*_U*aRj@rp-wDLGrdw&RzWPB*Bo{)N_-0OX@K9$DNs$XS;wC8-Frh%H68xi6r z6Mp3xp%mh`tyC*FIEavYK92aHUQr?kC+1YLYV%rkq_}`{ z2vxsGOghD|*$dmG)nGY(Gp{~T_d|A6duXbyFg+#Q*7U|p!as9hi%*v@YlCg5Y}OYd zLG~*%WvC;Olb2T+gg@pzZl?gxq#M^o8;#96ytg)J?ytGN6gC$me0LJ>BWq2!$sFFZ29aZS zev2s&!XTpMw$N16BF%%!(>K^<2weVrJ3iz5n=IwK$wDcgTZj>~9B7wiXYMv0;SQz+ zYKL>)xP8&WCw4<=+MV9x8|H$9%G`GWJp~f)*f0_jAWcBQ{*wC^r&WC6V+dtRj?mG$ zOTR}&wIwPK|Jks>UcVH`HX?biGa^Y>b%>O^NNs&h8!Z%3p4ayIxd+{~1K*#v+Xt)d za8d7g$3cNr=d$u!J@?%6mZt~69)14cxi5nN?f8rCw}Jm3>3%jZCP`c6rV){d)eNwM z9RMCKPEt+i|GzTBmx^d?c`i~QumBEuf2aR|^WSUG!&KnhYh$Y1>l#H!V`Jk;{=oh| z>YMYL9yu2jmpKezC_}!@oZu}n*Ogb8Gnm&M5{R?y;!ey%lq4W9iXv5{C-C zOZqo!he{x)Hk8x||6nYR7C*+{>gPfD*0rYa%Q-s#2%yx5U&ecag<8BjI@A2L! zP}LvxUVK{j{)>H1JcxQVUhrN+@2(&_)ulfUN^#7A<8!I)-f`fDD@T(vWa5U$h+c)0W8E+4t{0qSC(N-iQ20oS$x7VV4E{_A ztdt-FIW&{RJGz;gGCW(m_LU^cbaCm*K}nEIk*CcKsJFHSfb1hDCZ1Zs#WdeU`6UP>+}d21@>|irZ z$oQXBSF!^cg#n>tazIqSLf?zlzhb5T(t4ok;o@kj(4eA|DU<>WQ|;itoe(h_T5dhC z34^ZZCYt_iT&@X((a3+pNqwmZsiWR(6%9C(OzpI0M&I!X)x*P7X#7ZzCn36lAdoo< zI;E1zK7DuRtXb0IqaGVML#`;Mwt_rQj+V%BfKF144{t>3{A5p8caR}HMW&S_2dBBP z07{M6YC89qRP%cQiZznD#6U~Sc=P6ROEwNc>Pvly^I03?*>-lUw0J%$-eQr`t1LV` z94Po`#?EIMGfh1ojF5Jl^E+(VZY6OA#SX}4XIprc@tDvo{pvsuB8UbG>dNe<$~qYX zPR8`j(i9-|0zcLz;4?F>-@mFdNOCy=v)Dpm3g*&*Vm8eb;l3;>Q+fz+cow1o@DP0Qfxi#AvqKr=_N>zt)Wr7{Yx^EK1J%XuF53GNii1aQxJkM! zbpf?V1DJ{7udj>4**s zgvI9t7F{nz1?*>>_cS%u9&U7vz3wKT4r1YQR-;`G^o01asHiqSiqc2R94!U>?9Nl* z;5AP0JaPf%l{&0MNg2m&Y2JEL;;)hklvct%F281^MP_JzD-vEKkxTS8hOU_w-e(pg zsleOEXVnb~u&*Iz)_=60eX;TUqsQ!Qg%1p}pP<-U5S5QO zxJ93??|?H~;mWTk>+j#fUh9)lb(|X}eTs9pIpHNxuufkM-T|LS0c9oRXFr;x&tWme zX$twuCEsNOFBb9S>q%`tZoc1|Ny8)kD6Pe%q0xNYJKkL%MUW&RkX5YPAiY}!eybO2 z3&)3&$rGt1Db>8wa@pppG^Q|MvP|*jNt5JCz$oz60ZuRsA`L>Hp3(r z7o-c+gX5GKj>2%K_hK2OYT?22^DQCTk~?^KXka*a>WTz1I{k;uYsb~)2~%98z4Sp3 zNe~m_%KR75g=$@|LqSu;T&?<&g`Do@ADf~T!!M+%gj^3?{dzKY6g@ZivE#vS0cN8P zT3*4q4IM5+Cpd!N;u#Bp1L^?nYY&Abl3l#`t7R3*uJo=#2BFSXz>9O-;`#j>K%Kq$ z76rhz!cd_U1c2iIQvbhxzg#x*`E{lPinU%|39DO&<>cUlcllUt-E$3yX09_g6Ka$7%CXqw-k(sb3R&0fhXMbI|2164)-xcoLUAw2JOp2{vd~ zJk={Mym>y(-Z$t(ihzxm&Nc4!Mblg8bL?t9tF;s5_c&cU`nn%YJFee6C?S}XV{EeD=;+qdJqqspl(Qe!N)>Sa;h@EXWs zM@*I~rvsLwhsLC}4(#q#164PmQqhBSk&xAX&bV?T#h#VbJxUDNYhj7qE?!13pM}lI zAg`vA`XVB0lcsqJcYUJDWDP#^(+d4#Z0GeAT>!KF)_W4{IcP@hL?!uMKy5#9t(BW2 z?TgRuYN<`bxbh!nSx;=uHq1^WeH(u)ke>FvImq6h!o(N{v;85u{U38G6)Jkq+09eN z&NzZqmGx>}Dm9U!Pjq$PdFO%d#jw@O?n)j)n>Z6M4eX9rw&gk5gFg*O1+5S%TAWwF zt*f8$*=+Fy75185Dq}-U3+`GFnTWI2>KcB&%>Gs96 z;!61)8j9=TsG+2(brDC@mW}<)h{We6t>`iT)1L~gjK(0Nx!QxHAyplE-RTm@nERQdO)n6g`oY+R(G+j zFp6Ku!`Ie@XoHY`ag}d}A{(`Ba3k2x1)sjBacB1FomYO|AQjrX=nnr^Aj1zjJ;Nr1 z??$+KdcJRm@Y;$=B;V|R)Ocw6ASAu^6->DHb0XuYY(ZT(^HF6ZvK49@pWRtWv&EsT zoxL*nLv#AUXTn>Z;n}##!lOkGkvm5nK_n9n{ifqJz3(@L%N9-}lZYgGvL0%f6I7YN z!kxx=$fr8$21r@*`4n|ROefg+QZ5sIwrd!^nJRPhkjBPLU)WWGykelD@Fs^uYdxu~ zC_ZTP?vQCC_^{sHL1+Mh>hHGA?siR=Y|DhYcP5e7?x?|FwMDj06!aWcT8`u|6RoW| zwtESvpHE9J5S+3V+@!n{sfAu$U7Zb0=Qe9VZ@gd zGL17(MY^l;`tlPAgEbs{ZADMUoHR+ZJ-pfk zM(><)cN=v>v zVc^W*PFM+#-iQ9`7i5OEM1Y##Tq_46$h3AmD+=0MduusRA*-9>iMD%FopZwNIWp$J z(oK+XGl;$Z+(zFW1d`AMEwV^@vU<8cUDq<+hbG{eqZ5CSe<_l`Vvc>;Y3sGg=IO%9 zsR?>++-qi~ue3BEz}(~La(Lyt(x&odA!M0n2r?L(oOEDTvPpk!X!sLX- z?q$@sPxY#44)Kx^PBW-O?e8#~9VoIh!diPVyY5bm?{>=$x1k$#uIJqKLTuESm=LhY zG?ad+sZ`Qn&GDDIO&>c~YwJ7^4m#MOdi{$ghrd^Vde5nhV4t|qC=V8{`Q%1d60@)^ zWTY;4IXmEV!of7jZ6r@w09f*{p-KYtd_BE(VN>`>TX}g~{*3;F*u7_)OXv_0NFtit z(a+xGqZQU3ET*ABVR+`+!6&h3{Y%eK{L=bsM>=8x!jDJV$!~KD6yX?DRu+r9nla6T zgE=U)_no$g4nJjw?iI3#kDFezH*f5o;Pq?e%O3FugVzjne|dWkH_7v)gaOIYFrOu3 zh0YdFGn2#F`Tbm7dirQ^0>fP+fIU1Jy+t%t&gWe|SnGT^wUa`L*bl|O6ZsIGt;q&> z*yOL|(#40UgaqyL%mx$N_16KUNRto&fe@1x5L8J0GT?-2*X}C45&ST4@;K(+Y+_?| z!AmW4cXu}`FBqF|!~9ih2Fs^~gqE@cHT|?;W2R5Hl#sE4?;eO18@jyw9By>!LP*3e zL7(3kR3IKM{Rg7~E?z~j-tsXYI?HG}pl6Wunh(^yVpEDQ|NRmWWXgZX`TzSn;o2>3 z9XBVZ?cw3zn|!=h5tzhFF78c|)9{E0fx;BZec)0_EB9f0{d;=MSP5iH_xQ!)tB&P5 zjm8-=S^xOLq0x>98NW9Jzp${!*@?F#xF%feC-EQOxy4cc4zC1Xx(b?fzT<;NA>C(` zmYbVpvvq%jH4>0Be1gk1ZvX)gWtrQ2wh+*^A4`7@xcyZ%>$5)NIp>E)R?*hXs6Zxz zOn*dOxu?atHw4TEoyYh44rFfP{p;V3U{t#SxS$%n-W1%yv*{YwH_gnS-2mRO?a>)S zU*~~b+t?VTv>CiSL(3U`(GQtY5&ZHDa9=?`fpzHdr?J+rq1_FaCQ65Lb5T_T)vsqI zBMnUr6PO4xPp8VQnJv4zx(M(3A<{gbZfwrfX{U+?X^gwtWMb7WKJ2j>B=O_J>1YAn zpk!UI!E*yP()3BAZ#yxhodO<3knG#tUH=|Iu`-i|a3XTSf*l?l6b4_n6uKXl3naI` zHUQ~&j*Rsi1*{?~>QTN`QM@@OtmPa8lZFBVl{k=2$H*-KVyk>q*8&o6831A4l9pu|g-+0Mjb1Jlu)z5t}d1%^vgHv~0w>vQSlK|a= z{E{acZ<@tNhvSdmT&WJ5+Eb5CgGu|9y-IHHQl-3=ztR`fJAs3QKp^e1&w3KLcW3`P=n$nahc!HRlkw!v5YRZ#MO69y7_ezJnZG+M$LVZg%x1toX4Pv z_+vF{HQn)%X*#ETJkZ?82%?qSng0hQko4an9r5Fw} z05-!`-7J0niE9Oqrn<<0wu|uDkS95fXzS>3vc?=f_G9V{%J}$rU2IUMrm_-Xq(k(vhLtnW zGHnx*uOKP{#cJ#*+cIObH;MQ=3tbp{d~wS*okgi`Gki zQ}g4cuLOr{ciW}NXySf&;oHpIzI;nOsdryj6Pg8;o+HIihvgdDZwax#d7b$FsnpL$ zmW{(~>;h1uUi+0mdW|3yhjw~s#!aWS$uAa8kNnqoRwja{F-j>$>eH=##z&^q_TGmD z3nOV>MiTe79Q*A>kPAT&DlIMT<5QYc_Z7#fGDX`FrmCTa*Qm|9kmytbjOzLwcNO|M zl}|u8YZhhEi=DAW!9tmGo}P!Z4G=gS4&@?sp`D?%iM;@fn8#y*o4;^M7Bc{aHn&&3vm-lbRRkBNybtL#VwDjmXUdG$Uqz)QVLC*)N- z>G{d=o}Zszz9Ug}=EX=<{>%Ye*=&KWdOicKKTzJ|9MIH&n8z2QMCF{ zQOr#xf0M)VL-3HxR=GQvkdt|67Rh}4F}kq1%=ytS*VDk2ZZX#n4y4SvA+Uk$hS~A> zS|GuxrrZm=6SVJMp`BK&ub{H};>^L(Va|01xPMs4@H#+!;I`dY$0r9?PUoe^38v;g u_N2^gw_rfw_m2F(&8@52d+!|gBk9yqs5#wx;6E^6sVZvSFOh#5{C@y&5dMt- literal 0 HcmV?d00001 diff --git a/docs/doxygen-user/main.dox b/docs/doxygen-user/main.dox index a72256c7f6..f03ad68041 100644 --- a/docs/doxygen-user/main.dox +++ b/docs/doxygen-user/main.dox @@ -39,6 +39,7 @@ The following topics are available here: - \subpage tree_viewer_page - \subpage result_viewer_page - \subpage content_viewer_page + - \subpage quick_search - \subpage image_gallery_page - \subpage file_search_page - \subpage ad_hoc_keyword_search_page diff --git a/docs/doxygen-user/quick_search.dox b/docs/doxygen-user/quick_search.dox index ebb815d9c4..312572ec28 100644 --- a/docs/doxygen-user/quick_search.dox +++ b/docs/doxygen-user/quick_search.dox @@ -1,25 +1,23 @@ /*! \page quick_search Quick Search -Where it can be used -======== +How to use it +----- +In order to use the search you need to select any item in the area you wish to search, and start typing. If quick search is available in the area you have selected a search field will appear in the bottom left hand corner of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field. +\image html quick_search_result.PNG -- The tree view (at the left of the main window) -- The table view (in the results listing) -- The open multi user case panel -- The Auto Ingest Dashboard’s three job information panels +Configuration +----- +By default the search will match against the data in all fields which are currently visible in the selected area. The search will also ignore case by default. If you want to change either of these default behaviors you can click the magnifying glass with the down arrow icon and configure which columns will be searched as well as if the search should ignore case. +\image html quick_search_configuration.PNG + +Where it can be used +----- +- The tree view +- The table view +- The open multi-user case panel +- The Auto Ingest Dashboard’s tables - The Timeline tool’s table view - The Communication tool’s browse panel - The Communication tool’s message panel -How to use it -======= - -In order to use the search you need to select any item in the area you wish to search, and start typing what you are searching for. If the area you have selected is a search-able area a search field will appear at the bottom of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. - - -Configuration -====== -By default the search will match against the data in all fields which are currently visible in that area. By default the search will also ignore case. If you want to change either of these default behaviors you can click the spyglass with down area icon and configure which columns will be searched as well as if the search should ignore case. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field. - - */ From 4ddec9aab3ab9260e5d67ac6a1aeb04bdc5ed8a3 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 20 Apr 2018 14:40:20 -0400 Subject: [PATCH 061/100] 3744 fix case to cases typo --- docs/doxygen-user/case_management.dox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doxygen-user/case_management.dox b/docs/doxygen-user/case_management.dox index a8b550b989..48e2fbc2fa 100644 --- a/docs/doxygen-user/case_management.dox +++ b/docs/doxygen-user/case_management.dox @@ -34,7 +34,7 @@ To open a case, either: "Open Recent Case" will always bring up a screen allowing you to select one of the recently opened cases. "Open Case" will do one of two things; - If multi-user cases are not enabled, it will bring up a file chooser that can be used to browse to the ".aut" file in the case directory of the desired case -- If multi-user case are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The multi-user case selection screen has a \ref quick_search feature which can be used to quickly find a case in the table. The following shows the multi-user case selection screen: +- If multi-user cases are enabled, it will bring up the multi-user case selection screen. This uses the coordination services to find a list of multi-user cases. If needed, the "Open Single-User Case" button can be used to bring up the normal file chooser. The multi-user case selection screen has a \ref quick_search feature which can be used to quickly find a case in the table. The following shows the multi-user case selection screen: \image html multi_user_case_select.png From 1d05fdf688b8a3abe0f8022b3b7b794eb0069b5b Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 20 Apr 2018 14:45:45 -0400 Subject: [PATCH 062/100] Added database query metric --- .../EnterpriseHealthMonitor.java | 86 +++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 1b3ea2ab52..46c71ec4ee 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -28,6 +28,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; +import java.util.List; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.ExecutorService; @@ -38,6 +39,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import org.apache.commons.dbcp2.BasicDataSource; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferencesException; @@ -46,6 +48,9 @@ import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.ThreadUtils; import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.SleuthkitCase; + /** * Class for recording data on the health of the system. @@ -283,15 +288,16 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * This method is safe to call regardless of whether the Enterprise Health * Monitor is enabled. * @param metric The TimingMetric object obtained from getTimingMetric() - * @param count The number to divide the time by (a zero here will be treated as a one) + * @param normalization The number to divide the time by (a zero here will be treated as a one) */ - public static void submitNormalizedTimingMetric(TimingMetric metric, long count) { + public static void submitNormalizedTimingMetric(TimingMetric metric, long normalization) { if(isEnabled.get() && (metric != null)) { metric.stopTiming(); try { - System.out.println("### duration: " + metric.getDuration() + " count: " + count); - metric.normalize(count); - getInstance().addTimingMetric(metric); // TODO - make new method using count + System.out.print("### " + metric.getName() + " : " + normalization + "," + metric.getDuration() + ","); + metric.normalize(normalization); + System.out.println(metric.getDuration()); + getInstance().addTimingMetric(metric); } catch (HealthMonitorException ex) { // We don't want calling methods to have to check for exceptions, so just log it logger.log(Level.SEVERE, "Error adding timing metric", ex); @@ -322,12 +328,70 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } + /** + * Time a database query. + * Database queries are hard to test in normal processing because the time + * is so dependent on the size of the tables being queried. We use getImages here + * because every table it queries is the same size (one entry for each image) so + * we a) know the size of the tables and b) can use that table size to do + * normalization. + * @throws HealthMonitorException + */ + private void performDatabaseQuery() throws HealthMonitorException { + try { + SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query (normalized based on number of images)"); + List images = skCase.getImages(); + + // Through testing we found that this normalization gives us fairly + // consistent results for different numbers of data sources. + long normalization = images.size(); + if (images.isEmpty()) { + normalization += 2; + } else if (images.size() == 1){ + normalization += 3; + } else if (images.size() < 10) { + normalization += 5; + } else { + normalization += 7; + } + + EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization); + /*metric.stopTiming(); + + // TEMP TEMP TEMP + synchronized(this) { + if(timingInfoMap.containsKey(metric.getName())) { + //timingInfoMap.get(metric.getName()).addMetric(metric); + } else { + TimingInfo info = new TimingInfo(metric); + info.max = images.size(); + timingInfoMap.put(metric.getName(), info); + } + }*/ + + } catch (NoCurrentCaseException ex) { + // If there's no case open, we just can't do the metrics + } catch (Exception ex) { + //bleh + } + } + + /** + * Collect metrics at a scheduled time. + * @throws HealthMonitorException + */ + private void gatherTimerBasedMetrics() throws HealthMonitorException { + // Time a database query + //performDatabaseQuery(); // TEMP TEMP put back + } + /** * Write the collected metrics to the database. * @throws HealthMonitorException */ private void writeCurrentStateToDatabase() throws HealthMonitorException { - + Map timingMapCopy; // Do as little as possible within the synchronized block since it will @@ -668,6 +732,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { @Override public void run() { try { + getInstance().gatherTimerBasedMetrics(); getInstance().writeCurrentStateToDatabase(); } catch (HealthMonitorException ex) { logger.log(Level.SEVERE, "Error writing current metrics to database", ex); //NON-NLS @@ -675,12 +740,21 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } + public static void tempDoTimer() { + try { + getInstance().healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask()); + } catch (Exception ex) { + + } + } + @Override public void propertyChange(PropertyChangeEvent evt) { switch (Case.Events.valueOf(evt.getPropertyName())) { case CURRENT_CASE: + case DATA_SOURCE_ADDED: // TEMP TEMP if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { // When a case is closed, write the current metrics to the database healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask()); From f2044b9550895a498135f963842421891abd2c9b Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 20 Apr 2018 16:04:22 -0400 Subject: [PATCH 063/100] 3744 add links to documentation for sections which use Quick Search --- docs/doxygen-user/quick_search.dox | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/doxygen-user/quick_search.dox b/docs/doxygen-user/quick_search.dox index 312572ec28..4783f07b9d 100644 --- a/docs/doxygen-user/quick_search.dox +++ b/docs/doxygen-user/quick_search.dox @@ -12,12 +12,11 @@ By default the search will match against the data in all fields which are curren Where it can be used ----- -- The tree view -- The table view -- The open multi-user case panel -- The Auto Ingest Dashboard’s tables -- The Timeline tool’s table view -- The Communication tool’s browse panel -- The Communication tool’s message panel +- The \ref tree_viewer_page "tree viewer" +- The \ref result_viewer_page "table view" +- The \ref case_open "open multi-user case panel" +- The \ref timeline_page tool’s table view +- The \ref communications_page "Communication Visualization Tool's" browse panel +- The \ref communications_page "Communication Visualization Tool's" message panel */ From e86127039a64a1f68c04cca7dd27f0dd52bd4248 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 20 Apr 2018 16:43:35 -0400 Subject: [PATCH 064/100] Test not normalizing the hash metric --- .../autopsy/healthmonitor/EnterpriseHealthMonitor.java | 2 +- .../autopsy/modules/hashdatabase/HashDbIngestModule.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 46c71ec4ee..3d91bff41a 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -65,7 +65,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; private final static String MODULE_NAME = "EnterpriseHealthMonitor"; private final static String IS_ENABLED_KEY = "is_enabled"; - private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes + private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 0); diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index aaa721cc0d..5f9e3808fc 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -184,10 +184,12 @@ public class HashDbIngestModule implements FileIngestModule { String md5Hash = file.getMd5Hash(); if (md5Hash == null || md5Hash.isEmpty()) { try { - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk: Hash Calculation (time per byte)"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk: Hash Calculation (time per byte nono)"); long calcstart = System.currentTimeMillis(); md5Hash = HashUtility.calculateMd5Hash(file); - EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize()); + if(file.getSize() > 0) { + EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, 1); + } file.setMd5Hash(md5Hash); long delta = (System.currentTimeMillis() - calcstart); totals.totalCalctime.addAndGet(delta); From 57367742f78ba6993cabeee426635019ff9ec40c Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Fri, 20 Apr 2018 17:02:10 -0400 Subject: [PATCH 065/100] 3744 make language regarding what will be searched more clear --- docs/doxygen-user/quick_search.dox | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/doxygen-user/quick_search.dox b/docs/doxygen-user/quick_search.dox index 4783f07b9d..e1bb65248a 100644 --- a/docs/doxygen-user/quick_search.dox +++ b/docs/doxygen-user/quick_search.dox @@ -1,5 +1,7 @@ /*! \page quick_search Quick Search +The quick search feature allows you to search within the data on a panel for a given string, it will not search data in hidden columns or collapsed nodes. + How to use it ----- In order to use the search you need to select any item in the area you wish to search, and start typing. If quick search is available in the area you have selected a search field will appear in the bottom left hand corner of the area. As you type the string you are searching for it will auto-update to select one of the results which matches your string. You can switch between the results which match the string you have typed with the up and down keys. The search does not support the use of regular expressions but will match against any sub-sting in the fields it searches, not just at the beginning of the field. @@ -7,7 +9,7 @@ In order to use the search you need to select any item in the area you wish to s Configuration ----- -By default the search will match against the data in all fields which are currently visible in the selected area. The search will also ignore case by default. If you want to change either of these default behaviors you can click the magnifying glass with the down arrow icon and configure which columns will be searched as well as if the search should ignore case. +By default the search will match against the data in all fields which are in the currently selected area. The search will also ignore case by default. If you want to change either of these default behaviors you can click the magnifying glass with the down arrow icon and configure which columns will be searched as well as if the search should ignore case. \image html quick_search_configuration.PNG Where it can be used From dc8a5d7ffb92206bbec5649d7c2a583aa273a412 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 22 Apr 2018 11:12:36 -0400 Subject: [PATCH 066/100] Disable loading of Python modules to prevent exception until it is fixed --- .../autopsy/ingest/IngestJobSettings.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java index 3a06a3be4d..d512c6670c 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java @@ -501,13 +501,16 @@ public final class IngestJobSettings { this.warnings.add(warning); } } else { - try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { - settings = (IngestModuleIngestJobSettings) in.readObject(); - } catch (IOException | ClassNotFoundException exception) { - String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS - logger.log(Level.WARNING, warning, exception); - this.warnings.add(warning); - } + // @@@ BC Jython serialization is currently broken and this + // throws an exception. (-2323). Commenting out so that + // Python modules will at least load with default settings. +// try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { +// settings = (IngestModuleIngestJobSettings) in.readObject(); +// } catch (IOException | ClassNotFoundException exception) { +// String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS +// logger.log(Level.WARNING, warning, exception); +// this.warnings.add(warning); +// } } } if (settings == null) { From 72dfafc69367d241c240de47f96c38a11623b867 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 22 Apr 2018 11:13:01 -0400 Subject: [PATCH 067/100] renamed variable --- .../experimental/volatilityDSP/Bundle.properties | 2 +- .../experimental/volatilityDSP/MemoryDSInputPanel.form | 8 ++++---- .../experimental/volatilityDSP/MemoryDSInputPanel.java | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties index 4f6bc6b9b9..1c7d3312da 100755 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/Bundle.properties @@ -9,4 +9,4 @@ MemoryDSInputPanel.pathTextField.text= MemoryDSInputPanel.errorLabel.text=Error Label MemoryDSInputPanel.browseButton.text=Browse MemoryDSInputPanel.timeZoneLabel.text=Timezone: -MemoryDSInputPanel.jLabel1.text=Profile: +MemoryDSInputPanel.profileLabel.text=Profile: diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form index 3529fe8920..f899eeb503 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.form @@ -46,7 +46,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -173,10 +173,10 @@ - + - + diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index beaa46fc36..7c87a5f810 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -203,7 +203,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { PluginsToRunLabel = new javax.swing.JLabel(); listsScrollPane = new javax.swing.JScrollPane(); pluginTable = new javax.swing.JTable(); - jLabel1 = new javax.swing.JLabel(); + profileLabel = new javax.swing.JLabel(); profileComboBox = new javax.swing.JComboBox<>(); org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.pathLabel.text")); // NOI18N @@ -244,7 +244,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { )); listsScrollPane.setViewportView(pluginTable); - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.jLabel1.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(profileLabel, org.openide.util.NbBundle.getMessage(MemoryDSInputPanel.class, "MemoryDSInputPanel.profileLabel.text")); // NOI18N profileComboBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { @@ -276,7 +276,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(errorLabel) .addComponent(PluginsToRunLabel) - .addComponent(jLabel1)) + .addComponent(profileLabel)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -295,7 +295,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { .addComponent(errorLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) + .addComponent(profileLabel) .addComponent(profileComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -335,12 +335,12 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { private javax.swing.JButton browseButton; private javax.swing.JLabel errorLabel; private javax.swing.ButtonGroup infileTypeButtonGroup; - private javax.swing.JLabel jLabel1; private javax.swing.JScrollPane listsScrollPane; private javax.swing.JLabel pathLabel; private javax.swing.JTextField pathTextField; private javax.swing.JTable pluginTable; private javax.swing.JComboBox profileComboBox; + private javax.swing.JLabel profileLabel; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; // End of variables declaration//GEN-END:variables From b4df569d411cbb3a9251f9d6aca4dfc30cef29ac Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 22 Apr 2018 11:32:45 -0400 Subject: [PATCH 068/100] changed logic --- .../autopsy/experimental/volatilityDSP/VolatilityProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index 1d23367d65..f3f2c75b9d 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -193,7 +193,7 @@ class VolatilityProcessor { commandLine.add("\"" + executableFile + "\""); //NON-NLS File memoryImage = new File(memoryImagePath); commandLine.add("--filename=" + memoryImage.getName()); //NON-NLS - if (profile.isEmpty() == false) { + if (!profile.isEmpty()) { commandLine.add("--profile=" + profile); //NON-NLS } commandLine.add(pluginToRun); From 7b2e19aa5521ed0865d358a085e353cb71a187de Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sun, 22 Apr 2018 23:18:33 -0400 Subject: [PATCH 069/100] Added dump commands to Volatility list --- .../volatilityDSP/MemoryDSInputPanel.java | 2 +- .../volatilityDSP/VolatilityProcessor.java | 26 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java index 7c87a5f810..134cdbc914 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/MemoryDSInputPanel.java @@ -75,7 +75,7 @@ final class MemoryDSInputPanel extends JPanel implements DocumentListener { * Creates new MemoryDSInputPanel panel for user input */ private MemoryDSInputPanel(String context) { - this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo"}; + this.pluginList = new String[]{"amcache", "cmdline", "cmdscan", "consoles", "malfind", "netscan", "notepad", "pslist", "psxview", "shellbags", "shimcache", "shutdown", "userassist", "apihooks", "connscan", "devicetree", "dlllist", "envars", "filescan", "gahti", "getservicesids", "getsids", "handles", "hashdump", "hivelist", "hivescan", "impscan", "ldrmodules", "lsadump", "modules", "mutantscan", "privs", "psscan", "pstree", "sockets", "svcscan", "shimcache", "timeliner", "unloadedmodules", "userhandles", "vadinfo", "verinfo", "dlldump", "moddump", "procdump", "dumpfiles", "dumpregistry"}; Arrays.sort(this.pluginList); initComponents(); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java index f3f2c75b9d..9d3fcd15d5 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/volatilityDSP/VolatilityProcessor.java @@ -134,8 +134,7 @@ class VolatilityProcessor { moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), VOLATILITY, dataSourceId.toString()).toString(); File directory = new File(String.valueOf(moduleOutputPath)); if (!directory.exists()) { - directory.mkdirs(); - + directory.mkdirs(); } // if they did not specify a profile, then run imageinfo to get one @@ -198,7 +197,24 @@ class VolatilityProcessor { } commandLine.add(pluginToRun); - String outputFileAsString = moduleOutputPath + "\\" + pluginToRun + ".txt"; //NON-NLS + switch (pluginToRun) { + case "dlldump": + case "moddump": + case "procdump": + case "dumpregistry": + case "dumpfiles": + String outputDir = moduleOutputPath + File.separator + pluginToRun; + File directory = new File(outputDir); + if (!directory.exists()) { + directory.mkdirs(); + } + commandLine.add("--dump-dir=" + outputDir); //NON-NLS + break; + default: + break; + } + + String outputFileAsString = moduleOutputPath + File.separator + pluginToRun + ".txt"; //NON-NLS ProcessBuilder processBuilder = new ProcessBuilder(commandLine); /* * Add an environment variable to force Volatility to run with the same @@ -207,7 +223,7 @@ class VolatilityProcessor { processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS File outputFile = new File(outputFileAsString); processBuilder.redirectOutput(outputFile); - processBuilder.redirectError(new File(moduleOutputPath + "\\Volatility_Run.err")); //NON-NLS + processBuilder.redirectError(new File(moduleOutputPath + File.separator + "Volatility_err.txt")); //NON-NLS processBuilder.directory(new File(memoryImage.getParent())); try { @@ -262,7 +278,7 @@ class VolatilityProcessor { "VolatilityProcessor_exceptionMessage_failedToParseImageInfo=Could not parse image info" }) private String getProfileFromImageInfoOutput() throws VolatilityProcessorException { - File imageOutputFile = new File(moduleOutputPath + "\\imageinfo.txt"); //NON-NLS + File imageOutputFile = new File(moduleOutputPath + File.separator + "imageinfo.txt"); //NON-NLS try (BufferedReader br = new BufferedReader(new FileReader(imageOutputFile))) { String fileRead = br.readLine(); if (fileRead != null) { From dfaa2255c3da01cf501d115fa21b8c430ef7e252 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 23 Apr 2018 11:57:32 -0400 Subject: [PATCH 070/100] 3768 have AutoIngestMonitor's shutdown method called when the AutoIngestDashboard is closed --- .../autoingest/AutoIngestDashboard.java | 16 ++++++++++++---- .../AutoIngestDashboardTopComponent.java | 9 ++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 482742d7de..12218f203c 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -224,14 +224,22 @@ final class AutoIngestDashboard extends JPanel implements Observer { autoIngestMonitor.addObserver(this); new Thread(() -> { try { - autoIngestMonitor.startUp(); - } - catch (AutoIngestMonitor.AutoIngestMonitorException ex) { + autoIngestMonitor.startUp(); + } catch (AutoIngestMonitor.AutoIngestMonitorException ex) { LOGGER.log(Level.SEVERE, "Unable to start up Auto Ingest Monitor", ex); - } + } }).start(); } + /** + * Shut down parts of the AutoIngestDashboard which were initialized + */ + void shutDown() { + if (autoIngestMonitor != null) { + autoIngestMonitor.shutDown(); + } + } + @Override public void update(Observable observable, Object arg) { EventQueue.invokeLater(() -> { diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java index 9a5553519c..915e2a981f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboardTopComponent.java @@ -87,17 +87,24 @@ public final class AutoIngestDashboardTopComponent extends TopComponent { } } - public static void closeTopComponent() { + @Override + protected void componentClosed() { if (topComponentInitialized) { final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID); if (tc != null) { try { + for (Component comp : getComponents()) { + if (comp instanceof AutoIngestDashboard) { + ((AutoIngestDashboard) comp).shutDown(); + } + } tc.close(); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to close " + PREFERRED_ID, e); // NON-NLS } } } + super.componentClosed(); } public AutoIngestDashboardTopComponent() { From 27c6cf9e5d7072df05387b1fd1d18a51786ab3e5 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Mon, 23 Apr 2018 11:58:33 -0400 Subject: [PATCH 071/100] Deleted unrelated comment block to remove doxygen warnings. --- .../sleuthkit/autopsy/communications/VisualizationPanel.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java index 4f6b53ae39..f3f77fbef4 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java +++ b/Core/src/org/sleuthkit/autopsy/communications/VisualizationPanel.java @@ -208,11 +208,6 @@ final public class VisualizationPanel extends JPanel implements Lookup.Provider applyLayout(fastOrganicLayout); } - /** - * - * @param layoutButton the value of layoutButton - * @param layout the value of layout - */ @Override public Lookup getLookup() { return proxyLookup; From 0bf0e9d08ec0ed9b16e9891380c3b50faa12c7a9 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Mon, 23 Apr 2018 15:47:07 -0400 Subject: [PATCH 072/100] Completed functional test for password detection. --- Core/build.xml | 1 + .../EncryptionDetectionTest.java | 198 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java diff --git a/Core/build.xml b/Core/build.xml index 3555ff0711..25215d493f 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -85,6 +85,7 @@ + diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java new file mode 100755 index 0000000000..55d2a1ebdd --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -0,0 +1,198 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import static junit.framework.Assert.assertFalse; +import org.netbeans.junit.NbModuleSuite; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import junit.framework.Test; +import org.apache.commons.io.FileUtils; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner; +import org.sleuthkit.autopsy.testutils.DataSourceProcessorRunner.ProcessorCallback; +import org.sleuthkit.autopsy.testutils.IngestJobRunner; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +public class EncryptionDetectionTest extends NbTestCase { + + private static final String CASE_NAME = "EncryptionDetectionTest"; + private static final Path CASE_DIRECTORY_PATH = Paths.get(System.getProperty("java.io.tmpdir"), CASE_NAME); + private static final File CASE_DIR = new File(CASE_DIRECTORY_PATH.toString()); + private final Path IMAGE_PATH = Paths.get(this.getDataDir().toString(), "password_detection_test.img"); + + public static Test suite() { + NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(EncryptionDetectionTest.class). + clusters(".*"). + enableModules(".*"); + return conf.suite(); + } + + public EncryptionDetectionTest(String name) { + super(name); + } + + @Override + public void setUp() { + // Delete the test directory, if it exists + if (CASE_DIRECTORY_PATH.toFile().exists()) { + try { + FileUtils.deleteDirectory(CASE_DIRECTORY_PATH.toFile()); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + assertFalse("Unable to delete existing test directory", CASE_DIRECTORY_PATH.toFile().exists()); + + // Create the test directory + CASE_DIRECTORY_PATH.toFile().mkdirs(); + assertTrue("Unable to create test directory", CASE_DIRECTORY_PATH.toFile().exists()); + + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, CASE_DIRECTORY_PATH.toString(), new CaseDetails(CASE_NAME)); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + assertTrue(CASE_DIR.exists()); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + try { + ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, IMAGE_PATH); + List dataSourceContent = callBack.getDataSourceContent(); + assertEquals(1, dataSourceContent.size()); + List errorMessages = callBack.getErrorMessages(); + assertEquals(0, errorMessages.size()); + } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + + } + } + + @Override + public void tearDown() { + try { + Case.closeCurrentCase(); + //Seems like we need some time to close the case. + try { + Thread.sleep(2000); + } catch (InterruptedException ex) { + + } + + FileUtils.deleteDirectory(CASE_DIR); + } catch (CaseActionException | IOException ex) { + Exceptions.printStackTrace(ex); + } + assertFalse(CASE_DIR.exists()); + } + + /** + * Test the Encryption Detection module's password protection detection. + */ + public void testPasswordProtection() { + try { + Case openCase = Case.getOpenCase(); + runIngestJob(openCase.getDataSources(), new EncryptionDetectionModuleFactory()); + FileManager fileManager = openCase.getServices().getFileManager(); + Blackboard bb = openCase.getServices().getBlackboard(); + List results = fileManager.findFiles("%%", "ole2"); + results.addAll(fileManager.findFiles("%%", "ooxml")); + + for (AbstractFile file : results) { + /* + * Process only non-slack files. + */ + if (file.isFile() && !file.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK)) { + /* + * Determine which assertions to use for the file based on + * its name. + */ + boolean fileProtected = file.getName().split("\\.")[0].endsWith("-protected"); + List artifactsList = file.getAllArtifacts(); + if (fileProtected) { + /* + * Check that the protected file has one + * TSK_ENCRYPTION_DETECTED artifact. + */ + int artifactsListSize = artifactsList.size(); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but 1 was expected", file.getName(), file.getId(), artifactsListSize); + assertEquals(errorMessage, 1, artifactsListSize); + + String artifactTypeName = artifactsList.get(0).getArtifactTypeName(); + errorMessage = String.format("File '%s' (objId=%d) has an unexpected '%s' artifact.", file.getName(), file.getId(), artifactTypeName); + assertEquals(errorMessage, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.toString(), artifactTypeName); + } else { + /* + * Check that the unprotected file has no artifacts. + */ + int artifactsListSize = artifactsList.size(); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but none were expected", file.getName(), file.getId(), artifactsListSize); + assertEquals(errorMessage, 0, artifactsListSize); + } + } + } + } catch (NoCurrentCaseException | TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + private void runIngestJob(List datasources, IngestModuleFactory factory) { + IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); + template.setEnabled(true); + ArrayList templates = new ArrayList<>(); + templates.add(template); + IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); + try { + List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); + assertEquals(0, errs.size()); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + +} From ba0379b7f157f77f47d826e4f33029bfb1dcd771 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 23 Apr 2018 16:08:27 -0400 Subject: [PATCH 073/100] 3752 Encryption Detection data source level module added --- .../directorytree/ViewContextAction.java | 4 +- ...yptionDetectionDataSourceIngestModule.java | 181 ++++++++++++++++++ .../EncryptionDetectionFileIngestModule.java | 87 +-------- .../EncryptionDetectionIngestJobSettings.java | 13 +- .../EncryptionDetectionModuleFactory.java | 9 +- .../EncryptionDetectionTools.java | 116 +++++++++++ 6 files changed, 317 insertions(+), 93 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java create mode 100644 Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 792252605a..06628d6136 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -59,7 +59,7 @@ public class ViewContextAction extends AbstractAction { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName()); - private final Content content; + private Content content; /** * An action that displays the context for the source content of an artifact @@ -79,6 +79,8 @@ public class ViewContextAction extends AbstractAction { || (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK == file.getType() && UserPreferences.hideSlackFilesInDataSourcesTree())) { this.setEnabled(false); } + } else { + this.content = artifactNode.getLookup().lookup(Content.class); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java new file mode 100644 index 0000000000..cb9524df8c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -0,0 +1,181 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.autopsy.ingest.ModuleDataEvent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.Volume; +import org.sleuthkit.datamodel.VolumeSystem; + +/** + * Sample data source ingest module that doesn't do much. Demonstrates per + * ingest job module settings, checking for job cancellation, updating the + * DataSourceIngestModuleProgress object, and use of a subset of the available + * ingest services. + */ +class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule { + + private final IngestServices services = IngestServices.getInstance(); + private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); + private Blackboard blackboard; + private double calculatedEntropy; + + private final double minimumEntropy; + + EncryptionDetectionDataSourceIngestModule(EncryptionDetectionIngestJobSettings settings) { + minimumEntropy = settings.getMinimumEntropy(); + } + + @Override + public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { + try { + validateSettings(); + blackboard = Case.getOpenCase().getServices().getBlackboard(); + } catch (NoCurrentCaseException ex) { + throw new IngestModule.IngestModuleException("Exception while getting open case.", ex); + } + } + + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { + + try { + System.out.println("PROCESS DS"); + if (dataSource instanceof Image) { + List volumeSystems = ((Image) dataSource).getVolumeSystems(); + for (VolumeSystem volumeSystem : volumeSystems) { + for (Volume volume : volumeSystem.getVolumes()) { + if (volume.getFileSystems().isEmpty()) { + if (isDataSourceEncrypted(volume)) { + System.out.println("VOLUME ENCRYPTED"); + return flagVolume(volume); + } + } + } + } + } + } catch (ReadContentInputStream.ReadContentInputStreamException ex) { + logger.log(Level.WARNING, String.format("Unable to read data source '%s'", dataSource.getName()), ex); + return IngestModule.ProcessResult.ERROR; + } catch (IOException | TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Unable to process data source '%s'", dataSource.getName()), ex); + return IngestModule.ProcessResult.ERROR; + } + + return IngestModule.ProcessResult.OK; + } + + private void validateSettings() throws IngestModule.IngestModuleException { + EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); + } + + /** + * Create a blackboard artifact. + * + * @param The file to be processed. + * + * @return 'OK' if the file was processed successfully, or 'ERROR' if there + * was a problem. + */ + private IngestModule.ProcessResult flagVolume(Volume volume) { + try { + BlackboardArtifact artifact = volume.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); + + try { + /* + * Index the artifact for keyword search. + */ + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS + } + + /* + * Send an event to update the view with the new result. + */ + services.fireModuleDataEvent(new ModuleDataEvent(EncryptionDetectionModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED, Collections.singletonList(artifact))); + + /* + * Make an ingest inbox message. + */ + StringBuilder detailsSb = new StringBuilder(); + detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()).append("
\n"); + detailsSb.append("Entropy: ").append(calculatedEntropy); + + services.postMessage(IngestMessage.createDataMessage(EncryptionDetectionModuleFactory.getModuleName(), + "Encryption Detected Match: " + volume.getName(), + detailsSb.toString(), + volume.getName(), + artifact)); + + return IngestModule.ProcessResult.OK; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Failed to create blackboard artifact for '%s'.", volume.getName()), ex); //NON-NLS + return IngestModule.ProcessResult.ERROR; + } + } + + /** + * This method checks if the AbstractFile input is encrypted. Initial + * qualifications require that it be an actual file that is not known, meets + * file size requirements, and has a MIME type of + * 'application/octet-stream'. + * + * @param file AbstractFile to be checked. + * + * @return True if the AbstractFile is encrypted. + */ + private boolean isDataSourceEncrypted(Content dataSource) throws ReadContentInputStream.ReadContentInputStreamException, IOException, TskCoreException { + /* + * Criteria for the checks in this method are partially based on + * http://www.forensicswiki.org/wiki/TrueCrypt#Detection + */ + + boolean possiblyEncrypted = true; + + if (possiblyEncrypted) { + System.out.println("CALCULATE ENTROPY"); + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(dataSource); + if (calculatedEntropy >= minimumEntropy) { + System.out.println("ENTROPY INDICATED ENCRYPTED DS"); + return true; + } + } + + return false; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 7b48606ea0..08b314a7aa 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -18,9 +18,7 @@ */ package org.sleuthkit.autopsy.modules.encryptiondetection; -import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Collections; import java.util.logging.Level; import org.openide.util.NbBundle; @@ -37,7 +35,6 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -47,19 +44,7 @@ import org.sleuthkit.datamodel.TskData; */ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { - static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5; - static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB; - static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true; - static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true; - - static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; - static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; - static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; - private static final int FILE_SIZE_MODULUS = 512; - private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) - private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; - private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private FileTypeDetector fileTypeDetector; @@ -121,18 +106,9 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter * @throws IngestModule.IngestModuleException If the input is empty, * invalid, or out of range. */ - @NbBundle.Messages({ - "EncryptionDetectionFileIngestModule.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.", - "EncryptionDetectionFileIngestModule.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." - }) private void validateSettings() throws IngestModule.IngestModuleException { - if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { - throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumEntropyInput()); - } - - if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { - throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionFileIngestModule_errorMessage_minimumFileSizeInput()); - } + EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); + EncryptionDetectionTools.validateMinFileSizeValue(minimumFileSize); } /** @@ -230,7 +206,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter } if (possiblyEncrypted) { - calculatedEntropy = calculateEntropy(file); + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(file); if (calculatedEntropy >= minimumEntropy) { return true; } @@ -238,61 +214,4 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter return false; } - - /** - * Calculate the entropy of the file. The result is used to qualify the file - * as an encrypted file. - * - * @param file The file to be calculated against. - * - * @return The entropy of the file. - * - * @throws IOException If there is a failure closing or reading from the - * InputStream. - */ - private double calculateEntropy(AbstractFile file) throws ReadContentInputStreamException, IOException { - /* - * Logic in this method is based on - * https://github.com/willjasen/entropy/blob/master/entropy.java - */ - - InputStream in = null; - BufferedInputStream bin = null; - - try { - in = new ReadContentInputStream(file); - bin = new BufferedInputStream(in); - - /* - * Determine the number of times each byte value appears. - */ - int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; - int readByte; - while ((readByte = bin.read()) != -1) { - byteOccurences[readByte]++; - } - - /* - * Calculate the entropy based on the byte occurence counts. - */ - long dataLength = file.getSize() - 1; - double entropyAccumulator = 0; - for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { - if (byteOccurences[i] > 0) { - double byteProbability = (double) byteOccurences[i] / (double) dataLength; - entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); - } - } - - return -entropyAccumulator; - - } finally { - if (in != null) { - in.close(); - } - if (bin != null) { - bin.close(); - } - } - } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java index 2aa6ad860d..5acb97a518 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionIngestJobSettings.java @@ -26,7 +26,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJobSettings { private static final long serialVersionUID = 1L; - + private static final double DEFAULT_CONFIG_MINIMUM_ENTROPY = 7.5; + private static final int DEFAULT_CONFIG_MINIMUM_FILE_SIZE = 5242880; // 5MB; + private static final boolean DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED = true; + private static final boolean DEFAULT_CONFIG_SLACK_FILES_ALLOWED = true; private double minimumEntropy; private int minimumFileSize; private boolean fileSizeMultipleEnforced; @@ -36,10 +39,10 @@ final class EncryptionDetectionIngestJobSettings implements IngestModuleIngestJo * Instantiate the ingest job settings with default values. */ EncryptionDetectionIngestJobSettings() { - this.minimumEntropy = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_ENTROPY; - this.minimumFileSize = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_MINIMUM_FILE_SIZE; - this.fileSizeMultipleEnforced = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; - this.slackFilesAllowed = EncryptionDetectionFileIngestModule.DEFAULT_CONFIG_SLACK_FILES_ALLOWED; + this.minimumEntropy = DEFAULT_CONFIG_MINIMUM_ENTROPY; + this.minimumFileSize = DEFAULT_CONFIG_MINIMUM_FILE_SIZE; + this.fileSizeMultipleEnforced = DEFAULT_CONFIG_FILE_SIZE_MULTIPLE_ENFORCED; + this.slackFilesAllowed = DEFAULT_CONFIG_SLACK_FILES_ALLOWED; } /** diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java index 27549f648f..1e0fd8115a 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionModuleFactory.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2017 Basis Technology Corp. + * Copyright 2017-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -106,11 +106,14 @@ public class EncryptionDetectionModuleFactory implements IngestModuleFactory { @Override public boolean isDataSourceIngestModuleFactory() { - return false; + return true; } @Override public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { - throw new UnsupportedOperationException(); + if (!(settings instanceof EncryptionDetectionIngestJobSettings)) { + throw new IllegalArgumentException("Expected settings argument to be an instance of EncryptionDetectionIngestJobSettings."); + } + return new EncryptionDetectionDataSourceIngestModule((EncryptionDetectionIngestJobSettings) settings); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java new file mode 100644 index 0000000000..c42c6af1ec --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java @@ -0,0 +1,116 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.encryptiondetection; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.Content; + +/** + * + */ +final class EncryptionDetectionTools { + + private static final double ONE_OVER_LOG2 = 1.4426950408889634073599246810019; // (1 / log(2)) + private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; + static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; + static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; + + static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; + + @NbBundle.Messages({ + "EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.", + "EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." + }) + static void validateMinEntropyValue(double minimumEntropy) throws IngestModule.IngestModuleException { + if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { + throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumEntropyInput()); + } + } + + static void validateMinFileSizeValue(int minimumFileSize) throws IngestModule.IngestModuleException { + if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { + throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumFileSizeInput()); + } + } + + /** + * Calculate the entropy of the file. The result is used to qualify the file + * as an encrypted file. + * + * @param file The file to be calculated against. + * + * @return The entropy of the file. + * + * @throws IOException If there is a failure closing or reading from the + * InputStream. + */ + static double calculateEntropy(Content file) throws ReadContentInputStream.ReadContentInputStreamException, IOException { + /* + * Logic in this method is based on + * https://github.com/willjasen/entropy/blob/master/entropy.java + */ + + InputStream in = null; + BufferedInputStream bin = null; + + try { + in = new ReadContentInputStream(file); + bin = new BufferedInputStream(in); + + /* + * Determine the number of times each byte value appears. + */ + int[] byteOccurences = new int[BYTE_OCCURENCES_BUFFER_SIZE]; + int readByte; + while ((readByte = bin.read()) != -1) { + byteOccurences[readByte]++; + } + + /* + * Calculate the entropy based on the byte occurence counts. + */ + long dataLength = file.getSize() - 1; + double entropyAccumulator = 0; + for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { + if (byteOccurences[i] > 0) { + double byteProbability = (double) byteOccurences[i] / (double) dataLength; + entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); + } + } + System.out.println("ENTROPY VALUE: " + -entropyAccumulator); + return -entropyAccumulator; + + } finally { + if (in != null) { + in.close(); + } + if (bin != null) { + bin.close(); + } + } + } + + private EncryptionDetectionTools() { + } +} From c2bc250d60f598a7a020106a91887e5394034d5f Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 23 Apr 2018 16:58:05 -0400 Subject: [PATCH 074/100] 3752 minor adjustment to ViewContextAction --- .../sleuthkit/autopsy/directorytree/ViewContextAction.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 06628d6136..99d54e61a9 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -59,7 +59,7 @@ public class ViewContextAction extends AbstractAction { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(ViewContextAction.class.getName()); - private Content content; + private final Content content; /** * An action that displays the context for the source content of an artifact @@ -72,8 +72,9 @@ public class ViewContextAction extends AbstractAction { */ public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { super(displayName); - this.content = artifactNode.getLookup().lookup(AbstractFile.class); - if (this.content != null) { + Content fileContent = artifactNode.getLookup().lookup(AbstractFile.class); + if (fileContent != null) { + this.content = fileContent; AbstractFile file = (AbstractFile) content; if ((TskData.FileKnown.KNOWN == file.getKnown() && UserPreferences.hideKnownFilesInDataSourcesTree()) || (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK == file.getType() && UserPreferences.hideSlackFilesInDataSourcesTree())) { From 21bb53b223450fdb2d61f8243f67e017b416daf7 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 23 Apr 2018 17:11:39 -0400 Subject: [PATCH 075/100] Complete new documentation of core result viewers --- .../DataResultViewer.java | 141 +++++++++++------ .../AbstractDataResultViewer.java | 76 ++++----- .../corecomponents/DataResultViewerTable.java | 146 +++++++++--------- .../DataResultViewerThumbnail.java | 144 ++++++++--------- 4 files changed, 262 insertions(+), 245 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java index d88c979844..a5ace28245 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-17 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,76 +22,117 @@ import java.awt.Component; import org.openide.nodes.Node; /** - * Interface for the different viewers that show a set of nodes in the - * DataResult area. AbstractDataResultViewer has default implementations for the - * action handlers. + * An interface for result viewers. A result viewer uses a Swing component to + * provide a view of the application data represented by a given NetBeans node. + * Result viewers typically appear in a result view (a DataResultTopComponent) + * docked into the upper right hand side of the main application window. + * + * Typically, a result viewer is a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view as a child component. Such a result + * viewer should use the explorer manager of the ancestor top component to + * connect the lookups of the nodes displayed in the NetBeans explorer view to + * the actions global context. It is strongly recommended that this type of + * result viewer extends the abstract base class AbstractDataResultViewer, which + * will handle some key aspects of working with the ancestor top component's + * explorer manager. + * + * This interface is an extension point, so classes that implement it should + * provide an appropriate ServiceProvider annotation. * */ public interface DataResultViewer { /** - * Set the root node to display in this viewer. When called with null, must - * clear all references to previous nodes. - */ - public void setNode(Node selectedNode); - - /** - * Gets the title of this viewer - */ - public String getTitle(); - - /** - * Get a new instance of DataResultViewer + * Creates a new instance of this result viewer, which allows the + * application to use the capability provided by this result viewer in more + * than one result view. This is done by using the default instance of this + * result viewer as a "factory" for creating other instances. */ public DataResultViewer createInstance(); /** - * Get the Swing component (i.e. JPanel) for this viewer + * Indicates whether this result viewer is able to provide a meaningful view + * of the application data represented by a given node. Typically, indicates + * whether or not this result viewer can use the given node as the root node + * of its child explorer view component. + * + * @param node The node. + * + * @return True or false. + */ + public boolean isSupported(Node node); + + /** + * Sets the node for which this result viewer should provide a view of the + * underlying application data. Typically, this means using the given node + * as the root node of this result viewer's child explorer view component. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent. + */ + public void setNode(Node node); + + /** + * Requests selection of the given child nodes of the node passed to + * setNode. This method should be implemented as a no-op for result viewers + * that do not display the child nodes of a given root node using a NetBeans + * explorer view set up to use a given explorer manager. + * + * @param selectedNodes The child nodes to select. + */ + default public void setSelectedNodes(Node[] selectedNodes) { + } + + /** + * Gets the title of this result viewer. + * + * @return The title. + */ + public String getTitle(); + + /** + * Gets the Swing component for this viewer. + * + * @return The component. */ public Component getComponent(); /** - * Resets the viewer. + * Resets the state of the Swing component for this viewer to its default + * state. */ - public void resetComponent(); + default public void resetComponent() { + } /** - * Frees the objects that have been allocated by this viewer, in preparation - * for permanently disposing of it. + * Frees any resources tha have been allocated by this result viewer, in + * preparation for permanently disposing of it. */ - public void clearComponent(); + default public void clearComponent() { + } /** - * Expand node, if supported by the viewed + * Sets the node for which this result viewer should provide a view of the + * underlying application data model object, and expands the node. * - * @param n Node to expand - */ - public void expandNode(Node n); - - /** - * Select the given node array - */ - public void setSelectedNodes(Node[] selected); - - /** - * Checks whether the currently selected root node is supported by this - * viewer + * @param node The node. * - * @param selectedNode the selected node - * - * @return True if supported, else false - */ - public boolean isSupported(Node selectedNode); - - /** - * Set a custom content viewer to respond to selection events from this - * result viewer. If not set, the default content viewer is used - * - * @param contentViewer content viewer to respond to selection events from - * this viewer - * - * @deprecated All implementations of this in the standard DataResultViewers are now no-ops. + * @deprecated This API is not used by the application. */ @Deprecated - public void setContentViewer(DataContent contentViewer); + default public void expandNode(Node node) { + } + + /** + * Sets a custom content viewer to which nodes selected in this result + * viewer should be pushed via DataContent.setNode. + * + * @param contentViewer The content viewer. + * + * @deprecated This API is not used by the application. + */ + @Deprecated + default public void setContentViewer(DataContent contentViewer) { + } + } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java index 715973aa7d..fdabf16ac0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AbstractDataResultViewer.java @@ -23,65 +23,49 @@ import java.beans.PropertyVetoException; import java.util.logging.Level; import javax.swing.JPanel; import org.openide.explorer.ExplorerManager; -import org.openide.explorer.ExplorerManager.Provider; import org.openide.nodes.Node; -import org.sleuthkit.autopsy.corecomponentinterfaces.DataContent; import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Provides a JPanel base class that provides a default implementation of the - * ExplorerManager.Provider interface and selected methods of - * theDataResultViewer interface. The ExplorerManager.Provider interface is - * implemented to supply an explorer manager to subclasses and their child - * components. The explorer manager is expected to be the explorer manager of a - * top component that exposes a lookup maintained by the explorer manager to the - * actions global context. This connects the nodes displayed in the result - * viewer to the actions global context. The explorer manager may be either - * supplied during construction or discovered at runtime. + * An abstract base class for an implementation of the result viewer interface + * that is a JPanel that displays the child nodes of a given node using a + * NetBeans explorer view as a child component. Such a result viewer should use + * the explorer manager of an ancestor top component to connect the lookups of + * the nodes displayed in the NetBeans explorer view to the actions global + * context. This class handles some key aspects of working with the ancestor top + * component's explorer manager. + * + * Instances of this class can be supplied with the top component's explorer + * manager during construction, but the typical use case is for the result + * viewer to find the ancestor top component's explorer manager at runtime. + * + * IMPORTANT: If the result viewer is going to find the ancestor top component's + * explorer manager at runtime, the first call to the getExplorerManager method + * of this class must be made AFTER the component hierarchy is fully + * constructed. + * */ -abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, Provider { +public abstract class AbstractDataResultViewer extends JPanel implements DataResultViewer, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(AbstractDataResultViewer.class.getName()); private transient ExplorerManager explorerManager; /** - * Constructs a JPanel base class instance that provides a default - * implementation of selected methods of the DataResultViewer and - * ExplorerManager.Provider interfaces. The explorer manager of this viewer - * will be discovered at runtime. - */ - AbstractDataResultViewer() { - } - - /** - * Constructs a JPanel base class instance that provides a default - * implementation of selected methods of the DataResultViewer and - * ExplorerManager.Provider interfaces. + * Constructs an abstract base class for an implementation of the result + * viewer interface that is a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view as a child component. * - * @param explorerManager + * @param explorerManager The explorer manager to use in the NetBeans + * explorer view child component of this result + * viewer, may be null. If null, the explorer manager + * will be discovered the first time + * getExplorerManager is called. */ - AbstractDataResultViewer(ExplorerManager explorerManager) { + public AbstractDataResultViewer(ExplorerManager explorerManager) { this.explorerManager = explorerManager; } - @Override - public void clearComponent() { - } - - @Override - public void expandNode(Node n) { - } - - @Override - public void resetComponent() { - } - - @Override - public Component getComponent() { - return this; - } - @Override public ExplorerManager getExplorerManager() { if (this.explorerManager == null) { @@ -95,13 +79,13 @@ abstract class AbstractDataResultViewer extends JPanel implements DataResultView try { this.getExplorerManager().setSelectedNodes(selected); } catch (PropertyVetoException ex) { - logger.log(Level.WARNING, "Couldn't set selected nodes.", ex); //NON-NLS + logger.log(Level.SEVERE, "Couldn't set selected nodes", ex); //NON-NLS } } - @Deprecated @Override - public void setContentViewer(DataContent contentViewer) { + public Component getComponent() { + return this; } } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 42aa2b727d..ca0185ce29 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2012 - 2018 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,8 +67,14 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; /** - * A tabular results viewer that displays the children of a given root node + * A tabular result viewer that displays the children of the given root node * using an OutlineView. + * + * Instances of this class should use the explorer manager of an ancestor top + * component to connect the lookups of the nodes displayed in the OutlineView to + * the actions global context. The explorer manager can be supplied during + * construction, but the typical use case is for the result viewer to find the + * ancestor top component's explorer manager at runtime. */ @ServiceProvider(service = DataResultViewer.class) public final class DataResultViewerTable extends AbstractDataResultViewer { @@ -79,56 +85,52 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195); private static final Logger logger = Logger.getLogger(DataResultViewerTable.class.getName()); private final String title; - private Outline outline; - private TableListener outlineViewListener; - private Node currentRootNode; - private final Map columnMap = new HashMap<>(); - private final Map> propertiesMap = new TreeMap<>(); + private final Map columnMap; + private final Map> propertiesMap; + private final Outline outline; + private final TableListener outlineViewListener; + private Node rootNode; /** - * Constructs a tabular results viewer that displays the children of a given - * root node using an OutlineView. + * Constructs a tabular result viewer that displays the children of the + * given root node using an OutlineView. The viewer should have an ancestor + * top component to connect the lookups of the nodes displayed in the + * OutlineView to the actions global context. The explorer manager will be + * discovered at runtime. */ public DataResultViewerTable() { - super(); - this.title = Bundle.DataResultViewerTable_title(); - initialize(); + this(null, Bundle.DataResultViewerTable_title()); } /** - * Constructs a tabular results viewer that displays the children of a given - * root node using an OutlineView, with a given explorer manager. + * Constructs a tabular result viewer that displays the children of a given + * root node using an OutlineView. The viewer should have an ancestor top + * component to connect the lookups of the nodes displayed in the + * OutlineView to the actions global context. * - * @param explorerManager The explorer manager. + * @param explorerManager The explorer manager of the ancestor top + * component. */ public DataResultViewerTable(ExplorerManager explorerManager) { this(explorerManager, Bundle.DataResultViewerTable_title()); } /** - * Constructs a tabular results viewer that displays the children of a given - * root node using an OutlineView, with a given explorer manager, and a - * custom title. + * Constructs a tabular result viewer that displays the children of a given + * root node using an OutlineView with a given title. The viewer should have + * an ancestor top component to connect the lookups of the nodes displayed + * in the OutlineView to the actions global context. * - * @param explorerManager The explorer manager. - * @param title The custom title. + * @param explorerManager The explorer manager of the ancestor top + * component. + * @param title The title. */ public DataResultViewerTable(ExplorerManager explorerManager, String title) { super(explorerManager); this.title = title; - initialize(); - } + this.columnMap = new HashMap<>(); + this.propertiesMap = new TreeMap<>(); -// @Override -// public void addNotify() { -// super.addNotify(); -// initialize(); -// } - - /* - * Initializes this tabular results viewer. - */ - private void initialize() { /* * Execute the code generated by the GUI builder. */ @@ -159,13 +161,13 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Creates a new instance of a tabular results viewer that displays the + * Creates a new instance of a tabular result viewer that displays the * children of a given root node using an OutlineView. This method exists to * make it possible to use the default service provider instance of this * class in the "main" results view of the application, while using distinct * instances in other places in the UI. * - * @return A new instance of a tabular results viewer, + * @return A new instance of a tabular result viewer, */ @Override public DataResultViewer createInstance() { @@ -173,7 +175,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Gets the title of this tabular results viewer. + * Gets the title of this tabular result viewer. */ @Override @NbBundle.Messages("DataResultViewerTable.title=Table") @@ -195,7 +197,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Sets the current root node of this tabular results viewer. + * Sets the current root node of this tabular result viewer. * * @param rootNode The node to set as the current root node, possibly null. */ @@ -225,8 +227,8 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * case database round trips. */ if (rootNode != null && rootNode.getChildren().getNodesCount() > 0) { - currentRootNode = rootNode; - this.getExplorerManager().setRootContext(currentRootNode); + this.rootNode = rootNode; + this.getExplorerManager().setRootContext(this.rootNode); setupTable(); } else { Node emptyNode = new AbstractNode(Children.LEAF); @@ -241,7 +243,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { } /** - * Sets up the Outline view of this tabular results viewer by creating + * Sets up the Outline view of this tabular result viewer by creating * column headers based on the children of the current root node. The * persisted column order, sorting and visibility is used. */ @@ -310,10 +312,10 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * it. */ SwingUtilities.invokeLater(() -> { - if (currentRootNode instanceof TableFilterNode) { - NodeSelectionInfo selectedChildInfo = ((TableFilterNode) currentRootNode).getChildNodeSelectionInfo(); + if (rootNode instanceof TableFilterNode) { + NodeSelectionInfo selectedChildInfo = ((TableFilterNode) rootNode).getChildNodeSelectionInfo(); if (null != selectedChildInfo) { - Node[] childNodes = currentRootNode.getChildren().getNodes(true); + Node[] childNodes = rootNode.getChildren().getNodes(true); for (int i = 0; i < childNodes.length; ++i) { Node childNode = childNodes[i]; if (selectedChildInfo.matches(childNode)) { @@ -325,7 +327,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { break; } } - ((TableFilterNode) currentRootNode).setChildNodeSelectionInfo(null); + ((TableFilterNode) rootNode).setChildNodeSelectionInfo(null); } } }); @@ -339,7 +341,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { /* * Populates the column map for the child OutlineView of this tabular - * results viewer with references to the column objects for use when + * result viewer with references to the column objects for use when * loading/storing the visibility info. */ private void populateColumnMap() { @@ -361,7 +363,7 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * viewer. */ private void setColumnWidths() { - if (currentRootNode.getChildren().getNodesCount() != 0) { + if (rootNode.getChildren().getNodesCount() != 0) { final Graphics graphics = outlineView.getGraphics(); if (graphics != null) { final FontMetrics metrics = graphics.getFontMetrics(); @@ -419,14 +421,14 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { /** * Persists the current column visibility information for the child - * OutlineView of this tabular results viewer using a preferences file. + * OutlineView of this tabular result viewer using a preferences file. */ private synchronized void storeColumnVisibility() { - if (currentRootNode == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRootNode instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRootNode; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); final ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry entry : columnMap.entrySet()) { @@ -445,14 +447,14 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { /** * Persists the current column ordering for the child OutlineView of this - * tabular results viewer using a preferences file. + * tabular result viewer using a preferences file. */ private synchronized void storeColumnOrder() { - if (currentRootNode == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRootNode instanceof TableFilterNode) { - TableFilterNode tfn = (TableFilterNode) currentRootNode; + if (rootNode instanceof TableFilterNode) { + TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); // Store the current order of the columns into settings for (Map.Entry> entry : propertiesMap.entrySet()) { @@ -465,11 +467,11 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * Persists the current column sorting information using a preferences file. */ private synchronized void storeColumnSorting() { - if (currentRootNode == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRootNode instanceof TableFilterNode) { - final TableFilterNode tfn = ((TableFilterNode) currentRootNode); + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = ((TableFilterNode) rootNode); final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry entry : columnMap.entrySet()) { @@ -497,11 +499,11 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * it cannot set the sort on columns that have not been added to the table. */ private synchronized void loadColumnSorting() { - if (currentRootNode == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRootNode instanceof TableFilterNode) { - final TableFilterNode tfn = (TableFilterNode) currentRootNode; + if (rootNode instanceof TableFilterNode) { + final TableFilterNode tfn = (TableFilterNode) rootNode; final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); //organize property sorting information, sorted by rank TreeSet sortInfos = new TreeSet<>(Comparator.comparing(ColumnSortInfo::getRank)); @@ -523,12 +525,12 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { * preferences file. */ private synchronized void loadColumnVisibility() { - if (currentRootNode == null || propertiesMap.isEmpty()) { + if (rootNode == null || propertiesMap.isEmpty()) { return; } - if (currentRootNode instanceof TableFilterNode) { + if (rootNode instanceof TableFilterNode) { final Preferences preferences = NbPreferences.forModule(DataResultViewerTable.class); - final TableFilterNode tfn = ((TableFilterNode) currentRootNode); + final TableFilterNode tfn = ((TableFilterNode) rootNode); ETableColumnModel columnModel = (ETableColumnModel) outline.getColumnModel(); for (Map.Entry> entry : propertiesMap.entrySet()) { final String propName = entry.getValue().getName(); @@ -549,14 +551,14 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { */ private synchronized List> loadColumnOrder() { - List> props = ResultViewerPersistence.getAllChildProperties(currentRootNode, 100); + List> props = ResultViewerPersistence.getAllChildProperties(rootNode, 100); // If node is not table filter node, use default order for columns - if (!(currentRootNode instanceof TableFilterNode)) { + if (!(rootNode instanceof TableFilterNode)) { return props; } - final TableFilterNode tfn = ((TableFilterNode) currentRootNode); + final TableFilterNode tfn = ((TableFilterNode) rootNode); propertiesMap.clear(); /* @@ -593,16 +595,6 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { return new ArrayList<>(propertiesMap.values()); } - /** - * Expands a given child node of the current root node. - * - * @param node Node to expand - */ - @Override - public void expandNode(Node node) { - outlineView.expandNode(node); - } - /** * Frees the resources that have been allocated by this tabular results * viewer, in preparation for permanently disposing of it. @@ -782,8 +774,8 @@ public final class DataResultViewerTable extends AbstractDataResultViewer { Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); // only override the color if a node is not selected - if (currentRootNode != null && !isSelected) { - Node node = currentRootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row)); + if (rootNode != null && !isSelected) { + Node node = rootNode.getChildren().getNodeAt(table.convertRowIndexToModel(row)); boolean tagFound = false; if (node != null) { Node.PropertySet[] propSets = node.getPropertySets(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java index fd4727ca7b..b1d268c5a4 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerThumbnail.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -60,66 +60,67 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; /** - * A thumbnail viewer for the results view, with paging support. + * A thumbnail result viewer, with paging support, that displays the children of + * the given root node using an IconView. The paging is intended to reduce + * memory footprint by loading no more than two humdred images at a time. * - * The paging is intended to reduce memory footprint by load only up to - * (currently) 200 images at a time. This works whether or not the underlying - * content nodes are being lazy loaded or not. - * - * TODO (JIRA-2658): Fix DataResultViewer extension point. When this is done, - * restore implementation of DataResultViewerTable as a DataResultViewer service - * provider. + * Instances of this class should use the explorer manager of an ancestor top + * component to connect the lookups of the nodes displayed in the IconView to + * the actions global context. The explorer manager can be supplied during + * construction, but the typical use case is for the result viewer to find the + * ancestor top component's explorer manager at runtime. */ @ServiceProvider(service = DataResultViewer.class) public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(DataResultViewerThumbnail.class.getName()); - private int curPage; - private int totalPages; - private int curPageImages; - private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; private final PageUpdater pageUpdater = new PageUpdater(); - private TableFilterNode tfn; - private ThumbnailViewChildren tvc; + private TableFilterNode rootNode; + private ThumbnailViewChildren rootNodeChildren; private NodeSelectionListener selectionListener; + private int currentPage; + private int totalPages; + private int currentPageImages; + private int thumbSize = ImageUtils.ICON_SIZE_MEDIUM; /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is NOT compatible with node multiple selection actions. + * Constructs a thumbnail result viewer, with paging support, that displays + * the children of the given root node using an IconView. The viewer should + * have an ancestor top component to connect the lookups of the nodes + * displayed in the IconView to the actions global context. The explorer + * manager will be discovered at runtime. */ public DataResultViewerThumbnail() { - super(); - initialize(); + this(null); } /** - * Constructs a thumbnail viewer for the results view, with paging support, - * that is compatible with node multiple selection actions. + * Constructs a thumbnail result viewer, with paging support, that displays + * the children of the given root node using an IconView. The viewer should + * have an ancestor top component to connect the lookups of the nodes + * displayed in the IconView to the actions global context. * - * @param explorerManager The shared ExplorerManager for the result viewers. + * @param explorerManager The explorer manager of the ancestor top + * component. */ - public DataResultViewerThumbnail(ExplorerManager explorerManager) { - super(explorerManager); - initialize(); - } - - @NbBundle.Messages({"DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", + @NbBundle.Messages({ + "DataResultViewerThumbnail.thumbnailSizeComboBox.small=Small Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.medium=Medium Thumbnails", "DataResultViewerThumbnail.thumbnailSizeComboBox.large=Large Thumbnails" }) - private void initialize() { + public DataResultViewerThumbnail(ExplorerManager explorerManager) { + super(explorerManager); initComponents(); iconView.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); -// this.getExplorerManager().addPropertyChangeListener(new ExplorerManagerNodeSelectionListener()); - thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>( - new String[]{Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), - Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), - Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); + thumbnailSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[]{ + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_small(), + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_medium(), + Bundle.DataResultViewerThumbnail_thumbnailSizeComboBox_large()})); thumbnailSizeComboBox.setSelectedIndex(1); - curPage = -1; + currentPage = -1; totalPages = 0; - curPageImages = 0; + currentPageImages = 0; } /** @@ -315,7 +316,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { private void sortButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_sortButtonActionPerformed List> childProperties = ResultViewerPersistence.getAllChildProperties(this.getExplorerManager().getRootContext(), 100); - SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(tfn)); + SortChooser sortChooser = new SortChooser(childProperties, ResultViewerPersistence.loadSortCriteria(rootNode)); DialogDescriptor dialogDescriptor = new DialogDescriptor(sortChooser, sortChooser.getDialogTitle()); Dialog createDialog = DialogDisplayer.getDefault().createDialog(dialogDescriptor); createDialog.setVisible(true); @@ -336,8 +337,8 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { Node.Property prop = childProperties.get(i); String propName = prop.getName(); SortCriterion criterion = criteriaMap.get(prop); - final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(tfn, propName); - final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(tfn, propName); + final String columnSortOrderKey = ResultViewerPersistence.getColumnSortOrderKey(rootNode, propName); + final String columnSortRankKey = ResultViewerPersistence.getColumnSortRankKey(rootNode, propName); if (criterion != null) { preferences.putBoolean(columnSortOrderKey, criterion.getSortOrder() == SortOrder.ASCENDING); @@ -347,7 +348,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { preferences.remove(columnSortRankKey); } } - setNode(tfn); //this is just to force a refresh + setNode(rootNode); //this is just to force a refresh } }//GEN-LAST:event_sortButtonActionPerformed @@ -383,26 +384,26 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { if (selectionListener == null) { this.getExplorerManager().addPropertyChangeListener(new NodeSelectionListener()); // RJCTODO: remove listener on cleanup } - if (tvc != null) { - tvc.cancelLoadingThumbnails(); + if (rootNodeChildren != null) { + rootNodeChildren.cancelLoadingThumbnails(); } try { if (givenNode != null) { - tfn = (TableFilterNode) givenNode; + rootNode = (TableFilterNode) givenNode; /* * Wrap the given node in a ThumbnailViewChildren that will * produce ThumbnailPageNodes with ThumbnailViewNode children * from the child nodes of the given node. */ - tvc = new ThumbnailViewChildren(givenNode, thumbSize); - final Node root = new AbstractNode(tvc); + rootNodeChildren = new ThumbnailViewChildren(givenNode, thumbSize); + final Node root = new AbstractNode(rootNodeChildren); pageUpdater.setRoot(root); root.addNodeListener(pageUpdater); this.getExplorerManager().setRootContext(root); } else { - tfn = null; - tvc = null; + rootNode = null; + rootNodeChildren = null; Node emptyNode = new AbstractNode(Children.LEAF); this.getExplorerManager().setRootContext(emptyNode); iconView.setBackground(Color.BLACK); @@ -426,8 +427,8 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { public void resetComponent() { super.resetComponent(); this.totalPages = 0; - this.curPage = -1; - curPageImages = 0; + this.currentPage = -1; + currentPageImages = 0; updateControls(); } @@ -439,15 +440,15 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { } private void nextPage() { - if (curPage < totalPages) { - curPage++; + if (currentPage < totalPages) { + currentPage++; switchPage(); } } private void previousPage() { - if (curPage > 1) { - curPage--; + if (currentPage > 1) { + currentPage--; switchPage(); } } @@ -469,7 +470,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { return; } - curPage = newPage; + currentPage = newPage; switchPage(); } @@ -494,9 +495,9 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { progress.switchToIndeterminate(); ExplorerManager explorerManager = DataResultViewerThumbnail.this.getExplorerManager(); Node root = explorerManager.getRootContext(); - Node pageNode = root.getChildren().getNodeAt(curPage - 1); + Node pageNode = root.getChildren().getNodeAt(currentPage - 1); explorerManager.setExploredContext(pageNode); - curPageImages = pageNode.getChildren().getNodesCount(); + currentPageImages = pageNode.getChildren().getNodesCount(); return null; } @@ -539,20 +540,19 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { sortLabel.setText(DataResultViewerThumbnail_sortLabel_text()); } else { - pageNumLabel.setText( - NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", - Integer.toString(curPage), Integer.toString(totalPages))); - final int imagesFrom = (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; - final int imagesTo = curPageImages + (curPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; + pageNumLabel.setText(NbBundle.getMessage(this.getClass(), "DataResultViewerThumbnail.pageNumbers.curOfTotal", + Integer.toString(currentPage), Integer.toString(totalPages))); + final int imagesFrom = (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE + 1; + final int imagesTo = currentPageImages + (currentPage - 1) * ThumbnailViewChildren.IMAGES_PER_PAGE; imagesRangeLabel.setText(imagesFrom + "-" + imagesTo); - pageNextButton.setEnabled(!(curPage == totalPages)); - pagePrevButton.setEnabled(!(curPage == 1)); + pageNextButton.setEnabled(!(currentPage == totalPages)); + pagePrevButton.setEnabled(!(currentPage == 1)); goToPageField.setEnabled(totalPages > 1); sortButton.setEnabled(true); thumbnailSizeComboBox.setEnabled(true); - if (tfn != null) { - String sortString = ResultViewerPersistence.loadSortCriteria(tfn).stream() + if (rootNode != null) { + String sortString = ResultViewerPersistence.loadSortCriteria(rootNode).stream() .map(SortCriterion::toString) .collect(Collectors.joining(" ")); sortString = StringUtils.defaultIfBlank(sortString, "---"); @@ -583,30 +583,30 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { totalPages = root.getChildren().getNodesCount(); if (totalPages == 0) { - curPage = -1; + currentPage = -1; updateControls(); return; } - if (curPage == -1 || curPage > totalPages) { - curPage = 1; + if (currentPage == -1 || currentPage > totalPages) { + currentPage = 1; } //force load the curPage node - final Node pageNode = root.getChildren().getNodeAt(curPage - 1); + final Node pageNode = root.getChildren().getNodeAt(currentPage - 1); //em.setSelectedNodes(new Node[]{pageNode}); if (pageNode != null) { pageNode.addNodeListener(new NodeListener() { @Override public void childrenAdded(NodeMemberEvent nme) { - curPageImages = pageNode.getChildren().getNodesCount(); + currentPageImages = pageNode.getChildren().getNodesCount(); updateControls(); } @Override public void childrenRemoved(NodeMemberEvent nme) { - curPageImages = 0; + currentPageImages = 0; updateControls(); } @@ -632,7 +632,7 @@ public final class DataResultViewerThumbnail extends AbstractDataResultViewer { @Override public void childrenRemoved(NodeMemberEvent nme) { totalPages = 0; - curPage = -1; + currentPage = -1; updateControls(); } From f7fad3184effd84d4e888b2a808bcc6990af1bf1 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 23 Apr 2018 17:47:54 -0400 Subject: [PATCH 076/100] 3752 adjust ViewContextAction for encrypted volumes --- .../autopsy/directorytree/DataResultFilterNode.java | 9 ++++++++- .../autopsy/directorytree/ViewContextAction.java | 7 ++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index f51c94aaca..3aef1e5a2a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -386,8 +386,15 @@ public class DataResultFilterNode extends FilterNode { NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewFileInDir.text"), c)); } // action to go to the source file of the artifact - actionsList.add(new ViewContextAction( + // action to go to the source file of the artifact + Content fileContent = ban.getLookup().lookup(AbstractFile.class); + if (fileContent == null) { + Content content = ban.getLookup().lookup(Content.class); + actionsList.add(new ViewContextAction("View Source Content in Directory", content)); + } else { + actionsList.add(new ViewContextAction( NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewSrcFileInDir.text"), ban)); + } } Content c = ban.getLookup().lookup(File.class); Node n = null; diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java index 99d54e61a9..792252605a 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ViewContextAction.java @@ -72,16 +72,13 @@ public class ViewContextAction extends AbstractAction { */ public ViewContextAction(String displayName, BlackboardArtifactNode artifactNode) { super(displayName); - Content fileContent = artifactNode.getLookup().lookup(AbstractFile.class); - if (fileContent != null) { - this.content = fileContent; + this.content = artifactNode.getLookup().lookup(AbstractFile.class); + if (this.content != null) { AbstractFile file = (AbstractFile) content; if ((TskData.FileKnown.KNOWN == file.getKnown() && UserPreferences.hideKnownFilesInDataSourcesTree()) || (TskData.TSK_DB_FILES_TYPE_ENUM.SLACK == file.getType() && UserPreferences.hideSlackFilesInDataSourcesTree())) { this.setEnabled(false); } - } else { - this.content = artifactNode.getLookup().lookup(Content.class); } } From c36a1e6191bf6fa0f2a84a71dbd2051e276b711a Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Mon, 23 Apr 2018 18:05:59 -0400 Subject: [PATCH 077/100] 3752 clean up - make comments and names more accurate --- ...yptionDetectionDataSourceIngestModule.java | 27 ++++++++++--------- .../EncryptionDetectionTools.java | 25 ++++++++++------- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java index cb9524df8c..4160ef1c49 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -42,10 +42,7 @@ import org.sleuthkit.datamodel.Volume; import org.sleuthkit.datamodel.VolumeSystem; /** - * Sample data source ingest module that doesn't do much. Demonstrates per - * ingest job module settings, checking for job cancellation, updating the - * DataSourceIngestModuleProgress object, and use of a subset of the available - * ingest services. + * Data source module to detect encryption. */ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule { @@ -55,7 +52,13 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul private double calculatedEntropy; private final double minimumEntropy; - + + /** + * Create a EncryptionDetectionDataSourceIngestModule object that will detect + * volumes that are encrypted and create blackboard artifacts as appropriate. + * The supplied EncryptionDetectionIngestJobSettings object is used to + * configure the module. + */ EncryptionDetectionDataSourceIngestModule(EncryptionDetectionIngestJobSettings settings) { minimumEntropy = settings.getMinimumEntropy(); } @@ -80,7 +83,7 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul for (VolumeSystem volumeSystem : volumeSystems) { for (Volume volume : volumeSystem.getVolumes()) { if (volume.getFileSystems().isEmpty()) { - if (isDataSourceEncrypted(volume)) { + if (isVolumeEncrypted(volume)) { System.out.println("VOLUME ENCRYPTED"); return flagVolume(volume); } @@ -106,9 +109,9 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul /** * Create a blackboard artifact. * - * @param The file to be processed. + * @param The volume to be processed. * - * @return 'OK' if the file was processed successfully, or 'ERROR' if there + * @return 'OK' if the volume was processed successfully, or 'ERROR' if there * was a problem. */ private IngestModule.ProcessResult flagVolume(Volume volume) { @@ -157,9 +160,9 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul * * @param file AbstractFile to be checked. * - * @return True if the AbstractFile is encrypted. + * @return True if the Volume is encrypted. */ - private boolean isDataSourceEncrypted(Content dataSource) throws ReadContentInputStream.ReadContentInputStreamException, IOException, TskCoreException { + private boolean isVolumeEncrypted(Volume volume) throws ReadContentInputStream.ReadContentInputStreamException, IOException, TskCoreException { /* * Criteria for the checks in this method are partially based on * http://www.forensicswiki.org/wiki/TrueCrypt#Detection @@ -168,10 +171,8 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul boolean possiblyEncrypted = true; if (possiblyEncrypted) { - System.out.println("CALCULATE ENTROPY"); - calculatedEntropy = EncryptionDetectionTools.calculateEntropy(dataSource); + calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume); if (calculatedEntropy >= minimumEntropy) { - System.out.println("ENTROPY INDICATED ENCRYPTED DS"); return true; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java index c42c6af1ec..075af04726 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java @@ -37,17 +37,25 @@ final class EncryptionDetectionTools { static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; - + @NbBundle.Messages({ "EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.", "EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." }) + /** + * Check if the minimum entropy setting is in the accepted range for this + * module. + */ static void validateMinEntropyValue(double minimumEntropy) throws IngestModule.IngestModuleException { if (minimumEntropy < MINIMUM_ENTROPY_INPUT_RANGE_MIN || minimumEntropy > MINIMUM_ENTROPY_INPUT_RANGE_MAX) { throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumEntropyInput()); } } + /** + * Check if the minimum file size setting is in the accepted range for this + * module. + */ static void validateMinFileSizeValue(int minimumFileSize) throws IngestModule.IngestModuleException { if (minimumFileSize < MINIMUM_FILE_SIZE_INPUT_RANGE_MIN) { throw new IngestModule.IngestModuleException(Bundle.EncryptionDetectionTools_errorMessage_minimumFileSizeInput()); @@ -55,17 +63,17 @@ final class EncryptionDetectionTools { } /** - * Calculate the entropy of the file. The result is used to qualify the file - * as an encrypted file. + * Calculate the entropy of the content. The result is used to qualify the + * content as an encrypted content. * - * @param file The file to be calculated against. + * @param content The content to be calculated against. * - * @return The entropy of the file. + * @return The entropy of the content. * * @throws IOException If there is a failure closing or reading from the * InputStream. */ - static double calculateEntropy(Content file) throws ReadContentInputStream.ReadContentInputStreamException, IOException { + static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException { /* * Logic in this method is based on * https://github.com/willjasen/entropy/blob/master/entropy.java @@ -75,7 +83,7 @@ final class EncryptionDetectionTools { BufferedInputStream bin = null; try { - in = new ReadContentInputStream(file); + in = new ReadContentInputStream(content); bin = new BufferedInputStream(in); /* @@ -90,7 +98,7 @@ final class EncryptionDetectionTools { /* * Calculate the entropy based on the byte occurence counts. */ - long dataLength = file.getSize() - 1; + long dataLength = content.getSize() - 1; double entropyAccumulator = 0; for (int i = 0; i < BYTE_OCCURENCES_BUFFER_SIZE; i++) { if (byteOccurences[i] > 0) { @@ -98,7 +106,6 @@ final class EncryptionDetectionTools { entropyAccumulator += (byteProbability * Math.log(byteProbability) * ONE_OVER_LOG2); } } - System.out.println("ENTROPY VALUE: " + -entropyAccumulator); return -entropyAccumulator; } finally { From 7c9ffbe2fa2e039d90d821e58aef28632736511d Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Mon, 23 Apr 2018 22:22:01 -0400 Subject: [PATCH 078/100] PDF password detection implemented. --- Core/nbproject/project.properties | 3 +++ Core/nbproject/project.xml | 12 ++++++++++++ .../EncryptionDetectionFileIngestModule.java | 3 ++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 02df4b01bb..80652be5d2 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -19,6 +19,9 @@ file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar +file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar +file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar +file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 5dabff9f5d..aceef225d8 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -415,6 +415,18 @@ ext/tika-parsers-1.17.jar release/modules/ext/tika-parsers-1.17.jar + + ext/fontbox-2.0.8.jar + release/modules/ext/fontbox-2.0.8.jar + + + ext/pdfbox-2.0.8.jar + release/modules/ext/pdfbox-2.0.8.jar + + + ext/pdfbox-tools-2.0.8.jar + release/modules/ext/pdfbox-tools-2.0.8.jar + ext/sqlite-jdbc-3.8.11.jar release/modules/ext/sqlite-jdbc-3.8.11.jar diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index 89cc99230e..cb92b59718 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -249,6 +249,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter case "application/msword": case "application/vnd.ms-excel": case "application/vnd.ms-powerpoint": + case "application/pdf": /* * A file of one of these types will be determined to be * password protected or not by attempting to parse it via Tika. @@ -266,7 +267,7 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter parser.parse(bin, handler, metadata, new ParseContext()); } catch (EncryptedDocumentException ex) { /* - * Office OLE2 file is determined to be password protected. + * File is determined to be password protected. */ passwordProtected = true; } finally { From 19983efaac5d3ff5f09d6d0ff996f1cc609c06ce Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 24 Apr 2018 11:17:21 -0400 Subject: [PATCH 079/100] Improving hash metric --- .../EnterpriseHealthMonitor.java | 31 ++----------------- .../autopsy/healthmonitor/TimingMetric.java | 4 +-- .../hashdatabase/HashDbIngestModule.java | 14 +++++++-- 3 files changed, 16 insertions(+), 33 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 3d91bff41a..9bac941424 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -65,7 +65,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private final static String DATABASE_NAME = "EnterpriseHealthMonitor"; private final static String MODULE_NAME = "EnterpriseHealthMonitor"; private final static String IS_ENABLED_KEY = "is_enabled"; - private final static long DATABASE_WRITE_INTERVAL = 1; // Minutes + private final static long DATABASE_WRITE_INTERVAL = 60; // Minutes public static final CaseDbSchemaVersionNumber CURRENT_DB_SCHEMA_VERSION = new CaseDbSchemaVersionNumber(1, 0); @@ -294,9 +294,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { if(isEnabled.get() && (metric != null)) { metric.stopTiming(); try { - System.out.print("### " + metric.getName() + " : " + normalization + "," + metric.getDuration() + ","); metric.normalize(normalization); - System.out.println(metric.getDuration()); getInstance().addTimingMetric(metric); } catch (HealthMonitorException ex) { // We don't want calling methods to have to check for exceptions, so just log it @@ -310,7 +308,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * @param metric The metric to add. stopTiming() should already have been called. */ private void addTimingMetric(TimingMetric metric) throws HealthMonitorException { - // Do as little as possible within the synchronized block to minimize // blocking with multiple threads. synchronized(this) { @@ -340,7 +337,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { private void performDatabaseQuery() throws HealthMonitorException { try { SleuthkitCase skCase = Case.getOpenCase().getSleuthkitCase(); - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query (normalized based on number of images)"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Database: getImages query"); List images = skCase.getImages(); // Through testing we found that this normalization gives us fairly @@ -357,19 +354,6 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization); - /*metric.stopTiming(); - - // TEMP TEMP TEMP - synchronized(this) { - if(timingInfoMap.containsKey(metric.getName())) { - //timingInfoMap.get(metric.getName()).addMetric(metric); - } else { - TimingInfo info = new TimingInfo(metric); - info.max = images.size(); - timingInfoMap.put(metric.getName(), info); - } - }*/ - } catch (NoCurrentCaseException ex) { // If there's no case open, we just can't do the metrics } catch (Exception ex) { @@ -383,7 +367,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { */ private void gatherTimerBasedMetrics() throws HealthMonitorException { // Time a database query - //performDatabaseQuery(); // TEMP TEMP put back + performDatabaseQuery(); } /** @@ -740,21 +724,12 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { } } - public static void tempDoTimer() { - try { - getInstance().healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask()); - } catch (Exception ex) { - - } - } - @Override public void propertyChange(PropertyChangeEvent evt) { switch (Case.Events.valueOf(evt.getPropertyName())) { case CURRENT_CASE: - case DATA_SOURCE_ADDED: // TEMP TEMP if ((null == evt.getNewValue()) && (evt.getOldValue() instanceof Case)) { // When a case is closed, write the current metrics to the database healthMonitorExecutor.submit(new EnterpriseHealthMonitor.DatabaseWriteTask()); diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java index 4423ff24f4..d44ceca910 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/TimingMetric.java @@ -74,9 +74,9 @@ public class TimingMetric { if (duration != null) { if(count < 0) { throw new HealthMonitorException("normalize() called with negative count (" + count + ")"); - } else if(count > 1) { // Small optimization to prevent dividing by one + } else if(count > 1) { duration = duration / count; - } // If count = 0, do nothing + } // If count is 0 or 1, do nothing } else { throw new HealthMonitorException("normalize() called before stopTiming()"); } diff --git a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java index 5f9e3808fc..ea93d10434 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/hashdatabase/HashDbIngestModule.java @@ -184,11 +184,19 @@ public class HashDbIngestModule implements FileIngestModule { String md5Hash = file.getMd5Hash(); if (md5Hash == null || md5Hash.isEmpty()) { try { - TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk: Hash Calculation (time per byte nono)"); + TimingMetric metric = EnterpriseHealthMonitor.getTimingMetric("Disk Reads: Hash calculation"); long calcstart = System.currentTimeMillis(); md5Hash = HashUtility.calculateMd5Hash(file); - if(file.getSize() > 0) { - EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, 1); + if (file.getSize() > 0) { + // Surprisingly, the hash calculation does not seem to be correlated that + // strongly with file size until the files get large. + // Only normalize if the file size is greater than ~1MB. + if (file.getSize() < 1000000) { + EnterpriseHealthMonitor.submitTimingMetric(metric); + } else { + // In testing, this normalization gave reasonable resuls + EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, file.getSize() / 500000); + } } file.setMd5Hash(md5Hash); long delta = (System.currentTimeMillis() - calcstart); From 6d9256469f30096542253c789a5087d10c8870c0 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Tue, 24 Apr 2018 11:33:49 -0400 Subject: [PATCH 080/100] Cleanup --- .../autopsy/healthmonitor/EnterpriseHealthMonitor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java index 9bac941424..2186dc3be1 100644 --- a/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java +++ b/Core/src/org/sleuthkit/autopsy/healthmonitor/EnterpriseHealthMonitor.java @@ -50,6 +50,7 @@ import org.sleuthkit.datamodel.CaseDbConnectionInfo; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; /** @@ -308,6 +309,7 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { * @param metric The metric to add. stopTiming() should already have been called. */ private void addTimingMetric(TimingMetric metric) throws HealthMonitorException { + // Do as little as possible within the synchronized block to minimize // blocking with multiple threads. synchronized(this) { @@ -355,9 +357,9 @@ public final class EnterpriseHealthMonitor implements PropertyChangeListener { EnterpriseHealthMonitor.submitNormalizedTimingMetric(metric, normalization); } catch (NoCurrentCaseException ex) { - // If there's no case open, we just can't do the metrics - } catch (Exception ex) { - //bleh + // If there's no case open, we just can't do the metrics. + } catch (TskCoreException ex) { + throw new HealthMonitorException("Error running getImages()", ex); } } From a9acec5f2f0dcbffb444f80ce1c59ecf58e4fdc0 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Tue, 24 Apr 2018 12:31:52 -0400 Subject: [PATCH 081/100] Functional test now includes checks on PDF files. --- Core/nbproject/project.properties | 6 +++++ Core/nbproject/project.xml | 24 +++++++++++++++++++ .../EncryptionDetectionTest.java | 5 ++-- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 80652be5d2..958cf79f90 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -19,6 +19,12 @@ file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar +file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar +file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0.1.jar +file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar +file.reference.cxf-rt-frontend-jaxrs-3.0.16.jar=release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar +file.reference.cxf-rt-rs-client-3.0.16.jar=release/modules/ext/cxf-rt-rs-client-3.0.16.jar +file.reference.cxf-rt-transports-http-3.0.16.jar=release/modules/ext/cxf-rt-transports-http-3.0.16.jar file.reference.fontbox-2.0.8.jar=release/modules/ext/fontbox-2.0.8.jar file.reference.pdfbox-2.0.8.jar=release/modules/ext/pdfbox-2.0.8.jar file.reference.pdfbox-tools-2.0.8.jar=release/modules/ext/pdfbox-tools-2.0.8.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index aceef225d8..77141b5d33 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -411,6 +411,30 @@ ext/curator-client-2.8.0.jar release/modules/ext/curator-client-2.8.0.jar + + ext/jempbox-1.8.13.jar + release/modules/ext/jempbox-1.8.13.jar + + + ext/javax.ws.rs-api-2.0.1.jar + release/modules/ext/javax.ws.rs-api-2.0.1.jar + + + ext/cxf-rt-rs-client-3.0.16.jar + release/modules/ext/cxf-rt-rs-client-3.0.16.jar + + + ext/cxf-rt-transports-http-3.0.16.jar + release/modules/ext/cxf-rt-transports-http-3.0.16.jar + + + ext/cxf-core-3.0.16.jar + release/modules/ext/cxf-core-3.0.16.jar + + + ext/cxf-rt-frontend-jaxrs-3.0.16.jar + release/modules/ext/cxf-rt-frontend-jaxrs-3.0.16.jar + ext/tika-parsers-1.17.jar release/modules/ext/tika-parsers-1.17.jar diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 55d2a1ebdd..f6ac666e96 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -139,6 +139,7 @@ public class EncryptionDetectionTest extends NbTestCase { Blackboard bb = openCase.getServices().getBlackboard(); List results = fileManager.findFiles("%%", "ole2"); results.addAll(fileManager.findFiles("%%", "ooxml")); + results.addAll(fileManager.findFiles("%%", "pdf")); for (AbstractFile file : results) { /* @@ -157,7 +158,7 @@ public class EncryptionDetectionTest extends NbTestCase { * TSK_ENCRYPTION_DETECTED artifact. */ int artifactsListSize = artifactsList.size(); - String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but 1 was expected", file.getName(), file.getId(), artifactsListSize); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but 1 was expected.", file.getName(), file.getId(), artifactsListSize); assertEquals(errorMessage, 1, artifactsListSize); String artifactTypeName = artifactsList.get(0).getArtifactTypeName(); @@ -168,7 +169,7 @@ public class EncryptionDetectionTest extends NbTestCase { * Check that the unprotected file has no artifacts. */ int artifactsListSize = artifactsList.size(); - String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but none were expected", file.getName(), file.getId(), artifactsListSize); + String errorMessage = String.format("File '%s' (objId=%d) has %d artifacts, but none were expected.", file.getName(), file.getId(), artifactsListSize); assertEquals(errorMessage, 0, artifactsListSize); } } From 520ad092b97e1c6c4171b2143aa44e7024a662e2 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 24 Apr 2018 14:05:04 -0400 Subject: [PATCH 082/100] 3752 fix imports and method access from merge conflicts with 3748 --- .../EncryptionDetectionFileIngestModule.java | 5 +++++ .../encryptiondetection/EncryptionDetectionTools.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java index db8205ca40..0da14af61f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionFileIngestModule.java @@ -21,6 +21,9 @@ package org.sleuthkit.autopsy.modules.encryptiondetection; import java.io.IOException; import java.util.Collections; import java.util.logging.Level; +import org.sleuthkit.datamodel.ReadContentInputStream; +import java.io.BufferedInputStream; +import java.io.InputStream; import org.apache.tika.exception.EncryptedDocumentException; import org.apache.tika.exception.TikaException; import org.apache.tika.metadata.Metadata; @@ -46,6 +49,8 @@ import org.sleuthkit.datamodel.TskData; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; + + /** * File ingest module to detect encryption and password protection. */ diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java index cdb2935764..c776a8d7d9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java @@ -78,7 +78,7 @@ final class EncryptionDetectionTools { * @throws IOException If there is a failure closing or * reading from the InputStream. */ - private double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException { + static double calculateEntropy(Content content) throws ReadContentInputStream.ReadContentInputStreamException, IOException { /* * Logic in this method is based on * https://github.com/willjasen/entropy/blob/master/entropy.java From 9e1bbf7fa5ed94ed4ec591eea0e015709410aa4b Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Tue, 24 Apr 2018 14:18:43 -0400 Subject: [PATCH 083/100] 3752 remove sys outs from debugging --- .../EncryptionDetectionDataSourceIngestModule.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java index 4160ef1c49..08d85ec4d5 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -77,14 +77,12 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { try { - System.out.println("PROCESS DS"); if (dataSource instanceof Image) { List volumeSystems = ((Image) dataSource).getVolumeSystems(); for (VolumeSystem volumeSystem : volumeSystems) { for (Volume volume : volumeSystem.getVolumes()) { if (volume.getFileSystems().isEmpty()) { if (isVolumeEncrypted(volume)) { - System.out.println("VOLUME ENCRYPTED"); return flagVolume(volume); } } From 749b8833e3468565d334cac1262b7f2c6497c035 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 26 Apr 2018 09:48:33 -0400 Subject: [PATCH 084/100] Update EmbeddedFileTest.java Fix spelling error --- .../src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 4f9c197773..70a5ea8bcf 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -94,7 +94,7 @@ public class EmbeddedFileTest extends NbTestCase { CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH); } - public void testEncription() { + public void testEncryption() { try { List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'"); String protectedName1 = "password_protected.zip"; From 9f9622c2c11dd453906a70fb49f032970de30052 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 26 Apr 2018 11:56:55 -0400 Subject: [PATCH 085/100] 3709: Add IngestUtils class for unit test. --- .../autopsy/ingest/EmbeddedFileTest.java | 12 ++-- .../autopsy/ingest/IngestFileFiltersTest.java | 47 ++++++------ .../autopsy/testutils/CaseUtils.java | 61 +++++----------- .../autopsy/testutils/IngestUtils.java | 72 +++++++++++++++++++ 4 files changed, 119 insertions(+), 73 deletions(-) create mode 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index 4f9c197773..fa7226512f 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -18,7 +18,6 @@ */ package org.sleuthkit.autopsy.ingest; -import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -36,6 +35,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobSettings.IngestType; import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory; import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; import org.sleuthkit.autopsy.testutils.CaseUtils; +import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.TskCoreException; @@ -63,7 +63,7 @@ public class EmbeddedFileTest extends NbTestCase { public void setUp() { CaseUtils.createCase(CASE_DIRECTORY_PATH); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); try { openCase = Case.getOpenCase(); @@ -72,8 +72,8 @@ public class EmbeddedFileTest extends NbTestCase { Assert.fail(ex); } - IngestModuleTemplate embeddedTemplate = CaseUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()); - IngestModuleTemplate hashLookupTemplate = CaseUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); + IngestModuleTemplate embeddedTemplate = IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory()); + IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory()); ArrayList templates = new ArrayList<>(); templates.add(embeddedTemplate); @@ -81,7 +81,7 @@ public class EmbeddedFileTest extends NbTestCase { IngestJobSettings ingestJobSettings = new IngestJobSettings(EmbeddedFileTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); try { - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); } catch (TskCoreException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -94,7 +94,7 @@ public class EmbeddedFileTest extends NbTestCase { CaseUtils.deleteCaseDir(CASE_DIRECTORY_PATH); } - public void testEncription() { + public void testEncryption() { try { List results = openCase.getSleuthkitCase().findAllFilesWhere("name LIKE '%%'"); String protectedName1 = "password_protected.zip"; diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java index 73aaf245cc..70415d3c7e 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/IngestFileFiltersTest.java @@ -45,6 +45,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCo import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory; import org.sleuthkit.autopsy.testutils.CaseUtils; import org.sleuthkit.autopsy.testutils.IngestJobRunner; +import org.sleuthkit.autopsy.testutils.IngestUtils; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -75,7 +76,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testBasicDir"); CaseUtils.createCase(casePath); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); HashMap rule = new HashMap<>(); rule.put("Rule", new Rule("testFileType", null, new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); @@ -85,9 +86,9 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, dirFilter); - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); FileManager fileManager = openCase.getServices().getFileManager(); List results = fileManager.findFiles("file.jpg", "dir1"); String mimeType = results.get(0).getMIMEType(); @@ -118,7 +119,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithOneRule"); CaseUtils.createCase(casePath); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); HashMap rules = new HashMap<>(); rules.put("Rule", new Rule("testExtAndDirWithOneRule", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), new ParentPathCondition("dir1"), null, null, null)); @@ -128,9 +129,9 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter); - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(62, results.size()); @@ -154,7 +155,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testExtAndDirWithTwoRules"); CaseUtils.createCase(casePath); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); HashMap rules = new HashMap<>(); rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); @@ -165,9 +166,9 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, filesExtDirsFilter); - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(62, results.size()); @@ -199,7 +200,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testFullFileNameRule"); CaseUtils.createCase(casePath); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); HashMap rules = new HashMap<>(); rules.put("rule", new Rule("FindFileWithFullName", new FullNameCondition("file.docx"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); @@ -209,9 +210,9 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, fullNameFilter); - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(62, results.size()); @@ -235,7 +236,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingWithExtRuleAndUnallocSpace"); CaseUtils.createCase(casePath); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); HashMap rules = new HashMap<>(); rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); @@ -247,10 +248,10 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); - templates.add(CaseUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, extensionFilter); - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); List results = fileManager.findFiles("%%"); assertEquals(70, results.size()); @@ -284,7 +285,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testCarvingNoUnallocatedSpace"); CaseUtils.createCase(casePath); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, IMAGE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, IMAGE_PATH); HashMap rules = new HashMap<>(); rules.put("rule1", new Rule("FindJpgExtention", new ExtensionCondition("jpg"), new MetaTypeCondition(MetaTypeCondition.Type.FILES), null, null, null, null)); @@ -296,8 +297,8 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); - templates.add(CaseUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates, extensionFilter); try { List errs = IngestJobRunner.runIngestJob(openCase.getDataSources(), ingestJobSettings); @@ -318,7 +319,7 @@ public class IngestFileFiltersTest extends NbTestCase { Path casePath = Paths.get(System.getProperty("java.io.tmpdir"), "testEmbeddedModule"); CaseUtils.createCase(casePath); LocalFilesDSProcessor dataSourceProcessor = new LocalFilesDSProcessor(); - CaseUtils.addDataSourceToCase(dataSourceProcessor, ZIPFILE_PATH); + IngestUtils.addDataSource(dataSourceProcessor, ZIPFILE_PATH); //Build the filter to find jpg files HashMap rules = new HashMap<>(); @@ -331,10 +332,10 @@ public class IngestFileFiltersTest extends NbTestCase { try { Case openCase = Case.getOpenCase(); ArrayList templates = new ArrayList<>(); - templates.add(CaseUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); - templates.add(CaseUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory())); + templates.add(IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory())); IngestJobSettings ingestJobSettings = new IngestJobSettings(IngestFileFiltersTest.class.getCanonicalName(), IngestJobSettings.IngestType.FILES_ONLY, templates, embeddedFilter); - CaseUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); + IngestUtils.runIngestJob(openCase.getDataSources(), ingestJobSettings); FileManager fileManager = Case.getOpenCase().getServices().getFileManager(); //get all .jpg files in zip file List results = fileManager.findFiles("%%"); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java index ed71d80621..dad3f6d142 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java @@ -1,14 +1,25 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.sleuthkit.autopsy.testutils; import java.io.IOException; import java.nio.file.Path; -import java.util.List; -import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import org.apache.commons.io.FileUtils; @@ -17,13 +28,6 @@ import org.python.icu.impl.Assert; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseDetails; -import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; -import org.sleuthkit.autopsy.ingest.IngestJobSettings; -import org.sleuthkit.autopsy.ingest.IngestModuleError; -import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; -import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; -import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; -import org.sleuthkit.datamodel.Content; public final class CaseUtils { @@ -62,19 +66,7 @@ public final class CaseUtils { Assert.fail(ex); } } - - public static void addDataSourceToCase(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) { - try { - DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath); - List errorMessages = callBack.getErrorMessages(); - assertEquals(0, errorMessages.size()); - } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - - } - } - + public static void deleteCaseDir(Path caseDirectoryPath) { if (!caseDirectoryPath.toFile().exists()) { return; @@ -87,23 +79,4 @@ public final class CaseUtils { } } - public static void runIngestJob(List datasources, IngestJobSettings ingestJobSettings) { - try { - List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); - for (IngestModuleError err : errs) { - System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); - } - assertEquals(0, errs.size()); - } catch (InterruptedException ex) { - Exceptions.printStackTrace(ex); - Assert.fail(ex); - } - } - - public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { - IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); - IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); - template.setEnabled(true); - return template; - } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java new file mode 100755 index 0000000000..893f2d8cb2 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java @@ -0,0 +1,72 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.testutils; + +import java.nio.file.Path; +import java.util.List; +import static junit.framework.Assert.assertEquals; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.datamodel.Content; + +public final class IngestUtils { + + private IngestUtils() { + } + + public static void addDataSource(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) { + try { + DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath); + List errorMessages = callBack.getErrorMessages(); + assertEquals(0, errorMessages.size()); + } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + + } + } + + public static void runIngestJob(List datasources, IngestJobSettings ingestJobSettings) { + try { + List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); + for (IngestModuleError err : errs) { + System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); + } + assertEquals(0, errs.size()); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { + IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); + template.setEnabled(true); + return template; + } + +} From 57e644fbb89f36f19fd3f1fe362878b7ed842b51 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 26 Apr 2018 13:34:31 -0400 Subject: [PATCH 086/100] Add testutils to custom release May 2018 branch --- .../autopsy/testutils/CaseUtils.java | 82 +++++++++++++++++++ .../autopsy/testutils/IngestUtils.java | 72 ++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java create mode 100755 Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java new file mode 100755 index 0000000000..dad3f6d142 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/CaseUtils.java @@ -0,0 +1,82 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.testutils; + +import java.io.IOException; +import java.nio.file.Path; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; +import org.apache.commons.io.FileUtils; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; + +public final class CaseUtils { + + private CaseUtils() { + } + + public static void createCase(Path caseDirectoryPath) { + //Make sure the test is starting with a clean state. So delete the test directory, if it exists. + deleteCaseDir(caseDirectoryPath); + assertFalse("Unable to delete existing test directory", caseDirectoryPath.toFile().exists()); + + // Create the test directory + caseDirectoryPath.toFile().mkdirs(); + assertTrue("Unable to create test directory", caseDirectoryPath.toFile().exists()); + + try { + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), new CaseDetails("IngestFiltersTest")); + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + assertTrue(caseDirectoryPath.toFile().exists()); + } + + public static void closeCase() { + try { + Case.closeCurrentCase(); + //Seems like we need some time to close the case, so file handler later can delete the case directory. + try { + Thread.sleep(20000); + } catch (Exception ex) { + + } + } catch (CaseActionException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + public static void deleteCaseDir(Path caseDirectoryPath) { + if (!caseDirectoryPath.toFile().exists()) { + return; + } + try { + FileUtils.deleteDirectory(caseDirectoryPath.toFile()); + } catch (IOException ex) { + //We just want to make sure the case directory doesn't exist when the test starts. It shouldn't cause failure if the case directory couldn't be deleted after a test finished. + System.out.println("INFO: Unable to delete case directory: " + caseDirectoryPath.toString()); + } + } + +} diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java new file mode 100755 index 0000000000..893f2d8cb2 --- /dev/null +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/testutils/IngestUtils.java @@ -0,0 +1,72 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.sleuthkit.autopsy.testutils; + +import java.nio.file.Path; +import java.util.List; +import static junit.framework.Assert.assertEquals; +import org.openide.util.Exceptions; +import org.python.icu.impl.Assert; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleTemplate; +import org.sleuthkit.datamodel.Content; + +public final class IngestUtils { + + private IngestUtils() { + } + + public static void addDataSource(AutoIngestDataSourceProcessor dataSourceProcessor, Path dataSourcePath) { + try { + DataSourceProcessorRunner.ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, dataSourcePath); + List errorMessages = callBack.getErrorMessages(); + assertEquals(0, errorMessages.size()); + } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + + } + } + + public static void runIngestJob(List datasources, IngestJobSettings ingestJobSettings) { + try { + List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); + for (IngestModuleError err : errs) { + System.out.println(String.format("Error: %s: %s.", err.getModuleDisplayName(), err.toString())); + } + assertEquals(0, errs.size()); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + public static IngestModuleTemplate getIngestModuleTemplate(IngestModuleFactoryAdapter factory) { + IngestModuleIngestJobSettings settings = factory.getDefaultIngestJobSettings(); + IngestModuleTemplate template = new IngestModuleTemplate(factory, settings); + template.setEnabled(true); + return template; + } + +} From 6484e6b4d6c676e6342eecb2f43b47d255c006d4 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\zhaohui" Date: Thu, 26 Apr 2018 17:01:49 -0400 Subject: [PATCH 087/100] 3709: All test cases for Embedded file tests. --- .../autopsy/ingest/EmbeddedFileTest.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java index fa7226512f..36ecd56d8f 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/ingest/EmbeddedFileTest.java @@ -126,4 +126,153 @@ public class EmbeddedFileTest extends NbTestCase { } + public void testBigFolder() { + final int numOfFilesToTest = 1000; + try { + //Get all files under 'big folder' directory except '.' '..' 'slack' files + List results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%big folder/' and name != '.' and name != '..' and extension NOT LIKE '%slack'"); + assertEquals(numOfFilesToTest, results.size()); //There are 1000 files + int numOfFilesTested = 0; + for (AbstractFile file : results) { + String fileName = file.getName(); + //File name should like file1.txt, file2.txt ... file1000.txt + String errMsg = String.format("File name %s doesn't follow the expected naming convention: fileNaturalNumber.txt, eg. file234.txt.", fileName); + assertTrue(errMsg, file.getName().matches("file[1-9]\\d*.txt")); + String hashValue = file.getMd5Hash(); + //All files have the same hash value + assertEquals(HASH_VALUE, hashValue); + numOfFilesTested++; + } + //Make sure 1000 files have been tested + assertEquals(numOfFilesToTest, numOfFilesTested); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testDeepFolder() { + try { + //Get all files under 'deep folder' directory except '.' '..' + List results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%deep folder/' and name != '.' and name != '..'"); + assertEquals(1, results.size()); + StringBuffer dirReached = new StringBuffer(); + ArrayList fileReached = new ArrayList<>(); + checkEachFileInDeepFolder(results.get(0), dirReached, fileReached, 0); + //Check that all 25 folders/files have been reached + assertEquals(DEEP_FOLDER_COUNT, fileReached.size()); + //Make sure the test reached the last directory 'dir25'. The whole directory is dir1/dir2...dir24/dir25/ + assertTrue(dirReached.toString().startsWith("dir1/dir2/")); + assertTrue(dirReached.toString().endsWith("dir24/dir25/")); + //Make sure the test reached the last file.txt in dir1/dir2...dir24/dir25/ + assertTrue(fileReached.get(0).endsWith(dirReached.toString() + "file.txt")); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testEmbeddedFile() { + try { + //Query level3.txt under '/ZIP/embedded/level3.zip/' + List results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level3.txt' and parent_path = '/ZIP/embedded/level3.zip/'"); + assertEquals(1, results.size()); + + //Query level2.txt under '/ZIP/embedded/level3.zip/level2.zip/' + results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level2.txt' and parent_path = '/ZIP/embedded/level3.zip/level2.zip/'"); + assertEquals(1, results.size()); + + //Query level1.txt under '/ZIP/embedded/level3.zip/level2.zip/level1.zip/' + results = openCase.getSleuthkitCase().findAllFilesWhere("name = 'level1.txt' and parent_path = '/ZIP/embedded/level3.zip/level2.zip/level1.zip/'"); + assertEquals(1, results.size()); + + //Confirm that we can reach level1.txt from the embedded folder + results = openCase.getSleuthkitCase().findAllFilesWhere("parent_path LIKE '%embedded/' and name != '.' and name != '..' and extension NOT LIKE '%slack%'"); + assertEquals(1, results.size()); + assertTrue(checkFileInEmbeddedFolder(results.get(0))); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + + } + + public void testContent() { + final int numOfFilesToTest = 1029; + try { + //All files with txt extension should have the same hash value, + //except the zip file with txt extension and the .txt files extracted from password protected zip shouldn't have hash value + List results = openCase.getSleuthkitCase().findAllFilesWhere( + "extension = 'txt' and name != 'zipFileWithTxtExtension.txt' and parent_path NOT LIKE '%_protected%'"); + assertEquals(numOfFilesToTest, results.size()); + int numOfHashTested = 0; + for (AbstractFile file : results) { + String fileName = file.getName(); + String errMsg = String.format("File name %s doesn't have the extected hash value %s.", fileName, HASH_VALUE); + assertEquals(errMsg, HASH_VALUE, file.getMd5Hash()); + numOfHashTested++; + } + //Make sure the hash value of 1029 files have been tested + assertEquals(numOfFilesToTest, numOfHashTested); + + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + public void testExtension() { + try { + //Query zipFileWithTxtExtension.txt at extension folder + List results = openCase.getSleuthkitCase().findAllFilesWhere("extension = 'txt' and parent_path = '/ZIP/extension/zipFileWithTxtExtension.txt/'"); + assertEquals(1, results.size()); + assertEquals("file.txt wasn't extracted from the file: zipFileWithTxtExtension.txt", "file.txt", results.get(0).getName()); + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } + + private void checkEachFileInDeepFolder(AbstractFile file, StringBuffer dirReached, ArrayList fileReached, int numOfDir) { + String errMsg = String.format("File/Directory name is not as expected name: %s", file.getName()); + if (file.isDir() && !file.getName().equals(".") && !file.getName().equals("..")) { + numOfDir++; + assertEquals(errMsg, String.format("dir%d", numOfDir), file.getName()); + dirReached.append(file.getName()).append("/"); + try { + List children = file.listFiles(); + for (AbstractFile child : children) { + checkEachFileInDeepFolder(child, dirReached, fileReached, numOfDir); + } + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } else if (file.isFile() && !file.getName().endsWith("slack")) { + assertEquals(errMsg, "file.txt", file.getName()); + fileReached.add(file.getParentPath() + file.getName()); + } + } + + private boolean checkFileInEmbeddedFolder(AbstractFile file) { + if (file.getName().equals("level1.txt")) { + return true; + } else if (file.getNameExtension().equalsIgnoreCase("zip")) { + try { + List children = file.listFiles(); + for (AbstractFile child : children) { + return checkFileInEmbeddedFolder(child); + } + } catch (TskCoreException ex) { + Exceptions.printStackTrace(ex); + Assert.fail(ex); + } + } else { + assertTrue(file.getNameExtension().equalsIgnoreCase("txt")); + } + + return false; + } } From 30020ec60231fea3046ed1fa49c0506b225def96 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 26 Apr 2018 17:15:33 -0400 Subject: [PATCH 088/100] 3752 fix issues found in review. comments, and other minor things --- ...yptionDetectionDataSourceIngestModule.java | 47 +++++++++---------- .../EncryptionDetectionTools.java | 23 +++++---- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java index 08d85ec4d5..a269b2bdd2 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionDataSourceIngestModule.java @@ -44,20 +44,19 @@ import org.sleuthkit.datamodel.VolumeSystem; /** * Data source module to detect encryption. */ -class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule { +final class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModule { private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger(EncryptionDetectionModuleFactory.getModuleName()); private Blackboard blackboard; private double calculatedEntropy; - private final double minimumEntropy; - + /** - * Create a EncryptionDetectionDataSourceIngestModule object that will detect - * volumes that are encrypted and create blackboard artifacts as appropriate. - * The supplied EncryptionDetectionIngestJobSettings object is used to - * configure the module. + * Create an EncryptionDetectionDataSourceIngestModule object that will + * detect volumes that are encrypted and create blackboard artifacts as + * appropriate. The supplied EncryptionDetectionIngestJobSettings object is + * used to configure the module. */ EncryptionDetectionDataSourceIngestModule(EncryptionDetectionIngestJobSettings settings) { minimumEntropy = settings.getMinimumEntropy(); @@ -81,10 +80,8 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul List volumeSystems = ((Image) dataSource).getVolumeSystems(); for (VolumeSystem volumeSystem : volumeSystems) { for (Volume volume : volumeSystem.getVolumes()) { - if (volume.getFileSystems().isEmpty()) { - if (isVolumeEncrypted(volume)) { - return flagVolume(volume); - } + if (isVolumeEncrypted(volume)) { + return flagVolume(volume); } } } @@ -100,6 +97,14 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul return IngestModule.ProcessResult.OK; } + /** + * Validate the relevant settings for the + * EncryptionDetectionDataSourceIngestModule + * + * @throws IngestModule.IngestModuleException If the input is empty, + * invalid, or out of range. + * + */ private void validateSettings() throws IngestModule.IngestModuleException { EncryptionDetectionTools.validateMinEntropyValue(minimumEntropy); } @@ -109,8 +114,8 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul * * @param The volume to be processed. * - * @return 'OK' if the volume was processed successfully, or 'ERROR' if there - * was a problem. + * @return 'OK' if the volume was processed successfully, or 'ERROR' if + * there was a problem. */ private IngestModule.ProcessResult flagVolume(Volume volume) { try { @@ -133,7 +138,7 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul /* * Make an ingest inbox message. */ - StringBuilder detailsSb = new StringBuilder(); + StringBuilder detailsSb = new StringBuilder(""); detailsSb.append("File: ").append(volume.getParent().getUniquePath()).append(volume.getName()).append("
\n"); detailsSb.append("Entropy: ").append(calculatedEntropy); @@ -151,12 +156,10 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul } /** - * This method checks if the AbstractFile input is encrypted. Initial - * qualifications require that it be an actual file that is not known, meets - * file size requirements, and has a MIME type of - * 'application/octet-stream'. + * This method checks if the Volume input is encrypted. Initial + * qualifications require that the Volume not have a file system. * - * @param file AbstractFile to be checked. + * @param volume Volume to be checked. * * @return True if the Volume is encrypted. */ @@ -165,16 +168,12 @@ class EncryptionDetectionDataSourceIngestModule implements DataSourceIngestModul * Criteria for the checks in this method are partially based on * http://www.forensicswiki.org/wiki/TrueCrypt#Detection */ - - boolean possiblyEncrypted = true; - - if (possiblyEncrypted) { + if (volume.getFileSystems().isEmpty()) { calculatedEntropy = EncryptionDetectionTools.calculateEntropy(volume); if (calculatedEntropy >= minimumEntropy) { return true; } } - return false; } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java index c776a8d7d9..99dbc0aaeb 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java +++ b/Core/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTools.java @@ -27,7 +27,7 @@ import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.Content; /** - * + * Class containing common methods concerning the Encryption Detection module. */ final class EncryptionDetectionTools { @@ -35,12 +35,10 @@ final class EncryptionDetectionTools { private static final int BYTE_OCCURENCES_BUFFER_SIZE = 256; static final double MINIMUM_ENTROPY_INPUT_RANGE_MIN = 6.0; static final double MINIMUM_ENTROPY_INPUT_RANGE_MAX = 8.0; - static final int MINIMUM_FILE_SIZE_INPUT_RANGE_MIN = 1; @NbBundle.Messages({ - "EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0.", - "EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." + "EncryptionDetectionTools.errorMessage.minimumEntropyInput=Minimum entropy input must be a number between 6.0 and 8.0." }) /** * Check if the minimum entropy setting is in the accepted range for this @@ -52,6 +50,9 @@ final class EncryptionDetectionTools { } } + @NbBundle.Messages({ + "EncryptionDetectionTools.errorMessage.minimumFileSizeInput=Minimum file size input must be an integer (in megabytes) of 1 or greater." + }) /** * Check if the minimum file size setting is in the accepted range for this * module. @@ -62,12 +63,10 @@ final class EncryptionDetectionTools { } } - private EncryptionDetectionTools() { - } - + /** - * Calculate the entropy of the content. The result is used to qualify the content - * as possibly encrypted. + * Calculate the entropy of the content. The result is used to qualify the + * content as possibly encrypted. * * @param content The content to be calculated against. * @@ -123,4 +122,10 @@ final class EncryptionDetectionTools { } } } + + /** + * Private constructor for Encryption Detection Tools class. + */ + private EncryptionDetectionTools() { + } } From 81f46c41195574ae7459c801a5a01c772aa42986 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 26 Apr 2018 17:50:01 -0400 Subject: [PATCH 089/100] renamed classes - 3503 --- ...eryRequest.java => AdHocQueryRequest.java} | 4 +-- ...tory.java => AdHocSearchChildFactory.java} | 36 +++++++++---------- ...legator.java => AdHocSearchDelegator.java} | 14 ++++---- ...erNode.java => AdHocSearchFilterNode.java} | 10 +++--- ...SearchPanel.java => AdHocSearchPanel.java} | 8 ++--- .../DropdownListSearchPanel.java | 5 +-- .../DropdownSingleTermSearchPanel.java | 3 +- .../keywordsearch/ExtractedContentViewer.java | 2 +- ...rchRunner.java => IngestSearchRunner.java} | 28 +++++++-------- .../KeywordSearchIngestModule.java | 6 ++-- .../keywordsearch/SolrSearchService.java | 2 +- 11 files changed, 61 insertions(+), 57 deletions(-) rename KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/{QueryRequest.java => AdHocQueryRequest.java} (93%) rename KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/{KeywordSearchResultFactory.java => AdHocSearchChildFactory.java} (91%) rename KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/{KeywordSearchQueryDelegator.java => AdHocSearchDelegator.java} (89%) rename KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/{KeywordSearchFilterNode.java => AdHocSearchFilterNode.java} (95%) rename KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/{KeywordSearchPanel.java => AdHocSearchPanel.java} (96%) rename KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/{SearchRunner.java => IngestSearchRunner.java} (96%) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java similarity index 93% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java index 739fe787e8..b3a3ec463d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/QueryRequest.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocQueryRequest.java @@ -23,7 +23,7 @@ import java.util.Map; /** * Stores data about a search before it is done. */ -final class QueryRequest { +final class AdHocQueryRequest { private final KeywordSearchQuery query; private final String queryString; @@ -36,7 +36,7 @@ final class QueryRequest { * @param id ID that callers simply increment from 0 * @param query Query that will be performed. */ - QueryRequest(Map map, int id, KeywordSearchQuery query) { + AdHocQueryRequest(Map map, int id, KeywordSearchQuery query) { this.queryString = query.getEscapedQueryString(); this.queryProperties = map; this.query = query; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java similarity index 91% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java index 290fe8b086..4424ead298 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchChildFactory.java @@ -48,7 +48,7 @@ import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.EmptyNode; import org.sleuthkit.autopsy.datamodel.KeyValue; import org.sleuthkit.autopsy.datamodel.KeyValueNode; -import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent; +import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.KeywordHitKey; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; @@ -66,9 +66,9 @@ import org.sleuthkit.datamodel.TskCoreException; * Responsible for assembling nodes and columns in the right way and performing * lazy queries as needed. */ -class KeywordSearchResultFactory extends ChildFactory { +class AdHocSearchChildFactory extends ChildFactory { - private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName()); + private static final Logger logger = Logger.getLogger(AdHocSearchChildFactory.class.getName()); //common properties (superset of all Node properties) to be displayed as columns static final List COMMON_PROPERTIES @@ -82,9 +82,9 @@ class KeywordSearchResultFactory extends ChildFactory { .map(Object::toString)) .collect(Collectors.toList()); - private final Collection queryRequests; + private final Collection queryRequests; - KeywordSearchResultFactory(Collection queryRequests) { + AdHocSearchChildFactory(Collection queryRequests) { this.queryRequests = queryRequests; } @@ -98,7 +98,7 @@ class KeywordSearchResultFactory extends ChildFactory { @Override protected boolean createKeys(List toPopulate) { - for (QueryRequest queryRequest : queryRequests) { + for (AdHocQueryRequest queryRequest : queryRequests) { /** * Check the validity of the requested query. */ @@ -155,7 +155,7 @@ class KeywordSearchResultFactory extends ChildFactory { } int hitNumber = 0; - List tempList = new ArrayList<>(); + List tempList = new ArrayList<>(); for (KeywordHit hit : getOneHitPerObject(queryResults)) { /** @@ -203,7 +203,7 @@ class KeywordSearchResultFactory extends ChildFactory { hitName = contentName; } hitNumber++; - tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults)); + tempList.add(new KeywordHitKey(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults)); } @@ -253,8 +253,8 @@ class KeywordSearchResultFactory extends ChildFactory { protected Node createNodeForKey(KeyValue key) { Node resultNode; - if (key instanceof KeyValueQueryContent) { - AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key); + if (key instanceof KeywordHitKey) { + AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeywordHitKey) key); /** * Place the Content, Artifact and hit results into the lookup for @@ -262,17 +262,17 @@ class KeywordSearchResultFactory extends ChildFactory { */ ArrayList lookups = new ArrayList<>(); lookups.add(adHocQueryResult); - if (((KeyValueQueryContent) key).getContent() != null) { - lookups.add(((KeyValueQueryContent) key).getContent()); + if (((KeywordHitKey) key).getContent() != null) { + lookups.add(((KeywordHitKey) key).getContent()); } - if (((KeyValueQueryContent) key).getArtifact() != null) { - lookups.add(((KeyValueQueryContent) key).getArtifact()); + if (((KeywordHitKey) key).getArtifact() != null) { + lookups.add(((KeywordHitKey) key).getArtifact()); } Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.fixed(lookups.toArray())); //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization - resultNode = new KeywordSearchFilterNode(kvNode); + resultNode = new AdHocSearchFilterNode(kvNode); } else { resultNode = new EmptyNode("This Node Is Empty"); resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text")); @@ -298,7 +298,7 @@ class KeywordSearchResultFactory extends ChildFactory { * which the hit was found. * @param results The query results. */ - AdHocQueryResult(KeyValueQueryContent key) { + AdHocQueryResult(KeywordHitKey key) { this.solrObjectId = key.getSolrObjectId(); this.results = key.getHits(); } @@ -327,7 +327,7 @@ class KeywordSearchResultFactory extends ChildFactory { * Used to display keyword search results in table. Eventually turned into a * node. */ - class KeyValueQueryContent extends KeyValue { + class KeywordHitKey extends KeyValue { private final long solrObjectId; @@ -350,7 +350,7 @@ class KeywordSearchResultFactory extends ChildFactory { * @param query Query used in search * @param hits Full set of search results (for all files! @@@) */ - KeyValueQueryContent(String name, Map map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) { + KeywordHitKey(String name, Map map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) { super(name, map, id); this.solrObjectId = solrObjectId; this.content = content; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java similarity index 89% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java index f2dfb53622..9afb18e100 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchDelegator.java @@ -37,14 +37,14 @@ import org.sleuthkit.autopsy.coreutils.Logger; * Delegates the actual work to the various implementations of * KeywordSearchQuery. */ -class KeywordSearchQueryDelegator { +class AdHocSearchDelegator { - private List keywordLists; + private final List keywordLists; private List queryDelegates; private static int resultWindowCount = 0; //keep track of unique window ids to display - private static Logger logger = Logger.getLogger(KeywordSearchQueryDelegator.class.getName()); + private static final Logger logger = Logger.getLogger(AdHocSearchDelegator.class.getName()); - public KeywordSearchQueryDelegator(List keywordLists) { + public AdHocSearchDelegator(List keywordLists) { this.keywordLists = keywordLists; init(); } @@ -66,14 +66,14 @@ class KeywordSearchQueryDelegator { * Post results into a new DataResultViewer. */ public void execute() { - Collection queryRequests = new ArrayList<>(); + Collection queryRequests = new ArrayList<>(); int queryID = 0; StringBuilder queryConcat = new StringBuilder(); // concatenation of all query strings for (KeywordSearchQuery q : queryDelegates) { Map kvs = new LinkedHashMap<>(); final String queryStr = q.getQueryString(); queryConcat.append(queryStr).append(" "); - queryRequests.add(new QueryRequest(kvs, ++queryID, q)); + queryRequests.add(new AdHocQueryRequest(kvs, ++queryID, q)); } String queryConcatStr = queryConcat.toString(); @@ -85,7 +85,7 @@ class KeywordSearchQueryDelegator { Node rootNode; if (queryRequests.size() > 0) { Children childNodes = - Children.create(new KeywordSearchResultFactory(queryRequests), true); + Children.create(new AdHocSearchChildFactory(queryRequests), true); rootNode = new AbstractNode(childNodes); } else { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java similarity index 95% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java index 6dc49ff484..3177bbbbb4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchFilterNode.java @@ -53,15 +53,17 @@ import org.sleuthkit.datamodel.VirtualDirectory; /** * FilterNode containing properties and actions for keyword search. + * + * Wraps the generic KeyValue node and customizes the property sheet and lookup */ -class KeywordSearchFilterNode extends FilterNode { +class AdHocSearchFilterNode extends FilterNode { /** * Instantiate a KeywordSearchFilterNode. * * @param original The original source node. */ - KeywordSearchFilterNode(Node original) { + AdHocSearchFilterNode(Node original) { super(original, null, new ProxyLookup(original.getLookup())); } @@ -116,7 +118,7 @@ class KeywordSearchFilterNode extends FilterNode { @Override public List visit(Report r) { List actionsList = new ArrayList<>(); - actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), KeywordSearchFilterNode.this)); + actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); actionsList.addAll(ContextMenuExtensionPoint.getActions()); return actionsList; @@ -161,7 +163,7 @@ class KeywordSearchFilterNode extends FilterNode { private List getFileActions(boolean enableHashSearch) { List actionsList = new ArrayList<>(); - actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), KeywordSearchFilterNode.this)); + actionsList.add(new NewWindowViewAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.viewInNewWinActionLbl"), AdHocSearchFilterNode.this)); actionsList.add(new ExternalViewerAction(NbBundle.getMessage(this.getClass(), "KeywordSearchFilterNode.getFileActions.openExternViewActLbl"), getOriginal())); actionsList.add(null); actionsList.add(ExtractAction.getInstance()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java similarity index 96% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java index 1e74bceefe..13798614c5 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/AdHocSearchPanel.java @@ -30,12 +30,12 @@ import org.openide.util.NbBundle; * class and extended classes model the user's intentions, not necessarily how * the search manager and 3rd party tools actually perform the search. */ -abstract class KeywordSearchPanel extends javax.swing.JPanel { +abstract class AdHocSearchPanel extends javax.swing.JPanel { private final String keywordSearchErrorDialogHeader = org.openide.util.NbBundle.getMessage(this.getClass(), "AbstractKeywordSearchPerformer.search.dialogErrorHeader"); protected int filesIndexed; - KeywordSearchPanel() { + AdHocSearchPanel() { initListeners(); } @@ -110,7 +110,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel { } } - KeywordSearchQueryDelegator man = null; + AdHocSearchDelegator man = null; final List keywordLists = getKeywordLists(); if (keywordLists.isEmpty()) { @@ -119,7 +119,7 @@ abstract class KeywordSearchPanel extends javax.swing.JPanel { KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR); return; } - man = new KeywordSearchQueryDelegator(keywordLists); + man = new AdHocSearchDelegator(keywordLists); if (man.validate()) { man.execute(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java index 40cc83cd92..89e59cd2eb 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownListSearchPanel.java @@ -44,7 +44,8 @@ import org.sleuthkit.autopsy.ingest.IngestManager; * Viewer panel widget for keyword lists that is used in the ingest config and * options area. */ -class DropdownListSearchPanel extends KeywordSearchPanel { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +class DropdownListSearchPanel extends AdHocSearchPanel { private static final Logger logger = Logger.getLogger(DropdownListSearchPanel.class.getName()); private static DropdownListSearchPanel instance; @@ -131,7 +132,7 @@ class DropdownListSearchPanel extends KeywordSearchPanel { @Override public void actionPerformed(ActionEvent e) { if (ingestRunning) { - SearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists()); + IngestSearchRunner.getInstance().addKeywordListsToAllJobs(listsTableModel.getSelectedLists()); logger.log(Level.INFO, "Submitted enqueued lists to ingest"); //NON-NLS } else { searchAction(e); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java index 512d62238a..7c14f761ff 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownSingleTermSearchPanel.java @@ -42,7 +42,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; * perform this task at the desired size, and neither could numerous other * fonts. */ -public class DropdownSingleTermSearchPanel extends KeywordSearchPanel { +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class DropdownSingleTermSearchPanel extends AdHocSearchPanel { private static final long serialVersionUID = 1L; private static final Logger LOGGER = Logger.getLogger(DropdownSingleTermSearchPanel.class.getName()); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index 62a1837e4d..9d3acafaf4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -34,7 +34,7 @@ import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult; +import org.sleuthkit.autopsy.keywordsearch.AdHocSearchChildFactory.AdHocQueryResult; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java similarity index 96% rename from KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java rename to KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java index 396e95dc0d..70fd3ba370 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/IngestSearchRunner.java @@ -55,10 +55,10 @@ import org.sleuthkit.autopsy.ingest.IngestServices; * Singleton keyword search manager: Launches search threads for each job and * performs commits, both on timed intervals. */ -final class SearchRunner { +final class IngestSearchRunner { - private static final Logger logger = Logger.getLogger(SearchRunner.class.getName()); - private static SearchRunner instance = null; + private static final Logger logger = Logger.getLogger(IngestSearchRunner.class.getName()); + private static IngestSearchRunner instance = null; private IngestServices services = IngestServices.getInstance(); private Ingester ingester = null; private long currentUpdateIntervalMs; @@ -71,7 +71,7 @@ final class SearchRunner { // maps a jobID to the search private Map jobs = new ConcurrentHashMap<>(); - SearchRunner() { + IngestSearchRunner() { currentUpdateIntervalMs = ((long) KeywordSearchSettings.getUpdateFrequency().getTime()) * 60 * 1000; ingester = Ingester.getDefault(); jobProcessingExecutor = new ScheduledThreadPoolExecutor(NUM_SEARCH_SCHEDULING_THREADS, new ThreadFactoryBuilder().setNameFormat(SEARCH_SCHEDULER_THREAD_NAME).build()); @@ -81,9 +81,9 @@ final class SearchRunner { * * @return the singleton object */ - public static synchronized SearchRunner getInstance() { + public static synchronized IngestSearchRunner getInstance() { if (instance == null) { - instance = new SearchRunner(); + instance = new IngestSearchRunner(); } return instance; } @@ -167,7 +167,7 @@ final class SearchRunner { } //stop currentSearcher - SearchRunner.Searcher currentSearcher = job.getCurrentSearcher(); + IngestSearchRunner.Searcher currentSearcher = job.getCurrentSearcher(); if ((currentSearcher != null) && (!currentSearcher.isDone())) { logger.log(Level.INFO, "Cancelling search job {0}", jobId); //NON-NLS currentSearcher.cancel(true); @@ -229,7 +229,7 @@ final class SearchRunner { logger.log(Level.INFO, "Checking for previous search for search job {0} before executing final search", job.getJobId()); //NON-NLS job.waitForCurrentWorker(); - SearchRunner.Searcher finalSearcher = new SearchRunner.Searcher(job, true); + IngestSearchRunner.Searcher finalSearcher = new IngestSearchRunner.Searcher(job, true); job.setCurrentSearcher(finalSearcher); //save the ref logger.log(Level.INFO, "Kicking off final search for search job {0}", job.getJobId()); //NON-NLS finalSearcher.execute(); //start thread @@ -252,7 +252,7 @@ final class SearchRunner { */ private final class PeriodicSearchTask implements Runnable { - private final Logger logger = Logger.getLogger(SearchRunner.PeriodicSearchTask.class.getName()); + private final Logger logger = Logger.getLogger(IngestSearchRunner.PeriodicSearchTask.class.getName()); @Override public void run() { @@ -341,7 +341,7 @@ final class SearchRunner { // Map of keyword to the object ids that contain a hit private Map> currentResults; //guarded by SearchJobInfo.this - private SearchRunner.Searcher currentSearcher; + private IngestSearchRunner.Searcher currentSearcher; private AtomicLong moduleReferenceCount = new AtomicLong(0); private final Object finalSearchLock = new Object(); //used for a condition wait @@ -393,11 +393,11 @@ final class SearchRunner { workerRunning = flag; } - private synchronized SearchRunner.Searcher getCurrentSearcher() { + private synchronized IngestSearchRunner.Searcher getCurrentSearcher() { return currentSearcher; } - private synchronized void setCurrentSearcher(SearchRunner.Searcher searchRunner) { + private synchronized void setCurrentSearcher(IngestSearchRunner.Searcher searchRunner) { currentSearcher = searchRunner; } @@ -453,7 +453,7 @@ final class SearchRunner { private List keywordLists; private Map keywordToList; //keyword to list name mapping private AggregateProgressHandle progressGroup; - private final Logger logger = Logger.getLogger(SearchRunner.Searcher.class.getName()); + private final Logger logger = Logger.getLogger(IngestSearchRunner.Searcher.class.getName()); private boolean finalRun = false; Searcher(SearchJobInfo job) { @@ -483,7 +483,7 @@ final class SearchRunner { if (progressGroup != null) { progressGroup.setDisplayName(displayName + " " + NbBundle.getMessage(this.getClass(), "SearchRunner.doInBackGround.cancelMsg")); } - return SearchRunner.Searcher.this.cancel(true); + return IngestSearchRunner.Searcher.this.cancel(true); } }, null); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index 83cdafb88a..f9c06d34ad 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -288,7 +288,7 @@ public final class KeywordSearchIngestModule implements FileIngestModule { return ProcessResult.OK; } List keywordListNames = settings.getNamesOfEnabledKeyWordLists(); - SearchRunner.getInstance().startJob(context, keywordListNames); + IngestSearchRunner.getInstance().startJob(context, keywordListNames); startedSearching = true; } @@ -309,13 +309,13 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (context.fileIngestIsCancelled()) { logger.log(Level.INFO, "Keyword search ingest module instance {0} stopping search job due to ingest cancellation", instanceNum); //NON-NLS - SearchRunner.getInstance().stopJob(jobId); + IngestSearchRunner.getInstance().stopJob(jobId); cleanup(); return; } // Remove from the search list and trigger final commit and final search - SearchRunner.getInstance().endJob(jobId); + IngestSearchRunner.getInstance().endJob(jobId); // We only need to post the summary msg from the last module per job if (refCounter.decrementAndGet(jobId) == 0) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 3fea1a51ee..ec6542812d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -390,7 +390,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { * in less than roughly two seconds. This stuff should be reworked using * an ExecutorService and tasks with Futures. */ - KeywordSearchResultFactory.BlackboardResultWriter.stopAllWriters(); + AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters(); try { Thread.sleep(2000); } catch (InterruptedException ex) { From d0ddd3f5409fbed576ceb100e8ada98682e1a6c5 Mon Sep 17 00:00:00 2001 From: "U-BASIS\\dgrove" Date: Thu, 26 Apr 2018 22:35:00 -0400 Subject: [PATCH 090/100] Added assertion messages. --- .../EncryptionDetectionTest.java | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java index 55d2a1ebdd..498d790b0b 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/modules/encryptiondetection/EncryptionDetectionTest.java @@ -83,11 +83,11 @@ public class EncryptionDetectionTest extends NbTestCase { Assert.fail(ex); } } - assertFalse("Unable to delete existing test directory", CASE_DIRECTORY_PATH.toFile().exists()); + assertFalse(String.format("Unable to delete existing test directory '%s'.", CASE_DIRECTORY_PATH.toString()), CASE_DIRECTORY_PATH.toFile().exists()); // Create the test directory CASE_DIRECTORY_PATH.toFile().mkdirs(); - assertTrue("Unable to create test directory", CASE_DIRECTORY_PATH.toFile().exists()); + assertTrue(String.format("Unable to create test directory '%s'.", CASE_DIRECTORY_PATH.toString()), CASE_DIRECTORY_PATH.toFile().exists()); try { Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, CASE_DIRECTORY_PATH.toString(), new CaseDetails(CASE_NAME)); @@ -99,10 +99,12 @@ public class EncryptionDetectionTest extends NbTestCase { ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); try { ProcessorCallback callBack = DataSourceProcessorRunner.runDataSourceProcessor(dataSourceProcessor, IMAGE_PATH); - List dataSourceContent = callBack.getDataSourceContent(); - assertEquals(1, dataSourceContent.size()); - List errorMessages = callBack.getErrorMessages(); - assertEquals(0, errorMessages.size()); + List dataSourceContentList = callBack.getDataSourceContent(); + String errorMessage = String.format("The data source processor callback should produce 1 data source Content object, but the actual count was %d.", dataSourceContentList.size()); + assertEquals(errorMessage, 1, dataSourceContentList.size()); + List callbackErrorMessageList = callBack.getErrorMessages(); + errorMessage = String.format("The data source processor callback produced %d error messages.", callbackErrorMessageList.size()); + assertEquals(errorMessage, 0, callbackErrorMessageList.size()); } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | InterruptedException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); @@ -114,18 +116,9 @@ public class EncryptionDetectionTest extends NbTestCase { public void tearDown() { try { Case.closeCurrentCase(); - //Seems like we need some time to close the case. - try { - Thread.sleep(2000); - } catch (InterruptedException ex) { - - } - - FileUtils.deleteDirectory(CASE_DIR); - } catch (CaseActionException | IOException ex) { + } catch (CaseActionException ex) { Exceptions.printStackTrace(ex); } - assertFalse(CASE_DIR.exists()); } /** @@ -187,8 +180,9 @@ public class EncryptionDetectionTest extends NbTestCase { templates.add(template); IngestJobSettings ingestJobSettings = new IngestJobSettings(EncryptionDetectionTest.class.getCanonicalName(), IngestType.FILES_ONLY, templates); try { - List errs = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); - assertEquals(0, errs.size()); + List ingestModuleErrorsList = IngestJobRunner.runIngestJob(datasources, ingestJobSettings); + String errorMessage = String.format("The ingest job runner produced %d error messages.", ingestModuleErrorsList.size()); + assertEquals(errorMessage, 0, ingestModuleErrorsList.size()); } catch (InterruptedException ex) { Exceptions.printStackTrace(ex); Assert.fail(ex); From fda8b94741059f3b3673d0ffc24f53073a1efc17 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Fri, 27 Apr 2018 20:06:04 -0400 Subject: [PATCH 091/100] Complete documentation nad clean up of result view infrastructure --- .../corecomponentinterfaces/DataResult.java | 41 +- .../DataResultViewer.java | 23 +- .../corecomponents/DataResultPanel.java | 38 +- .../DataResultTopComponent.form | 2 +- .../DataResultTopComponent.java | 435 +++++++++++------- .../DirectoryTreeTopComponent.java | 2 +- 6 files changed, 320 insertions(+), 221 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java index 191a851b9a..6af30b8729 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResult.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011 Basis Technology Corp. + * Copyright 2012-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,41 +22,58 @@ import java.util.List; import org.openide.nodes.Node; /** - * The interface for the "top right component" window. + * An interface for result view components. A result view component provides + * multiple views of the application data represented by a given NetBeans Node. + * The differing views of the node are supplied by a collection of result + * viewers (implementations of the DataResultViewer interface). * + * A typical implementation of this interface are the NetBeans TopComponents + * (DataResultTopComponents) that use a child result view component + * (DataResultPanel) for displaying their result viewers, and are docked into + * the upper right hand side (editor mode) of the main application window. */ public interface DataResult { /** - * Sets the "selected" node in this class. + * Sets the node for which this result view component should provide + * multiple views of the underlying application data. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent on this result view + * component's result viewers. */ - public void setNode(Node selectedNode); + public void setNode(Node node); /** - * Gets the unique TopComponent ID of this class. + * Gets the preferred identifier for this result view panel in the window + * system. * - * @return preferredID the unique ID + * @return The preferred identifier. */ public String getPreferredID(); /** - * Sets the title of this TopComponent + * Sets the title of this result view component. * - * @param title the given title (String) + * @param title The title. */ public void setTitle(String title); /** - * Sets the descriptive context text at the top of the pane. + * Sets the descriptive text about the source of the nodes displayed in this + * result view component. * - * @param pathText Descriptive text giving context for the current results + * @param description The text to display. */ public void setPath(String pathText); /** - * Checks if this is the main (uncloseable) instance of DataResult + * Gets whether or not this result view panel is the "main" result view + * panel used to view the child nodes of a node selected in the application + * tree view (DirectoryTreeTopComponent) that is normally docked into the + * left hand side of the main window. * - * @return true if it is the main instance, otherwise false + * @return True or false. */ public boolean isMain(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java index a5ace28245..c9d305f53e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponentinterfaces/DataResultViewer.java @@ -22,23 +22,26 @@ import java.awt.Component; import org.openide.nodes.Node; /** - * An interface for result viewers. A result viewer uses a Swing component to - * provide a view of the application data represented by a given NetBeans node. - * Result viewers typically appear in a result view (a DataResultTopComponent) - * docked into the upper right hand side of the main application window. + * An interface for result viewers. A result viewer uses a Swing Component to + * provide a view of the application data represented by a NetBeans Node passed + * to it via its setNode method. * - * Typically, a result viewer is a JPanel that displays the child nodes of the - * given node using a NetBeans explorer view as a child component. Such a result + * Result viewers are most commonly presented as a tab in a result view panel + * (DataResultPanel) inside a result view top component (DataResultTopComponent) + * that is docked into the upper right hand side (editor mode) of the main + * application window. + * + * A result viewer is typically a JPanel that displays the child nodes of the + * given node using a NetBeans explorer view child component. Such a result * viewer should use the explorer manager of the ancestor top component to * connect the lookups of the nodes displayed in the NetBeans explorer view to * the actions global context. It is strongly recommended that this type of - * result viewer extends the abstract base class AbstractDataResultViewer, which - * will handle some key aspects of working with the ancestor top component's - * explorer manager. + * result viewer is implemented by extending the abstract base class + * AbstractDataResultViewer, which will handle some key aspects of working with + * the ancestor top component's explorer manager. * * This interface is an extension point, so classes that implement it should * provide an appropriate ServiceProvider annotation. - * */ public interface DataResultViewer { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 9d5f2747d6..c9bae6afb9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -45,33 +45,31 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo; /** - * A Swing JPanel with a JTabbedPane child component that contains a collection - * of result viewers (DataResultViewer) that is either supplied or provided by - * the result viewer extension point (service providers that implement - * DataResultViewer). + * A result view panel is a JPanel with a JTabbedPane child component that + * contains a collection of result viewers and implements the DataResult + * interface. The result viewers in a result view panel are either supplied + * during construction of the panel or are obtained from the result viewer + * extension point (DataResultViewer service providers). * - * The "main" result view panel resides in the "main" result view (a - * DataResultTopComponent) that is normally docked into the upper right hand - * side of the main application window. + * A result view panel provides an implementation of the setNode API of the the + * DataResult interface that pushes a given NetBeans Node into its child result + * viewers via the DataResultViewer.setNode API. The result viewers are + * responsible for providing a view of the application data represented by the + * node. A typical result viewer is a JPanel that displays the child nodes of + * the given node using a NetBeans explorer view child component. * - * All result view panels push a given root node into their child result - * viewers, which are responsible for displaying the children of the node. The - * application tree view (DirectoryTreeTopComponent) supplies the current root - * node for the "main" result view panel by pushing its current selection to the - * panel. + * A result panel should be child components of top components that are explorer + * manager providers. The parent top component is expected to expose a lookup + * maintained by its explorer manager to the actions global context. The child + * result view panel will then find the parent top component's explorer manager + * at runtime, so that it can act as an explorer manager provider for its child + * result viewers. This connects the nodes displayed in the result viewers to + * the actions global context. * * All result view panels push single node selections in the child result * viewers to either the "main" content view (DataContentTopComponent) that is * normally docked into the lower right hand side of the main application * window, or to a supplied custom content view (implements DataContent). - * - * All result view panels should be child components of top components that are - * explorer manager providers. The parent top component is expected to expose a - * lookup maintained by its explorer manager to the actions global context. The - * child result view panel will then find the parent top component's explorer - * manager at runtime, so that it can act as an explorer manager provider for - * its child result viewers. This connects the nodes displayed in the result - * viewers to the actions global context. */ public class DataResultPanel extends javax.swing.JPanel implements DataResult, ChangeListener, ExplorerManager.Provider { diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form index f0f49dcff7..755df0fcf0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.form @@ -28,7 +28,7 @@ - + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java index aec2cb2316..797a8d5262 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultTopComponent.java @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.corecomponents; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -39,201 +40,237 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; import org.sleuthkit.autopsy.coreutils.Logger; /** - * Top component which displays results (top-right editor mode by default). + * A DataResultTopComponent object is a NetBeans top component that provides + * multiple views of the application data represented by a NetBeans Node. It is + * a result view component (implements DataResult) that contains a result view + * panel (DataResultPanel), which is also a result view component. The result + * view panel is a JPanel with a JTabbedPane child component that contains a + * collection of result viewers. Each result viewer (implements + * DataResultViewer) presents a different view of the current node. Result + * viewers are usually JPanels that display the child nodes of the current node + * using a NetBeans explorer view child component. The result viewers are either + * supplied during construction of the result view top component or provided by + * the result viewer extension point (service providers that implement + * DataResultViewer). * - * There is a main tc instance that responds to directory tree selections. - * Others can also create an additional result viewer tc using one of the - * factory methods, that can be: + * Result view top components are typically docked into the upper right hand + * side of the main application window (editor mode), and are linked to the + * content view in the lower right hand side of the main application window + * (output mode) by the result view panel. The panel passes single node + * selections in the active result viewer to the content view. * - * - added to top-right corner as an additional, closeable viewer - added to a - * different, custom mode, - linked to a custom content viewer that responds to - * selections from this top component. + * The "main" result view top component receives its current node as a selection + * from the case tree view in the top component (DirectoryTreeYopComponent) + * docked into the left hand side of the main application window. * - * For embedding custom data result in other top components window, use - * DataResultPanel component instead, since we cannot nest top components. - * - * Encapsulates the internal DataResultPanel and delegates to it. - * - * Implements DataResult interface by delegating to the encapsulated - * DataResultPanel. + * Result view top components are explorer manager providers to connect the + * lookups of the nodes displayed in the NetBeans explorer views of the result + * viewers to the actions global context. */ @RetainLocation("editor") public class DataResultTopComponent extends TopComponent implements DataResult, ExplorerManager.Provider { private static final Logger logger = Logger.getLogger(DataResultTopComponent.class.getName()); - private final ExplorerManager explorerManager = new ExplorerManager(); - private final DataResultPanel dataResultPanel; //embedded component with all the logic - private boolean isMain; - private String customModeName; - - //keep track of tcs opened for menu presenters private static final List activeComponentIds = Collections.synchronizedList(new ArrayList()); + private final boolean isMain; + private final String customModeName; + private final ExplorerManager explorerManager; + private final DataResultPanel dataResultPanel; /** - * Create a new data result top component + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into the upper right hand side of the main application window + * (editor mode) and will be linked to the content view in the lower right + * hand side of the main application window (output mode). Its result + * viewers are provided by the result viewer extension point (service + * providers that implement DataResultViewer). * - * @param isMain whether it is the main, application default result viewer, - * there can be only 1 main result viewer - * @param title title of the data result window + * @param title The title for the top component, appears on the top + * component's tab. + * @param description Descriptive text about the node displayed, appears + * on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * + * @return The result view top component. */ - public DataResultTopComponent(boolean isMain, String title) { - associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); - this.dataResultPanel = new DataResultPanel(title, isMain, Collections.emptyList(), null); - initComponents(); - customizeComponent(isMain, title); + public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null); + initInstance(description, node, childNodeCount, resultViewTopComponent); + return resultViewTopComponent; } /** - * Create a new, custom data result top component, in addition to the - * application main one + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into the upper right hand side of the main application window + * (editor mode) and will be linked to the content view in the lower right + * hand side of the main application window (output mode). * - * @param name unique name of the data result window, also - * used as title - * @param mode custom mode to dock into - * @param customContentViewer custom content viewer to send selection events - * to + * @param title The title for the top component, appears on the top + * component's tab. + * @param description Descriptive text about the node displayed, appears + * on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param viewers A collection of result viewers to use instead of + * the result viewers provided by the results viewer + * extension point. + * + * @return The result view top component. */ - DataResultTopComponent(String name, String mode, DataContentTopComponent customContentViewer) { + public static DataResultTopComponent createInstance(String title, String description, Node node, int childNodeCount, Collection viewers) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, viewers, null); + initInstance(description, node, childNodeCount, resultViewTopComponent); + return resultViewTopComponent; + } + + /** + * Creates a partially initialized result view top component that provides + * multiple views of the application data represented by a NetBeans Node. + * The result view will be docked into the upper right hand side of the main + * application window (editor mode) and will be linked to the content view + * in the lower right hand side of the main application window (output + * mode). Its result viewers are provided by the result viewer extension + * point (service providers that implement DataResultViewer). + * + * IMPORTANT: Initialization MUST be completed by calling initInstance. + * + * @param title The title for the result view top component, appears on the + * top component's tab. + * + * @return The partially initialized result view top component. + */ + public static DataResultTopComponent createInstance(String title) { + DataResultTopComponent resultViewTopComponent = new DataResultTopComponent(false, title, null, Collections.emptyList(), null); + return resultViewTopComponent; + } + + /** + * Initializes a partially initialized result view top component. + * + * @param description Descriptive text about the node displayed, + * appears on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param resultViewTopComponent The partially initialized result view top + * component. + */ + public static void initInstance(String description, Node node, int childNodeCount, DataResultTopComponent resultViewTopComponent) { + resultViewTopComponent.setNumberOfChildNodes(childNodeCount); + resultViewTopComponent.open(); + resultViewTopComponent.setNode(node); + resultViewTopComponent.setPath(description); + resultViewTopComponent.requestActive(); + } + + /** + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * docked into a custom mode and linked to the supplied content view. Its + * result viewers are provided by the result viewer extension point (service + * providers that implement DataResultViewer). + * + * @param title The title for the top component, appears + * on the top component's tab. + * @param mode The NetBeans Window system mode into which + * this top component should be docked. + * @param description Descriptive text about the node displayed, + * appears on the top component's tab + * @param node The node to display. + * @param childNodeCount The cardinality of the node's children. + * @param contentViewTopComponent A content view to which this result view + * will be linked. + * + * @return The result view top component. + */ + public static DataResultTopComponent createInstance(String title, String mode, String description, Node node, int childNodeCount, DataContentTopComponent contentViewTopComponent) { + DataResultTopComponent newDataResult = new DataResultTopComponent(false, title, mode, Collections.emptyList(), contentViewTopComponent); + initInstance(description, node, childNodeCount, newDataResult); + return newDataResult; + } + + /** + * Creates a result view top component that provides multiple views of the + * application data represented by a NetBeans Node. The result view will be + * the "main" result view and will docked into the upper right hand side of + * the main application window (editor mode) and will be linked to the + * content view in the lower right hand side of the main application window + * (output mode). Its result viewers are provided by the result viewer + * extension point (service providers that implement DataResultViewer). + * + * IMPORTANT: The "main" result view top component receives its current node + * as a selection from the case tree view in the top component + * (DirectoryTreeTopComponent) docked into the left hand side of the main + * application window. This constructor is RESERVED for the use of the + * DirectoryTreeTopComponent singleton only. DO NOT USE OTHERWISE. + * + * @param title The title for the top component, appears on the top + * component's tab. + */ + public DataResultTopComponent(String title) { + this(true, title, null, Collections.emptyList(), null); + } + + /** + * Constructs a result view top component that provides multiple views of + * the application data represented by a NetBeans Node. + * + * @param isMain Whether or not this is the "main" result + * view top component. + * @param title The title for the top component, appears + * on the top component's tab. + * @param mode The NetBeans Window system mode into which + * this top component should be docked. If + * null, the editor mode will be used by + * default. + * @param viewers A collection of result viewers. If empty, + * the result viewers provided by the results + * viewer extension point will be used. + * @param contentViewTopComponent A content view to which this result view + * will be linked. If null, this result view + * will be linked to the content view docked + * into the lower right hand side of the main + * application window, + */ + private DataResultTopComponent(boolean isMain, String title, String mode, Collection viewers, DataContentTopComponent contentViewTopComponent) { + this.isMain = isMain; + this.explorerManager = new ExplorerManager(); associateLookup(ExplorerUtils.createLookup(explorerManager, getActionMap())); this.customModeName = mode; - this.dataResultPanel = new DataResultPanel(name, false, Collections.emptyList(), customContentViewer); + this.dataResultPanel = new DataResultPanel(title, isMain, viewers, contentViewTopComponent); initComponents(); - customizeComponent(isMain, name); + customizeComponent(title); } - private void customizeComponent(boolean isMain, String title) { - this.isMain = isMain; - this.customModeName = null; - - setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); - - setTitle(title); // set the title + private void customizeComponent(String title) { + setToolTipText(NbBundle.getMessage(DataResultTopComponent.class, "HINT_NodeTableTopComponent")); //NON-NLS + setTitle(title); setName(title); getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(AddBookmarkTagAction.BOOKMARK_SHORTCUT, "addBookmarkTag"); //NON-NLS getActionMap().put("addBookmarkTag", new AddBookmarkTagAction()); //NON-NLS - - putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); // set option to close compoment in GUI + putClientProperty(TopComponent.PROP_CLOSING_DISABLED, isMain); putClientProperty(TopComponent.PROP_MAXIMIZATION_DISABLED, true); putClientProperty(TopComponent.PROP_DRAGGING_DISABLED, true); - activeComponentIds.add(title); } - /** - * Initialize previously created tc instance with additional data - * - * @param pathText - * @param givenNode - * @param totalMatches - * @param newDataResult previously created with createInstance() - * uninitialized instance - */ - public static void initInstance(String pathText, Node givenNode, int totalMatches, DataResultTopComponent newDataResult) { - newDataResult.setNumberOfChildNodes(totalMatches); - - newDataResult.open(); // open it first so the component can be initialized - - // set the tree table view - newDataResult.setNode(givenNode); - newDataResult.setPath(pathText); - - newDataResult.requestActive(); - } - - /** - * Creates a new non-default DataResult component and initializes it - * - * @param title Title of the component window - * @param pathText Descriptive text about the source of the nodes - * displayed - * @param givenNode The new root node - * @param totalMatches Cardinality of root node's children - * - * @return a new, not default, initialized DataResultTopComponent instance - */ - public static DataResultTopComponent createInstance(String title, String pathText, Node givenNode, int totalMatches) { - DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); - - initInstance(pathText, givenNode, totalMatches, newDataResult); - - return newDataResult; - } - - /** - * Creates a new non-default DataResult component linked with a custom data - * content, and initializes it. - * - * - * @param title Title of the component window - * @param mode custom mode to dock this custom TopComponent to - * @param pathText Descriptive text about the source of the nodes - * displayed - * @param givenNode The new root node - * @param totalMatches Cardinality of root node's children - * @param dataContentWindow a handle to data content top component window to - * - * @return a new, not default, initialized DataResultTopComponent instance - */ - public static DataResultTopComponent createInstance(String title, final String mode, String pathText, Node givenNode, int totalMatches, DataContentTopComponent dataContentWindow) { - DataResultTopComponent newDataResult = new DataResultTopComponent(title, mode, dataContentWindow); - - initInstance(pathText, givenNode, totalMatches, newDataResult); - return newDataResult; - } - - /** - * Creates a new non-default DataResult component. You probably want to use - * initInstance after it - * - * @param title - * - * @return a new, not default, not fully initialized DataResultTopComponent - * instance - */ - public static DataResultTopComponent createInstance(String title) { - final DataResultTopComponent newDataResult = new DataResultTopComponent(false, title); - - return newDataResult; - } - @Override public ExplorerManager getExplorerManager() { return explorerManager; } /** - * Get a list with names of active windows ids, e.g. for the menus + * Get a listing of the preferred identifiers of all the result view top + * components that have been created. * - * @return + * @return The listing. */ public static List getActiveComponentIds() { return new ArrayList<>(activeComponentIds); } - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - // //GEN-BEGIN:initComponents - private void initComponents() { - - org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel; - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE) - ); - }// //GEN-END:initComponents - // Variables declaration - do not modify//GEN-BEGIN:variables - // End of variables declaration//GEN-END:variables - @Override public int getPersistenceType() { if (customModeName == null) { @@ -245,16 +282,6 @@ public class DataResultTopComponent extends TopComponent implements DataResult, @Override public void open() { - setCustomMode(); - super.open(); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public List getViewers() { - return dataResultPanel.getViewers(); - } - - private void setCustomMode() { if (customModeName != null) { Mode mode = WindowManager.getDefault().findMode(customModeName); if (mode != null) { @@ -264,6 +291,12 @@ public class DataResultTopComponent extends TopComponent implements DataResult, logger.log(Level.WARNING, "Could not find mode: {0}, will dock into the default one", customModeName);//NON-NLS } } + super.open(); + } + + @Override + public List getViewers() { + return dataResultPanel.getViewers(); } @Override @@ -290,7 +323,7 @@ public class DataResultTopComponent extends TopComponent implements DataResult, } else { selectedNode = null; } - + /* * If the selected node of the content viewer is different than that of * the result viewer, the content viewer needs to be updated. Otherwise, @@ -343,15 +376,10 @@ public class DataResultTopComponent extends TopComponent implements DataResult, @Override public boolean canClose() { - /* - * If this is the results top component in the upper right of the main - * window, only allow it to be closed when there's no case opened or no - * data sources in the open case. - */ Case openCase; try { openCase = Case.getOpenCase(); - } catch (NoCurrentCaseException ex) { + } catch (NoCurrentCaseException unused) { return true; } return (!this.isMain) || openCase.hasData() == false; @@ -365,21 +393,74 @@ public class DataResultTopComponent extends TopComponent implements DataResult, return dataResultPanel.getRootNode(); } - - void setNumberOfChildNodes(int matches) { - this.dataResultPanel.setNumberOfChildNodes(matches); + /** + * Sets the cardinality of the current node's children + * + * @param childNodeCount The cardinality of the node's children. + */ + private void setNumberOfChildNodes(int childNodeCount) { + this.dataResultPanel.setNumberOfChildNodes(childNodeCount); } /** - * Resets the tabs based on the selected Node. If the selected node is null - * or not supported, disable that tab as well. + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + org.sleuthkit.autopsy.corecomponents.DataResultPanel dataResultPanelLocal = dataResultPanel; + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 967, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(dataResultPanelLocal, javax.swing.GroupLayout.DEFAULT_SIZE, 579, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + + /** + * Creates a partially initialized result view top component that provides + * multiple views of the application data represented by a NetBeans Node. + * The result view will be docked into the upper right hand side of the main + * application window (editor mode) and will be linked to the content view + * in the lower right hand side of the main application window (output + * mode). Its result viewers are provided by the result viewer extension + * point (service providers that implement DataResultViewer). + * + * IMPORTANT: Initialization MUST be completed by calling initInstance. + * + * @param isMain Ignored. + * @param title The title for the top component, appears on the top + * component's tab. + * + * @deprecated Use an appropriate overload of createIntance instead. + */ + @Deprecated + public DataResultTopComponent(boolean isMain, String title) { + this(false, title, null, Collections.emptyList(), null); + } + + /** + * Sets the node for which this result view component should provide + * multiple views of the underlying application data. + * + * @param node The node, may be null. If null, the call to this method is + * equivalent to a call to resetComponent on this result view + * component's result viewers. * - * @param selectedNode the selected content Node * @deprecated Use setNode instead. */ @Deprecated - public void resetTabs(Node selectedNode) { - dataResultPanel.setNode(selectedNode); + public void resetTabs(Node node) { + dataResultPanel.setNode(node); } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index 13219f863d..3069b7fb5d 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -101,7 +101,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat private final transient ExplorerManager em = new ExplorerManager(); private static DirectoryTreeTopComponent instance; - private final DataResultTopComponent dataResult = new DataResultTopComponent(true, Bundle.DirectoryTreeTopComponent_resultsView_title()); + private final DataResultTopComponent dataResult = new DataResultTopComponent(Bundle.DirectoryTreeTopComponent_resultsView_title()); private final LinkedList backList; private final LinkedList forwardList; private static final String PREFERRED_ID = "DirectoryTreeTopComponent"; //NON-NLS From da4b415e203d5e531a486d419199310b57ca7f43 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 27 Apr 2018 22:33:12 -0400 Subject: [PATCH 092/100] update TSK dependency --- TSKVersion.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TSKVersion.xml b/TSKVersion.xml index 544d73ea7f..57096d8d35 100644 --- a/TSKVersion.xml +++ b/TSKVersion.xml @@ -1,3 +1,3 @@ - + From ec6850f5203b0246adfa6180a6be2db284a0e6d7 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 27 Apr 2018 22:34:13 -0400 Subject: [PATCH 093/100] update TSK dependency --- TSKVersion.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TSKVersion.xml b/TSKVersion.xml index 544d73ea7f..57096d8d35 100644 --- a/TSKVersion.xml +++ b/TSKVersion.xml @@ -1,3 +1,3 @@ - + From 3fbca050573bc7860e5fd38e52626bc27dfe4145 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 27 Apr 2018 22:45:51 -0400 Subject: [PATCH 094/100] Update to use tsk 4.6.1 jar --- Core/nbproject/project.properties | 2 +- Core/nbproject/project.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 02df4b01bb..334d93ae55 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -18,7 +18,7 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar -file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar +file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar file.reference.tika-core-1.17.jar=release/modules/ext/tika-core-1.17.jar file.reference.tika-parsers-1.17.jar=release/modules/ext/tika-parsers-1.17.jar file.reference.curator-client-2.8.0.jar=release/modules/ext/curator-client-2.8.0.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 0159ec9880..16d78b628d 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -347,8 +347,8 @@ release/modules/ext/jdom-2.0.5.jar - ext/sleuthkit-postgresql-4.6.0.jar - release/modules/ext/sleuthkit-postgresql-4.6.0.jar + ext/sleuthkit-postgresql-4.6.1.jar + release/modules/ext/sleuthkit-postgresql-4.6.1.jar ext/opencv-248.jar From a5fe9517617ecf921c539ba6f3b050376afb95d7 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 27 Apr 2018 22:48:26 -0400 Subject: [PATCH 095/100] Updated to use 4.6.1 TSK --- Core/nbproject/project.properties | 2 +- Core/nbproject/project.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 958cf79f90..2b9b13caf7 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -18,7 +18,7 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar -file.reference.sleuthkit-postgresql-4.6.0.jar=release/modules/ext/sleuthkit-postgresql-4.6.0.jar +file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0.1.jar file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 77141b5d33..26676f0054 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -348,8 +348,8 @@ release/modules/ext/jdom-2.0.5.jar - ext/sleuthkit-postgresql-4.6.0.jar - release/modules/ext/sleuthkit-postgresql-4.6.0.jar + ext/sleuthkit-postgresql-4.6.1.jar + release/modules/ext/sleuthkit-postgresql-4.6.1.jar ext/opencv-248.jar From e29ec34d171bc4e9349fdeb541156f1a01303166 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sat, 28 Apr 2018 10:41:49 -0400 Subject: [PATCH 096/100] adjusted CI scripts for new TSK names --- .travis.yml | 2 +- appveyor.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d8d478ed13..8cde465f7d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ before_install: install: - sudo apt-get install testdisk - cd sleuthkit/sleuthkit - - sh install-sleuthkit.sh + - sh travis_build.sh script: - set -e - echo "building autopsy..." && echo -en 'travis_fold:start:script.build\\r' diff --git a/appveyor.yml b/appveyor.yml index 8911def936..9d9f961405 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,14 +30,14 @@ install: - ps: pushd C:\ - git clone https://github.com/sleuthkit/sleuthkit - ps: popd -services: - - postgresql95 +services: + - postgresql95 build_script: - cd %TSK_HOME% - - python setupLibs.py - - python win32\updateBuildLibs.py -m + - python setupDevRepos.py + - python updateAndBuildAll.py -m - ps: pushd bindings/java - ps: ant -version - cmd: ant dist-PostgreSQL From d5d623bcbbfee86579a18528a112f20b2d65c235 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Sat, 28 Apr 2018 13:33:12 -0400 Subject: [PATCH 097/100] fix typo --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9d9f961405..8500f65855 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -37,7 +37,7 @@ services: build_script: - cd %TSK_HOME% - python setupDevRepos.py - - python updateAndBuildAll.py -m + - python win32\updateAndBuildAll.py -m - ps: pushd bindings/java - ps: ant -version - cmd: ant dist-PostgreSQL From f87fd0cc8a5e8574d23236e62a6f57416179b466 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 30 Apr 2018 18:57:19 -0400 Subject: [PATCH 098/100] Remove methods with unnecessary public access from ThunderbirdMboxFileIngestModule --- .../thunderbirdparser/ThunderbirdMboxFileIngestModule.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 1e439cb39d..c6587da68d 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -306,7 +306,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @throws NoCurrentCaseException if there is no open case. * @return the temporary folder */ - public static String getTempPath() throws NoCurrentCaseException { + static String getTempPath() throws NoCurrentCaseException { String tmpDir = Case.getOpenCase().getTempDirectory() + File.separator + "EmailParser"; //NON-NLS File dir = new File(tmpDir); @@ -322,7 +322,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @throws NoCurrentCaseException if there is no open case. * @return the module output folder */ - public static String getModuleOutputPath() throws NoCurrentCaseException { + static String getModuleOutputPath() throws NoCurrentCaseException { String outDir = Case.getOpenCase().getModuleDirectory() + File.separator + EmailParserModuleFactory.getModuleName(); File dir = new File(outDir); @@ -338,7 +338,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @throws NoCurrentCaseException if there is no open case. * @return the relative path of the module output folder */ - public static String getRelModuleOutputPath() throws NoCurrentCaseException { + static String getRelModuleOutputPath() throws NoCurrentCaseException { return Case.getOpenCase().getModuleOutputDirectoryRelativePath() + File.separator + EmailParserModuleFactory.getModuleName(); } From 611cd721a1c6aa158357a7a330649758797b9ca2 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Tue, 1 May 2018 09:19:44 -0400 Subject: [PATCH 099/100] use custom TSK branch --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 90b51996f9..166c387dbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ before_install: install: - sudo apt-get install testdisk - cd sleuthkit/sleuthkit + - git checkout custom-release-may-2018 - sh install-sleuthkit.sh script: - cd $TRAVIS_BUILD_DIR/ From ebeed4893e1345f863b45e4a87a619c1637efcb9 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 1 May 2018 10:58:55 -0400 Subject: [PATCH 100/100] Merge in custom-release-may-2018 and resolve conflicts --- Core/nbproject/project.properties | 3 --- 1 file changed, 3 deletions(-) diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index b73c5ba277..0e683d15fd 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -18,10 +18,7 @@ file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbi file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar -<<<<<<< HEAD file.reference.sleuthkit-postgresql-4.6.1.jar=release/modules/ext/sleuthkit-postgresql-4.6.1.jar -======= ->>>>>>> upstream/custom-release-may-2018 file.reference.jempbox-1.8.13.jar=release/modules/ext/jempbox-1.8.13.jar file.reference.javax.ws.rs-api-2.0.1.jar=release/modules/ext/javax.ws.rs-api-2.0.1.jar file.reference.cxf-core-3.0.16.jar=release/modules/ext/cxf-core-3.0.16.jar