diff --git a/app/src/main/java/de/k3b/android/io/ExifInterfaceExAndroid.java b/app/src/main/java/de/k3b/android/io/ExifInterfaceExAndroid.java index da595b7f..56a46263 100644 --- a/app/src/main/java/de/k3b/android/io/ExifInterfaceExAndroid.java +++ b/app/src/main/java/de/k3b/android/io/ExifInterfaceExAndroid.java @@ -28,6 +28,7 @@ import java.io.OutputStream; import de.k3b.android.widget.FilePermissionActivity; +import de.k3b.io.IFile; import de.k3b.media.ExifInterfaceEx; import de.k3b.media.IPhotoProperties; @@ -59,10 +60,47 @@ public static void setContext(FilePermissionActivity activity) { documentFileTranslator = activity.getDocumentFileTranslator(); } + /** + * @deprecated use {@link #saveAttributes(IFile, IFile, boolean)} instead + */ + @Deprecated + @Override public void saveAttributes(File inFile, File outFile, boolean deleteInFileOnFinish) throws IOException { super.saveAttributes(inFile, outFile, deleteInFileOnFinish); } + public void saveAttributes(IFile inFile, IFile outFile, boolean deleteInFileOnFinish) throws IOException { + throw new RuntimeException("not implemented yet"); + // super.saveAttributes(inFile, outFile, deleteInFileOnFinish); + } + + /** + * @deprecated use {@link #fixDateTakenIfNeccessary(IFile)} instead + */ + @Deprecated + @Override + protected void fixDateTakenIfNeccessary(File inFile) { + super.fixDateTakenIfNeccessary(inFile); + } + + protected void fixDateTakenIfNeccessary(IFile inFile) { + throw new RuntimeException("not implemented yet"); + } + + /** + * @deprecated use {@link #setFilelastModified(IFile)} instead + */ + @Deprecated + @Override + public void setFilelastModified(File file) { + super.setFilelastModified(file); + + } + + public void setFilelastModified(IFile file) { + throw new RuntimeException("not implemented yet"); + } + //------------- File api to be overwritten for android specific DocumentFile implementation protected InputStream createInputStream(File exifFile) throws FileNotFoundException { return documentFileTranslator.openInputStream(exifFile); @@ -80,8 +118,8 @@ protected String getAbsolutePath(File inFile) { return inFile.getAbsolutePath(); } - protected boolean deleteFile(File renamedInFile) { - return getDocumentFileOrDir(renamedInFile, false).delete(); + protected boolean deleteFile(File file) { + return getDocumentFileOrDir(file, false).delete(); } // ----- diff --git a/fotolib2/src/main/java/de/k3b/io/Converter.java b/fotolib2/src/main/java/de/k3b/io/Converter.java new file mode 100644 index 00000000..4653888d --- /dev/null +++ b/fotolib2/src/main/java/de/k3b/io/Converter.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 by k3b. + * + * This file is part of #APhotoManager (https://github.com/k3b/APhotoManager/) + * and #toGoZip (https://github.com/k3b/ToGoZip/). + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.io; + +/** + * converts from type SOURCE to RESULT so that RESULT can be used as a facade for SOURCE + * + * @param + * @param + */ +public interface Converter { + public TO convert(FROM from); +} diff --git a/fotolib2/src/main/java/de/k3b/io/File.java b/fotolib2/src/main/java/de/k3b/io/FileFacade.java similarity index 55% rename from fotolib2/src/main/java/de/k3b/io/File.java rename to fotolib2/src/main/java/de/k3b/io/FileFacade.java index a9eac050..340abee8 100644 --- a/fotolib2/src/main/java/de/k3b/io/File.java +++ b/fotolib2/src/main/java/de/k3b/io/FileFacade.java @@ -19,6 +19,7 @@ */ package de.k3b.io; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -32,112 +33,152 @@ replacement (aka man-in-the-middle-attack to add support for Android DocumentFile */ -public class File { - public final java.io.File file; +public class FileFacade implements IFile { + private final java.io.File file; - public File(java.io.File file) { + public FileFacade(java.io.File file) { this.file = file; } - public File(String absolutPath) { + public FileFacade(String absolutPath) { this(new java.io.File(absolutPath)); } - public File(File parent, String newFolderName) { + public FileFacade(FileFacade parent, String newFolderName) { this(new java.io.File(parent.file, newFolderName)); } - public File(String parent, String newFolderName) { + public FileFacade(String parent, String newFolderName) { this(new java.io.File(parent, newFolderName)); } - public static File[] get(java.io.File[] files) { - File f[] = new File[files.length]; + public static FileFacade[] get(java.io.File[] files) { + FileFacade f[] = new FileFacade[files.length]; for (int i = 0; i < files.length; i++) { - f[i] = new File(files[i]); + f[i] = new FileFacade(files[i]); } return f; } - public boolean renameTo(File newName) { - return file.renameTo(newName.file); + @Deprecated + @Override + public boolean renameTo(IFile newName) { + return file.renameTo(((FileFacade) newName).file); } + @Override + public boolean renameTo(String newName) { + File newFile = new File(this.file.getParentFile(), newName); + final boolean result = this.file.renameTo(newFile); + return result; + } + + @Override public boolean delete() { return file.delete(); } + @Override public boolean exists() { return file.exists(); } + @Override + public IFile findExisting(String name) { + final File candidate = new File(this.file, name); + if (candidate.exists()) { + return new FileFacade(candidate); + } + return null; + } + + @Override public boolean canWrite() { return file.canWrite(); } + @Override public boolean canRead() { return canRead(); } + @Override public boolean isFile() { return file.isFile(); } + @Override public boolean isDirectory() { return file.isDirectory(); } + @Override public boolean isHidden() { return file.isHidden(); } + @Override public boolean isAbsolute() { return file.isAbsolute(); } + @Override public String getAbsolutePath() { return file.getAbsolutePath(); } - public File getCanonicalFile() { - return new File(FileUtils.tryGetCanonicalFile(file)); + @Override + public IFile getCanonicalFile() { + return new FileFacade(FileUtils.tryGetCanonicalFile(file)); } + @Override public String getCanonicalPath() { return FileUtils.tryGetCanonicalPath(file, null); } - public File getParentFile() { - return new File(file.getParentFile()); + @Override + public IFile getParentFile() { + return new FileFacade(file.getParentFile()); } + @Override public String getParent() { return file.getParent(); } + @Override public String getName() { return file.getName(); } + @Override public long lastModified() { return file.lastModified(); } + @Override public boolean mkdirs() { return file.mkdirs(); } - public File[] listFiles() { + @Override + public IFile[] listFiles() { return get(file.listFiles()); } - public void copy(File targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { + @Override + public void copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { + copyImpl((FileFacade) targetFullPath, deleteSourceWhenSuccess); + } + + private void copyImpl(FileFacade targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { FileChannel in = null; FileChannel out = null; try { in = new FileInputStream(file).getChannel(); - out = new FileOutputStream(targetFullPath.file).getChannel(); + out = new FileOutputStream((targetFullPath).file).getChannel(); long size = in.size(); MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size); out.write(buf); @@ -150,12 +191,41 @@ public void copy(File targetFullPath, boolean deleteSourceWhenSuccess) throws IO } } - public OutputStream openOutputStream(boolean var2) throws FileNotFoundException { - return new FileOutputStream(file, var2); + @Override + public OutputStream openOutputStream() throws FileNotFoundException { + // create parent dirs if not exist + file.getParentFile().mkdirs(); + + // replace existing + file.delete(); + + return new FileOutputStream(file); } + @Override public InputStream openInputStream() throws FileNotFoundException { return new FileInputStream(file); } + /** + * android DocumentFile specific, not supported in non-android + */ + @Override + public String getMime() { + return null; + } + + @Override + public IFile create(String name, String mime) { + return new FileFacade(new File(file, name)); + } + + @Override + public String toString() { + return String.format("%s: %s", this.getClass().getSimpleName(), file.getAbsoluteFile()); + } + + public File getFile() { + return file; + } } diff --git a/fotolib2/src/main/java/de/k3b/io/IFile.java b/fotolib2/src/main/java/de/k3b/io/IFile.java new file mode 100644 index 00000000..3e821da9 --- /dev/null +++ b/fotolib2/src/main/java/de/k3b/io/IFile.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 by k3b. + * + * This file is part of #APhotoManager (https://github.com/k3b/APhotoManager/) + * and #toGoZip (https://github.com/k3b/ToGoZip/). + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.io; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Goal: to become an android independant replacement for java.io.File + * that can be implemented by android independant de.k3b.io.File + * and android specific de.k3b.android.io.... + */ +public interface IFile { + @Deprecated + boolean renameTo(IFile newName); + + boolean renameTo(String newName); + + boolean delete(); + + boolean exists(); + + IFile findExisting(String name); + + boolean canWrite(); + + boolean canRead(); + + boolean isFile(); + + boolean isDirectory(); + + boolean isHidden(); + + boolean isAbsolute(); + + String getAbsolutePath(); + + IFile getCanonicalFile(); + + String getCanonicalPath(); + + IFile getParentFile(); + + String getParent(); + + String getName(); + + long lastModified(); + + boolean mkdirs(); + + IFile[] listFiles(); + + void copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException; + + OutputStream openOutputStream() throws FileNotFoundException; + + InputStream openInputStream() throws FileNotFoundException; + + String getMime(); + + /** + * overwrite existing + */ + IFile create(String name, String mime); +} diff --git a/fotolib2/src/main/java/de/k3b/media/ExifInterface.java b/fotolib2/src/main/java/de/k3b/media/ExifInterface.java index e46d4a5a..5786d613 100644 --- a/fotolib2/src/main/java/de/k3b/media/ExifInterface.java +++ b/fotolib2/src/main/java/de/k3b/media/ExifInterface.java @@ -52,6 +52,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import de.k3b.io.Converter; +import de.k3b.io.FileFacade; +import de.k3b.io.IFile; + /** * This is a class for reading and writing Exif tags in a JPEG file. * It is based on ExifInterface of android-6 version. @@ -64,9 +68,17 @@ public class ExifInterface { // public to allow error filtering public static final String LOG_TAG = "ExifInterface"; + private static final boolean OLD_API = false; + private static final Logger logger = LoggerFactory.getLogger(LOG_TAG); private static final boolean DEBUG_INTERNAL = false; + protected static Converter fileFacade = new Converter() { + @Override + public IFile convert(File file) { + return new FileFacade(file); + } + }; // public to allow global settings to enable/disable public static boolean DEBUG = false; @@ -1098,8 +1110,7 @@ private ExifTag(String name, int id, int primaryFormat, int secondaryFormat) { } private boolean validJpgExifFormat = true; - - protected File mExifFile = null; + protected IFile mExifFile = null; //!!! tagname => tagvalue(with assoziated tagdefinition) protected final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; @@ -1120,11 +1131,12 @@ private ExifTag(String name, int id, int primaryFormat, int secondaryFormat) { public ExifInterface(String filename) throws IOException { this(filename, null); } + public ExifInterface(String filename, InputStream in) throws IOException { if (filename == null) { throw new IllegalArgumentException("filename cannot be null"); } - mExifFile = (filename != null) ? new File(filename) : null; + mExifFile = (filename != null) ? fileFacade.convert(new File(filename)) : null; if (in == null) { InputStream inputStream = null; try { @@ -1488,28 +1500,37 @@ public void saveAttributes() throws IOException { saveAttributes(mExifFile, mExifFile, true); } + /** + * @deprecated use {@link #saveAttributes(IFile, IFile, boolean)} instead + */ + @Deprecated public void saveAttributes(File inFile, File outFile, boolean deleteInFileOnFinish) throws IOException { + saveAttributes(fileFacade.convert(inFile), fileFacade.convert(outFile), deleteInFileOnFinish); + } + + public void saveAttributes(IFile inFile, IFile outFile, boolean deleteInFileOnFinish) throws IOException { + String debugContext = String.format("%s.saveAttributes(%s=>%s,deleteInFileOnFinish=%s)", this.getClass().getSimpleName(), inFile, outFile, deleteInFileOnFinish); + IFile currentOutFile = outFile; fixAttributes(); + // Keep the thumbnail in memory mThumbnailBytes = getThumbnail(inFile); - File renamedInFile = inFile; + IFile renamedInFile = inFile; - boolean overwriteOriginal = inFile.equals(outFile); + boolean overwriteOriginal = inFile.equals(currentOutFile); + String originalName = inFile.getName(); if (overwriteOriginal) { - // Move the original file to temporary file. - renamedInFile = new File(getAbsolutePath(inFile) + ".tmp.jpg"); - File originalInFile = inFile; - if (!renameTo(originalInFile, renamedInFile)) { - throw new IOException("Could'nt rename sourcefile from " + inFile + - " to " + getAbsolutePath(renamedInFile)); - } + String tempName = originalName + ".new.jpg"; + logDebug(String.format("%s: overwrite original:\n\twriting to %s", debugContext, tempName)); + currentOutFile = outFile.getParentFile().create(tempName, outFile.getMime()); } + InputStream in = null; OutputStream out = null; + try { - // Save the new file. in = createInputStream(renamedInFile); out = createOutputStream(outFile); @@ -1517,15 +1538,38 @@ public void saveAttributes(File inFile, File outFile, boolean deleteInFileOnFini } finally { closeQuietly(in); closeQuietly(out); + } - if (deleteInFileOnFinish || overwriteOriginal) { - deleteFile(renamedInFile); + // all exif writing is done successfully + if (overwriteOriginal) { + // cleanup + String savedOriginalName = originalName + ".old.jpg"; + IFile previousFailedOverwriteOriginal = inFile.getParentFile().findExisting(savedOriginalName); + if (previousFailedOverwriteOriginal != null) { + logDebug(String.format("delete old %s", previousFailedOverwriteOriginal)); + previousFailedOverwriteOriginal.delete(); } + renameOrThrow(inFile, savedOriginalName); + renameOrThrow(currentOutFile, originalName); + } + + if (deleteInFileOnFinish || overwriteOriginal) { + deleteFile(inFile); } + // Discard the thumbnail in memory mThumbnailBytes = null; } + private void renameOrThrow(IFile file, String newName) throws IOException { + logDebug(String.format("rename %s to %s", file, newName)); + + if (!file.renameTo(newName)) { + throw new IOException("Could'nt rename sourcefile from " + file + + " to " + newName); + } + } + /** repairs wrong/missing attributes */ protected void fixAttributes() { if (ExifInterface.fixDateOnSave) { @@ -1582,7 +1626,21 @@ public byte[] getThumbnail() { return getThumbnail(mExifFile); } + /** + * @deprecated use {@link #saveAttributes(IFile, IFile, boolean)} instead + */ + @Deprecated public byte[] getThumbnail(File inFile) { + if (!mHasThumbnail) { + return null; + } + if (mThumbnailBytes != null) { + return mThumbnailBytes; + } + return getThumbnail(fileFacade.convert(inFile)); + } + + public byte[] getThumbnail(IFile inFile) { if (!mHasThumbnail) { return null; } @@ -2730,23 +2788,53 @@ private static boolean startsWith(byte[] content, byte[] prefix) { } //------------- File api to be overwritten for android specific DocumentFile implementation + protected InputStream createInputStream(IFile exifFile) throws FileNotFoundException { + if (OLD_API) return new FileInputStream(((FileFacade) exifFile).getFile()); + return exifFile.openInputStream(); + } + + protected OutputStream createOutputStream(IFile outFile) throws FileNotFoundException { + if (OLD_API) return new FileOutputStream(((FileFacade) outFile).getFile()); + return mExifFile.openOutputStream(); + } + + protected boolean renameTo(IFile originalInFile, IFile renamedInFile) { + if (OLD_API) { + return ((FileFacade) originalInFile).getFile().renameTo(((FileFacade) renamedInFile).getFile()); + } + return originalInFile.renameTo(renamedInFile.getName()); + } + + protected String getAbsolutePath(IFile inFile) { + return inFile.getAbsolutePath(); + } + + protected boolean deleteFile(IFile renamedInFile) { + return renamedInFile.delete(); + } + + @Deprecated protected InputStream createInputStream(File exifFile) throws FileNotFoundException { return new FileInputStream(exifFile); } + @Deprecated protected OutputStream createOutputStream(File outFile) throws FileNotFoundException { return new FileOutputStream(outFile); } + @Deprecated protected boolean renameTo(File originalInFile, File renamedInFile) { return originalInFile.renameTo(renamedInFile); } + @Deprecated protected String getAbsolutePath(File inFile) { return inFile.getAbsolutePath(); } - protected boolean deleteFile(File renamedInFile) { - return renamedInFile.delete(); + @Deprecated + protected boolean deleteFile(File file) { + return file.delete(); } } diff --git a/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java b/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java index 63ea0def..042d80d2 100644 --- a/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java +++ b/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java @@ -35,6 +35,8 @@ import java.util.TimeZone; import de.k3b.LibGlobal; +import de.k3b.io.FileFacade; +import de.k3b.io.IFile; import de.k3b.io.ListUtils; import de.k3b.io.VISIBILITY; import de.k3b.media.MediaFormatter.FieldID; @@ -107,8 +109,17 @@ public static int getOrientationId(String fullPath) { protected ExifInterfaceEx() {super();xmpExtern=null; mDbg_context = "";} + /** + * @deprecated use {@link #saveAttributes(IFile, IFile, boolean)} instead + */ + @Deprecated @Override public void saveAttributes(File inFile, File outFile, boolean deleteInFileOnFinish) throws IOException { + saveAttributes(fileFacade.convert(inFile), fileFacade.convert(outFile), deleteInFileOnFinish); + } + + @Override + public void saveAttributes(IFile inFile, IFile outFile, boolean deleteInFileOnFinish) throws IOException { fixDateTakenIfNeccessary(inFile); super.saveAttributes(inFile, outFile, deleteInFileOnFinish); setFilelastModified(outFile); @@ -126,6 +137,11 @@ public void saveJpegAttributes(InputStream inputStream, OutputStream outputStrea @Override protected boolean deleteFile(File file) { + return deleteFile(new FileFacade(file)); + } + + @Override + protected boolean deleteFile(IFile file) { boolean result = super.deleteFile(file); if (result && LibGlobal.debugEnabledJpg || LibGlobal.debugEnabledJpgMetaIo) { logger.debug(mDbg_context + " deleteFile: " + file); @@ -133,7 +149,15 @@ protected boolean deleteFile(File file) { return result; } - private void fixDateTakenIfNeccessary(File inFile) { + /** + * @deprecated use {@link #fixDateTakenIfNeccessary(IFile)} instead + */ + @Deprecated + protected void fixDateTakenIfNeccessary(File inFile) { + fixDateTakenIfNeccessary(fileFacade.convert(inFile)); + } + + protected void fixDateTakenIfNeccessary(IFile inFile) { // donot fix in unittests if (ExifInterfaceEx.fixDateOnSave && (null == getDateTimeTaken()) && (inFile != null)) { long lastModified = inFile.lastModified(); @@ -165,7 +189,7 @@ public String getPath() { @Override public IPhotoProperties setPath(String filePath) { - mExifFile = (filePath != null) ? new File(filePath) : null; + mExifFile = (filePath != null) ? fileFacade.convert(new File(filePath)) : null; if (xmpExtern != null) xmpExtern.setPath(filePath); return this; } @@ -441,8 +465,16 @@ private void loadLatLon() { } } - /** when xmp sidecar file was last modified or 0 */ + /** + * @deprecated use {@link #setFilelastModified(IFile)} instead + */ + @Deprecated public void setFilelastModified(File file) { + setFilelastModified(fileFacade.convert(file)); + } + + /** when xmp sidecar file was last modified or 0 */ + public void setFilelastModified(IFile file) { if (file != null) this.filelastModified = file.lastModified(); } diff --git a/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesBulkUpdateService.java b/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesBulkUpdateService.java index 5ea6fe63..23768493 100644 --- a/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesBulkUpdateService.java +++ b/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesBulkUpdateService.java @@ -80,16 +80,16 @@ public PhotoPropertiesUpdateHandler applyChanges(File inFilePath, String outFile : null; File outFile = (outFilePath != null) ? new File(outFilePath) : inFilePath; if ((inFilePath != null) && outFile.getParentFile().canWrite()) { - PhotoPropertiesUpdateHandler exif = null; + PhotoPropertiesUpdateHandler exifHandler = null; try { long lastModified = inFilePath.lastModified(); - exif = PhotoPropertiesUpdateHandler.create (inFilePath.getAbsolutePath(), outFilePath, false, "PhotoPropertiesUpdateHandler:"); - debugExif(sb, "old", exif, inFilePath); - List oldTags = exif.getTags(); + exifHandler = PhotoPropertiesUpdateHandler.create(inFilePath.getAbsolutePath(), outFilePath, false, "PhotoPropertiesUpdateHandler:"); + debugExif(sb, "old", exifHandler, inFilePath); + List oldTags = exifHandler.getTags(); boolean sameFile = (outFile.equals(inFilePath)); - File newOutFile = handleVisibility(metaDiffCopy.getVisibility(), outFile, exif); + File newOutFile = handleVisibility(metaDiffCopy.getVisibility(), outFile, exifHandler); if (newOutFile != null) { outFile = newOutFile; outFilePath = outFile.getAbsolutePath(); @@ -99,12 +99,12 @@ public PhotoPropertiesUpdateHandler applyChanges(File inFilePath, String outFile } } - List changed = metaDiffCopy.applyChanges(exif); + List changed = metaDiffCopy.applyChanges(exifHandler); if (!sameFile || (changed != null)) { - debugExif(sb, "assign ", exif, inFilePath); + debugExif(sb, "assign ", exifHandler, inFilePath); - exif.save("PhotoPropertiesUpdateHandler save"); + exifHandler.save("PhotoPropertiesUpdateHandler save"); if (LibGlobal.preserveJpgFileModificationDate) { // preseve file modification date @@ -127,7 +127,7 @@ public PhotoPropertiesUpdateHandler applyChanges(File inFilePath, String outFile } transactionLogger.set(id, outFilePath); if ((changed != null) && (changed.size() > 0)) { - transactionLogger.addChanges(exif, EnumSet.copyOf(changed), oldTags); + transactionLogger.addChanges(exifHandler, EnumSet.copyOf(changed), oldTags); } } @@ -143,17 +143,17 @@ public PhotoPropertiesUpdateHandler applyChanges(File inFilePath, String outFile } } else { if (sb != null) sb.append("no changes "); - exif = null; + exifHandler = null; } if (sb != null) { PhotoPropertiesBulkUpdateService.logger.info(sb.toString()); } - return exif; + return exifHandler; } catch (IOException e) { if (sb == null) { sb = createDebugStringBuilder(inFilePath); - debugExif(sb, "err content", exif, inFilePath); + debugExif(sb, "err content", exifHandler, inFilePath); } sb.append("error='").append(e.getMessage()).append("' ");