Skip to content

Commit

Permalink
Refactor OS handling and enhance Downloader logging
Browse files Browse the repository at this point in the history
Replaced `OperatingSystem` with `Platform` for uniform naming and simplified platform detection across the project. Enhanced the `Downloader` to use buffered file writes and added detailed debug-level logging to track progress and improve error handling. Removed unnecessary Ktor redirect plugin for streamlining configuration.
  • Loading branch information
kdroidFilter committed Jan 8, 2025
1 parent d1f78b5 commit 2906eac
Show file tree
Hide file tree
Showing 16 changed files with 86 additions and 93 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.github.kdroidfilter.platformtools.appmanager

import io.github.kdroidfilter.platformtools.OperatingSystem
import io.github.kdroidfilter.platformtools.Platform
import io.github.kdroidfilter.platformtools.appmanager.WindowsPrivilegeHelper.installOnWindows
import io.github.kdroidfilter.platformtools.getOperatingSystem
import io.github.kdroidfilter.platformtools.getPlatform
import io.github.oshai.kotlinlogging.KotlinLogging
import java.io.File

Expand All @@ -28,16 +28,16 @@ class DesktopInstaller : AppInstaller {
*/
override suspend fun canRequestInstallPackages(): Boolean {
logger.debug { "Checking if install packages request can be made." }
return when (getOperatingSystem()) {
OperatingSystem.WINDOWS -> {
return when (getPlatform()) {
Platform.WINDOWS -> {
logger.debug { "OS=Windows, returning true directly." }
true
}
OperatingSystem.LINUX -> {
Platform.LINUX -> {
logger.debug { "OS=Linux, returning true directly." }
true
}
OperatingSystem.MAC -> {
Platform.MAC -> {
logger.debug { "OS=Mac, assuming elevation is not needed." }
true
}
Expand Down Expand Up @@ -67,14 +67,14 @@ class DesktopInstaller : AppInstaller {
*/
override suspend fun requestInstallPackagesPermission() {
logger.debug { "Requesting install packages permission." }
when (getOperatingSystem()) {
OperatingSystem.WINDOWS -> {
when (getPlatform()) {
Platform.WINDOWS -> {
logger.debug { "OS=Windows, no elevation request needed." }
}
OperatingSystem.LINUX -> {
Platform.LINUX -> {
logger.debug { "OS=Linux, elevation handled by pkexec." }
}
OperatingSystem.MAC -> {
Platform.MAC -> {
logger.debug { "OS=Mac, elevation not implemented." }
}
else -> {
Expand All @@ -96,15 +96,15 @@ class DesktopInstaller : AppInstaller {
*/
override suspend fun installApp(appFile: File, onResult: (Boolean, String?) -> Unit) {
logger.debug { "Starting installation for file: ${appFile.absolutePath}" }
val osDetected = getOperatingSystem()
val osDetected = getPlatform()
logger.debug { "Detected OS: $osDetected" }

when (osDetected) {
OperatingSystem.WINDOWS -> installOnWindows(appFile, onResult)
OperatingSystem.LINUX -> installOnLinux(appFile, onResult)
OperatingSystem.MAC -> installOnMac(appFile, onResult)
Platform.WINDOWS -> installOnWindows(appFile, onResult)
Platform.LINUX -> installOnLinux(appFile, onResult)
Platform.MAC -> installOnMac(appFile, onResult)
else -> {
val message = "Installation not supported for: ${getOperatingSystem()}"
val message = "Installation not supported for: ${getPlatform()}"
logger.debug { message }
onResult(false, message)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.ANDROID
actual fun getPlatform(): Platform = Platform.ANDROID
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ package io.github.kdroidfilter.platformtools
* - `WASMJS`: Represents WebAssembly environments executed via JavaScript.
* - `UNKNOWN`: Represents an unrecognized or unsupported operating system.
*/
enum class OperatingSystem {
enum class Platform {
WINDOWS, MAC, LINUX, ANDROID, IOS, JS, WASMJS, UNKNOWN
}

/**
* Determines and returns the current operating system on which the code is running.
*
* @return An instance of [OperatingSystem] representing the detected operating system.
* @return An instance of [Platform] representing the detected operating system.
*/
expect fun getOperatingSystem(): OperatingSystem
expect fun getPlatform(): Platform
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.IOS
actual fun getPlatform(): Platform = Platform.IOS
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.JS
actual fun getPlatform(): Platform = Platform.JS
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem {
actual fun getPlatform(): Platform {
val osName = System.getProperty("os.name").lowercase()
return when {
osName.contains("win") -> OperatingSystem.WINDOWS
osName.contains("mac") -> OperatingSystem.MAC
osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> OperatingSystem.LINUX
else -> OperatingSystem.UNKNOWN
osName.contains("win") -> Platform.WINDOWS
osName.contains("mac") -> Platform.MAC
osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> Platform.LINUX
else -> Platform.UNKNOWN
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.LINUX
actual fun getPlatform(): Platform = Platform.LINUX
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.MAC
actual fun getPlatform(): Platform = Platform.MAC
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.WINDOWS
actual fun getPlatform(): Platform = Platform.WINDOWS
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package io.github.kdroidfilter.platformtools

actual fun getOperatingSystem(): OperatingSystem = OperatingSystem.WASMJS
actual fun getPlatform(): Platform = Platform.WASMJS
2 changes: 1 addition & 1 deletion platformtools/releasefetcher/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ kotlin {
implementation(libs.ktor.client.cio)
api(libs.semver)
implementation(libs.slf4j.simple)

implementation(libs.kotlin.logging)

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import io.ktor.client.plugins.*

// Global Ktor configuration
val client = HttpClient(CIO) {
install(HttpRedirect) {
checkHttpMethod = false
}
followRedirects = true

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,80 @@ package io.github.kdroidfilter.platformtools.releasefetcher.downloader

import io.github.kdroidfilter.platformtools.getCacheDir
import io.github.kdroidfilter.platformtools.releasefetcher.config.client
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.utils.io.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File

private val logger = KotlinLogging.logger {}

private const val BUFFER_SIZE = 2621440 // 256KB buffer

class Downloader {
/**
* Downloads the application and reports progress via [onProgress].
* - [percentage] = -1.0 if the total size is unknown
* - Otherwise, it’s the calculated percentage
*/

suspend fun downloadApp(
downloadUrl: String,
onProgress: (percentage: Double, file: File?) -> Unit
): Boolean {
logger.debug { "Starting download from URL: $downloadUrl" }

val fileName = downloadUrl.substringAfterLast('/').substringBefore('?')
val cacheDir = getCacheDir()
val destinationFile = File(cacheDir, fileName)

return try {
// 1. D'abord, on tente de récupérer la taille via une requête HEAD
val headResponse = client.head(downloadUrl)
val contentLengthFromHead =
headResponse.headers[HttpHeaders.ContentLength]?.toLongOrNull() ?: -1L
// Notify 0% immediately at the start
onProgress(0.0, null)
logger.debug { "Download initialized: 0%" }

// 2. On fait la requête GET (qui devrait suivre la redirection si followRedirects = true)
val response: HttpResponse = client.get(downloadUrl)

if (response.status.isSuccess()) {
// Essayons de récupérer la taille depuis cette requête GET,
// au cas où la HEAD n’en aurait pas renvoyé.
val contentLengthGet = response.contentLength() ?: -1L
val finalContentLength = if (contentLengthFromHead > 0) {
contentLengthFromHead
} else {
contentLengthGet
return try {
val response = client.get(downloadUrl) {
onDownload { bytesSentTotal, contentLength ->
val progress = if (contentLength != null && contentLength > 0) {
(bytesSentTotal * 100.0 / contentLength)
} else 0.0
logger.debug { "Progress: $bytesSentTotal / $contentLength bytes" }
onProgress(progress, null)
}
}

if (response.status.isSuccess()) {
val channel: ByteReadChannel = response.body()
val contentLength = response.contentLength() ?: -1L
logger.debug { "Content length: $contentLength bytes" }

withContext(Dispatchers.IO) {
destinationFile.outputStream().use { output ->
var bytesReceived: Long = 0
val buffer = ByteArray(8192)

destinationFile.outputStream().buffered(BUFFER_SIZE).use { output ->
val buffer = ByteArray(BUFFER_SIZE)
while (!channel.isClosedForRead) {
val bytesRead = channel.readAvailable(buffer)
if (bytesRead == -1) break

// Écrire dans le fichier
output.write(buffer, 0, bytesRead)
bytesReceived += bytesRead

if (finalContentLength > 0) {
val percentage = (bytesReceived * 100.0) / finalContentLength
onProgress(percentage, null)
} else {
// Taille totale inconnue
onProgress(-1.0, null)
}
}
}
}
// Téléchargement terminé, notifier le fichier
onProgress(100.0, destinationFile)
true

if (destinationFile.exists()) {
logger.debug { "Download completed. Size: ${destinationFile.length()} bytes" }
onProgress(100.0, destinationFile)
true
} else {
logger.error { "Error: File not created" }
onProgress(-1.0, null) // Keep -1.0 for errors
false
}
} else {
// Le serveur a retourné un code d’erreur (ex. 4xx, 5xx)
onProgress(-1.0, null)
logger.error { "Download failed: ${response.status}" }
onProgress(-1.0, null) // Keep -1.0 for errors
false
}
} catch (e: Exception) {
e.printStackTrace()
onProgress(-1.0, null)
logger.error(e) { "Download error: ${e.message}" }
onProgress(-1.0, null) // Keep -1.0 for errors
false
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.github.kdroidfilter.platformtools.releasefetcher.github

import io.github.kdroidfilter.platformtools.OperatingSystem
import io.github.kdroidfilter.platformtools.Platform
import io.github.kdroidfilter.platformtools.getAppVersion
import io.github.kdroidfilter.platformtools.getOperatingSystem
import io.github.kdroidfilter.platformtools.getPlatform
import io.github.kdroidfilter.platformtools.releasefetcher.config.client
import io.github.kdroidfilter.platformtools.releasefetcher.github.model.Release
import io.github.z4kn4fein.semver.toVersion
Expand All @@ -15,7 +15,7 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json

class GitHubReleaseFetcher(
private val repoOwner: String,
private val owner: String,
private val repo: String,
) {

Expand All @@ -26,7 +26,7 @@ class GitHubReleaseFetcher(
*/
suspend fun getLatestRelease(): Release? = withContext(Dispatchers.IO) {
try {
val response: HttpResponse = client.get("https://api.github.com/repos/$repoOwner/$repo/releases/latest")
val response: HttpResponse = client.get("https://api.github.com/repos/$owner/$repo/releases/latest")

if (response.status == HttpStatusCode.OK) {
val responseBody: String = response.body()
Expand Down Expand Up @@ -64,13 +64,13 @@ class GitHubReleaseFetcher(
*/
fun getDownloadLinkForPlatform(release: Release): String? {
val platformFileTypes = mapOf(
OperatingSystem.ANDROID to ".apk",
OperatingSystem.WINDOWS to ".msi",
OperatingSystem.LINUX to ".deb",
OperatingSystem.MAC to ".dmg"
Platform.ANDROID to ".apk",
Platform.WINDOWS to ".msi",
Platform.LINUX to ".deb",
Platform.MAC to ".dmg"
)

val fileType = platformFileTypes[getOperatingSystem()] ?: return null
val fileType = platformFileTypes[getPlatform()] ?: return null

// Find the corresponding asset
val asset = release.assets.firstOrNull { it.name.endsWith(fileType, ignoreCase = true) }
Expand Down
8 changes: 4 additions & 4 deletions sample/composeApp/src/commonMain/kotlin/sample/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import io.github.kdroidfilter.platformtools.appmanager.getAppInstaller
import io.github.kdroidfilter.platformtools.getOperatingSystem
import io.github.kdroidfilter.platformtools.getPlatform
import io.github.kdroidfilter.platformtools.releasefetcher.downloader.Downloader
import io.github.kdroidfilter.platformtools.releasefetcher.github.GitHubReleaseFetcher
import kotlinx.coroutines.CoroutineScope
Expand All @@ -26,11 +26,11 @@ fun App() {
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {

UpdateCheckerUI(GitHubReleaseFetcher(repoOwner = "kdroidfilter", repo = "KmpRealTimeLogger"))
UpdateCheckerUI(GitHubReleaseFetcher(owner = "kdroidfilter", repo = "KmpRealTimeLogger"))

Text(
"Operating System: " +
getOperatingSystem().name.lowercase().replaceFirstChar { it.uppercase() },
"Platform: " +
getPlatform().name.lowercase().replaceFirstChar { it.uppercase() },
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground
)
Expand Down
4 changes: 2 additions & 2 deletions sample/terminalApp/src/commonMain/kotlin/main.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import io.github.kdroidfilter.platformtools.getOperatingSystem
import io.github.kdroidfilter.platformtools.getPlatform

fun main() {
println("The Operating System is " + getOperatingSystem().name.lowercase().replaceFirstChar { it.uppercase()})
println("The Operating System is " + getPlatform().name.lowercase().replaceFirstChar { it.uppercase()})
}

0 comments on commit 2906eac

Please sign in to comment.