Skip to content

Commit d61b4b8

Browse files
authored
Merge pull request #10992 from Stypox/fix-download-nnfp
Fix free storage space check for all APIs
2 parents 00770fc + b8daf16 commit d61b4b8

3 files changed

Lines changed: 55 additions & 67 deletions

File tree

app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -859,20 +859,19 @@ private void prepareSelectedDownload() {
859859
return;
860860
}
861861

862-
// Check for free memory space (for api 24 and up)
863-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
864-
final long freeSpace = mainStorage.getFreeMemory();
865-
if (freeSpace <= size) {
866-
Toast.makeText(context, getString(R.
867-
string.error_insufficient_storage), Toast.LENGTH_LONG).show();
868-
// move the user to storage setting tab
869-
final Intent storageSettingsIntent = new Intent(Settings.
870-
ACTION_INTERNAL_STORAGE_SETTINGS);
871-
if (storageSettingsIntent.resolveActivity(context.getPackageManager()) != null) {
872-
startActivity(storageSettingsIntent);
873-
}
874-
return;
862+
// Check for free storage space
863+
final long freeSpace = mainStorage.getFreeStorageSpace();
864+
if (freeSpace <= size) {
865+
Toast.makeText(context, getString(R.
866+
string.error_insufficient_storage), Toast.LENGTH_LONG).show();
867+
// move the user to storage setting tab
868+
final Intent storageSettingsIntent = new Intent(Settings.
869+
ACTION_INTERNAL_STORAGE_SETTINGS);
870+
if (storageSettingsIntent.resolveActivity(context.getPackageManager())
871+
!= null) {
872+
startActivity(storageSettingsIntent);
875873
}
874+
return;
876875
}
877876

878877
// check for existing file with the same name

app/src/main/java/org/schabi/newpipe/streams/io/StoredDirectoryHelper.java

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
package org.schabi.newpipe.streams.io;
22

3+
import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
4+
import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
5+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
6+
37
import android.content.ContentResolver;
48
import android.content.Context;
59
import android.content.Intent;
610
import android.database.Cursor;
711
import android.net.Uri;
8-
import android.os.Build;
9-
import android.os.storage.StorageManager;
10-
import android.os.storage.StorageVolume;
12+
import android.os.ParcelFileDescriptor;
1113
import android.provider.DocumentsContract;
14+
import android.system.Os;
15+
import android.system.StructStatVfs;
1216
import android.util.Log;
1317

1418
import androidx.annotation.NonNull;
1519
import androidx.annotation.Nullable;
16-
import androidx.annotation.RequiresApi;
1720
import androidx.documentfile.provider.DocumentFile;
1821

1922
import org.schabi.newpipe.settings.NewPipeSettings;
2023
import org.schabi.newpipe.util.FilePickerActivityHelper;
2124

25+
import java.io.FileDescriptor;
2226
import java.io.IOException;
2327
import java.net.URI;
2428
import java.nio.file.Files;
@@ -27,16 +31,9 @@
2731
import java.util.ArrayList;
2832
import java.util.Collections;
2933
import java.util.List;
30-
import java.util.UUID;
3134
import java.util.stream.Collectors;
3235
import java.util.stream.Stream;
3336

34-
import static android.provider.DocumentsContract.Document.COLUMN_DISPLAY_NAME;
35-
import static android.provider.DocumentsContract.Root.COLUMN_DOCUMENT_ID;
36-
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
37-
38-
import us.shandian.giga.util.Utility;
39-
4037
public class StoredDirectoryHelper {
4138
private static final String TAG = StoredDirectoryHelper.class.getSimpleName();
4239
public static final int PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -45,6 +42,10 @@ public class StoredDirectoryHelper {
4542
private Path ioTree;
4643
private DocumentFile docTree;
4744

45+
/**
46+
* Context is `null` for non-SAF files, i.e. files that use `ioTree`.
47+
*/
48+
@Nullable
4849
private Context context;
4950

5051
private final String tag;
@@ -176,41 +177,43 @@ public boolean isDirect() {
176177
}
177178

178179
/**
179-
* Get free memory of the storage partition (root of the directory).
180-
* @return amount of free memory in the volume of current directory (bytes)
180+
* Get free memory of the storage partition this file belongs to (root of the directory).
181+
* See <a href="https://stackoverflow.com/q/31171838">StackOverflow</a> and
182+
* <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html">
183+
* {@code statvfs()} and {@code fstatvfs()} docs</a>
184+
*
185+
* @return amount of free memory in the volume of current directory (bytes), or {@link
186+
* Long#MAX_VALUE} if an error occurred
181187
*/
182-
@RequiresApi(api = Build.VERSION_CODES.N) // Necessary for `getStorageVolume()`
183-
public long getFreeMemory() {
184-
final Uri uri = getUri();
185-
final StorageManager storageManager = (StorageManager) context.
186-
getSystemService(Context.STORAGE_SERVICE);
187-
final List<StorageVolume> volumes = storageManager.getStorageVolumes();
188-
189-
final String docId = DocumentsContract.getDocumentId(uri);
190-
final String[] split = docId.split(":");
191-
if (split.length > 0) {
192-
final String volumeId = split[0];
193-
194-
for (final StorageVolume volume : volumes) {
195-
// if the volume is an internal system volume
196-
if (volume.isPrimary() && volumeId.equalsIgnoreCase("primary")) {
197-
return Utility.getSystemFreeMemory();
198-
}
188+
public long getFreeStorageSpace() {
189+
try {
190+
final StructStatVfs stat;
191+
192+
if (ioTree != null) {
193+
// non-SAF file, use statvfs with the path directly (also, `context` would be null
194+
// for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway)
195+
stat = Os.statvfs(ioTree.toString());
199196

200-
// if the volume is a removable volume (normally an SD card)
201-
if (volume.isRemovable() && !volume.isPrimary()) {
202-
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
203-
try {
204-
final String sdCardUUID = volume.getUuid();
205-
return storageManager.getAllocatableBytes(UUID.fromString(sdCardUUID));
206-
} catch (final Exception e) {
207-
// do nothing
208-
}
197+
} else {
198+
// SAF file, we can't get a path directly, so obtain a file descriptor first
199+
// and then use fstatvfs with the file descriptor
200+
try (ParcelFileDescriptor parcelFileDescriptor =
201+
context.getContentResolver().openFileDescriptor(getUri(), "r")) {
202+
if (parcelFileDescriptor == null) {
203+
return Long.MAX_VALUE;
209204
}
205+
final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
206+
stat = Os.fstatvfs(fileDescriptor);
210207
}
211208
}
209+
210+
// this is the same formula used inside the FsStat class
211+
return stat.f_bavail * stat.f_frsize;
212+
} catch (final Throwable e) {
213+
// ignore any error
214+
Log.e(TAG, "Could not get free storage space", e);
215+
return Long.MAX_VALUE;
212216
}
213-
return Long.MAX_VALUE;
214217
}
215218

216219
/**

app/src/main/java/us/shandian/giga/util/Utility.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,6 @@ public enum FileType {
4040
UNKNOWN
4141
}
4242

43-
/**
44-
* Get amount of free system's memory.
45-
* @return free memory (bytes)
46-
*/
47-
public static long getSystemFreeMemory() {
48-
try {
49-
final StatFs statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
50-
return statFs.getAvailableBlocksLong() * statFs.getBlockSizeLong();
51-
} catch (final Exception e) {
52-
// do nothing
53-
}
54-
return -1;
55-
}
56-
5743
public static String formatBytes(long bytes) {
5844
Locale locale = Locale.getDefault();
5945
if (bytes < 1024) {

0 commit comments

Comments
 (0)