Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the download helpers using the Java 7 NIO API. #10248

Merged
merged 4 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand All @@ -14,21 +15,27 @@
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;
Expand All @@ -40,7 +47,7 @@ public StoredDirectoryHelper(@NonNull final Context context, @NonNull final Uri
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;
}

Expand All @@ -64,13 +71,17 @@ public StoredFileHelper createFile(final String filename, final String mime) {
}

public StoredFileHelper createUniqueFile(final String name, final String mime) {
final ArrayList<String> matches = new ArrayList<>();
final List<String> 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<Path> 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
Expand All @@ -82,37 +93,37 @@ public StoredFileHelper createUniqueFile(final String name, final String mime) {
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));
}
}
}
}

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++) {
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);
}
}
Expand Down Expand Up @@ -141,11 +152,11 @@ private StoredFileHelper createFile(final String filename, final String mime,
}

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();
}

/**
Expand All @@ -169,7 +180,9 @@ public boolean isDirect() {
*/
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()) {
Expand Down Expand Up @@ -206,16 +219,16 @@ public String getTag() {

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);
return res == null ? null : res.getUri();
}

public boolean canWrite() {
return docTree == null ? ioTree.canWrite() : docTree.canWrite();
return docTree == null ? Files.isWritable(ioTree) : docTree.canWrite();
}

/**
Expand All @@ -230,14 +243,14 @@ public boolean isInvalidSafStorage() {
@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<String> list, @NonNull final String base,
private static void addIfStartWith(final List<String> list, @NonNull final String base,
final String str) {
if (isNullOrEmpty(str)) {
return;
Expand All @@ -248,6 +261,12 @@ private static void addIfStartWith(final ArrayList<String> list, @NonNull final
}
}

/**
* 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('.');

Expand All @@ -259,7 +278,7 @@ private static String[] splitFilename(@NonNull final String filename) {
}

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;
}

/**
Expand Down
Loading
Loading