Skip to content

Commit

Permalink
Cleanup sync code
Browse files Browse the repository at this point in the history
  • Loading branch information
jobobby04 committed Mar 16, 2024
1 parent 54cb379 commit d70258b
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority
import logcat.logcat
import tachiyomi.core.common.util.system.logcat
import tachiyomi.data.Chapters
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.manga.MangaMapper.mapManga
Expand Down Expand Up @@ -196,7 +197,7 @@ class SyncManager(
Uri.fromFile(cacheFile)
}
} catch (e: IOException) {
logcat(LogPriority.ERROR) { "Failed to write sync data to cache" }
logcat(LogPriority.ERROR, throwable = e) { "Failed to write sync data to cache" }
null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.util.Log
import com.google.api.client.auth.oauth2.TokenResponseException
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest
Expand All @@ -19,21 +18,19 @@ import com.google.api.services.drive.Drive
import com.google.api.services.drive.DriveScopes
import com.google.api.services.drive.model.File
import eu.kanade.domain.sync.SyncPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import logcat.LogPriority
import logcat.logcat
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStreamReader
import java.time.Instant
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
Expand Down Expand Up @@ -118,8 +115,8 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
throw Exception(context.stringResource(MR.strings.error_before_sync_gdrive) + ": Max retries reached.")
}
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Error in GoogleDrive beforeSync: ${e.message}" }
throw Exception(context.stringResource(MR.strings.error_before_sync_gdrive) + ": ${e.message}")
logcat(LogPriority.ERROR, throwable = e) { "Error in GoogleDrive beforeSync" }
throw Exception(context.stringResource(MR.strings.error_before_sync_gdrive) + ": ${e.message}", e)
}
}

Expand All @@ -145,22 +142,23 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
drive.files().get(gdriveFileId).executeMediaAndDownloadTo(outputStream)
logcat(LogPriority.DEBUG) { "File downloaded successfully" }
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Error downloading file: ${e.message}" }
logcat(LogPriority.ERROR, throwable = e) { "Error downloading file" }
return null
}

return withContext(Dispatchers.IO) {
return withIOContext {
try {
val gzipInputStream = GZIPInputStream(ByteArrayInputStream(outputStream.toByteArray()))
val jsonString = gzipInputStream.bufferedReader(Charsets.UTF_8).use { it.readText() }
val gzipInputStream = GZIPInputStream(outputStream.toByteArray().inputStream())
val jsonString = gzipInputStream.bufferedReader().use { it.readText() }
val syncData = json.decodeFromString(SyncData.serializer(), jsonString)
logcat(LogPriority.DEBUG) { "JSON deserialized successfully" }
this@GoogleDriveSyncService.logcat(LogPriority.DEBUG) { "JSON deserialized successfully" }
syncData
} catch (e: Exception) {
logcat(
this@GoogleDriveSyncService.logcat(
LogPriority.ERROR,
) { "Failed to convert json to sync data with kotlinx.serialization: ${e.message}" }
throw Exception(e.message)
throwable = e,
) { "Failed to convert json to sync data with kotlinx.serialization" }
throw Exception(e.message, e)
}
}
}
Expand All @@ -172,11 +170,11 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync

val fileList = getAppDataFileList(drive)
val byteArrayOutputStream = ByteArrayOutputStream()
withContext(Dispatchers.IO) {
withIOContext {
GZIPOutputStream(byteArrayOutputStream).use { gzipOutputStream ->
gzipOutputStream.write(jsonData.toByteArray(Charsets.UTF_8))
}
logcat(LogPriority.DEBUG) { "JSON serialized successfully" }
this@GoogleDriveSyncService.logcat(LogPriority.DEBUG) { "JSON serialized successfully" }
}

val byteArrayContent = ByteArrayContent("application/octet-stream", byteArrayOutputStream.toByteArray())
Expand Down Expand Up @@ -206,8 +204,8 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
// Data has been successfully pushed or updated, delete the lock file
deleteLockFile(drive)
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Failed to push or update sync data: ${e.message}" }
throw Exception(context.stringResource(MR.strings.error_uploading_sync_data) + ": ${e.message}")
logcat(LogPriority.ERROR, throwable = e) { "Failed to push or update sync data" }
throw Exception(context.stringResource(MR.strings.error_uploading_sync_data) + ": ${e.message}", e)
}
}

Expand All @@ -222,11 +220,11 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
.setFields("files(id, name, createdTime)")
.execute()
.files
Log.d("GoogleDrive", "AppData folder file list: $fileList")
logcat { "AppData folder file list: $fileList" }

return fileList
} catch (e: Exception) {
Log.e("GoogleDrive", "Error no sync data found in appData folder: ${e.message}")
logcat(LogPriority.ERROR, throwable = e) { "Error no sync data found in appData folder" }
return mutableListOf()
}
}
Expand All @@ -246,27 +244,27 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
.setFields("id, name, createdTime")
.execute()

Log.d("GoogleDrive", "Created lock file with ID: ${file.id}")
logcat { "Created lock file with ID: ${file.id}" }
} catch (e: Exception) {
Log.e("GoogleDrive", "Error creating lock file: ${e.message}")
throw Exception(e.message)
logcat(LogPriority.ERROR, throwable = e) { "Error creating lock file" }
throw Exception(e.message, e)
}
}

private fun findLockFile(drive: Drive): MutableList<File> {
try {
return try {
val query = "mimeType='text/plain' and name = '$lockFileName'"
val fileList = drive.files()
.list()
.setSpaces("appDataFolder")
.setQ(query)
.setFields("files(id, name, createdTime)")
.execute().files
Log.d("GoogleDrive", "Lock file search result: $fileList")
return fileList
logcat { "Lock file search result: $fileList" }
fileList
} catch (e: Exception) {
Log.e("GoogleDrive", "Error finding lock file: ${e.message}")
return mutableListOf()
logcat(LogPriority.ERROR, throwable = e) { "Error finding lock file" }
mutableListOf()
}
}

Expand All @@ -277,14 +275,14 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
if (lockFiles.isNotEmpty()) {
for (file in lockFiles) {
drive.files().delete(file.id).execute()
Log.d("GoogleDrive", "Deleted lock file with ID: ${file.id}")
logcat { "Deleted lock file with ID: ${file.id}" }
}
} else {
Log.d("GoogleDrive", "No lock file found to delete.")
logcat { "No lock file found to delete." }
}
} catch (e: Exception) {
Log.e("GoogleDrive", "Error deleting lock file: ${e.message}")
throw Exception(context.stringResource(MR.strings.error_deleting_google_drive_lock_file))
logcat(LogPriority.ERROR, throwable = e) { "Error deleting lock file" }
throw Exception(context.stringResource(MR.strings.error_deleting_google_drive_lock_file), e)
}
}

Expand All @@ -297,24 +295,27 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
}
googleDriveService.refreshToken()

return withContext(Dispatchers.IO) {
return withIOContext {
try {
val appDataFileList = getAppDataFileList(drive)

if (appDataFileList.isEmpty()) {
logcat(LogPriority.DEBUG) { "No sync data file found in appData folder of Google Drive" }
this@GoogleDriveSyncService
.logcat(LogPriority.DEBUG) { "No sync data file found in appData folder of Google Drive" }
DeleteSyncDataStatus.NO_FILES
} else {
for (file in appDataFileList) {
drive.files().delete(file.id).execute()
logcat(
this@GoogleDriveSyncService.logcat(
LogPriority.DEBUG,
) { "Deleted sync data file in appData folder of Google Drive with file ID: ${file.id}" }
}
DeleteSyncDataStatus.SUCCESS
}
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Error occurred while interacting with Google Drive: ${e.message}" }
this@GoogleDriveSyncService.logcat(LogPriority.ERROR, throwable = e) {
"Error occurred while interacting with Google Drive"
}
DeleteSyncDataStatus.ERROR
}
}
Expand Down Expand Up @@ -374,7 +375,7 @@ class GoogleDriveService(private val context: Context) {
val jsonFactory: JsonFactory = JacksonFactory.getDefaultInstance()
val secrets = GoogleClientSecrets.load(
jsonFactory,
InputStreamReader(context.assets.open("client_secrets.json")),
context.assets.open("client_secrets.json").reader(),
)

val flow = GoogleAuthorizationCodeFlow.Builder(
Expand All @@ -389,14 +390,14 @@ class GoogleDriveService(private val context: Context) {
.setApprovalPrompt("force")
.build()
}
internal suspend fun refreshToken() = withContext(Dispatchers.IO) {
internal suspend fun refreshToken() = withIOContext {
val refreshToken = syncPreferences.googleDriveRefreshToken().get()
val accessToken = syncPreferences.googleDriveAccessToken().get()

val jsonFactory: JsonFactory = JacksonFactory.getDefaultInstance()
val secrets = GoogleClientSecrets.load(
jsonFactory,
InputStreamReader(context.assets.open("client_secrets.json")),
context.assets.open("client_secrets.json").reader(),
)

val credential = GoogleCredential.Builder()
Expand All @@ -411,31 +412,34 @@ class GoogleDriveService(private val context: Context) {

credential.refreshToken = refreshToken

logcat(LogPriority.DEBUG) { "Refreshing access token with: $refreshToken" }
this@GoogleDriveService.logcat(LogPriority.DEBUG) { "Refreshing access token with: $refreshToken" }

try {
credential.refreshToken()
val newAccessToken = credential.accessToken
// Save the new access token
syncPreferences.googleDriveAccessToken().set(newAccessToken)
setupGoogleDriveService(newAccessToken, credential.refreshToken)
logcat(LogPriority.DEBUG) { "Google Access token refreshed old: $accessToken new: $newAccessToken" }
this@GoogleDriveService
.logcat(LogPriority.DEBUG) { "Google Access token refreshed old: $accessToken new: $newAccessToken" }
} catch (e: TokenResponseException) {
if (e.details.error == "invalid_grant") {
// The refresh token is invalid, prompt the user to sign in again
logcat(LogPriority.ERROR) { "Refresh token is invalid, prompt user to sign in again" }
throw e.message?.let { Exception(it) } ?: Exception("Unknown error")
this@GoogleDriveService.logcat(LogPriority.ERROR, throwable = e) {
"Refresh token is invalid, prompt user to sign in again"
}
throw e.message?.let { Exception(it, e) } ?: Exception("Unknown error", e)
} else {
// Token refresh failed; handle this situation
logcat(LogPriority.ERROR) { "Failed to refresh access token ${e.message}" }
logcat(LogPriority.ERROR) { "Google Drive sync will be disabled" }
throw e.message?.let { Exception(it) } ?: Exception("Unknown error")
this@GoogleDriveService.logcat(LogPriority.ERROR) { "Failed to refresh access token ${e.message}" }
this@GoogleDriveService.logcat(LogPriority.ERROR) { "Google Drive sync will be disabled" }
throw e.message?.let { Exception(it, e) } ?: Exception("Unknown error", e)
}
} catch (e: IOException) {
// Token refresh failed; handle this situation
logcat(LogPriority.ERROR) { "Failed to refresh access token ${e.message}" }
logcat(LogPriority.ERROR) { "Google Drive sync will be disabled" }
throw e.message?.let { Exception(it) } ?: Exception("Unknown error")
this@GoogleDriveService.logcat(LogPriority.ERROR, throwable = e) { "Failed to refresh access token" }
this@GoogleDriveService.logcat(LogPriority.ERROR) { "Google Drive sync will be disabled" }
throw e.message?.let { Exception(it, e) } ?: Exception("Unknown error", e)
}
}

Expand All @@ -448,7 +452,7 @@ class GoogleDriveService(private val context: Context) {
val jsonFactory: JsonFactory = JacksonFactory.getDefaultInstance()
val secrets = GoogleClientSecrets.load(
jsonFactory,
InputStreamReader(context.assets.open("client_secrets.json")),
context.assets.open("client_secrets.json").reader(),
)

val credential = GoogleCredential.Builder()
Expand Down Expand Up @@ -487,7 +491,7 @@ class GoogleDriveService(private val context: Context) {
val jsonFactory: JsonFactory = JacksonFactory.getDefaultInstance()
val secrets = GoogleClientSecrets.load(
jsonFactory,
InputStreamReader(context.assets.open("client_secrets.json")),
context.assets.open("client_secrets.json").reader(),
)

val tokenResponse: GoogleTokenResponse = GoogleAuthorizationCodeTokenRequest(
Expand Down Expand Up @@ -515,6 +519,7 @@ class GoogleDriveService(private val context: Context) {
onSuccess()
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, throwable = e) { "Failed to handle authorization code" }
activity.runOnUiThread {
onFailure(e.localizedMessage ?: "Unknown error")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.sync.SyncNotifier
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.PATCH
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import kotlinx.coroutines.delay
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand Down Expand Up @@ -104,15 +105,15 @@ class SyncYomiSyncService(
)

// create lock file first
client.newCall(lockFileCreate).execute()
client.newCall(lockFileCreate).await()
// update lock file acquired_by
client.newCall(lockFileUpdate).execute()
client.newCall(lockFileUpdate).await()

var backoff = 2000L // Start with 2 seconds
val maxBackoff = 32000L // Maximum backoff time e.g., 32 seconds
var lockFile: LockFile
do {
val response = client.newCall(lockFileRequest).execute()
val response = client.newCall(lockFileRequest).await()
val responseBody = response.body.string()
lockFile = json.decodeFromString<LockFile>(responseBody)
logcat(LogPriority.DEBUG) { "SyncYomi lock file status: ${lockFile.status}" }
Expand All @@ -125,7 +126,7 @@ class SyncYomiSyncService(
} while (lockFile.status != SyncStatus.Success)

// update lock file acquired_by
client.newCall(lockFileUpdate).execute()
client.newCall(lockFileUpdate).await()
}

override suspend fun pullSyncData(): SyncData? {
Expand All @@ -141,16 +142,15 @@ class SyncYomiSyncService(
headers = headers,
)

client.newCall(downloadRequest).execute().use { response ->
val responseBody = response.body.string()
val response = client.newCall(downloadRequest).await()
val responseBody = response.body.string()

if (response.isSuccessful) {
return json.decodeFromString<SyncData>(responseBody)
} else {
notifier.showSyncError("Failed to download sync data: $responseBody")
responseBody.let { logcat(LogPriority.ERROR) { "SyncError:$it" } }
return null
}
return if (response.isSuccessful) {
json.decodeFromString<SyncData>(responseBody)
} else {
notifier.showSyncError("Failed to download sync data: $responseBody")
responseBody.let { logcat(LogPriority.ERROR) { "SyncError:$it" } }
null
}
}

Expand Down Expand Up @@ -183,7 +183,7 @@ class SyncYomiSyncService(
body = body,
)

client.newCall(uploadRequest).execute().use {
client.newCall(uploadRequest).await().use {
if (it.isSuccessful) {
logcat(
LogPriority.DEBUG,
Expand Down

0 comments on commit d70258b

Please sign in to comment.