mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-17 18:17:43 +00:00
Refactor image writer
This commit is contained in:
parent
56781ac73a
commit
457dc82dd1
@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult;
|
||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor;
|
||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||
import org.sleuthkit.autopsy.datasourceprocessors.ImageWriter;
|
||||
import org.sleuthkit.datamodel.Content;
|
||||
import org.sleuthkit.datamodel.Image;
|
||||
import org.sleuthkit.datamodel.SleuthkitJNI;
|
||||
@ -207,7 +208,9 @@ class AddImageTask implements Runnable {
|
||||
errorMessages.add(verificationError);
|
||||
}
|
||||
if(! imageWriterPath.isEmpty()){
|
||||
Case.getCurrentCase().scheduleImageWriterFinish(imageId);
|
||||
// The ImageWriter object registers itself as an event listener and will
|
||||
// stick around after this task is complete.
|
||||
ImageWriter writer = new ImageWriter(imageId);
|
||||
}
|
||||
newDataSources.add(newImage);
|
||||
} else {
|
||||
|
@ -287,7 +287,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
private static final int MIN_SECS_BETWEEN_TSK_ERROR_REPORTS = 60;
|
||||
private static final Logger logger = Logger.getLogger(Case.class.getName());
|
||||
private static final AutopsyEventPublisher eventPublisher = new AutopsyEventPublisher();
|
||||
private final ImageWriter imageWriter = new ImageWriter();
|
||||
private static String appName;
|
||||
private static Case currentCase;
|
||||
private static CoordinationService.Lock currentCaseLock;
|
||||
@ -1439,7 +1438,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||
});
|
||||
IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED);
|
||||
oldCase.closeImageWriter();
|
||||
completeCaseChange(null); //closes windows, etc
|
||||
if (null != oldCase.tskErrorReporter) {
|
||||
oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case
|
||||
@ -1617,22 +1615,6 @@ public class Case implements SleuthkitCase.ErrorObserver {
|
||||
return currentCaseExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the ID of an image that is being copied using ImageWriter.
|
||||
* This will cause the image to be finished after ingest is complete.
|
||||
* @param imageID The image ID
|
||||
*/
|
||||
void scheduleImageWriterFinish(long imageID){
|
||||
imageWriter.addDataSourceId(imageID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all tasks associated with Image Writer
|
||||
*/
|
||||
void closeImageWriter(){
|
||||
imageWriter.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the time zone(s) of the image data source(s) in this case.
|
||||
*
|
||||
|
@ -21,7 +21,6 @@ package org.sleuthkit.autopsy.datasourceprocessors;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
@ -41,111 +40,124 @@ import org.sleuthkit.datamodel.TskCoreException;
|
||||
|
||||
/**
|
||||
* The ImageWriter class is used to complete VHD copies created from local disks
|
||||
* after the ingest process completes.
|
||||
* after the ingest process completes. The AddImageTask for this data source must have included
|
||||
* a non-empty imageWriterPath parameter to enable Image Writer.
|
||||
*/
|
||||
public class ImageWriter {
|
||||
public class ImageWriter implements PropertyChangeListener{
|
||||
|
||||
private final Logger logger = Logger.getLogger(ImageWriter.class.getName());
|
||||
|
||||
private final HashSet<Long> dataSourceIds = new HashSet<>();
|
||||
private final Object dataSourceIdsLock; // Get this lock before accessing dataSourceIds
|
||||
private final Long dataSourceId;
|
||||
private Long imageHandle = null;
|
||||
|
||||
private final HashSet<ScheduledFuture<?>> progressUpdaters = new HashSet<>();
|
||||
private final HashSet<Long> imagesBeingFinished = new HashSet<>();
|
||||
private final HashSet<ProgressHandle> progressBars = new HashSet<>();
|
||||
private final HashSet<Future<?>> finishTasksInProgress = new HashSet<>();
|
||||
private Future<?> finishTask;
|
||||
ProgressHandle progressHandle = null;
|
||||
ScheduledFuture<?> progressUpdateTask = null;
|
||||
private boolean isCancelled;
|
||||
private final Object currentTasksLock; // Get this lock before accessing imagesBeingFinished, progressBars, progressUpdaters, finishTasksInProgress or isCancelled
|
||||
private boolean isStarted;
|
||||
private boolean isFinished;
|
||||
private final Object currentTasksLock; // Get this lock before accessing finishTask, progressHandle, progressUpdateTask, isCancelled,
|
||||
// isStarted, or isFinished
|
||||
|
||||
private boolean listenerStarted;
|
||||
private ScheduledThreadPoolExecutor periodicTasksExecutor = null;
|
||||
private final boolean doUI;
|
||||
|
||||
public ImageWriter(){
|
||||
dataSourceIdsLock = new Object();
|
||||
private static int numFinishJobsInProgress = 0;
|
||||
|
||||
public ImageWriter(Long dataSourceId){
|
||||
this.dataSourceId = dataSourceId;
|
||||
|
||||
currentTasksLock = new Object();
|
||||
listenerStarted = false;
|
||||
isCancelled = false;
|
||||
isStarted = false;
|
||||
isFinished = false;
|
||||
progressHandle = null;
|
||||
progressUpdateTask = null;
|
||||
finishTask = null;
|
||||
|
||||
doUI = RuntimeProperties.coreComponentsAreActive();
|
||||
if(doUI){
|
||||
periodicTasksExecutor = new ScheduledThreadPoolExecutor(5, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS
|
||||
periodicTasksExecutor = new ScheduledThreadPoolExecutor(1, new ThreadFactoryBuilder().setNameFormat("image-writer-progress-update-%d").build()); //NON-NLS
|
||||
}
|
||||
|
||||
IngestManager.getInstance().addIngestJobEventListener(this);
|
||||
Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a listener on IngestJobEvents if it hasn't already been started.
|
||||
* When a DataSourceAnalysisCompletedEvent arrives, if it matches
|
||||
* the data source ID of an image that is using Image Writer, then finish the image
|
||||
* (fill in any gaps). The AddImageTask for this data source must have included
|
||||
* a non-empty imageWriterPath parameter to enable Image Writer.
|
||||
* Handle the events:
|
||||
* DATA_SOURCE_ANALYSIS_COMPLETED - start the finish image process and clean up after it is complete
|
||||
* CURRENT_CASE (case closing) - cancel the finish image process (if necessary)
|
||||
*/
|
||||
private synchronized void startListener(){
|
||||
if(! listenerStarted){
|
||||
IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if(evt.getPropertyName().equals(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED.toString())){
|
||||
|
||||
DataSourceAnalysisCompletedEvent event = (DataSourceAnalysisCompletedEvent)evt;
|
||||
|
||||
if(event.getDataSource() != null){
|
||||
long imageId = event.getDataSource().getId();
|
||||
String name = event.getDataSource().getName();
|
||||
|
||||
// Check whether we need to run finishImage for this data source
|
||||
synchronized(dataSourceIdsLock){
|
||||
if( ! ImageWriter.this.dataSourceIds.contains(imageId)){
|
||||
// Image writer was not used on this data source or we've already finished it
|
||||
// Check that the event corresponds to this datasource
|
||||
if(imageId != dataSourceId){
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
startFinishImage(name);
|
||||
}).start();
|
||||
|
||||
} else {
|
||||
logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS
|
||||
}
|
||||
}
|
||||
else if(evt.getPropertyName().equals(Case.Events.CURRENT_CASE.toString())){
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
private void startFinishImage(String dataSourceName){
|
||||
synchronized(currentTasksLock){
|
||||
// If we've already started the finish process for this datasource, return.
|
||||
// Multiple DataSourceAnalysisCompletedEvent events can come from
|
||||
// the same image if more ingest modules are run later
|
||||
if(isStarted){
|
||||
return;
|
||||
} else {
|
||||
// Remove the imageId from the list here so we can't get past this point twice
|
||||
// for the same image. Multiple DataSourceAnalysisCompletedEvent events can come from
|
||||
// the same image if more ingest modules are run later, but the imageId is only added
|
||||
// to the list during the intial task to add the image to the database.
|
||||
ImageWriter.this.dataSourceIds.remove(imageId);
|
||||
isStarted = true;
|
||||
}
|
||||
}
|
||||
logger.log(Level.INFO, String.format("Finishing VHD image for %s",
|
||||
event.getDataSource().getName())); //NON-NLS
|
||||
|
||||
new Thread(() -> {
|
||||
logger.log(Level.INFO, String.format("Finishing VHD image for %s",
|
||||
dataSourceName)); //NON-NLS
|
||||
|
||||
try{
|
||||
Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(imageId);
|
||||
ProgressHandle progressHandle = null;
|
||||
ScheduledFuture<?> progressUpdateTask = null;
|
||||
Image image = Case.getCurrentCase().getSleuthkitCase().getImageById(dataSourceId);
|
||||
imageHandle = image.getImageHandle();
|
||||
|
||||
synchronized(currentTasksLock){
|
||||
if(isCancelled){
|
||||
return;
|
||||
}
|
||||
|
||||
if(doUI){
|
||||
progressHandle = ProgressHandle.createHandle("Image writer - " + name);
|
||||
progressHandle = ProgressHandle.createHandle("Image writer - " + dataSourceName);
|
||||
progressHandle.start(100);
|
||||
progressUpdateTask = periodicTasksExecutor.scheduleAtFixedRate(
|
||||
new ProgressUpdateTask(progressHandle, image.getImageHandle()), 0, 250, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
synchronized(currentTasksLock){
|
||||
ImageWriter.this.imagesBeingFinished.add(image.getImageHandle());
|
||||
|
||||
if(doUI){
|
||||
if(isCancelled){
|
||||
progressUpdateTask.cancel(true);
|
||||
return;
|
||||
}
|
||||
ImageWriter.this.progressUpdaters.add(progressUpdateTask);
|
||||
ImageWriter.this.progressBars.add(progressHandle);
|
||||
}
|
||||
}
|
||||
|
||||
// The added complexity here with the Future is because we absolutely need to make sure
|
||||
// the call to finishImageWriter returns before allowing the TSK data structures to be freed
|
||||
// during case close.
|
||||
Future<?> finishTask = Executors.newSingleThreadExecutor().submit(() -> {
|
||||
numFinishJobsInProgress++;
|
||||
finishTask = Executors.newSingleThreadExecutor().submit(() -> {
|
||||
try{
|
||||
SleuthkitJNI.finishImageWriter(image.getImageHandle());
|
||||
SleuthkitJNI.finishImageWriter(imageHandle);
|
||||
} catch (TskCoreException ex){
|
||||
logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
|
||||
}
|
||||
});
|
||||
synchronized(currentTasksLock){
|
||||
ImageWriter.this.finishTasksInProgress.add(finishTask);
|
||||
}
|
||||
|
||||
// Wait for finishImageWriter to complete
|
||||
@ -155,77 +167,70 @@ public class ImageWriter {
|
||||
} catch (InterruptedException | ExecutionException ex){
|
||||
logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
|
||||
}
|
||||
numFinishJobsInProgress--;
|
||||
|
||||
IngestManager.getInstance().removeIngestJobEventListener(this);
|
||||
Case.removeEventSubscriber(Case.Events.CURRENT_CASE.toString(), this);
|
||||
synchronized(currentTasksLock){
|
||||
ImageWriter.this.finishTasksInProgress.remove(finishTask);
|
||||
ImageWriter.this.imagesBeingFinished.remove(image.getImageHandle());
|
||||
|
||||
if(doUI){
|
||||
if(doUI && ! isCancelled){
|
||||
progressUpdateTask.cancel(true);
|
||||
ImageWriter.this.progressUpdaters.remove(progressUpdateTask);
|
||||
progressHandle.finish();
|
||||
ImageWriter.this.progressBars.remove(progressHandle);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
logger.log(Level.INFO, String.format("Finished writing VHD image for %s", event.getDataSource().getName())); //NON-NLS
|
||||
} catch (TskCoreException ex){
|
||||
|
||||
logger.log(Level.INFO, String.format("Finished writing VHD image for %s", dataSourceName)); //NON-NLS
|
||||
} catch (TskCoreException | IllegalStateException ex){
|
||||
logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
logger.log(Level.SEVERE, "DataSourceAnalysisCompletedEvent did not contain a dataSource object"); //NON-NLS
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
listenerStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a data source ID to the list of images to run finishImage on.
|
||||
* Also starts the listener if needed.
|
||||
* @param id The dataSource/Image ID
|
||||
* Tell the finishImage process to stop and wait for it to do so.
|
||||
*/
|
||||
public void addDataSourceId(Long id){
|
||||
startListener();
|
||||
synchronized(dataSourceIdsLock){
|
||||
dataSourceIds.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop any open progress update task, finish the progress bars, and tell
|
||||
* the finishImage process to stop
|
||||
*/
|
||||
public void close(){
|
||||
private void close(){
|
||||
synchronized(currentTasksLock){
|
||||
isCancelled = true;
|
||||
|
||||
for(ScheduledFuture<?> task:ImageWriter.this.progressUpdaters){
|
||||
task.cancel(true);
|
||||
if(imageHandle == null){
|
||||
// The case got closed during ingest (before the finish process could start)
|
||||
return;
|
||||
}
|
||||
|
||||
for(Long handle:imagesBeingFinished){
|
||||
SleuthkitJNI.cancelFinishImage(handle);
|
||||
if(!isFinished){
|
||||
SleuthkitJNI.cancelFinishImage(imageHandle);
|
||||
logger.log(Level.SEVERE, "Case closed before VHD image could be finished"); //NON-NLS
|
||||
}
|
||||
|
||||
// Wait for all the finish tasks to end
|
||||
for(Future<?> task:ImageWriter.this.finishTasksInProgress){
|
||||
// Wait for the finish task to end
|
||||
try{
|
||||
task.get();
|
||||
finishTask.get();
|
||||
} catch (InterruptedException | ExecutionException ex){
|
||||
logger.log(Level.SEVERE, "Error finishing VHD image", ex); //NON-NLS
|
||||
}
|
||||
|
||||
// Stop the progress bar and progress bar update task.
|
||||
// The thread from startFinishImage will also stop these
|
||||
// once the task completes, but we have to make absolutely sure
|
||||
// this gets done before the Sleuthkit data structures are freed.
|
||||
if(progressUpdateTask != null){
|
||||
progressUpdateTask.cancel(true);
|
||||
}
|
||||
|
||||
for(ProgressHandle progressHandle:ImageWriter.this.progressBars){
|
||||
if(progressHandle != null){
|
||||
progressHandle.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of images currently being finished.
|
||||
* @return number of images in progress
|
||||
*/
|
||||
public static int numberOfJobsInProgress(){
|
||||
return numFinishJobsInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to query the Sleuthkit processing to get the percentage done.
|
||||
|
Loading…
x
Reference in New Issue
Block a user