preview of files realization

This commit is contained in:
InsanusMokrassar 2023-04-03 15:49:46 +06:00
parent e5207f5bc5
commit d23e005985
6 changed files with 371 additions and 7 deletions

View File

@ -0,0 +1,146 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.alloc
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.readBytes
import platform.linux.statvfs
import platform.posix.ACCESSPERMS
import platform.posix.FILE
import platform.posix.F_OK
import platform.posix.SEEK_END
import platform.posix.S_IFDIR
import platform.posix.S_IFMT
import platform.posix.access
import platform.posix.fgets
import platform.posix.fopen
import platform.posix.fseek
import platform.posix.ftell
import platform.posix.remove
import platform.posix.rename
import platform.posix.rmdir
import platform.posix.stat
actual data class MPPFile(internal val filename: FileName) {
val path = filename.string
val isFile: Boolean
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped false
(S_IFDIR != (stat.st_mode and S_IFMT.convert()).convert<Int>())
}
val isDirectory: Boolean
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped false
S_IFDIR == (stat.st_mode and S_IFMT.convert()).convert<Int>()
}
val size: Long
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped 0
return stat.st_size.convert()
}
val lastModified: Long
get() = memScoped {
val stat = alloc<stat>()
if (stat(path, stat.ptr) != 0)
return@memScoped 0
return stat.st_ctim.tv_nsec / 1000L
}
val freeSpace: Long
get() =
memScoped {
val stat = alloc<statvfs>()
statvfs(path, stat.ptr)
(stat.f_bfree.toULong() * stat.f_bsize.toULong()).toLong()
}
val availableSpace: Long
get() =
memScoped {
val stat = alloc<statvfs>()
statvfs(path, stat.ptr)
(stat.f_bavail.toULong() * stat.f_bsize.toULong()).toLong()
}
val totalSpace: Long
get() = memScoped {
val stat = alloc<statvfs>()
statvfs(path, stat.ptr)
(stat.f_blocks.toULong() * stat.f_frsize.toULong()).toLong()
}
constructor(vararg path: String) : this(FileName(path.map { it.removeSuffix(SEPARATOR_STRING) }.joinToString(SEPARATOR_STRING)))
constructor(parent: MPPFile, subpath: String) : this("${parent.filename.withoutSlashAtTheEnd}$SEPARATOR${subpath}")
fun createPointer(mode: String = "r") = fopen(filename.name, mode)
fun delete(): Boolean {
return when {
isDirectory -> rmdir(path) == 0
isFile -> remove(path) == 0
else -> false
}
}
fun mkdir(): Boolean = platform.posix.mkdir(path, ACCESSPERMS) == 0
override fun toString(): String = path
override fun equals(other: Any?): Boolean {
if (other !is MPPFile) return false
return path == other.path
}
override fun hashCode(): Int = 31 + path.hashCode()
fun renameTo(newPath: MPPFile): Boolean = rename(path, newPath.path) == 0
fun list(): List<MPPFile> {
val out = ArrayList<MPPFile>()
FileIterator(this).forEach { file ->
out += file
}
return out
}
fun fileChannel(mode: String = "r") = FileChannel(this, mode)
companion object {
val SEPARATOR: Char = '/'
val SEPARATOR_STRING: String = SEPARATOR.toString()
val temporalDirectory: MPPFile?
get() = MPPFile("/tmp").takeIf {
it.isDirectory
}
}
}
actual val MPPFile.filename: FileName
get() = this.filename
actual val MPPFile.filesize: Long
get() = memScoped {
val pointer = createPointer()
fseek(pointer, 0L, SEEK_END)
ftell(pointer)
}
actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator
get() = {
memScoped {
fileChannel().readFully()
}
}
actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator
get() = {
bytesAllocatorSync()
}

View File

@ -0,0 +1,107 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toCValues
import kotlinx.cinterop.toKString
import platform.posix.SEEK_END
import platform.posix.SEEK_SET
import platform.posix.fclose
import platform.posix.feof
import platform.posix.fread
import platform.posix.fseek
import platform.posix.ftell
import platform.posix.fwrite
import platform.posix.size_t
class FileChannel (file: MPPFile, mode: String) : RandomAccess {
internal val handler = file.createPointer(mode)
private var position: ULong
get() = ftell(handler).convert()
set(value) {
fseek(handler, value.convert(), SEEK_SET)
}
private val size: ULong
get() {
val pos = position
gotoEnd()
val result = position
position = pos
return result
}
fun skip(length: Long): Long {
checkClosed()
memScoped { }
if (length == 0L)
return 0L
if (feof(handler) != 0)
return 0L
val endOfFile = size
val position = minOf(endOfFile, this.position + length.toULong())
this.position = position
return (endOfFile - position).toLong()
}
fun read(dest: ByteArray): Int {
checkClosed()
if (feof(handler) != 0)
return 0
memScoped {
val tmp = allocArray<ByteVar>(dest.size);
fread(tmp, Byte.SIZE_BYTES.convert<size_t>(), dest.size.convert(), handler).convert<Int>()
tmp.toKString()
}
return fread(dest.toCValues(), Byte.SIZE_BYTES.convert<size_t>(), dest.size.convert(), handler).convert<Int>()
}
private var closed = false
private fun checkClosed() {
if (closed) {
error("File channel has been closed already")
}
}
fun close() {
checkClosed()
fclose(handler)
closed = true
}
fun write(data: ByteArray): Int {
checkClosed()
if (feof(handler) != 0)
return 0
return fwrite(data.toCValues(), 1.convert<size_t>(), data.size.convert<size_t>(), handler).convert<Int>()
}
fun flush() {
checkClosed()
}
private fun gotoEnd() {
fseek(handler, 0, SEEK_END)
}
fun readFully(): ByteArray {
var result = ByteArray(0)
memScoped {
val tmp = ByteArray(64 * 1024)
do {
val read = read(tmp)
result += tmp.take(read)
} while (read > 0)
}
val tmp = ByteArray(64 * 1024)
return result
}
}

View File

@ -0,0 +1,56 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.pointed
import kotlinx.cinterop.toKString
import platform.posix.closedir
import platform.posix.dirent
import platform.posix.opendir
import platform.posix.readdir
import kotlin.native.internal.createCleaner
class FileIterator internal constructor(private val file: MPPFile) : Iterator<MPPFile> {
init {
if (!file.isDirectory)
error("\"${file.path}\" is not direction")
}
private val handler = opendir(file.path)
private var next: dirent? = null
private var end = false
override fun hasNext(): Boolean {
while (true) {
if (end)
return false
if (next == null) {
next = readdir(handler)?.pointed
if (next == null) {
end = true
return false
}
val name = next!!.d_name.toKString()
if (name == "." || name == "..") {
next = null
continue
}
return true
}
return true
}
}
override fun next(): MPPFile {
if (!hasNext())
throw NoSuchElementException()
val result = MPPFile(file, next!!.d_name.toKString())
next = null
return result
}
@OptIn(ExperimentalStdlibApi::class)
private val cleaner = createCleaner(handler) {
closedir(it)
}
}

View File

@ -0,0 +1,26 @@
package dev.inmo.micro_utils.common
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.allocArray
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.toKString
import platform.posix.snprintf
import platform.posix.sprintf
actual fun Float.fixed(signs: Int): Float {
return memScoped {
val buff = allocArray<ByteVar>(Float.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toFloat()
}
}
actual fun Double.fixed(signs: Int): Double {
return memScoped {
val buff = allocArray<ByteVar>(Double.SIZE_BYTES * 2)
sprintf(buff, "%.${signs}f", this@fixed)
buff.toKString().toDouble()
}
}

View File

@ -0,0 +1,36 @@
package dev.inmo.micro_utils.common
import dev.inmo.micro_utils.common.MPPFile
import kotlin.test.Test
import kotlin.test.assertEquals
fun createTestFile(content: String): MPPFile = MPPFile(
MPPFile.temporalDirectory!!,
"tmp.file.txt"
).apply {
val channel = fileChannel("wc")
runCatching {
channel.write(content.encodeToByteArray())
}
channel.close()
}
fun MPPFile.removeTestFile() {
delete()
}
class FileTests {
@Test
fun testReadFromFile() {
val testContent = "Test"
val file = createTestFile(testContent)
try {
val content = runCatching {
file.fileChannel().readFully().contentToString()
}.getOrThrow()
assertEquals(testContent, content)
} finally {
file.removeTestFile()
}
}
}

View File

@ -18,7 +18,6 @@ kotlin {
android {
publishAllLibraryVariants()
}
linuxArm64()
linuxX64()
mingwX64()
@ -53,12 +52,6 @@ kotlin {
implementation libs.android.espresso
}
}
linuxArm64Test {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
mingwX64Test {
dependencies {
implementation kotlin('test-js')