Skip to content

Commit

Permalink
Merge branch 'master' into 5023-submission-search
Browse files Browse the repository at this point in the history
  • Loading branch information
dogi authored Jan 17, 2025
2 parents c64c586 + a4b3028 commit b8f5d47
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 45 deletions.
74 changes: 63 additions & 11 deletions app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,85 @@
package org.ole.planet.myplanet.datamanager

import com.google.gson.GsonBuilder
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.IOException
import java.lang.reflect.Modifier
import java.util.concurrent.TimeUnit
import kotlin.math.pow

object ApiClient {
private const val BASE_URL = "https://vi.media.mit.edu/"
private var retrofit: Retrofit? = null
@JvmStatic
val client: Retrofit?
get() {
val client = OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).build()
val client = OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES).readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS).addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Accept-Encoding", "gzip").build()
chain.proceed(request)
}
.retryOnConnectionFailure(true).addInterceptor(RetryInterceptor()).build()
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder()
.excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC)
.serializeNulls()
.create()
.baseUrl(BASE_URL).client(client)
.addConverterFactory(GsonConverterFactory.create(
GsonBuilder()
.excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC)
.serializeNulls()
.create()
)
).build()
}
return retrofit
}
}

class RetryInterceptor : Interceptor {
val maxRetryCount = 3
val retryDelayMillis = 1000L

override fun intercept(chain: Interceptor.Chain): Response {
var attempt = 0
var response: Response? = null
val request = chain.request()
val url = request.url().toString()

while (true) {
try {
response?.close()
response = chain.proceed(request)

when (response.code()) {
in 200..299 -> {
return response
}
404 -> {
return response
}
401, 403 -> {
response.close()
throw IOException("Authentication failed: ${response.code()}")
}
else -> {
response.close()
throw IOException("Response unsuccessful: ${response.code()}")
}
}
} catch (e: IOException) {
attempt++

if (attempt >= maxRetryCount) {
throw IOException("Failed after $maxRetryCount attempts: $url", e)
}

val delayMillis = retryDelayMillis * 2.0.pow((attempt - 1).toDouble()).toLong()
Thread.sleep(delayMillis)
}
}
}
}
}
140 changes: 109 additions & 31 deletions app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class SyncManager private constructor(private val context: Context) {
settings.edit().putString("LastWifiSSID", wifiInfo.ssid).apply()
}
isSyncing = true
create(context, R.mipmap.ic_launcher, " Syncing data", "Please wait...")
create(context, R.mipmap.ic_launcher, "Syncing data", "Please wait...")
mRealm = dbService.realmInstance
TransactionSyncManager.syncDb(mRealm, "tablet_users")
myLibraryTransactionSync()
Expand Down Expand Up @@ -144,50 +144,111 @@ class SyncManager private constructor(private val context: Context) {
}
}

@Throws(IOException::class)
private fun syncResource(dbClient: ApiInterface?) {
val newIds: MutableList<String?> = ArrayList()
val allDocs = dbClient?.getJsonObject(Utilities.header, Utilities.getUrl() + "/resources/_all_docs?include_doc=false")
val all = allDocs?.execute()
val rows = getJsonArray("rows", all?.body())
val keys: MutableList<String> = ArrayList()
for (i in 0 until rows.size()) {
val `object` = rows[i].asJsonObject
if (!TextUtils.isEmpty(getString("id", `object`))) keys.add(getString("key", `object`))
if (i == rows.size() - 1 || keys.size == 1000) {
val obj = JsonObject()
obj.add("keys", Gson().fromJson(Gson().toJson(keys), JsonArray::class.java))
val response = dbClient?.findDocs(Utilities.header, "application/json", Utilities.getUrl() + "/resources/_all_docs?include_docs=true", obj)?.execute()
if (response?.body() != null) {
val ids: List<String?> = save(getJsonArray("rows", response.body()), mRealm)
newIds.addAll(ids)
try {
val allDocs = dbClient?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/resources/_all_docs?include_doc=false")
val all = allDocs?.execute()
if (all?.isSuccessful != true) {
return
}

val rows = getJsonArray("rows", all.body())
val keys: MutableList<String> = ArrayList()
val failedIds: MutableList<String> = ArrayList()

for (i in 0 until rows.size()) {
val `object` = rows[i].asJsonObject
if (!TextUtils.isEmpty(getString("id", `object`))) {
keys.add(getString("key", `object`))
}

if (i == rows.size() - 1 || keys.size == 1000) {
val obj = JsonObject()
obj.add("keys", Gson().fromJson(Gson().toJson(keys), JsonArray::class.java))
val response = dbClient.findDocs(Utilities.header, "application/json", "${Utilities.getUrl()}/resources/_all_docs?include_docs=true", obj).execute()

when {
response.isSuccessful == true -> {
response.body()?.let { body ->
val ids: List<String?> = save(getJsonArray("rows", body), mRealm)
newIds.addAll(ids)
}
}
response.code() == 404 -> {
failedIds.addAll(keys)
}
else -> {
val errorMessage = "Failed to sync resources: ${response.code()}"
handleException(errorMessage)

when (response.code()) {
in 500..599 -> {
addToRetryQueue(keys)
}
401, 403 -> {
handleAuthenticationError()
}
else -> {
failedIds.addAll(keys)
}
}
}
}
keys.clear()
}
keys.clear()
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
removeDeletedResource(newIds, mRealm)
} catch (e: Exception) {
e.printStackTrace()
}
}
removeDeletedResource(newIds, mRealm)
}

private fun addToRetryQueue(keys: List<String>) {
settings.edit().apply {
val existingQueue = settings.getStringSet("retry_queue", setOf()) ?: setOf()
putStringSet("retry_queue", existingQueue + keys)
apply()
}
}

private fun handleAuthenticationError() {
settings.edit().remove("credentials").apply()
handleException("Authentication failed.")
}

private fun myLibraryTransactionSync() {
val apiInterface = client?.create(ApiInterface::class.java)
try {
val res = apiInterface?.getDocuments(Utilities.header, Utilities.getUrl() + "/shelf/_all_docs")?.execute()?.body()
for (i in res?.rows!!.indices) {
shelfDoc = res.rows!![i]
populateShelfItems(apiInterface)
val response = apiInterface?.getDocuments(Utilities.header, "${Utilities.getUrl()}/shelf/_all_docs")?.execute()

val res = response?.body()
res?.rows?.let { rows ->
for (i in rows.indices) {
shelfDoc = rows[i]
populateShelfItems(apiInterface)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}

private fun populateShelfItems(apiInterface: ApiInterface) {
private fun populateShelfItems(apiInterface: ApiInterface?) {
try {
val jsonDoc = apiInterface.getJsonObject(Utilities.header, Utilities.getUrl() + "/shelf/" + shelfDoc?.id).execute().body()
for (i in Constants.shelfDataList.indices) {
val shelfData = Constants.shelfDataList[i]
val array = getJsonArray(shelfData.key, jsonDoc)
memberShelfData(array, shelfData)
val response = apiInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/shelf/${shelfDoc?.id}")?.execute()

response?.body()?.let { jsonDoc ->
for (i in Constants.shelfDataList.indices) {
val shelfData = Constants.shelfDataList[i]
val array = getJsonArray(shelfData.key, jsonDoc)
memberShelfData(array, shelfData)
}
}
} catch (err: Exception) {
err.printStackTrace()
Expand Down Expand Up @@ -217,10 +278,27 @@ class SyncManager private constructor(private val context: Context) {
}

private fun validateDocument(arrayCategoryIds: JsonArray, x: Int) {
val apiInterface = client!!.create(ApiInterface::class.java)
val apiInterface = client?.create(ApiInterface::class.java)
try {
val resourceDoc = apiInterface.getJsonObject(Utilities.header, Utilities.getUrl() + "/" + stringArray[2] + "/" + arrayCategoryIds[x].asString).execute().body()
resourceDoc?.let { triggerInsert(stringArray, it) }
val response = apiInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/${stringArray[2]}/${arrayCategoryIds[x].asString}")?.execute()

when {
response?.isSuccessful == true -> {
response.body()?.let { resourceDoc ->
triggerInsert(stringArray, resourceDoc)
}
}
response?.code() == 404 -> {
return
}
else -> {
val errorMessage = "Failed to validate document: ${response?.code()}"
handleException(errorMessage)
if (response?.code() in 500..599) {
throw IOException(errorMessage)
}
}
}
} catch (e: IOException) {
e.printStackTrace()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class CoursesFragment : BaseRecyclerFragment<RealmMyCourse?>(), OnCourseItemSele
spnGrade.adapter = gradeAdapter

val subjectAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.subject_level, R.layout.spinner_item)
subjectAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
subjectAdapter.setDropDownViewResource(R.layout.custom_simple_list_item_1)
spnSubject.adapter = subjectAdapter

spnGrade.onItemSelectedListener = itemSelectedListener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ object JsonUtils {
return try {
if (jsonObject?.has(fieldName) == true) {
val el: JsonElement = jsonObject.get(fieldName)
if (el is JsonNull) 0 else el.asInt
if (el is JsonNull || el.asString.isEmpty()) 0 else el.asInt
} else {
0
}
Expand All @@ -107,7 +107,7 @@ object JsonUtils {
return try {
if (jsonObject?.has(fieldName) == true) {
val el: JsonElement = jsonObject.get(fieldName)
if (el is JsonNull) 0f else el.asFloat
if (el is JsonNull || el.asString.isEmpty()) 0f else el.asFloat
} else {
getInt(fieldName, jsonObject).toFloat()
}
Expand Down

0 comments on commit b8f5d47

Please sign in to comment.