All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dankito.utils.io.FileUtils.kt Maven / Gradle / Ivy

There is a newer version: 1.0.20
Show newest version
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)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy