Skip to content

Commit

Permalink
General improvements, add GetClients action
Browse files Browse the repository at this point in the history
  • Loading branch information
toasterofbread committed Dec 9, 2023
1 parent 890d746 commit 91445b1
Show file tree
Hide file tree
Showing 16 changed files with 166 additions and 80 deletions.
31 changes: 17 additions & 14 deletions src/nativeMain/kotlin/cinterop/indicator/LibAppIndicator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,23 @@ class LibAppIndicator(name: String, icon_path: List<String>): TrayIndicator {
// TODO
}

override fun addButton(label: String, onClick: ButtonCallback) {
val callback_index: CPointer<IntVar> = button_callbacks.addCallback(onClick, mem_scope)

override fun addButton(label: String, onClick: ButtonCallback?) {
val item: CPointer<GtkWidget> = gtk_menu_item_new_with_label(label)!!
g_signal_connect_data(
item,
"activate",
staticCFunction { _: CPointer<*>, index: CPointer<IntVar> ->
button_callbacks.getCallback(index).invoke()
}.reinterpret(),
callback_index,
null,
0U
)

if (onClick != null) {
val callback_index: CPointer<IntVar> = button_callbacks.addCallback(onClick, mem_scope)
g_signal_connect_data(
item,
"activate",
staticCFunction { _: CPointer<*>, index: CPointer<IntVar> ->
button_callbacks.getCallback(index).invoke()
}.reinterpret(),
callback_index,
null,
0U
)
}

gtk_menu_shell_append(menu.reinterpret(), item)
gtk_widget_show(item)
}
Expand All @@ -80,7 +83,7 @@ class LibAppIndicator(name: String, icon_path: List<String>): TrayIndicator {
}

init {
// Effectively disable GTK warnings
// Effectively disables GTK warnings
g_log_set_writer_func(
staticCFunction { level: GLogLevelFlags ->
if (level == G_LOG_LEVEL_ERROR || level == G_LOG_LEVEL_CRITICAL) {
Expand Down
2 changes: 1 addition & 1 deletion src/nativeMain/kotlin/cinterop/indicator/TrayIndicator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface TrayIndicator {
fun release() {}

fun addClickCallback(onClick: ClickCallback)
fun addButton(label: String, onClick: ButtonCallback)
fun addButton(label: String, onClick: ButtonCallback?)
fun addScrollCallback(onScroll: ScrollCallback)

companion object {
Expand Down
11 changes: 6 additions & 5 deletions src/nativeMain/kotlin/spms/client/cli/CommandLineClientMode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import spms.LocalisedMessageProvider
import spms.client.ClientOptions
import spms.client.cli.modes.Interactive
import spms.localisation.loc
import spms.server.SpMsClientInfo
import spms.server.SpMsClientHandshake
import spms.server.SpMsClientType

abstract class CommandLineClientMode(
Expand All @@ -37,11 +37,12 @@ abstract class CommandLineClientMode(

log(currentContext.loc.cli.sending_handshake)

val info: SpMsClientInfo = SpMsClientInfo(
context.client_name,
SpMsClientType.HEADLESS
val handshake: SpMsClientHandshake = SpMsClientHandshake(
name = context.client_name,
type = SpMsClientType.HEADLESS_PLAYER,
language = currentContext.loc.language.name
)
context.socket.sendStringMultipart(listOf(Json.encodeToString(info)))
context.socket.sendStringMultipart(listOf(Json.encodeToString(handshake)))

val reply: List<String>? = context.socket.recvStringMultipart(SERVER_REPLY_TIMEOUT_MS)

Expand Down
13 changes: 7 additions & 6 deletions src/nativeMain/kotlin/spms/client/player/PlayerClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import spms.player.Player
import spms.player.PlayerEvent
import spms.player.StreamProviderServer
import spms.server.PlayerOptions
import spms.server.SpMsClientInfo
import spms.server.SpMsClientHandshake
import spms.server.SpMsClientType
import kotlin.system.getTimeMillis

Expand Down Expand Up @@ -157,11 +157,12 @@ class PlayerClient private constructor(): Command(

log(currentContext.loc.cli.sending_handshake)

val info: SpMsClientInfo = SpMsClientInfo(
getClientName(),
SpMsClientType.PLAYER
val handshake: SpMsClientHandshake = SpMsClientHandshake(
name = getClientName(),
type = SpMsClientType.PLAYER,
language = currentContext.loc.language.name
)
socket.sendStringMultipart(listOf(json.encodeToString(info)))
socket.sendStringMultipart(listOf(json.encodeToString(handshake)))

val reply: List<String>? = socket.recvStringMultipart(SERVER_REPLY_TIMEOUT_MS)

Expand All @@ -171,7 +172,7 @@ class PlayerClient private constructor(): Command(

var shutdown: Boolean = false
val queued_messages: MutableList<Pair<String, List<JsonPrimitive>>> = mutableListOf()
val stream_provider_server: StreamProviderServer = StreamProviderServer()
val stream_provider_server: StreamProviderServer = StreamProviderServer(client_options.port + 1)

val player: PlayerImpl = object : PlayerImpl(headless = !player_options.enable_gui) {
override fun onShutdown() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ interface ServerActionLocalisation {
val status_help: String
val status_output_start: String

val clients_name: String
val clients_help: String

val ready_to_play_name: String
val ready_to_play_help: String
val ready_to_play_param_item_index: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class ServerActionLocalisationEn: ServerActionLocalisation {
override val status_help: String = "Get detailed information about the server's current status"
override val status_output_start: String = "Server status"

override val clients_name: String = "Get clients"
override val clients_help: String = "Get a list of clients connected to the server"

override val ready_to_play_name: String = "Notify ready to play"
override val ready_to_play_help: String = "Notify server that client is ready to play the current item"
override val ready_to_play_param_item_index: String = "Index of the current item"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class ServerActionLocalisationJa: ServerActionLocalisation {
override val status_help: String = "サーバーの現在の状態を表示"
override val status_output_start: String = "サーバー状態"

override val clients_name: String = "クライアント情報"
override val clients_help: String = "サーバーに接続されているクライアントの情報を表示"

override val ready_to_play_name: String = "サーバーに再生準備完了と知らせる"
override val ready_to_play_help: String = "クライアントが再生を始める準備ができたとサーバーに知らせる"
override val ready_to_play_param_item_index: String = "再生準備が完了したアイテムのインデックス"
Expand Down
4 changes: 1 addition & 3 deletions src/nativeMain/kotlin/spms/player/StreamProviderServer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import platform.posix.signal
import spms.server.PROJECT_URL
import kotlin.system.exitProcess

private const val DEFAULT_PORT: Int = spms.server.DEFAULT_PORT + 1

@OptIn(ExperimentalForeignApi::class)
class StreamProviderServer(val port: Int = DEFAULT_PORT) {
class StreamProviderServer(val port: Int) {
private val server: ApplicationEngine

init {
Expand Down
80 changes: 55 additions & 25 deletions src/nativeMain/kotlin/spms/server/SpMs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import cinterop.zmq.ZmqRouter
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.MemScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
Expand All @@ -26,7 +25,7 @@ const val SERVER_EXPECT_REPLY_CHAR: Char = '!'
const val SEND_EVENTS_TO_INSTIGATING_CLIENT: Boolean = true

@OptIn(ExperimentalForeignApi::class)
class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean): ZmqRouter(mem_scope) {
class SpMs(mem_scope: MemScope, val secondary_port: Int, headless: Boolean = false, enable_gui: Boolean = false): ZmqRouter(mem_scope) {
@Serializable
data class ActionReply(val success: Boolean, val error: String? = null, val error_cause: String? = null, val result: JsonElement? = null)

Expand All @@ -52,7 +51,7 @@ class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean):
}
else
object : MpvClientImpl(!enable_gui) {
val stream_provider_server = StreamProviderServer()
val stream_provider_server = StreamProviderServer(secondary_port)

override fun urlToId(url: String): String = url.drop(stream_provider_server.getStreamUrl().length)
override fun idToUrl(item_id: String): String = stream_provider_server.getStreamUrl() + item_id
Expand All @@ -71,18 +70,23 @@ class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean):
private val clients: MutableList<SpMsClient> = mutableListOf()
private var playback_waiting_for_clients: Boolean = false

fun getClients(): List<SpMsClientInfo> =
clients.map { it.info }

fun poll(client_reply_timeout_ms: Long): Boolean {

// Process stray messages (hopefully client handshakes)
while (true) {
val message: Message = recvMultipart(null) ?: break
println("Got stray message before polling")
onClientMessage(message)
}

// Send relevant events to each client
var client_i: Int = clients.size - 1
while (client_i >= 0) {
val client: SpMsClient = clients[client_i--]

val message_parts: MutableList<String> = mutableListOf()

val events: List<PlayerEvent> = getEventsForClient(client)
Expand Down Expand Up @@ -113,16 +117,20 @@ class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean):
var client_reply: Message? = null

while (true) {
val message: Message = recvMultipart(
(wait_end - getTimeMillis()).coerceAtLeast(ZMQ_NOBLOCK.toLong())
) ?: break
val remaining = wait_end - getTimeMillis()
if (remaining <= 0) {
break
}

val message: Message = recvMultipart(remaining) ?: continue

if (message.client_id.contentHashCode() == client.id) {
client_reply = message
break
}
else {
// Handle connections from other clients
println("Got stray message during polling")
onClientMessage(message)
}
}
Expand All @@ -148,6 +156,8 @@ class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean):
check(executing_client_id == null)
executing_client_id = client.id

val reply: MutableList<ActionReply> = mutableListOf()

var i: Int = 0
while (i < client_reply.parts.size) {
val first: String = client_reply.parts[i++]
Expand All @@ -156,34 +166,43 @@ class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean):
val action_name: String = if (expects_reply) first.substring(1) else first
val action_params: List<JsonPrimitive> = Json.decodeFromString(client_reply.parts[i++])

val result: JsonElement?
try {
val result: JsonElement? = ServerAction.executeByName(this@SpMs, client.id, action_name, action_params)
if (expects_reply) {
val reply: ActionReply = ActionReply(
success = true,
result = result
)
sendMultipart(client.createMessage(listOf(Json.encodeToString(reply))))

println("Sent reply to $client for action '$action_name': $reply")
}
result = ServerAction.executeByName(this@SpMs, client.id, action_name, action_params)
}
catch (e: Throwable) {
val message: String = "Action $action_name(${action_params.map { it.contentOrNull }}) from $client failed"

if (expects_reply) {
val reply = ActionReply(
success = false,
error = message,
error_cause = e.message
reply.add(
ActionReply(
success = false,
error = message,
error_cause = e.message
)
)
sendMultipart(client.createMessage(listOf(Json.encodeToString(reply))))
}

RuntimeException(message, e).printStackTrace()

continue
}

if (expects_reply) {
reply.add(
ActionReply(
success = true,
result = result
)
)
}
}

if (reply.isNotEmpty()) {
sendMultipart(client.createMessage(listOf(Json.encodeToString(reply))))
println("Sent reply to $client: $reply")
}

executing_client_id = null
}

Expand Down Expand Up @@ -276,11 +295,22 @@ class SpMs(mem_scope: MemScope, headless: Boolean = false, enable_gui: Boolean):
return
}

val info: SpMsClientInfo = handshake_message.parts.firstOrNull()?.let { Json.decodeFromString(it) } ?: return
val handshake: SpMsClientHandshake
try {
handshake = handshake_message.parts.firstOrNull()?.let { Json.decodeFromString(it) } ?: return
}
catch (e: SerializationException) {
println("Ignoring SerializationException in onClientMessage")
return
}

val client: SpMsClient = SpMsClient(
handshake_message.client_id,
getNewClientName(info.name),
info.type,
SpMsClientInfo(
getNewClientName(handshake.name),
handshake.type,
handshake.getLanguage()
),
player_event_inc
)

Expand Down
25 changes: 20 additions & 5 deletions src/nativeMain/kotlin/spms/server/SpMsClient.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
package spms.server

import kotlinx.serialization.Serializable
import spms.localisation.Language

typealias SpMsClientID = Int

enum class SpMsClientType {
HEADLESS, PLAYER
PLAYER, HEADLESS_PLAYER
}

@Serializable
data class SpMsClientHandshake(
val name: String,
val type: SpMsClientType,
val language: String? = null
) {
fun getLanguage(): Language =
Language.fromCode(language) ?: Language.default
}

@Serializable
data class SpMsClientInfo(
val name: String,
val type: SpMsClientType
val type: SpMsClientType,
val language: Language
)

internal class SpMsClient(
val id_bytes: ByteArray,
val name: String,
val type: SpMsClientType,
val info: SpMsClientInfo,
var event_head: Int
) {
val name: String get() = info.name
val type: SpMsClientType get() = info.type
val language: Language get() = info.language

val id: SpMsClientID = id_bytes.contentHashCode()
var ready_to_play: Boolean = false

override fun toString(): String =
"Client(id=$id, name=$name, type=$type, event_head=$event_head)"
"Client(id=$id, name=$name, type=$type, language=$language, event_head=$event_head)"
}
Loading

0 comments on commit 91445b1

Please sign in to comment.