diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index ee4e4c4a..a2a9c658 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -58,6 +58,7 @@ import de.k3b.android.widget.LocalizedActivity; import de.k3b.database.QueryParameter; import de.k3b.io.FileApi; +import de.k3b.io.IFile; import de.k3b.io.PhotoAutoprocessingDto; import de.k3b.media.ExifInterface; import de.k3b.media.PhotoPropertiesImageReader; @@ -178,7 +179,7 @@ public static RefWatcher getRefWatcher(Context context) { MediaDBRepository.LOG_TAG, MediaContentproviderRepositoryImpl.LOG_TAG, DocumentFileTranslator.TAG, DocumentFileTranslator.TAG_DOCFILE, - FilePermissionActivity.TAG, FileApi.TAG) { + FilePermissionActivity.TAG, FileApi.TAG, IFile.TAG) { @Override public void uncaughtException(Thread thread, Throwable ex) { diff --git a/app/src/main/java/de/k3b/android/io/AndroidFileFacade.java b/app/src/main/java/de/k3b/android/io/AndroidFileFacade.java new file mode 100644 index 00000000..3314601b --- /dev/null +++ b/app/src/main/java/de/k3b/android/io/AndroidFileFacade.java @@ -0,0 +1,242 @@ +/* + * 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.android.io; + +import android.support.v4.provider.DocumentFile; +import android.util.Log; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import de.k3b.io.FileFacade; +import de.k3b.io.FileUtils; +import de.k3b.io.IFile; + +/** + * de.k3b.android.io.AndroidFileFacade havs the same methods as java.io.File + * but is implemented through Android specific {@link DocumentFile} + */ +public class AndroidFileFacade extends FileFacade { + private static DocumentFileTranslator documentFileTranslator = null; + private DocumentFile androidFile; + + private AndroidFileFacade(DocumentFile parentFile, File parentFile1) { + super(parentFile1); + androidFile = parentFile; + } + + public AndroidFileFacade(File file) { + this(documentFileTranslator.getDocumentFileOrDir(file, null), file); + } + + public static void setContext(DocumentFileTranslator documentFileTranslator) { + AndroidFileFacade.documentFileTranslator = documentFileTranslator; + } + + public static AndroidFileFacade[] get(File[] files) { + AndroidFileFacade f[] = new AndroidFileFacade[files.length]; + for (int i = 0; i < files.length; i++) { + f[i] = new AndroidFileFacade(files[i]); + } + + return f; + } + + @Override + public boolean renameTo(IFile newName) { + if (exists() && !newName.exists()) { + if (getParentFile().equals(newName.getParentFile())) { + // same directory + return renameTo(newName.getName()); + } + + if (copyImpl((AndroidFileFacade) newName, true)) { + setFile(((AndroidFileFacade) newName).getFile()); + return true; + } + } + Log.e(TAG, "renameTo " + this + " -> " + newName + " failed"); + return false; + } + + private boolean copyImpl(AndroidFileFacade targetFullPath, boolean deleteSourceWhenSuccess) { + InputStream in = null; + OutputStream out = null; + try { + in = openInputStream(); + out = targetFullPath.openOutputStream(); + FileUtils.copy(in, out); + } catch (IOException ex) { + Log.e(TAG, "copyImpl " + this + " -> " + targetFullPath + " failed", ex); + return false; + } finally { + FileUtils.close(in, this); + FileUtils.close(out, targetFullPath); + } + if (deleteSourceWhenSuccess) { + this.delete(); + } + return true; + } + + @Override + public boolean renameTo(String newName) { + if (exists() && androidFile.renameTo(newName)) { + setFile(new File(getFile().getParentFile(), newName)); + return true; + } + + Log.e(TAG, "renameTo " + this + " -> " + newName + " failed"); + return false; + } + + @Override + public boolean delete() { + return exists() && androidFile.delete(); + } + + private void notImplemented() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean exists() { + return (androidFile != null) && (androidFile.exists()); + } + + @Override + public IFile findExisting(String name) { + DocumentFile doc = androidFile.findFile(name); + if (doc != null) { + return new AndroidFileFacade(doc, new File(getFile(), name)); + } + return null; + } + + @Override + public boolean canWrite() { + return (androidFile != null) && androidFile.canWrite(); + } + + @Override + public boolean canRead() { + return (androidFile != null) && androidFile.canRead(); + } + + @Override + public boolean isFile() { + return (androidFile != null) && androidFile.isFile(); + } + + @Override + public boolean isDirectory() { + return (androidFile != null) && androidFile.isDirectory(); + } + + @Override + public boolean isHidden() { + if ((androidFile != null)) { + final String name = androidFile.getName(); + return (name == null) || name.startsWith("."); + } + return true; + } + + @Override + public IFile getCanonicalFile() { + return this; + } + + @Override + public IFile getParentFile() { + return new AndroidFileFacade(androidFile.getParentFile(), getFile().getParentFile()); + } + + @Override + public String getName() { + return androidFile.getName(); + } + + @Override + public long lastModified() { + return androidFile.lastModified(); + } + + @Override + public boolean mkdirs() { + return null != documentFileTranslator.getOrCreateDirectory(getFile()); + } + + @Override + public IFile[] listFiles() { + return get(androidFile.listFiles()); + } + + @Override + public boolean copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { + return copyImpl((AndroidFileFacade) targetFullPath, deleteSourceWhenSuccess); + } + + @Override + public OutputStream openOutputStream() throws FileNotFoundException { + return documentFileTranslator.createOutputStream(androidFile); + } + + @Override + public InputStream openInputStream() throws FileNotFoundException { + return documentFileTranslator.openInputStream(androidFile); + } + + @Override + public String getMime() { + notImplemented(); + return null; + } + + /** + * overwrite existing + * + * @param name + * @param mime + */ + @Override + public IFile create(String name, String mime) { + if (null == findExisting(name)) { + return new AndroidFileFacade(androidFile.createFile(mime, name), new File(getFile(), name)); + } + Log.e(TAG, "create " + this + "/" + name + " failed"); + return null; + } + + private IFile[] get(DocumentFile[] docs) { + AndroidFileFacade f[] = new AndroidFileFacade[docs.length]; + final File parent = getFile(); + for (int i = 0; i < docs.length; i++) { + final DocumentFile doc = docs[i]; + f[i] = new AndroidFileFacade(doc, new File(parent, doc.getName())); + } + + return f; + } + +} diff --git a/app/src/main/java/de/k3b/android/io/DocumentFileTranslator.java b/app/src/main/java/de/k3b/android/io/DocumentFileTranslator.java index 68d229bf..30a1331a 100644 --- a/app/src/main/java/de/k3b/android/io/DocumentFileTranslator.java +++ b/app/src/main/java/de/k3b/android/io/DocumentFileTranslator.java @@ -18,6 +18,7 @@ */ package de.k3b.android.io; +import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; @@ -180,7 +181,15 @@ public DocumentFile getOrCreateDirectory(File directory) { return result; } - public DocumentFile getDocumentFileOrDir(File fileOrDir, boolean isDir) { + /** + * gets existing DocumentFile that correspondws to fileOrDir + * or null if not exists or no write permissions + * + * @param fileOrDir where DocumentFile is searched for + * @param isDir if null: return null if isDir is matchning + * @return DocumentFile or null + */ + public DocumentFile getDocumentFileOrDir(File fileOrDir, Boolean isDir) { DocumentFile result = null; final String context = mDebugPrefix + "getDocumentFile('" + fileOrDir.getAbsolutePath() + "') "; @@ -193,7 +202,9 @@ public DocumentFile getDocumentFileOrDir(File fileOrDir, boolean isDir) { Log.w(TAG, context, ex); } - if ((result != null) && (result.isDirectory() != isDir)) { + + + if ((result != null) && (isDir != null) && (result.isDirectory() != isDir)) { Log.i(TAG, context + "wrong type isDirectory=" + result.isDirectory()); return null; } @@ -204,21 +215,36 @@ public InputStream openInputStream(File in) throws FileNotFoundException { if (in != null) { DocumentFile doc = getDocumentFileOrDir(in, false); if (doc != null) { - return context.getContentResolver().openInputStream(doc.getUri()); + return getContentResolver().openInputStream(doc.getUri()); } } return null; } - protected OutputStream createOutputStream(String mime, File outFile) throws FileNotFoundException { + public InputStream openInputStream(DocumentFile doc) throws FileNotFoundException { + if (doc != null) { + return getContentResolver().openInputStream(doc.getUri()); + } + return null; + } + + public OutputStream createOutputStream(String mime, File outFile) throws FileNotFoundException { DocumentFile dir = (outFile != null) ? getDocumentFileOrDir(outFile.getParentFile(), true) : null; DocumentFile doc = (dir != null) ? dir.createFile(mime, outFile.getName()) : null; + return createOutputStream(doc); + } + + public OutputStream createOutputStream(DocumentFile doc) throws FileNotFoundException { if (doc != null) { - return context.getContentResolver().openOutputStream(doc.getUri()); + return getContentResolver().openOutputStream(doc.getUri()); } return null; } + public ContentResolver getContentResolver() { + return context.getContentResolver(); + } + @Override public String toString() { final StringBuilder result = new StringBuilder().append(mDebugPrefix).append("[") 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 56a46263..114f0ece 100644 --- a/app/src/main/java/de/k3b/android/io/ExifInterfaceExAndroid.java +++ b/app/src/main/java/de/k3b/android/io/ExifInterfaceExAndroid.java @@ -19,16 +19,10 @@ package de.k3b.android.io; -import android.support.v4.provider.DocumentFile; - -import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -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; @@ -60,70 +54,4 @@ 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); - } - - protected OutputStream createOutputStream(File outFile) throws FileNotFoundException { - return documentFileTranslator.createOutputStream(MIMNE, outFile); - } - - protected boolean renameTo(File originalInFile, File renamedInFile) { - return getDocumentFileOrDir(originalInFile, false).renameTo(renamedInFile.getName()); - } - - protected String getAbsolutePath(File inFile) { - return inFile.getAbsolutePath(); - } - - protected boolean deleteFile(File file) { - return getDocumentFileOrDir(file, false).delete(); - } - - // ----- - private DocumentFile getDocumentFileOrDir(File fileOrDir, boolean isDir) { - return documentFileTranslator.getDocumentFileOrDir(fileOrDir, isDir); - } } diff --git a/fotolib2/src/main/java/de/k3b/io/FileFacade.java b/fotolib2/src/main/java/de/k3b/io/FileFacade.java index b4bf0502..404b34d2 100644 --- a/fotolib2/src/main/java/de/k3b/io/FileFacade.java +++ b/fotolib2/src/main/java/de/k3b/io/FileFacade.java @@ -29,30 +29,31 @@ import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; -/* de.k3b.io.File have the same methods as java.io.File so it may become a -replacement (aka man-in-the-middle-attack to -add support for Android DocumentFile +/** + * {@link FileFacade} has the same methods as {@link File} so it may become a + * replacement (aka man-in-the-middle-attack to + * add support for Android DocumentFile */ public class FileFacade implements IFile { - private java.io.File file; + private File file; - public FileFacade(java.io.File file) { + public FileFacade(File file) { this.file = file; } public FileFacade(String absolutPath) { - this(new java.io.File(absolutPath)); + this(new File(absolutPath)); } public FileFacade(FileFacade parent, String newFolderName) { - this(new java.io.File(parent.file, newFolderName)); + this(new File(parent.file, newFolderName)); } public FileFacade(String parent, String newFolderName) { - this(new java.io.File(parent, newFolderName)); + this(new File(parent, newFolderName)); } - public static FileFacade[] get(java.io.File[] files) { + public static FileFacade[] get(File[] files) { FileFacade f[] = new FileFacade[files.length]; for (int i = 0; i < files.length; i++) { f[i] = new FileFacade(files[i]); @@ -177,11 +178,12 @@ public IFile[] listFiles() { } @Override - public void copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { - copyImpl((FileFacade) targetFullPath, deleteSourceWhenSuccess); + public boolean copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { + return copyImpl((FileFacade) targetFullPath, deleteSourceWhenSuccess); } - private void copyImpl(FileFacade targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { + private boolean copyImpl(FileFacade targetFullPath, boolean deleteSourceWhenSuccess) throws IOException { + boolean success = true; FileChannel in = null; FileChannel out = null; try { @@ -189,14 +191,15 @@ private void copyImpl(FileFacade targetFullPath, boolean deleteSourceWhenSuccess out = new FileOutputStream((targetFullPath).file).getChannel(); long size = in.size(); MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size); - out.write(buf); + success = size == out.write(buf); } finally { FileUtils.close(in, "_osFileCopy-close"); FileUtils.close(out, "_osFileCopy-close"); } - if (deleteSourceWhenSuccess) { + if (success && deleteSourceWhenSuccess) { this.delete(); } + return success; } @Override @@ -233,7 +236,11 @@ public String toString() { return String.format("%s: %s", this.getClass().getSimpleName(), file.getAbsoluteFile()); } - public File getFile() { + protected File getFile() { return file; } + + protected void setFile(File file) { + this.file = file; + } } diff --git a/fotolib2/src/main/java/de/k3b/io/IFile.java b/fotolib2/src/main/java/de/k3b/io/IFile.java index 3e821da9..c96708e3 100644 --- a/fotolib2/src/main/java/de/k3b/io/IFile.java +++ b/fotolib2/src/main/java/de/k3b/io/IFile.java @@ -30,6 +30,8 @@ * and android specific de.k3b.android.io.... */ public interface IFile { + public static final String TAG = "k3b.File"; + @Deprecated boolean renameTo(IFile newName); @@ -71,7 +73,7 @@ public interface IFile { IFile[] listFiles(); - void copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException; + boolean copy(IFile targetFullPath, boolean deleteSourceWhenSuccess) throws IOException; OutputStream openOutputStream() throws FileNotFoundException;