Improve sync logging

This commit is contained in:
Koitharu
2023-05-17 17:00:42 +03:00
parent 51ff1ff7b7
commit 43a92bdf08
6 changed files with 63 additions and 18 deletions

View File

@@ -4,12 +4,15 @@ import android.content.Context
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.processLifecycleScope import org.koitharu.kotatsu.utils.ext.processLifecycleScope
@@ -82,6 +85,15 @@ class FileLogger(
flushImpl() flushImpl()
} }
@WorkerThread
fun flushBlocking() {
if (!isEnabled) {
return
}
runBlockingSafe { flushJob?.cancelAndJoin() }
runBlockingSafe { flushImpl() }
}
private fun postFlush() { private fun postFlush() {
if (flushJob?.isActive == true) { if (flushJob?.isActive == true) {
return return
@@ -96,10 +108,10 @@ class FileLogger(
} }
} }
private suspend fun flushImpl() { private suspend fun flushImpl() = withContext(NonCancellable) {
mutex.withLock { mutex.withLock {
if (buffer.isEmpty()) { if (buffer.isEmpty()) {
return return@withContext
} }
runInterruptible(Dispatchers.IO) { runInterruptible(Dispatchers.IO) {
if (file.length() > MAX_SIZE_BYTES) { if (file.length() > MAX_SIZE_BYTES) {
@@ -131,4 +143,9 @@ class FileLogger(
} }
bakFile.delete() bakFile.delete()
} }
private inline fun runBlockingSafe(crossinline block: suspend () -> Unit) = try {
runBlocking(NonCancellable) { block() }
} catch (_: InterruptedException) {
}
} }

View File

@@ -13,6 +13,7 @@ import androidx.annotation.WorkerThread
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -22,7 +23,9 @@ import org.koitharu.kotatsu.core.db.TABLE_HISTORY
import org.koitharu.kotatsu.core.db.TABLE_MANGA import org.koitharu.kotatsu.core.db.TABLE_MANGA
import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS
import org.koitharu.kotatsu.core.db.TABLE_TAGS import org.koitharu.kotatsu.core.db.TABLE_TAGS
import org.koitharu.kotatsu.core.logs.LoggersModule
import org.koitharu.kotatsu.core.network.GZipInterceptor import org.koitharu.kotatsu.core.network.GZipInterceptor
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.json.mapJSONTo import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
import org.koitharu.kotatsu.sync.data.SyncAuthApi import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.data.SyncAuthenticator import org.koitharu.kotatsu.sync.data.SyncAuthenticator
@@ -61,6 +64,7 @@ class SyncHelper(
} }
private val defaultGcPeriod: Long // gc period if sync enabled private val defaultGcPeriod: Long // gc period if sync enabled
get() = TimeUnit.DAYS.toMillis(4) get() = TimeUnit.DAYS.toMillis(4)
private val logger = LoggersModule.provideSyncLogger(context, AppSettings(context))
fun syncFavourites(syncResult: SyncResult) { fun syncFavourites(syncResult: SyncResult) {
val data = JSONObject() val data = JSONObject()
@@ -71,7 +75,7 @@ class SyncHelper(
.url("$baseUrl/resource/$TABLE_FAVOURITES") .url("$baseUrl/resource/$TABLE_FAVOURITES")
.post(data.toRequestBody()) .post(data.toRequestBody())
.build() .build()
val response = httpClient.newCall(request).execute().parseJsonOrNull() val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
if (response != null) { if (response != null) {
val timestamp = response.getLong(FIELD_TIMESTAMP) val timestamp = response.getLong(FIELD_TIMESTAMP)
val categoriesResult = val categoriesResult =
@@ -93,7 +97,7 @@ class SyncHelper(
.url("$baseUrl/resource/$TABLE_HISTORY") .url("$baseUrl/resource/$TABLE_HISTORY")
.post(data.toRequestBody()) .post(data.toRequestBody())
.build() .build()
val response = httpClient.newCall(request).execute().parseJsonOrNull() val response = httpClient.newCall(request).execute().log().parseJsonOrNull()
if (response != null) { if (response != null) {
val result = upsertHistory( val result = upsertHistory(
json = response.getJSONArray(TABLE_HISTORY), json = response.getJSONArray(TABLE_HISTORY),
@@ -105,6 +109,19 @@ class SyncHelper(
gcHistory() gcHistory()
} }
fun onError(e: Throwable) {
if (logger.isEnabled) {
logger.log("Sync error", e)
}
}
fun onSyncComplete(result: SyncResult) {
if (logger.isEnabled) {
logger.log("Sync finshed: ${result.toDebugString()}")
logger.flushBlocking()
}
}
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> { private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(authorityHistory, TABLE_HISTORY) val uri = uri(authorityHistory, TABLE_HISTORY)
val operations = ArrayList<ContentProviderOperation>() val operations = ArrayList<ContentProviderOperation>()
@@ -298,4 +315,10 @@ class SyncHelper(
private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject
private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray
private fun Response.log() = apply {
if (logger.isEnabled) {
logger.log("$code ${request.url}")
}
}
} }

View File

@@ -14,8 +14,6 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import org.koitharu.kotatsu.core.db.* import org.koitharu.kotatsu.core.db.*
import org.koitharu.kotatsu.core.logs.FileLogger
import org.koitharu.kotatsu.core.logs.SyncLogger
import java.util.concurrent.Callable import java.util.concurrent.Callable
abstract class SyncProvider : ContentProvider() { abstract class SyncProvider : ContentProvider() {
@@ -24,7 +22,6 @@ abstract class SyncProvider : ContentProvider() {
EntryPointAccessors.fromApplication(checkNotNull(context), SyncProviderEntryPoint::class.java) EntryPointAccessors.fromApplication(checkNotNull(context), SyncProviderEntryPoint::class.java)
} }
private val database by lazy { entryPoint.database } private val database by lazy { entryPoint.database }
private val logger by lazy { entryPoint.logger }
private val supportedTables = setOf( private val supportedTables = setOf(
TABLE_FAVOURITES, TABLE_FAVOURITES,
@@ -52,7 +49,6 @@ abstract class SyncProvider : ContentProvider() {
.selection(selection, selectionArgs) .selection(selection, selectionArgs)
.orderBy(sortOrder) .orderBy(sortOrder)
.create() .create()
logger.log("query: ${sqlQuery.sql} (${selectionArgs.contentToString()})")
return database.openHelper.readableDatabase.query(sqlQuery) return database.openHelper.readableDatabase.query(sqlQuery)
} }
@@ -65,7 +61,6 @@ abstract class SyncProvider : ContentProvider() {
if (values == null || table == null) { if (values == null || table == null) {
return null return null
} }
logger.log { "insert: $table [$values]" }
val db = database.openHelper.writableDatabase val db = database.openHelper.writableDatabase
if (db.insert(table, SQLiteDatabase.CONFLICT_IGNORE, values) < 0) { if (db.insert(table, SQLiteDatabase.CONFLICT_IGNORE, values) < 0) {
db.update(table, values) db.update(table, values)
@@ -75,7 +70,6 @@ abstract class SyncProvider : ContentProvider() {
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int { override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
val table = getTableName(uri) ?: return 0 val table = getTableName(uri) ?: return 0
logger.log { "delete: $table ($selection) : (${selectionArgs.contentToString()})" }
return database.openHelper.writableDatabase.delete(table, selection, selectionArgs) return database.openHelper.writableDatabase.delete(table, selection, selectionArgs)
} }
@@ -84,7 +78,6 @@ abstract class SyncProvider : ContentProvider() {
if (values == null || table == null) { if (values == null || table == null) {
return 0 return 0
} }
logger.log { "update: $table ($selection) : (${selectionArgs.contentToString()}) [$values]" }
return database.openHelper.writableDatabase return database.openHelper.writableDatabase
.update(table, SQLiteDatabase.CONFLICT_IGNORE, values, selection, selectionArgs) .update(table, SQLiteDatabase.CONFLICT_IGNORE, values, selection, selectionArgs)
} }
@@ -127,8 +120,5 @@ abstract class SyncProvider : ContentProvider() {
interface SyncProviderEntryPoint { interface SyncProviderEntryPoint {
val database: MangaDatabase val database: MangaDatabase
@get:SyncLogger
val logger: FileLogger
} }
} }

View File

@@ -28,6 +28,10 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont
runCatchingCancellable { runCatchingCancellable {
syncHelper.syncFavourites(syncResult) syncHelper.syncFavourites(syncResult)
SyncController.setLastSync(context, account, authority, System.currentTimeMillis()) SyncController.setLastSync(context, account, authority, System.currentTimeMillis())
}.onFailure(syncResult::onError) }.onFailure { e ->
syncResult.onError(e)
syncHelper.onError(e)
}
syncHelper.onSyncComplete(syncResult)
} }
} }

View File

@@ -28,6 +28,10 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context
runCatchingCancellable { runCatchingCancellable {
syncHelper.syncHistory(syncResult) syncHelper.syncHistory(syncResult)
SyncController.setLastSync(context, account, authority, System.currentTimeMillis()) SyncController.setLastSync(context, account, authority, System.currentTimeMillis())
}.onFailure(syncResult::onError) }.onFailure { e ->
syncResult.onError(e)
syncHelper.onError(e)
}
syncHelper.onSyncComplete(syncResult)
} }
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.utils
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.widget.Toast
import androidx.core.app.ShareCompat import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
@@ -84,6 +85,7 @@ class ShareHelper(private val context: Context) {
fun shareLogs(loggers: Collection<FileLogger>) { fun shareLogs(loggers: Collection<FileLogger>) {
val intentBuilder = ShareCompat.IntentBuilder(context) val intentBuilder = ShareCompat.IntentBuilder(context)
.setType(TYPE_TEXT) .setType(TYPE_TEXT)
var hasLogs = false
for (logger in loggers) { for (logger in loggers) {
val logFile = logger.file val logFile = logger.file
if (!logFile.exists()) { if (!logFile.exists()) {
@@ -91,8 +93,13 @@ class ShareHelper(private val context: Context) {
} }
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", logFile) val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", logFile)
intentBuilder.addStream(uri) intentBuilder.addStream(uri)
hasLogs = true
}
if (hasLogs) {
intentBuilder.setChooserTitle(R.string.share_logs)
intentBuilder.startChooser()
} else {
Toast.makeText(context, R.string.nothing_here, Toast.LENGTH_SHORT).show()
} }
intentBuilder.setChooserTitle(R.string.share_logs)
intentBuilder.startChooser()
} }
} }