Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Socket subscribe improvements #285

Merged
merged 3 commits into from
Feb 18, 2025
Merged
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
39 changes: 39 additions & 0 deletions crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.crowdin.platform.util.FeatureFlags
import com.crowdin.platform.util.ThreadUtils
import com.crowdin.platform.util.getFormattedCode
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.util.Locale

Expand All @@ -41,6 +42,8 @@
const val SUPPORTED_LANGUAGES = "supported_languages"
const val MANIFEST_DATA = "manifest_data"
const val SYNC_DATA = "sync_data"
const val EVENT_TICKETS = "event_tickets"
const val EVENT_TICKETS_EXPIRATION = 1000 * 60 * 4
}

private var loadingStateListeners: ArrayList<LoadingStateListener>? = null
Expand Down Expand Up @@ -266,4 +269,40 @@

return info
}

@WorkerThread
fun getTicket(event: String): String? {
var ticketValue: String? = null
val type = object : TypeToken<MutableMap<String, TicketItem>>() {}.type
var ticketsMap: MutableMap<String, TicketItem>? = localRepository.getData(EVENT_TICKETS, type)

Check warning on line 277 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L275-L277

Added lines #L275 - L277 were not covered by tests
if (ticketsMap == null) {
ticketsMap = mutableMapOf()

Check warning on line 279 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L279

Added line #L279 was not covered by tests
}

val ticketItem = ticketsMap[event]

Check warning on line 282 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L282

Added line #L282 was not covered by tests
if (ticketItem == null || ticketItem.isExpired()) {
Log.d(CROWDIN_TAG, "Ticket expired for event: $event")

Check warning on line 284 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L284

Added line #L284 was not covered by tests
remoteRepository.getTicket(event)?.data?.ticket?.let {
ticketsMap[event] = TicketItem(it, System.currentTimeMillis() + EVENT_TICKETS_EXPIRATION)
localRepository.saveData(EVENT_TICKETS, ticketsMap)
ticketValue = it
}

Check warning on line 289 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L286-L289

Added lines #L286 - L289 were not covered by tests
} else {
Log.d(CROWDIN_TAG, "Ticket not expired for event: $event")
ticketValue = ticketItem.ticket

Check warning on line 292 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L291-L292

Added lines #L291 - L292 were not covered by tests
}

return ticketValue

Check warning on line 295 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L295

Added line #L295 was not covered by tests
}

fun clearSocketData() {
localRepository.saveData(EVENT_TICKETS, null)
}

Check warning on line 300 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L299-L300

Added lines #L299 - L300 were not covered by tests

data class TicketItem(
val ticket: String,
val expirationTime: Long,

Check warning on line 304 in crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/DataManager.kt#L302-L304

Added lines #L302 - L304 were not covered by tests
) {
fun isExpired(): Boolean = System.currentTimeMillis() > expirationTime
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.crowdin.platform.data.model

internal data class TicketRequestBody(
val event: String,
val context: ContextData = ContextData(),
)

Check warning on line 6 in crowdin/src/main/java/com/crowdin/platform/data/model/TicketRequestBody.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/model/TicketRequestBody.kt#L3-L6

Added lines #L3 - L6 were not covered by tests

internal data class ContextData(
val mode: String = "translate",
)

Check warning on line 10 in crowdin/src/main/java/com/crowdin/platform/data/model/TicketRequestBody.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/model/TicketRequestBody.kt#L8-L10

Added lines #L8 - L10 were not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.crowdin.platform.data.model

internal data class TicketResponseBody(
val data: TicketData,

Check warning on line 4 in crowdin/src/main/java/com/crowdin/platform/data/model/TicketResponseBody.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/model/TicketResponseBody.kt#L3-L4

Added lines #L3 - L4 were not covered by tests
)

internal data class TicketData(
val ticket: String,

Check warning on line 8 in crowdin/src/main/java/com/crowdin/platform/data/model/TicketResponseBody.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/model/TicketResponseBody.kt#L7-L8

Added lines #L7 - L8 were not covered by tests
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.crowdin.platform.data.model.LanguageInfo
import com.crowdin.platform.data.model.LanguagesInfo
import com.crowdin.platform.data.model.ManifestData
import com.crowdin.platform.data.model.TicketRequestBody
import com.crowdin.platform.data.model.TicketResponseBody
import com.crowdin.platform.data.remote.api.CrowdinApi
import com.crowdin.platform.data.remote.api.CrowdinDistributionApi
import com.crowdin.platform.util.ThreadUtils
Expand Down Expand Up @@ -94,6 +96,13 @@
return info
}

@WorkerThread
override fun getTicket(event: String): TicketResponseBody? {
var result: TicketResponseBody? = null

Check warning on line 101 in crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt#L101

Added line #L101 was not covered by tests
executeIO { result = crowdinApi?.getTicket(TicketRequestBody(event))?.execute()?.body() }
return result

Check warning on line 103 in crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt#L103

Added line #L103 was not covered by tests
}

fun getLanguageInfo(sourceLanguage: String): LanguageInfo? {
crowdinLanguages?.data?.forEach {
val languageInfo = it.data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.crowdin.platform.data.LanguageDataCallback
import com.crowdin.platform.data.model.LanguageData
import com.crowdin.platform.data.model.LanguagesInfo
import com.crowdin.platform.data.model.ManifestData
import com.crowdin.platform.data.model.TicketResponseBody

/**
* Repository of strings from network.
Expand Down Expand Up @@ -32,4 +33,10 @@ internal interface RemoteRepository {
*/
@WorkerThread
fun getSupportedLanguages(): LanguagesInfo?

/**
* Fetch ticket for WebSocket connection.
*/
@WorkerThread
fun getTicket(event: String): TicketResponseBody?
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.crowdin.platform.data.model.BuildTranslationRequest
import com.crowdin.platform.data.model.FileResponse
import com.crowdin.platform.data.model.LanguagesInfo
import com.crowdin.platform.data.model.ListScreenshotsResponse
import com.crowdin.platform.data.model.TicketRequestBody
import com.crowdin.platform.data.model.TicketResponseBody
import com.crowdin.platform.data.model.TranslationResponse
import okhttp3.RequestBody
import okhttp3.ResponseBody
Expand Down Expand Up @@ -80,4 +82,9 @@ internal interface CrowdinApi {
fun getLanguagesInfo(
@Query("limit") limit: Int = LANGUAGE_COUNT,
): Call<LanguagesInfo>

@POST("/api/v2/user/websocket-ticket")
fun getTicket(
@Body body: TicketRequestBody,
): Call<TicketResponseBody>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import android.os.Build
import android.util.Log
import android.widget.TextView
import com.crowdin.platform.Crowdin
import com.crowdin.platform.data.DataManager
import com.crowdin.platform.data.getMappingValueForKey
import com.crowdin.platform.data.model.LanguageData
import com.crowdin.platform.data.model.TextMetaData
Expand All @@ -23,7 +25,11 @@
import java.util.Locale
import java.util.WeakHashMap

private const val UPDATE_DRAFT = "update-draft"
private const val TOP_SUGGESTION = "top-suggestion"

internal class EchoWebSocketListener(
private val dataManager: DataManager,
private var mappingData: LanguageData,
private var distributionData: DistributionInfoResponse.DistributionData,
private var viewTransformerManager: ViewTransformerManager,
Expand All @@ -41,7 +47,9 @@
val user = distributionData.user

saveMatchedTextViewWithMappingId(mappingData)
subscribeViews(webSocket, project, user)
ThreadUtils.runInBackgroundPool({
subscribeViews(webSocket, project, user)

Check warning on line 51 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L51

Added line #L51 was not covered by tests
}, false)

viewTransformerManager.setOnViewsChangeListener(
object : ViewsChangeListener {
Expand Down Expand Up @@ -80,6 +88,7 @@
code: Int,
reason: String,
) {
dataManager.clearSocketData()
dataHolderMap.clear()
webSocket.close(NORMAL_CLOSURE_STATUS, reason)
output("Closing : $code / $reason")
Expand Down Expand Up @@ -123,26 +132,35 @@
user: DistributionInfoResponse.DistributionData.UserData,
mappingValue: String,
) {
webSocket.send(
SubscribeUpdateEvent(
project.wsHash,
project.id,
user.id,
languageCode,
mappingValue,
).toString(),
)
try {
val updateEvent = "$UPDATE_DRAFT:${project.wsHash}:${project.id}:${user.id}:$languageCode:$mappingValue"

Check warning on line 136 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L135-L136

Added lines #L135 - L136 were not covered by tests
dataManager.getTicket(updateEvent)?.let {
webSocket.send(getSubscribeEventJson(updateEvent, it))

Check warning on line 138 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L138

Added line #L138 was not covered by tests
}
} catch (e: Exception) {
Log.e(Crowdin.CROWDIN_TAG, "Get ticket for update event failed", e)

Check warning on line 141 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L140-L141

Added lines #L140 - L141 were not covered by tests
}

webSocket.send(
SubscribeSuggestionEvent(
project.wsHash,
project.id,
languageCode,
mappingValue,
).toString(),
)
try {
val suggestionEvent = "$TOP_SUGGESTION:${project.wsHash}:${project.id}:$languageCode:$mappingValue"

Check warning on line 145 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L144-L145

Added lines #L144 - L145 were not covered by tests
dataManager.getTicket(suggestionEvent)?.let {
webSocket.send(getSubscribeEventJson(suggestionEvent, it))

Check warning on line 147 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L147

Added line #L147 was not covered by tests
}
} catch (e: Exception) {
Log.e(Crowdin.CROWDIN_TAG, "Get ticket for suggestion event failed", e)

Check warning on line 150 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L149-L150

Added lines #L149 - L150 were not covered by tests
}
}

private fun getSubscribeEventJson(
eventType: String,
ticket: String,
): String =
"{" +

Check warning on line 158 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L158

Added line #L158 was not covered by tests
"\"action\":\"subscribe\", " +
"\"event\":\"$eventType\", " +
"\"ticket\": \"$ticket\"" +
"}"

Check warning on line 162 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/EchoWebSocketListener.kt#L160-L162

Added lines #L160 - L162 were not covered by tests

private fun handleMessage(message: String?) {
message?.let {
val eventResponse = parseResponse(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
val languageCode = getMatchedCode(it.languages, it.customLanguages) ?: return@let
val listener =
EchoWebSocketListener(
dataManager,

Check warning on line 62 in crowdin/src/main/java/com/crowdin/platform/realtimeupdate/RealTimeUpdateManager.kt

View check run for this annotation

Codecov / codecov/patch

crowdin/src/main/java/com/crowdin/platform/realtimeupdate/RealTimeUpdateManager.kt#L62

Added line #L62 was not covered by tests
mappingData,
distributionData,
viewTransformerManager,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.crowdin.platform

import com.crowdin.platform.data.DataManager
import com.crowdin.platform.data.model.LanguageData
import com.crowdin.platform.data.remote.api.DistributionInfoResponse
import com.crowdin.platform.realtimeupdate.EchoWebSocketListener
Expand All @@ -17,9 +18,11 @@ class EchoWebSocketListenerTest {
// Given
val mockMappingData = spy(LanguageData::class.java)
val mockDistributionData = mock(DistributionInfoResponse.DistributionData::class.java)
val mockDataManager = mock(DataManager::class.java)
val mockViewTransformerManager = spy(ViewTransformerManager::class.java)
val echoWebSocketListener =
EchoWebSocketListener(
mockDataManager,
mockMappingData,
mockDistributionData,
mockViewTransformerManager,
Expand All @@ -38,9 +41,11 @@ class EchoWebSocketListenerTest {
// Given
val mockMappingData = spy(LanguageData::class.java)
val mockDistributionData = mock(DistributionInfoResponse.DistributionData::class.java)
val mockDataManager = mock(DataManager::class.java)
val mockViewTransformerManager = spy(ViewTransformerManager::class.java)
val echoWebSocketListener =
EchoWebSocketListener(
mockDataManager,
mockMappingData,
mockDistributionData,
mockViewTransformerManager,
Expand Down
44 changes: 0 additions & 44 deletions crowdin/src/test/java/com/crowdin/platform/SocketEventsTest.kt

This file was deleted.

Loading