11package 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+
37import android .content .ContentResolver ;
48import android .content .Context ;
59import android .content .Intent ;
610import android .database .Cursor ;
711import 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 ;
1113import android .provider .DocumentsContract ;
14+ import android .system .ErrnoException ;
15+ import android .system .Os ;
16+ import android .system .StructStatVfs ;
1217import android .util .Log ;
1318
1419import androidx .annotation .NonNull ;
1520import androidx .annotation .Nullable ;
16- import androidx .annotation .RequiresApi ;
1721import androidx .documentfile .provider .DocumentFile ;
1822
1923import org .schabi .newpipe .settings .NewPipeSettings ;
2024import org .schabi .newpipe .util .FilePickerActivityHelper ;
2125
26+ import java .io .FileDescriptor ;
2227import java .io .IOException ;
2328import java .net .URI ;
2429import java .nio .file .Files ;
2732import java .util .ArrayList ;
2833import java .util .Collections ;
2934import java .util .List ;
30- import java .util .UUID ;
3135import java .util .stream .Collectors ;
3236import java .util .stream .Stream ;
3337
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-
4038public class StoredDirectoryHelper {
4139 private static final String TAG = StoredDirectoryHelper .class .getSimpleName ();
4240 public static final int PERMISSION_FLAGS = Intent .FLAG_GRANT_READ_URI_PERMISSION
@@ -45,6 +43,7 @@ public class StoredDirectoryHelper {
4543 private Path ioTree ;
4644 private DocumentFile docTree ;
4745
46+ // will be `null` for non-SAF files, i.e. files that use `ioTree`
4847 private Context context ;
4948
5049 private final String tag ;
@@ -176,41 +175,41 @@ public boolean isDirect() {
176175 }
177176
178177 /**
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)
178+ * Get free memory of the storage partition this file belongs to (root of the directory).
179+ * See <a href="https://stackoverflow.com/q/31171838">StackOverflow</a> and
180+ * <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatvfs.html">
181+ * {@code statvfs()} and {@code fstatvfs()} docs</a>
182+ *
183+ * @return amount of free memory in the volume of current directory (bytes), or {@link
184+ * Long#MAX_VALUE} if an error occurred
181185 */
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- }
186+ public long getFreeStorageSpace () {
187+ try {
188+ final StructStatVfs stat ;
199189
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- }
190+ if (ioTree != null ) {
191+ // non-SAF file, use statvfs with the path directly (also, `context` would be null
192+ // for non-SAF files, so we wouldn't be able to call `getContentResolver` anyway)
193+ stat = Os .statvfs (ioTree .toString ());
194+
195+ } else {
196+ // SAF file, we can't get a path directly, so obtain a file descriptor first
197+ // and then use fstatvfs with the file descriptor
198+ try (ParcelFileDescriptor parcelFileDescriptor =
199+ context .getContentResolver ().openFileDescriptor (getUri (), "r" )) {
200+ if (parcelFileDescriptor == null ) {
201+ return Long .MAX_VALUE ;
209202 }
203+ final FileDescriptor fileDescriptor = parcelFileDescriptor .getFileDescriptor ();
204+ stat = Os .fstatvfs (fileDescriptor );
210205 }
211206 }
207+
208+ // this is the same formula used inside the FsStat class
209+ return stat .f_bavail * stat .f_frsize ;
210+ } catch (final IOException | ErrnoException e ) {
211+ return Long .MAX_VALUE ;
212212 }
213- return Long .MAX_VALUE ;
214213 }
215214
216215 /**
0 commit comments