Background
Suppose I'm about to create a large file (or even multiple of them), and yet I want to be sure I will succeed.
The problem
I was sure that to do it, I need to use either freeSpace or usableSpace on the File that I'm about to create or modify.
But, as it turns out, Android is more sophisticated than that: Android has cache folders that will get reduced in size when needed, and so the docs say that I shouldn't rely on these functions ("The returned number of available bytes is a hint, but not a guarantee") .
Plus not sure which is more trustful.
So, even if you get some number from these functions, and you try to create a file that's larger in space than those, you might probably still succeed, as the OS will clear some cache for you. That's quite a good approach.
What I've found
I was told that these should probably be used:
getAllocatableBytes - This returns bytes that an app could allocate and it considers cache the system would be willing to delete. The app can then allocate space using: allocateBytes(FileDescriptor,long), allocateBytes(UUID,long)- This allocates the requested bytes for an app (deleting any cached files necessary to satisfy the request).
allocateBytes(FileDescriptor,long) "guarantees that bytes have been allocated to the opened file, otherwise it will throw [...]" from getAllocatableBytes(UUID), while allocateBytes(UUID , long bytes) "may be subject to race conditions." from allocateBytes(UUID,long).
Os.open(String,int,int) and File.getAbsolutePath() might help to obtain a FileDescriptor.
Unsure what exactly this means, I still tried to do it myself.
Given a File or a file-path that I intend to create a large file into, I wanted to check how much free space I have for it, to know if I could really create it there, create it, and check again how much space is left :
val storageManager = ContextCompat.getSystemService(this, StorageManager::class.java)!!
val file = File(getExternalFilesDir(null), "largeFile.zip")
file.delete()
val uuidForPath = storageManager.getUuidForPath(file)
val allocatableBytes = storageManager.getAllocatableBytes(uuidForPath)
Log.d("AppLog", "allocatableBytes :${Formatter.formatFileSize(this, allocatableBytes)} - $allocatableBytes bytes")
Log.d("AppLog", "trying to create a new, large file:")
file.parentFile!!.mkdirs()
val fileDescriptor = Os.open(file.absolutePath, 0, 0)
val fileSize: Long = 2L * 1024L * 1024L * 1024L
storageManager.allocateBytes(fileDescriptor, fileSize)
Log.d("AppLog", "file exists?${file.exists()} fileSize:${file.length()}")
val allocatableBytesAfterFileCreated = storageManager.getAllocatableBytes(uuidForPath)
Log.d("AppLog", "allocatableBytes :${Formatter.formatFileSize(this, allocatableBytesAfterFileCreated)} - $allocatableBytesAfterFileCreated bytes")
This showed me a result of 10.04 GB (10,042,085,376 bytes),but it's weird because on the settings app, it showed 10.57 GB free.
So I'm not sure if getting the available space is correct, because it's a bit far from what the OS says.
Also, when the app reached the part of creating the file (which is 2GB of size, that should be enough to fit into what I got), it failed with this exception:
android.system.ErrnoException: open failed: ENOENT (No such file or directory) at libcore.io.Linux.open(Native Method) at libcore.io.ForwardingOs.open(ForwardingOs.java:166) at libcore.io.BlockGuardOs.open(BlockGuardOs.java:254) at libcore.io.ForwardingOs.open(ForwardingOs.java:166) at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:7542) at android.system.Os.open(Os.java:412)
So even for creating of the file directly I couldn't figure out how to do it. Checking the file that was supposed to be created (using a file manager app), I couldn't see it, so indeed it failed.
I also tried the other function of allocateBytes, which gets the UUID of the file instead, and got the same exception.
Not only that, but all of these require Android O (API 26 - Android 8.0). I don't know what to do before. Surely there should be something similar that should suffice...
The questions
Seeing that I failed, I want to ask how to do it properly:
How can I query the real max size of a file that I can create on a given file path? Or, if I intend to create multiple files, to know that they could be created, before I really create them?
Even when creating the file/s, how can I allocate them right away (without querying), to see that indeed I've succeeded?
What's the best approach for this matter, when running on older Android versions (before Android 8.0)?
Are there other alternatives to this? I think I've heard , in the past (back in C/C++ lessons) that there is a trick to create a large file by moving a "cursor" inside of it to the farthest location you'd write into it. Maybe it's a possible alternative on Java/Kotlin too, on Android? Maybe using RandomAccessFile ?