├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle.properties ├── local.properties ├── settings.gradle ├── src ├── es │ └── kotlin │ │ ├── async │ │ ├── EventLoop.kt │ │ ├── Promise.kt │ │ ├── Signal.kt │ │ ├── coroutine │ │ │ ├── asynGenerate.kt │ │ │ ├── async.kt │ │ │ ├── asyncProducer.kt │ │ │ ├── cancelHandler.kt │ │ │ └── invokeSuspend.kt │ │ └── utils │ │ │ ├── downloadUrlAsync.kt │ │ │ ├── executeInWorkerAsync.kt │ │ │ └── waitAsync.kt │ │ ├── collection │ │ ├── coroutine │ │ │ └── generate.kt │ │ └── lazy │ │ │ └── lazy.kt │ │ ├── crypto │ │ └── Hash.kt │ │ ├── db │ │ └── async │ │ │ ├── Db.kt │ │ │ ├── mysql │ │ │ ├── MysqlClient.kt │ │ │ ├── MysqlEnums.kt │ │ │ ├── MysqlPacket.kt │ │ │ └── mysql.kt │ │ │ └── redis │ │ │ ├── RedisClient.kt │ │ │ └── RedisVfs.kt │ │ ├── di │ │ └── AsyncInjector.kt │ │ ├── lang │ │ ├── ClassFactory.kt │ │ ├── DynamicConvert.kt │ │ ├── Once.kt │ │ └── StringUtils.kt │ │ ├── net │ │ └── async │ │ │ ├── AsyncClient.kt │ │ │ └── AsyncServer.kt │ │ ├── random │ │ └── RandomExt.kt │ │ ├── time │ │ └── TimeSpan.kt │ │ ├── vertx │ │ ├── VertxExt.kt │ │ ├── VertxKtPromise.kt │ │ ├── redis │ │ │ └── RedisExt.kt │ │ └── route │ │ │ └── RouterMap.kt │ │ └── vfs │ │ └── async │ │ ├── JailVfs.kt │ │ ├── LocalVfs.kt │ │ ├── Vfs.kt │ │ ├── VfsFile.kt │ │ └── VfsStat.kt ├── example-async-generator.kt ├── example-await-async.kt ├── example-chat-server.kt ├── example-echo-server.kt ├── example-generator.kt ├── example-redis.kt ├── example-tic-tac-toe-async-with-implicit-state-machine.kt ├── example-vertx-router.kt └── example-vfs.kt └── test ├── TicTacToeTest.kt └── es └── kotlin └── collection └── coroutine └── generateTest.kt /.gitignore: -------------------------------------------------------------------------------- 1 | /out/ 2 | *.iws 3 | *.iml 4 | *.ipr 5 | .gradle 6 | .vertx 7 | .idea 8 | /build -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | `asyncGenerate` and `generate` methods are from https://github.com/Kotlin/kotlin-coroutines and follow its license. 2 | `fun vx` method suggested at kotlinlang slack #coroutines by Roman Elizarov from JetBrains 3 | 4 | For the rest here, do whatever you want without having to credit anything or anyone. 5 | But I'm not going to take ANY kind responsibilities if this code or derivatives 6 | including but not exclusively: breaks your computer, an arm, your dog or your house :) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coroutine-examples 2 | Examples of co-routines using Kotlin 1.1 3 | 4 | VFS: 5 | 6 | For a more complete asynchronous kotlin coroutine vfs implementation: 7 | https://github.com/soywiz/coktvfs 8 | 9 | LICENSE: 10 | 11 | `asyncGenerate` and `generate` methods are from https://github.com/Kotlin/kotlin-coroutines and follow its license. 12 | `fun vx` method suggested at kotlinlang slack #coroutines by Roman Elizarov from JetBrains 13 | 14 | For the rest here, do whatever you want without having to credit anything or anyone. 15 | But I'm not going to take ANY kind responsibilities if this code or derivatives 16 | including but not exclusively: breaks your computer, an arm, your dog or your house :) -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'es.kotlin' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | mavenLocal() 7 | maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } 8 | mavenCentral() 9 | } 10 | dependencies { 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 12 | } 13 | } 14 | 15 | apply plugin: 'application' 16 | apply plugin: 'kotlin' 17 | 18 | mainClassName = "VertxExample" 19 | 20 | compileJava.options.encoding = 'UTF-8' 21 | compileTestJava.options.encoding = 'UTF-8' 22 | 23 | sourceCompatibility = 1.5 24 | 25 | sourceSets { 26 | main.java.srcDirs = ['src'] 27 | test.java.srcDirs = ['test'] 28 | main.resources.srcDirs = ['resources'] 29 | } 30 | 31 | repositories { 32 | mavenLocal() 33 | maven { url "http://dl.bintray.com/kotlin/kotlin-eap-1.1" } 34 | mavenCentral() 35 | } 36 | 37 | dependencies { 38 | compile "io.vertx:vertx-core:$vertxVersion" 39 | compile "io.vertx:vertx-web:$vertxVersion" 40 | compile "io.vertx:vertx-redis-client:$vertxVersion" 41 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" 42 | compile "com.google.code.gson:gson:2.2.4" 43 | 44 | testCompile "io.vertx:vertx-unit:3.3.3" 45 | testCompile "junit:junit:4.12" 46 | } 47 | 48 | task fatJar(type: Jar) { 49 | manifest { 50 | attributes 'Implementation-Title': 'Gradle Jar File Example', 51 | 'Implementation-Version': version, 52 | 'Main-Class': project.mainClassName 53 | } 54 | baseName = project.name + '-all' 55 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } 56 | with jar 57 | } 58 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | vertxVersion=3.3.3 2 | kotlinVersion=1.1-M04 3 | kotlin.coroutines=enable -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | kotlin.coroutines=enable -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'coroutine-examples' 2 | 3 | -------------------------------------------------------------------------------- /src/es/kotlin/async/EventLoop.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async 2 | 3 | import es.kotlin.async.coroutine.async 4 | import java.io.Closeable 5 | import java.util.concurrent.ConcurrentLinkedDeque 6 | 7 | open class EventLoop { 8 | data class TimerHandler(val time: Long, val handler: () -> Unit) 9 | 10 | companion object { 11 | var impl = EventLoop() 12 | 13 | fun mainAsync(routine: suspend () -> Unit): Unit = impl.mainAsync(routine) 14 | fun main(entry: (() -> Unit)? = null) = impl.main(entry) 15 | 16 | fun queue(handler: () -> Unit) = impl.queue(handler) 17 | fun step() = impl.step() 18 | fun setImmediate(handler: () -> Unit) = impl.queue(handler) 19 | fun setTimeout(ms: Int, callback: () -> Unit): Closeable = impl.setTimeout(ms, callback) 20 | fun setInterval(ms: Int, callback: () -> Unit): Closeable = impl.setInterval(ms, callback) 21 | } 22 | 23 | val handlers = ConcurrentLinkedDeque<() -> Unit>() 24 | var timerHandlers = ConcurrentLinkedDeque() 25 | var timerHandlersBack = ConcurrentLinkedDeque() 26 | 27 | fun mainAsync(routine: suspend () -> Unit): Unit { 28 | main { 29 | async(routine) 30 | } 31 | } 32 | 33 | fun main(entry: (() -> Unit)? = null) { 34 | entry?.invoke() 35 | 36 | while (handlers.isNotEmpty() || timerHandlers.isNotEmpty() || Thread.activeCount() > 1) { 37 | step() 38 | Thread.sleep(1L) 39 | } 40 | } 41 | 42 | open fun step() { 43 | while (handlers.isNotEmpty()) { 44 | val handler = handlers.removeFirst() 45 | handler?.invoke() 46 | } 47 | val now = System.currentTimeMillis() 48 | while (timerHandlers.isNotEmpty()) { 49 | val handler = timerHandlers.removeFirst() 50 | if (now >= handler.time) { 51 | handler.handler() 52 | } else { 53 | timerHandlersBack.add(handler) 54 | } 55 | } 56 | val temp = timerHandlersBack 57 | timerHandlersBack = timerHandlers 58 | timerHandlers = temp 59 | } 60 | 61 | open fun queue(handler: () -> Unit) { 62 | handlers += handler 63 | } 64 | 65 | fun setImmediate(handler: () -> Unit) = queue(handler) 66 | 67 | open fun setTimeout(ms: Int, callback: () -> Unit): Closeable { 68 | val handler = TimerHandler(System.currentTimeMillis() + ms, callback) 69 | timerHandlers.add(handler) 70 | return object : Closeable { 71 | override fun close(): Unit = run { timerHandlers.remove(handler) } 72 | } 73 | } 74 | 75 | open fun setInterval(ms: Int, callback: () -> Unit): Closeable { 76 | var ccallback: (() -> Unit)? = null 77 | var disposable: Closeable? = null 78 | 79 | ccallback = { 80 | callback() 81 | disposable = setTimeout(ms, ccallback!!) 82 | } 83 | 84 | //ccallback() 85 | disposable = setTimeout(ms, ccallback!!) 86 | 87 | return object : Closeable { 88 | override fun close(): Unit = run { disposable?.close() } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/es/kotlin/async/Promise.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async 2 | 3 | import java.util.* 4 | import kotlin.coroutines.Continuation 5 | 6 | typealias ResolvedHandler = (T) -> Unit 7 | typealias RejectedHandler = (Throwable) -> Unit 8 | 9 | class Promise { 10 | class Deferred { 11 | val promise = Promise() 12 | 13 | fun resolve(value: T): Unit = run { promise.complete(value, null) } 14 | fun reject(error: Throwable): Unit = run { promise.complete(null, error) } 15 | 16 | fun onCancel(handler: (Throwable) -> Unit) { 17 | promise.cancelHandlers += handler 18 | } 19 | } 20 | 21 | companion object { 22 | fun resolved(value: T) = Promise().complete(value, null) 23 | fun rejected(error: Throwable) = Promise().complete(null, error) 24 | } 25 | 26 | private var value: T? = null 27 | private var error: Throwable? = null 28 | private var done: Boolean = false 29 | private val resolvedHandlers = LinkedList>() 30 | private val rejectedHandlers = LinkedList() 31 | private val cancelHandlers = LinkedList() 32 | 33 | private fun flush() { 34 | if (!done) return 35 | if (error != null) { 36 | while (rejectedHandlers.isNotEmpty()) { 37 | val handler = rejectedHandlers.removeFirst() 38 | EventLoop.setImmediate { handler(error ?: RuntimeException()) } 39 | } 40 | } else { 41 | while (resolvedHandlers.isNotEmpty()) { 42 | val handler = resolvedHandlers.removeFirst() 43 | EventLoop.setImmediate { handler(value!!) } 44 | } 45 | } 46 | } 47 | 48 | internal fun complete(value: T?, error: Throwable?): Promise { 49 | if (!this.done) { 50 | if (value == null && error == null) { 51 | throw RuntimeException("Invalid completion!") 52 | } 53 | 54 | this.value = value 55 | this.error = error 56 | this.done = true 57 | 58 | if (error != null && this.rejectedHandlers.isEmpty()) { 59 | error.printStackTrace() 60 | } 61 | 62 | flush() 63 | } 64 | return this 65 | } 66 | 67 | fun then(resolved: ResolvedHandler) { 68 | resolvedHandlers += resolved 69 | flush() 70 | } 71 | 72 | fun then(resolved: ResolvedHandler, rejected: RejectedHandler) { 73 | resolvedHandlers += resolved 74 | rejectedHandlers += rejected 75 | flush() 76 | } 77 | 78 | fun then(c: Continuation) { 79 | this.then( 80 | resolved = { c.resume(it) }, 81 | rejected = { c.resumeWithException(it) } 82 | ) 83 | } 84 | 85 | fun cancel(reason: Throwable) { 86 | for (handler in cancelHandlers) handler(reason) 87 | complete(null, reason) 88 | } 89 | } 90 | 91 | fun Promise.Deferred.toContinuation(): Continuation = object : Continuation { 92 | override fun resume(value: T) = this@toContinuation.resolve(value) 93 | override fun resumeWithException(exception: Throwable) = this@toContinuation.reject(exception) 94 | } 95 | -------------------------------------------------------------------------------- /src/es/kotlin/async/Signal.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async 2 | 3 | class Signal { 4 | internal val handlers = arrayListOf<(T) -> Unit>() 5 | 6 | fun add(handler: (T) -> Unit) { 7 | handlers += handler 8 | } 9 | 10 | operator fun invoke(value: T) { 11 | for (handler in handlers) handler.invoke(value) 12 | } 13 | 14 | operator fun invoke(value: (T) -> Unit) = add(value) 15 | } 16 | 17 | operator fun Signal.invoke() = invoke(Unit) 18 | -------------------------------------------------------------------------------- /src/es/kotlin/async/coroutine/asynGenerate.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.coroutine 2 | 3 | import kotlin.coroutines.Continuation 4 | import kotlin.coroutines.createCoroutine 5 | import kotlin.coroutines.suspendCoroutine 6 | 7 | // Copy from here: https://github.com/Kotlin/kotlin-coroutines/blob/master/examples/asyncGenerate.kt 8 | // Added extensions methods from: https://github.com/Kotlin/kotlinx.coroutines/issues/18 9 | 10 | interface AsyncGenerator { 11 | suspend fun yield(value: T) 12 | } 13 | 14 | interface AsyncSequence { 15 | operator fun iterator(): AsyncIterator 16 | } 17 | 18 | interface AsyncIterator { 19 | suspend operator fun hasNext(): Boolean 20 | suspend operator fun next(): T 21 | } 22 | 23 | fun asyncGenerate(block: suspend AsyncGenerator.() -> Unit): AsyncSequence = object : AsyncSequence { 24 | override fun iterator(): AsyncIterator { 25 | val iterator = AsyncGeneratorIterator() 26 | iterator.nextStep = block.createCoroutine(receiver = iterator, completion = iterator) 27 | return iterator 28 | } 29 | } 30 | 31 | class AsyncGeneratorIterator: AsyncIterator, AsyncGenerator, Continuation { 32 | enum class State { INITIAL, COMPUTING_HAS_NEXT, COMPUTING_NEXT, COMPUTED, DONE } 33 | var state: State = State.INITIAL 34 | var nextValue: T? = null 35 | var nextStep: Continuation? = null // null when sequence complete 36 | 37 | // if (state == COMPUTING_NEXT) computeContinuation is Continuation 38 | // if (state == COMPUTING_HAS_NEXT) computeContinuation is Continuation 39 | var computeContinuation: Continuation<*>? = null 40 | 41 | suspend fun computeHasNext(): Boolean = suspendCoroutine { c -> 42 | state = State.COMPUTING_HAS_NEXT 43 | computeContinuation = c 44 | nextStep!!.resume(Unit) 45 | } 46 | 47 | suspend fun computeNext(): T = suspendCoroutine { c -> 48 | state = State.COMPUTING_NEXT 49 | computeContinuation = c 50 | nextStep!!.resume(Unit) 51 | } 52 | 53 | override suspend fun hasNext(): Boolean { 54 | when (state) { 55 | State.INITIAL -> return computeHasNext() 56 | State.COMPUTED -> return true 57 | State.DONE -> return false 58 | else -> throw IllegalStateException("Recursive dependency detected -- already computing next") 59 | } 60 | } 61 | 62 | override suspend fun next(): T { 63 | when (state) { 64 | State.INITIAL -> return computeNext() 65 | State.COMPUTED -> { 66 | state = State.INITIAL 67 | return nextValue as T 68 | } 69 | State.DONE -> throw NoSuchElementException() 70 | else -> { 71 | throw IllegalStateException("Recursive dependency detected -- already computing next") 72 | } 73 | } 74 | } 75 | 76 | @Suppress("UNCHECKED_CAST") 77 | fun resumeIterator(hasNext: Boolean) { 78 | when (state) { 79 | State.COMPUTING_HAS_NEXT -> { 80 | state = State.COMPUTED 81 | (computeContinuation as Continuation).resume(hasNext) 82 | } 83 | State.COMPUTING_NEXT -> { 84 | state = State.INITIAL 85 | (computeContinuation as Continuation).resume(nextValue as T) 86 | } 87 | else -> throw IllegalStateException("Was not supposed to be computing next value. Spurious yield?") 88 | } 89 | } 90 | 91 | // Completion continuation implementation 92 | override fun resume(value: Unit) { 93 | nextStep = null 94 | resumeIterator(false) 95 | } 96 | 97 | override fun resumeWithException(exception: Throwable) { 98 | nextStep = null 99 | state = State.DONE 100 | computeContinuation!!.resumeWithException(exception) 101 | } 102 | 103 | // Generator implementation 104 | override suspend fun yield(value: T): Unit = suspendCoroutine { c -> 105 | nextValue = value 106 | nextStep = c 107 | resumeIterator(true) 108 | } 109 | } 110 | 111 | /////////////////////////////////////////////////////////////////////////////////////////////////////// 112 | /////////////////////////////////////////////////////////////////////////////////////////////////////// 113 | /////////////////////////////////////////////////////////////////////////////////////////////////////// 114 | /////////////////////////////////////////////////////////////////////////////////////////////////////// 115 | 116 | inline suspend fun AsyncSequence.map(crossinline transform: (T) -> T2) = asyncGenerate { 117 | for (e in this@map) { 118 | yield(transform(e)) 119 | } 120 | } 121 | 122 | inline suspend fun AsyncSequence.filter(crossinline filter: (T) -> Boolean) = asyncGenerate { 123 | for (e in this@filter) { 124 | if (filter(e)) yield(e) 125 | } 126 | } 127 | 128 | suspend fun AsyncSequence.chunks(count: Int) = asyncGenerate> { 129 | val chunk = arrayListOf() 130 | 131 | for (e in this@chunks) { 132 | chunk += e 133 | if (chunk.size > count) { 134 | yield(chunk.toList()) 135 | chunk.clear() 136 | } 137 | } 138 | 139 | if (chunk.size > 0) { 140 | yield(chunk.toList()) 141 | } 142 | } 143 | 144 | suspend fun AsyncSequence.toList(): List = asyncFun { 145 | val out = arrayListOf() 146 | for (e in this@toList) out += e 147 | out 148 | } 149 | 150 | inline suspend fun AsyncSequence.fold(initial: TR, crossinline folder: (T, TR) -> TR): TR = asyncFun { 151 | var result: TR = initial 152 | for (e in this) result = folder(e, result) 153 | result 154 | } 155 | 156 | suspend fun AsyncSequence.sum(): Int = this.fold(0) { a, b -> a + b } 157 | -------------------------------------------------------------------------------- /src/es/kotlin/async/coroutine/async.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.coroutine 2 | 3 | import es.kotlin.async.EventLoop 4 | import es.kotlin.async.Promise 5 | import es.kotlin.async.Signal 6 | import es.kotlin.async.invoke 7 | import es.kotlin.lang.Once 8 | import es.kotlin.time.TimeSpan 9 | import java.util.concurrent.CancellationException 10 | import java.util.concurrent.TimeoutException 11 | import kotlin.coroutines.Continuation 12 | import kotlin.coroutines.startCoroutine 13 | import kotlin.coroutines.suspendCoroutine 14 | 15 | inline suspend fun awaitAsync(routine: suspend () -> T) = asyncFun(routine) 16 | 17 | // No need for promises here at all! 18 | inline suspend fun asyncFun(routine: suspend () -> T): T = suspendCoroutine { routine.startCoroutine(it) } 19 | 20 | inline suspend fun process(routine: suspend () -> T): T = suspendCoroutine { routine.startCoroutine(it) } 21 | 22 | fun async(routine: suspend () -> T): Promise { 23 | val deferred = Promise.Deferred() 24 | routine.startCoroutine(completion = object : Continuation { 25 | override fun resume(value: T) = Unit.apply { deferred.resolve(value) } 26 | override fun resumeWithException(exception: Throwable) = Unit.apply { deferred.reject(exception) } 27 | }) 28 | return deferred.promise 29 | } 30 | 31 | suspend fun await(p: Promise) = suspendCoroutine(p::then) 32 | 33 | suspend fun awaitAsyncTask(callback: () -> T): T = asyncFun { awaitTask { callback() } } 34 | 35 | suspend fun awaitTask(callback: () -> T): T = suspendCoroutine { c -> 36 | Thread { 37 | try { 38 | val result = callback() 39 | EventLoop.queue { c.resume(result) } 40 | } catch (t: Throwable) { 41 | EventLoop.queue { c.resumeWithException(t) } 42 | } 43 | }.start() 44 | } 45 | 46 | suspend fun withTimeout(timeout: TimeSpan, callback: suspend CancelHandler.() -> T) = suspendCoroutine { c -> 47 | val once = Once() 48 | 49 | val cancelHandler = CancelHandler() 50 | 51 | val closeable = EventLoop.setTimeout(timeout.milliseconds.toInt()) { 52 | once { 53 | cancelHandler() 54 | c.resumeWithException(TimeoutException()) 55 | } 56 | } 57 | 58 | callback.startCoroutine(cancelHandler, object : Continuation { 59 | override fun resume(value: T) { 60 | once { 61 | closeable.close() 62 | EventLoop.setImmediate { 63 | c.resume(value) 64 | } 65 | } 66 | } 67 | 68 | override fun resumeWithException(exception: Throwable) { 69 | once { 70 | closeable.close() 71 | EventLoop.setImmediate { 72 | c.resumeWithException(exception) 73 | } 74 | } 75 | } 76 | }) 77 | } 78 | 79 | fun sync(routine: suspend () -> T): T = async(routine).syncWait() 80 | 81 | fun Promise.syncWait(): T { 82 | var completed = false 83 | var result: T? = null 84 | var exception: Throwable? = null 85 | 86 | this.then(resolved = { 87 | result = it 88 | completed = true 89 | }, rejected = { 90 | exception = it 91 | completed = true 92 | }) 93 | while (!completed) { 94 | EventLoop.step() 95 | Thread.sleep(1L) 96 | } 97 | if (exception != null) throw exception!! 98 | return result!! 99 | } 100 | -------------------------------------------------------------------------------- /src/es/kotlin/async/coroutine/asyncProducer.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.coroutine 2 | 3 | import java.util.* 4 | import java.util.concurrent.CancellationException 5 | import kotlin.coroutines.Continuation 6 | import kotlin.coroutines.startCoroutine 7 | import kotlin.coroutines.suspendCoroutine 8 | 9 | interface Consumer { 10 | suspend fun consume(): T 11 | suspend fun consumeWithCancelHandler(cancel: CancelHandler): T 12 | } 13 | 14 | interface Producer { 15 | fun produce(v: T): Unit 16 | } 17 | 18 | class ProduceConsumer : Consumer, Producer { 19 | val items = LinkedList() 20 | val consumers = LinkedList<(T) -> Unit>() 21 | 22 | override fun produce(v: T) { 23 | items.addLast(v) 24 | flush() 25 | } 26 | 27 | private fun flush() { 28 | while (items.isNotEmpty() && consumers.isNotEmpty()) { 29 | val consumer = consumers.removeFirst() 30 | val item = items.removeFirst() 31 | consumer(item) 32 | } 33 | } 34 | 35 | suspend override fun consume(): T = suspendCoroutine { c -> 36 | consumers += { c.resume(it) } 37 | flush() 38 | } 39 | 40 | suspend override fun consumeWithCancelHandler(cancel: CancelHandler): T = suspendCoroutine { c -> 41 | val consumer: (T) -> Unit = { c.resume(it) } 42 | cancel { 43 | consumers -= consumer 44 | c.resumeWithException(CancellationException()) 45 | } 46 | consumers += consumer 47 | flush() 48 | } 49 | 50 | } 51 | 52 | fun asyncProducer(callback: suspend Producer.() -> Unit): Consumer { 53 | val p = ProduceConsumer() 54 | 55 | callback.startCoroutine(p, completion = object : Continuation { 56 | override fun resumeWithException(exception: Throwable) { 57 | exception.printStackTrace() 58 | } 59 | 60 | override fun resume(value: Unit) { 61 | } 62 | }) 63 | return p 64 | } 65 | -------------------------------------------------------------------------------- /src/es/kotlin/async/coroutine/cancelHandler.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.coroutine 2 | 3 | import es.kotlin.async.Signal 4 | 5 | typealias CancelHandler = Signal 6 | -------------------------------------------------------------------------------- /src/es/kotlin/async/coroutine/invokeSuspend.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.coroutine 2 | 3 | import java.lang.reflect.Method 4 | import java.util.concurrent.locks.ReentrantLock 5 | import kotlin.coroutines.Continuation 6 | import kotlin.coroutines.CoroutineIntrinsics 7 | import kotlin.coroutines.suspendCoroutine 8 | 9 | 10 | // Useful for invoking methods with suspend 11 | class ContinuationWait { 12 | val lock = ReentrantLock() 13 | var completed = false 14 | var c_value: T? = null 15 | var c_exception: Throwable? = null 16 | var attachedContinuation: Continuation? = null 17 | 18 | inline fun locked(block: () -> R): R { 19 | lock.lock() 20 | return try { block() } finally { lock.unlock() } 21 | } 22 | 23 | val continuation = object : Continuation { 24 | override fun resume(value: T) { 25 | locked { 26 | completed = true 27 | c_value = value 28 | attachedContinuation 29 | }?.resume(value) 30 | } 31 | 32 | override fun resumeWithException(exception: Throwable) { 33 | locked { 34 | completed = true 35 | c_exception = exception 36 | attachedContinuation 37 | }?.resumeWithException(exception) 38 | } 39 | 40 | } 41 | 42 | suspend fun await(): T = suspendCoroutine { c -> 43 | var was_completed = false 44 | var was_c_value: T? = null 45 | var was_c_exception: Throwable? = null 46 | locked { 47 | was_completed = completed 48 | was_c_value = c_value 49 | was_c_exception = c_exception 50 | if (!was_completed) attachedContinuation = c 51 | } 52 | if (was_completed) { 53 | if (was_c_exception != null) { 54 | c.resumeWithException(was_c_exception as Throwable) 55 | } else { 56 | c.resume(was_c_value as T) 57 | } 58 | } 59 | } 60 | } 61 | 62 | suspend fun Method.invokeSuspend(obj: Any?, args: List): Any? = asyncFun { 63 | val method = this 64 | 65 | val lastParam = method.parameters.lastOrNull() 66 | val margs = java.util.ArrayList(args) 67 | var cont: ContinuationWait<*>? = null 68 | 69 | if (lastParam != null && lastParam.type.isAssignableFrom(Continuation::class.java)) { 70 | cont = ContinuationWait() 71 | margs += cont.continuation 72 | } 73 | val result = method.invoke(obj, *margs.toTypedArray()) 74 | if (result == CoroutineIntrinsics.SUSPENDED) { 75 | cont?.await() 76 | } else { 77 | result 78 | } 79 | } -------------------------------------------------------------------------------- /src/es/kotlin/async/utils/downloadUrlAsync.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.utils 2 | 3 | import java.net.URL 4 | 5 | suspend fun downloadUrl(url: URL) = executeInWorker { String(url.openStream().readBytes(), Charsets.UTF_8) } -------------------------------------------------------------------------------- /src/es/kotlin/async/utils/executeInWorkerAsync.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.utils 2 | 3 | import kotlin.coroutines.suspendCoroutine 4 | 5 | suspend fun executeInWorker(task: () -> T): T = suspendCoroutine { c -> 6 | Thread { 7 | try { 8 | val result = task() 9 | c.resume(result) 10 | } catch (e: Throwable) { 11 | c.resumeWithException(e) 12 | } 13 | }.run() 14 | } -------------------------------------------------------------------------------- /src/es/kotlin/async/utils/waitAsync.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.async.utils 2 | 3 | import es.kotlin.async.EventLoop 4 | import es.kotlin.time.TimeSpan 5 | import kotlin.coroutines.suspendCoroutine 6 | 7 | suspend fun sleep(time: TimeSpan) = suspendCoroutine { c -> 8 | EventLoop.setTimeout(time.milliseconds.toInt()) { 9 | c.resume(Unit) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/es/kotlin/collection/coroutine/generate.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.collection.coroutine 2 | 3 | import kotlin.coroutines.Continuation 4 | import kotlin.coroutines.RestrictsSuspension 5 | import kotlin.coroutines.createCoroutine 6 | import kotlin.coroutines.suspendCoroutine 7 | 8 | // From: https://github.com/Kotlin/kotlin-coroutines/blob/master/examples/generate.kt 9 | 10 | @RestrictsSuspension 11 | interface Generator { 12 | suspend fun yield(value: T) 13 | } 14 | 15 | fun generate(block: suspend Generator.() -> Unit): Iterable = object : Iterable { 16 | override fun iterator(): Iterator { 17 | val iterator = GeneratorIterator() 18 | iterator.nextStep = block.createCoroutine(receiver = iterator, completion = iterator) 19 | return iterator 20 | } 21 | } 22 | 23 | private class GeneratorIterator: AbstractIterator(), Generator, Continuation { 24 | lateinit var nextStep: Continuation 25 | 26 | // AbstractIterator implementation 27 | override fun computeNext() { nextStep.resume(Unit) } 28 | 29 | // Completion continuation implementation 30 | override fun resume(value: Unit) { done() } 31 | override fun resumeWithException(exception: Throwable) { throw exception } 32 | 33 | // Generator implementation 34 | override suspend fun yield(value: T) { 35 | setNext(value) 36 | return suspendCoroutine { c -> nextStep = c } 37 | } 38 | } 39 | 40 | /* 41 | fun generate(routine: suspend GeneratorController.() -> Unit): Iterable { 42 | return object : Iterable { 43 | override fun iterator(): Iterator { 44 | return object : Iterator { 45 | val controller = GeneratorController() 46 | 47 | init { 48 | controller.lastContinuation = routine.createCoroutine( 49 | controller, 50 | completion = object : Continuation { 51 | override fun resume(value: Unit) = run { controller.done = true } 52 | override fun resumeWithException(exception: Throwable) = run { throw exception } 53 | } 54 | ) 55 | } 56 | 57 | private fun prepare() { 58 | if (!controller.hasValue) { 59 | controller.lastContinuation.resume(Unit) 60 | } 61 | } 62 | 63 | override fun hasNext(): Boolean { 64 | prepare() 65 | return !controller.done 66 | } 67 | 68 | override fun next(): T { 69 | prepare() 70 | val v = controller.lastValue 71 | controller.hasValue = false 72 | return v as T 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | class GeneratorController { 80 | internal var lastValue: T? = null 81 | internal var hasValue = false 82 | internal lateinit var lastContinuation: Continuation 83 | internal var done: Boolean = false 84 | 85 | suspend fun yield(value: T) = suspendCoroutine { x -> 86 | lastValue = value 87 | hasValue = true 88 | lastContinuation = x 89 | } 90 | } 91 | */ 92 | -------------------------------------------------------------------------------- /src/es/kotlin/collection/lazy/lazy.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.collection.lazy 2 | 3 | import es.kotlin.collection.coroutine.generate 4 | 5 | fun Iterable.lazyFilter(filter: (value: T) -> Boolean): Iterable = generate { for (v in this@lazyFilter) if (filter(v)) yield(v) } 6 | fun Iterable.lazyMap(map: (value: T) -> R): Iterable = generate { for (v in this@lazyMap) yield(map(v)) } 7 | -------------------------------------------------------------------------------- /src/es/kotlin/crypto/Hash.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.crypto 2 | 3 | import java.security.MessageDigest 4 | 5 | class Hash(private val digest: java.security.MessageDigest) { 6 | companion object { 7 | val SHA1 = Hash(MessageDigest.getInstance("SHA1")) 8 | val MD5 = Hash(MessageDigest.getInstance("MD5")) 9 | } 10 | 11 | fun hash(data: ByteArray): ByteArray { 12 | digest.reset() 13 | return digest.digest(data) 14 | } 15 | } -------------------------------------------------------------------------------- /src/es/kotlin/db/async/Db.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async 2 | 3 | -------------------------------------------------------------------------------- /src/es/kotlin/db/async/mysql/MysqlClient.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async.mysql 2 | 3 | /* 4 | class MysqlClient( 5 | private val Host: String = "localhost", 6 | private val Port: Int = 3306, 7 | private val User: String? = null, 8 | private val Password: String? = null, 9 | private val Database: String? = null, 10 | private val Debug: Boolean = false, 11 | private val MaxPacketSize: Int = 0x01000000, 12 | private val KeepAlive: Boolean = false 13 | ) { 14 | private var TcpSocket: AsyncSocket? = null 15 | private lateinit var ScrambleBuffer: ByteArray 16 | private lateinit var ConnectionEncoding: Charset 17 | private var ConnectionEncodingInternal: MysqlLanguageEnum = MysqlLanguageEnum.UTF8_UNICODE_CI 18 | var LastPackedId: Byte = 0 19 | 20 | val isConnected: Boolean get() = TcpSocket?.connected ?: false 21 | 22 | private fun CheckConnectedAsync(): Promise = async { 23 | if (!isConnected) { 24 | ConnectAsync().await() 25 | } 26 | Unit 27 | } 28 | 29 | fun ConnectAsync(): Promise = async { 30 | TcpSocket = AsyncSocket.createAndConnectAsync(host = Host, port = Port, bufferSize = 1024).await() 31 | HandleHandshakePacket(ReadPacketAsync().await()) 32 | SendPacketAsync(CreateNewAuthPacket()).await() 33 | HandleResultPacket(ReadPacketAsync().await()) 34 | if (KeepAlive) { 35 | EventLoop.setInterval(5.seconds) { 36 | PingAsync() 37 | } 38 | } 39 | } 40 | 41 | private fun HandleResultPacket(Packet: MysqlPacket) { 42 | var FieldCount = Packet.ReadByte() 43 | var AffectedRows = Packet.ReadLengthCoded() 44 | var InsertId = Packet.ReadLengthCoded() 45 | var ServerStatus = Packet.ReadUInt16() 46 | var WarningCount = Packet.ReadUInt16() 47 | var Message = Packet.ReadStringz(ConnectionEncoding) 48 | //Console.WriteLine("PacketNumber: {0}", Packet.PacketNumber); 49 | //Console.WriteLine("Result:"); 50 | //Console.WriteLine("AffectedRows:{0}", AffectedRows); 51 | //Console.WriteLine("InsertId:{0}", InsertId); 52 | //Console.WriteLine("ServerStatus:{0}", ServerStatus); 53 | //Console.WriteLine("WarningCount:{0}", WarningCount); 54 | //Console.WriteLine("Message:{0}", Message); 55 | } 56 | 57 | private fun HandleHandshakePacket(Packet: MysqlPacket) { 58 | //Console.WriteLine(Packet.PacketNumber); 59 | 60 | //Trace.Assert(Packet.Number == 0); 61 | var ProtocolVersion = MysqlProtocolVersionEnum.forInt(Packet.ReadByte()) 62 | var ServerVersion = Packet.ReadStringz(ConnectionEncoding) 63 | var ThreadId = Packet.ReadUInt32() 64 | val Scramble0 = Packet.ReadStringzBytes() 65 | val ServerCapabilitiesLow = Packet.ReadUInt16() 66 | var ServerLanguage = Packet.ReadByte() 67 | var ServerStatus = Packet.ReadUInt16() 68 | val ServerCapabilitiesHigh = Packet.ReadUInt16() 69 | var PluginLength = Packet.ReadByte() 70 | Packet.ReadBytes(10) 71 | val Scramble1 = Packet.ReadStringzBytes() 72 | var Extra = Packet.ReadStringz(ConnectionEncoding) 73 | 74 | this.ScrambleBuffer = (Scramble0 + Scramble1) 75 | 76 | var ServerCapabilities = MysqlCapabilitiesSet.forInt((ServerCapabilitiesLow shl 0) or (ServerCapabilitiesHigh shl 16)) 77 | 78 | //Console.WriteLine("PacketNumber: {0}", Packet.PacketNumber); 79 | //Console.WriteLine(ProtocolVersion); 80 | //Console.WriteLine(ServerVersion); 81 | //Console.WriteLine(ServerCapabilities.ToString()); 82 | //Console.WriteLine(Scramble0); 83 | //Console.WriteLine(Scramble1); 84 | //Console.WriteLine(Extra); 85 | } 86 | 87 | private fun CreateNextPacket(): MysqlPacket { 88 | //return new MysqlPacket(++LastPackedId); 89 | return MysqlPacket(ConnectionEncoding, 0 + 1) 90 | } 91 | 92 | private fun CreateNewAuthPacket(): MysqlPacket { 93 | val Token = MysqlAuth.Token(if (this.Password != null) ConnectionEncoding.GetBytes(this.Password) else null, ScrambleBuffer) 94 | val Packet = MysqlPacket(ConnectionEncoding, 0 + 1) 95 | Packet.WriteNumber(4, MysqlCapabilitiesSet.DEFAULT) 96 | Packet.WriteNumber(4, MaxPacketSize) 97 | Packet.WriteNumber(1, ConnectionEncodingInternal) 98 | Packet.WriteFiller(23) 99 | Packet.WriteNullTerminated(this.User, ConnectionEncoding) 100 | Packet.WriteLengthCodedString(Token) 101 | Packet.WriteNullTerminated(this.Database, ConnectionEncoding) 102 | return Packet 103 | } 104 | 105 | private fun SendPacketAsync(Packet: MysqlPacket): Promise = async { 106 | CheckConnectedAsync().await() 107 | Packet.SendToAsync(this.TcpSocket).await() 108 | Unit 109 | } 110 | 111 | private fun ReadPacketAsync(): Promise = async { 112 | val HeaderData = ByteArray(4) 113 | TcpSocket!!.readAsync(HeaderData, 0, HeaderData.size).await() 114 | val PacketLength = ((HeaderData[0] and 0xFF) shl 0) or ((HeaderData[1] and 0xFF) shl 8) or ((HeaderData[2] and 0xFF) shl 16) 115 | val PacketNumber = HeaderData[3] 116 | LastPackedId = PacketNumber 117 | val Data = ByteArray(PacketLength) 118 | TcpSocket!!.readAsync(Data).await() 119 | 120 | // Error Packet 121 | if ((Data[0].toInt() and 0xFF) == 0xFF) { 122 | val Packet = MysqlPacket(ConnectionEncoding, PacketNumber, Data) 123 | Packet.ReadByte() 124 | val Errno = Packet.ReadUInt16() 125 | var SqlStateMarker = Packet.ReadByte() 126 | val SqlState = Packet.ReadBytes(5).toString(Charsets.UTF_8) 127 | val Message = Packet.ReadBytes(Packet.available).toString(Charsets.UTF_8) 128 | throw MysqlException(Errno, SqlState, Message) 129 | } 130 | 131 | MysqlPacket(ConnectionEncoding, PacketNumber, Data) 132 | } 133 | 134 | fun QueryAs(Query: String, vararg Params: Any?): AsyncStream = generateAsync { 135 | for (Row in QueryAsync(Query, Params).await()) { 136 | emit(Row.CastTo()) 137 | } 138 | } 139 | 140 | private fun CheckEofPacket(Packet: MysqlPacket): Boolean { 141 | try { 142 | if (Packet.ReadUByte() == 0xFE) return true 143 | } finally { 144 | Packet.Reset() 145 | } 146 | return false 147 | } 148 | 149 | private fun HandleResultSetHeaderPacket(Packet: MysqlPacket): Int { 150 | // field_count: See the section "Types Of Result Packets" to see how one can distinguish the first byte of field_count from the first byte of an OK Packet, or other packet types. 151 | val FieldCount = Packet.ReadLengthCoded() 152 | // extra: For example, SHOW COLUMNS uses this to send the number of rows in the table. 153 | var Extra = Packet.ReadLengthCoded() 154 | return FieldCount?.toInt() ?: 0 155 | } 156 | 157 | private fun HandleRowDataPacket(Packet: MysqlPacket, MysqlColumns: MysqlColumns): MysqlRow { 158 | val MysqlRow = MysqlRow(MysqlColumns) 159 | 160 | for (n in 0 until MysqlColumns.length) { 161 | val Cell = Packet.ReadLengthCodedString() 162 | MysqlRow.Cells += Cell 163 | //Console.WriteLine(Cell); 164 | } 165 | 166 | return MysqlRow 167 | } 168 | 169 | private fun HandleFieldPacket(Packet: MysqlPacket): MysqlField = MysqlField( 170 | Catalog = Packet.ReadLengthCodedString(), 171 | Database = Packet.ReadLengthCodedString(), 172 | Table = Packet.ReadLengthCodedString(), 173 | OrgTable = Packet.ReadLengthCodedString(), 174 | Name = Packet.ReadLengthCodedString(), 175 | OrgName = Packet.ReadLengthCodedString(), 176 | Unk1 = Packet.ReadByte().toInt(), 177 | Charset = Packet.ReadUInt16(), 178 | Length = Packet.ReadUInt32(), 179 | Type = MysqlFieldTypeEnum.fromInt(Packet.ReadByte ()), 180 | Flags = MysqlFieldFlagsSet.fromInt(Packet.ReadUInt16 ()), 181 | Decimals = Packet.ReadByte(), 182 | Unk2 = Packet.ReadByte(), 183 | Default = Packet.ReadLengthCodedBinary() 184 | ) 185 | 186 | fun CloseAsync() = async { 187 | TcpSocket?.closeAsync()?.await() 188 | TcpSocket = null 189 | Unit 190 | } 191 | 192 | fun Dispose() { 193 | //AsyncHelpers. 194 | //AsyncHelpers. 195 | //CloseAsync().GetAwaiter(). 196 | } 197 | 198 | fun Quote(Param: Any?): String { 199 | if (Param == null) return "NULL" 200 | var Out = "" 201 | for (Char in Param.toString()) { 202 | when (Char) { 203 | '"' -> Out += "\\\"" 204 | '\'' -> Out += "\\'" 205 | '\\' -> Out += "\\\\" 206 | else -> Out += Char 207 | } 208 | } 209 | return "'$Out'" 210 | } 211 | 212 | private fun ReplaceParameters(Query: String, vararg Params: Any?): String { 213 | if (Params.size == 0) return Query 214 | var ParamIndex = 0 215 | var Out = "" 216 | 217 | for (Char in Query) { 218 | if (Char == '?') { 219 | Out += Quote(Params[ParamIndex]) 220 | ParamIndex++ 221 | } else { 222 | Out += Char 223 | } 224 | } 225 | //Console.WriteLine("---{0}", Out.ToString()); 226 | return Out 227 | } 228 | 229 | val asyncTaskQueue = AsyncTaskQueue() 230 | 231 | fun QuitAsync(): Promise = async { 232 | asyncTaskQueue.enqueueAsync { 233 | val OutPacket = MysqlPacket(ConnectionEncoding, 0) 234 | OutPacket.WriteNumber(1, MysqlCommandEnum.COM_QUIT.type) 235 | SendPacketAsync(OutPacket).await() 236 | }.await() 237 | Unit 238 | } 239 | 240 | fun PingAsync(): Promise = async { 241 | asyncTaskQueue.enqueueAsync { 242 | val OutPacket = MysqlPacket(ConnectionEncoding, 0) 243 | OutPacket.WriteNumber(1, MysqlCommandEnum.COM_PING.type) 244 | SendPacketAsync(OutPacket).await() 245 | }.await() 246 | Unit 247 | } 248 | 249 | fun SelectDatabaseAsync(DatabaseName: String): Promise = async { 250 | asyncTaskQueue.enqueueAsync { 251 | val OutPacket = MysqlPacket(ConnectionEncoding, 0) 252 | OutPacket.WriteNumber(1, MysqlCommandEnum.COM_INIT_DB.type) 253 | OutPacket.WryteBytes(DatabaseName.toByteArray(ConnectionEncoding)) 254 | SendPacketAsync(OutPacket).await() 255 | ReadPacketAsync().await() 256 | }.await() 257 | Unit 258 | } 259 | 260 | fun QueryAsync(Query: String, vararg Params: Any?): Promise = async { 261 | val MysqlQueryResult = MysqlQueryResult() 262 | 263 | val Query = ReplaceParameters(Query, Params) 264 | 265 | asyncTaskQueue.enqueueAsync { 266 | val OutPacket = MysqlPacket(ConnectionEncoding, 0) 267 | OutPacket.WriteNumber(1, MysqlCommandEnum.COM_QUERY.type) 268 | OutPacket.WryteBytes(Query.toByteArray(ConnectionEncoding)) 269 | SendPacketAsync(OutPacket).await() 270 | 271 | val NumberOfFields = HandleResultSetHeaderPacket(ReadPacketAsync().await()) 272 | //Console.WriteLine("Number of fields: {0}", NumberOfFields); 273 | 274 | if (NumberOfFields > 0) { 275 | // Read fields 276 | while (true) { 277 | val InPacket = ReadPacketAsync().await() 278 | if (CheckEofPacket(InPacket)) break 279 | MysqlQueryResult.columns += HandleFieldPacket(InPacket) 280 | } 281 | 282 | // Read words 283 | while (true) { 284 | val InPacket = ReadPacketAsync().await() 285 | if (CheckEofPacket(InPacket)) break 286 | MysqlQueryResult.rows += HandleRowDataPacket(InPacket, MysqlQueryResult.columns) 287 | } 288 | } 289 | }.await() 290 | 291 | return MysqlQueryResult 292 | } 293 | } 294 | 295 | class AsyncTaskQueue { 296 | fun enqueueAsync(coroutine routine: AwaitAsyncController.() -> Continuation): Promise { 297 | async(routine) 298 | } 299 | Unit 300 | } 301 | */ 302 | -------------------------------------------------------------------------------- /src/es/kotlin/db/async/mysql/MysqlEnums.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async.mysql 2 | 3 | 4 | enum class MysqlFieldFlagsSet(val value: Short) { 5 | NOT_NULL_FLAG(0x0001), PRI_KEY_FLAG(0x0002), 6 | UNIQUE_KEY_FLAG(0x0004), MULTIPLE_KEY_FLAG(0x0008), 7 | BLOB_FLAG(0x0010), UNSIGNED_FLAG(0x0020), 8 | ZEROFILL_FLAG(0x0040), BINARY_FLAG(0x0080), 9 | ENUM_FLAG(0x0100), AUTO_INCREMENT_FLAG(0x0200), 10 | TIMESTAMP_FLAG(0x0400), SET_FLAG(0x0800), 11 | } 12 | 13 | enum class MysqlFieldTypeEnum(value: Int) { 14 | FIELD_TYPE_DECIMAL(0x00), FIELD_TYPE_TINY(0x01), 15 | FIELD_TYPE_SHORT(0x02), FIELD_TYPE_LONG(0x03), 16 | FIELD_TYPE_FLOAT(0x04), FIELD_TYPE_DOUBLE(0x05), 17 | FIELD_TYPE_NULL(0x06), FIELD_TYPE_TIMESTAMP(0x07), 18 | FIELD_TYPE_LONGLONG(0x08), FIELD_TYPE_INT24(0x09), 19 | FIELD_TYPE_DATE(0x0a), FIELD_TYPE_TIME(0x0b), 20 | FIELD_TYPE_DATETIME(0x0c), FIELD_TYPE_YEAR(0x0d), 21 | FIELD_TYPE_NEWDATE(0x0e), 22 | FIELD_TYPE_VARCHAR(0x0f), // (new in MySQL 5.0) 23 | FIELD_TYPE_BIT(0x10), // (new in MySQL 5.0) 24 | FIELD_TYPE_NEWDECIMAL(0xf6), // (new in MYSQL 5.0) 25 | FIELD_TYPE_ENUM(0xf7), FIELD_TYPE_SET(0xf8), 26 | FIELD_TYPE_TINY_BLOB(0xf9), FIELD_TYPE_MEDIUM_BLOB(0xfa), 27 | FIELD_TYPE_LONG_BLOB(0xfb), FIELD_TYPE_BLOB(0xfc), 28 | FIELD_TYPE_VAR_STRING(0xfd), FIELD_TYPE_STRING(0xfe), 29 | FIELD_TYPE_GEOMETRY(0xff), 30 | } 31 | 32 | enum class MysqlLanguageEnum(val value: Int) { 33 | BIG5_CHINESE_CI(1), LATIN2_CZECH_CS(2), DEC8_SWEDISH_CI(3), CP850_GENERAL_CI(4), 34 | LATIN1_GERMAN1_CI(5), HP8_ENGLISH_CI(6), KOI8R_GENERAL_CI(7), LATIN1_SWEDISH_CI(8), 35 | LATIN2_GENERAL_CI(9), SWE7_SWEDISH_CI(10), ASCII_GENERAL_CI(11), UJIS_JAPANESE_CI(12), 36 | SJIS_JAPANESE_CI(13), CP1251_BULGARIAN_CI(14), LATIN1_DANISH_CI(15), HEBREW_GENERAL_CI(16), 37 | TIS620_THAI_CI(18), EUCKR_KOREAN_CI(19), LATIN7_ESTONIAN_CS(20), LATIN2_HUNGARIAN_CI(21), 38 | KOI8U_GENERAL_CI(22), CP1251_UKRAINIAN_CI(23), GB2312_CHINESE_CI(24), GREEK_GENERAL_CI(25), 39 | CP1250_GENERAL_CI(26), LATIN2_CROATIAN_CI(27), GBK_CHINESE_CI(28), CP1257_LITHUANIAN_CI(29), 40 | LATIN5_TURKISH_CI(30), LATIN1_GERMAN2_CI(31), ARMSCII8_GENERAL_CI(32), UTF8_GENERAL_CI(33), 41 | CP1250_CZECH_CS(34), UCS2_GENERAL_CI(35), CP866_GENERAL_CI(36), KEYBCS2_GENERAL_CI(37), 42 | MACCE_GENERAL_CI(38), MACROMAN_GENERAL_CI(39), CP852_GENERAL_CI(40), LATIN7_GENERAL_CI(41), 43 | LATIN7_GENERAL_CS(42), MACCE_BIN(43), CP1250_CROATIAN_CI(44), LATIN1_BIN(47), 44 | LATIN1_GENERAL_CI(48), LATIN1_GENERAL_CS(49), CP1251_BIN(50), CP1251_GENERAL_CI(51), 45 | CP1251_GENERAL_CS(52), MACROMAN_BIN(53), CP1256_GENERAL_CI(57), CP1257_BIN(58), 46 | CP1257_GENERAL_CI(59), BINARY(63), ARMSCII8_BIN(64), ASCII_BIN(65), 47 | CP1250_BIN(66), CP1256_BIN(67), CP866_BIN(68), DEC8_BIN(69), 48 | GREEK_BIN(70), HEBREW_BIN(71), HP8_BIN(72), KEYBCS2_BIN(73), 49 | KOI8R_BIN(74), KOI8U_BIN(75), LATIN2_BIN(77), LATIN5_BIN(78), 50 | LATIN7_BIN(79), CP850_BIN(80), CP852_BIN(81), SWE7_BIN(82), 51 | UTF8_BIN(83), BIG5_BIN(84), EUCKR_BIN(85), GB2312_BIN(86), 52 | GBK_BIN(87), SJIS_BIN(88), TIS620_BIN(89), UCS2_BIN(90), 53 | UJIS_BIN(91), GEOSTD8_GENERAL_CI(92), GEOSTD8_BIN(93), LATIN1_SPANISH_CI(94), 54 | CP932_JAPANESE_CI(95), CP932_BIN(96), EUCJPMS_JAPANESE_CI(97), EUCJPMS_BIN(98), 55 | CP1250_POLISH_CI(99), UCS2_UNICODE_CI(128), UCS2_ICELANDIC_CI(129), UCS2_LATVIAN_CI(130), 56 | UCS2_ROMANIAN_CI(131), UCS2_SLOVENIAN_CI(132), UCS2_POLISH_CI(133), UCS2_ESTONIAN_CI(134), 57 | UCS2_SPANISH_CI(135), UCS2_SWEDISH_CI(136), UCS2_TURKISH_CI(137), UCS2_CZECH_CI(138), 58 | UCS2_DANISH_CI(139), UCS2_LITHUANIAN_CI(140), UCS2_SLOVAK_CI(141), UCS2_SPANISH2_CI(142), 59 | UCS2_ROMAN_CI(143), UCS2_PERSIAN_CI(144), UCS2_ESPERANTO_CI(145), UCS2_HUNGARIAN_CI(146), 60 | UTF8_UNICODE_CI(192), UTF8_ICELANDIC_CI(193), UTF8_LATVIAN_CI(194), UTF8_ROMANIAN_CI(195), 61 | UTF8_SLOVENIAN_CI(196), UTF8_POLISH_CI(197), UTF8_ESTONIAN_CI(198), UTF8_SPANISH_CI(199), 62 | UTF8_SWEDISH_CI(200), UTF8_TURKISH_CI(201), UTF8_CZECH_CI(202), UTF8_DANISH_CI(203), 63 | UTF8_LITHUANIAN_CI(204), UTF8_SLOVAK_CI(205), UTF8_SPANISH2_CI(206), UTF8_ROMAN_CI(207), 64 | UTF8_PERSIAN_CI(208), UTF8_ESPERANTO_CI(209), UTF8_HUNGARIAN_CI(210), 65 | } 66 | 67 | enum class MysqlProtocolVersionEnum(value: Int) { 68 | Version0(0), Version10(10), Error(0xFF), 69 | } 70 | 71 | enum class MysqlCommandEnum(val type: Int) { 72 | COM_SLEEP(0x00), COM_QUIT(0x01), COM_INIT_DB(0x02), COM_QUERY(0x03), 73 | COM_FIELD_LIST(0x04), COM_CREATE_DB(0x05), COM_DROP_DB(0x06), COM_REFRESH(0x07), 74 | COM_SHUTDOWN(0x08), COM_STATISTICS(0x09), COM_PROCESS_INFO(0x0a), COM_CONNECT(0x0b), 75 | COM_PROCESS_KILL(0x0c), COM_DEBUG(0x0d), COM_PING(0x0e), COM_TIME(0x0f), 76 | COM_DELAYED_INSERT(0x10), COM_CHANGE_USER(0x11), COM_BINLOG_DUMP(0x12), COM_TABLE_DUMP(0x13), 77 | COM_CONNECT_OUT(0x14), COM_REGISTER_SLAVE(0x15), COM_STMT_PREPARE(0x16), COM_STMT_EXECUTE(0x17), 78 | COM_STMT_SEND_LONG_DATA(0x18), COM_STMT_CLOSE(0x19), COM_STMT_RESET(0x1a), COM_SET_OPTION(0x1b), 79 | COM_STMT_FETCH(0x1c), 80 | } 81 | 82 | object MysqlCapabilitiesSet { 83 | val CLIENT_LONG_PASSWORD = 1 // new more secure passwords 84 | val CLIENT_FOUND_ROWS = 2 // Found instead of affected rows 85 | val CLIENT_LONG_FLAG = 4 // Get all column flags 86 | val CLIENT_CONNECT_WITH_DB = 8 // One can specify db on connect 87 | val CLIENT_NO_SCHEMA = 16 // Don't allow database.table.column 88 | val CLIENT_COMPRESS = 32 // Can use compression protocol 89 | val CLIENT_ODBC = 64 // Odbc client 90 | val CLIENT_LOCAL_FILES = 128 // Can use LOAD DATA LOCAL 91 | val CLIENT_IGNORE_SPACE = 256 // Ignore spaces before '(' 92 | val CLIENT_PROTOCOL_41 = 512 // New 4.1 protocol 93 | val CLIENT_INTERACTIVE = 1024 // This is an interactive client 94 | val CLIENT_SSL = 2048 // Switch to SSL after handshake 95 | val CLIENT_IGNORE_SIGPIPE = 4096 // IGNORE sigpipes 96 | val CLIENT_TRANSACTIONS = 8192 // Client knows about transactions 97 | val CLIENT_RESERVED = 16384 // Old flag for 4.1 protocol 98 | val CLIENT_SECURE_CONNECTION = 32768 // New 4.1 authentication 99 | val CLIENT_MULTI_STATEMENTS = 65536 // Enable/disable multi-stmt support 100 | val CLIENT_MULTI_RESULTS = 131072 // Enable/disable multi-results 101 | 102 | val DEFAULT = CLIENT_LONG_PASSWORD or CLIENT_FOUND_ROWS or 103 | CLIENT_LONG_FLAG or CLIENT_CONNECT_WITH_DB or 104 | CLIENT_ODBC or CLIENT_LOCAL_FILES or 105 | CLIENT_IGNORE_SPACE or CLIENT_PROTOCOL_41 or 106 | CLIENT_INTERACTIVE or CLIENT_IGNORE_SIGPIPE or 107 | CLIENT_TRANSACTIONS or CLIENT_RESERVED or 108 | CLIENT_SECURE_CONNECTION or CLIENT_MULTI_STATEMENTS or 109 | CLIENT_MULTI_RESULTS 110 | } 111 | -------------------------------------------------------------------------------- /src/es/kotlin/db/async/mysql/MysqlPacket.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async.mysql 2 | 3 | import es.kotlin.async.Promise 4 | import es.kotlin.async.coroutine.async 5 | import es.kotlin.net.async.AsyncClient 6 | import java.io.ByteArrayInputStream 7 | import java.io.ByteArrayOutputStream 8 | import java.nio.charset.Charset 9 | 10 | /* 11 | class MysqlPacket( 12 | val Encoding: Charset, 13 | val PacketNumber: Byte, 14 | val ByteData: ByteArray = ByteArray(0) 15 | ) { 16 | val Stream = ByteArrayInputStream(ByteData) 17 | val StreamReader = StreamReader (Stream) 18 | 19 | val length: Int get() = Stream.size 20 | val available: Int get() = Stream.available 21 | 22 | fun ReadStringzMemoryStream(): MemoryStream { 23 | var Buffer = MemoryStream () 24 | while (Stream.Position < Stream.Length) { 25 | val c = Stream.read() and 0xFF 26 | if (c == -1) break 27 | if (c == 0) break 28 | Buffer.WriteByte(c.toByte()) 29 | } 30 | Buffer.Position = 0 31 | return Buffer 32 | } 33 | 34 | fun ReadLengthCodedStringBytes(): ByteArray? { 35 | val Size = ReadLengthCoded() ?: return null 36 | return ReadBytes(Size.toInt()) 37 | } 38 | 39 | fun ReadLengthCodedString(): String? { 40 | val Bytes: ByteArray = ReadLengthCodedStringBytes() ?: return null 41 | return Encoding.GetString(Bytes) 42 | } 43 | 44 | fun ReadLengthCodedBinary(): ByteArray { 45 | return ReadLengthCodedStringBytes() 46 | } 47 | 48 | fun Reset() { 49 | Stream.reset() 50 | } 51 | 52 | fun ReadStringzBytes(): ByteArray { 53 | val Buffer = ReadStringzMemoryStream() 54 | val Out = ByteArray(Buffer.Length) 55 | Buffer.Read(Out, 0, Out.Length) 56 | return Out 57 | } 58 | 59 | fun ReadStringz(Encoding: Charset): String { 60 | var Buffer = ReadStringzMemoryStream() 61 | return Encoding.GetString(Buffer.GetBuffer(), 0, Buffer.Length.toInt()) 62 | } 63 | 64 | fun ReadByte(): Byte { 65 | return Stream.read().toByte() 66 | } 67 | 68 | fun ReadUByte(): Int { 69 | return Stream.read() and 0xFF 70 | } 71 | 72 | fun ReadBytes(Count: Int): ByteArray { 73 | val Bytes = ByteArray(Count) 74 | Stream.read(Bytes, 0, Count) 75 | return Bytes 76 | } 77 | 78 | fun ReadUInt16(): Int { 79 | val V0 = ReadUByte() 80 | val V1 = ReadUByte() 81 | return ((V0 shl 0) or (V1 shl 8)) 82 | } 83 | 84 | fun ReadUInt32(): Long { 85 | val V0 = ReadUInt16() 86 | val V1 = ReadUInt16() 87 | return (((V0 shl 0) or (V1 shl 16))).toLong() 88 | } 89 | 90 | fun ReadUInt64(): Long { 91 | val V0 = ReadUInt32() 92 | val V1 = ReadUInt32() 93 | return ((V0 shl 0) or (V1 shl 32)).toLong() 94 | } 95 | 96 | fun ReadLengthCoded(): Long? { 97 | var ReadCount = 0 98 | val First = ReadUByte() 99 | if (First <= 250) { 100 | return First.toLong() 101 | } else { 102 | when (First) { 103 | 251 -> return null 104 | 252 -> ReadCount = 2 105 | 253 -> ReadCount = 3 106 | 254 -> ReadCount = 8 107 | } 108 | } 109 | var Value = 0L 110 | for (n in 0 until ReadCount) { 111 | Value = Value or (ReadUByte().toLong() shl (8 * n)) 112 | } 113 | return Value 114 | } 115 | 116 | fun SendToAsync(Client: AsyncSocket): Promise = async { 117 | Stream.Position = 0 118 | val PacketSize = Stream.size 119 | val Header = ByteArray(4) 120 | Header[0] = (PacketSize ushr 0).toByte() 121 | Header[1] = (PacketSize ushr 8).toByte() 122 | Header[2] = (PacketSize ushr 16).toByte() 123 | Header[3] = this.PacketNumber 124 | Client.WriteAsync(Header).await() 125 | Client.WriteAsync(GetPacketBytes()).await() 126 | Client.FlushAsync().await() 127 | Unit 128 | } 129 | 130 | fun WriteNumber(BytesCount: Int, Value: Int) { 131 | var value = Value 132 | for (n in 0 until BytesCount) 133 | { 134 | this.Stream.WriteByte(Value.toByte()) 135 | value = value ushr 8 136 | } 137 | } 138 | 139 | fun WriteFiller(Count: Int) { 140 | this.Stream.Write(ByteArray(Count), 0, Count) 141 | } 142 | 143 | fun WriteNullTerminated(Data: ByteArray?) { 144 | if (Data != null) this.Stream.Write(Data, 0, Data.Length) 145 | this.Stream.WriteByte(0) 146 | } 147 | 148 | fun WriteNullTerminated(string: String?, encoding: Charset) = WriteNullTerminated(string?.toByteArray(encoding)) 149 | 150 | fun WriteLengthCodedInt(Value: Long) { 151 | var value = Value 152 | 153 | var Count = if (value <= 250) 1 // 8 bits 154 | else if (value <= 0xffff) 2 // 16 bits 155 | else if (value <= 0xffffff) 3 // 24 bits 156 | else 8 // 64 bits 157 | 158 | when (Count) { 159 | 2 -> this.Stream.WriteByte(252) 160 | 3 -> this.Stream.WriteByte(253) 161 | 8 -> this.Stream.WriteByte(254) 162 | } 163 | 164 | while (Count-- > 0) { 165 | this.Stream.WriteByte((byte)(value)) 166 | value = value ushr 8 167 | } 168 | } 169 | 170 | fun WriteLengthCodedString(Value: ByteArray) { 171 | WriteLengthCodedInt(Value.size.toLong()) 172 | this.Stream.Write(Value, 0, Value.size) 173 | } 174 | 175 | fun WryteBytes(Value: ByteArray, Offset: Int = 0, Length: Int = Value.size) { 176 | this.Stream.Write(Value, Offset, Length) 177 | } 178 | } 179 | */ 180 | -------------------------------------------------------------------------------- /src/es/kotlin/db/async/mysql/mysql.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async.mysql 2 | 3 | import es.kotlin.crypto.Hash 4 | 5 | // Converted from my .NET code at: https://github.com/soywiz/NodeNetAsync/blob/master/NodeNetAsync/Db/Mysql/ 6 | 7 | internal object MysqlAuth { 8 | fun Token(password: ByteArray?, scrambleBuffer: ByteArray): ByteArray { 9 | if (password == null || password.size === 0) return ByteArray(0) 10 | val Stage1 = sha1(password) 11 | val Stage2 = sha1(Stage1) 12 | val Stage3 = sha1(scrambleBuffer + Stage2) 13 | return xor(Stage3, Stage1) 14 | } 15 | 16 | private fun sha1(data: ByteArray): ByteArray = Hash.SHA1.hash(data) 17 | 18 | private fun xor(left: ByteArray, right: ByteArray): ByteArray { 19 | val out = ByteArray(left.size) 20 | for (n in 0 until out.size) out[n] = (left[n].toInt() xor right[n].toInt()).toByte() 21 | return out 22 | } 23 | } 24 | 25 | class MysqlException(val errorCode: Int, val sqlState: String, message: String) : RuntimeException(message) 26 | 27 | class MysqlField( 28 | val Catalog: String?, 29 | val Database: String?, 30 | val Table: String?, 31 | val OrgTable: String?, 32 | val Name: String?, 33 | val OrgName: String?, 34 | val Unk1: Int, 35 | val Charset: Int, 36 | val Length: Long, 37 | val Type: MysqlFieldTypeEnum, 38 | val Flags: MysqlFieldFlagsSet, 39 | val Decimals: Byte, 40 | val Unk2: Byte, 41 | val Default: ByteArray 42 | ) 43 | 44 | class MysqlColumns : Iterable { 45 | private val columnsByIndex = arrayListOf() 46 | private val columnsByName = hashMapOf() 47 | private val columnIndexByName = hashMapOf() 48 | 49 | fun add(Column: MysqlField) { 50 | columnsByIndex += Column 51 | columnsByName[Column.Name] = Column 52 | columnIndexByName[Column.Name] = columnsByIndex.size - 1 53 | } 54 | 55 | operator fun get(index: Int) = columnsByIndex[index] 56 | operator fun get(name: String) = columnsByName[name] 57 | 58 | fun getIndexByColumnName(Name: String): Int = columnIndexByName[Name]!! 59 | 60 | val length: Int get() = columnsByIndex.size 61 | 62 | override fun iterator(): Iterator = columnsByIndex.iterator() 63 | } 64 | 65 | class MysqlQueryResult : Iterable { 66 | val columns = MysqlColumns() 67 | val rows = arrayListOf() 68 | 69 | override fun iterator(): Iterator = rows.iterator() 70 | } 71 | 72 | //class MysqlRow : IEnumerable>, IDictionary { 73 | class MysqlRow(val columns: MysqlColumns) { 74 | val Cells = arrayListOf() 75 | 76 | operator fun get(name: String) = this[columns.getIndexByColumnName(name)] 77 | operator fun get(index: Int) = Cells[index] 78 | 79 | //public fun CastTo(): TType 80 | //{ 81 | // var ItemValue = (TType)Activator.CreateInstance(typeof(TType)); 82 | // var ItemType = typeof(TType); 83 | // 84 | // for (int n = 0; n < Columns.Length; n++) 85 | // { 86 | // var Column = Columns[n]; 87 | // var Value = Cells[n]; 88 | // var Field = ItemType.GetField(Column.Name); 89 | // if (Field != null) 90 | // { 91 | // object ValueObject = null; 92 | // 93 | // if (Field.FieldType == typeof(bool)) 94 | // { 95 | // ValueObject = (int.Parse(Value) != 0); 96 | // } 97 | // else if (Field.FieldType == typeof(int)) 98 | // { 99 | // ValueObject = int.Parse(Value); 100 | // } 101 | // else if (Field.FieldType == typeof(string)) 102 | // { 103 | // ValueObject = Value; 104 | // } 105 | // else 106 | // { 107 | // throw(new NotImplementedException("Can't handle type '" + Field.FieldType + "'")); 108 | // } 109 | // 110 | // Field.SetValueDirect(__makeref(ItemValue), ValueObject); 111 | // } 112 | // } 113 | // 114 | // return ItemValue; 115 | //} 116 | 117 | //public override string ToString() 118 | //{ 119 | // var Parts = new List(); 120 | // for (int n = 0; n < Cells.Count; n++) 121 | // { 122 | // Parts.Add("'" + Columns[n].Name + "': '" + Cells[n] + "'"); 123 | // } 124 | // return "MysqlRow(" + String.Join(", ", Parts) + ")"; 125 | //} 126 | // 127 | //IEnumerator> IEnumerable>.GetEnumerator() 128 | //{ 129 | // for (int n = 0; n < Columns.Length; n++) 130 | // { 131 | // var Column = Columns[n]; 132 | // var Value = Cells[n]; 133 | // yield return new KeyValuePair(Column.Name, Value); 134 | // } 135 | //} 136 | // 137 | //IEnumerator IEnumerable.GetEnumerator() 138 | //{ 139 | // foreach (var Item in ((IEnumerable>)this).AsEnumerable()) yield return Item; 140 | //} 141 | // 142 | //IDictionaryEnumerator IDictionary.GetEnumerator() 143 | //{ 144 | // return new Enumerator(this); 145 | //} 146 | } 147 | 148 | -------------------------------------------------------------------------------- /src/es/kotlin/db/async/redis/RedisClient.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async.redis 2 | 3 | import es.kotlin.async.coroutine.async 4 | import es.kotlin.async.coroutine.asyncFun 5 | import es.kotlin.async.coroutine.await 6 | import es.kotlin.net.async.AsyncClient 7 | import es.kotlin.net.async.readLine 8 | 9 | // Ported from by .NET code: https://github.com/soywiz/NodeNetAsync/blob/master/NodeNetAsync/Db/Redis/RedisClient.cs 10 | class RedisClient( 11 | private val host: String = "127.0.0.1", 12 | private val port: Int = 6379 13 | ) { 14 | private val charset = Charsets.UTF_8 15 | private val socket = AsyncClient() 16 | 17 | suspend private fun ensureConnect() = asyncFun { 18 | if (!socket.connected) socket.connect(host, port) 19 | } 20 | 21 | suspend fun command(vararg args: String) = asyncFun { 22 | var cmd = "*${args.size}\r\n" 23 | 24 | for (arg in args) { 25 | val argBytes = arg.toByteArray(charset) 26 | cmd += "\$${argBytes.size}\r\n" 27 | cmd += "$arg\r\n" 28 | } 29 | 30 | val data = cmd.toByteArray(charset) 31 | ensureConnect() 32 | socket.write(data) 33 | readValue() 34 | } 35 | 36 | suspend private fun readValue(): Any = async { 37 | val firstLine = socket.readLine(charset) 38 | val type = firstLine[0] 39 | val data = firstLine.substring(1) 40 | 41 | val out: Any = when (type) { 42 | // Status reply 43 | '+' -> data 44 | // Error reply 45 | '-' -> throw RedisResponseException(data) 46 | // Integer reply 47 | ':' -> data.toLong() 48 | // Bulk replies 49 | '$' -> { 50 | //println("data\n\n: '$data'") 51 | val bytesToRead = data.toInt() 52 | if (bytesToRead == -1) { 53 | Unit 54 | } else { 55 | ///////////////////////////////////////////////////////// 56 | // @TODO: @BUG with git version: 57 | ///////////////////////////////////////////////////////// 58 | // @TODO: https://youtrack.jetbrains.com/issue/KT-12958 59 | ///////////////////////////////////////////////////////// 60 | 61 | //val data2 = socket.readAsync(bytesToRead).await() 62 | //socket.readAsync(2).await() 63 | //data2.toString(charset) 64 | 65 | // @WORKS 66 | val data2 = socket.read(bytesToRead + 2) 67 | val out = data2.toString(charset) 68 | out.substring(0, out.length - 2) 69 | } 70 | } 71 | // Array reply 72 | '*' -> { 73 | val bulksToRead = data.toLong() 74 | val bulks = arrayListOf() 75 | for (n in 0 until bulksToRead) { 76 | bulks += readValue() 77 | } 78 | bulks 79 | } 80 | else -> throw RedisResponseException("Unknown param type '$type'") 81 | } 82 | //println("out:$out") 83 | out 84 | } 85 | } 86 | 87 | ///////////////////////////////////////////////////////// 88 | // @TODO: @BUG with git version? Should produce error because there is no suitable handleResult? Or Just call handleResult with Unit 89 | // @TODO: Instead of it, it just "hangs", because promise is not resolved because handleResult is not called and it is hard to locate the problem (though in this case it was easy) 90 | ///////////////////////////////////////////////////////// 91 | //fun RedisClient.setAsync(key: String, value: String) = async { commandAsync("set", key, value).await() } 92 | 93 | 94 | operator suspend fun RedisClient.set(key: String, value: String) = command("set", key, value) 95 | //fun RedisClient.setAsync(key: String, value: String) = commandAsync("set", key, value) 96 | 97 | 98 | operator suspend fun RedisClient.get(key: String) = command("get", key) 99 | //fun RedisClient.getAsync(key: String) = commandAsync("get", key) 100 | 101 | suspend fun RedisClient.exists(key: String) = asyncFun { command("exists", key) == 1L } 102 | 103 | operator suspend fun RedisClient.contains(key: String) = asyncFun { command("exists", key) == 1L } 104 | 105 | class RedisResponseException(message: String) : RuntimeException(message) -------------------------------------------------------------------------------- /src/es/kotlin/db/async/redis/RedisVfs.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.db.async.redis 2 | 3 | import es.kotlin.async.Promise 4 | import es.kotlin.async.coroutine.async 5 | import es.kotlin.async.coroutine.asyncFun 6 | import es.kotlin.async.coroutine.await 7 | import es.kotlin.vfs.async.Vfs 8 | import es.kotlin.vfs.async.VfsFile 9 | import es.kotlin.vfs.async.VfsStat 10 | 11 | fun RedisVfs(client: RedisClient): VfsFile { 12 | class Impl : Vfs() { 13 | suspend override fun readFully(path: String) = asyncFun { 14 | ("" + client.get(path)).toByteArray(Charsets.UTF_8) 15 | } 16 | 17 | suspend override fun writeFully(path: String, data: ByteArray): Unit = asyncFun { 18 | client.set(path, data.toString(Charsets.UTF_8)) 19 | Unit 20 | } 21 | 22 | suspend override fun stat(path: String): VfsStat = asyncFun { 23 | val exists = client.exists(path) 24 | VfsStat(file = VfsFile(this@Impl, path), exists = exists, isDirectory = false, size = -1L) 25 | } 26 | } 27 | 28 | return Impl().root 29 | } -------------------------------------------------------------------------------- /src/es/kotlin/di/AsyncInjector.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.di 2 | 3 | import es.kotlin.async.Promise 4 | import es.kotlin.async.coroutine.async 5 | import es.kotlin.async.coroutine.await 6 | 7 | @Target(AnnotationTarget.CLASS) 8 | annotation class Prototype 9 | 10 | @Target(AnnotationTarget.CLASS) 11 | annotation class Singleton 12 | 13 | class AsyncInjector { 14 | private val instances = hashMapOf, Any?>() 15 | 16 | suspend inline fun get() = await(getAsync(T::class.java)) 17 | 18 | inline fun getAsync(): Promise = getAsync(T::class.java) 19 | inline fun map(instance: T): AsyncInjector = map(T::class.java, instance) 20 | 21 | init { 22 | map(this) 23 | } 24 | 25 | fun map(clazz: Class, instance: T): AsyncInjector { 26 | instances[clazz] = instance as Any 27 | return this 28 | } 29 | 30 | @Suppress("UNCHECKED_CAST") 31 | fun getAsync(clazz: Class): Promise = async { 32 | if (instances.containsKey(clazz) || clazz.getAnnotation(Singleton::class.java) != null) { 33 | if (!instances.containsKey(clazz)) { 34 | val instance = await(createAsync(clazz)) 35 | instances[clazz] = instance 36 | } 37 | instances[clazz]!! as T 38 | } else { 39 | await(createAsync(clazz)) 40 | } 41 | } 42 | 43 | @Suppress("UNCHECKED_CAST") 44 | fun createAsync(clazz: Class): Promise = async { 45 | val constructor = clazz.declaredConstructors.first() 46 | val promises = arrayListOf>() 47 | val out = arrayListOf() 48 | for (paramType in constructor.parameterTypes) { 49 | promises += getAsync(paramType) 50 | } 51 | for (prom in promises) { 52 | out += await(prom) 53 | } 54 | val instance = constructor.newInstance(*out.toTypedArray()) as T 55 | if (instance is AsyncDependency) { 56 | try { 57 | instance.init() 58 | } catch (e: Throwable) { 59 | println("AsyncInjector (${e.message}):") 60 | e.printStackTrace() 61 | throw e 62 | } 63 | } 64 | instance 65 | } 66 | } 67 | 68 | interface AsyncDependency { 69 | suspend fun init(): Unit 70 | } -------------------------------------------------------------------------------- /src/es/kotlin/lang/ClassFactory.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.lang 2 | 3 | import java.lang.Boolean 4 | import java.lang.Byte 5 | import java.lang.Double 6 | import java.lang.Float 7 | import java.lang.Long 8 | import java.lang.Short 9 | import java.lang.reflect.Array 10 | import java.lang.reflect.Modifier 11 | 12 | // @TODO: This should use ASM library to create a class per class to be as fast as possible 13 | class ClassFactory(val clazz: Class) { 14 | val fields = clazz.declaredFields 15 | .filter { !Modifier.isTransient(it.modifiers) } 16 | 17 | init { 18 | for (field in fields) { 19 | field.isAccessible = true 20 | } 21 | } 22 | 23 | fun create(values: Map): T { 24 | val instance = createDummy(clazz) 25 | for (field in fields) { 26 | if (values.containsKey(field.name)) { 27 | field.set(instance, values[field.name]) 28 | } 29 | } 30 | return instance 31 | } 32 | 33 | fun toMap(instance: T): Map { 34 | return fields.map { it.name to it.get(instance) }.toMap() 35 | } 36 | } 37 | 38 | @Suppress("UNCHECKED_CAST") 39 | fun createDummy(clazz: Class): T = createDummyUnchecked(clazz) as T 40 | 41 | fun createDummyUnchecked(clazz: Class<*>): Any { 42 | when (clazz) { 43 | Boolean.TYPE -> return false 44 | Byte.TYPE -> return 0.toByte() 45 | Short.TYPE -> return 0.toShort() 46 | Character.TYPE -> return 0.toChar() 47 | Integer.TYPE -> return 0 48 | Long.TYPE -> return 0L 49 | Float.TYPE -> return 0f 50 | Double.TYPE -> return 0.0 51 | } 52 | if (clazz.isArray) return Array.newInstance(clazz.componentType, 0) 53 | if (clazz.isAssignableFrom(List::class.java)) return ArrayList() 54 | if (clazz.isAssignableFrom(Map::class.java)) return HashMap() 55 | val constructor = clazz.declaredConstructors.first() 56 | val args = constructor.parameterTypes.map { createDummyUnchecked(it) } 57 | return constructor.newInstance(*args.toTypedArray()) 58 | } 59 | -------------------------------------------------------------------------------- /src/es/kotlin/lang/DynamicConvert.kt: -------------------------------------------------------------------------------- 1 | package ext.lang 2 | 3 | fun DynamicConvert(value: Any?, type: Class<*>): Any? { 4 | return when (type) { 5 | java.lang.Integer.TYPE -> "$value".toInt() 6 | java.lang.Long.TYPE -> "$value".toLong() 7 | java.lang.String::class.java -> "$value" 8 | else -> null 9 | } 10 | } -------------------------------------------------------------------------------- /src/es/kotlin/lang/Once.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.lang 2 | 3 | class Once { 4 | var completed = false 5 | 6 | inline operator fun invoke(callback: () -> Unit) { 7 | if (!completed) { 8 | completed = true 9 | callback() 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/es/kotlin/lang/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.lang 2 | 3 | fun String.tryInt(): Int? = try { 4 | this.toInt() 5 | } catch (t: Throwable) { 6 | null 7 | } -------------------------------------------------------------------------------- /src/es/kotlin/net/async/AsyncClient.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.net.async 2 | 3 | import es.kotlin.async.Signal 4 | import es.kotlin.async.coroutine.* 5 | import java.io.ByteArrayOutputStream 6 | import java.net.InetSocketAddress 7 | import java.net.SocketAddress 8 | import java.nio.ByteBuffer 9 | import java.nio.channels.AsynchronousSocketChannel 10 | import java.nio.channels.CompletionHandler 11 | import java.nio.charset.Charset 12 | import java.util.* 13 | import kotlin.coroutines.startCoroutine 14 | import kotlin.coroutines.suspendCoroutine 15 | 16 | class AsyncClient( 17 | private val sc: AsynchronousSocketChannel = AsynchronousSocketChannel.open() 18 | ) { 19 | private var _connected = false 20 | 21 | val millisecondsTimeout = 60 * 1000L 22 | 23 | companion object { 24 | suspend operator fun invoke(host: String, port: Int, bufferSize: Int = 1024) = createAndConnect(host, port, bufferSize) 25 | 26 | suspend fun createAndConnect(host: String, port: Int, bufferSize: Int = 1024) = asyncFun { 27 | val socket = AsyncClient() 28 | socket.connect(host, port) 29 | socket 30 | } 31 | } 32 | 33 | suspend fun connect(host: String, port: Int) = connect(InetSocketAddress(host, port)) 34 | 35 | suspend fun connect(remote: SocketAddress): Unit = suspendCoroutine { c -> 36 | sc.connect(remote, this, object : CompletionHandler { 37 | override fun completed(result: Void?, attachment: AsyncClient): Unit = run { _connected = true; c.resume(Unit) } 38 | override fun failed(exc: Throwable, attachment: AsyncClient): Unit = run { _connected = false; c.resumeWithException(exc) } 39 | }) 40 | } 41 | 42 | val connected: Boolean get() = this._connected 43 | val bytesStream = readStream() 44 | 45 | fun readStream(): Consumer = asyncProducer { 46 | try { 47 | while (true) { 48 | val c = ___read(1)[0] 49 | produce(c) 50 | } 51 | } catch (e: Throwable) { 52 | e.printStackTrace() 53 | } 54 | } 55 | 56 | suspend fun read(size: Int, cancelHandler: CancelHandler): ByteArray = asyncFun { 57 | val out = ByteArray(size) 58 | for (n in 0 until size) out[n] = bytesStream.consumeWithCancelHandler(cancelHandler) as Byte 59 | out 60 | } 61 | 62 | suspend fun read(size: Int): ByteArray = asyncFun { 63 | val out = ByteArray(size) 64 | for (n in 0 until size) out[n] = bytesStream.consume() as Byte 65 | out 66 | } 67 | 68 | suspend private fun ___read(size: Int): ByteArray = suspendCoroutine { c -> 69 | val out = ByteArray(size) 70 | val buffer = ByteBuffer.wrap(out) 71 | 72 | sc.read(buffer, this, object : CompletionHandler { 73 | override fun completed(result: Int, attachment: AsyncClient): Unit = run { 74 | if (result < 0) { 75 | c.resumeWithException(RuntimeException("EOF")) 76 | } else { 77 | c.resume(Arrays.copyOf(out, result)) 78 | } 79 | } 80 | 81 | override fun failed(exc: Throwable, attachment: AsyncClient): Unit = run { 82 | c.resumeWithException(exc) 83 | } 84 | }) 85 | } 86 | 87 | //suspend private fun _read(size: Int): ByteArray = asyncFun { 88 | // val onCancel: Signal = Signal() 89 | // try { 90 | // __read(size, onCancel) 91 | // } finally { 92 | // onCancel.invoke(Unit) 93 | // } 94 | //} 95 | 96 | //suspend private fun __read(size: Int, onCancel: Signal): ByteArray = suspendCoroutine { c -> 97 | // val out = ByteArray(size) 98 | // val buffer = ByteBuffer.wrap(out) 99 | // 100 | // sc.read(buffer, this, object : CompletionHandler { 101 | // override fun completed(result: Int, attachment: AsyncClient): Unit = run { 102 | // if (result < 0) { 103 | // c.resumeWithException(RuntimeException("EOF")) 104 | // } else { 105 | // c.resume(Arrays.copyOf(out, result)) 106 | // } 107 | // } 108 | // 109 | // override fun failed(exc: Throwable, attachment: AsyncClient): Unit = run { 110 | // c.resumeWithException(exc) 111 | // } 112 | // }) 113 | // 114 | // onCancel.add { 115 | // // Do cancellation 116 | // } 117 | //} 118 | 119 | suspend fun write(data: ByteArray) = suspendCoroutine { c -> 120 | val buffer = ByteBuffer.wrap(data) 121 | sc.write(buffer, this, object : CompletionHandler { 122 | override fun completed(result: Int, attachment: AsyncClient): Unit = run { c.resume(Unit) } 123 | override fun failed(exc: Throwable, attachment: AsyncClient): Unit = run { c.resumeWithException(exc) } 124 | }) 125 | } 126 | 127 | suspend fun close(): Unit = asyncFun { 128 | sc.close() 129 | } 130 | } 131 | 132 | suspend fun AsyncClient.readLine(charset: Charset = Charsets.UTF_8) = asyncFun { 133 | try { 134 | val os = ByteArrayOutputStream() 135 | // @TODO: optimize this! 136 | while (true) { 137 | val ba = read(1) 138 | os.write(ba[0].toInt()) 139 | if (ba[0].toChar() == '\n') break 140 | } 141 | val out = os.toByteArray().toString(charset) 142 | val res = if (out.endsWith("\r\n")) { 143 | out.substring(0, out.length - 2) 144 | } else if (out.endsWith("\n")) { 145 | out.substring(0, out.length - 1) 146 | } else { 147 | out 148 | } 149 | res 150 | } catch (e: Throwable) { 151 | println("readLine.ERROR: ${e.message}") 152 | throw e 153 | } finally { 154 | println("readLine completed!") 155 | } 156 | } 157 | 158 | suspend fun AsyncClient.readLine(cancelHandler: CancelHandler, charset: Charset = Charsets.UTF_8) = asyncFun { 159 | try { 160 | val os = ByteArrayOutputStream() 161 | // @TODO: optimize this! 162 | while (true) { 163 | val ba = read(1, cancelHandler) 164 | os.write(ba[0].toInt()) 165 | if (ba[0].toChar() == '\n') break 166 | } 167 | val out = os.toByteArray().toString(charset) 168 | val res = if (out.endsWith("\r\n")) { 169 | out.substring(0, out.length - 2) 170 | } else if (out.endsWith("\n")) { 171 | out.substring(0, out.length - 1) 172 | } else { 173 | out 174 | } 175 | res 176 | } finally { 177 | //println("readLine completed!") 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/es/kotlin/net/async/AsyncServer.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.net.async 2 | 3 | import es.kotlin.async.coroutine.asyncGenerate 4 | import java.net.InetSocketAddress 5 | import java.net.SocketAddress 6 | import java.nio.channels.AsynchronousServerSocketChannel 7 | import java.nio.channels.AsynchronousSocketChannel 8 | import java.nio.channels.CompletionHandler 9 | import kotlin.coroutines.suspendCoroutine 10 | 11 | class AsyncServer(val local: SocketAddress, val backlog: Int = 128) { 12 | constructor(port: Int, host: String = "127.0.0.1") : this(InetSocketAddress(host, port)) 13 | 14 | val ssc = AsynchronousServerSocketChannel.open() 15 | 16 | init { 17 | ssc.bind(local, backlog) 18 | } 19 | 20 | suspend fun listen() = asyncGenerate { 21 | while (true) yield(AsyncClient(ssc.saccept())) 22 | } 23 | 24 | suspend fun AsynchronousServerSocketChannel.saccept() = suspendCoroutine { c -> 25 | this.accept(Unit, object : CompletionHandler { 26 | override fun completed(result: AsynchronousSocketChannel, attachment: Unit) = Unit.apply { c.resume(result) } 27 | override fun failed(exc: Throwable, attachment: Unit) = Unit.apply { c.resumeWithException(exc) } 28 | }) 29 | } 30 | } -------------------------------------------------------------------------------- /src/es/kotlin/random/RandomExt.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.random 2 | 3 | import java.util.* 4 | 5 | operator fun List.get(random :Random) = this[random.nextInt(this.size)] -------------------------------------------------------------------------------- /src/es/kotlin/time/TimeSpan.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.time 2 | 3 | data class TimeSpan(val milliseconds: Double) { 4 | val seconds: Double get() = milliseconds / 1000.0 5 | 6 | override fun toString() = "$seconds seconds" 7 | } 8 | 9 | val Int.seconds: TimeSpan get() = TimeSpan((this * 1000).toDouble()) 10 | val Double.seconds: TimeSpan get() = TimeSpan(this * 1000) -------------------------------------------------------------------------------- /src/es/kotlin/vertx/VertxExt.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vertx 2 | 3 | import es.kotlin.async.EventLoop 4 | import es.kotlin.async.coroutine.asyncFun 5 | import es.kotlin.async.coroutine.awaitAsyncTask 6 | import io.vertx.core.Vertx 7 | import io.vertx.core.buffer.Buffer 8 | import java.io.Closeable 9 | import java.io.File 10 | 11 | val vertx by lazy { 12 | Vertx.vertx().apply { 13 | val vertx = this 14 | 15 | EventLoop.impl = object : EventLoop() { 16 | override fun queue(callback: () -> Unit) { 17 | vertx.runOnContext { callback() } 18 | } 19 | 20 | override fun setInterval(ms: Int, callback: () -> Unit): Closeable { 21 | val timer = vertx.setPeriodic(ms.toLong(), { 22 | callback() 23 | }) 24 | return Closeable { 25 | vertx.cancelTimer(timer) 26 | } 27 | } 28 | 29 | override fun setTimeout(ms: Int, callback: () -> Unit): Closeable { 30 | var done = false 31 | val timer = vertx.setTimer(ms.toLong()) { 32 | done = true 33 | callback() 34 | } 35 | return Closeable { 36 | if (!done) { 37 | done = true 38 | vertx.cancelTimer(timer) 39 | } 40 | } 41 | } 42 | 43 | override fun step() { 44 | } 45 | } 46 | } 47 | } 48 | 49 | val vertxFileSystem by lazy { vertx.fileSystem() } 50 | 51 | suspend fun readResourceAsString(name: String) = asyncFun { 52 | readResource(name).toString(Charsets.UTF_8) 53 | } 54 | 55 | suspend fun readResource(name: String) = ClassLoader.getSystemClassLoader().readBytes(name) 56 | 57 | suspend fun ClassLoader.readBytes(name: String) = awaitAsyncTask { 58 | val resource = getResourceAsStream(name) ?: throw RuntimeException("Can't find resource '$name'") 59 | resource.readBytes() 60 | } 61 | 62 | 63 | suspend fun File.readBytes() = asyncFun { readBuffer().bytes } 64 | 65 | suspend fun File.readBuffer(): Buffer = vx { vertxFileSystem.readFile(absolutePath, it) } -------------------------------------------------------------------------------- /src/es/kotlin/vertx/VertxKtPromise.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vertx 2 | 3 | import es.kotlin.async.Promise 4 | import es.kotlin.async.coroutine.async 5 | import es.kotlin.async.coroutine.await 6 | import io.vertx.core.AsyncResult 7 | import io.vertx.core.Handler 8 | import io.vertx.core.Vertx 9 | import io.vertx.core.http.HttpServer 10 | import io.vertx.ext.web.Route 11 | import io.vertx.ext.web.Router 12 | import io.vertx.ext.web.RoutingContext 13 | import kotlin.coroutines.suspendCoroutine 14 | 15 | fun Router.get(path: String, callback: (RoutingContext) -> Unit): Route { 16 | return this.get(path).handler { callback(it) } 17 | } 18 | 19 | fun RoutingContext.header(key: String, value: String) { 20 | this.response().putHeader(key, value) 21 | } 22 | 23 | fun Vertx.router(callback: Router.() -> Unit): Router { 24 | val router = Router.router(this) 25 | router.callback() 26 | return router 27 | } 28 | 29 | fun routedHttpServerAsync(port: Int = System.getenv("PORT")?.toIntOrNull() ?: 8080, callback: Router.() -> Promise = { Promise.resolved(Unit) }): Promise = async { 30 | val router = Router.router(vertx) 31 | 32 | println("Preparing router...") 33 | await(callback(router)) 34 | println("Preparing router...Ok") 35 | 36 | vertx.createHttpServer() 37 | .requestHandler { req -> router.accept(req) } 38 | .listen(port) { 39 | if (it.failed()) { 40 | println("routedHttpServerAsync (${it.cause().message}):") 41 | it.cause().printStackTrace() 42 | } else { 43 | println("Listening at port ${it.result().actualPort()}") 44 | } 45 | } 46 | } 47 | 48 | 49 | fun Promise.Deferred.toVertxHandler(): Handler> { 50 | val deferred = this 51 | return Handler> { event -> 52 | val result = event.result() 53 | if (result != null) { 54 | deferred.resolve(event.result()) 55 | } else { 56 | var cause = event.cause() 57 | if (cause == null) cause = RuntimeException("Invalid") 58 | deferred.reject(cause) 59 | } 60 | } 61 | } 62 | 63 | 64 | // Suggested by Roman Elizarov @ kotlinlang slack #coroutines 22nd december 2016: 65 | // Roman Elizarov [JB] [3:51 PM] 66 | // Why do you care about alloc? It an sync call. Its cost is already much higher than even a few extra objects. 67 | // `vx { doSomeVertexOperation(args, it) }` 68 | inline suspend fun vx(crossinline callback: (Handler>) -> Unit) = suspendCoroutine { c -> 69 | callback(object : Handler> { 70 | override fun handle(event: AsyncResult) { 71 | if (event.succeeded()) { 72 | c.resume(event.result()) 73 | } else { 74 | c.resumeWithException(event.cause()) 75 | } 76 | } 77 | }) 78 | } -------------------------------------------------------------------------------- /src/es/kotlin/vertx/redis/RedisExt.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vertx.redis 2 | 3 | import es.kotlin.async.Promise 4 | import es.kotlin.async.coroutine.asyncFun 5 | import es.kotlin.async.coroutine.await 6 | import es.kotlin.vertx.vx 7 | import io.vertx.core.json.JsonArray 8 | import io.vertx.core.json.JsonObject 9 | import io.vertx.redis.RedisClient 10 | import io.vertx.redis.op.RangeOptions 11 | 12 | operator fun RedisClient.get(key: String) = RedisKey(this, key) 13 | 14 | class RedisKey(val client: RedisClient, val key: String) 15 | 16 | suspend fun RedisKey.hget(member: String): String = vx { client.hget(key, member, it) } 17 | 18 | suspend fun RedisKey.hgetall() = asyncFun { 19 | val parts: JsonObject = vx { client.hgetall(key, it) } 20 | parts.map { it.key to it.value as String }.toMap() 21 | } 22 | 23 | suspend fun RedisKey.hincrby(member: String, increment: Long): Long = vx { client.hincrby(key, member, increment, it) } 24 | 25 | suspend fun RedisKey.zaddMany(scores: Map): Long = vx { client.zaddMany(key, scores, it) } 26 | suspend fun RedisKey.zadd(member: String, score: Double): Long = vx { client.zadd(key, score, member, it) } 27 | suspend fun RedisKey.zincrby(member: String, score: Double): String = vx { client.zincrby(key, score, member, it) } 28 | suspend fun RedisKey.zcard(): Long = vx { client.zcard(key, it) } 29 | suspend fun RedisKey.zrevrank(member: String): Long = vx { client.zrevrank(key, member, it) } 30 | 31 | suspend fun RedisKey.zrevrange(start: Long, stop: Long) = asyncFun { 32 | val result: JsonArray = vx { client.zrevrange(key, start, stop, RangeOptions.WITHSCORES, it) } 33 | (0 until result.size() / 2).map { result.getString(it * 2 + 0) to result.getString(it * 2 + 1).toDouble() } 34 | } 35 | 36 | suspend fun RedisKey.del(): Long = vx { client.del(key, it) } 37 | -------------------------------------------------------------------------------- /src/es/kotlin/vertx/route/RouterMap.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vertx.route 2 | 3 | import es.kotlin.async.Promise 4 | import es.kotlin.async.coroutine.async 5 | import es.kotlin.async.coroutine.await 6 | import es.kotlin.async.coroutine.invokeSuspend 7 | import es.kotlin.di.AsyncInjector 8 | import ext.lang.DynamicConvert 9 | import io.netty.handler.codec.http.QueryStringDecoder 10 | import io.vertx.core.http.HttpMethod 11 | import io.vertx.core.json.Json 12 | import io.vertx.ext.web.Router 13 | import java.lang.reflect.InvocationTargetException 14 | import kotlin.coroutines.Continuation 15 | 16 | annotation class Route(val method: HttpMethod, val path: String) 17 | 18 | annotation class Param(val name: String, val limit: Int = -1) 19 | annotation class Post(val name: String, val limit: Int = -1) 20 | 21 | suspend inline fun Router.registerRouter(injector: AsyncInjector) = await(this.registerRouterAsync(injector, T::class.java)) 22 | suspend fun Router.registerRouter(injector: AsyncInjector, clazz: Class<*>) = await(this.registerRouterAsync(injector, clazz)) 23 | 24 | fun Router.registerRouterAsync(injector: AsyncInjector, clazz: Class<*>): Promise = async { 25 | val router = this@registerRouterAsync 26 | val instance = await(injector.getAsync(clazz)) 27 | 28 | for (method in clazz.declaredMethods) { 29 | val route = method.getAnnotation(Route::class.java) 30 | if (route != null) { 31 | router.route(route.method, route.path).handler { rreq -> 32 | val res = rreq.response() 33 | 34 | val req = rreq.request() 35 | val contentType = req.headers().get("Content-Type") 36 | 37 | val bodyHandler = Promise.Deferred>>() 38 | 39 | req.bodyHandler { buf -> 40 | if ("application/x-www-form-urlencoded" == contentType) { 41 | val qsd = QueryStringDecoder(buf.toString(), false) 42 | val params = qsd.parameters() 43 | bodyHandler.resolve(params) 44 | } 45 | } 46 | 47 | async { 48 | try { 49 | val args = arrayListOf() 50 | for ((paramType, annotations) in method.parameterTypes.zip(method.parameterAnnotations)) { 51 | val get = annotations.filterIsInstance().firstOrNull() 52 | val post = annotations.filterIsInstance().firstOrNull() 53 | if (get != null) { 54 | args += DynamicConvert(rreq.pathParam(get.name), paramType) 55 | } else if (post != null) { 56 | val postParams = await(bodyHandler.promise) 57 | val result = postParams[post.name]?.firstOrNull() 58 | args += DynamicConvert(result, paramType) 59 | } else if (Continuation::class.java.isAssignableFrom(paramType)) { 60 | //deferred = Promise.Deferred() 61 | //args += deferred.toContinuation() 62 | } else { 63 | throw RuntimeException("Expected @Get annotation") 64 | } 65 | } 66 | 67 | val result = method.invokeSuspend(instance, args) 68 | 69 | val finalResult = if (result is Promise<*>) { 70 | await(result) 71 | } else { 72 | result 73 | } 74 | 75 | when (finalResult) { 76 | is String -> res.end("$finalResult") 77 | else -> res.end(Json.encode(finalResult)) 78 | } 79 | } catch (t: Throwable) { 80 | println("Router.registerRouterAsync (${t.message}):") 81 | t.printStackTrace() 82 | res.statusCode = 500 83 | val t2 = when (t) { 84 | is InvocationTargetException -> t.cause ?: t 85 | else -> t 86 | } 87 | res.end("${t2.message}") 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/es/kotlin/vfs/async/JailVfs.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vfs.async 2 | 3 | class JailVfs(val file: VfsFile) : Vfs.Proxy() { 4 | override fun access(path: String): VfsFile = file[VfsFile.normalize(path)] 5 | 6 | // @TODO: Implement this! 7 | override fun transformStat(stat: VfsStat): VfsStat { 8 | System.err.println("@TODO: Implement JailVfs.transformStat") 9 | return super.transformStat(stat) 10 | } 11 | } -------------------------------------------------------------------------------- /src/es/kotlin/vfs/async/LocalVfs.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vfs.async 2 | 3 | import es.kotlin.async.coroutine.asyncGenerate 4 | import es.kotlin.async.utils.executeInWorker 5 | import java.io.File 6 | 7 | fun LocalVfs(base: File): VfsFile { 8 | class Impl : Vfs() { 9 | suspend override fun readChunk(path: String, offset: Long, size: Long) = super.readChunk(path, offset, size) 10 | suspend override fun writeChunk(path: String, data: ByteArray, offset: Long, resize: Boolean) = super.writeChunk(path, data, offset, resize) 11 | suspend override fun setSize(path: String, size: Long): Unit = super.setSize(path, size) 12 | suspend override fun stat(path: String): VfsStat = super.stat(path) 13 | 14 | suspend override fun list(path: String) = asyncGenerate { 15 | val enumBase = File("${base.absolutePath}/$path").absoluteFile 16 | val enumBaseLength = enumBase.absolutePath.length 17 | val files = executeInWorker { enumBase.listFiles() ?: arrayOf() } 18 | for (file in files) { 19 | yield(VfsStat( 20 | file = VfsFile(this@Impl, path + "/" + file.absolutePath.substring(enumBaseLength + 1)), 21 | exists = file.exists(), 22 | isDirectory = file.isDirectory, 23 | size = file.length() 24 | )) 25 | } 26 | } 27 | 28 | override fun toString(): String = "LocalVfs(${base.absolutePath})" 29 | } 30 | return Impl().root 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/es/kotlin/vfs/async/Vfs.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vfs.async 2 | 3 | import es.kotlin.async.coroutine.AsyncSequence 4 | import es.kotlin.async.coroutine.asyncFun 5 | import es.kotlin.async.coroutine.asyncGenerate 6 | 7 | abstract class Vfs { 8 | val root by lazy { VfsFile(this, "/") } 9 | 10 | suspend open fun readFully(path: String) = asyncFun { 11 | val stat = stat(path) 12 | readChunk(path, 0L, stat.size) 13 | } 14 | 15 | suspend open fun writeFully(path: String, data: ByteArray) = writeChunk(path, data, 0L, true) 16 | 17 | suspend open fun readChunk(path: String, offset: Long, size: Long): ByteArray = throw UnsupportedOperationException() 18 | suspend open fun writeChunk(path: String, data: ByteArray, offset: Long, resize: Boolean): Unit = throw UnsupportedOperationException() 19 | suspend open fun setSize(path: String, size: Long): Unit = throw UnsupportedOperationException() 20 | suspend open fun stat(path: String): VfsStat = throw UnsupportedOperationException() 21 | suspend open fun list(path: String): AsyncSequence = throw UnsupportedOperationException() 22 | 23 | abstract class Proxy : Vfs() { 24 | abstract protected fun access(path: String): VfsFile 25 | open protected fun transformStat(stat: VfsStat): VfsStat = stat 26 | 27 | suspend override fun readFully(path: String): ByteArray = access(path).read() 28 | suspend override fun writeFully(path: String, data: ByteArray): Unit = access(path).write(data) 29 | suspend override fun readChunk(path: String, offset: Long, size: Long): ByteArray = access(path).readChunk(offset, size) 30 | suspend override fun writeChunk(path: String, data: ByteArray, offset: Long, resize: Boolean): Unit = access(path).writeChunk(data, offset, resize) 31 | suspend override fun setSize(path: String, size: Long): Unit = access(path).setSize(size) 32 | suspend override fun stat(path: String): VfsStat = asyncFun { transformStat(access(path).stat()) } 33 | suspend override fun list(path: String) = asyncGenerate { 34 | for (it in access(path).list()) { 35 | yield(transformStat(it)) 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/es/kotlin/vfs/async/VfsFile.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vfs.async 2 | 3 | import es.kotlin.async.coroutine.AsyncSequence 4 | import es.kotlin.async.coroutine.asyncFun 5 | import es.kotlin.async.coroutine.asyncGenerate 6 | import java.nio.charset.Charset 7 | import java.util.* 8 | 9 | // @TODO: Give feedback about this! 10 | // @NOTE: Would be great if I could extend VfsFile inside the AsyncStreamController/Awaitable interface 11 | // so I could provide here a 'stat' method (and the other methods) as an alias for statAsync().await() as an extension instead 12 | // of having to provide it in the controller. 13 | 14 | // Examples: 15 | //suspend fun Awaitable.stat(file: VfsFile, c: Continuation) { // Modifier suspend is not aplicable to top level function 16 | // file.statAsync().then(resolved = { c.resume(it) }, rejected = { c.resumeWithException(it) }) 17 | //} 18 | // 19 | //suspend fun Awaitable::VfsFile.stat(c: Continuation) { 20 | // this.statAsync().then(resolved = { c.resume(it) }, rejected = { c.resumeWithException(it) }) 21 | //} 22 | 23 | class VfsFile( 24 | val vfs: Vfs, 25 | path: String 26 | ) { 27 | val path: String = normalize(path) 28 | 29 | companion object { 30 | fun normalize(path: String): String { 31 | var path2 = path 32 | while (path2.startsWith("/")) path2 = path2.substring(1) 33 | val out = LinkedList() 34 | for (part in path2.split("/")) { 35 | when (part) { 36 | "", "." -> Unit 37 | ".." -> if (out.isNotEmpty()) out.removeLast() 38 | else -> out += part 39 | } 40 | } 41 | return out.joinToString("/") 42 | } 43 | 44 | fun combine(base: String, access: String): String = normalize(base + "/" + access) 45 | } 46 | 47 | operator fun get(path: String): VfsFile = VfsFile(vfs, combine(this.path, path)) 48 | 49 | suspend fun read(): ByteArray = vfs.readFully(path) 50 | suspend fun write(data: ByteArray): Unit = vfs.writeFully(path, data) 51 | 52 | suspend fun readString(charset: Charset = Charsets.UTF_8): String = asyncFun { vfs.readFully(path).toString(charset) } 53 | suspend fun writeString(data: String, charset: Charset = Charsets.UTF_8): Unit = asyncFun { vfs.writeFully(path, data.toByteArray(charset)) } 54 | 55 | suspend fun readChunk(offset: Long, size: Long): ByteArray = vfs.readChunk(path, offset, size) 56 | suspend fun writeChunk(data: ByteArray, offset: Long, resize: Boolean = false): Unit = vfs.writeChunk(path, data, offset, resize) 57 | 58 | suspend fun stat(): VfsStat = vfs.stat(path) 59 | suspend fun size(): Long = asyncFun { vfs.stat(path).size } 60 | suspend fun exists(): Boolean = asyncFun { 61 | try { 62 | vfs.stat(path).exists 63 | } catch (e: Throwable) { 64 | false 65 | } 66 | } 67 | 68 | suspend fun setSize(size: Long): Unit = vfs.setSize(path, size) 69 | 70 | fun jail(): VfsFile = JailVfs(this).root 71 | 72 | suspend fun list(): AsyncSequence = vfs.list(path) 73 | 74 | //fun listRecursive(): AsyncStream = generateAsync { 75 | // @TODO: Report ERROR: java.lang.IllegalAccessError: tried to access field es.kotlin.vfs.async.VfsFile$listRecursive$1.controller from class es.kotlin.vfs.async.VfsFile$listRecursive$1$1 76 | // @TODO: This was a runtime error, if not supported this should be a compile-time error 77 | // 78 | // this@VfsFile.list().eachAsync { 79 | // emit(it) 80 | // } 81 | //} 82 | 83 | suspend fun listRecursive(): AsyncSequence = asyncGenerate { 84 | // @TODO: This is not lazy at all! (at least per directory). Find a way to flatMap lazily this 85 | for (file in list()) { 86 | yield(file) 87 | if (file.isDirectory) { 88 | for (file in file.file.listRecursive()) { 89 | yield(file) 90 | } 91 | } 92 | } 93 | } 94 | 95 | override fun toString(): String = "VfsFile($vfs, $path)" 96 | } -------------------------------------------------------------------------------- /src/es/kotlin/vfs/async/VfsStat.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.vfs.async 2 | 3 | data class VfsStat( 4 | val file: VfsFile, 5 | val exists: Boolean, 6 | val isDirectory: Boolean, 7 | val size: Long 8 | ) -------------------------------------------------------------------------------- /src/example-async-generator.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.async.coroutine.* 3 | import es.kotlin.async.utils.sleep 4 | import es.kotlin.time.seconds 5 | 6 | fun main(args: Array) = EventLoop.mainAsync { 7 | data class User(val name: String, val age: Int) 8 | // Asynchronous Producer 9 | 10 | fun readUsers() = asyncGenerate { 11 | // This could read data results from disk or a socket 12 | for (n in 0 until 4) { 13 | sleep(0.3.seconds) 14 | yield(User(name = "test$n", age = n * 5)) 15 | } 16 | } 17 | 18 | // Consumer 19 | for (user in readUsers()) { 20 | println(user) 21 | } 22 | println("----") 23 | val sumPromise = async { readUsers().map { it.age }.sum() } 24 | val sumGreatOrEqualThan10Promise = async { readUsers().filter { it.age >= 10 }.map { it.age }.sum() } 25 | println("Parallelized:") 26 | println("All ages summed: " + await(sumPromise)) 27 | println("All ages (greater than 10) summed: " + await(sumGreatOrEqualThan10Promise)) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/example-await-async.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.async.coroutine.asyncFun 3 | import es.kotlin.async.utils.downloadUrl 4 | import es.kotlin.async.utils.sleep 5 | import es.kotlin.time.seconds 6 | import java.net.URL 7 | 8 | fun main(args: Array) { 9 | EventLoop.mainAsync { 10 | val secondsToWait = 3 11 | println("Started!") 12 | println("Waiting $secondsToWait seconds...") 13 | for (n in 0 until secondsToWait) { 14 | sleep(1.seconds) 15 | println("One second elapsed!") 16 | } 17 | 18 | println("Downloading url...") 19 | val result = MyNetTasks.downloadGoogleAsString() 20 | println("Downloaded") 21 | println(result) 22 | 23 | try { 24 | sleep(3.seconds) 25 | println("Unexpected!") 26 | } catch (t: Throwable) { 27 | println("Exception catched! : $t") 28 | } finally { 29 | println("Finally!") 30 | } 31 | 32 | try { 33 | println("Downloaded url: ${MyNetTasks.downloadUnexistantUrl()}") 34 | } catch (e: Throwable) { 35 | println("Error downloading url: $e") 36 | } 37 | } 38 | } 39 | 40 | object MyNetTasks { 41 | suspend fun downloadGoogleAsString() = asyncFun { 42 | downloadUrl(URL("http://google.com/")) 43 | } 44 | 45 | suspend fun downloadUnexistantUrl() = asyncFun { 46 | downloadUrl(URL("http://google.com/adasda/asdasd")) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/example-chat-server.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.async.coroutine.asyncFun 3 | import es.kotlin.lang.tryInt 4 | import es.kotlin.net.async.AsyncClient 5 | import es.kotlin.net.async.AsyncServer 6 | import es.kotlin.net.async.readLine 7 | 8 | // Multi-client telnet chat server using AsyncStream for listening clients 9 | object ChatServer { 10 | lateinit var server: AsyncServer 11 | 12 | val charset = Charsets.UTF_8 13 | var lastClientId = 0L 14 | 15 | val clients = hashMapOf() 16 | val EMPTY_SET = setOf() 17 | 18 | suspend fun broadcast(message: ByteArray, exclude: Set = EMPTY_SET) = asyncFun { 19 | for (client in clients.values) { 20 | if (client in exclude) continue 21 | client.write(message) 22 | //client.writeAsync(message) // Do not wait! 23 | } 24 | } 25 | 26 | suspend fun broadcastLine(line: String, exclude: Set = EMPTY_SET) = asyncFun { 27 | broadcast("$line\r\n".toByteArray(charset), exclude = exclude) 28 | } 29 | 30 | @JvmStatic fun main(args: Array) = EventLoop.mainAsync { 31 | val port = args.getOrNull(0)?.tryInt() ?: 9090 32 | println("Preparing telnet chat server...") 33 | 34 | server = AsyncServer(port) 35 | println("Listening at $port") 36 | 37 | for (me in server.listen()) { 38 | val id = lastClientId++ 39 | clients[id] = me 40 | val meSet = setOf(me) 41 | println("Client#$id connected!") 42 | 43 | me.write("You are connected as Client#$id\r\n".toByteArray(charset)) 44 | broadcastLine("Client#$id connected!", exclude = meSet) 45 | 46 | try { 47 | while (true) { 48 | val line = me.readLine(charset) 49 | println("Client#$id said: $line") 50 | broadcastLine("Client#$id said: $line", exclude = meSet) 51 | } 52 | } catch (t: Throwable) { 53 | t.printStackTrace() 54 | } finally { 55 | clients.remove(id) 56 | println("Client#$id disconnected!") 57 | broadcastLine("Client#$id disconnected!", exclude = meSet) 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/example-echo-server.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.lang.tryInt 3 | import es.kotlin.net.async.AsyncServer 4 | import es.kotlin.net.async.readLine 5 | 6 | // Multi-client echo server using AsyncStream for listening clients 7 | fun main(args: Array) = EventLoop.mainAsync { 8 | val port = args.getOrNull(0)?.tryInt() ?: 9090 9 | val server = AsyncServer(port) 10 | println("Listening at $port") 11 | 12 | println("Preparing...") 13 | val charset = Charsets.UTF_8 14 | var lastClientId = 0 15 | for (client in server.listen()) { 16 | val id = lastClientId++ 17 | println("Client#$id connected!") 18 | try { 19 | while (true) { 20 | val line = client.readLine(charset) 21 | println("Client#$id said: $line") 22 | client.write(line.toByteArray(charset)) 23 | } 24 | } catch (t: Throwable) { 25 | t.printStackTrace() 26 | } finally { 27 | println("Client#$id disconnected!") 28 | } 29 | } 30 | println("Listening at $port") 31 | } 32 | -------------------------------------------------------------------------------- /src/example-generator.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.collection.coroutine.generate 2 | import es.kotlin.collection.lazy.lazyFilter 3 | import es.kotlin.collection.lazy.lazyMap 4 | 5 | fun main(args: Array) { 6 | //val infiniteList = generate { for (n in 0 .. 3) yield(n) } 7 | val infiniteList = generate { 8 | var n = 0 9 | while (true) yield(n++) 10 | } 11 | 12 | for (i in infiniteList.lazyFilter { it % 2 == 0 }.lazyMap { -it }) { 13 | //for (i in infiniteList) { 14 | println(i) 15 | if (i < -10000000) break 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/example-redis.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.db.async.redis.RedisClient 3 | import es.kotlin.db.async.redis.RedisVfs 4 | import es.kotlin.db.async.redis.get 5 | import es.kotlin.db.async.redis.set 6 | 7 | fun main(args: Array) = EventLoop.mainAsync { 8 | val redis = RedisClient("127.0.0.1", 6379) 9 | redis["test123"] = "from kotlin!" 10 | println("done!") 11 | println(redis["test123"]) 12 | 13 | val vfs = RedisVfs(redis) 14 | 15 | println("vfs:" + vfs["test123"].readString()) 16 | } -------------------------------------------------------------------------------- /src/example-tic-tac-toe-async-with-implicit-state-machine.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.async.coroutine.* 3 | import es.kotlin.collection.coroutine.generate 4 | import es.kotlin.net.async.AsyncClient 5 | import es.kotlin.net.async.AsyncServer 6 | import es.kotlin.net.async.readLine 7 | import es.kotlin.random.get 8 | import es.kotlin.time.TimeSpan 9 | import es.kotlin.time.seconds 10 | import java.nio.charset.Charset 11 | import java.util.* 12 | import java.util.concurrent.TimeoutException 13 | 14 | // Example showing how to create a tic-tac-toe server without a explicit state machine 15 | // Using await-async coroutine as it's state as an implicit (and cool) state machine 16 | fun main(args: Array) = EventLoop.mainAsync { 17 | TicTacToe(moveTimeout = 10.seconds).server() 18 | } 19 | 20 | suspend fun AsyncClient.println(line: String, charset: Charset = Charsets.UTF_8) = this.write("$line\r\n".toByteArray(charset)) 21 | suspend fun Iterable.println(line: String, charset: Charset = Charsets.UTF_8) = asyncFun { for (s in this) s.println(line, charset) } 22 | 23 | class TicTacToe(val moveTimeout: TimeSpan = 1.seconds, val port: Int = 9090) { 24 | val server = AsyncServer(port) 25 | 26 | lateinit var connections: AsyncIterator 27 | 28 | suspend fun readConnection() = process { 29 | val player = connections.next() 30 | player.println("Joined a tic-tac-toe game! Write row and column to place a chip: 00 - 22") 31 | player 32 | } 33 | 34 | suspend fun server() = process { 35 | connections = server.listen().iterator() 36 | 37 | println("Listening at ... $port") 38 | 39 | // Match-making 40 | while (true) { 41 | try { 42 | val player1 = readConnection() 43 | player1.println("Waiting for other player to start!") 44 | val player2 = readConnection() 45 | async { 46 | // no wait so we continue creating matches while game is running! 47 | val result = Match(moveTimeout, player1, player2) 48 | println("Match resolved with $result") 49 | } 50 | } catch (t: Throwable) { 51 | println("Match-making failed! Start over!") 52 | t.printStackTrace() 53 | } 54 | } 55 | } 56 | 57 | class Match private constructor(val moveTimeout: TimeSpan, val player1: AsyncClient, val player2: AsyncClient, val dummy: Boolean) { 58 | // Trick! 59 | companion object { 60 | operator suspend fun invoke(moveTimeout: TimeSpan, player1: AsyncClient, player2: AsyncClient) = Match(moveTimeout, player1, player2, true).match() 61 | } 62 | 63 | class PlayerInfo(val id: Int, val chip: Char) 64 | 65 | var turn = 0 66 | val info = hashMapOf(player1 to PlayerInfo(id = 0, chip = 'X'), player2 to PlayerInfo(id = 1, chip = 'O')) 67 | val players = listOf(player1, player2) 68 | fun AsyncClient.info(): PlayerInfo = info[this]!! 69 | 70 | val board = Board() 71 | 72 | suspend private fun readMove(currentPlayer: AsyncClient) = process { 73 | var pos: Point 74 | selectmove@ while (true) { 75 | val line = withTimeout(moveTimeout) { currentPlayer.readLine(this@withTimeout) } 76 | try { 77 | val x = ("" + line[0]).toInt() 78 | val y = ("" + line[1]).toInt() 79 | pos = Point(x, y) 80 | if (board.hasChipAt(pos)) { 81 | currentPlayer.println("Already has a chip: $pos") 82 | continue 83 | } else { 84 | break@selectmove 85 | } 86 | } catch (e: Throwable) { 87 | currentPlayer.println("ERROR: ${e.javaClass}: ${e.message}") 88 | e.printStackTrace() 89 | } 90 | } 91 | pos 92 | } 93 | 94 | suspend fun match() = process { 95 | players.println("tic-tac-toe: Game started!") 96 | 97 | var result: GameResult 98 | 99 | ingame@ while (true) { 100 | players.println("Turn: $turn") 101 | board.sendBoardTo(players) 102 | val currentPlayer = players[turn % players.size] 103 | currentPlayer.println("Your turn! You have $moveTimeout to move, or a move will perform automatically!") 104 | 105 | val pos = try { 106 | readMove(currentPlayer) 107 | } catch (e: TimeoutException) { 108 | board.getOneAvailablePositions() 109 | } 110 | 111 | currentPlayer.println("Placed at $pos!") 112 | board[pos] = currentPlayer.info().chip 113 | 114 | result = board.checkResult() 115 | when (result) { 116 | is GameResult.Playing -> { 117 | turn++ 118 | continue@ingame 119 | } 120 | is GameResult.Draw, is GameResult.Win -> { 121 | board.sendBoardTo(players) 122 | players.println("$result") 123 | break@ingame 124 | } 125 | } 126 | } 127 | 128 | players.println("End of game!") 129 | for (player in players) { 130 | try { 131 | player.close() 132 | } catch (t: Throwable) { 133 | 134 | } 135 | } 136 | 137 | result 138 | } 139 | } 140 | 141 | data class Point(val x: Int, val y: Int) 142 | 143 | class Board { 144 | val random = Random() 145 | 146 | val data = listOf( 147 | arrayListOf(' ', ' ', ' '), 148 | arrayListOf(' ', ' ', ' '), 149 | arrayListOf(' ', ' ', ' ') 150 | ) 151 | 152 | suspend fun sendBoardTo(players: Iterable) = process { 153 | players.println("---+---+---") 154 | for (row in data) { 155 | players.println(" " + row.joinToString(" | ")) 156 | players.println("---+---+---") 157 | } 158 | } 159 | 160 | operator fun set(x: Int, y: Int, v: Char) = run { data[y][x] = v } 161 | operator fun get(x: Int, y: Int) = data[y][x] 162 | fun hasChipAt(x: Int, y: Int) = this[x, y] != ' ' 163 | 164 | operator fun get(p: Point) = this[p.x, p.y] 165 | operator fun set(p: Point, v: Char) = run { this[p.x, p.y] = v } 166 | fun hasChipAt(p: Point) = hasChipAt(p.x, p.y) 167 | 168 | fun getAllPositions() = generate { 169 | for (y in 0 until 3) for (x in 0 until 3) yield(Point(x, y)) 170 | } 171 | 172 | fun getAvailablePositions() = generate { 173 | for (p in getAllPositions()) if (!hasChipAt(p)) yield(p) 174 | } 175 | 176 | fun getOneAvailablePositions(): Point = getAvailablePositions().toList()[random] 177 | 178 | fun getRow(y: Int) = (0 until 3).map { x -> Point(x, y) } 179 | fun getCol(x: Int) = (0 until 3).map { y -> Point(x, y) } 180 | fun getDiagonal1() = (0 until 3).map { n -> Point(n, n) } 181 | fun getDiagonal2() = (0 until 3).map { n -> Point(2 - n, n) } 182 | 183 | fun List.getType(): Char = if (this.all { it == this[0] }) this[0] else ' ' 184 | 185 | fun checkResult(): GameResult { 186 | val pointsList = generate { 187 | for (n in 0 until 3) { 188 | yield(getRow(n)) 189 | yield(getCol(n)) 190 | } 191 | yield(getDiagonal1()) 192 | yield(getDiagonal2()) 193 | } 194 | 195 | for (points in pointsList) { 196 | val type = points.map { this[it] }.getType() 197 | if (type != ' ') { 198 | return GameResult.Win(type, points) 199 | } 200 | } 201 | 202 | return if (getAllPositions().map { this[it] }.all { it != ' ' }) { 203 | GameResult.Draw 204 | } else { 205 | GameResult.Playing 206 | } 207 | } 208 | } 209 | 210 | sealed class GameResult { 211 | object Playing : GameResult() 212 | object Draw : GameResult() 213 | data class Win(val type: Char, val positions: List) : GameResult() 214 | } 215 | 216 | operator fun Iterable.get(random: Random): T { 217 | val list = this.toList() 218 | return list[random.nextInt(list.size)] 219 | } 220 | } 221 | 222 | -------------------------------------------------------------------------------- /src/example-vertx-router.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.coroutine.async 2 | import es.kotlin.async.coroutine.asyncFun 3 | import es.kotlin.async.utils.sleep 4 | import es.kotlin.di.AsyncInjector 5 | import es.kotlin.time.seconds 6 | import es.kotlin.vertx.redis.* 7 | import es.kotlin.vertx.route.Param 8 | import es.kotlin.vertx.route.Route 9 | import es.kotlin.vertx.route.registerRouter 10 | import es.kotlin.vertx.routedHttpServerAsync 11 | import es.kotlin.vertx.vertx 12 | import io.vertx.core.http.HttpMethod 13 | import io.vertx.redis.RedisClient 14 | import io.vertx.redis.RedisOptions 15 | 16 | object VertxExample { 17 | @JvmStatic fun main(args: Array) { 18 | val redisNode = System.getenv("REDIS_CLUSTER") ?: "127.0.0.1" 19 | 20 | //for ((env, value) in System.getenv()) println("ENV:$env:$value") 21 | 22 | println("redisNode: $redisNode") 23 | 24 | val injector = AsyncInjector() 25 | .map(vertx) 26 | .map(vertx.fileSystem()) 27 | .map(RedisClient.create(vertx, RedisOptions().setHost(redisNode))) 28 | 29 | routedHttpServerAsync { 30 | async { 31 | println("Registering routes...") 32 | registerRouter(injector) 33 | println("Ok") 34 | 35 | // Static handler! 36 | //route("/assets/*").handler(StaticHandler.create("assets")); 37 | } 38 | } 39 | } 40 | } 41 | 42 | class SimpleRoute( 43 | val redis: RedisClient 44 | ) { 45 | val ranking = redis["ranking"] 46 | 47 | @Route(HttpMethod.GET, "/") 48 | suspend fun root() = asyncFun { 49 | sleep(0.5.seconds) 50 | "HELLO after 0.5 seconds without blocking!" 51 | } 52 | 53 | @Route(HttpMethod.GET, "/top") 54 | suspend fun top() = asyncFun { 55 | ranking.zrevrange(0L, 10L) 56 | } 57 | 58 | @Route(HttpMethod.GET, "/incr/:user/:value") 59 | suspend fun incr(@Param("user") user: String, @Param("value") value: Long) = asyncFun { 60 | ranking.zincrby(user, value.toDouble()) 61 | } 62 | 63 | @Route(HttpMethod.GET, "/rank/:user") 64 | suspend fun rank(@Param("user") user: String) = asyncFun { 65 | ranking.zrevrank(user) 66 | } 67 | } -------------------------------------------------------------------------------- /src/example-vfs.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.EventLoop 2 | import es.kotlin.vfs.async.LocalVfs 3 | import java.io.File 4 | 5 | fun main(args: Array) = EventLoop.mainAsync { 6 | val vfs = LocalVfs(File(".")) 7 | for (it in vfs.listRecursive()) { // lazy and asynchronous! 8 | it.file.read() 9 | } 10 | } -------------------------------------------------------------------------------- /test/TicTacToeTest.kt: -------------------------------------------------------------------------------- 1 | import es.kotlin.async.coroutine.AsyncIterator 2 | import es.kotlin.async.coroutine.async 3 | import es.kotlin.async.coroutine.asyncGenerate 4 | import es.kotlin.async.coroutine.sync 5 | import es.kotlin.async.utils.sleep 6 | import es.kotlin.collection.coroutine.generate 7 | import es.kotlin.net.async.AsyncClient 8 | import es.kotlin.time.seconds 9 | import org.junit.Test 10 | 11 | class TicTacToeTest { 12 | @Test 13 | fun name() { 14 | sync { 15 | val port = 9091 16 | async { TicTacToe(moveTimeout = 0.1.seconds, port = port).server() } 17 | async { AsyncClient("127.0.0.1", port) } 18 | async { AsyncClient("127.0.0.1", port) } 19 | sleep(10.seconds) 20 | } 21 | } 22 | 23 | /* 24 | class X { 25 | fun s(): AsyncIterator = asyncGenerate { t().next() }.iterator() 26 | fun t(): AsyncIterator = asyncGenerate { s().next() }.iterator() 27 | } 28 | 29 | @Test 30 | fun asyncGenerateBug() { 31 | sync { X().s().next() } 32 | } 33 | */ 34 | 35 | //class X { 36 | // fun s(): Iterator = generate { t().next() }.iterator() 37 | // fun t(): Iterator = generate { s().next() }.iterator() 38 | //} 39 | // 40 | //@Test 41 | //fun syncGenerateBug() { 42 | // sync { X().s().next() } 43 | //} 44 | } -------------------------------------------------------------------------------- /test/es/kotlin/collection/coroutine/generateTest.kt: -------------------------------------------------------------------------------- 1 | package es.kotlin.collection.coroutine 2 | 3 | import es.kotlin.async.EventLoop 4 | import es.kotlin.async.utils.sleep 5 | import es.kotlin.time.seconds 6 | import org.junit.Assert 7 | import org.junit.Assert.assertEquals 8 | import org.junit.Test 9 | 10 | class GenerateTest { 11 | @Test 12 | fun testGenerateNumbers() { 13 | fun gen() = generate { for (n in 3..7) yield(n) } 14 | assertEquals("3,4,5,6,7", gen().joinToString(",")) 15 | } 16 | 17 | @Test 18 | fun testGenerateNulls() { 19 | fun gen() = generate { 20 | for (n in 3..7) { 21 | yield(n); 22 | yield(null) 23 | } 24 | } 25 | assertEquals("3,null,4,null,5,null,6,null,7,null", gen().joinToString(",")) 26 | } 27 | 28 | @Test 29 | fun testAsyncAwait1() { 30 | var out = "" 31 | EventLoop.mainAsync { 32 | out += "a" 33 | sleep(0.1.seconds) 34 | out += "b" 35 | } 36 | Assert.assertEquals("ab", out) 37 | } 38 | 39 | @Test 40 | fun testAsyncAwait2() { 41 | var out = "" 42 | EventLoop.mainAsync { 43 | out += "a" 44 | sleep(0.1.seconds) 45 | out += "b" 46 | Unit 47 | } 48 | Assert.assertEquals("ab", out) 49 | } 50 | } --------------------------------------------------------------------------------