mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-14 17:06:16 +00:00
Merge pull request #2616 from millmanorama/2404-cached-file-race-condition
2404 fix race condition when accessing cached thumbnail files
This commit is contained in:
commit
c066935745
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2012-16 Basis Technology Corp.
|
* Copyright 2011-17 Basis Technology Corp.
|
||||||
*
|
*
|
||||||
* Copyright 2012 42six Solutions.
|
* Copyright 2012 42six Solutions.
|
||||||
* Contact: aebadirad <at> 42six <dot> com
|
* Contact: aebadirad <at> 42six <dot> com
|
||||||
@ -40,6 +40,7 @@ import java.util.List;
|
|||||||
import static java.util.Objects.nonNull;
|
import static java.util.Objects.nonNull;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -93,7 +94,16 @@ public class ImageUtils {
|
|||||||
private static final List<String> SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>();
|
private static final List<String> SUPPORTED_IMAGE_EXTENSIONS = new ArrayList<>();
|
||||||
private static final SortedSet<String> SUPPORTED_IMAGE_MIME_TYPES;
|
private static final SortedSet<String> SUPPORTED_IMAGE_MIME_TYPES;
|
||||||
|
|
||||||
private static final boolean openCVLoaded;
|
private static final boolean OPEN_CV_LOADED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map from tsk object id to Java File object. Used to get the same File for
|
||||||
|
* different tasks related to the same object so we can then synchronize on
|
||||||
|
* the File.
|
||||||
|
*
|
||||||
|
* NOTE: Must be cleared when the case is changed.
|
||||||
|
*/
|
||||||
|
private static final ConcurrentHashMap<Long, File> cacheFileMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ImageIO.scanForPlugins();
|
ImageIO.scanForPlugins();
|
||||||
@ -124,7 +134,7 @@ public class ImageUtils {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openCVLoaded = openCVLoadedTemp;
|
OPEN_CV_LOADED = openCVLoadedTemp;
|
||||||
SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes()));
|
SUPPORTED_IMAGE_EXTENSIONS.addAll(Arrays.asList(ImageIO.getReaderFileSuffixes()));
|
||||||
SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files
|
SUPPORTED_IMAGE_EXTENSIONS.add("tec"); // Add JFIF .tec files
|
||||||
SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
|
SUPPORTED_IMAGE_MIME_TYPES = new TreeSet<>(Arrays.asList(ImageIO.getReaderMIMETypes()));
|
||||||
@ -139,6 +149,9 @@ public class ImageUtils {
|
|||||||
"image/x-portable-bitmap", //NON-NLS
|
"image/x-portable-bitmap", //NON-NLS
|
||||||
"application/x-123")); //TODO: is this correct? -jm //NON-NLS
|
"application/x-123")); //TODO: is this correct? -jm //NON-NLS
|
||||||
SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NON-NLS
|
SUPPORTED_IMAGE_MIME_TYPES.removeIf("application/octet-stream"::equals); //NON-NLS
|
||||||
|
|
||||||
|
//Clear the file map when the case changes, so we don't accidentaly get images from the old case.
|
||||||
|
Case.addEventSubscriber(Case.Events.CURRENT_CASE.toString(), evt -> cacheFileMap.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,7 +160,7 @@ public class ImageUtils {
|
|||||||
private static FileTypeDetector fileTypeDetector;
|
private static FileTypeDetector fileTypeDetector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* thread that saves generated thumbnails to disk in the background
|
*Thread/Executor that saves generated thumbnails to disk in the background
|
||||||
*/
|
*/
|
||||||
private static final Executor imageSaver
|
private static final Executor imageSaver
|
||||||
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
= Executors.newSingleThreadExecutor(new BasicThreadFactory.Builder()
|
||||||
@ -296,7 +309,7 @@ public class ImageUtils {
|
|||||||
try {
|
try {
|
||||||
return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
|
return SwingFXUtils.fromFXImage(thumbnailTask.get(), null);
|
||||||
} catch (InterruptedException | ExecutionException ex) {
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
LOGGER.log(Level.WARNING, "Failed to get thumbnail for {0}: " + ex.toString(), getContentPathSafe(content)); //NON-NLS
|
LOGGER.log(Level.WARNING, "Failed to get thumbnail for " + getContentPathSafe(content), ex); //NON-NLS
|
||||||
return DEFAULT_THUMBNAIL;
|
return DEFAULT_THUMBNAIL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -305,7 +318,6 @@ public class ImageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* Get a thumbnail of a specified size for the given image. Generates the
|
* Get a thumbnail of a specified size for the given image. Generates the
|
||||||
* thumbnail if it is not already cached.
|
* thumbnail if it is not already cached.
|
||||||
*
|
*
|
||||||
@ -332,13 +344,15 @@ public class ImageUtils {
|
|||||||
* any problem getting the file, such as no case was open.
|
* any problem getting the file, such as no case was open.
|
||||||
*/
|
*/
|
||||||
private static File getCachedThumbnailLocation(long fileID) {
|
private static File getCachedThumbnailLocation(long fileID) {
|
||||||
try {
|
return cacheFileMap.computeIfAbsent(fileID, id -> {
|
||||||
String cacheDirectory = Case.getCurrentCase().getCacheDirectory();
|
try {
|
||||||
return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
|
String cacheDirectory = Case.getCurrentCase().getCacheDirectory();
|
||||||
} catch (IllegalStateException e) {
|
return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
|
||||||
LOGGER.log(Level.WARNING, "Could not get cached thumbnail location. No case is open."); //NON-NLS
|
} catch (IllegalStateException e) {
|
||||||
return null;
|
LOGGER.log(Level.WARNING, "Could not get cached thumbnail location. No case is open."); //NON-NLS
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -489,6 +503,7 @@ public class ImageUtils {
|
|||||||
"ImageIO could not determine height of {0}: ", //NON-NLS
|
"ImageIO could not determine height of {0}: ", //NON-NLS
|
||||||
imageReader -> imageReader.getHeight(0)
|
imageReader -> imageReader.getHeight(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -576,6 +591,7 @@ public class ImageUtils {
|
|||||||
*/
|
*/
|
||||||
public static Task<javafx.scene.image.Image> newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
|
public static Task<javafx.scene.image.Image> newGetThumbnailTask(AbstractFile file, int iconSize, boolean defaultOnFailure) {
|
||||||
return new GetThumbnailTask(file, iconSize, defaultOnFailure);
|
return new GetThumbnailTask(file, iconSize, defaultOnFailure);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -609,16 +625,21 @@ public class ImageUtils {
|
|||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a thumbnail file is already saved locally, just read that.
|
// If a thumbnail file is already saved locally, just read that.
|
||||||
if (cacheFile != null && cacheFile.exists()) {
|
if (cacheFile != null) {
|
||||||
try {
|
synchronized (cacheFile) {
|
||||||
BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
|
if (cacheFile.exists()) {
|
||||||
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
|
try {
|
||||||
return SwingFXUtils.toFXImage(cachedThumbnail, null);
|
BufferedImage cachedThumbnail = ImageIO.read(cacheFile);
|
||||||
|
if (nonNull(cachedThumbnail) && cachedThumbnail.getWidth() == iconSize) {
|
||||||
|
return SwingFXUtils.toFXImage(cachedThumbnail, null);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||||
|
cacheFile.delete(); //since we can't read the file we might as well delete it.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
|
||||||
LOGGER.log(Level.WARNING, "ImageIO had a problem reading the cached thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
|
|
||||||
cacheFile.delete(); //since we can't read the file we might as well delete it.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,7 +650,7 @@ public class ImageUtils {
|
|||||||
//There was no correctly-sized cached thumbnail so make one.
|
//There was no correctly-sized cached thumbnail so make one.
|
||||||
BufferedImage thumbnail = null;
|
BufferedImage thumbnail = null;
|
||||||
if (VideoUtils.isVideoThumbnailSupported(file)) {
|
if (VideoUtils.isVideoThumbnailSupported(file)) {
|
||||||
if (openCVLoaded) {
|
if (OPEN_CV_LOADED) {
|
||||||
updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
|
updateMessage(Bundle.GetOrGenerateThumbnailTask_generatingPreviewFor(file.getName()));
|
||||||
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
|
thumbnail = VideoUtils.generateVideoThumbnail(file, iconSize);
|
||||||
}
|
}
|
||||||
@ -698,12 +719,14 @@ public class ImageUtils {
|
|||||||
private void saveThumbnail(BufferedImage thumbnail) {
|
private void saveThumbnail(BufferedImage thumbnail) {
|
||||||
imageSaver.execute(() -> {
|
imageSaver.execute(() -> {
|
||||||
try {
|
try {
|
||||||
Files.createParentDirs(cacheFile);
|
synchronized (cacheFile) {
|
||||||
if (cacheFile.exists()) {
|
Files.createParentDirs(cacheFile);
|
||||||
cacheFile.delete();
|
if (cacheFile.exists()) {
|
||||||
|
cacheFile.delete();
|
||||||
|
}
|
||||||
|
ImageIO.write(thumbnail, FORMAT, cacheFile);
|
||||||
}
|
}
|
||||||
ImageIO.write(thumbnail, FORMAT, cacheFile);
|
} catch (Exception ex) {
|
||||||
} catch (IllegalArgumentException | IOException ex) {
|
|
||||||
LOGGER.log(Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
|
LOGGER.log(Level.WARNING, "Could not write thumbnail for {0}: " + ex.toString(), ImageUtils.getContentPathSafe(file)); //NON-NLS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -725,6 +748,7 @@ public class ImageUtils {
|
|||||||
*/
|
*/
|
||||||
public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
|
public static Task<javafx.scene.image.Image> newReadImageTask(AbstractFile file) {
|
||||||
return new ReadImageTask(file);
|
return new ReadImageTask(file);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -753,7 +777,6 @@ public class ImageUtils {
|
|||||||
|
|
||||||
private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS
|
private static final String IMAGEIO_COULD_NOT_READ_UNSUPPORTED_OR_CORRUPT = "ImageIO could not read {0}. It may be unsupported or corrupt"; //NON-NLS
|
||||||
final AbstractFile file;
|
final AbstractFile file;
|
||||||
// private ImageReader reader;
|
|
||||||
|
|
||||||
ReadImageTaskBase(AbstractFile file) {
|
ReadImageTaskBase(AbstractFile file) {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user