From 2cb973f1504c724fc37cac29f98486a9023520fe Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 11 Mar 2023 09:48:56 +0530 Subject: [PATCH 1/4] Use desugar_jdk_libs_nio. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e48f6e907..e1d6bc1f6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -192,7 +192,7 @@ sonar { dependencies { /** Desugaring **/ - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.3' /** NewPipe libraries **/ // You can use a local version by uncommenting a few lines in settings.gradle From 6df808f266ff3a46630bd3b1d2612de23669b016 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sat, 11 Mar 2023 10:44:11 +0530 Subject: [PATCH 2/4] Use Path in the download helper classes. --- .../streams/io/StoredDirectoryHelper.java | 51 +++++++----- .../newpipe/streams/io/StoredFileHelper.java | 78 +++++++++++-------- 2 files changed, 77 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index 48ae54284..dede5088e 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.DocumentsContract; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -14,21 +15,27 @@ import androidx.documentfile.provider.DocumentFile; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; -import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME; import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; public class StoredDirectoryHelper { + private static final String TAG = StoredDirectoryHelper.class.getSimpleName(); public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - private File ioTree; + private Path ioTree; private DocumentFile docTree; private Context context; @@ -40,7 +47,7 @@ public class StoredDirectoryHelper { this.tag = tag; if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(path.getScheme())) { - this.ioTree = new File(URI.create(path.toString())); + ioTree = Paths.get(URI.create(path.toString())); return; } @@ -64,13 +71,17 @@ public class StoredDirectoryHelper { } public StoredFileHelper createUniqueFile(final String name, final String mime) { - final ArrayList matches = new ArrayList<>(); + final List matches = new ArrayList<>(); final String[] filename = splitFilename(name); - final String lcFilename = filename[0].toLowerCase(); + final String lcFileName = filename[0].toLowerCase(); if (docTree == null) { - for (final File file : ioTree.listFiles()) { - addIfStartWith(matches, lcFilename, file.getName()); + try (Stream stream = Files.list(ioTree)) { + matches.addAll(stream.map(path -> path.getFileName().toString().toLowerCase()) + .filter(fileName -> fileName.startsWith(lcFileName)) + .collect(Collectors.toList())); + } catch (final IOException e) { + Log.e(TAG, "Exception while traversing " + ioTree, e); } } else { // warning: SAF file listing is very slow @@ -82,10 +93,10 @@ public class StoredDirectoryHelper { final ContentResolver cr = context.getContentResolver(); try (Cursor cursor = cr.query(docTreeChildren, projection, selection, - new String[]{lcFilename}, null)) { + new String[]{lcFileName}, null)) { if (cursor != null) { while (cursor.moveToNext()) { - addIfStartWith(matches, lcFilename, cursor.getString(0)); + addIfStartWith(matches, lcFileName, cursor.getString(0)); } } } @@ -112,7 +123,7 @@ public class StoredDirectoryHelper { Collections.sort(matches, String::compareTo); for (int i = 1; i < 1000; i++) { - if (Collections.binarySearch(matches, makeFileName(lcFilename, i, filename[1])) < 0) { + if (Collections.binarySearch(matches, makeFileName(lcFileName, i, filename[1])) < 0) { return createFile(makeFileName(filename[0], i, filename[1]), mime, true); } } @@ -141,11 +152,11 @@ public class StoredDirectoryHelper { } public Uri getUri() { - return docTree == null ? Uri.fromFile(ioTree) : docTree.getUri(); + return docTree == null ? Uri.fromFile(ioTree.toFile()) : docTree.getUri(); } public boolean exists() { - return docTree == null ? ioTree.exists() : docTree.exists(); + return docTree == null ? Files.exists(ioTree) : docTree.exists(); } /** @@ -169,7 +180,9 @@ public class StoredDirectoryHelper { */ public boolean mkdirs() { if (docTree == null) { - return ioTree.exists() || ioTree.mkdirs(); + // TODO: Use Files.createDirectories() when AGP 8.1 is available: + // https://issuetracker.google.com/issues/282544786 + return Files.exists(ioTree) || ioTree.toFile().mkdirs(); } if (docTree.exists()) { @@ -206,8 +219,8 @@ public class StoredDirectoryHelper { public Uri findFile(final String filename) { if (docTree == null) { - final File res = new File(ioTree, filename); - return res.exists() ? Uri.fromFile(res) : null; + final Path res = ioTree.resolve(filename); + return Files.exists(res) ? Uri.fromFile(res.toFile()) : null; } final DocumentFile res = findFileSAFHelper(context, docTree, filename); @@ -215,7 +228,7 @@ public class StoredDirectoryHelper { } public boolean canWrite() { - return docTree == null ? ioTree.canWrite() : docTree.canWrite(); + return docTree == null ? Files.isWritable(ioTree) : docTree.canWrite(); } /** @@ -230,14 +243,14 @@ public class StoredDirectoryHelper { @NonNull @Override public String toString() { - return (docTree == null ? Uri.fromFile(ioTree) : docTree.getUri()).toString(); + return (docTree == null ? Uri.fromFile(ioTree.toFile()) : docTree.getUri()).toString(); } //////////////////// // Utils /////////////////// - private static void addIfStartWith(final ArrayList list, @NonNull final String base, + private static void addIfStartWith(final List list, @NonNull final String base, final String str) { if (isNullOrEmpty(str)) { return; @@ -259,7 +272,7 @@ public class StoredDirectoryHelper { } private static String makeFileName(final String name, final int idx, final String ext) { - return name.concat(" (").concat(String.valueOf(idx)).concat(")").concat(ext); + return name + "(" + idx + ")" + ext; } /** diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java index 1f0c91456..9f8ef2e6c 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java @@ -23,6 +23,9 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import us.shandian.giga.io.FileStream; import us.shandian.giga.io.FileStreamSAF; @@ -36,7 +39,7 @@ public class StoredFileHelper implements Serializable { private transient DocumentFile docFile; private transient DocumentFile docTree; - private transient File ioFile; + private transient Path ioPath; private transient Context context; protected String source; @@ -49,7 +52,8 @@ public class StoredFileHelper implements Serializable { public StoredFileHelper(final Context context, final Uri uri, final String mime) { if (FilePickerActivityHelper.isOwnFileUri(context, uri)) { - ioFile = Utils.getFileForUri(uri); + final File ioFile = Utils.getFileForUri(uri); + ioPath = ioFile.toPath(); source = Uri.fromFile(ioFile).toString(); } else { docFile = DocumentFile.fromSingleUri(context, uri); @@ -100,26 +104,18 @@ public class StoredFileHelper implements Serializable { this.srcType = this.docFile.getType(); } - StoredFileHelper(final File location, final String filename, final String mime) + StoredFileHelper(final Path location, final String filename, final String mime) throws IOException { - this.ioFile = new File(location, filename); + ioPath = location.resolve(filename); - if (this.ioFile.exists()) { - if (!this.ioFile.isFile() && !this.ioFile.delete()) { - throw new IOException("The filename is already in use by non-file entity " - + "and cannot overwrite it"); - } - } else { - if (!this.ioFile.createNewFile()) { - throw new IOException("Cannot create the file"); - } - } + Files.deleteIfExists(ioPath); + Files.createFile(ioPath); - this.source = Uri.fromFile(this.ioFile).toString(); - this.sourceTree = Uri.fromFile(location).toString(); + source = Uri.fromFile(ioPath.toFile()).toString(); + sourceTree = Uri.fromFile(location.toFile()).toString(); - this.srcName = ioFile.getName(); - this.srcType = mime; + srcName = ioPath.getFileName().toString(); + srcType = mime; } public StoredFileHelper(final Context context, @Nullable final Uri parent, @@ -129,7 +125,7 @@ public class StoredFileHelper implements Serializable { if (path.getScheme() == null || path.getScheme().equalsIgnoreCase(ContentResolver.SCHEME_FILE)) { - this.ioFile = new File(URI.create(this.source)); + this.ioPath = Paths.get(URI.create(this.source)); } else { final DocumentFile file = DocumentFile.fromSingleUri(context, path); @@ -187,7 +183,7 @@ public class StoredFileHelper implements Serializable { assertValid(); if (docFile == null) { - return new FileStream(ioFile); + return new FileStream(ioPath.toFile()); } else { return new FileStreamSAF(context.getContentResolver(), docFile.getUri()); } @@ -211,7 +207,7 @@ public class StoredFileHelper implements Serializable { public Uri getUri() { assertValid(); - return docFile == null ? Uri.fromFile(ioFile) : docFile.getUri(); + return docFile == null ? Uri.fromFile(ioPath.toFile()) : docFile.getUri(); } public Uri getParentUri() { @@ -233,7 +229,12 @@ public class StoredFileHelper implements Serializable { return true; } if (docFile == null) { - return ioFile.delete(); + try { + return Files.deleteIfExists(ioPath); + } catch (final IOException e) { + Log.e(TAG, "Exception while deleting " + ioPath, e); + return false; + } } final boolean res = docFile.delete(); @@ -252,21 +253,30 @@ public class StoredFileHelper implements Serializable { public long length() { assertValid(); - return docFile == null ? ioFile.length() : docFile.length(); + if (docFile == null) { + try { + return Files.size(ioPath); + } catch (final IOException e) { + Log.e(TAG, "Exception while getting the size of " + ioPath, e); + return 0; + } + } else { + return docFile.length(); + } } public boolean canWrite() { if (source == null) { return false; } - return docFile == null ? ioFile.canWrite() : docFile.canWrite(); + return docFile == null ? Files.isWritable(ioPath) : docFile.canWrite(); } public String getName() { if (source == null) { return srcName; } else if (docFile == null) { - return ioFile.getName(); + return ioPath.getFileName().toString(); } final String name = docFile.getName(); @@ -287,12 +297,11 @@ public class StoredFileHelper implements Serializable { } public boolean existsAsFile() { - if (source == null || (docFile == null && ioFile == null)) { + if (source == null || (docFile == null && ioPath == null)) { if (DEBUG) { Log.d(TAG, "existsAsFile called but something is null: source = [" + (source == null ? "null => storage is invalid" : source) - + "], docFile = [" + (docFile == null ? "null" : docFile) - + "], ioFile = [" + (ioFile == null ? "null" : ioFile) + "]"); + + "], docFile = [" + docFile + "], ioPath = [" + ioPath + "]"); } return false; } @@ -300,7 +309,7 @@ public class StoredFileHelper implements Serializable { // WARNING: DocumentFile.exists() and DocumentFile.isFile() methods are slow // docFile.isVirtual() means it is non-physical? return docFile == null - ? (ioFile.exists() && ioFile.isFile()) + ? Files.isRegularFile(ioPath) : (docFile.exists() && docFile.isFile()); } @@ -310,8 +319,10 @@ public class StoredFileHelper implements Serializable { if (docFile == null) { try { - result = ioFile.createNewFile(); + Files.createFile(ioPath); + result = true; } catch (final IOException e) { + Log.e(TAG, "Exception while creating " + ioPath, e); return false; } } else if (docTree == null) { @@ -332,7 +343,8 @@ public class StoredFileHelper implements Serializable { } if (result) { - source = (docFile == null ? Uri.fromFile(ioFile) : docFile.getUri()).toString(); + source = (docFile == null ? Uri.fromFile(ioPath.toFile()) : docFile.getUri()) + .toString(); srcName = getName(); srcType = getType(); } @@ -352,7 +364,7 @@ public class StoredFileHelper implements Serializable { docTree = null; docFile = null; - ioFile = null; + ioPath = null; context = null; } @@ -383,7 +395,7 @@ public class StoredFileHelper implements Serializable { } if (this.isDirect()) { - return this.ioFile.getPath().equalsIgnoreCase(storage.ioFile.getPath()); + return this.ioPath.equals(storage.ioPath); } return DocumentsContract.getDocumentId(this.docFile.getUri()) From 4e41e12bd24286a6c903e55a3c9f8eae58f18e6e Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sun, 17 Sep 2023 15:31:06 +0200 Subject: [PATCH 3/4] Small code and doc improvements Remove unnecessary else-branch and use Collections.isEmpty(). Add doc comment for splitFilename() --- .../streams/io/StoredDirectoryHelper.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java index dede5088e..1b74c90a5 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java @@ -102,24 +102,24 @@ public class StoredDirectoryHelper { } } - if (matches.size() < 1) { + if (matches.isEmpty()) { return createFile(name, mime, true); - } else { - // check if the filename is in use - String lcName = name.toLowerCase(); - for (final String testName : matches) { - if (testName.equals(lcName)) { - lcName = null; - break; - } - } + } - // check if not in use - if (lcName != null) { - return createFile(name, mime, true); + // check if the filename is in use + String lcName = name.toLowerCase(); + for (final String testName : matches) { + if (testName.equals(lcName)) { + lcName = null; + break; } } + // create file if filename not in use + if (lcName != null) { + return createFile(name, mime, true); + } + Collections.sort(matches, String::compareTo); for (int i = 1; i < 1000; i++) { @@ -261,6 +261,12 @@ public class StoredDirectoryHelper { } } + /** + * Splits the filename into the name and extension. + * + * @param filename The filename to split + * @return A String array with the name at index 0 and extension at index 1 + */ private static String[] splitFilename(@NonNull final String filename) { final int dotIndex = filename.lastIndexOf('.'); From f2c2f1735e6c986c3631d601ebe3ff42658069bd Mon Sep 17 00:00:00 2001 From: TobiGr Date: Sun, 17 Sep 2023 15:25:47 +0200 Subject: [PATCH 4/4] Replace RuntimeException with IOException The RuntimeException was not explicitly declared and thus not caught at every call of this constructor. This change ensures that this possible exception is handled by the dedicated error handlers. --- .../java/org/schabi/newpipe/streams/io/StoredFileHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java b/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java index 9f8ef2e6c..5404426c4 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java +++ b/app/src/main/java/org/schabi/newpipe/streams/io/StoredFileHelper.java @@ -130,7 +130,7 @@ public class StoredFileHelper implements Serializable { final DocumentFile file = DocumentFile.fromSingleUri(context, path); if (file == null) { - throw new RuntimeException("SAF not available"); + throw new IOException("SAF not available"); } this.context = context;