Fix free storage space check for all APIs

See https://stackoverflow.com/q/31171838
See https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html
This commit is contained in:
Stypox 2024-04-23 12:16:06 +02:00
parent 00770fc634
commit c3c39a7b24
No known key found for this signature in database
GPG key ID: 4BDF1B40A49FDD23
3 changed files with 50 additions and 66 deletions

View file

@ -859,20 +859,19 @@ public class DownloadDialog extends DialogFragment
return; return;
} }
// Check for free memory space (for api 24 and up) // Check for free storage space
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { final long freeSpace = mainStorage.getFreeStorageSpace();
final long freeSpace = mainStorage.getFreeMemory(); if (freeSpace <= size) {
if (freeSpace <= size) { Toast.makeText(context, getString(R.
Toast.makeText(context, getString(R. string.error_insufficient_storage), Toast.LENGTH_LONG).show();
string.error_insufficient_storage), Toast.LENGTH_LONG).show(); // move the user to storage setting tab
// move the user to storage setting tab final Intent storageSettingsIntent = new Intent(Settings.
final Intent storageSettingsIntent = new Intent(Settings. ACTION_INTERNAL_STORAGE_SETTINGS);
ACTION_INTERNAL_STORAGE_SETTINGS); if (storageSettingsIntent.resolveActivity(context.getPackageManager())
if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) { != null) {
startActivity(storageSettingsIntent); startActivity(storageSettingsIntent);
}
return;
} }
return;
} }
// check for existing file with the same name // check for existing file with the same name

View file

@ -1,24 +1,29 @@
package org.schabi.newpipe.streams.io; 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.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.ParcelFileDescriptor;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.DocumentsContract; import android.provider.DocumentsContract;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStatVfs;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.documentfile.provider.DocumentFile; import androidx.documentfile.provider.DocumentFile;
import org.schabi.newpipe.settings.NewPipeSettings; import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.util.FilePickerActivityHelper; import org.schabi.newpipe.util.FilePickerActivityHelper;
import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.nio.file.Files; import java.nio.file.Files;
@ -27,16 +32,9 @@ import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; 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 { public class StoredDirectoryHelper {
private static final String TAG = StoredDirectoryHelper.class.getSimpleName(); private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@ -45,6 +43,7 @@ public class StoredDirectoryHelper {
private Path ioTree; private Path ioTree;
private DocumentFile docTree; private DocumentFile docTree;
// will be `null` for non-SAF files, i.e. files that use `ioTree`
private Context context; private Context context;
private final String tag; private final String tag;
@ -176,41 +175,41 @@ public class StoredDirectoryHelper {
} }
/** /**
* Get free memory of the storage partition (root of the directory). * Get free memory of the storage partition this file belongs to (root of the directory).
* @return amount of free memory in the volume of current directory (bytes) * See <a href="https://stackoverflow.com/q/31171838">StackOverflow</a> and
* <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html">
* {@code statvfs()} and {@code fstatvfs()} docs</a>
*
* @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 getFreeStorageSpace() {
public long getFreeMemory() { try {
final Uri uri = getUri(); final StructStatVfs stat;
final StorageManager storageManager = (StorageManager) context.
getSystemService(Context.STORAGE_SERVICE);
final List<StorageVolume> volumes = storageManager.getStorageVolumes();
final String docId = DocumentsContract.getDocumentId(uri); if (ioTree != null) {
final String[] split = docId.split(":"); // non-SAF file, use statvfs with the path directly (also, `context` would be null
if (split.length > 0) { // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway)
final String volumeId = split[0]; stat = Os.statvfs(ioTree.toString());
for (final StorageVolume volume : volumes) { } else {
// if the volume is an internal system volume // SAF file, we can't get a path directly, so obtain a file descriptor first
if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) { // and then use fstatvfs with the file descriptor
return Utility.getSystemFreeMemory(); try (ParcelFileDescriptor parcelFileDescriptor =
} context.getContentResolver().openFileDescriptor(getUri(), "r")) {
if (parcelFileDescriptor == null) {
// if the volume is a removable volume (normally an SD card) return Long.MAX_VALUE;
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
}
} }
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;
} }
/** /**

View file

@ -40,20 +40,6 @@ public class Utility {
UNKNOWN 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) { public static String formatBytes(long bytes) {
Locale locale = Locale.getDefault(); Locale locale = Locale.getDefault();
if (bytes < 1024) { if (bytes < 1024) {