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:
parent
00770fc634
commit
c3c39a7b24
3 changed files with 50 additions and 66 deletions
|
@ -859,21 +859,20 @@ 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()) != null) {
|
if (storageSettingsIntent.resolveActivity(context.getPackageManager())
|
||||||
|
!= null) {
|
||||||
startActivity(storageSettingsIntent);
|
startActivity(storageSettingsIntent);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// check for existing file with the same name
|
// check for existing file with the same name
|
||||||
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
|
checkSelectedDownload(mainStorage, mainStorage.findFile(filenameTmp), filenameTmp,
|
||||||
|
|
|
@ -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,42 +175,42 @@ 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() {
|
|
||||||
final Uri uri = getUri();
|
|
||||||
final StorageManager storageManager = (StorageManager) context.
|
|
||||||
getSystemService(Context.STORAGE_SERVICE);
|
|
||||||
final List<StorageVolume> volumes = storageManager.getStorageVolumes();
|
|
||||||
|
|
||||||
final String docId = DocumentsContract.getDocumentId(uri);
|
|
||||||
final String[] split = docId.split(":");
|
|
||||||
if (split.length > 0) {
|
|
||||||
final String volumeId = split[0];
|
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
final String sdCardUUID = volume.getUuid();
|
final StructStatVfs stat;
|
||||||
return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID));
|
|
||||||
} catch (final Exception e) {
|
if (ioTree != null) {
|
||||||
// do nothing
|
// 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());
|
||||||
}
|
|
||||||
}
|
} 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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only using Java I/O. Creates the directory named by this abstract pathname, including any
|
* Only using Java I/O. Creates the directory named by this abstract pathname, including any
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue