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

com.fireflysource.common.io.Nio.kt Maven / Gradle / Ivy

The newest version!
@file:Suppress("BlockingMethodInNonBlockingContext", "KDocUnresolvedReference")

package com.fireflysource.common.io

import com.fireflysource.common.coroutine.CoroutineDispatchers.ioBlockingThreadPool
import com.fireflysource.common.coroutine.blocking
import com.fireflysource.common.coroutine.blockingAsync
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.future.await
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.io.Closeable
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.nio.channels.*
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.LinkOption
import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.TimeUnit

/**
 * Performs [AsynchronousFileChannel.lock] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousFileChannel.lockAwait() = suspendCancellableCoroutine { cont ->
    lock(cont, asyncIOHandler())
    closeOnCancel(cont)
}

/**
 * Performs [AsynchronousFileChannel.lock] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousFileChannel.lockAwait(
    position: Long,
    size: Long,
    shared: Boolean
) = suspendCancellableCoroutine { cont ->
    lock(position, size, shared, cont, asyncIOHandler())
    closeOnCancel(cont)
}

suspend fun  T.useAwait(block: suspend (T) -> R): R {
    try {
        return block(this)
    } catch (e: Throwable) {
        throw e
    } finally {
        withContext(NonCancellable) {
            this@useAwait?.closeAsync()?.join()
        }
    }
}

suspend fun  T.useAwait(block: suspend (T) -> R): R {
    try {
        return block(this)
    } catch (e: Throwable) {
        throw e
    } finally {
        withContext(NonCancellable) {
            this@useAwait?.closeAsync()?.await()
        }
    }
}

/**
 * Close in the I/O blocking coroutine dispatcher
 */
fun Closeable.closeAsync() = blocking {
    close()
}

fun openFileChannelAsync(file: Path, vararg options: OpenOption) = blockingAsync {
    AsynchronousFileChannel.open(file, setOf(*options), ioBlockingThreadPool)
}

fun openFileChannelAsync(file: Path, options: Set) = blockingAsync {
    AsynchronousFileChannel.open(file, options, ioBlockingThreadPool)
}

fun listFilesAsync(dir: Path) = blockingAsync {
    Files.list(dir)
}

fun readFileLinesAsync(file: Path, charset: Charset = StandardCharsets.UTF_8) = blockingAsync {
    Files.readAllLines(file, charset)
}

fun readFileBytesAsync(file: Path) = blockingAsync {
    Files.readAllBytes(file)
}

fun deleteIfExistsAsync(file: Path) = blockingAsync {
    Files.deleteIfExists(file)
}

fun existsAsync(file: Path, vararg options: LinkOption) = blockingAsync {
    Files.exists(file, *options)
}

fun readAttributesAsync(file: Path, vararg options: LinkOption) = blockingAsync {
    Files.readAttributes(file, BasicFileAttributes::class.java, *options)
}

fun writeFileLinesAsync(file: Path, iterable: Iterable, vararg options: OpenOption) = blockingAsync {
    Files.write(file, iterable, *options)
}

fun writeFileBytesAsync(file: Path, byteArray: ByteArray, vararg options: OpenOption) = blockingAsync {
    Files.write(file, byteArray, *options)
}



/**
 * Performs [AsynchronousFileChannel.read] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousFileChannel.readAwait(
    buf: ByteBuffer,
    position: Long
) = suspendCancellableCoroutine { cont ->
    read(buf, position, cont, asyncIOHandler())
    closeOnCancel(cont)
}

/**
 * Performs [AsynchronousFileChannel.write] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousFileChannel.writeAwait(
    buf: ByteBuffer,
    position: Long
) = suspendCancellableCoroutine { cont ->
    write(buf, position, cont, asyncIOHandler())
    closeOnCancel(cont)
}

/**
 * Performs [AsynchronousServerSocketChannel.accept] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousServerSocketChannel.acceptAwait() =
    suspendCancellableCoroutine { cont ->
        accept(cont, asyncIOHandler())
        closeOnCancel(cont)
    }

/**
 * Performs [AsynchronousSocketChannel.connect] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousSocketChannel.connectAwait(
    socketAddress: SocketAddress
) = suspendCancellableCoroutine { cont ->
    connect(socketAddress, cont, AsyncVoidIOHandler)
    closeOnCancel(cont)
}

/**
 * Performs [AsynchronousSocketChannel.read] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousSocketChannel.readAwait(
    buf: ByteBuffer,
    timeout: Long = 0L,
    timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
    read(buf, timeout, timeUnit, cont, asyncIOHandler())
    closeOnCancel(cont)
}

suspend fun AsynchronousSocketChannel.readAwait(
    buffers: Array,
    offset: Int,
    length: Int,
    timeout: Long = 0L,
    timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
    read(buffers, offset, length, timeout, timeUnit, cont, asyncIOHandler())
    closeOnCancel(cont)
}

/**
 * Performs [AsynchronousSocketChannel.write] without blocking a thread and resumes when asynchronous operation completes.
 * This suspending function is cancellable.
 * If the [Job] of the current coroutine cancelled or completed while this suspending function is waiting, this function
 * *closes the underlying channel* and immediately resumes with [CancellationException].
 */
suspend fun AsynchronousSocketChannel.writeAwait(
    buf: ByteBuffer,
    timeout: Long = 0L,
    timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
    write(buf, timeout, timeUnit, cont, asyncIOHandler())
    closeOnCancel(cont)
}

suspend fun AsynchronousSocketChannel.writeAwait(
    buffers: Array,
    offset: Int,
    length: Int,
    timeout: Long = 0L,
    timeUnit: TimeUnit = TimeUnit.MILLISECONDS
) = suspendCancellableCoroutine { cont ->
    write(buffers, offset, length, timeout, timeUnit, cont, asyncIOHandler())
    closeOnCancel(cont)
}

// ---------------- private details ----------------

private fun Channel.closeOnCancel(cont: CancellableContinuation<*>) {
    cont.invokeOnCancellation {
        try {
            close()
        } catch (ex: Throwable) {
            // Specification says that it is Ok to call it any time, but reality is different,
            // so we have just to ignore exception
        }
    }
}

@Suppress("UNCHECKED_CAST")
private fun  asyncIOHandler(): CompletionHandler> =
    AsyncIOHandlerAny as CompletionHandler>

private object AsyncIOHandlerAny : CompletionHandler> {
    override fun completed(result: Any, cont: CancellableContinuation) {
        cont.resumeWith(Result.success(result))
    }

    override fun failed(ex: Throwable, cont: CancellableContinuation) {
        // just return if already cancelled and got an expected exception for that case
        if (ex is AsynchronousCloseException && cont.isCancelled) {
            return
        }
        cont.resumeWith(Result.failure(ex))
    }
}

private object AsyncVoidIOHandler : CompletionHandler> {
    override fun completed(result: Void?, cont: CancellableContinuation) {
        cont.resumeWith(Result.success(Unit))
    }

    override fun failed(ex: Throwable, cont: CancellableContinuation) {
        // just return if already cancelled and got an expected exception for that case
        if (ex is AsynchronousCloseException && cont.isCancelled) {
            return
        }
        cont.resumeWith(Result.failure(ex))
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy