From c3c39a7b24af5c3b062999103e90be3e56547850 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 23 Apr 2024 12:16:06 +0200 Subject: [PATCH] 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) {