Background HTTP fetcher and JSON parser example. (#2177)

This commit is contained in:
Nikolay Igotti
2018-10-08 08:07:54 +03:00
committed by GitHub
parent 10b05b6229
commit e98ef837b7
+92 -25
View File
@@ -1,9 +1,27 @@
import kotlin.native.concurrent.*
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
import kotlinx.cinterop.*
import platform.AppKit.*
import platform.Foundation.*
import platform.darwin.*
import platform.posix.*
import platform.darwin.NSObject
import platform.darwin.dispatch_async_f
import kotlin.native.concurrent.DetachedObjectGraph
import kotlin.native.concurrent.attach
import kotlin.native.concurrent.freeze
inline fun <reified T> executeAsync(queue: NSOperationQueue, crossinline producerConsumer: () -> Pair<T, (T) -> Unit>) {
dispatch_async_f(queue.underlyingQueue, DetachedObjectGraph {
producerConsumer()
}.asCPointer(), staticCFunction { it ->
val result = DetachedObjectGraph<Pair<T, (T) -> Unit>>(it).attach()
result.second(result.first)
})
}
data class QueryResult(val json: Map<String, *>?, val error: String?)
fun main(args: Array<String>) {
autoreleasepool {
@@ -11,53 +29,97 @@ fun main(args: Array<String>) {
}
}
val appDelegate = MyAppDelegate()
private fun runApp() {
val app = NSApplication.sharedApplication()
app.delegate = MyAppDelegate()
app.delegate = appDelegate
app.setActivationPolicy(NSApplicationActivationPolicy.NSApplicationActivationPolicyRegular)
app.activateIgnoringOtherApps(true)
app.run()
}
data class Data(val stamp: ULong)
private class Controller : NSObject() {
private val asyncQueue = dispatch_queue_create("com.jetbrains.CustomQueue", null)
class Controller : NSObject() {
private var index = 1
private var httpDelegate = HttpDelegate()
@ObjCAction
fun onClick() {
// Execute some async action on button click.
dispatch_async_f(asyncQueue, DetachedObjectGraph {
Data(clock_gettime_nsec_np(CLOCK_REALTIME))
}.asCPointer(), staticCFunction {
it ->
initRuntimeIfNeeded()
val data = DetachedObjectGraph<Data>(it).attach()
println("in async: $data")
})
if (!appDelegate.canClick) {
appDelegate.contentText.string = "Another load in progress..."
return
}
appDelegate.canClick = false
// Fetch URL in the background on the button click.
httpDelegate.fetchUrl("https://jsonplaceholder.typicode.com/todos/${index++}")
}
@ObjCAction
fun onQuit() {
NSApplication.sharedApplication().stop(this)
}
class HttpDelegate: NSObject(), NSURLSessionDataDelegateProtocol {
private val asyncQueue = NSOperationQueue()
internal val receivedData = NSMutableData()
init {
freeze()
}
fun fetchUrl(url: String) {
receivedData.setLength(0)
val session = NSURLSession.sessionWithConfiguration(
NSURLSessionConfiguration.defaultSessionConfiguration(),
this,
delegateQueue = asyncQueue
)
session.dataTaskWithURL(NSURL(string = url)).resume()
}
override fun URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData: NSData) {
initRuntimeIfNeeded()
receivedData.appendData(didReceiveData)
}
override fun URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError: NSError?) {
initRuntimeIfNeeded()
executeAsync(NSOperationQueue.mainQueue) {
val response = task.response as? NSHTTPURLResponse
Pair(when {
response == null -> QueryResult(null, didCompleteWithError?.localizedDescription)
response.statusCode.toInt() != 200 -> QueryResult(null, "${response.statusCode.toInt()})")
else -> QueryResult(
NSJSONSerialization.JSONObjectWithData(receivedData, 0, null) as? Map<String, *>,
null
)
}, { result: QueryResult ->
appDelegate.contentText.string = result.json?.toString() ?: "Error: ${result.error}"
appDelegate.canClick = true
})
}
}
}
}
private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
private val window: NSWindow
private val controller = Controller()
val contentText: NSText
var canClick = true
init {
val mainDisplayRect = NSScreen.mainScreen()!!.frame
val windowRect = mainDisplayRect.useContents {
NSMakeRect(
origin.x + size.width * 0.25,
origin.y + size.height * 0.25,
size.width * 0.5,
size.height * 0.5
origin.x + size.width * 0.25,
origin.y + size.height * 0.25,
size.width * 0.5,
size.height * 0.5
)
}
@@ -66,7 +128,7 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
NSWindowStyleMaskClosable or NSWindowStyleMaskResizable
window = NSWindow(windowRect, windowStyle, NSBackingStoreBuffered, false).apply {
title = "Окошко Konan"
title = "URL async fetcher"
opaque = true
hasShadow = true
preferredBackingLocation = NSWindowBackingLocationVideoMemory
@@ -83,7 +145,7 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
}
val buttonPress = NSButton(NSMakeRect(10.0, 10.0, 100.0, 40.0)).apply {
title = "Press me"
title = "Click"
target = controller
action = NSSelectorFromString("onClick")
}
@@ -94,11 +156,16 @@ private class MyAppDelegate() : NSObject(), NSApplicationDelegateProtocol {
action = NSSelectorFromString("onQuit")
}
window.contentView!!.addSubview(buttonQuit)
contentText = NSText(NSMakeRect(10.0, 80.0, 600.0, 350.0)).apply {
string = "Press 'Click' to start fetching"
verticallyResizable = false
horizontallyResizable = false
}
window.contentView!!.addSubview(contentText)
}
override fun applicationWillFinishLaunching(notification: NSNotification) {
window.makeKeyAndOrderFront(this)
}
}