Improve sync logging
This commit is contained in:
@@ -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) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user