Connect timeout exception handling #16

Merged
ArystanK merged 3 commits from connect_timeout_exception_handling into main 2022-08-26 14:23:51 +03:00
3 changed files with 42 additions and 41 deletions
Showing only changes of commit 3467a6dbe0 - Show all commits

View File

@ -13,7 +13,7 @@ public data class TileId(
public data class MapTile( public data class MapTile(
val id: TileId, val id: TileId,
val image: ImageBitmap?, val image: ImageBitmap,
) )
public interface MapTileProvider { public interface MapTileProvider {

View File

@ -195,20 +195,21 @@ public actual fun MapView(
for (j in verticalIndices) { for (j in verticalIndices) {
for (i in horizontalIndices) { for (i in horizontalIndices) {
val id = TileId(zoom, i, j) val id = TileId(zoom, i, j)
//start all try {
val deferred = loadTileAsync(id) //start all
//wait asynchronously for it to finish val deferred = loadTileAsync(id)
launch { //wait asynchronously for it to finish
try { launch {
mapTiles += deferred.await() mapTiles += deferred.await()
} catch (ex: Exception) { }
if (ex !is CancellationException) { } catch (ex: Exception) {
//displaying the error is maps responsibility if (ex !is CancellationException) {
logger.error(ex) { "Failed to load tile with id=$id" } //displaying the error is maps responsibility
} logger.error(ex) { "Failed to load tile with id=$id" }
} }
} }
} }
} }
} }
} }
@ -232,6 +233,7 @@ public actual fun MapView(
feature.size, feature.size,
center = feature.center.toOffset() center = feature.center.toOffset()
) )
is MapRectangleFeature -> drawRect( is MapRectangleFeature -> drawRect(
feature.color, feature.color,
topLeft = feature.center.toOffset() - Offset( topLeft = feature.center.toOffset() - Offset(
@ -240,6 +242,7 @@ public actual fun MapView(
), ),
size = feature.size.toSize() size = feature.size.toSize()
) )
is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset()) is MapLineFeature -> drawLine(feature.color, feature.a.toOffset(), feature.b.toOffset())
is MapArcFeature -> { is MapArcFeature -> {
val topLeft = feature.oval.topLeft.toOffset() val topLeft = feature.oval.topLeft.toOffset()
@ -252,6 +255,7 @@ public actual fun MapView(
drawPath(path, color = feature.color, style = Stroke()) drawPath(path, color = feature.color, style = Stroke())
} }
is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset()) is MapBitmapImageFeature -> drawImage(feature.image, feature.position.toOffset())
is MapVectorImageFeature -> { is MapVectorImageFeature -> {
val offset = feature.position.toOffset() val offset = feature.position.toOffset()
@ -262,6 +266,7 @@ public actual fun MapView(
} }
} }
} }
is MapTextFeature -> drawIntoCanvas { canvas -> is MapTextFeature -> drawIntoCanvas { canvas ->
val offset = feature.position.toOffset() val offset = feature.position.toOffset()
canvas.nativeCanvas.drawString( canvas.nativeCanvas.drawString(
@ -272,17 +277,20 @@ public actual fun MapView(
feature.color.toPaint() feature.color.toPaint()
) )
} }
is MapDrawFeature -> { is MapDrawFeature -> {
val offset = feature.position.toOffset() val offset = feature.position.toOffset()
translate(offset.x, offset.y) { translate(offset.x, offset.y) {
feature.drawFeature(this) feature.drawFeature(this)
} }
} }
is MapFeatureGroup -> { is MapFeatureGroup -> {
feature.children.values.forEach { feature.children.values.forEach {
drawFeature(zoom, it) drawFeature(zoom, it)
} }
} }
is MapPointsFeature -> { is MapPointsFeature -> {
val points = feature.points.map { it.toOffset() } val points = feature.points.map { it.toOffset() }
drawPoints( drawPoints(
@ -292,6 +300,7 @@ public actual fun MapView(
pointMode = feature.pointMode pointMode = feature.pointMode
) )
} }
else -> { else -> {
logger.error { "Unrecognized feature type: ${feature::class}" } logger.error { "Unrecognized feature type: ${feature::class}" }
} }
@ -313,13 +322,11 @@ public actual fun MapView(
(canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale.toFloat()).roundToPx(), (canvasSize.width / 2 + (mapTileProvider.toCoordinate(id.i).dp - centerCoordinates.x.dp) * tileScale.toFloat()).roundToPx(),
(canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx() (canvasSize.height / 2 + (mapTileProvider.toCoordinate(id.j).dp - centerCoordinates.y.dp) * tileScale.toFloat()).roundToPx()
) )
image?.let { drawImage(
drawImage( image = image,
image = it, dstOffset = offset,
dstOffset = offset, dstSize = tileSize
dstSize = tileSize )
)
}
} }
features.values.filter { zoom in it.zoomRange }.forEach { feature -> features.values.filter { zoom in it.zoomRange }.forEach { feature ->
drawFeature(zoom, feature) drawFeature(zoom, feature)

View File

@ -2,11 +2,10 @@ package center.sciprog.maps.compose
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.network.sockets.* import io.ktor.client.request.get
import io.ktor.client.request.* import io.ktor.client.statement.readBytes
import io.ktor.client.statement.* import io.ktor.utils.io.CancellationException
import io.ktor.utils.io.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -25,11 +24,11 @@ import kotlin.io.path.*
public class OpenStreetMapTileProvider( public class OpenStreetMapTileProvider(
private val client: HttpClient, private val client: HttpClient,
private val cacheDirectory: Path, private val cacheDirectory: Path,
parallelism: Int = 1, parallelism: Int = 4,
cacheCapacity: Int = 200, cacheCapacity: Int = 200,
) : MapTileProvider { ) : MapTileProvider {
private val semaphore = Semaphore(parallelism) private val semaphore = Semaphore(parallelism)
private val cache = LruCache<TileId, Deferred<ImageBitmap?>>(cacheCapacity) private val cache = LruCache<TileId, Deferred<ImageBitmap>>(cacheCapacity)
private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png") private fun TileId.osmUrl() = URL("https://tile.openstreetmap.org/${zoom}/${i}/${j}.png")
@ -38,7 +37,7 @@ public class OpenStreetMapTileProvider(
/** /**
* Download and cache the tile image * Download and cache the tile image
*/ */
private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<ImageBitmap?> = async(Dispatchers.IO) { private fun CoroutineScope.downloadImageAsync(id: TileId): Deferred<ImageBitmap> = async(Dispatchers.IO) {
id.cacheFilePath()?.let { path -> id.cacheFilePath()?.let { path ->
if (path.exists()) { if (path.exists()) {
@ -53,22 +52,17 @@ public class OpenStreetMapTileProvider(
//semaphore works only for actual download //semaphore works only for actual download
semaphore.withPermit { semaphore.withPermit {
try { val url = id.osmUrl()
val url = id.osmUrl() val byteArray = client.get(url).readBytes()
val byteArray = client.get(url).readBytes() logger.debug { "Finished downloading map tile with id $id from $url" }
logger.debug { "Finished downloading map tile with id $id from $url" } id.cacheFilePath()?.let { path ->
id.cacheFilePath()?.let { path -> logger.debug { "Caching map tile $id to $path" }
logger.debug { "Caching map tile $id to $path" }
path.parent.createDirectories() path.parent.createDirectories()
path.writeBytes(byteArray) path.writeBytes(byteArray)
}
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
} catch (e: ConnectTimeoutException) {
logger.error(e) { e.localizedMessage }
null
} }
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
} }
} }
@ -83,7 +77,7 @@ public class OpenStreetMapTileProvider(
//collect the result asynchronously //collect the result asynchronously
return async { return async {
val image = try { val image: ImageBitmap = try {
imageDeferred.await() imageDeferred.await()
} catch (ex: Exception) { } catch (ex: Exception) {
cache.remove(tileId) cache.remove(tileId)