package de.jeisfeld.augendiagnoselib.util.imagefile;
import android.graphics.Bitmap;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import org.apache.commons.imaging.ImageReadException;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import de.jeisfeld.augendiagnoselib.Application;
import de.jeisfeld.augendiagnoselib.R;
import de.jeisfeld.augendiagnoselib.util.DateUtil;
/**
* Utility class to handle an eye photo, in particular regarding personName policies.
*/
public class EyePhoto {
/**
* The date format used for the file name.
*/
private static final String DATE_FORMAT = "yyyy-MM-dd";
/**
* Indicator if the file has already a formatted name.
*/
private boolean mFormattedName = false;
/**
* The path of the file.
*/
private String mPath;
/**
* The filename.
*/
private String mFilename;
/**
* The name of the person.
*/
@Nullable
private String mPersonName;
/**
* The date of the image.
*/
private Date mDate;
/**
* The information of right/left eye.
*/
private RightLeft mRightLeft;
/**
* The file suffix.
*/
private String mSuffix;
/**
* A cache of the bitmap (to avoid too frequent generation).
*/
private Bitmap mCachedBitmap;
/**
* The size of the cached bitmap (to avoid getting a badly sized bitmap from the cache).
*/
private int mCachedSize;
/**
* Create the EyePhoto, giving a filename.
*
* @param filename the file name.
*/
public EyePhoto(@NonNull final String filename) {
this(new File(filename));
}
/**
* Create the EyePhoto, giving a file resource.
*
* @param file the file.
*/
public EyePhoto(@NonNull final File file) {
setPath(file.getParent());
setFilename(file.getName());
// Auto-correct file name if safely possible
if (mFilename != null && !mFilename.equals(getFilename()) && !getFile().exists()) {
boolean success = FileUtil.moveFile(file, getFile());
if (!success) {
Log.w(Application.TAG, "Failed to rename file" + file.getName() + " to " + getAbsolutePath());
}
}
}
/**
* Create the EyePhoto, giving details.
*
* @param path The file path
* @param name The person name
* @param date The date
* @param rightLeft right or left eye?
* @param suffix File suffix (".jpg")
*/
public EyePhoto(final String path, final String name, final Date date, final RightLeft rightLeft,
@NonNull final String suffix) {
setPath(path);
setPersonName(name);
setDate(date);
setRightLeft(rightLeft);
setSuffix(suffix);
mFormattedName = true;
}
/**
* Retrieve the filename (excluding path).
*
* @return the filename.
*/
public final String getFilename() {
if (mFormattedName) {
return getPersonName() + " " + getDateString(DATE_FORMAT) + " " + getRightLeft().toShortString() + "."
+ getSuffix();
}
else {
return mFilename;
}
}
/**
* Retrieve the file path.
*
* @return the file path.
*/
public final String getAbsolutePath() {
return getFile().getAbsolutePath();
}
/**
* Set the filename (extracting from it the person personName, the date and the left/right property).
*
* @param filename the filename
*/
private void setFilename(@NonNull final String filename) {
this.mFilename = filename;
int suffixPosition = filename.lastIndexOf('.');
int rightLeftPosition = filename.lastIndexOf(' ', suffixPosition);
int datePosition = filename.lastIndexOf(' ', rightLeftPosition - 1);
if (datePosition > 0) {
setPersonName(filename.substring(0, datePosition));
mFormattedName = setDateString(filename.substring(datePosition + 1, rightLeftPosition), DATE_FORMAT);
setRightLeft(RightLeft.fromString(filename.substring(rightLeftPosition + 1, suffixPosition)));
setSuffix(filename.substring(suffixPosition + 1));
}
else {
if (suffixPosition > 0) {
setSuffix(filename.substring(suffixPosition + 1));
}
setDate(ImageUtil.getExifDate(getAbsolutePath()));
}
if (!mFormattedName) {
try {
JpegMetadata metadata = JpegMetadataUtil.getMetadata(getAbsolutePath());
setDate(metadata.getOrganizeDate());
setRightLeft(metadata.getRightLeft());
}
catch (ImageReadException | IOException e) {
// ignore
}
if (getDate() == null) {
setDate(ImageUtil.getExifDate(getAbsolutePath()));
}
}
}
/**
* Retrieve the file path.
*
* @return the file path.
*/
private String getPath() {
return mPath;
}
private void setPath(final String path) {
this.mPath = path;
}
/**
* Retrieve the right/left information.
*
* @return the right/left information.
*/
public final RightLeft getRightLeft() {
return mRightLeft;
}
public final void setRightLeft(final RightLeft rightLeft) {
this.mRightLeft = rightLeft;
}
/**
* Retrieve the person name (use getFilename for the file name).
*
* @return the person name.
*/
@Nullable
public final String getPersonName() {
return mPersonName;
}
/**
* Set the person name (trimmed).
*
* @param name the person name
*/
private void setPersonName(@Nullable final String name) {
if (name == null) {
this.mPersonName = null;
}
else {
this.mPersonName = name.trim();
}
}
/**
* Retrieve the date as a string.
*
* @param format the date format.
* @return the date string.
*/
private String getDateString(final String format) {
return DateUtil.format(getDate(), format);
}
/**
* Retrieve the date as a string with default date format.
*
* @return the date string.
*/
public final String getDateString() {
return DateUtil.format(getDate());
}
/**
* Set the date from a String.
*
* @param dateString the date string
* @param format the date format
* @return true if successful.
*/
private boolean setDateString(final String dateString, final String format) {
try {
setDate(DateUtil.parse(dateString, format));
return true;
}
catch (Exception e) {
return false;
}
}
/**
* Retrieve the date.
*
* @return the date.
*/
public final Date getDate() {
return mDate;
}
private void setDate(final Date date) {
this.mDate = date;
}
/**
* Retrieve the file suffix.
*
* @return the suffix.
*/
public final String getSuffix() {
return mSuffix;
}
private void setSuffix(@NonNull final String suffix) {
this.mSuffix = suffix.toLowerCase(Locale.getDefault());
}
/**
* Check if the file name is formatted as eye photo.
*
* @return true if the file name is formatted as eye photo.
*/
public final boolean isFormatted() {
return mFormattedName;
}
/**
* Retrieve the photo as File.
*
* @return the file
*/
@NonNull
private File getFile() {
return new File(getPath(), getFilename());
}
/**
* Check if the file exists.
*
* @return true if the file exists.
*/
public final boolean exists() {
return getFile().exists();
}
/**
* Delete the eye photo from the file system.
*
* @return true if the deletion was successful.
*/
public final boolean delete() {
return FileUtil.deleteFile(getFile());
}
/**
* Move the eye photo to a target path and target personName (given via EyePhoto object).
*
* @param target the file information of the target file.
* @param allowOverwrite if true, then an existing file is overwritten.
* @return true if the renaming was successful.
*/
public final boolean moveTo(final EyePhoto target, final boolean allowOverwrite) {
if (target == null || (target.getFile().exists() && !allowOverwrite)) {
return false;
}
return FileUtil.moveFile(getFile(), target.getFile());
}
/**
* Move the eye photo to a target folder.
*
* @param folderName the target folder
* @param createUnique if true, then a unique target file name is created if a file with the same name exists in the target folder.
* @return true if the move was successful.
*/
public final boolean moveToFolder(@NonNull final String folderName, final boolean createUnique) {
File folder = new File(folderName);
if (!folder.exists() || !folder.isDirectory()) {
// target folder does not exist
return false;
}
EyePhoto newPhoto = new EyePhoto(new File(folder, getFilename()));
if (newPhoto.exists() && !createUnique) {
return false;
}
return FileUtil.moveFile(getFile(), newPhoto.getNonExistingEyePhoto().getFile());
}
/**
* Create a non-existing File object in the same folder.
*
* @return a non-existing File object in the same folder.
*/
private EyePhoto getNonExistingEyePhoto() {
if (!exists()) {
return this;
}
if (!new File(getPath()).isDirectory()) {
return null;
}
if (mFormattedName) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(getDate());
EyePhoto eyePhoto;
do {
calendar.add(Calendar.DATE, 1);
eyePhoto = new EyePhoto(getPath(), getPersonName(), calendar.getTime(), getRightLeft(), getSuffix());
}
while (eyePhoto.exists());
return eyePhoto;
}
else {
String fileNameBase = mFilename;
String fileNameSuffix = "";
int suffixIndex = mFilename.lastIndexOf('.');
if (suffixIndex >= 0) {
fileNameBase = mFilename.substring(0, suffixIndex) + "-";
fileNameSuffix = mFilename.substring(suffixIndex);
}
int i = 0;
while (new File(getPath(), fileNameBase + i + fileNameSuffix).exists()) {
i++;
}
return new EyePhoto(new File(getPath(), fileNameBase + i + fileNameSuffix).getAbsolutePath());
}
}
/**
* Copy the eye photo to a target path and target personName (given via EyePhoto object).
*
* @param target the file information of the target file.
* @return true if the copying was successful.
*/
public final boolean copyTo(final EyePhoto target) {
if (target == null || target.getFile().exists()) {
// do not allow overwriting
return false;
}
return FileUtil.copyFile(getFile(), target.getFile());
}
/**
* Change the personName renaming the file (keeping the path).
*
* @param targetName the target name
* @return true if the renaming was successful.
*/
public final boolean changePersonName(final String targetName) {
EyePhoto target = cloneFromPath();
target.setPersonName(targetName);
boolean success = moveTo(target, false);
if (success) {
// update metadata
JpegMetadata metadata = target.getImageMetadata();
if (metadata == null) {
metadata = new JpegMetadata();
target.updateMetadataWithDefaults(metadata);
}
if (metadata.getPerson() == null || metadata.getPerson().length() == 0 || metadata.getPerson().equals(getPersonName())) {
metadata.setPerson(targetName);
}
target.storeImageMetadata(metadata);
}
return success;
}
/**
* Check if the date of the eye photo is changeable to the given date.
*
* @param newDate The new date.
* @return true if it is changeable
*/
public final boolean isDateChangeable(final Date newDate) {
EyePhoto target = cloneFromPath();
target.setDate(newDate);
return !target.exists();
}
/**
* Change the date renaming the file (keeping the path).
*
* @param newDate the target date.
* @return true if the change was successful.
*/
public final boolean changeDate(final Date newDate) {
EyePhoto target = cloneFromPath();
target.setDate(newDate);
boolean success = moveTo(target, false);
if (success) {
// update metadata
JpegMetadata metadata = target.getImageMetadata();
if (metadata == null) {
metadata = new JpegMetadata();
target.updateMetadataWithDefaults(metadata);
}
metadata.setOrganizeDate(newDate);
target.storeImageMetadata(metadata);
}
return success;
}
/**
* Add the photo to the media store. Must be used carefully - may lead to failures if the photo is later moved away
* again.
*/
public final void addToMediaStore() {
MediaStoreUtil.addPictureToMediaStore(getAbsolutePath());
}
/**
* Retrieve a clone of this object from the absolute path.
*
* @return a clone (recreation) of this object having the same absolute path.
*/
@NonNull
private EyePhoto cloneFromPath() {
return new EyePhoto(getAbsolutePath());
}
/**
* Calculate a bitmap of this photo and store it for later retrieval.
*
* @param maxSize the target size of the bitmap
*/
public final synchronized void precalculateImageBitmap(final int maxSize) {
if (maxSize != mCachedSize || mCachedBitmap == null) {
mCachedBitmap = ImageUtil.getImageBitmap(getAbsolutePath(), maxSize);
mCachedSize = maxSize;
}
}
/**
* Return a bitmap of this photo.
*
* @param maxSize The maximum size of this bitmap. If bigger, it will be resized
* @return the bitmap
*/
public final Bitmap getImageBitmap(final int maxSize) {
precalculateImageBitmap(maxSize);
return mCachedBitmap;
}
/**
* Retrieve a bitmap of this photo in full resolution.
*
* @return The bitmap.
*/
public final Bitmap getFullBitmap() {
return ImageUtil.getImageBitmap(getAbsolutePath(), 0);
}
/**
* Get the metadata stored in the file.
*
* @return the metadata.
*/
@Nullable
public final JpegMetadata getImageMetadata() {
return JpegSynchronizationUtil.getJpegMetadata(getAbsolutePath());
}
/**
* Store the metadata in the file.
*
* @param metadata the metadata to be stored.
*/
public final void storeImageMetadata(final JpegMetadata metadata) {
JpegSynchronizationUtil.storeJpegMetadata(getAbsolutePath(), metadata);
}
/**
* Update metadata object with default metadata, based on the file name.
*
* @param metadata the metadata object to be enhanced by the default information.
*/
public final void updateMetadataWithDefaults(@NonNull final JpegMetadata metadata) {
metadata.setPerson(getPersonName());
metadata.setOrganizeDate(getDate());
metadata.setRightLeft(getRightLeft());
metadata.setTitle(getPersonName() + " - " + getRightLeft().getTitleSuffix());
}
/**
* Store person, date and rightLeft in the metadata.
*/
public final void storeDefaultMetadata() {
JpegMetadata metadata = getImageMetadata();
if (metadata == null) {
metadata = new JpegMetadata();
}
updateMetadataWithDefaults(metadata);
storeImageMetadata(metadata);
}
/**
* Compare two images for equality (by path).
*
* @param other the other image to be compared to
* @return true if the bitmaps have the same path.
*/
@Override
public final boolean equals(final Object other) {
if (!(other instanceof EyePhoto)) {
return false;
}
EyePhoto otherPhoto = (EyePhoto) other;
return otherPhoto.getAbsolutePath().equals(getAbsolutePath());
}
/**
* Ensure that hashCode() matches equals().
*
* @return the hashCode.
*/
@Override
public final int hashCode() {
return getAbsolutePath().hashCode();
}
/**
* Enumeration for left eye vs. right eye.
*/
public enum RightLeft {
/**
* Enumeration values for right eye and left eye.
*/
RIGHT, LEFT;
/**
* Convert into a short string, to be used for filenames.
*
* @return the short string (dependent on language!)
*/
public final String toShortString() {
switch (this) {
case LEFT:
return Application.getResourceString(R.string.file_infix_left);
case RIGHT:
return Application.getResourceString(R.string.file_infix_right);
default:
return "";
}
}
@Override
public String toString() {
switch (this) {
case LEFT:
return "LEFT";
case RIGHT:
return "RIGHT";
default:
return "";
}
}
/**
* The suffix to be used for the title of the image.
*
* @return the title suffix.
*/
public final String getTitleSuffix() {
switch (this) {
case LEFT:
return Application.getResourceString(R.string.suffix_title_left);
case RIGHT:
return Application.getResourceString(R.string.suffix_title_right);
default:
return null;
}
}
/**
* Convert a String into a RightLeft enum (by first letter).
*
* @param rightLeftString The String to be converted.
* @return the converted RightString.
*/
@NonNull
public static RightLeft fromString(@Nullable final String rightLeftString) {
if (rightLeftString != null && rightLeftString.matches("[rRdD].*")) {
return RIGHT;
}
else {
return LEFT;
}
}
}
}