Implemented sync display names.

This commit is contained in:
Koen J 2025-02-25 11:00:54 +01:00
parent b5ac8b3ec6
commit 0006da7385
6 changed files with 74 additions and 16 deletions

View File

@ -197,7 +197,7 @@ dependencies {
implementation 'org.jsoup:jsoup:1.15.3' implementation 'org.jsoup:jsoup:1.15.3'
implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.arthenica:ffmpeg-kit-full:5.1' implementation 'com.arthenica:ffmpeg-kit-full:6.0-2.LTS'
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0' implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0'
implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.github.dhaval2404:imagepicker:2.1'
implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.zxing:core:3.4.1'

View File

@ -101,7 +101,8 @@ class SyncHomeActivity : AppCompatActivity() {
private fun updateDeviceView(syncDeviceView: SyncDeviceView, publicKey: String, session: SyncSession?): SyncDeviceView { private fun updateDeviceView(syncDeviceView: SyncDeviceView, publicKey: String, session: SyncSession?): SyncDeviceView {
val connected = session?.connected ?: false val connected = session?.connected ?: false
syncDeviceView.setLinkType(if (connected) LinkType.Local else LinkType.None) syncDeviceView.setLinkType(if (connected) LinkType.Local else LinkType.None)
.setName(publicKey) .setName(session?.displayName ?: StateSync.instance.getCachedName(publicKey) ?: publicKey)
//TODO: also display public key?
.setStatus(if (connected) "Connected" else "Disconnected") .setStatus(if (connected) "Connected" else "Disconnected")
return syncDeviceView return syncDeviceView
} }

View File

@ -922,7 +922,7 @@ class VideoDetailView : ConstraintLayout {
} else if(devices.size == 1){ } else if(devices.size == 1){
val device = devices.first(); val device = devices.first();
Logger.i(TAG, "Send to device? (public key: ${device.remotePublicKey}): " + videoToSend.url) Logger.i(TAG, "Send to device? (public key: ${device.remotePublicKey}): " + videoToSend.url)
UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non ${device.remotePublicKey}" , { UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non '${device.displayName}'" , {
Logger.i(TAG, "Send to device confirmed (public key: ${device.remotePublicKey}): " + videoToSend.url) Logger.i(TAG, "Send to device confirmed (public key: ${device.remotePublicKey}): " + videoToSend.url)
fragment.lifecycleScope.launch(Dispatchers.IO) { fragment.lifecycleScope.launch(Dispatchers.IO) {

View File

@ -44,6 +44,7 @@ import kotlin.system.measureTimeMillis
class StateSync { class StateSync {
private val _authorizedDevices = FragmentedStorage.get<StringArrayStorage>("authorized_devices") private val _authorizedDevices = FragmentedStorage.get<StringArrayStorage>("authorized_devices")
private val _nameStorage = FragmentedStorage.get<StringStringMapStorage>("sync_remembered_name_storage")
private val _syncKeyPair = FragmentedStorage.get<StringStorage>("sync_key_pair") private val _syncKeyPair = FragmentedStorage.get<StringStorage>("sync_key_pair")
private val _lastAddressStorage = FragmentedStorage.get<StringStringMapStorage>("sync_last_address_storage") private val _lastAddressStorage = FragmentedStorage.get<StringStringMapStorage>("sync_last_address_storage")
private val _syncSessionData = FragmentedStorage.get<StringTMapStorage<SyncSessionData>>("syncSessionData") private val _syncSessionData = FragmentedStorage.get<StringTMapStorage<SyncSessionData>>("syncSessionData")
@ -305,12 +306,22 @@ class StateSync {
synchronized(_sessions) { synchronized(_sessions) {
session = _sessions[s.remotePublicKey] session = _sessions[s.remotePublicKey]
if (session == null) { if (session == null) {
val remoteDeviceName = synchronized(_nameStorage) {
_nameStorage.get(remotePublicKey)
}
session = SyncSession(remotePublicKey, onAuthorized = { it, isNewlyAuthorized, isNewSession -> session = SyncSession(remotePublicKey, onAuthorized = { it, isNewlyAuthorized, isNewSession ->
if (!isNewSession) { if (!isNewSession) {
return@SyncSession return@SyncSession
} }
Logger.i(TAG, "${s.remotePublicKey} authorized") it.remoteDeviceName?.let { remoteDeviceName ->
synchronized(_nameStorage) {
_nameStorage.setAndSave(remotePublicKey, remoteDeviceName)
}
}
Logger.i(TAG, "${s.remotePublicKey} authorized (name: ${it.displayName})")
synchronized(_lastAddressStorage) { synchronized(_lastAddressStorage) {
_lastAddressStorage.setAndSave(remotePublicKey, s.remoteAddress) _lastAddressStorage.setAndSave(remotePublicKey, s.remoteAddress)
} }
@ -341,7 +352,7 @@ class StateSync {
deviceRemoved.emit(it.remotePublicKey) deviceRemoved.emit(it.remotePublicKey)
}) }, remoteDeviceName)
_sessions[remotePublicKey] = session!! _sessions[remotePublicKey] = session!!
} }
session!!.addSocketSession(s) session!!.addSocketSession(s)
@ -469,6 +480,12 @@ class StateSync {
} }
} }
fun getCachedName(publicKey: String): String? {
return synchronized(_nameStorage) {
_nameStorage.get(publicKey)
}
}
suspend fun delete(publicKey: String) { suspend fun delete(publicKey: String) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {

View File

@ -6,12 +6,10 @@ import com.futo.platformplayer.api.media.Serializer
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.HistoryVideo import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.models.SubscriptionGroup
import com.futo.platformplayer.smartMerge import com.futo.platformplayer.smartMerge
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateBackup import com.futo.platformplayer.states.StateBackup
import com.futo.platformplayer.states.StateHistory import com.futo.platformplayer.states.StateHistory
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.states.StateSubscriptionGroups import com.futo.platformplayer.states.StateSubscriptionGroups
import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.states.StateSubscriptions
@ -30,6 +28,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.time.Instant import java.time.Instant
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
@ -53,6 +52,9 @@ class SyncSession : IAuthorizable {
private val _id = UUID.randomUUID() private val _id = UUID.randomUUID()
private var _remoteId: UUID? = null private var _remoteId: UUID? = null
private var _lastAuthorizedRemoteId: UUID? = null private var _lastAuthorizedRemoteId: UUID? = null
var remoteDeviceName: String? = null
private set
val displayName: String get() = remoteDeviceName ?: remotePublicKey
var connected: Boolean = false var connected: Boolean = false
private set(v) { private set(v) {
@ -62,7 +64,7 @@ class SyncSession : IAuthorizable {
} }
} }
constructor(remotePublicKey: String, onAuthorized: (session: SyncSession, isNewlyAuthorized: Boolean, isNewSession: Boolean) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit) { constructor(remotePublicKey: String, onAuthorized: (session: SyncSession, isNewlyAuthorized: Boolean, isNewSession: Boolean) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit, remoteDeviceName: String?) {
this.remotePublicKey = remotePublicKey this.remotePublicKey = remotePublicKey
_onAuthorized = onAuthorized _onAuthorized = onAuthorized
_onUnauthorized = onUnauthorized _onUnauthorized = onUnauthorized
@ -85,7 +87,20 @@ class SyncSession : IAuthorizable {
fun authorize(socketSession: SyncSocketSession) { fun authorize(socketSession: SyncSocketSession) {
Logger.i(TAG, "Sent AUTHORIZED with session id $_id") Logger.i(TAG, "Sent AUTHORIZED with session id $_id")
socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(_id.toString().toByteArray()))
if (socketSession.remoteVersion >= 3) {
val idStringBytes = _id.toString().toByteArray()
val nameBytes = "${android.os.Build.MANUFACTURER}-${android.os.Build.MODEL}".toByteArray()
val buffer = ByteArray(1 + idStringBytes.size + 1 + nameBytes.size)
socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).apply {
put(idStringBytes.size.toByte())
put(idStringBytes)
put(nameBytes.size.toByte())
put(nameBytes)
}.apply { flip() })
} else {
socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(_id.toString().toByteArray()))
}
_authorized = true _authorized = true
checkAuthorized() checkAuthorized()
} }
@ -138,15 +153,37 @@ class SyncSession : IAuthorizable {
when (opcode) { when (opcode) {
Opcode.NOTIFY_AUTHORIZED.value -> { Opcode.NOTIFY_AUTHORIZED.value -> {
val str = data.toUtf8String() if (socketSession.remoteVersion >= 3) {
_remoteId = if (data.remaining() >= 0) UUID.fromString(str) else UUID.fromString("00000000-0000-0000-0000-000000000000") val idByteCount = data.get().toInt()
if (idByteCount > 64)
throw Exception("Id should always be smaller than 64 bytes")
val idBytes = ByteArray(idByteCount)
data.get(idBytes)
val nameByteCount = data.get().toInt()
if (nameByteCount > 64)
throw Exception("Name should always be smaller than 64 bytes")
val nameBytes = ByteArray(nameByteCount)
data.get(nameBytes)
_remoteId = UUID.fromString(idBytes.toString(Charsets.UTF_8))
remoteDeviceName = nameBytes.toString(Charsets.UTF_8)
} else {
val str = data.toUtf8String()
_remoteId = if (data.remaining() >= 0) UUID.fromString(str) else UUID.fromString("00000000-0000-0000-0000-000000000000")
remoteDeviceName = null
}
_remoteAuthorized = true _remoteAuthorized = true
Logger.i(TAG, "Received AUTHORIZED with session id $_remoteId") Logger.i(TAG, "Received AUTHORIZED with session id $_remoteId (device name: '${remoteDeviceName ?: "not set"}')")
checkAuthorized() checkAuthorized()
return return
} }
Opcode.NOTIFY_UNAUTHORIZED.value -> { Opcode.NOTIFY_UNAUTHORIZED.value -> {
_remoteId = null _remoteId = null
remoteDeviceName = null
_lastAuthorizedRemoteId = null _lastAuthorizedRemoteId = null
_remoteAuthorized = false _remoteAuthorized = false
_onUnauthorized(this) _onUnauthorized(this)

View File

@ -46,6 +46,8 @@ class SyncSocketSession {
val localPublicKey: String get() = _localPublicKey val localPublicKey: String get() = _localPublicKey
private val _onData: (session: SyncSocketSession, opcode: UByte, subOpcode: UByte, data: ByteBuffer) -> Unit private val _onData: (session: SyncSocketSession, opcode: UByte, subOpcode: UByte, data: ByteBuffer) -> Unit
var authorizable: IAuthorizable? = null var authorizable: IAuthorizable? = null
var remoteVersion: Int = -1
private set
val remoteAddress: String val remoteAddress: String
@ -162,11 +164,12 @@ class SyncSocketSession {
} }
private fun performVersionCheck() { private fun performVersionCheck() {
val CURRENT_VERSION = 2 val CURRENT_VERSION = 3
val MINIMUM_VERSION = 2
_outputStream.writeInt(CURRENT_VERSION) _outputStream.writeInt(CURRENT_VERSION)
val version = _inputStream.readInt() remoteVersion = _inputStream.readInt()
Logger.i(TAG, "performVersionCheck (version = $version)") Logger.i(TAG, "performVersionCheck (version = $remoteVersion)")
if (version != CURRENT_VERSION) if (remoteVersion < MINIMUM_VERSION)
throw Exception("Invalid version") throw Exception("Invalid version")
} }