Remove loggers and reorganize settings
parent
8b93b699d3
commit
f4186a2787
@ -1,148 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.logs
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.annotation.WorkerThread
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.NonCancellable
|
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.subdir
|
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import java.time.format.FormatStyle
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
|
|
||||||
private const val DIR = "logs"
|
|
||||||
private const val FLUSH_DELAY = 2_000L
|
|
||||||
private const val MAX_SIZE_BYTES = 1024 * 1024 // 1 MB
|
|
||||||
|
|
||||||
class FileLogger(
|
|
||||||
context: Context,
|
|
||||||
private val settings: AppSettings,
|
|
||||||
name: String,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val file by lazy {
|
|
||||||
val dir = context.getExternalFilesDir(DIR) ?: context.filesDir.subdir(DIR)
|
|
||||||
File(dir, "$name.log")
|
|
||||||
}
|
|
||||||
val isEnabled: Boolean
|
|
||||||
get() = settings.isLoggingEnabled
|
|
||||||
private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(Locale.ROOT)
|
|
||||||
private val buffer = ConcurrentLinkedQueue<String>()
|
|
||||||
private val mutex = Mutex()
|
|
||||||
private var flushJob: Job? = null
|
|
||||||
|
|
||||||
fun log(message: String, e: Throwable? = null) {
|
|
||||||
if (!isEnabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val text = buildString {
|
|
||||||
append(dateTimeFormatter.format(LocalDateTime.now()))
|
|
||||||
append(": ")
|
|
||||||
if (e != null) {
|
|
||||||
append("E!")
|
|
||||||
}
|
|
||||||
append(message)
|
|
||||||
if (e != null) {
|
|
||||||
append(' ')
|
|
||||||
append(e.stackTraceToString())
|
|
||||||
appendLine()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.add(text)
|
|
||||||
postFlush()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun log(messageProducer: () -> String) {
|
|
||||||
if (isEnabled) {
|
|
||||||
log(messageProducer())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun flush() {
|
|
||||||
if (!isEnabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flushJob?.cancelAndJoin()
|
|
||||||
flushImpl()
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
fun flushBlocking() {
|
|
||||||
if (!isEnabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
runBlockingSafe { flushJob?.cancelAndJoin() }
|
|
||||||
runBlockingSafe { flushImpl() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun postFlush() {
|
|
||||||
if (flushJob?.isActive == true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flushJob = processLifecycleScope.launch(Dispatchers.Default) {
|
|
||||||
delay(FLUSH_DELAY)
|
|
||||||
runCatchingCancellable {
|
|
||||||
flushImpl()
|
|
||||||
}.onFailure {
|
|
||||||
it.printStackTraceDebug()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun flushImpl() = withContext(NonCancellable) {
|
|
||||||
mutex.withLock {
|
|
||||||
if (buffer.isEmpty()) {
|
|
||||||
return@withContext
|
|
||||||
}
|
|
||||||
runInterruptible(Dispatchers.IO) {
|
|
||||||
if (file.length() > MAX_SIZE_BYTES) {
|
|
||||||
rotate()
|
|
||||||
}
|
|
||||||
FileOutputStream(file, true).use {
|
|
||||||
while (true) {
|
|
||||||
val message = buffer.poll() ?: break
|
|
||||||
it.write(message.toByteArray())
|
|
||||||
it.write('\n'.code)
|
|
||||||
}
|
|
||||||
it.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@WorkerThread
|
|
||||||
private fun rotate() {
|
|
||||||
val length = file.length()
|
|
||||||
val bakFile = File(file.parentFile, file.name + ".bak")
|
|
||||||
file.renameTo(bakFile)
|
|
||||||
bakFile.inputStream().use { input ->
|
|
||||||
input.skip(length - MAX_SIZE_BYTES / 2)
|
|
||||||
file.outputStream().use { output ->
|
|
||||||
input.copyTo(output)
|
|
||||||
output.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bakFile.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun runBlockingSafe(crossinline block: suspend () -> Unit) = try {
|
|
||||||
runBlocking(NonCancellable) { block() }
|
|
||||||
} catch (_: InterruptedException) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.logs
|
|
||||||
|
|
||||||
import javax.inject.Qualifier
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class TrackerLogger
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(AnnotationRetention.BINARY)
|
|
||||||
annotation class SyncLogger
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.logs
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.collection.arraySetOf
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import dagger.multibindings.ElementsIntoSet
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object LoggersModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@TrackerLogger
|
|
||||||
fun provideTrackerLogger(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
settings: AppSettings,
|
|
||||||
) = FileLogger(context, settings, "tracker")
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@SyncLogger
|
|
||||||
fun provideSyncLogger(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
settings: AppSettings,
|
|
||||||
) = FileLogger(context, settings, "sync")
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ElementsIntoSet
|
|
||||||
fun provideAllLoggers(
|
|
||||||
@TrackerLogger trackerLogger: FileLogger,
|
|
||||||
@SyncLogger syncLogger: FileLogger,
|
|
||||||
): Set<@JvmSuppressWildcards FileLogger> = arraySetOf(
|
|
||||||
trackerLogger,
|
|
||||||
syncLogger,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.settings.utils
|
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.view.forEach
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceViewHolder
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.databinding.PreferenceAboutLinksBinding
|
|
||||||
|
|
||||||
class AboutLinksPreference @JvmOverloads constructor(
|
|
||||||
context: Context,
|
|
||||||
attrs: AttributeSet? = null,
|
|
||||||
) : Preference(context, attrs), View.OnClickListener {
|
|
||||||
|
|
||||||
init {
|
|
||||||
layoutResource = R.layout.preference_about_links
|
|
||||||
isSelectable = false
|
|
||||||
isPersistent = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
|
||||||
super.onBindViewHolder(holder)
|
|
||||||
|
|
||||||
val binding = PreferenceAboutLinksBinding.bind(holder.itemView)
|
|
||||||
binding.root.forEach { button ->
|
|
||||||
TooltipCompat.setTooltipText(button, button.contentDescription)
|
|
||||||
button.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
|
||||||
val urlResId = when (v.id) {
|
|
||||||
R.id.btn_discord -> R.string.url_discord
|
|
||||||
R.id.btn_telegram -> R.string.url_telegram
|
|
||||||
R.id.btn_github -> R.string.url_github
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
openLink(v, v.context.getString(urlResId), v.contentDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openLink(v: View, url: String, title: CharSequence?) {
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
|
||||||
try {
|
|
||||||
context.startActivity(
|
|
||||||
if (title != null) {
|
|
||||||
Intent.createChooser(intent, title)
|
|
||||||
} else {
|
|
||||||
intent
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} catch (_: ActivityNotFoundException) {
|
|
||||||
Snackbar.make(v, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue