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

nativeMain.File.kt Maven / Gradle / Ivy

package dev.petuska.klip.core.ext

import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.alloc
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.staticCFunction
import kotlinx.cinterop.toKString
import platform.posix.EOF
import platform.posix.FTW_DEPTH
import platform.posix.F_OK
import platform.posix.S_IFDIR
import platform.posix.S_IFMT
import platform.posix.S_IRWXU
import platform.posix.access
import platform.posix.fclose
import platform.posix.fgets
import platform.posix.fopen
import platform.posix.fputs
import platform.posix.getcwd
import platform.posix.nftw
import platform.posix.perror
import platform.posix.remove
import platform.posix.rmdir
import platform.posix.stat

/**
 * Multiplatform wrapper over java.io.File
 */
public actual class File actual constructor(path: String) {
  private val _path: String

  init {
    require(path.isNotEmpty()) { "Path cannot be empty" }
    this._path = cleanupPath(path)
  }

  /**
   * Retrieves parent file
   */
  public actual fun getParentFile(): File? {
    val pPath = _path.removeSuffix(separator)
      .let { it.removeSuffix(it.substringAfterLast(separator)) }
      .removeSuffix(separator)
    return if (pPath.isEmpty()) {
      null
    } else {
      File(pPath)
    }
  }

  /**
   * Returns local path to this file
   */
  public actual fun getPath(): String = _path

  /**
   * Returns absolute path to this file
   */
  public actual fun getAbsolutePath(): String {
    return if (isRooted) {
      _path
    } else {
      memScoped {
        "${getcwd(null, 0)!!.toKString()}$separator$_path"
      }
    }
  }

  /**
   * Recursively makes all directories up to this directory file
   */
  public actual fun mkdirs(): Boolean {
    val parent = getParentFile()
    if (parent != null && !parent.exists() && parent._path != separator && parent._path.isNotEmpty()) {
      parent.mkdirs()
    }

    return mppMkdir(_path, S_IRWXU) == 0
  }

  /**
   * Checks if the file exists
   */
  public actual fun exists(): Boolean = access(_path, F_OK) == 0

  /**
   * checks if the file is directory
   */
  public actual fun isDirectory(): Boolean = memScoped {
    val stat = alloc()
    if (stat(_path, stat.ptr) != 0) {
      false
    } else {
      S_IFDIR == (stat.st_mode and S_IFMT.convert()).convert()
    }
  }

  actual override fun toString(): String = getPath()
}

/**
 * Writes text to file creating it if needed and fully overwriting any previous content
 */
public actual fun File.writeText(text: String) {
  val file = fopen(getPath(), "w") ?: error("Cannot open output file ${getPath()}")
  try {
    memScoped {
      if (fputs(text, file) == EOF) {
        error("File write error")
      }
    }
  } finally {
    fclose(file)
  }
}

/**
 * Reads this file as text
 */
public actual fun File.readText(): String {
  val returnBuffer = StringBuilder()
  val file = fopen(getPath(), "r") ?: error("Cannot open input file ${getPath()}")

  try {
    memScoped {
      val readBufferLength = 64 * 1024
      val buffer = allocArray(readBufferLength)
      var line = fgets(buffer, readBufferLength, file)?.toKString()
      while (line != null) {
        returnBuffer.append(line)
        line = fgets(buffer, readBufferLength, file)?.toKString()
      }
    }
  } finally {
    fclose(file)
  }

  return returnBuffer.toString()
}

/**
 * Deletes this file and any subdirectories recursively
 */
public actual fun File.deleteRecursively(): Boolean {
  return if (isDirectory()) {
    nftw(
      getPath(),
      staticCFunction { fpath, _, _, _ ->
        val spath = fpath?.toKString()
        val isDir = memScoped {
          val stat = alloc()
          if (stat(spath, stat.ptr) != 0) {
            false
          } else {
            S_IFDIR == (stat.st_mode and S_IFMT.convert()).convert()
          }
        }
        if (isDir) {
          rmdir(spath).also {
            if (it != 0) {
              perror("Directory removal error[$it]: $spath")
            }
          }
        } else {
          remove(spath).also {
            if (it != 0) {
              perror("File removal error[$it]: $spath")
            }
          }
        }
      },
      64, FTW_DEPTH
    )
  } else {
    remove(getPath())
  } == 0
}

internal expect fun mppMkdir(path: String, permissions: Int): Int

/**
 * Checks if file path is starting from root (thanks a bunch, windows...)
 */
internal expect val File.isRooted: Boolean




© 2015 - 2025 Weber Informatics LLC | Privacy Policy