diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/media/AndroidExifInterfaceEx.java b/app/src/main/java/de/k3b/android/androFotoFinder/media/AndroidExifInterfaceEx.java index fe1bd772..b9316fed 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/media/AndroidExifInterfaceEx.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/media/AndroidExifInterfaceEx.java @@ -4,21 +4,29 @@ import android.util.Log; import java.io.IOException; +import java.util.Date; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.io.FileUtils; import de.k3b.io.VISIBILITY; import de.k3b.io.collections.SelectedFiles; import de.k3b.io.filefacade.IFile; import de.k3b.media.ExifInterfaceEx; +import de.k3b.media.PhotoPropertiesUtil; +/** + * Android specific Version of {@link ExifInterfaceEx} that updates the + * Database, when saving exif changes. + */ public class AndroidExifInterfaceEx extends ExifInterfaceEx { public static boolean DBG_ENABLED = true; private boolean overwriteOriginal; private String inPath; private String outPath; + private Boolean hasXmp; public static void init() { setFactory(new Factory() { @@ -29,6 +37,13 @@ public ExifInterfaceEx create() { }); } + @Override + public void saveAttributes(IFile inFile, IFile outFile, + boolean deleteInFileOnFinish, Boolean hasXmp) throws IOException { + super.saveAttributes(inFile, outFile, deleteInFileOnFinish, hasXmp); + this.hasXmp = hasXmp; + } + @Override protected IFile renameSouraceFileBeforeReplaceOrThrow(IFile oldSourcefile, String newName) throws IOException { debugIdPaths("renameSouraceFileBeforeReplaceOrThrow begin", oldSourcefile.getAbsolutePath(), newName); @@ -36,7 +51,7 @@ protected IFile renameSouraceFileBeforeReplaceOrThrow(IFile oldSourcefile, Strin this.inPath = oldSourcefile.getAbsolutePath(); this.outPath = this.inPath + TMP_FILE_SUFFIX; - if (!renameInDatabase(":renameSouraceFileBeforeReplaceOrThrow", this.inPath, this.outPath)) { + if (!renameInDatabase(":renameSouraceFileBeforeReplaceOrThrow", this.inPath, this.outPath, false)) { this.outPath = null; // failed } @@ -48,16 +63,33 @@ protected IFile renameSouraceFileBeforeReplaceOrThrow(IFile oldSourcefile, Strin @Override protected void beforeCloseSaveOutputStream() { if (this.outPath != null) { - renameInDatabase(":beforeCloseSaveOutputStream", this.outPath, this.inPath); + renameInDatabase(":beforeCloseSaveOutputStream", this.outPath, this.inPath, true); this.outPath = null; } super.beforeCloseSaveOutputStream(); } - private boolean renameInDatabase(String dbgContext, String fromPath, String toPath) { - debugIdPaths(dbgContext + " renameInDatabase begin", fromPath, toPath); + // TODO additional database parameters (see scanner) + // DateLastModified, xmpDate, .... + private boolean renameInDatabase(String dbgContext, String fromPath, String toPath, boolean thransferExif) { ContentValues values = new ContentValues(); + if (thransferExif) { + PhotoPropertiesMediaDBContentValues mediaValueAdapter = new PhotoPropertiesMediaDBContentValues().set(values, null); + + PhotoPropertiesUtil.copyNonEmpty(mediaValueAdapter, this); + + Date lastModified = new Date(); + TagSql.setFileModifyDate(values, lastModified); + if (this.hasXmp != null) { + if (this.hasXmp) { + TagSql.setXmpFileModifyDate(values, lastModified); + } else { + TagSql.setXmpFileModifyDate(values, TagSql.EXT_LAST_EXT_SCAN_NO_XMP); + } + } + } values.put(FotoSql.SQL_COL_PATH, toPath); + debugIdPaths(dbgContext + " renameInDatabase begin", fromPath, toPath); final int execResultCount = FotoSql.getMediaDBApi(). execUpdate(this.getClass().getSimpleName() + dbgContext, fromPath, values, null); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java index c068d0f3..e087c754 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/media/PhotoPropertiesMediaDBCsvImportActivity.java @@ -204,7 +204,7 @@ private void updateDB(String dbgContext, String _path, long xmlLastFileModifyDat TagSql.setXmpFileModifyDate(dbValues, xmlLastFileModifyDate); } - TagSql.setFileModifyDate(dbValues, new Date().getTime() / 1000); + TagSql.setFileModifyDate(dbValues, new Date()); mUpdateCount += TagSql.execUpdate(dbgContext, path, xmlLastFileModifyDate, dbValues, VISIBILITY.PRIVATE_PUBLIC); mItemCount++; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java index 0036b423..a663a295 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaContent2DBUpdateService.java @@ -60,7 +60,7 @@ public void clearMediaCopy() { public void rebuild(Context context, IProgessListener progessListener) { long start = new Date().getTime(); clearMediaCopy(); - MediaDBRepository.Impl.updateMedaiCopy(context, writableDatabase, null, progessListener); + MediaDBRepository.Impl.updateMediaCopy(context, writableDatabase, null, progessListener); start = (new Date().getTime() - start) / 1000; final String text = "load db " + start + " secs"; Toast.makeText(context, text, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java index 81cdf536..09475844 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/MediaDBRepository.java @@ -72,7 +72,7 @@ public class MediaDBRepository implements IMediaRepositoryApi { // #155 public static final boolean debugEnabledSqlRefresh = true; - private static final String MODUL_NAME = MediaContentproviderRepositoryImpl.class.getName(); + private static final String MODUL_NAME = MediaDBRepository.class.getSimpleName(); private static String currentUpdateReason = null; private static long currentUpdateId = 1; private static int transactionNumber = 0; @@ -360,6 +360,41 @@ public void endTransaction() { db.endTransaction(); } + /** + * generic method to get values from current MediaDBApi-Implementation + * + * @param fullFilePathFilter + * @param destination + * @param dbgContext + * @return + */ + public static ContentValues getContentValues(String fullFilePathFilter, ContentValues destination, String dbgContext) { + final String meldung = MODUL_NAME + ".getContentValues(" + dbgContext + "," + fullFilePathFilter + ")"; + QueryParameter query = new QueryParameter().addColumn(MediaDBRepository.Impl.USED_MEDIA_COLUMNS); + query.removeFirstColumnThatContains(FotoSql.SQL_COL_PK); + FotoSql.setWhereFileNames(query, fullFilePathFilter); + + Cursor c = null; + + try { + c = FotoSql.getMediaDBApi().createCursorForQuery(null, meldung, query, null, null); + if (c.moveToNext()) { + if (destination == null) { + destination = new ContentValues(); + } + return Impl.getContentValues(c, destination); + } + } catch (Exception ex) { + Log.e(LOG_TAG, meldung + + " error :", ex); + } finally { + if (c != null) c.close(); + } + + return null; + } + + public static class Impl { /** * SQL to create copy of contentprovider MediaStore.Images. @@ -561,7 +596,7 @@ public static void clearMedaiCopy(SQLiteDatabase db) { } - public static int updateMedaiCopy(Context context, SQLiteDatabase db, Date lastUpdate, IProgessListener progessListener) { + public static int updateMediaCopy(Context context, SQLiteDatabase db, Date lastUpdate, IProgessListener progessListener) { int progress = 0; java.util.Date startTime = new java.util.Date(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java index bd3ec960..b607cfa9 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java @@ -318,6 +318,12 @@ public static void setFileModifyDate(ContentValues values, String path) { } } + public static void setFileModifyDate(ContentValues values, Date fileModifyDate) { + if (fileModifyDate != null) { + setFileModifyDate(values, fileModifyDate.getTime() / 1000); + } + } + public static void setFileModifyDate(ContentValues values, long fileModifyDateSecs) { if (fileModifyDateSecs != 0) { values.put(SQL_COL_LAST_MODIFIED, fileModifyDateSecs); diff --git a/app/src/main/java/de/k3b/android/io/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/io/AndroidFileCommands.java index 9d7f2d9f..d5456224 100644 --- a/app/src/main/java/de/k3b/android/io/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/io/AndroidFileCommands.java @@ -61,7 +61,6 @@ import de.k3b.android.widget.FilePermissionActivity; import de.k3b.database.QueryParameter; import de.k3b.io.DirectoryFormatter; -import de.k3b.io.FileCommands; import de.k3b.io.FileUtils; import de.k3b.io.IDirectory; import de.k3b.io.IProgessListener; @@ -78,10 +77,10 @@ /** * Api to manipulate files/photos. * Same as FileCommands with update media database. - * + *
* Created by k3b on 03.08.2015.
*/
-public class AndroidFileCommands extends FileCommands {
+public class AndroidFileCommands extends AndroidFileCommandsDbImpl {
private static final String SETTINGS_KEY_LAST_COPY_TO_PATH = "last_copy_to_path";
private static final String mDebugPrefix = "AndroidFileCommands.";
private boolean isInBackground = false;
diff --git a/app/src/main/java/de/k3b/android/io/AndroidFileCommandsDbImpl.java b/app/src/main/java/de/k3b/android/io/AndroidFileCommandsDbImpl.java
new file mode 100644
index 00000000..f0dc49d8
--- /dev/null
+++ b/app/src/main/java/de/k3b/android/io/AndroidFileCommandsDbImpl.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2015-2020 by k3b.
+ *
+ * This file is part of AndroFotoFinder / #APhotoManager.
+ *
+ * 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
+ * Created by k3b on 03.08.2015. + */ +public class AndroidFileCommandsDbImpl extends FileCommands { + /** + * copies a file from the sourceFullPath path to the target path. + * Android specific: also updates database. + * + * @param sourceFullPath the path of the file that shall be copied including the file name with ending + * @param targetFullPath the path of the file that shall be written to with filename + */ + @Override + protected boolean osFileCopy(IFile targetFullPath, IFile sourceFullPath) { + //!!! TODO muss noch getestet werden + final String srcPath = sourceFullPath.getAbsolutePath(); + String toPath = null; + boolean dbSuccess = false; + if (PhotoPropertiesUtil.isImage(srcPath, PhotoPropertiesUtil.IMG_TYPE_ALL)) { + toPath = new File(targetFullPath.getFile(), targetFullPath.getName()).getAbsolutePath(); + dbSuccess = (null != copyInDatabase("osFileCopy", srcPath, toPath)); + } + + if (dbSuccess) { + dbSuccess = super.osFileCopy(targetFullPath, sourceFullPath); + } + return dbSuccess; + } + + /** + * Moves a file from the sourceFullPath path to the target path. + * Android specific: also updates database. + * + * @param sourceFullPath the path of the file that shall be copied including the file name with ending + * @param targetFullPath the path of the file that shall be written to with filename + */ + @Override + protected boolean osFileMove(IFile targetFullPath, IFile sourceFullPath) { + final String srcPath = sourceFullPath.getAbsolutePath(); + String toPath = null; + boolean dbSuccess = false; + // Database update must be done before super.osFileMove to avoid deleting/recreating old file entry + if (PhotoPropertiesUtil.isImage(srcPath, PhotoPropertiesUtil.IMG_TYPE_ALL)) { + toPath = new File(targetFullPath.getFile(), targetFullPath.getName()).getAbsolutePath(); + dbSuccess = renameInDatabase("osFileMove", srcPath, toPath); + } + final boolean osSuccess = super.osFileMove(targetFullPath, sourceFullPath); + if (!osSuccess && dbSuccess) { + // os falled. Rollback + renameInDatabase("osFileMove-rollback", toPath, srcPath); + } + return osSuccess; + } + + /** + * Renames a file from the sourceFullPath path to the target path. + * Android specific: also updates database. + * + * @param sourceFullPath the path of the file that shall be copied including the file name with ending + * @param targetFullPath the path of the file that shall be written to with filename + */ + @Override + protected boolean osRenameTo(IFile targetFullPath, IFile sourceFullPath) { + final String srcPath = sourceFullPath.getAbsolutePath(); + String toPath = null; + boolean dbSuccess = false; + if (PhotoPropertiesUtil.isImage(srcPath, PhotoPropertiesUtil.IMG_TYPE_ALL)) { + toPath = targetFullPath.getAbsolutePath(); + dbSuccess = renameInDatabase("osRenameTo", srcPath, toPath); + } + final boolean osSuccess = super.osRenameTo(targetFullPath, sourceFullPath); + if (!osSuccess && dbSuccess) { + // os falled. Rollback + renameInDatabase("osRenameTo-rollback", toPath, srcPath); + } + return osSuccess; + } + + @Override + protected boolean osDeleteFile(IFile file) { + return super.osDeleteFile(file); + } + + private boolean renameInDatabase(String dbgContext, String fromPath, String toPath) { + ContentValues values = new ContentValues(); + values.put(FotoSql.SQL_COL_PATH, toPath); + final int execResultCount = FotoSql.getMediaDBApi(). + execUpdate(this.getClass().getSimpleName() + dbgContext, fromPath, values, null); + + return 1 == execResultCount; + } + + private Uri copyInDatabase(String dbgContext, String fromPath, String toPath) { + ContentValues values = MediaDBRepository.getContentValues(fromPath, null, dbgContext); + if (values != null) { + values.put(FotoSql.SQL_COL_PATH, toPath); + return FotoSql.getMediaDBApi(). + execInsert(this.getClass().getSimpleName() + dbgContext, values); + } + return null; + } + +} + diff --git a/fotolib2/src/main/java/de/k3b/io/FileCommands.java b/fotolib2/src/main/java/de/k3b/io/FileCommands.java index 5e23025e..ccd56151 100644 --- a/fotolib2/src/main/java/de/k3b/io/FileCommands.java +++ b/fotolib2/src/main/java/de/k3b/io/FileCommands.java @@ -127,11 +127,10 @@ protected boolean canProcessFile(int opCode) { } /** - * * @param sourceFullPath the path of the file that shall be copied including the file name with ending - * @param targetFullPath the path of the file that shall be written to without filename - * - * Copies a file from the sourceFullPath path to the target path. + * @param targetFullPath the path of the file that shall be written to with filename + *
+ * Copies a file from the sourceFullPath path to the target path. */ private static boolean _osFileCopy(IFile targetFullPath, IFile sourceFullPath, FileCommands owner) { boolean result = true; @@ -454,22 +453,25 @@ protected int moveOrCopyFiles(final boolean move, String what, PhotoPropertiesDi /** * executes os specific move or copy operation and updates the list of modified files + * + * @param sourceFullPath the path of the file that shall be copied including the file name with ending + * @param targetFullPath the path of the file that shall be written to with filename */ - protected boolean osFileMoveOrCopy(boolean move, IFile dest, IFile source) { + protected boolean osFileMoveOrCopy(boolean move, IFile targetFullPath, IFile sourceFullPath) { boolean result = false; - long fileTime = source.lastModified(); + long fileTime = sourceFullPath.lastModified(); if (move) { - result = osFileMove(dest, source); + result = osFileMove(targetFullPath, sourceFullPath); } else { - result = osFileCopy(dest, source); + result = osFileCopy(targetFullPath, sourceFullPath); } - if (dest.lastModified() != fileTime) { - dest.setLastModified(fileTime); + if (targetFullPath.lastModified() != fileTime) { + targetFullPath.setLastModified(fileTime); } if (result) { - addProcessedFiles(move, dest, source); + addProcessedFiles(move, targetFullPath, sourceFullPath); } return result; @@ -484,35 +486,38 @@ private void addProcessedFiles(boolean move, IFile dest, IFile source) { /** * can be replaced by mock/stub in unittests + * + * @param sourceFullPath the path of the file that shall be copied including the file name with ending + * @param targetFullPath the path of the file that shall be written to with filename */ - protected boolean osFileMove(IFile dest, IFile source) { - if (osRenameTo(dest, source)) { + protected boolean osFileMove(IFile targetFullPath, IFile sourceFullPath) { + if (osRenameTo(targetFullPath, sourceFullPath)) { // move within same mountpoint if (LibGlobal.debugEnabledJpg) { - logger.info("osFileMove(rename) '" + source - + "' => '" + dest + "'"); + logger.info("osFileMove(rename) '" + sourceFullPath + + "' => '" + targetFullPath + "'"); } return true; } // #61 cannot move between different mountpoints/devices/partitions. do Copy+Delete instead - if (osFileExists(source) && source.isFile() && source.canRead() - && source.canWrite() // to delete after success - && !osFileExists(dest) - && osFileCopy(dest, source)) { - if (osDeleteFile(source)) { + if (osFileExists(sourceFullPath) && sourceFullPath.isFile() && sourceFullPath.canRead() + && sourceFullPath.canWrite() // to delete after success + && !osFileExists(targetFullPath) + && osFileCopy(targetFullPath, sourceFullPath)) { + if (osDeleteFile(sourceFullPath)) { if (LibGlobal.debugEnabledJpg) { - logger.info("osFileMove(copy+delete) '" + source - + "' => '" + dest + "'"); + logger.info("osFileMove(copy+delete) '" + sourceFullPath + + "' => '" + targetFullPath + "'"); } - return true; // move: copy + delete(source) : success + return true; // move: copy + delete(sourceFullPath) : success } else { // cannot delete souce: undo copy if (LibGlobal.debugEnabledJpg) { - logger.info("osFileMove failed for '" + source - + "' => '" + dest + "'"); + logger.info("osFileMove failed for '" + sourceFullPath + + "' => '" + targetFullPath + "'"); } - osDeleteFile(dest); + osDeleteFile(targetFullPath); } } return false; @@ -530,11 +535,10 @@ protected boolean osRenameTo(IFile dest, IFile source) { } /** + * Copies a file from the sourceFullPath path to the target path. * * @param sourceFullPath the path of the file that shall be copied including the file name with ending - * @param targetFullPath the path of the file that shall be written to without filename - * - * Copies a file from the sourceFullPath path to the target path. + * @param targetFullPath the path of the file that shall be written to with filename */ protected boolean osFileCopy(IFile targetFullPath, IFile sourceFullPath) { return _osFileCopy(targetFullPath, sourceFullPath, this); diff --git a/fotolib2/src/main/java/de/k3b/media/ExifInterface.java b/fotolib2/src/main/java/de/k3b/media/ExifInterface.java index c34b3bb9..979860c1 100644 --- a/fotolib2/src/main/java/de/k3b/media/ExifInterface.java +++ b/fotolib2/src/main/java/de/k3b/media/ExifInterface.java @@ -1174,20 +1174,23 @@ public String getDebugString(String lineDelimiter, String... _keysToExclude) { * and make a single call rather than multiple calls for each attribute. */ public void saveAttributes() throws IOException { - saveAttributes(mExifFile, mExifFile, true); + saveAttributes(mExifFile, mExifFile, true, null); } /** * Old File based implementation. * - * @deprecated use {@link #saveAttributes(IFile, IFile, boolean)} instead + * @deprecated use {@link #saveAttributes(IFile, IFile, boolean, Boolean)} instead */ @Deprecated public void saveAttributes(File inFile, File outFile, boolean deleteInFileOnFinish) throws IOException { - saveAttributes(FileFacade.convert("ExifInterface.saveAttributes in", inFile), FileFacade.convert("ExifInterface.saveAttributes out", outFile), deleteInFileOnFinish); + saveAttributes(FileFacade.convert("ExifInterface.saveAttributes in", inFile), + FileFacade.convert("ExifInterface.saveAttributes out", outFile), + deleteInFileOnFinish, null); } - public void saveAttributes(IFile inFile, IFile outFile, boolean deleteInFileOnFinish) throws IOException { + public void saveAttributes(IFile inFile, IFile outFile, + boolean deleteInFileOnFinish, Boolean hasXmp) throws IOException { String debugContext = String.format("%s.saveAttributes(%s=>%s,deleteInFileOnFinish=%s)", this.getClass().getSimpleName(), inFile, outFile, deleteInFileOnFinish); IFile currentOutFile = outFile; fixAttributes(); @@ -1284,7 +1287,7 @@ public byte[] getThumbnail() { } /** - * @deprecated use {@link #saveAttributes(IFile, IFile, boolean)} instead + * @deprecated use {@link #getThumbnail(IFile)} instead */ @Deprecated public byte[] getThumbnail(File inFile) { diff --git a/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java b/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java index 3ecdae38..ac3d9467 100644 --- a/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java +++ b/fotolib2/src/main/java/de/k3b/media/ExifInterfaceEx.java @@ -169,9 +169,9 @@ public static int getOrientationId(IFile fullPath) { } @Override - public void saveAttributes(IFile inFile, IFile outFile, boolean deleteInFileOnFinish) throws IOException { + public void saveAttributes(IFile inFile, IFile outFile, boolean deleteInFileOnFinish, Boolean hasXmp) throws IOException { fixDateTakenIfNeccessary(inFile); - super.saveAttributes(inFile, outFile, deleteInFileOnFinish); + super.saveAttributes(inFile, outFile, deleteInFileOnFinish, hasXmp); setFilelastModified(outFile); } diff --git a/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesUpdateHandler.java b/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesUpdateHandler.java index 42c8eaab..7d311baa 100644 --- a/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesUpdateHandler.java +++ b/fotolib2/src/main/java/de/k3b/media/PhotoPropertiesUpdateHandler.java @@ -278,7 +278,8 @@ private int transferExif(String dbg_context) throws IOException { exif.saveAttributes( inJpgFullPath, outJpgFullPath, - this.deleteOriginalAfterFinish); + this.deleteOriginalAfterFinish, + this.xmp != null); } else if (!isSameFile) { // changes are NOT written to exif. Do File copy instead. FileUtils.copyReplace(inJpgFullPath, outJpgFullPath, this.deleteOriginalAfterFinish, dbg_context + "-transferExif");