mirror of
https://github.com/GrakovNe/lissen-android.git
synced 2026-06-17 13:19:37 -04:00
Feature/optional logging (#416)
This commit is contained in:
@@ -185,11 +185,13 @@ dependencies {
|
||||
|
||||
implementation(libs.converter.moshi)
|
||||
implementation(libs.moshi)
|
||||
implementation(libs.zip4j)
|
||||
|
||||
debugImplementation(libs.androidx.ui.tooling)
|
||||
debugImplementation(libs.androidx.ui.test.manifest)
|
||||
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(libs.mockk)
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.acra.security.TLS
|
||||
import org.acra.sender.HttpSender
|
||||
import org.grakovne.lissen.common.RunningComponent
|
||||
import org.grakovne.lissen.logging.LissenLogProvider
|
||||
import org.grakovne.lissen.persistence.preferences.LissenSharedPreferences
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -24,6 +25,9 @@ class LissenApplication : Application() {
|
||||
@Inject
|
||||
lateinit var lissenLogProvider: LissenLogProvider
|
||||
|
||||
@Inject
|
||||
lateinit var preferences: LissenSharedPreferences
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
|
||||
@@ -55,9 +59,11 @@ class LissenApplication : Application() {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
|
||||
try {
|
||||
Timber.plant(lissenLogProvider.provideLoggingTree())
|
||||
} catch (_: Exception) {
|
||||
if (preferences.isActivityLoggingEnabled()) {
|
||||
try {
|
||||
Timber.plant(lissenLogProvider.provideLoggingTree())
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ private fun sourceWithBackdropBlur(
|
||||
|
||||
scaled.recycle()
|
||||
|
||||
scaled.recycle()
|
||||
|
||||
val backdrop = Bitmap.createBitmap(blurredPadded, padding / 2, padding / 2, size, size)
|
||||
blurredPadded.recycle()
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class CachedBookmarkProvider
|
||||
@Inject
|
||||
constructor(
|
||||
private val channelProvider: AudiobookshelfChannelProvider,
|
||||
private val localCacheRepository: LocalCacheRepository
|
||||
private val localCacheRepository: LocalCacheRepository,
|
||||
) {
|
||||
val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@ package org.grakovne.lissen.logging
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.model.ZipParameters
|
||||
import net.lingala.zip4j.model.enums.CompressionLevel
|
||||
import net.lingala.zip4j.model.enums.CompressionMethod
|
||||
import org.grakovne.lissen.persistence.preferences.LissenSharedPreferences
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -11,6 +16,7 @@ class LissenLogProvider
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val preferences: LissenSharedPreferences,
|
||||
) {
|
||||
private val tree: FileLoggingTree by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||
FileLoggingTree(profileLogFile())
|
||||
@@ -20,7 +26,36 @@ class LissenLogProvider
|
||||
|
||||
fun provideLoggingTree(): FileLoggingTree = tree
|
||||
|
||||
fun enableLogging() {
|
||||
preferences.saveActivityLoggingEnabled(true)
|
||||
}
|
||||
|
||||
fun disableLogging() {
|
||||
preferences.saveActivityLoggingEnabled(false)
|
||||
}
|
||||
|
||||
fun archiveLogFile(): File? {
|
||||
val logFile = profileLogFile()
|
||||
if (!logFile.exists() || logFile.length() == 0L) return null
|
||||
|
||||
val archiveFile = File(context.cacheDir, FILE_LOG_ARCHIVE_NAME)
|
||||
archiveFile.delete()
|
||||
|
||||
val parameters =
|
||||
ZipParameters().apply {
|
||||
compressionMethod = CompressionMethod.DEFLATE
|
||||
compressionLevel = CompressionLevel.ULTRA
|
||||
}
|
||||
|
||||
ZipFile(archiveFile).use { zip ->
|
||||
zip.addFile(logFile, parameters)
|
||||
}
|
||||
|
||||
return archiveFile
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FILE_LOG_NAME = "lissen_log.txt"
|
||||
private const val FILE_LOG_ARCHIVE_NAME = "lissen_logs.zip"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +499,13 @@ class LissenSharedPreferences
|
||||
putBoolean(KEY_SOFTWARE_CODECS, value)
|
||||
}
|
||||
|
||||
fun isActivityLoggingEnabled(): Boolean = sharedPreferences.getBoolean(KEY_ACTIVITY_LOGGING, true)
|
||||
|
||||
fun saveActivityLoggingEnabled(value: Boolean) =
|
||||
sharedPreferences.edit {
|
||||
putBoolean(KEY_ACTIVITY_LOGGING, value)
|
||||
}
|
||||
|
||||
fun getHideCompleted(): Boolean = sharedPreferences.getBoolean(KEY_HIDE_COMPLETED, false)
|
||||
|
||||
fun saveHideCompleted(value: Boolean) =
|
||||
@@ -534,6 +541,7 @@ class LissenSharedPreferences
|
||||
private const val KEY_AUTO_DOWNLOAD_DELAYED = "auto_download_delayed"
|
||||
private const val KEY_PREFERRED_LIBRARY_ORDERING = "preferred_library_ordering"
|
||||
private const val KEY_SOFTWARE_CODECS = "software_codecs"
|
||||
private const val KEY_ACTIVITY_LOGGING = "activity_logging_enabled"
|
||||
private const val KEY_HIDE_COMPLETED = "hide_completed"
|
||||
|
||||
private const val KEY_CUSTOM_HEADERS = "custom_headers"
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -16,6 +15,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Description
|
||||
import androidx.compose.material.icons.outlined.Memory
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -43,6 +42,7 @@ import org.grakovne.lissen.R
|
||||
import org.grakovne.lissen.common.restartApplication
|
||||
import org.grakovne.lissen.ui.navigation.AppNavigationService
|
||||
import org.grakovne.lissen.ui.screens.settings.composable.PlaybackVolumeBoostSettingsComposable
|
||||
import org.grakovne.lissen.ui.screens.settings.composable.SettingsInfoBanner
|
||||
import org.grakovne.lissen.ui.screens.settings.composable.SettingsToggleItem
|
||||
import org.grakovne.lissen.viewmodel.CachingModelView
|
||||
import org.grakovne.lissen.viewmodel.SettingsViewModel
|
||||
@@ -63,6 +63,8 @@ fun AdvancedSettingsComposable(
|
||||
val materialYouColorsEnabled by viewModel.materialYouEnabled.observeAsState(false)
|
||||
val softwareCodecsEnabled by viewModel.softwareCodecsEnabled.observeAsState(false)
|
||||
val softwareCodecsEnabledOnStart = viewModel.softwareCodecsEnabledOnStart
|
||||
val activityLoggingEnabled by viewModel.activityLoggingEnabled.observeAsState(true)
|
||||
val activityLoggingEnabledOnStart = viewModel.activityLoggingEnabledOnStart
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -152,9 +154,16 @@ fun AdvancedSettingsComposable(
|
||||
},
|
||||
)
|
||||
|
||||
SettingsToggleItem(
|
||||
title = stringResource(R.string.settings_screen_activity_logging_title),
|
||||
description = stringResource(R.string.settings_screen_activity_logging_description),
|
||||
initialState = activityLoggingEnabled,
|
||||
) { viewModel.preferActivityLoggingEnabled(it) }
|
||||
|
||||
AdvancedSettingsSimpleItemComposable(
|
||||
title = stringResource(R.string.export_logs_title),
|
||||
description = stringResource(R.string.export_logs_description),
|
||||
enabled = activityLoggingEnabledOnStart,
|
||||
onclick = { shareLogs(context, viewModel) },
|
||||
)
|
||||
}
|
||||
@@ -162,6 +171,10 @@ fun AdvancedSettingsComposable(
|
||||
if (softwareCodecsEnabledOnStart != softwareCodecsEnabled) {
|
||||
SoftwareCodecsPreferenceBanner()
|
||||
}
|
||||
|
||||
if (activityLoggingEnabledOnStart != activityLoggingEnabled) {
|
||||
ActivityLoggingPreferenceBanner()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -170,52 +183,34 @@ fun AdvancedSettingsComposable(
|
||||
@Composable
|
||||
fun SoftwareCodecsPreferenceBanner(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
SettingsInfoBanner(
|
||||
icon = Icons.Outlined.Memory,
|
||||
text = stringResource(R.string.restart_the_app_to_start_using_the_new_codecs_title),
|
||||
ctaText = stringResource(R.string.restart_the_app_to_start_using_the_new_codecs_cta),
|
||||
onAction = { context.restartApplication() },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Memory,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.restart_the_app_to_start_using_the_new_codecs_title),
|
||||
style =
|
||||
typography.bodyMedium.copy(
|
||||
color = colorScheme.onSurface,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
TextButton(
|
||||
onClick = { context.restartApplication() },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.restart_the_app_to_start_using_the_new_codecs_cta),
|
||||
style =
|
||||
typography.bodyMedium.copy(
|
||||
color = colorScheme.primary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun ActivityLoggingPreferenceBanner(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
SettingsInfoBanner(
|
||||
icon = Icons.Outlined.Description,
|
||||
text = stringResource(R.string.restart_the_app_to_apply_logging_changes_title),
|
||||
ctaText = stringResource(R.string.restart_the_app_to_apply_logging_changes_cta),
|
||||
onAction = { context.restartApplication() },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
private fun shareLogs(
|
||||
context: Context,
|
||||
viewModel: SettingsViewModel,
|
||||
) {
|
||||
val logFile = viewModel.provideLogFileOrNull()
|
||||
val archiveFile = viewModel.provideLogArchiveOrNull()
|
||||
|
||||
if (logFile == null) {
|
||||
if (archiveFile == null) {
|
||||
Toast.makeText(context, context.getString(R.string.export_logs_no_logs), Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
@@ -224,7 +219,7 @@ private fun shareLogs(
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
logFile,
|
||||
archiveFile,
|
||||
)
|
||||
|
||||
val exportTimestamp = OffsetDateTime.now()
|
||||
@@ -232,7 +227,7 @@ private fun shareLogs(
|
||||
|
||||
val formattedTimestamp = exportTimestamp.format(formatter)
|
||||
|
||||
val sizeKb = logFile.length() / 1024
|
||||
val sizeKb = archiveFile.length() / 1024
|
||||
|
||||
val subject =
|
||||
"${context.getString(R.string.app_name)} logs • $formattedTimestamp • $sizeKb KB"
|
||||
@@ -246,14 +241,13 @@ private fun shareLogs(
|
||||
|
||||
val shareIntent =
|
||||
Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
type = "application/zip"
|
||||
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
putExtra(Intent.EXTRA_SUBJECT, subject)
|
||||
putExtra(Intent.EXTRA_TEXT, details)
|
||||
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
|
||||
context.startActivity(Intent.createChooser(shareIntent, "Export logs"))
|
||||
|
||||
@@ -19,13 +19,14 @@ import androidx.compose.ui.unit.dp
|
||||
fun AdvancedSettingsSimpleItemComposable(
|
||||
title: String,
|
||||
description: String,
|
||||
enabled: Boolean = true,
|
||||
onclick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onclick() }
|
||||
.let { if (enabled) it.clickable { onclick() } else it }
|
||||
.padding(start = 24.dp, end = 12.dp, top = 12.dp, bottom = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
@@ -38,11 +39,12 @@ fun AdvancedSettingsSimpleItemComposable(
|
||||
modifier = Modifier.padding(bottom = 4.dp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (enabled) colorScheme.onBackground else colorScheme.onBackground.copy(alpha = 0.4f),
|
||||
)
|
||||
Text(
|
||||
text = description,
|
||||
style = typography.bodyMedium,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
color = if (enabled) colorScheme.onSurfaceVariant else colorScheme.onSurfaceVariant.copy(alpha = 0.4f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ import kotlinx.coroutines.launch
|
||||
import org.grakovne.lissen.R
|
||||
import org.grakovne.lissen.lib.domain.connection.LocalUrl
|
||||
import org.grakovne.lissen.ui.screens.common.hasLocationPermission
|
||||
import org.grakovne.lissen.ui.screens.settings.composable.SettingsInfoBanner
|
||||
import org.grakovne.lissen.viewmodel.SettingsViewModel
|
||||
import kotlin.math.max
|
||||
|
||||
@@ -182,46 +183,17 @@ fun LocationPermissionBanner(
|
||||
modifier: Modifier = Modifier,
|
||||
onResult: (Boolean) -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 20.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.LocationOn,
|
||||
contentDescription = null,
|
||||
tint = colorScheme.primary,
|
||||
modifier = Modifier.padding(end = 12.dp),
|
||||
val permissionRequestLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = onResult,
|
||||
)
|
||||
|
||||
val permissionRequestLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = onResult,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.location_permission_request_hint),
|
||||
style =
|
||||
typography.bodyMedium.copy(
|
||||
color = colorScheme.onSurface,
|
||||
),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
TextButton(
|
||||
onClick = { permissionRequestLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) },
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.permission_request_grant_button),
|
||||
style =
|
||||
typography.bodyMedium.copy(
|
||||
color = colorScheme.primary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
SettingsInfoBanner(
|
||||
icon = Icons.Default.LocationOn,
|
||||
text = stringResource(R.string.location_permission_request_hint),
|
||||
ctaText = stringResource(R.string.permission_request_grant_button),
|
||||
onAction = { permissionRequestLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -93,17 +93,18 @@ class SettingsViewModel
|
||||
val softwareCodecsEnabled: LiveData<Boolean> = _softwareCodecsEnabled
|
||||
val softwareCodecsEnabledOnStart: Boolean = preferences.getSoftwareCodecsEnabled()
|
||||
|
||||
private val _activityLoggingEnabled = MutableLiveData(preferences.isActivityLoggingEnabled())
|
||||
|
||||
val activityLoggingEnabled: LiveData<Boolean> = _activityLoggingEnabled
|
||||
val activityLoggingEnabledOnStart: Boolean = preferences.isActivityLoggingEnabled()
|
||||
|
||||
private val _hideCompleted = preferences.hideCompletedFlow
|
||||
val hideCompleted = _hideCompleted
|
||||
|
||||
private val _autoDownloadDelayed = MutableLiveData(preferences.getAutoDownloadDelayed())
|
||||
val autoDownloadDelayed = _autoDownloadDelayed
|
||||
|
||||
fun provideLogFileOrNull(): File? {
|
||||
val logFile = logProvider.profileLogFile()
|
||||
|
||||
return logFile.takeIf { it.exists() && it.length() > 0L }
|
||||
}
|
||||
fun provideLogArchiveOrNull(): File? = logProvider.archiveLogFile()
|
||||
|
||||
fun preferCrashReporting(value: Boolean) {
|
||||
Timber.d("User action: preferCrashReporting $value")
|
||||
@@ -249,6 +250,12 @@ class SettingsViewModel
|
||||
preferences.saveSoftwareCodecsEnabled(value)
|
||||
}
|
||||
|
||||
fun preferActivityLoggingEnabled(value: Boolean) {
|
||||
Timber.d("User action: preferActivityLoggingEnabled $value")
|
||||
_activityLoggingEnabled.postValue(value)
|
||||
if (value) logProvider.enableLogging() else logProvider.disableLogging()
|
||||
}
|
||||
|
||||
fun preferAutoDownloadOption(option: DownloadOption?) {
|
||||
Timber.d("User action: preferAutoDownloadOption $option")
|
||||
_preferredAutoDownloadOption.postValue(option)
|
||||
|
||||
@@ -190,6 +190,10 @@
|
||||
<string name="settings_screen_client_cert_remove_action">Отвязать сертификат</string>
|
||||
<string name="settings_screen_client_cert_picker_cancelled_toast">mTLS не найден</string>
|
||||
<string name="login_error_client_cert_error">mTLS не прошел проверку на сервере</string>
|
||||
<string name="settings_screen_activity_logging_title">Записывать логи</string>
|
||||
<string name="settings_screen_activity_logging_description">Сохранять диагностические данные в файл</string>
|
||||
<string name="restart_the_app_to_apply_logging_changes_title">Перезапустите приложение, чтобы применить настройки логирования</string>
|
||||
<string name="restart_the_app_to_apply_logging_changes_cta">Перезапустить</string>
|
||||
<string name="export_logs_title">Экспортировать логи</string>
|
||||
<string name="export_logs_description">Выгрузить данные приложения для отладки</string>
|
||||
<string name="export_logs_no_logs">Файл с логами не найден</string>
|
||||
|
||||
@@ -188,6 +188,10 @@
|
||||
<string name="connection_settings_title">Connection preferences</string>
|
||||
<string name="connection_settings_description">Control server address, proxy, and SSL settings</string>
|
||||
<string name="disconnect_from_server_title">Disconnect from the server</string>
|
||||
<string name="settings_screen_activity_logging_title">Activity Logging</string>
|
||||
<string name="settings_screen_activity_logging_description">Record diagnostic events to a local file</string>
|
||||
<string name="restart_the_app_to_apply_logging_changes_title">Restart the app to apply logging changes</string>
|
||||
<string name="restart_the_app_to_apply_logging_changes_cta">Restart</string>
|
||||
<string name="export_logs_title">Export logs</string>
|
||||
<string name="export_logs_description">Share diagnostic data for troubleshooting</string>
|
||||
<string name="export_logs_no_logs">No logs available</string>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package org.grakovne.lissen.logging
|
||||
|
||||
import android.content.Context
|
||||
import io.mockk.every
|
||||
import io.mockk.justRun
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import org.grakovne.lissen.persistence.preferences.LissenSharedPreferences
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.io.File
|
||||
|
||||
class LissenLogProviderTest {
|
||||
@TempDir
|
||||
lateinit var tempDir: File
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var preferences: LissenSharedPreferences
|
||||
private lateinit var provider: LissenLogProvider
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
context = mockk()
|
||||
preferences = mockk()
|
||||
every { context.cacheDir } returns tempDir
|
||||
provider = LissenLogProvider(context, preferences)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `archiveLogFile returns null when log file does not exist`() {
|
||||
assertNull(provider.archiveLogFile())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `archiveLogFile returns null when log file is empty`() {
|
||||
provider.profileLogFile().createNewFile()
|
||||
|
||||
assertNull(provider.archiveLogFile())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `archiveLogFile returns zip file when log file has content`() {
|
||||
provider.profileLogFile().writeText("some log content")
|
||||
|
||||
val archive = provider.archiveLogFile()
|
||||
|
||||
assertNotNull(archive)
|
||||
assertTrue(archive!!.exists())
|
||||
assertTrue(archive.length() > 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `archiveLogFile produces valid zip containing the log file`() {
|
||||
val logContent = "line1\nline2\nline3"
|
||||
provider.profileLogFile().writeText(logContent)
|
||||
|
||||
val archive = provider.archiveLogFile()!!
|
||||
|
||||
val zip = ZipFile(archive)
|
||||
assertTrue(zip.isValidZipFile)
|
||||
|
||||
val entry = zip.fileHeaders.single()
|
||||
assertEquals(provider.profileLogFile().name, entry.fileName)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `archiveLogFile overwrites previous archive on repeated calls`() {
|
||||
provider.profileLogFile().writeText("first run")
|
||||
val first = provider.archiveLogFile()!!
|
||||
|
||||
provider.profileLogFile().writeText("second run with more content to make it larger")
|
||||
val second = provider.archiveLogFile()!!
|
||||
|
||||
assertEquals(first.absolutePath, second.absolutePath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disableLogging saves preference as false`() {
|
||||
provider.profileLogFile().writeText("logs")
|
||||
justRun { preferences.saveActivityLoggingEnabled(false) }
|
||||
|
||||
provider.disableLogging()
|
||||
|
||||
verify { preferences.saveActivityLoggingEnabled(false) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disableLogging is safe when log file does not exist`() {
|
||||
justRun { preferences.saveActivityLoggingEnabled(false) }
|
||||
|
||||
provider.disableLogging()
|
||||
|
||||
assertTrue(!provider.profileLogFile().exists())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `enableLogging saves preference as true`() {
|
||||
justRun { preferences.saveActivityLoggingEnabled(true) }
|
||||
|
||||
provider.enableLogging()
|
||||
|
||||
verify { preferences.saveActivityLoggingEnabled(true) }
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ junitJupiter = "6.1.0"
|
||||
mockk = "1.14.9"
|
||||
androidxTestExtJunit = "1.3.0"
|
||||
androidxTestRunner = "1.7.0"
|
||||
zip4j = "2.11.5"
|
||||
|
||||
[libraries]
|
||||
acra-core = { module = "ch.acra:acra-core", version.ref = "acraCore" }
|
||||
@@ -89,9 +90,11 @@ moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
||||
moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
|
||||
androidx-media3-ffmpeg-decoder = { module = "org.jellyfin.media3:media3-ffmpeg-decoder", version.ref = "media3Ffmpeg" }
|
||||
process-phoenix = { module = "com.jakewharton:process-phoenix", version.ref = "processPhoenix" }
|
||||
zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" }
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junitJupiter" }
|
||||
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitJupiter" }
|
||||
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
||||
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
|
||||
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxTestRunner" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user