From 43a92bdf088451c11990cea8e4de6e6e8f666d9b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 17 May 2023 17:00:42 +0300 Subject: [PATCH] Improve sync logging --- .../koitharu/kotatsu/core/logs/FileLogger.kt | 21 +++++++++++++-- .../kotatsu/sync/domain/SyncHelper.kt | 27 +++++++++++++++++-- .../koitharu/kotatsu/sync/ui/SyncProvider.kt | 10 ------- .../ui/favourites/FavouritesSyncAdapter.kt | 6 ++++- .../sync/ui/history/HistorySyncAdapter.kt | 6 ++++- .../org/koitharu/kotatsu/utils/ShareHelper.kt | 11 ++++++-- 6 files changed, 63 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt b/app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt index 4ade0b3a6..8c46d4c01 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/logs/FileLogger.kt @@ -4,12 +4,15 @@ 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.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.processLifecycleScope @@ -82,6 +85,15 @@ class FileLogger( flushImpl() } + @WorkerThread + fun flushBlocking() { + if (!isEnabled) { + return + } + runBlockingSafe { flushJob?.cancelAndJoin() } + runBlockingSafe { flushImpl() } + } + private fun postFlush() { if (flushJob?.isActive == true) { return @@ -96,10 +108,10 @@ class FileLogger( } } - private suspend fun flushImpl() { + private suspend fun flushImpl() = withContext(NonCancellable) { mutex.withLock { if (buffer.isEmpty()) { - return + return@withContext } runInterruptible(Dispatchers.IO) { if (file.length() > MAX_SIZE_BYTES) { @@ -131,4 +143,9 @@ class FileLogger( } bakFile.delete() } + + private inline fun runBlockingSafe(crossinline block: suspend () -> Unit) = try { + runBlocking(NonCancellable) { block() } + } catch (_: InterruptedException) { + } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt index 1b0e5e958..089cb6a9c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt @@ -13,6 +13,7 @@ import androidx.annotation.WorkerThread import androidx.core.content.contentValuesOf import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.Response import org.json.JSONArray import org.json.JSONObject 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_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.prefs.AppSettings import org.koitharu.kotatsu.parsers.util.json.mapJSONTo import org.koitharu.kotatsu.sync.data.SyncAuthApi import org.koitharu.kotatsu.sync.data.SyncAuthenticator @@ -61,6 +64,7 @@ class SyncHelper( } private val defaultGcPeriod: Long // gc period if sync enabled get() = TimeUnit.DAYS.toMillis(4) + private val logger = LoggersModule.provideSyncLogger(context, AppSettings(context)) fun syncFavourites(syncResult: SyncResult) { val data = JSONObject() @@ -71,7 +75,7 @@ class SyncHelper( .url("$baseUrl/resource/$TABLE_FAVOURITES") .post(data.toRequestBody()) .build() - val response = httpClient.newCall(request).execute().parseJsonOrNull() + val response = httpClient.newCall(request).execute().log().parseJsonOrNull() if (response != null) { val timestamp = response.getLong(FIELD_TIMESTAMP) val categoriesResult = @@ -93,7 +97,7 @@ class SyncHelper( .url("$baseUrl/resource/$TABLE_HISTORY") .post(data.toRequestBody()) .build() - val response = httpClient.newCall(request).execute().parseJsonOrNull() + val response = httpClient.newCall(request).execute().log().parseJsonOrNull() if (response != null) { val result = upsertHistory( json = response.getJSONArray(TABLE_HISTORY), @@ -105,6 +109,19 @@ class SyncHelper( 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 { val uri = uri(authorityHistory, TABLE_HISTORY) val operations = ArrayList() @@ -298,4 +315,10 @@ class SyncHelper( private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray + + private fun Response.log() = apply { + if (logger.isEnabled) { + logger.log("$code ${request.url}") + } + } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncProvider.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncProvider.kt index eabd8cd0e..3e9d75c85 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/SyncProvider.kt @@ -14,8 +14,6 @@ import dagger.hilt.InstallIn import dagger.hilt.android.EntryPointAccessors import dagger.hilt.components.SingletonComponent 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 abstract class SyncProvider : ContentProvider() { @@ -24,7 +22,6 @@ abstract class SyncProvider : ContentProvider() { EntryPointAccessors.fromApplication(checkNotNull(context), SyncProviderEntryPoint::class.java) } private val database by lazy { entryPoint.database } - private val logger by lazy { entryPoint.logger } private val supportedTables = setOf( TABLE_FAVOURITES, @@ -52,7 +49,6 @@ abstract class SyncProvider : ContentProvider() { .selection(selection, selectionArgs) .orderBy(sortOrder) .create() - logger.log("query: ${sqlQuery.sql} (${selectionArgs.contentToString()})") return database.openHelper.readableDatabase.query(sqlQuery) } @@ -65,7 +61,6 @@ abstract class SyncProvider : ContentProvider() { if (values == null || table == null) { return null } - logger.log { "insert: $table [$values]" } val db = database.openHelper.writableDatabase if (db.insert(table, SQLiteDatabase.CONFLICT_IGNORE, values) < 0) { db.update(table, values) @@ -75,7 +70,6 @@ abstract class SyncProvider : ContentProvider() { override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { val table = getTableName(uri) ?: return 0 - logger.log { "delete: $table ($selection) : (${selectionArgs.contentToString()})" } return database.openHelper.writableDatabase.delete(table, selection, selectionArgs) } @@ -84,7 +78,6 @@ abstract class SyncProvider : ContentProvider() { if (values == null || table == null) { return 0 } - logger.log { "update: $table ($selection) : (${selectionArgs.contentToString()}) [$values]" } return database.openHelper.writableDatabase .update(table, SQLiteDatabase.CONFLICT_IGNORE, values, selection, selectionArgs) } @@ -127,8 +120,5 @@ abstract class SyncProvider : ContentProvider() { interface SyncProviderEntryPoint { val database: MangaDatabase - - @get:SyncLogger - val logger: FileLogger } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt index cdeacf2db..ff0157857 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/favourites/FavouritesSyncAdapter.kt @@ -28,6 +28,10 @@ class FavouritesSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(cont runCatchingCancellable { syncHelper.syncFavourites(syncResult) SyncController.setLastSync(context, account, authority, System.currentTimeMillis()) - }.onFailure(syncResult::onError) + }.onFailure { e -> + syncResult.onError(e) + syncHelper.onError(e) + } + syncHelper.onSyncComplete(syncResult) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt index d56529bb1..9297ea5fc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/ui/history/HistorySyncAdapter.kt @@ -28,6 +28,10 @@ class HistorySyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context runCatchingCancellable { syncHelper.syncHistory(syncResult) SyncController.setLastSync(context, account, authority, System.currentTimeMillis()) - }.onFailure(syncResult::onError) + }.onFailure { e -> + syncResult.onError(e) + syncHelper.onError(e) + } + syncHelper.onSyncComplete(syncResult) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt index 8535fbed3..3bdbc5ce3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ShareHelper.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.utils import android.content.Context import android.net.Uri +import android.widget.Toast import androidx.core.app.ShareCompat import androidx.core.content.FileProvider import org.koitharu.kotatsu.BuildConfig @@ -84,6 +85,7 @@ class ShareHelper(private val context: Context) { fun shareLogs(loggers: Collection) { val intentBuilder = ShareCompat.IntentBuilder(context) .setType(TYPE_TEXT) + var hasLogs = false for (logger in loggers) { val logFile = logger.file if (!logFile.exists()) { @@ -91,8 +93,13 @@ class ShareHelper(private val context: Context) { } val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.files", logFile) 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() } }