From c3c39a7b24af5c3b062999103e90be3e56547850 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 12:16:06 +0200 Subject: [PATCH 1/3] Fix free storage space check for all APIs See https://stackoverflow.com/q/31171838 See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html --- .../newpipe/download/DownloadDialog.java | 25 +++--- .../streams/io/StoredDirectoryHelper.java | 77 +++++++++---------- .../java/us/shandian/giga/util/Utility.java | 14 ---- 3 files changed, 50 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index bbdb46292..db2066b27 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -859,20 +859,19 @@ public class DownloadDialog extends DialogFragment return; } - // Check for free memory space (for api 24 and up) - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - final long freeSpace = mainStorage.getFreeMemory(); - if (freeSpace <= size) { - Toast.makeText(context, getString(R. - string.error_insufficient_storage), Toast.LENGTH_LONG).show(); - // move the user to storage setting tab - final Intent storageSettingsIntent = new Intent(Settings. - ACTION_INTERNAL_STORAGE_SETTINGS); - if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) { - startActivity(storageSettingsIntent); - } - return; + // Check for free storage space + final long freeSpace = mainStorage.getFreeStorageSpace(); + if (freeSpace <= size) { + Toast.makeText(context, getString(R. + string.error_insufficient_storage), Toast.LENGTH_LONG).show(); + // move the user to storage setting tab + final Intent storageSettingsIntent = new Intent(Settings. + ACTION_INTERNAL_STORAGE_SETTINGS); + if (storageSettingsIntent.resolveActivity(context.getPackageManager()) + != null) { + startActivity(storageSettingsIntent); } + return; } // check for existing file with the same name 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 0fe2e0408..8dd819293 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 @@ -1,24 +1,29 @@ package org.schabi.newpipe.streams.io; +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; + import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; -import android.os.Build; -import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; +import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStatVfs; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.documentfile.provider.DocumentFile; import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.util.FilePickerActivityHelper; +import java.io.FileDescriptor; import java.io.IOException; import java.net.URI; import java.nio.file.Files; @@ -27,16 +32,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.UUID; 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; - -import us.shandian.giga.util.Utility; - public class StoredDirectoryHelper { private static final String TAG = StoredDirectoryHelper.class.getSimpleName(); public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -45,6 +43,7 @@ public class StoredDirectoryHelper { private Path ioTree; private DocumentFile docTree; + // will be `null` for non-SAF files, i.e. files that use `ioTree` private Context context; private final String tag; @@ -176,41 +175,41 @@ public class StoredDirectoryHelper { } /** - * Get free memory of the storage partition (root of the directory). - * @return amount of free memory in the volume of current directory (bytes) + * Get free memory of the storage partition this file belongs to (root of the directory). + * See StackOverflow and + * + * {@code statvfs()} and {@code fstatvfs()} docs + * + * @return amount of free memory in the volume of current directory (bytes), or {@link + * Long#MAX_VALUE} if an error occurred */ - @RequiresApi(api = Build.VERSION_CODES.N) // Necessary for `getStorageVolume()` - public long getFreeMemory() { - final Uri uri = getUri(); - final StorageManager storageManager = (StorageManager) context. - getSystemService(Context.STORAGE_SERVICE); - final List volumes = storageManager.getStorageVolumes(); + public long getFreeStorageSpace() { + try { + final StructStatVfs stat; - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - if (split.length > 0) { - final String volumeId = split[0]; + if (ioTree != null) { + // non-SAF file, use statvfs with the path directly (also, `context` would be null + // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway) + stat = Os.statvfs(ioTree.toString()); - for (final StorageVolume volume : volumes) { - // if the volume is an internal system volume - if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) { - return Utility.getSystemFreeMemory(); - } - - // if the volume is a removable volume (normally an SD card) - if (volume.isRemovable() && !volume.isPrimary()) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - try { - final String sdCardUUID = volume.getUuid(); - return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID)); - } catch (final Exception e) { - // do nothing - } + } else { + // SAF file, we can't get a path directly, so obtain a file descriptor first + // and then use fstatvfs with the file descriptor + try (ParcelFileDescriptor parcelFileDescriptor = + context.getContentResolver().openFileDescriptor(getUri(), "r")) { + if (parcelFileDescriptor == null) { + return Long.MAX_VALUE; } + final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + stat = Os.fstatvfs(fileDescriptor); } } + + // this is the same formula used inside the FsStat class + return stat.f_bavail * stat.f_frsize; + } catch (final IOException | ErrnoException e) { + return Long.MAX_VALUE; } - return Long.MAX_VALUE; } /** diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java index c75269757..86a08c57f 100644 --- a/app/src/main/java/us/shandian/giga/util/Utility.java +++ b/app/src/main/java/us/shandian/giga/util/Utility.java @@ -40,20 +40,6 @@ public class Utility { UNKNOWN } - /** - * Get amount of free system's memory. - * @return free memory (bytes) - */ - public static long getSystemFreeMemory() { - try { - final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath()); - return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong(); - } catch (final Exception e) { - // do nothing - } - return -1; - } - public static String formatBytes(long bytes) { Locale locale = Locale.getDefault(); if (bytes < 1024) { From caa3812e13d888f31b23487505462eea85f12e96 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 18:05:31 +0200 Subject: [PATCH 2/3] Ignore all errors when getting free storage space It's not a critical check that needs to be perfomed, so in case something does not work on some device/version, let's just ignore the error. --- .../org/schabi/newpipe/streams/io/StoredDirectoryHelper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 8dd819293..3f6bf37ea 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 @@ -11,7 +11,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; -import android.system.ErrnoException; import android.system.Os; import android.system.StructStatVfs; import android.util.Log; @@ -207,7 +206,9 @@ public class StoredDirectoryHelper { // this is the same formula used inside the FsStat class return stat.f_bavail * stat.f_frsize; - } catch (final IOException | ErrnoException e) { + } catch (final Throwable e) { + // ignore any error + Log.e(TAG, "Could not get free storage space", e); return Long.MAX_VALUE; } } From b8daf16b920e90ed98d3a06b4c6ddf30a21171f4 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 18:39:56 +0200 Subject: [PATCH 3/3] Update app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java Co-authored-by: Tobi --- .../org/schabi/newpipe/streams/io/StoredDirectoryHelper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 3f6bf37ea..bb47a4b91 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 @@ -42,7 +42,10 @@ public class StoredDirectoryHelper { private Path ioTree; private DocumentFile docTree; - // will be `null` for non-SAF files, i.e. files that use `ioTree` + /** + * Context is `null` for non-SAF files, i.e. files that use `ioTree`. + */ + @Nullable private Context context; private final String tag;