Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions app-compose/dependencies/marketReleaseRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,6 @@ com.squareup.okhttp3:okhttp-android:5.3.2
com.squareup.okhttp3:okhttp:5.3.2
com.squareup.okio:okio-jvm:3.16.4
com.squareup.okio:okio:3.16.4
com.squareup.retrofit2:converter-kotlinx-serialization:3.0.0
com.squareup.retrofit2:retrofit:3.0.0
commons-codec:commons-codec:1.21.0
dev.drewhamilton.poko:poko-annotations-jvm:0.21.1
dev.drewhamilton.poko:poko-annotations:0.21.1
Expand Down
7 changes: 4 additions & 3 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ dependencies {
api(libs.kotlinx.datetime)
api(projects.core.common)
api(projects.core.model)
testImplementation(libs.kotlinx.coroutines.test)

implementation(libs.kotlinx.serialization.json)
implementation(libs.okhttp.logging)
implementation(libs.retrofit.core)
implementation(libs.retrofit.kotlin.serialization)

testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.okhttp.mockwebserver)
testImplementation(libs.truth)
}
6 changes: 1 addition & 5 deletions core/network/consumer-proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response

# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2025 Blocker
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.merxury.blocker.core.network

import java.io.IOException

class NetworkException(
message: String,
cause: Throwable? = null,
) : IOException(message, cause)
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@

package com.merxury.blocker.core.network.di

import android.content.Context
import androidx.tracing.trace
import com.merxury.blocker.core.dispatchers.BlockerDispatchers.IO
import com.merxury.blocker.core.dispatchers.Dispatcher
import com.merxury.blocker.core.network.BlockerNetworkDataSource
import com.merxury.blocker.core.network.BuildConfig
import com.merxury.blocker.core.network.fake.FakeAssetManager
import com.merxury.blocker.core.network.retrofit.RetrofitBlockerNetwork
import com.merxury.blocker.core.network.okhttp.OkHttpBlockerNetwork
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.serialization.json.Json
import okhttp3.Call
import okhttp3.OkHttpClient
Expand Down Expand Up @@ -66,11 +66,7 @@ internal object NetworkModule {
@Singleton
fun provideBlockerNetworkDataSource(
okHttpCallFactory: dagger.Lazy<Call.Factory>,
): BlockerNetworkDataSource = RetrofitBlockerNetwork(okHttpCallFactory)

@Provides
@Singleton
fun providesFakeAssetManager(
@ApplicationContext context: Context,
): FakeAssetManager = FakeAssetManager(context.assets::open)
networkJson: Json,
@Dispatcher(IO) ioDispatcher: CoroutineDispatcher,
): BlockerNetworkDataSource = OkHttpBlockerNetwork(okHttpCallFactory, networkJson, ioDispatcher)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream

private const val CHUNK_SIZE = 1024
private const val CHUNK_SIZE = 8192

class BinaryFileWriter(
private val outputStream: OutputStream,
Expand All @@ -31,19 +31,18 @@ class BinaryFileWriter(

@Throws(IOException::class)
fun write(inputStream: InputStream?, length: Long): Long {
if (length.toInt() == 0) {
Timber.w("Nothing to write, file length is 0")
if (length <= 0L) {
Timber.w("Nothing to write, file length is $length")
return 0
}
BufferedInputStream(inputStream).use { input ->
val dataBuffer =
ByteArray(CHUNK_SIZE)
val dataBuffer = ByteArray(CHUNK_SIZE)
var readBytes: Int
var totalBytes: Long = 0
while (input.read(dataBuffer).also { readBytes = it } != -1) {
totalBytes += readBytes.toLong()
outputStream.write(dataBuffer, 0, readBytes)
onProgressUpdate.invoke(totalBytes / length * 100.0)
onProgressUpdate.invoke(totalBytes.toDouble() / length * 100.0)
}
return totalBytes
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

package com.merxury.blocker.core.network.retrofit
package com.merxury.blocker.core.network.okhttp

import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CompletionHandler
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2025 Blocker
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.merxury.blocker.core.network.okhttp

import com.merxury.blocker.core.dispatchers.BlockerDispatchers.IO
import com.merxury.blocker.core.dispatchers.Dispatcher
import com.merxury.blocker.core.model.preference.RuleServerProvider
import com.merxury.blocker.core.model.preference.RuleServerProvider.GITHUB
import com.merxury.blocker.core.network.BlockerNetworkDataSource
import com.merxury.blocker.core.network.NetworkException
import com.merxury.blocker.core.network.io.BinaryFileWriter
import com.merxury.blocker.core.network.model.NetworkChangeList
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Call
import okhttp3.Request
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

/**
* OkHttp backed [BlockerNetworkDataSource]
*/
@Singleton
internal class OkHttpBlockerNetwork @Inject constructor(
private val okhttpCallFactory: dagger.Lazy<Call.Factory>,
private val networkJson: Json,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
) : BlockerNetworkDataSource {

override suspend fun getRuleLatestCommitId(provider: RuleServerProvider): NetworkChangeList {
val request = Request.Builder()
.url(provider.commitApiUrl)
.build()
val json = okhttpCallFactory.get()
.newCall(request)
.await()
.use { response ->
if (!response.isSuccessful) {
throw NetworkException("Failed to get latest commit id: HTTP ${response.code}")
}
response.body.string()
}
val commitId = getLatestCommitId(provider, json)
return NetworkChangeList(commitId)
}

override suspend fun downloadRules(
provider: RuleServerProvider,
writer: BinaryFileWriter,
): Long {
Timber.d("Downloading rules from ${provider.downloadLink}")
val request = Request.Builder()
.url(provider.downloadLink)
.build()
val response = okhttpCallFactory.get()
.newCall(request)
.await()
return response.use {
if (!it.isSuccessful) {
throw NetworkException("Failed to download rules: HTTP ${it.code}")
}
val responseBody = it.body
val contentLength = responseBody.contentLength()
if (contentLength == 0L) {
Timber.e("Response body is empty.")
return@use 0L
}
Timber.v("Zip length: $contentLength")
withContext(ioDispatcher) {
writer.write(responseBody.byteStream(), contentLength)
}
}
}

internal fun getLatestCommitId(provider: RuleServerProvider, json: String): String {
if (json.isBlank()) {
throw NetworkException("Empty response body when fetching commit id")
}
val elements = networkJson.parseToJsonElement(json)
val firstElementInList = elements.jsonArray.firstOrNull()
?: throw NetworkException("Empty commit list in response")
val commitId = if (provider == GITHUB) {
firstElementInList.jsonObject["sha"]?.jsonPrimitive?.content
} else {
firstElementInList.jsonObject["id"]?.jsonPrimitive?.content
}
if (commitId.isNullOrBlank()) {
throw NetworkException("Missing commit id in response JSON")
}
return commitId
}
}

This file was deleted.

Loading