1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

update Hilt to 2.37, inject HttpClient in FosdemApi, inject Call.Factory in HttpClient

This commit is contained in:
Christophe Beyls 2021-06-21 02:33:59 +02:00
parent cf41d38547
commit 71b1bcaf37
5 changed files with 88 additions and 61 deletions

View file

@ -18,8 +18,7 @@ import be.digitalia.fosdem.parsers.EventsParser
import be.digitalia.fosdem.parsers.RoomStatusesParser import be.digitalia.fosdem.parsers.RoomStatusesParser
import be.digitalia.fosdem.utils.BackgroundWorkScope import be.digitalia.fosdem.utils.BackgroundWorkScope
import be.digitalia.fosdem.utils.ByteCountSource import be.digitalia.fosdem.utils.ByteCountSource
import be.digitalia.fosdem.utils.network.HttpUtils import be.digitalia.fosdem.utils.network.HttpClient
import be.digitalia.fosdem.utils.network.HttpUtils.lastModified
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -36,6 +35,7 @@ import kotlin.math.pow
*/ */
@Singleton @Singleton
class FosdemApi @Inject constructor( class FosdemApi @Inject constructor(
private val httpClient: HttpClient,
private val scheduleDao: ScheduleDao, private val scheduleDao: ScheduleDao,
private val alarmManager: FosdemAlarmManager private val alarmManager: FosdemAlarmManager
) { ) {
@ -50,21 +50,19 @@ class FosdemApi @Inject constructor(
@MainThread @MainThread
fun downloadSchedule(): Job { fun downloadSchedule(): Job {
// Returns the download job in progress, if any // Returns the download job in progress, if any
return downloadJob ?: run { return downloadJob ?: BackgroundWorkScope.launch {
BackgroundWorkScope.launch {
downloadScheduleInternal() downloadScheduleInternal()
downloadJob = null downloadJob = null
}.also { }.also {
downloadJob = it downloadJob = it
} }
} }
}
@MainThread @MainThread
private suspend fun downloadScheduleInternal() { private suspend fun downloadScheduleInternal() {
_downloadScheduleState.value = LoadingState.Loading() _downloadScheduleState.value = LoadingState.Loading()
val res = try { val res = try {
val response = HttpUtils.get(FosdemUrls.schedule, scheduleDao.lastModifiedTag) { body, rawResponse -> val response = httpClient.get(FosdemUrls.schedule, scheduleDao.lastModifiedTag) { body, headers ->
val length = body.contentLength() val length = body.contentLength()
val source = if (length > 0L) { val source = if (length > 0L) {
// Broadcast the progression in percents, with a precision of 1/10 of the total file size // Broadcast the progression in percents, with a precision of 1/10 of the total file size
@ -78,11 +76,11 @@ class FosdemApi @Inject constructor(
} }
val events = EventsParser().parse(source) val events = EventsParser().parse(source)
scheduleDao.storeSchedule(events, rawResponse.lastModified) scheduleDao.storeSchedule(events, headers.get(HttpClient.LAST_MODIFIED_HEADER_NAME))
} }
when (response) { when (response) {
is HttpUtils.Response.NotModified -> DownloadScheduleResult.UpToDate // Nothing parsed, the result is up-to-date is HttpClient.Response.NotModified -> DownloadScheduleResult.UpToDate // Nothing parsed, the result is up-to-date
is HttpUtils.Response.Success -> { is HttpClient.Response.Success -> {
alarmManager.onScheduleRefreshed() alarmManager.onScheduleRefreshed()
DownloadScheduleResult.Success(response.body) DownloadScheduleResult.Success(response.body)
} }
@ -139,7 +137,7 @@ class FosdemApi @Inject constructor(
} }
nextRefreshDelay = try { nextRefreshDelay = try {
val response = HttpUtils.get(FosdemUrls.rooms) { body, _ -> val response = httpClient.get(FosdemUrls.rooms) { body, _ ->
RoomStatusesParser().parse(body.source()) RoomStatusesParser().parse(body.source())
} }
now = SystemClock.elapsedRealtime() now = SystemClock.elapsedRealtime()

View file

@ -30,13 +30,13 @@ object DatabaseModule {
@Provides @Provides
@Named("Database") @Named("Database")
fun providesSharedPreferences(@ApplicationContext context: Context): SharedPreferences { fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
return context.applicationContext.getSharedPreferences(DB_PREFS_FILE, Context.MODE_PRIVATE) return context.applicationContext.getSharedPreferences(DB_PREFS_FILE, Context.MODE_PRIVATE)
} }
@Provides @Provides
@Singleton @Singleton
fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase { fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
val MIGRATION_1_2 = object : Migration(1, 2) { val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) = with(database) { override fun migrate(database: SupportSQLiteDatabase) = with(database) {
// Events: make primary key and track_id not null // Events: make primary key and track_id not null
@ -80,8 +80,8 @@ object DatabaseModule {
} }
@Provides @Provides
fun providesScheduleDao(appDatabase: AppDatabase): ScheduleDao = appDatabase.scheduleDao fun provideScheduleDao(appDatabase: AppDatabase): ScheduleDao = appDatabase.scheduleDao
@Provides @Provides
fun providesBookmarksDao(appDatabase: AppDatabase): BookmarksDao = appDatabase.bookmarksDao fun provideBookmarksDao(appDatabase: AppDatabase): BookmarksDao = appDatabase.bookmarksDao
} }

View file

@ -0,0 +1,56 @@
package be.digitalia.fosdem.inject
import android.os.Build
import be.digitalia.fosdem.utils.BackgroundWorkScope
import be.digitalia.fosdem.utils.network.Tls12SocketFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.tls.HandshakeCertificates
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private const val DEFAULT_CONNECT_TIMEOUT = 10L
private const val DEFAULT_READ_TIMEOUT = 10L
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.enableTls12()
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.build()
}
private fun OkHttpClient.Builder.enableTls12(): OkHttpClient.Builder {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
val clientCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.build()
sslSocketFactory(
Tls12SocketFactory(clientCertificates.sslSocketFactory()),
clientCertificates.trustManager()
)
}
return this
}
/**
* Returns a Call.Factory lazily initialized on a background thread
*/
@Provides
@Singleton
fun provideDeferredCallFactory(lazyClient: dagger.Lazy<OkHttpClient>): Deferred<Call.Factory> {
return BackgroundWorkScope.async(Dispatchers.IO, CoroutineStart.LAZY) { lazyClient.get() }
}
}

View file

@ -1,42 +1,26 @@
package be.digitalia.fosdem.utils.network package be.digitalia.fosdem.utils.network
import android.os.Build import kotlinx.coroutines.Deferred
import be.digitalia.fosdem.utils.BackgroundWorkScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
import okhttp3.OkHttpClient import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okhttp3.tls.HandshakeCertificates
import java.io.IOException import java.io.IOException
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.util.concurrent.TimeUnit import javax.inject.Inject
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
/** /**
* Utility class to perform HTTP requests. * High-level coroutines-based HTTP client.
* *
* @author Christophe Beyls * @author Christophe Beyls
*/ */
object HttpUtils { class HttpClient @Inject constructor(private val deferredCallFactory: @JvmSuppressWildcards Deferred<Call.Factory>) {
private const val DEFAULT_CONNECT_TIMEOUT = 10L suspend fun <T> get(url: String, bodyParser: (body: ResponseBody, headers: Headers) -> T): Response.Success<T> {
private const val DEFAULT_READ_TIMEOUT = 10L
private val deferredClient = BackgroundWorkScope.async(Dispatchers.IO, CoroutineStart.LAZY) {
OkHttpClient.Builder()
.enableTls12()
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
.build()
}
suspend fun <T> get(url: String, bodyParser: (body: ResponseBody, rawResponse: okhttp3.Response) -> T): Response.Success<T> {
return when (val response = get(url, null, bodyParser)) { return when (val response = get(url, null, bodyParser)) {
// Can only receive NotModified if lastModified argument is non-null // Can only receive NotModified if lastModified argument is non-null
is Response.NotModified -> throw IllegalStateException() is Response.NotModified -> throw IllegalStateException()
@ -47,7 +31,7 @@ object HttpUtils {
/** /**
* @param lastModified header value matching a previous "Last-Modified" response header. * @param lastModified header value matching a previous "Last-Modified" response header.
*/ */
suspend fun <T> get(url: String, lastModified: String?, bodyParser: (body: ResponseBody, rawResponse: okhttp3.Response) -> T): Response<T> { suspend fun <T> get(url: String, lastModified: String?, bodyParser: (body: ResponseBody, headers: Headers) -> T): Response<T> {
val requestBuilder = Request.Builder() val requestBuilder = Request.Builder()
if (lastModified != null) { if (lastModified != null) {
requestBuilder.header("If-Modified-Since", lastModified) requestBuilder.header("If-Modified-Since", lastModified)
@ -56,10 +40,8 @@ object HttpUtils {
.url(url) .url(url)
.build() .build()
val client = deferredClient.await() val call = deferredCallFactory.await().newCall(request)
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
val call = client.newCall(request)
call.enqueue(object : Callback { call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e) continuation.resumeWithException(e)
@ -78,7 +60,7 @@ object HttpUtils {
} }
} else { } else {
try { try {
val parsedBody = checkNotNull(body).use { bodyParser(it, response) } val parsedBody = checkNotNull(body).use { bodyParser(it, response.headers()) }
continuation.resume(Response.Success(parsedBody, response)) continuation.resume(Response.Success(parsedBody, response))
} catch (e: Exception) { } catch (e: Exception) {
continuation.resumeWithException(e) continuation.resumeWithException(e)
@ -90,21 +72,12 @@ object HttpUtils {
} }
} }
val okhttp3.Response.lastModified: String?
get() = header("Last-Modified")
private fun OkHttpClient.Builder.enableTls12(): OkHttpClient.Builder {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
val clientCertificates = HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.build()
sslSocketFactory(Tls12SocketFactory(clientCertificates.sslSocketFactory()), clientCertificates.trustManager())
}
return this
}
sealed class Response<out T> { sealed class Response<out T> {
object NotModified : Response<Nothing>() object NotModified : Response<Nothing>()
class Success<T>(val body: T, val raw: okhttp3.Response) : Response<T>() class Success<T>(val body: T, val raw: okhttp3.Response) : Response<T>()
} }
companion object {
const val LAST_MODIFIED_HEADER_NAME = "Last-Modified"
}
} }

View file

@ -2,7 +2,7 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.5.10' kotlin_version = '1.5.10'
hilt_version = '2.35.1' hilt_version = '2.37'
} }
repositories { repositories {
google() google()