net.dankito.utils.io.FileUtils.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-utils Show documentation
Show all versions of java-utils Show documentation
Some basic utils needed in many projects
package net.dankito.utils.io
import net.dankito.utils.extensions.isFull
import net.dankito.utils.extensions.writeToChannel
import net.dankito.utils.os.OsHelper
import java.io.*
import java.nio.ByteBuffer
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.*
import java.util.concurrent.ThreadLocalRandom
import kotlin.Comparator
import kotlin.collections.ArrayList
open class FileUtils(protected val osHelper: OsHelper = OsHelper()) {
companion object {
const val IllegalPathCharacters = "\\/:*?\"<>|"
val IllegalPathCharactersRegex = Regex("[$IllegalPathCharacters]")
const val DefaultFileOperationBufferSize = 16 * 1024 // be aware most other people / frameworks use 4 * 1024
const val MaxFolderDepth = Int.MAX_VALUE
}
@Throws(Exception::class)
open fun writeToTextFile(fileContent: String, file: File) {
val outputStreamWriter = OutputStreamWriter(createFileOutputStream(file))
outputStreamWriter.write(fileContent)
outputStreamWriter.flush()
outputStreamWriter.close()
}
@Throws(Exception::class)
open fun writeToBinaryFile(fileContent: ByteArray, file: File) {
val outputStream = createFileOutputStream(file)
outputStream.write(fileContent)
outputStream.flush()
outputStream.close()
}
@Throws(FileNotFoundException::class)
open fun createFileOutputStream(file: File): OutputStream {
return FileOutputStream(file)
}
@Throws(Exception::class)
open fun readFromTextFile(file: File): String? {
val inputStream = createFileInputStream(file)
val inputStreamReader = InputStreamReader(inputStream)
val bufferedReader = BufferedReader(inputStreamReader)
val fileContent = bufferedReader.use { it.readText() }
bufferedReader.close()
inputStream.close()
return fileContent
}
@Throws(Exception::class)
open fun readFromBinaryFile(file: File): ByteArray? {
val inputStream = createFileInputStream(file)
val buffer = ByteArrayOutputStream()
inputStream.copyTo(buffer, DefaultFileOperationBufferSize)
buffer.flush()
inputStream.close()
return buffer.toByteArray()
}
@JvmOverloads
open fun forEachBlock(file: File, blockSize: Int = DefaultFileOperationBufferSize, action: (buffer: ByteBuffer, bytesRead: Int) -> Unit) {
forEachBlock(file.toPath(), blockSize, action)
}
@JvmOverloads
open fun forEachBlock(path: Path, blockSize: Int = DefaultFileOperationBufferSize, action: (buffer: ByteBuffer, bytesRead: Int) -> Unit) {
val buffer = ByteBuffer.allocateDirect(blockSize)
Files.newByteChannel(path).use { channel ->
var bytesRead = channel.read(buffer)
while (bytesRead > 0) {
buffer.flip()
action(buffer, bytesRead)
bytesRead = channel.read(buffer)
}
}
}
@Throws(FileNotFoundException::class)
open fun createFileInputStream(file: File): InputStream {
return FileInputStream(file)
}
open fun deleteFolderRecursively(path: File) {
deleteRecursively(path)
}
protected open fun deleteRecursively(file: File) {
if (file.isDirectory) {
for (containingFile in file.listFiles()!!) {
deleteRecursively(containingFile)
}
}
file.delete()
}
open fun ensureFileInFolderExists(folder: File, filename: String, subFolderName: String?): File {
var folderVar = folder
subFolderName?.let { folderVar = File(folderVar, subFolderName) }
folderVar.mkdirs()
return File(folderVar, filename)
}
open fun getOsRootFolder(): File {
if (osHelper.isRunningOnAndroid) {
return File("/storage")
}
// TODO: add detection for Windows
// TODO: is this also valid for MacOS?
return File("/")
}
open fun getTempDir(): File {
val tempFile = File.createTempFile("going_to_be_deleted_immediately", ".tmp")
val tempDir = tempFile.parentFile
tempFile.delete()
return tempDir
}
@JvmOverloads
open fun createDirectoryInTempDir(directoryNamePrefix: String = ""): File {
val directoryName = directoryNamePrefix + if (directoryNamePrefix.isBlank()) "" else "_" + UUID.randomUUID().toString()
val directoryInTempDir = File(getTempDir(), directoryName)
directoryInTempDir.mkdirs()
directoryInTempDir.deleteOnExit()
return directoryInTempDir
}
open fun writeRandomContentToFile(file: File, fileSizeInBytes: Long) {
val random = ThreadLocalRandom.current()
val bufferSize = DefaultFileOperationBufferSize
val buffer = ByteBuffer.allocate(bufferSize)
Files.newByteChannel(file.toPath(), StandardOpenOption.WRITE).use { channel ->
for (i in 0 until fileSizeInBytes) {
buffer.put(random.nextInt(32, 127).toByte())
if (buffer.isFull || i == fileSizeInBytes - 1) {
buffer.writeToChannel(channel)
}
}
}
}
@JvmOverloads
open fun getFilesOfDirectorySorted(directory: File, listDirectory: ListDirectory = ListDirectory.DirectoriesAndFiles,
folderDepth: Int = 1, extensionsFilters: List = emptyList()): List? {
getFilesOfDirectory(directory, listDirectory, folderDepth, extensionsFilters)?.let { files ->
return files.sortedWith(fileComparator)
}
return null
}
@JvmOverloads
open fun getFilesOfDirectorySortedAsFileInfo(directory: File, listDirectory: ListDirectory = ListDirectory.DirectoriesAndFiles,
folderDepth: Int = 1, extensionsFilters: List = emptyList()): List? {
getFilesOfDirectorySorted(directory, listDirectory, 1, extensionsFilters)?.let { files ->
val fileInfos = files.map { JavaIoFileInfo(it) }
if (folderDepth > 1 && fileInfos.isNotEmpty()) {
fileInfos.forEach { fileInfo ->
if (fileInfo.isDirectory) {
fileInfo.subFiles = getFilesOfDirectorySortedAsFileInfo(fileInfo.file, listDirectory,
folderDepth - 1, extensionsFilters)
}
}
}
return fileInfos
}
return null
}
@JvmOverloads
open fun getFilesOfDirectory(directory: File, listDirectory: ListDirectory = ListDirectory.DirectoriesAndFiles,
folderDepth: Int = 1, extensionsFilters: List = emptyList()): List? {
val result = doGetFilesOfDirectory(extensionsFilters, listDirectory, directory)
if (folderDepth > 1 && result != null) {
val resultIncludingSubFolders = ArrayList(result)
result.forEach { fileOrFolder ->
if (fileOrFolder.isDirectory) {
getFilesOfDirectory(fileOrFolder, listDirectory, folderDepth - 1, extensionsFilters)?.let { subFoldersFiles ->
resultIncludingSubFolders.addAll(subFoldersFiles)
}
}
}
return resultIncludingSubFolders
}
return result
}
protected open fun doGetFilesOfDirectory(extensionsFilters: List, listDirectory: ListDirectory,
directory: File): List? {
if (extensionsFilters.isEmpty() && listDirectory == ListDirectory.DirectoriesAndFiles) {
return directory.listFiles()?.toList() // listFiles() can return null, e.g when not having permission to read this directory
}
val returnDirectories = listDirectory != ListDirectory.FilesOnly
val returnFiles = listDirectory != ListDirectory.DirectoriesOnly
val normalizedExtensionsFilters = normalizeExtensionFilters(extensionsFilters)
return directory.listFiles { file ->
file?.let {
return@listFiles (returnDirectories && file.isDirectory) ||
(returnFiles && file.isFile && (normalizedExtensionsFilters.isEmpty() ||
normalizedExtensionsFilters.contains(file.extension.toLowerCase())))
}
false
}?.toList()
}
/**
* Removes '*.' at start of extension filter on lower cases extension
*/
open fun normalizeExtensionFilters(extensionsFilters: List): List {
return extensionsFilters.map {
var normalizedFilter = it
if(normalizedFilter.startsWith('*')) {
normalizedFilter = normalizedFilter.substring(1)
}
if(normalizedFilter.startsWith('.')) {
normalizedFilter = normalizedFilter.substring(1)
}
normalizedFilter.toLowerCase()
}
}
open fun isExistingFile(string: String): Boolean {
try {
val file = File(string)
return file.exists() && file.isFile
} catch(ignored: Exception) { }
return false
}
open fun removeIllegalPathCharacters(path: String, replacementString: String): String {
return replaceIllegalPathCharacters(path, "")
}
open fun replaceIllegalPathCharacters(path: String, replacementString: String): String {
return path.replace(IllegalPathCharactersRegex, replacementString)
}
protected val fileComparator = Comparator { file0, file1 ->
if(file0 != null && file1 == null) {
return@Comparator -1
}
else if(file0 == null && file1 != null) {
return@Comparator 1
}
else if(file0 == null && file1 == null) {
return@Comparator 0
}
if(file0.isDirectory && file1.isDirectory == false) { // list directories before files
return@Comparator -1
}
else if(file0.isDirectory == false && file1.isDirectory) {
return@Comparator 1
}
return@Comparator file0.name.compareTo(file1.name, true)
}
}