Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions app/src/main/java/org/ole/planet/myplanet/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.os.Bundle
import android.os.StrictMode
import android.os.StrictMode.VmPolicy
import android.provider.Settings
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceManager
import androidx.work.ExistingPeriodicWorkPolicy
Expand Down Expand Up @@ -125,36 +126,55 @@ class MainApplication : Application(), Application.ActivityLifecycleCallbacks {
}

suspend fun isServerReachable(urlString: String): Boolean {
Log.d("PerformanceLog", "[IS_SERVER_REACHABLE_START: $urlString] Time: ${System.currentTimeMillis()}")
val startTime = System.currentTimeMillis()

val serverUrlMapper = ServerUrlMapper(context)
val mapping = serverUrlMapper.processUrl(urlString)

val urlsToTry = mutableListOf(urlString)
mapping.alternativeUrl?.let { urlsToTry.add(it) }

return try {
if (urlString.isBlank()) return false
if (urlString.isBlank()) {
Log.d("PerformanceLog", "[IS_SERVER_REACHABLE_BLANK_URL: $urlString] Time: ${System.currentTimeMillis()}")
return false
}

val formattedUrl = if (!urlString.startsWith("http://") && !urlString.startsWith("https://")) {
"http://$urlString"
} else {
urlString
}

Log.d("PerformanceLog", "[BEFORE_URL_CONNECTION: $formattedUrl] Time: ${System.currentTimeMillis()}")
val url = URL(formattedUrl)
val connection = withContext(Dispatchers.IO) {
url.openConnection()
} as HttpURLConnection

Log.d("PerformanceLog", "[AFTER_URL_CONNECTION: $formattedUrl] Time: ${System.currentTimeMillis()}")
connection.requestMethod = "GET"
connection.connectTimeout = 5000
connection.readTimeout = 5000

Log.d("PerformanceLog", "[BEFORE_CONNECT: $formattedUrl] Time: ${System.currentTimeMillis()}")
withContext(Dispatchers.IO) {
connection.connect()
}
Log.d("PerformanceLog", "[AFTER_CONNECT: $formattedUrl] Time: ${System.currentTimeMillis()}")

val responseCode = connection.responseCode
Log.d("PerformanceLog", "[GOT_RESPONSE_CODE: $formattedUrl] Code: $responseCode, Time: ${System.currentTimeMillis()}")

connection.disconnect()
responseCode in 200..299
val result = responseCode in 200..299

Log.d("PerformanceLog", "[IS_SERVER_REACHABLE_END: $urlString] Time: ${System.currentTimeMillis()}, Duration: ${System.currentTimeMillis() - startTime} ms, Result: $result")
result
} catch (e: Exception) {
val endTime = System.currentTimeMillis()
Log.d("PerformanceLog", "[IS_SERVER_REACHABLE_ERROR: $urlString] Time: $endTime, Duration: ${endTime - startTime} ms, Error: ${e.javaClass.simpleName}: ${e.message}")
e.printStackTrace()
false
}
Expand Down Expand Up @@ -219,6 +239,7 @@ class MainApplication : Application(), Application.ActivityLifecycleCallbacks {
override fun onAppNotResponding(message: String, blockedThread: Thread, duration: Long) {
applicationScope.launch {
createLog("anr", "ANR detected! Duration: ${duration}ms\n $message")
Log.d("ANRWatchdog", "ANR detected! Duration: ${duration}ms\n $message")
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,10 @@ abstract class BaseNewsFragment : BaseContainerFragment(), OnNewsItemClickListen
if (context is OnHomeItemClickListener) homeItemClickListener = context
}

override fun onDestroy() {
super.onDestroy()
profileDbHandler.onDestroy()
}
// override fun onDestroy() {
// super.onDestroy()
// profileDbHandler.onDestroy()
// }

override fun showReply(news: RealmNews?, fromLogin: Boolean, nonTeamMember: Boolean) {
if (news != null) {
Expand Down
128 changes: 111 additions & 17 deletions app/src/main/java/org/ole/planet/myplanet/datamanager/Service.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.SharedPreferences
import android.net.Uri
import android.os.*
import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.realm.Realm
Expand Down Expand Up @@ -55,11 +56,13 @@ import java.util.concurrent.Executors
import kotlin.math.min
import androidx.core.net.toUri
import androidx.core.content.edit
import java.util.concurrent.ConcurrentHashMap

class Service(private val context: Context) {
private val preferences: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
private val retrofitInterface: ApiInterface? = ApiClient.client?.create(ApiInterface::class.java)
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val serverAvailabilityCache = ConcurrentHashMap<String, Pair<Boolean, Long>>()

fun healthAccess(listener: SuccessListener) {
retrofitInterface?.healthAccess(Utilities.getHealthAccessUrl(preferences))?.enqueue(object : Callback<ResponseBody> {
Expand Down Expand Up @@ -176,35 +179,69 @@ class Service(private val context: Context) {
}

fun isPlanetAvailable(callback: PlanetAvailableListener?) {
Log.d("PerformanceLog", "[IS_PLANET_AVAILABLE_START] Time: ${System.currentTimeMillis()}")

val updateUrl = "${preferences.getString("serverURL", "")}"

// Check cache first
serverAvailabilityCache[updateUrl]?.let { (available, timestamp) ->
// Use cached result if less than 30 seconds old
if (System.currentTimeMillis() - timestamp < 30000) {
Log.d("PerformanceLog", "[IS_PLANET_AVAILABLE_CACHED] Time: ${System.currentTimeMillis()}, Cached Value: $available")
if (available) {
callback?.isAvailable()
} else {
callback?.notAvailable()
}
return
}
}

val serverUrlMapper = ServerUrlMapper(context)
val mapping = serverUrlMapper.processUrl(updateUrl)

CoroutineScope(Dispatchers.IO).launch {
Log.d("PerformanceLog", "[SERVER_REACHABLE_CHECK_START] Time: ${System.currentTimeMillis()}")

val primaryAvailable = isServerReachable(mapping.primaryUrl)
val alternativeAvailable = mapping.alternativeUrl?.let { isServerReachable(it) } == true

Log.d("PerformanceLog", "[SERVER_REACHABLE_CHECK_END] Time: ${System.currentTimeMillis()}")

if (!primaryAvailable && alternativeAvailable) {
mapping.alternativeUrl.let { alternativeUrl ->
val uri = updateUrl.toUri()
val editor = preferences.edit()

serverUrlMapper.updateUrlPreferences(editor, uri, alternativeUrl, mapping.primaryUrl, preferences)
}
}

withContext(Dispatchers.Main) {
Log.d("PerformanceLog", "[BEFORE_IS_PLANET_AVAILABLE_RETROFIT] Time: ${System.currentTimeMillis()}")

retrofitInterface?.isPlanetAvailable(Utilities.getUpdateUrl(preferences))
?.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(call: Call<ResponseBody?>, response: Response<ResponseBody?>) {
if (callback != null && response.code() == 200) {
Log.d("PerformanceLog", "[IS_PLANET_AVAILABLE_RESPONSE] Time: ${System.currentTimeMillis()}")

val isAvailable = callback != null && response.code() == 200

// Update cache
serverAvailabilityCache[updateUrl] = Pair(isAvailable, System.currentTimeMillis())

if (isAvailable) {
callback.isAvailable()
} else {
callback?.notAvailable()
}
}

override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
Log.d("PerformanceLog", "[IS_PLANET_AVAILABLE_FAILURE] Time: ${System.currentTimeMillis()}")

// Update cache with failure
serverAvailabilityCache[updateUrl] = Pair(false, System.currentTimeMillis())

callback?.notAvailable()
}
})
Expand All @@ -213,51 +250,55 @@ class Service(private val context: Context) {
}

fun becomeMember(realm: Realm, obj: JsonObject, callback: CreateUserCallback) {
Log.d("PerformanceLog", "[BECOME_MEMBER_START] Time: ${System.currentTimeMillis()}")

isPlanetAvailable(object : PlanetAvailableListener {
override fun isAvailable() {
val settings = MainApplication.context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
if (isUserExists(realm, obj["name"].asString)) {
callback.onSuccess(context.getString(R.string.unable_to_create_user_user_already_exists))
return
}
realm.beginTransaction()
val model = populateUsersTable(obj, realm, settings)
val keyString = generateKey()
val iv = generateIv()
if (model != null) {
model.key = keyString
model.iv = iv
}
realm.commitTransaction()
Log.d("PerformanceLog", "[PLANET_AVAILABLE] Time: ${System.currentTimeMillis()}")

retrofitInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/_users/org.couchdb.user:${obj["name"].asString}")?.enqueue(object : Callback<JsonObject> {
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
Log.d("PerformanceLog", "[USER_CHECK_RESPONSE] Time: ${System.currentTimeMillis()}")

if (response.body() != null && response.body()?.has("_id") == true) {
Log.d("PerformanceLog", "[USER_EXISTS] Time: ${System.currentTimeMillis()}")
callback.onSuccess(context.getString(R.string.unable_to_create_user_user_already_exists))
} else {
Log.d("PerformanceLog", "[BEFORE_PUT_DOC] Time: ${System.currentTimeMillis()}")

retrofitInterface.putDoc(null, "application/json", "${Utilities.getUrl()}/_users/org.couchdb.user:${obj["name"].asString}", obj).enqueue(object : Callback<JsonObject> {
override fun onResponse(call: Call<JsonObject>, response: Response<JsonObject>) {
if (response.body() != null && response.body()?.has("id") == true) {
Log.d("PerformanceLog", "[PUT_DOC_RESPONSE] Time: ${System.currentTimeMillis()}")

if (response.body() != null && response.body()!!.has("id")) {
Log.d("PerformanceLog", "[BEFORE_UPLOAD_TO_SHELF] Time: ${System.currentTimeMillis()}")
uploadToShelf(obj)

Log.d("PerformanceLog", "[BEFORE_SAVE_USER_TO_DB] Time: ${System.currentTimeMillis()}")
saveUserToDb(realm, response.body()!!.get("id").asString, obj, callback)
} else {
Log.d("PerformanceLog", "[PUT_DOC_FAILED] Time: ${System.currentTimeMillis()}")
callback.onSuccess(context.getString(R.string.unable_to_create_user_user_already_exists))
}
}

override fun onFailure(call: Call<JsonObject>, t: Throwable) {
Log.d("PerformanceLog", "[PUT_DOC_NETWORK_FAILURE] Time: ${System.currentTimeMillis()}")
callback.onSuccess(context.getString(R.string.unable_to_create_user_user_already_exists))
}
})
}
}

override fun onFailure(call: Call<JsonObject>, t: Throwable) {
Log.d("PerformanceLog", "[USER_CHECK_NETWORK_FAILURE] Time: ${System.currentTimeMillis()}")
callback.onSuccess(context.getString(R.string.unable_to_create_user_user_already_exists))
}
})
}

override fun notAvailable() {
Log.d("PerformanceLog", "[PLANET_NOT_AVAILABLE] Time: ${System.currentTimeMillis()}")
val settings = MainApplication.context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
if (isUserExists(realm, obj["name"].asString)) {
callback.onSuccess(context.getString(R.string.unable_to_create_user_user_already_exists))
Expand Down Expand Up @@ -286,6 +327,59 @@ class Service(private val context: Context) {
})
}

private fun saveUserToDb(realm: Realm, id: String, obj: JsonObject, callback: CreateUserCallback) {
Log.d("PerformanceLog", "[SAVE_USER_TO_DB_START] Time: ${System.currentTimeMillis()}")
val settings = MainApplication.context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
realm.executeTransactionAsync({ realm1: Realm? ->
Log.d("PerformanceLog", "[REALM_TRANSACTION_START] Time: ${System.currentTimeMillis()}")
try {
Log.d("PerformanceLog", "[BEFORE_GET_JSON_OBJECT] Time: ${System.currentTimeMillis()}")
val res = retrofitInterface?.getJsonObject(Utilities.header, Utilities.getUrl() + "/_users/" + id)?.execute()
Log.d("PerformanceLog", "[AFTER_GET_JSON_OBJECT] Time: ${System.currentTimeMillis()}")
if (res?.body() != null) {
Log.d("PerformanceLog", "[BEFORE_POPULATE_USERS_TABLE] Time: ${System.currentTimeMillis()}")
val model = populateUsersTable(res.body(), realm1, settings)
if (model != null) {
Log.d("PerformanceLog", "[BEFORE_SAVE_KEY_IV] Time: ${System.currentTimeMillis()}")
UploadToShelfService(MainApplication.context).saveKeyIv(retrofitInterface, model, obj)
Log.d("PerformanceLog", "[AFTER_SAVE_KEY_IV] Time: ${System.currentTimeMillis()}")
}
}
} catch (e: IOException) {
Log.d("PerformanceLog", "[REALM_TRANSACTION_ERROR] Time: ${System.currentTimeMillis()}")
e.printStackTrace()
}
Log.d("PerformanceLog", "[REALM_TRANSACTION_END] Time: ${System.currentTimeMillis()}")
}, {
Log.d("PerformanceLog", "[REALM_TRANSACTION_SUCCESS] Time: ${System.currentTimeMillis()}")
callback.onSuccess(context.getString(R.string.user_created_successfully))
isNetworkConnectedFlow.onEach { isConnected ->
if (isConnected) {
val serverUrl = settings.getString("serverURL", "")
if (!serverUrl.isNullOrEmpty()) {
serviceScope.launch {
val canReachServer = withContext(Dispatchers.IO) {
isServerReachable(serverUrl)
}
if (canReachServer) {
if (context is ProcessUserDataActivity) {
context.runOnUiThread {
context.startUpload("becomeMember")
}
}
TransactionSyncManager.syncDb(realm, "tablet_users")
}
}
}
}
}.launchIn(serviceScope)
}) { error: Throwable ->
Log.d("PerformanceLog", "[REALM_TRANSACTION_ERROR] Time: ${System.currentTimeMillis()}")
error.printStackTrace()
callback.onSuccess(context.getString(R.string.unable_to_save_user_please_sync))
}
}

fun syncPlanetServers(callback: SuccessListener) {
retrofitInterface?.getJsonObject("", "https://planet.earth.ole.org/db/communityregistrationrequests/_all_docs?include_docs=true")?.enqueue(object : Callback<JsonObject?> {
override fun onResponse(call: Call<JsonObject?>, response: Response<JsonObject?>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,7 @@ open class RealmMyCourse : RealmObject() {
@JvmStatic
fun getMyByUserId(mRealm: Realm, settings: SharedPreferences?): RealmResults<RealmMyCourse> {
val userId = settings?.getString("userId", "--")
return mRealm.where(RealmMyCourse::class.java)
.equalTo("userId", userId)
.findAll()
return mRealm.where(RealmMyCourse::class.java).equalTo("userId", userId).findAll()
}

@JvmStatic
Expand Down
Loading