Skip to content
Merged
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
22 changes: 22 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env sh

# Enable with: git config core.hooksPath .githooks

set -e

cd "$(git rev-parse --show-toplevel)"

echo "[pre-commit] detekt auto-format…"
./gradlew -q :app:detektFormat < /dev/null

git diff --cached --name-only --diff-filter=ACM -- '*.kt' '*.kts' | while IFS= read -r f; do
[ -f "$f" ] && git add -- "$f"
done

echo "[pre-commit] detekt check…"
if ! ./gradlew -q :app:detekt < /dev/null; then
echo "[pre-commit] detekt reported new violations." >&2
echo " Fix them, or grandfather existing ones with:" >&2
echo " ./gradlew :app:detektBaseline" >&2
exit 1
fi
51 changes: 51 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask

plugins {
alias(libs.plugins.android.application)
Expand All @@ -7,6 +9,7 @@ plugins {
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.compose)
alias(libs.plugins.detekt)
}

android {
Expand Down Expand Up @@ -124,7 +127,55 @@ java {
}
}

detekt {
buildUponDefaultConfig = true
parallel = true
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
baseline = file("$rootDir/config/detekt/baseline.xml")
}

val contextParamFiles = listOf("**/extension/Flow.kt", "**/extension/Number.kt")

tasks.withType<Detekt>().configureEach {
jvmTarget = JavaVersion.VERSION_17.toString()
exclude(contextParamFiles)
reports {
html.required.set(true)
sarif.required.set(true)
xml.required.set(false)
txt.required.set(false)
}
}
tasks.withType<DetektCreateBaselineTask>().configureEach {
jvmTarget = JavaVersion.VERSION_17.toString()
exclude(contextParamFiles)
}

val detektFormat by tasks.registering(Detekt::class) {
description = "Auto-format Kotlin sources via detekt (ktlint rules)."
group = "formatting"
autoCorrect = true
parallel = true
buildUponDefaultConfig = true
ignoreFailures = true
setSource(files("src/main/kotlin", "src/test/kotlin", "src/androidTest/kotlin"))
config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
include("**/*.kt", "**/*.kts")
exclude("**/build/**", "**/resources/**")
exclude(contextParamFiles)
jvmTarget = JavaVersion.VERSION_17.toString()
reports {
html.required.set(false)
sarif.required.set(false)
xml.required.set(false)
txt.required.set(false)
}
}

dependencies {
detektPlugins(libs.detekt.formatting)
detektPlugins(libs.detekt.compose)

coreLibraryDesugaring(libs.desugaring)

implementation(libs.material)
Expand Down
23 changes: 23 additions & 0 deletions app/src/main/kotlin/com/looker/droidify/di/DownloadModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.looker.droidify.di

import android.content.Context
import com.looker.droidify.network.Downloader
import com.looker.droidify.service.DownloadManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object DownloadModule {

@Singleton
@Provides
fun providesDownloadManager(
@ApplicationContext context: Context,
downloader: Downloader,
): DownloadManager = DownloadManager(context, downloader)
}
100 changes: 100 additions & 0 deletions app/src/main/kotlin/com/looker/droidify/service/DownloadManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.looker.droidify.service

import android.content.Context
import androidx.annotation.StringRes
import com.looker.droidify.R.string.connection_error_DESC
import com.looker.droidify.R.string.could_not_download_FORMAT
import com.looker.droidify.R.string.http_error_DESC
import com.looker.droidify.R.string.io_error_DESC
import com.looker.droidify.R.string.socket_error_DESC
import com.looker.droidify.R.string.unknown_error_DESC
import com.looker.droidify.network.Downloader
import com.looker.droidify.network.NetworkResponse
import com.looker.droidify.network.percentBy
import com.looker.droidify.utility.common.cache.Cache
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit

private const val MAX_PARALLEL_DOWNLOADS = 1

class DownloadManager(
private val context: Context,
private val downloader: Downloader,
) {

sealed interface Status {
data object Queue : Status
data object Downloading : Status
sealed interface Finished : Status {
data object Success : Finished
data class Error(@get:StringRes val titleResId: Int, val message: Int?) : Finished
}
}

// percent 0..100
val progress = ConcurrentHashMap<String, Int>()
val tasks = ConcurrentHashMap<String, Status>()

private val semaphore = Semaphore(MAX_PARALLEL_DOWNLOADS)

suspend fun enqueue(
key: String,
url: String,
fileName: String,
authentication: String,
): Boolean {
if (Cache.getReleaseFile(context, fileName).exists()) return false
tasks[key] = Status.Queue

semaphore.withPermit {
download(key, url, fileName, authentication)
}
return true
}

fun cancel(key: String) {
tasks.remove(key)
}

fun cancelAll() {
tasks.keys.forEach(::cancel)
}

private suspend fun download(
key: String,
url: String,
fileName: String,
authentication: String,
) {
tasks[key] = Status.Downloading
val target = Cache.getPartialReleaseFile(context, fileName)

val response = downloader.downloadToFile(
url = url,
target = target,
headers = { if (authentication.isNotEmpty()) authentication(authentication) },
) { read, total ->
progress[key] = read.value percentBy total?.value
}
progress.remove(key)
when (response) {
is NetworkResponse.Success -> {
val releaseFile = Cache.getReleaseFile(context, fileName)
target.renameTo(releaseFile)
tasks[key] = Status.Finished.Success
}

is NetworkResponse.Error -> {
val descRes = when (response) {
is NetworkResponse.Error.ConnectionTimeout -> connection_error_DESC
is NetworkResponse.Error.Http -> http_error_DESC
is NetworkResponse.Error.IO -> io_error_DESC
is NetworkResponse.Error.SocketTimeout -> socket_error_DESC
is NetworkResponse.Error.Unknown -> unknown_error_DESC
}
tasks[key] = Status.Finished.Error(could_not_download_FORMAT, descRes)
}
}
}
}
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ plugins {
alias(libs.plugins.hilt) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.compose) apply false
alias(libs.plugins.detekt) apply false
}
Loading
Loading