From 461c2b1167f5f2897be45ad72e62e1141cd20b38 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Tue, 6 May 2025 20:17:41 +0200 Subject: [PATCH 01/23] refactor: main.go > ./cmd/server --- .goreleaser.yaml | 3 ++- main.go => cmd/server/main.go | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename main.go => cmd/server/main.go (100%) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 25294e9..055a56b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,7 +6,8 @@ before: - go generate ./... builds: - - env: + - main: ./cmd/server + env: # - CGO_ENABLED=0 goos: - linux diff --git a/main.go b/cmd/server/main.go similarity index 100% rename from main.go rename to cmd/server/main.go -- 2.47.2 From f67a456d1ec46a4765f57a021980b0d8ca88457b Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Tue, 6 May 2025 21:38:14 +0200 Subject: [PATCH 02/23] wip: refactor: API --- go.mod | 4 + go.sum | 2 + internal/app/app.go | 21 + internal/generated/grpc/proto/api.pb.go | 1691 ++++++++++++++++++ internal/generated/grpc/proto/api_grpc.pb.go | 135 ++ internal/server/server.go | 7 + mise/config.toml | 6 + proto/api.proto | 105 ++ 8 files changed, 1971 insertions(+) create mode 100644 internal/generated/grpc/proto/api.pb.go create mode 100644 internal/generated/grpc/proto/api_grpc.pb.go create mode 100644 internal/server/server.go create mode 100644 proto/api.proto diff --git a/go.mod b/go.mod index 56cb2df..81d5088 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ require ( github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.35.0 golang.design/x/clipboard v0.7.0 + google.golang.org/grpc v1.69.4 + google.golang.org/protobuf v1.36.3 gopkg.in/yaml.v3 v3.0.1 ) @@ -95,12 +97,14 @@ require ( golang.org/x/image v0.26.0 // indirect golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.39.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.32.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 2e59131..f8574d0 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= diff --git a/internal/app/app.go b/internal/app/app.go index 0f8a17e..779875c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,7 +5,9 @@ import ( "context" "errors" "fmt" + "log" "log/slog" + "net" "slices" "time" @@ -13,10 +15,13 @@ import ( "git.netflux.io/rob/octoplex/internal/container" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc/proto" "git.netflux.io/rob/octoplex/internal/mediaserver" "git.netflux.io/rob/octoplex/internal/replicator" + "git.netflux.io/rob/octoplex/internal/server" "git.netflux.io/rob/octoplex/internal/terminal" "github.com/docker/docker/client" + "google.golang.org/grpc" ) // App is an instance of the app. @@ -120,6 +125,19 @@ func (a *App) Run(ctx context.Context) error { <-a.dispatchC } + const grpcAddr = ":50051" + lis, err := net.Listen("tcp", grpcAddr) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + grpcServer := grpc.NewServer() + grpcDone := make(chan error, 1) + pb.RegisterInternalAPIServer(grpcServer, server.Server{}) + go func() { + a.logger.Info("gRPC server started", "addr", grpcAddr) + grpcDone <- grpcServer.Serve(lis) + }() + containerClient, err := container.NewClient(ctx, a.dockerClient, a.logger.With("component", "container_client")) if err != nil { err = fmt.Errorf("create container client: %w", err) @@ -192,6 +210,9 @@ func (a *App) Run(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() + case grpcErr := <-grpcDone: + a.logger.Error("gRPC server exited", "err", grpcErr) + return grpcErr case <-startMediaServerC: if err = srv.Start(ctx); err != nil { return fmt.Errorf("start mediaserver: %w", err) diff --git a/internal/generated/grpc/proto/api.pb.go b/internal/generated/grpc/proto/api.pb.go new file mode 100644 index 0000000..f308548 --- /dev/null +++ b/internal/generated/grpc/proto/api.pb.go @@ -0,0 +1,1691 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.6 +// source: proto/api.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Envelope struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Payload: + // + // *Envelope_Command + // *Envelope_Event + Payload isEnvelope_Payload `protobuf_oneof:"payload"` +} + +func (x *Envelope) Reset() { + *x = Envelope{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Envelope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Envelope) ProtoMessage() {} + +func (x *Envelope) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Envelope.ProtoReflect.Descriptor instead. +func (*Envelope) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{0} +} + +func (m *Envelope) GetPayload() isEnvelope_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (x *Envelope) GetCommand() *Command { + if x, ok := x.GetPayload().(*Envelope_Command); ok { + return x.Command + } + return nil +} + +func (x *Envelope) GetEvent() *Event { + if x, ok := x.GetPayload().(*Envelope_Event); ok { + return x.Event + } + return nil +} + +type isEnvelope_Payload interface { + isEnvelope_Payload() +} + +type Envelope_Command struct { + Command *Command `protobuf:"bytes,1,opt,name=command,proto3,oneof"` +} + +type Envelope_Event struct { + Event *Event `protobuf:"bytes,2,opt,name=event,proto3,oneof"` +} + +func (*Envelope_Command) isEnvelope_Payload() {} + +func (*Envelope_Event) isEnvelope_Payload() {} + +type Command struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to CommandType: + // + // *Command_AddDestinaion + // *Command_RemoveDestination + // *Command_StartDestination + // *Command_StopDestination + // *Command_CloseOtherInstances + // *Command_Quit + CommandType isCommand_CommandType `protobuf_oneof:"command_type"` +} + +func (x *Command) Reset() { + *x = Command{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Command) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Command) ProtoMessage() {} + +func (x *Command) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Command.ProtoReflect.Descriptor instead. +func (*Command) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{1} +} + +func (m *Command) GetCommandType() isCommand_CommandType { + if m != nil { + return m.CommandType + } + return nil +} + +func (x *Command) GetAddDestinaion() *AddDestinationCommand { + if x, ok := x.GetCommandType().(*Command_AddDestinaion); ok { + return x.AddDestinaion + } + return nil +} + +func (x *Command) GetRemoveDestination() *RemoveDestinationCommand { + if x, ok := x.GetCommandType().(*Command_RemoveDestination); ok { + return x.RemoveDestination + } + return nil +} + +func (x *Command) GetStartDestination() *StartDestinationCommand { + if x, ok := x.GetCommandType().(*Command_StartDestination); ok { + return x.StartDestination + } + return nil +} + +func (x *Command) GetStopDestination() *StopDestinationCommand { + if x, ok := x.GetCommandType().(*Command_StopDestination); ok { + return x.StopDestination + } + return nil +} + +func (x *Command) GetCloseOtherInstances() *CloseOtherInstancesCommand { + if x, ok := x.GetCommandType().(*Command_CloseOtherInstances); ok { + return x.CloseOtherInstances + } + return nil +} + +func (x *Command) GetQuit() *QuitCommand { + if x, ok := x.GetCommandType().(*Command_Quit); ok { + return x.Quit + } + return nil +} + +type isCommand_CommandType interface { + isCommand_CommandType() +} + +type Command_AddDestinaion struct { + AddDestinaion *AddDestinationCommand `protobuf:"bytes,1,opt,name=add_destinaion,json=addDestinaion,proto3,oneof"` +} + +type Command_RemoveDestination struct { + RemoveDestination *RemoveDestinationCommand `protobuf:"bytes,2,opt,name=remove_destination,json=removeDestination,proto3,oneof"` +} + +type Command_StartDestination struct { + StartDestination *StartDestinationCommand `protobuf:"bytes,3,opt,name=start_destination,json=startDestination,proto3,oneof"` +} + +type Command_StopDestination struct { + StopDestination *StopDestinationCommand `protobuf:"bytes,4,opt,name=stop_destination,json=stopDestination,proto3,oneof"` +} + +type Command_CloseOtherInstances struct { + CloseOtherInstances *CloseOtherInstancesCommand `protobuf:"bytes,5,opt,name=close_other_instances,json=closeOtherInstances,proto3,oneof"` +} + +type Command_Quit struct { + Quit *QuitCommand `protobuf:"bytes,6,opt,name=quit,proto3,oneof"` +} + +func (*Command_AddDestinaion) isCommand_CommandType() {} + +func (*Command_RemoveDestination) isCommand_CommandType() {} + +func (*Command_StartDestination) isCommand_CommandType() {} + +func (*Command_StopDestination) isCommand_CommandType() {} + +func (*Command_CloseOtherInstances) isCommand_CommandType() {} + +func (*Command_Quit) isCommand_CommandType() {} + +type AddDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *AddDestinationCommand) Reset() { + *x = AddDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddDestinationCommand) ProtoMessage() {} + +func (x *AddDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddDestinationCommand.ProtoReflect.Descriptor instead. +func (*AddDestinationCommand) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{2} +} + +func (x *AddDestinationCommand) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AddDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type RemoveDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *RemoveDestinationCommand) Reset() { + *x = RemoveDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveDestinationCommand) ProtoMessage() {} + +func (x *RemoveDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveDestinationCommand.ProtoReflect.Descriptor instead. +func (*RemoveDestinationCommand) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{3} +} + +func (x *RemoveDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type StartDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *StartDestinationCommand) Reset() { + *x = StartDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartDestinationCommand) ProtoMessage() {} + +func (x *StartDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartDestinationCommand.ProtoReflect.Descriptor instead. +func (*StartDestinationCommand) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{4} +} + +func (x *StartDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type StopDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *StopDestinationCommand) Reset() { + *x = StopDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StopDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopDestinationCommand) ProtoMessage() {} + +func (x *StopDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StopDestinationCommand.ProtoReflect.Descriptor instead. +func (*StopDestinationCommand) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{5} +} + +func (x *StopDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type CloseOtherInstancesCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CloseOtherInstancesCommand) Reset() { + *x = CloseOtherInstancesCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseOtherInstancesCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseOtherInstancesCommand) ProtoMessage() {} + +func (x *CloseOtherInstancesCommand) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseOtherInstancesCommand.ProtoReflect.Descriptor instead. +func (*CloseOtherInstancesCommand) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{6} +} + +type QuitCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *QuitCommand) Reset() { + *x = QuitCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QuitCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QuitCommand) ProtoMessage() {} + +func (x *QuitCommand) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QuitCommand.ProtoReflect.Descriptor instead. +func (*QuitCommand) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{7} +} + +type Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to EventType: + // + // *Event_AppStateChanged + // *Event_DestinationStreamExited + // *Event_DestinationAdded + // *Event_AddDestinationFailed + // *Event_DestinationRemoved + // *Event_RemoveDestinationFailed + // *Event_StartDestinationFailed + // *Event_MediaServerStarted + // *Event_OtherInstanceDetected + // *Event_FatalError + EventType isEvent_EventType `protobuf_oneof:"event_type"` +} + +func (x *Event) Reset() { + *x = Event{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{8} +} + +func (m *Event) GetEventType() isEvent_EventType { + if m != nil { + return m.EventType + } + return nil +} + +func (x *Event) GetAppStateChanged() *AppStateChangedEvent { + if x, ok := x.GetEventType().(*Event_AppStateChanged); ok { + return x.AppStateChanged + } + return nil +} + +func (x *Event) GetDestinationStreamExited() *DestinationStreamExitedEvent { + if x, ok := x.GetEventType().(*Event_DestinationStreamExited); ok { + return x.DestinationStreamExited + } + return nil +} + +func (x *Event) GetDestinationAdded() *DestinationAddedEvent { + if x, ok := x.GetEventType().(*Event_DestinationAdded); ok { + return x.DestinationAdded + } + return nil +} + +func (x *Event) GetAddDestinationFailed() *AddDestinationFailedEvent { + if x, ok := x.GetEventType().(*Event_AddDestinationFailed); ok { + return x.AddDestinationFailed + } + return nil +} + +func (x *Event) GetDestinationRemoved() *DestinationRemovedEvent { + if x, ok := x.GetEventType().(*Event_DestinationRemoved); ok { + return x.DestinationRemoved + } + return nil +} + +func (x *Event) GetRemoveDestinationFailed() *RemoveDestinationFailedEvent { + if x, ok := x.GetEventType().(*Event_RemoveDestinationFailed); ok { + return x.RemoveDestinationFailed + } + return nil +} + +func (x *Event) GetStartDestinationFailed() *StartDestinationFailedEvent { + if x, ok := x.GetEventType().(*Event_StartDestinationFailed); ok { + return x.StartDestinationFailed + } + return nil +} + +func (x *Event) GetMediaServerStarted() *MediaServerStartedEvent { + if x, ok := x.GetEventType().(*Event_MediaServerStarted); ok { + return x.MediaServerStarted + } + return nil +} + +func (x *Event) GetOtherInstanceDetected() *OtherInstanceDetectedEvent { + if x, ok := x.GetEventType().(*Event_OtherInstanceDetected); ok { + return x.OtherInstanceDetected + } + return nil +} + +func (x *Event) GetFatalError() *FatalErrorEvent { + if x, ok := x.GetEventType().(*Event_FatalError); ok { + return x.FatalError + } + return nil +} + +type isEvent_EventType interface { + isEvent_EventType() +} + +type Event_AppStateChanged struct { + AppStateChanged *AppStateChangedEvent `protobuf:"bytes,1,opt,name=app_state_changed,json=appStateChanged,proto3,oneof"` +} + +type Event_DestinationStreamExited struct { + DestinationStreamExited *DestinationStreamExitedEvent `protobuf:"bytes,2,opt,name=destination_stream_exited,json=destinationStreamExited,proto3,oneof"` +} + +type Event_DestinationAdded struct { + DestinationAdded *DestinationAddedEvent `protobuf:"bytes,3,opt,name=destination_added,json=destinationAdded,proto3,oneof"` +} + +type Event_AddDestinationFailed struct { + AddDestinationFailed *AddDestinationFailedEvent `protobuf:"bytes,4,opt,name=add_destination_failed,json=addDestinationFailed,proto3,oneof"` +} + +type Event_DestinationRemoved struct { + DestinationRemoved *DestinationRemovedEvent `protobuf:"bytes,5,opt,name=destination_removed,json=destinationRemoved,proto3,oneof"` +} + +type Event_RemoveDestinationFailed struct { + RemoveDestinationFailed *RemoveDestinationFailedEvent `protobuf:"bytes,6,opt,name=remove_destination_failed,json=removeDestinationFailed,proto3,oneof"` +} + +type Event_StartDestinationFailed struct { + StartDestinationFailed *StartDestinationFailedEvent `protobuf:"bytes,7,opt,name=start_destination_failed,json=startDestinationFailed,proto3,oneof"` +} + +type Event_MediaServerStarted struct { + MediaServerStarted *MediaServerStartedEvent `protobuf:"bytes,8,opt,name=media_server_started,json=mediaServerStarted,proto3,oneof"` +} + +type Event_OtherInstanceDetected struct { + OtherInstanceDetected *OtherInstanceDetectedEvent `protobuf:"bytes,9,opt,name=other_instance_detected,json=otherInstanceDetected,proto3,oneof"` +} + +type Event_FatalError struct { + FatalError *FatalErrorEvent `protobuf:"bytes,10,opt,name=fatal_error,json=fatalError,proto3,oneof"` +} + +func (*Event_AppStateChanged) isEvent_EventType() {} + +func (*Event_DestinationStreamExited) isEvent_EventType() {} + +func (*Event_DestinationAdded) isEvent_EventType() {} + +func (*Event_AddDestinationFailed) isEvent_EventType() {} + +func (*Event_DestinationRemoved) isEvent_EventType() {} + +func (*Event_RemoveDestinationFailed) isEvent_EventType() {} + +func (*Event_StartDestinationFailed) isEvent_EventType() {} + +func (*Event_MediaServerStarted) isEvent_EventType() {} + +func (*Event_OtherInstanceDetected) isEvent_EventType() {} + +func (*Event_FatalError) isEvent_EventType() {} + +// TODO: complete +type AppStateChangedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *AppStateChangedEvent) Reset() { + *x = AppStateChangedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppStateChangedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppStateChangedEvent) ProtoMessage() {} + +func (x *AppStateChangedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppStateChangedEvent.ProtoReflect.Descriptor instead. +func (*AppStateChangedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{9} +} + +type DestinationStreamExitedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *DestinationStreamExitedEvent) Reset() { + *x = DestinationStreamExitedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DestinationStreamExitedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DestinationStreamExitedEvent) ProtoMessage() {} + +func (x *DestinationStreamExitedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DestinationStreamExitedEvent.ProtoReflect.Descriptor instead. +func (*DestinationStreamExitedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{10} +} + +func (x *DestinationStreamExitedEvent) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DestinationStreamExitedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type DestinationAddedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *DestinationAddedEvent) Reset() { + *x = DestinationAddedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DestinationAddedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DestinationAddedEvent) ProtoMessage() {} + +func (x *DestinationAddedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DestinationAddedEvent.ProtoReflect.Descriptor instead. +func (*DestinationAddedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{11} +} + +func (x *DestinationAddedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type AddDestinationFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *AddDestinationFailedEvent) Reset() { + *x = AddDestinationFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddDestinationFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddDestinationFailedEvent) ProtoMessage() {} + +func (x *AddDestinationFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddDestinationFailedEvent.ProtoReflect.Descriptor instead. +func (*AddDestinationFailedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{12} +} + +func (x *AddDestinationFailedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *AddDestinationFailedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type DestinationRemovedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *DestinationRemovedEvent) Reset() { + *x = DestinationRemovedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DestinationRemovedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DestinationRemovedEvent) ProtoMessage() {} + +func (x *DestinationRemovedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DestinationRemovedEvent.ProtoReflect.Descriptor instead. +func (*DestinationRemovedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{13} +} + +func (x *DestinationRemovedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type RemoveDestinationFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *RemoveDestinationFailedEvent) Reset() { + *x = RemoveDestinationFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveDestinationFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveDestinationFailedEvent) ProtoMessage() {} + +func (x *RemoveDestinationFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveDestinationFailedEvent.ProtoReflect.Descriptor instead. +func (*RemoveDestinationFailedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{14} +} + +func (x *RemoveDestinationFailedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *RemoveDestinationFailedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type StartDestinationFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *StartDestinationFailedEvent) Reset() { + *x = StartDestinationFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartDestinationFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartDestinationFailedEvent) ProtoMessage() {} + +func (x *StartDestinationFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartDestinationFailedEvent.ProtoReflect.Descriptor instead. +func (*StartDestinationFailedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{15} +} + +func (x *StartDestinationFailedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *StartDestinationFailedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type MediaServerStartedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RtmpUrl string `protobuf:"bytes,1,opt,name=rtmp_url,json=rtmpUrl,proto3" json:"rtmp_url,omitempty"` + RtmpsUrl string `protobuf:"bytes,2,opt,name=rtmps_url,json=rtmpsUrl,proto3" json:"rtmps_url,omitempty"` +} + +func (x *MediaServerStartedEvent) Reset() { + *x = MediaServerStartedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MediaServerStartedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MediaServerStartedEvent) ProtoMessage() {} + +func (x *MediaServerStartedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MediaServerStartedEvent.ProtoReflect.Descriptor instead. +func (*MediaServerStartedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{16} +} + +func (x *MediaServerStartedEvent) GetRtmpUrl() string { + if x != nil { + return x.RtmpUrl + } + return "" +} + +func (x *MediaServerStartedEvent) GetRtmpsUrl() string { + if x != nil { + return x.RtmpsUrl + } + return "" +} + +type OtherInstanceDetectedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OtherInstanceDetectedEvent) Reset() { + *x = OtherInstanceDetectedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OtherInstanceDetectedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtherInstanceDetectedEvent) ProtoMessage() {} + +func (x *OtherInstanceDetectedEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OtherInstanceDetectedEvent.ProtoReflect.Descriptor instead. +func (*OtherInstanceDetectedEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{17} +} + +type FatalErrorEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *FatalErrorEvent) Reset() { + *x = FatalErrorEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_api_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FatalErrorEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FatalErrorEvent) ProtoMessage() {} + +func (x *FatalErrorEvent) ProtoReflect() protoreflect.Message { + mi := &file_proto_api_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FatalErrorEvent.ProtoReflect.Descriptor instead. +func (*FatalErrorEvent) Descriptor() ([]byte, []int) { + return file_proto_api_proto_rawDescGZIP(), []int{18} +} + +func (x *FatalErrorEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +var File_proto_api_proto protoreflect.FileDescriptor + +var file_proto_api_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x22, 0x63, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, + 0x70, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc4, 0x03, 0x0a, 0x07, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x43, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x5f, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, + 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x12, + 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x11, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x10, 0x73, 0x74, 0x6f, + 0x70, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x48, 0x00, 0x52, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x15, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x74, 0x68, + 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, + 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x13, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, + 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x71, 0x75, + 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x51, + 0x75, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x04, 0x71, 0x75, + 0x69, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x22, 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x6c, 0x22, 0x2c, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, + 0x2b, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2a, 0x0a, 0x16, + 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, + 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, + 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x51, 0x75, 0x69, 0x74, 0x43, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xd8, 0x06, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x47, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, + 0x78, 0x69, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x17, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, + 0x64, 0x64, 0x65, 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x13, + 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5f, 0x0a, + 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x5c, + 0x0a, 0x18, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x14, + 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, + 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, 0x64, 0x69, + 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, + 0x0a, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x61, 0x74, + 0x61, 0x6c, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x42, 0x0c, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x16, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x48, 0x0a, 0x1c, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, + 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x43, 0x0a, + 0x19, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x22, 0x2b, 0x0a, 0x17, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, + 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, + 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x45, 0x0a, 0x1b, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x51, + 0x0a, 0x17, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x74, 0x6d, + 0x70, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x74, 0x6d, + 0x70, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x5f, 0x75, 0x72, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x55, 0x72, + 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x27, 0x0a, 0x0f, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x50, 0x49, 0x12, 0x2f, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, + 0x6e, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, + 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x1a, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, + 0x6c, 0x6f, 0x70, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x2e, + 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, + 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_api_proto_rawDescOnce sync.Once + file_proto_api_proto_rawDescData = file_proto_api_proto_rawDesc +) + +func file_proto_api_proto_rawDescGZIP() []byte { + file_proto_api_proto_rawDescOnce.Do(func() { + file_proto_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_api_proto_rawDescData) + }) + return file_proto_api_proto_rawDescData +} + +var file_proto_api_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_proto_api_proto_goTypes = []interface{}{ + (*Envelope)(nil), // 0: api.Envelope + (*Command)(nil), // 1: api.Command + (*AddDestinationCommand)(nil), // 2: api.AddDestinationCommand + (*RemoveDestinationCommand)(nil), // 3: api.RemoveDestinationCommand + (*StartDestinationCommand)(nil), // 4: api.StartDestinationCommand + (*StopDestinationCommand)(nil), // 5: api.StopDestinationCommand + (*CloseOtherInstancesCommand)(nil), // 6: api.CloseOtherInstancesCommand + (*QuitCommand)(nil), // 7: api.QuitCommand + (*Event)(nil), // 8: api.Event + (*AppStateChangedEvent)(nil), // 9: api.AppStateChangedEvent + (*DestinationStreamExitedEvent)(nil), // 10: api.DestinationStreamExitedEvent + (*DestinationAddedEvent)(nil), // 11: api.DestinationAddedEvent + (*AddDestinationFailedEvent)(nil), // 12: api.AddDestinationFailedEvent + (*DestinationRemovedEvent)(nil), // 13: api.DestinationRemovedEvent + (*RemoveDestinationFailedEvent)(nil), // 14: api.RemoveDestinationFailedEvent + (*StartDestinationFailedEvent)(nil), // 15: api.StartDestinationFailedEvent + (*MediaServerStartedEvent)(nil), // 16: api.MediaServerStartedEvent + (*OtherInstanceDetectedEvent)(nil), // 17: api.OtherInstanceDetectedEvent + (*FatalErrorEvent)(nil), // 18: api.FatalErrorEvent +} +var file_proto_api_proto_depIdxs = []int32{ + 1, // 0: api.Envelope.command:type_name -> api.Command + 8, // 1: api.Envelope.event:type_name -> api.Event + 2, // 2: api.Command.add_destinaion:type_name -> api.AddDestinationCommand + 3, // 3: api.Command.remove_destination:type_name -> api.RemoveDestinationCommand + 4, // 4: api.Command.start_destination:type_name -> api.StartDestinationCommand + 5, // 5: api.Command.stop_destination:type_name -> api.StopDestinationCommand + 6, // 6: api.Command.close_other_instances:type_name -> api.CloseOtherInstancesCommand + 7, // 7: api.Command.quit:type_name -> api.QuitCommand + 9, // 8: api.Event.app_state_changed:type_name -> api.AppStateChangedEvent + 10, // 9: api.Event.destination_stream_exited:type_name -> api.DestinationStreamExitedEvent + 11, // 10: api.Event.destination_added:type_name -> api.DestinationAddedEvent + 12, // 11: api.Event.add_destination_failed:type_name -> api.AddDestinationFailedEvent + 13, // 12: api.Event.destination_removed:type_name -> api.DestinationRemovedEvent + 14, // 13: api.Event.remove_destination_failed:type_name -> api.RemoveDestinationFailedEvent + 15, // 14: api.Event.start_destination_failed:type_name -> api.StartDestinationFailedEvent + 16, // 15: api.Event.media_server_started:type_name -> api.MediaServerStartedEvent + 17, // 16: api.Event.other_instance_detected:type_name -> api.OtherInstanceDetectedEvent + 18, // 17: api.Event.fatal_error:type_name -> api.FatalErrorEvent + 0, // 18: api.InternalAPI.Communicate:input_type -> api.Envelope + 0, // 19: api.InternalAPI.Communicate:output_type -> api.Envelope + 19, // [19:20] is the sub-list for method output_type + 18, // [18:19] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name +} + +func init() { file_proto_api_proto_init() } +func file_proto_api_proto_init() { + if File_proto_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Envelope); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Command); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseOtherInstancesCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QuitCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Event); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppStateChangedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DestinationStreamExitedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DestinationAddedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddDestinationFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DestinationRemovedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveDestinationFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartDestinationFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MediaServerStartedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OtherInstanceDetectedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_api_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FatalErrorEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_proto_api_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Envelope_Command)(nil), + (*Envelope_Event)(nil), + } + file_proto_api_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*Command_AddDestinaion)(nil), + (*Command_RemoveDestination)(nil), + (*Command_StartDestination)(nil), + (*Command_StopDestination)(nil), + (*Command_CloseOtherInstances)(nil), + (*Command_Quit)(nil), + } + file_proto_api_proto_msgTypes[8].OneofWrappers = []interface{}{ + (*Event_AppStateChanged)(nil), + (*Event_DestinationStreamExited)(nil), + (*Event_DestinationAdded)(nil), + (*Event_AddDestinationFailed)(nil), + (*Event_DestinationRemoved)(nil), + (*Event_RemoveDestinationFailed)(nil), + (*Event_StartDestinationFailed)(nil), + (*Event_MediaServerStarted)(nil), + (*Event_OtherInstanceDetected)(nil), + (*Event_FatalError)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_api_proto_rawDesc, + NumEnums: 0, + NumMessages: 19, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_api_proto_goTypes, + DependencyIndexes: file_proto_api_proto_depIdxs, + MessageInfos: file_proto_api_proto_msgTypes, + }.Build() + File_proto_api_proto = out.File + file_proto_api_proto_rawDesc = nil + file_proto_api_proto_goTypes = nil + file_proto_api_proto_depIdxs = nil +} diff --git a/internal/generated/grpc/proto/api_grpc.pb.go b/internal/generated/grpc/proto/api_grpc.pb.go new file mode 100644 index 0000000..431ebaf --- /dev/null +++ b/internal/generated/grpc/proto/api_grpc.pb.go @@ -0,0 +1,135 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.6 +// source: proto/api.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// InternalAPIClient is the client API for InternalAPI service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type InternalAPIClient interface { + Communicate(ctx context.Context, opts ...grpc.CallOption) (InternalAPI_CommunicateClient, error) +} + +type internalAPIClient struct { + cc grpc.ClientConnInterface +} + +func NewInternalAPIClient(cc grpc.ClientConnInterface) InternalAPIClient { + return &internalAPIClient{cc} +} + +func (c *internalAPIClient) Communicate(ctx context.Context, opts ...grpc.CallOption) (InternalAPI_CommunicateClient, error) { + stream, err := c.cc.NewStream(ctx, &InternalAPI_ServiceDesc.Streams[0], "/api.InternalAPI/Communicate", opts...) + if err != nil { + return nil, err + } + x := &internalAPICommunicateClient{stream} + return x, nil +} + +type InternalAPI_CommunicateClient interface { + Send(*Envelope) error + Recv() (*Envelope, error) + grpc.ClientStream +} + +type internalAPICommunicateClient struct { + grpc.ClientStream +} + +func (x *internalAPICommunicateClient) Send(m *Envelope) error { + return x.ClientStream.SendMsg(m) +} + +func (x *internalAPICommunicateClient) Recv() (*Envelope, error) { + m := new(Envelope) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// InternalAPIServer is the server API for InternalAPI service. +// All implementations should embed UnimplementedInternalAPIServer +// for forward compatibility +type InternalAPIServer interface { + Communicate(InternalAPI_CommunicateServer) error +} + +// UnimplementedInternalAPIServer should be embedded to have forward compatible implementations. +type UnimplementedInternalAPIServer struct { +} + +func (UnimplementedInternalAPIServer) Communicate(InternalAPI_CommunicateServer) error { + return status.Errorf(codes.Unimplemented, "method Communicate not implemented") +} + +// UnsafeInternalAPIServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to InternalAPIServer will +// result in compilation errors. +type UnsafeInternalAPIServer interface { + mustEmbedUnimplementedInternalAPIServer() +} + +func RegisterInternalAPIServer(s grpc.ServiceRegistrar, srv InternalAPIServer) { + s.RegisterService(&InternalAPI_ServiceDesc, srv) +} + +func _InternalAPI_Communicate_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(InternalAPIServer).Communicate(&internalAPICommunicateServer{stream}) +} + +type InternalAPI_CommunicateServer interface { + Send(*Envelope) error + Recv() (*Envelope, error) + grpc.ServerStream +} + +type internalAPICommunicateServer struct { + grpc.ServerStream +} + +func (x *internalAPICommunicateServer) Send(m *Envelope) error { + return x.ServerStream.SendMsg(m) +} + +func (x *internalAPICommunicateServer) Recv() (*Envelope, error) { + m := new(Envelope) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// InternalAPI_ServiceDesc is the grpc.ServiceDesc for InternalAPI service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var InternalAPI_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "api.InternalAPI", + HandlerType: (*InternalAPIServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Communicate", + Handler: _InternalAPI_Communicate_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "proto/api.proto", +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..7311bd2 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,7 @@ +package server + +import pb "git.netflux.io/rob/octoplex/internal/generated/grpc/proto" + +type Server struct { + pb.UnimplementedInternalAPIServer +} diff --git a/mise/config.toml b/mise/config.toml index 38f1a91..999624d 100644 --- a/mise/config.toml +++ b/mise/config.toml @@ -40,3 +40,9 @@ description = "Generate mocks" dir = "{{cwd}}" run = "go tool mockery" alias = "m" + +[tasks.generate_proto] +description = "Generate gRPC files from proto" +dir = "{{cwd}}" +run = "protoc --go_out=./generated/grpc --go-grpc_out=./generated/grpc proto/*.proto" +alias = "p" diff --git a/proto/api.proto b/proto/api.proto new file mode 100644 index 0000000..190ea64 --- /dev/null +++ b/proto/api.proto @@ -0,0 +1,105 @@ +syntax = "proto3"; + +package api; + +option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc/proto"; + +service InternalAPI { + rpc Communicate(stream Envelope) returns (stream Envelope); +} + +message Envelope { + oneof payload { + Command command = 1; + Event event = 2; + } +} + +message Command { + oneof command_type { + AddDestinationCommand add_destinaion = 1; + RemoveDestinationCommand remove_destination = 2; + StartDestinationCommand start_destination = 3; + StopDestinationCommand stop_destination = 4; + CloseOtherInstancesCommand close_other_instances = 5; + QuitCommand quit = 6; + } +} + +message AddDestinationCommand { + string name = 1; + string url = 2; +} + +message RemoveDestinationCommand { + string url= 1; +} + +message StartDestinationCommand { + string url = 1; +} + +message StopDestinationCommand { + string url = 1; +} + +message CloseOtherInstancesCommand {} + +message QuitCommand {} + +message Event { + oneof event_type { + AppStateChangedEvent app_state_changed = 1; + DestinationStreamExitedEvent destination_stream_exited = 2; + DestinationAddedEvent destination_added = 3; + AddDestinationFailedEvent add_destination_failed = 4; + DestinationRemovedEvent destination_removed = 5; + RemoveDestinationFailedEvent remove_destination_failed = 6; + StartDestinationFailedEvent start_destination_failed = 7; + MediaServerStartedEvent media_server_started = 8; + OtherInstanceDetectedEvent other_instance_detected = 9; + FatalErrorEvent fatal_error = 10; + } +} + +// TODO: complete +message AppStateChangedEvent {} + +message DestinationStreamExitedEvent { + string name = 1; + string error = 2; +} + +message DestinationAddedEvent { + string url = 1; +} + +message AddDestinationFailedEvent { + string url = 1; + string error = 2; +} + +message DestinationRemovedEvent { + string url = 1; +} + +message RemoveDestinationFailedEvent { + string url = 1; + string error = 2; +} + +message StartDestinationFailedEvent { + string url = 1; + string error = 2; +} + +message MediaServerStartedEvent { + string rtmp_url = 1; + string rtmps_url = 2; +} + +message OtherInstanceDetectedEvent {} + +message FatalErrorEvent { + string error = 1; +} -- 2.47.2 From 0a6b9fad9075acaa33344815ba03c9d922f25ff1 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Wed, 7 May 2025 20:32:27 +0200 Subject: [PATCH 03/23] fixup! wip: refactor: API --- cmd/client/main.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ cmd/server/main.go | 69 +++++++++++---------------------------- internal/app/app.go | 51 +++-------------------------- 3 files changed, 100 insertions(+), 98 deletions(-) create mode 100644 cmd/client/main.go diff --git a/cmd/client/main.go b/cmd/client/main.go new file mode 100644 index 0000000..5d04a6f --- /dev/null +++ b/cmd/client/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "context" + "fmt" + "log/slog" + "os" + "runtime/debug" + + "git.netflux.io/rob/octoplex/internal/domain" + "git.netflux.io/rob/octoplex/internal/event" + "git.netflux.io/rob/octoplex/internal/terminal" + "golang.design/x/clipboard" +) + +var ( + // version is the version of the application. + version string + // commit is the commit hash of the application. + commit string + // date is the date of the build. + date string +) + +func main() { + if err := run(); err != nil { + os.Stderr.WriteString("Error: " + err.Error() + "\n") + } +} + +func run() error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // TODO: logger from config + fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return fmt.Errorf("open log file: %w", err) + } + logger := slog.New(slog.NewTextHandler(fptr, nil)) + + bus := event.NewBus(logger) + + var clipboardAvailable bool + if err = clipboard.Init(); err != nil { + logger.Warn("Clipboard not available", "err", err) + } else { + clipboardAvailable = true + } + + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return fmt.Errorf("read build info: %w", err) + } + + ui, err := terminal.StartUI(ctx, terminal.StartParams{ + EventBus: bus, + Dispatcher: func(cmd event.Command) { + // TODO: this must call the gRPC client + logger.Info("Command dispatched", "cmd", cmd) + }, + ClipboardAvailable: clipboardAvailable, + ConfigFilePath: "TODO", + BuildInfo: domain.BuildInfo{ + GoVersion: buildInfo.GoVersion, + Version: version, + Commit: commit, + Date: date, + }, + Logger: logger.With("component", "ui"), + }) + if err != nil { + return fmt.Errorf("start terminal user interface: %w", err) + } + defer ui.Close() + + return nil +} diff --git a/cmd/server/main.go b/cmd/server/main.go index 4573104..15b2ab2 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -6,7 +6,6 @@ import ( "errors" "flag" "fmt" - "io" "log/slog" "os" "os/exec" @@ -18,7 +17,6 @@ import ( "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/domain" dockerclient "github.com/docker/docker/client" - "golang.design/x/clipboard" ) var ( @@ -85,31 +83,21 @@ func run() error { return fmt.Errorf("read or create config: %w", err) } - headless := os.Getenv("OCTO_HEADLESS") != "" - logger, err := buildLogger(cfg.LogFile, headless) + logger, err := buildLogger(cfg.LogFile) if err != nil { return fmt.Errorf("build logger: %w", err) } - if headless { - // When running in headless mode tview doesn't handle SIGINT for us. - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + // When running in headless mode tview doesn't handle SIGINT for us. + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-ch - logger.Info("Received interrupt signal, exiting") - signal.Stop(ch) - cancel(errShutdown) - }() - } - - var clipboardAvailable bool - if err = clipboard.Init(); err != nil { - logger.Warn("Clipboard not available", "err", err) - } else { - clipboardAvailable = true - } + go func() { + <-ch + logger.Info("Received interrupt signal, exiting") + signal.Stop(ch) + cancel(errShutdown) + }() dockerClient, err := dockerclient.NewClientWithOpts( dockerclient.FromEnv, @@ -125,11 +113,9 @@ func run() error { } app := app.New(app.Params{ - ConfigService: configService, - DockerClient: dockerClient, - Headless: headless, - ClipboardAvailable: clipboardAvailable, - ConfigFilePath: configService.Path(), + ConfigService: configService, + DockerClient: dockerClient, + ConfigFilePath: configService.Path(), BuildInfo: domain.BuildInfo{ GoVersion: buildInfo.GoVersion, Version: version, @@ -189,32 +175,13 @@ func printUsage() { os.Stderr.WriteString("\n") os.Stderr.WriteString("Additionally, Octoplex can be configured with the following environment variables:\n\n") os.Stderr.WriteString(" OCTO_DEBUG Enables debug logging if set\n") - os.Stderr.WriteString(" OCTO_HEADLESS Enables headless mode if set (experimental)\n\n") } // buildLogger builds the logger, which may be a no-op logger. -func buildLogger(cfg config.LogFile, headless bool) (*slog.Logger, error) { - build := func(w io.Writer) *slog.Logger { - var handlerOpts slog.HandlerOptions - if os.Getenv("OCTO_DEBUG") != "" { - handlerOpts.Level = slog.LevelDebug - } - return slog.New(slog.NewTextHandler(w, &handlerOpts)) +func buildLogger(cfg config.LogFile) (*slog.Logger, error) { + var handlerOpts slog.HandlerOptions + if os.Getenv("OCTO_DEBUG") != "" { + handlerOpts.Level = slog.LevelDebug } - - // In headless mode, always log to stderr. - if headless { - return build(os.Stderr), nil - } - - if !cfg.Enabled { - return slog.New(slog.DiscardHandler), nil - } - - fptr, err := os.OpenFile(cfg.GetPath(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - return nil, fmt.Errorf("error opening log file: %w", err) - } - - return build(fptr), nil + return slog.New(slog.NewTextHandler(os.Stderr, &handlerOpts)), nil } diff --git a/internal/app/app.go b/internal/app/app.go index 779875c..27f07fb 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -32,7 +32,6 @@ type App struct { dispatchC chan event.Command dockerClient container.DockerClient screen *terminal.Screen // Screen may be nil. - headless bool clipboardAvailable bool configFilePath string buildInfo domain.BuildInfo @@ -45,7 +44,6 @@ type Params struct { DockerClient container.DockerClient ChanSize int Screen *terminal.Screen // Screen may be nil. - Headless bool ClipboardAvailable bool ConfigFilePath string BuildInfo domain.BuildInfo @@ -64,7 +62,6 @@ func New(params Params) *App { dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)), dockerClient: params.DockerClient, screen: params.Screen, - headless: params.Headless, clipboardAvailable: params.ClipboardAvailable, configFilePath: params.ConfigFilePath, buildInfo: params.BuildInfo, @@ -83,46 +80,10 @@ func (a *App) Run(ctx context.Context) error { return errors.New("config: either sources.mediaServer.rtmp.enabled or sources.mediaServer.rtmps.enabled must be set") } - if !a.headless { - ui, err := terminal.StartUI(ctx, terminal.StartParams{ - EventBus: a.eventBus, - Dispatcher: func(cmd event.Command) { a.dispatchC <- cmd }, - Screen: a.screen, - ClipboardAvailable: a.clipboardAvailable, - ConfigFilePath: a.configFilePath, - BuildInfo: a.buildInfo, - Logger: a.logger.With("component", "ui"), - }) - if err != nil { - return fmt.Errorf("start terminal user interface: %w", err) - } - defer ui.Close() - } - - // emptyUI is a dummy function that sets the UI state to an empty state, and - // re-renders the screen. - // - // This is a workaround for a weird interaction between tview and - // tcell.SimulationScreen which leads to newly-added pages not rendering if - // the UI is not re-rendered for a second time. - // It is only needed for integration tests when rendering modals before the - // main loop starts. It would be nice to remove this but the risk/impact on - // non-test code is pretty low. - emptyUI := func() { - a.eventBus.Send(event.AppStateChangedEvent{State: domain.AppState{}}) - } - // doFatalError publishes a fatal error to the event bus, waiting for the // user to acknowledge it if not in headless mode. doFatalError := func(msg string) { a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg}) - - if a.headless { - return - } - - emptyUI() - <-a.dispatchC } const grpcAddr = ":50051" @@ -195,15 +156,11 @@ func (a *App) Run(ctx context.Context) error { defer uiUpdateT.Stop() startMediaServerC := make(chan struct{}, 1) - if a.headless { // disable startup check in headless mode for now + if ok, startupErr := doStartupCheck(ctx, containerClient, a.eventBus); startupErr != nil { + doFatalError(startupErr.Error()) + return startupErr + } else if ok { startMediaServerC <- struct{}{} - } else { - if ok, startupErr := doStartupCheck(ctx, containerClient, a.eventBus); startupErr != nil { - doFatalError(startupErr.Error()) - return startupErr - } else if ok { - startMediaServerC <- struct{}{} - } } for { -- 2.47.2 From 6ceb286e6b54d8fff041a5585cae8164e3028d62 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Thu, 8 May 2025 07:08:43 +0200 Subject: [PATCH 04/23] fixup! wip: refactor: API --- cmd/client/main.go | 48 +- internal/app/app.go | 8 +- internal/client/protocol.go | 238 ++ internal/event/events.go | 7 +- internal/generated/grpc/api.pb.go | 2351 +++++++++++++++++ .../generated/grpc/{proto => }/api_grpc.pb.go | 14 +- internal/generated/grpc/proto/api.pb.go | 1691 ------------ internal/server/protocol.go | 1 + internal/server/server.go | 2 +- mise/config.toml | 2 +- proto/api.proto | 67 +- 11 files changed, 2718 insertions(+), 1711 deletions(-) create mode 100644 internal/client/protocol.go create mode 100644 internal/generated/grpc/api.pb.go rename internal/generated/grpc/{proto => }/api_grpc.pb.go (91%) delete mode 100644 internal/generated/grpc/proto/api.pb.go create mode 100644 internal/server/protocol.go diff --git a/cmd/client/main.go b/cmd/client/main.go index 5d04a6f..4afe0cf 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -6,11 +6,16 @@ import ( "log/slog" "os" "runtime/debug" + "time" + "git.netflux.io/rob/octoplex/internal/client" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" "git.netflux.io/rob/octoplex/internal/terminal" "golang.design/x/clipboard" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) var ( @@ -53,11 +58,50 @@ func run() error { return fmt.Errorf("read build info: %w", err) } + conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return fmt.Errorf("connect to gRPC server: %w", err) + } + apiClient := pb.NewInternalAPIClient(conn) + stream, err := apiClient.Communicate(ctx) + if err != nil { + return fmt.Errorf("create gRPC stream: %w", err) + } + + go func() { + for evt := range bus.Register() { + if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: client.EventToProto(evt)}}); sendErr != nil { + logger.Error("Error sending event to gRPC API", "err", sendErr) + } + } + }() + + go func() { + for { + envelope, recErr := stream.Recv() + if recErr != nil { + logger.Error("Error receiving envelope from gRPC API", "err", recErr) + continue + } + + evt := envelope.GetEvent() + if evt == nil { + logger.Error("Received envelope without event") + continue + } + + logger.Info("Received event from gRPC API", "event", evt) + // TODO: convert to domain event + } + }() + ui, err := terminal.StartUI(ctx, terminal.StartParams{ EventBus: bus, Dispatcher: func(cmd event.Command) { - // TODO: this must call the gRPC client logger.Info("Command dispatched", "cmd", cmd) + if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: client.CommandToProto(cmd)}}); sendErr != nil { + logger.Error("Error sending command to gRPC API", "err", sendErr) + } }, ClipboardAvailable: clipboardAvailable, ConfigFilePath: "TODO", @@ -74,5 +118,7 @@ func run() error { } defer ui.Close() + time.Sleep(10 * time.Minute) // Simulate long-running process + return nil } diff --git a/internal/app/app.go b/internal/app/app.go index 27f07fb..16aecea 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -15,7 +15,7 @@ import ( "git.netflux.io/rob/octoplex/internal/container" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" - pb "git.netflux.io/rob/octoplex/internal/generated/grpc/proto" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" "git.netflux.io/rob/octoplex/internal/mediaserver" "git.netflux.io/rob/octoplex/internal/replicator" "git.netflux.io/rob/octoplex/internal/server" @@ -259,7 +259,7 @@ func (a *App) handleCommand( }) if err := a.configService.SetConfig(newCfg); err != nil { a.logger.Error("Add destination failed", "err", err) - return event.AddDestinationFailedEvent{Err: err}, nil + return event.AddDestinationFailedEvent{URL: c.URL, Err: err}, nil } a.cfg = newCfg a.handleConfigUpdate(state) @@ -272,7 +272,7 @@ func (a *App) handleCommand( }) if err := a.configService.SetConfig(newCfg); err != nil { a.logger.Error("Remove destination failed", "err", err) - a.eventBus.Send(event.RemoveDestinationFailedEvent{Err: err}) + a.eventBus.Send(event.RemoveDestinationFailedEvent{URL: c.URL, Err: err}) break } a.cfg = newCfg @@ -280,7 +280,7 @@ func (a *App) handleCommand( a.eventBus.Send(event.DestinationRemovedEvent{URL: c.URL}) //nolint:gosimple case event.CommandStartDestination: if !state.Source.Live { - a.eventBus.Send(event.StartDestinationFailedEvent{}) + a.eventBus.Send(event.StartDestinationFailedEvent{URL: c.URL, Message: "source not live"}) break } diff --git a/internal/client/protocol.go b/internal/client/protocol.go new file mode 100644 index 0000000..b210b87 --- /dev/null +++ b/internal/client/protocol.go @@ -0,0 +1,238 @@ +// TODO: move protocol to a separate package +package client + +import ( + "git.netflux.io/rob/octoplex/internal/domain" + "git.netflux.io/rob/octoplex/internal/event" + "google.golang.org/protobuf/types/known/timestamppb" + + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" +) + +// EventToProto converts an event to a protobuf message. +func EventToProto(ev event.Event) *pb.Event { + switch evt := ev.(type) { + case event.AppStateChangedEvent: + return buildAppStateChangeEvent(evt) + case event.DestinationAddedEvent: + return buildDestinationAddedEvent(evt) + case event.AddDestinationFailedEvent: + return buildAddDestinationFailedEvent(evt) + case event.DestinationStreamExitedEvent: + return buildDestinationStreamExitedEvent(evt) + case event.StartDestinationFailedEvent: + return buildStartDestinationFailedEvent(evt) + case event.DestinationRemovedEvent: + return buildDestinationRemovedEvent(evt) + case event.RemoveDestinationFailedEvent: + return buildRemoveDestinationFailedEvent(evt) + case event.FatalErrorOccurredEvent: + return buildFatalErrorOccurredEvent(evt) + case event.OtherInstanceDetectedEvent: + return buildOtherInstanceDetectedEvent(evt) + case event.MediaServerStartedEvent: + return buildMediaServerStartedEvent(evt) + default: + panic("unknown event type") + } +} + +// CommandToProto converts a command to a protobuf message. +func CommandToProto(command event.Command) *pb.Command { + switch evt := command.(type) { + case event.CommandAddDestination: + return buildAddDestinationCommand(evt) + case event.CommandRemoveDestination: + return buildRemoveDestinationCommand(evt) + case event.CommandStartDestination: + return buildStartDestinationCommand(evt) + case event.CommandStopDestination: + return buildStopDestinationCommand(evt) + case event.CommandCloseOtherInstance: + return buildCloseOtherInstanceCommand(evt) + case event.CommandQuit: + return buildQuitCommand(evt) + default: + panic("unknown command type") + } +} + +func buildAppStateChangeEvent(evt event.AppStateChangedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_AppStateChanged{ + AppStateChanged: &pb.AppStateChangedEvent{ + AppState: &pb.AppState{ + Source: &pb.Source{ + Container: containerToProto(evt.State.Source.Container), + Live: evt.State.Source.Live, + LiveChangedAt: timestamppb.New(evt.State.Source.LiveChangedAt), + Tracks: evt.State.Source.Tracks, + ExitReason: evt.State.Source.ExitReason, + }, + Destinations: destinationsToProto(evt.State.Destinations), + BuildInfo: &pb.BuildInfo{ + GoVersion: evt.State.BuildInfo.GoVersion, + Version: evt.State.BuildInfo.Version, + Commit: evt.State.BuildInfo.Commit, + Date: evt.State.BuildInfo.Date, + }, + }, + }, + }, + } +} + +func buildDestinationAddedEvent(evt event.DestinationAddedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_DestinationAdded{ + DestinationAdded: &pb.DestinationAddedEvent{Url: evt.URL}, + }, + } +} + +func buildAddDestinationFailedEvent(evt event.AddDestinationFailedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_AddDestinationFailed{ + AddDestinationFailed: &pb.AddDestinationFailedEvent{Url: evt.URL, Error: evt.Err.Error()}, + }, + } +} + +func buildDestinationStreamExitedEvent(evt event.DestinationStreamExitedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_DestinationStreamExited{ + DestinationStreamExited: &pb.DestinationStreamExitedEvent{Name: evt.Name, Error: evt.Err.Error()}, + }, + } +} + +func buildStartDestinationFailedEvent(evt event.StartDestinationFailedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_StartDestinationFailed{ + StartDestinationFailed: &pb.StartDestinationFailedEvent{Url: evt.URL, Message: evt.Message}, + }, + } +} + +func buildDestinationRemovedEvent(evt event.DestinationRemovedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_DestinationRemoved{ + DestinationRemoved: &pb.DestinationRemovedEvent{Url: evt.URL}, + }, + } +} + +func buildRemoveDestinationFailedEvent(evt event.RemoveDestinationFailedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_RemoveDestinationFailed{ + RemoveDestinationFailed: &pb.RemoveDestinationFailedEvent{Url: evt.URL, Error: evt.Err.Error()}, + }, + } +} + +func buildFatalErrorOccurredEvent(evt event.FatalErrorOccurredEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_FatalError{ + FatalError: &pb.FatalErrorEvent{Message: evt.Message}, + }, + } +} + +func buildOtherInstanceDetectedEvent(_ event.OtherInstanceDetectedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_OtherInstanceDetected{ + OtherInstanceDetected: &pb.OtherInstanceDetectedEvent{}, + }, + } +} + +func buildMediaServerStartedEvent(evt event.MediaServerStartedEvent) *pb.Event { + return &pb.Event{ + EventType: &pb.Event_MediaServerStarted{ + MediaServerStarted: &pb.MediaServerStartedEvent{RtmpUrl: evt.RTMPURL, RtmpsUrl: evt.RTMPSURL}, + }, + } +} + +func containerToProto(c domain.Container) *pb.Container { + var errString string + if c.Err != nil { + errString = c.Err.Error() + } + + var exitCode *int32 + if c.ExitCode != nil { + code := int32(*c.ExitCode) + exitCode = &code + } + + return &pb.Container{ + Id: c.ID, + Status: c.Status, + HealthState: c.HealthState, + CpuPercent: c.CPUPercent, + MemoryUsageBytes: c.MemoryUsageBytes, + RxRate: int32(c.RxRate), + TxRate: int32(c.TxRate), + RxSince: timestamppb.New(c.RxSince), + ImageName: c.ImageName, + PullStatus: c.PullStatus, + PullProgress: c.PullProgress, + PullPercent: int32(c.PullPercent), + RestartCount: int32(c.RestartCount), + ExitCode: exitCode, + Err: errString, + } +} + +func destinationsToProto(inDests []domain.Destination) []*pb.Destination { + destinations := make([]*pb.Destination, 0, len(inDests)) + for _, d := range inDests { + destinations = append(destinations, destinationToProto(d)) + } + return destinations +} + +func destinationToProto(d domain.Destination) *pb.Destination { + return &pb.Destination{ + Container: containerToProto(d.Container), + Status: destinationStatusToProto(d.Status), + Name: d.Name, + Url: d.URL, + } +} + +func destinationStatusToProto(s domain.DestinationStatus) pb.Destination_Status { + switch s { + case domain.DestinationStatusStarting: + return pb.Destination_STATUS_STARTING + case domain.DestinationStatusLive: + return pb.Destination_STATUS_LIVE + default: + return pb.Destination_STATUS_OFF_AIR + } +} + +func buildAddDestinationCommand(cmd event.CommandAddDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_AddDestination{AddDestination: &pb.AddDestinationCommand{Url: cmd.URL}}} +} + +func buildRemoveDestinationCommand(cmd event.CommandRemoveDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_RemoveDestination{RemoveDestination: &pb.RemoveDestinationCommand{Url: cmd.URL}}} +} + +func buildStartDestinationCommand(cmd event.CommandStartDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_StartDestination{StartDestination: &pb.StartDestinationCommand{Url: cmd.URL}}} +} + +func buildStopDestinationCommand(cmd event.CommandStopDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_StopDestination{StopDestination: &pb.StopDestinationCommand{Url: cmd.URL}}} +} + +func buildCloseOtherInstanceCommand(event.CommandCloseOtherInstance) *pb.Command { + return &pb.Command{CommandType: &pb.Command_CloseOtherInstances{CloseOtherInstances: &pb.CloseOtherInstancesCommand{}}} +} + +func buildQuitCommand(event.CommandQuit) *pb.Command { + return &pb.Command{CommandType: &pb.Command_Quit{Quit: &pb.QuitCommand{}}} +} diff --git a/internal/event/events.go b/internal/event/events.go index b9d15a7..a43bae9 100644 --- a/internal/event/events.go +++ b/internal/event/events.go @@ -42,6 +42,7 @@ func (e DestinationAddedEvent) name() Name { // AddDestinationFailedEvent is emitted when a destination fails to be added. type AddDestinationFailedEvent struct { + URL string Err error } @@ -60,7 +61,10 @@ func (e DestinationStreamExitedEvent) name() Name { } // StartDestinationFailedEvent is emitted when a destination fails to start. -type StartDestinationFailedEvent struct{} +type StartDestinationFailedEvent struct { + URL string + Message string +} func (e StartDestinationFailedEvent) name() Name { return EventNameStartDestinationFailed @@ -79,6 +83,7 @@ func (e DestinationRemovedEvent) name() Name { // RemoveDestinationFailedEvent is emitted when a destination fails to be // removed. type RemoveDestinationFailedEvent struct { + URL string Err error } diff --git a/internal/generated/grpc/api.pb.go b/internal/generated/grpc/api.pb.go new file mode 100644 index 0000000..70fcdcc --- /dev/null +++ b/internal/generated/grpc/api.pb.go @@ -0,0 +1,2351 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v6.30.1 +// source: api.proto + +package grpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Destination_Status int32 + +const ( + Destination_STATUS_OFF_AIR Destination_Status = 0 + Destination_STATUS_STARTING Destination_Status = 1 + Destination_STATUS_LIVE Destination_Status = 2 +) + +// Enum value maps for Destination_Status. +var ( + Destination_Status_name = map[int32]string{ + 0: "STATUS_OFF_AIR", + 1: "STATUS_STARTING", + 2: "STATUS_LIVE", + } + Destination_Status_value = map[string]int32{ + "STATUS_OFF_AIR": 0, + "STATUS_STARTING": 1, + "STATUS_LIVE": 2, + } +) + +func (x Destination_Status) Enum() *Destination_Status { + p := new(Destination_Status) + *p = x + return p +} + +func (x Destination_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Destination_Status) Descriptor() protoreflect.EnumDescriptor { + return file_api_proto_enumTypes[0].Descriptor() +} + +func (Destination_Status) Type() protoreflect.EnumType { + return &file_api_proto_enumTypes[0] +} + +func (x Destination_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Destination_Status.Descriptor instead. +func (Destination_Status) EnumDescriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{11, 0} +} + +type Envelope struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Payload: + // + // *Envelope_Command + // *Envelope_Event + Payload isEnvelope_Payload `protobuf_oneof:"payload"` +} + +func (x *Envelope) Reset() { + *x = Envelope{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Envelope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Envelope) ProtoMessage() {} + +func (x *Envelope) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Envelope.ProtoReflect.Descriptor instead. +func (*Envelope) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{0} +} + +func (m *Envelope) GetPayload() isEnvelope_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (x *Envelope) GetCommand() *Command { + if x, ok := x.GetPayload().(*Envelope_Command); ok { + return x.Command + } + return nil +} + +func (x *Envelope) GetEvent() *Event { + if x, ok := x.GetPayload().(*Envelope_Event); ok { + return x.Event + } + return nil +} + +type isEnvelope_Payload interface { + isEnvelope_Payload() +} + +type Envelope_Command struct { + Command *Command `protobuf:"bytes,1,opt,name=command,proto3,oneof"` +} + +type Envelope_Event struct { + Event *Event `protobuf:"bytes,2,opt,name=event,proto3,oneof"` +} + +func (*Envelope_Command) isEnvelope_Payload() {} + +func (*Envelope_Event) isEnvelope_Payload() {} + +type Command struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to CommandType: + // + // *Command_AddDestination + // *Command_RemoveDestination + // *Command_StartDestination + // *Command_StopDestination + // *Command_CloseOtherInstances + // *Command_Quit + CommandType isCommand_CommandType `protobuf_oneof:"command_type"` +} + +func (x *Command) Reset() { + *x = Command{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Command) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Command) ProtoMessage() {} + +func (x *Command) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Command.ProtoReflect.Descriptor instead. +func (*Command) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{1} +} + +func (m *Command) GetCommandType() isCommand_CommandType { + if m != nil { + return m.CommandType + } + return nil +} + +func (x *Command) GetAddDestination() *AddDestinationCommand { + if x, ok := x.GetCommandType().(*Command_AddDestination); ok { + return x.AddDestination + } + return nil +} + +func (x *Command) GetRemoveDestination() *RemoveDestinationCommand { + if x, ok := x.GetCommandType().(*Command_RemoveDestination); ok { + return x.RemoveDestination + } + return nil +} + +func (x *Command) GetStartDestination() *StartDestinationCommand { + if x, ok := x.GetCommandType().(*Command_StartDestination); ok { + return x.StartDestination + } + return nil +} + +func (x *Command) GetStopDestination() *StopDestinationCommand { + if x, ok := x.GetCommandType().(*Command_StopDestination); ok { + return x.StopDestination + } + return nil +} + +func (x *Command) GetCloseOtherInstances() *CloseOtherInstancesCommand { + if x, ok := x.GetCommandType().(*Command_CloseOtherInstances); ok { + return x.CloseOtherInstances + } + return nil +} + +func (x *Command) GetQuit() *QuitCommand { + if x, ok := x.GetCommandType().(*Command_Quit); ok { + return x.Quit + } + return nil +} + +type isCommand_CommandType interface { + isCommand_CommandType() +} + +type Command_AddDestination struct { + AddDestination *AddDestinationCommand `protobuf:"bytes,1,opt,name=add_destination,json=addDestination,proto3,oneof"` +} + +type Command_RemoveDestination struct { + RemoveDestination *RemoveDestinationCommand `protobuf:"bytes,2,opt,name=remove_destination,json=removeDestination,proto3,oneof"` +} + +type Command_StartDestination struct { + StartDestination *StartDestinationCommand `protobuf:"bytes,3,opt,name=start_destination,json=startDestination,proto3,oneof"` +} + +type Command_StopDestination struct { + StopDestination *StopDestinationCommand `protobuf:"bytes,4,opt,name=stop_destination,json=stopDestination,proto3,oneof"` +} + +type Command_CloseOtherInstances struct { + CloseOtherInstances *CloseOtherInstancesCommand `protobuf:"bytes,5,opt,name=close_other_instances,json=closeOtherInstances,proto3,oneof"` +} + +type Command_Quit struct { + Quit *QuitCommand `protobuf:"bytes,6,opt,name=quit,proto3,oneof"` +} + +func (*Command_AddDestination) isCommand_CommandType() {} + +func (*Command_RemoveDestination) isCommand_CommandType() {} + +func (*Command_StartDestination) isCommand_CommandType() {} + +func (*Command_StopDestination) isCommand_CommandType() {} + +func (*Command_CloseOtherInstances) isCommand_CommandType() {} + +func (*Command_Quit) isCommand_CommandType() {} + +type AddDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *AddDestinationCommand) Reset() { + *x = AddDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddDestinationCommand) ProtoMessage() {} + +func (x *AddDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddDestinationCommand.ProtoReflect.Descriptor instead. +func (*AddDestinationCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +func (x *AddDestinationCommand) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *AddDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type RemoveDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *RemoveDestinationCommand) Reset() { + *x = RemoveDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveDestinationCommand) ProtoMessage() {} + +func (x *RemoveDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveDestinationCommand.ProtoReflect.Descriptor instead. +func (*RemoveDestinationCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *RemoveDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type StartDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *StartDestinationCommand) Reset() { + *x = StartDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartDestinationCommand) ProtoMessage() {} + +func (x *StartDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartDestinationCommand.ProtoReflect.Descriptor instead. +func (*StartDestinationCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{4} +} + +func (x *StartDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type StopDestinationCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *StopDestinationCommand) Reset() { + *x = StopDestinationCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StopDestinationCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StopDestinationCommand) ProtoMessage() {} + +func (x *StopDestinationCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StopDestinationCommand.ProtoReflect.Descriptor instead. +func (*StopDestinationCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{5} +} + +func (x *StopDestinationCommand) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type CloseOtherInstancesCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CloseOtherInstancesCommand) Reset() { + *x = CloseOtherInstancesCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseOtherInstancesCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseOtherInstancesCommand) ProtoMessage() {} + +func (x *CloseOtherInstancesCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseOtherInstancesCommand.ProtoReflect.Descriptor instead. +func (*CloseOtherInstancesCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{6} +} + +type QuitCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *QuitCommand) Reset() { + *x = QuitCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QuitCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QuitCommand) ProtoMessage() {} + +func (x *QuitCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QuitCommand.ProtoReflect.Descriptor instead. +func (*QuitCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{7} +} + +type Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to EventType: + // + // *Event_AppStateChanged + // *Event_DestinationStreamExited + // *Event_DestinationAdded + // *Event_AddDestinationFailed + // *Event_DestinationRemoved + // *Event_RemoveDestinationFailed + // *Event_StartDestinationFailed + // *Event_MediaServerStarted + // *Event_OtherInstanceDetected + // *Event_FatalError + EventType isEvent_EventType `protobuf_oneof:"event_type"` +} + +func (x *Event) Reset() { + *x = Event{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{8} +} + +func (m *Event) GetEventType() isEvent_EventType { + if m != nil { + return m.EventType + } + return nil +} + +func (x *Event) GetAppStateChanged() *AppStateChangedEvent { + if x, ok := x.GetEventType().(*Event_AppStateChanged); ok { + return x.AppStateChanged + } + return nil +} + +func (x *Event) GetDestinationStreamExited() *DestinationStreamExitedEvent { + if x, ok := x.GetEventType().(*Event_DestinationStreamExited); ok { + return x.DestinationStreamExited + } + return nil +} + +func (x *Event) GetDestinationAdded() *DestinationAddedEvent { + if x, ok := x.GetEventType().(*Event_DestinationAdded); ok { + return x.DestinationAdded + } + return nil +} + +func (x *Event) GetAddDestinationFailed() *AddDestinationFailedEvent { + if x, ok := x.GetEventType().(*Event_AddDestinationFailed); ok { + return x.AddDestinationFailed + } + return nil +} + +func (x *Event) GetDestinationRemoved() *DestinationRemovedEvent { + if x, ok := x.GetEventType().(*Event_DestinationRemoved); ok { + return x.DestinationRemoved + } + return nil +} + +func (x *Event) GetRemoveDestinationFailed() *RemoveDestinationFailedEvent { + if x, ok := x.GetEventType().(*Event_RemoveDestinationFailed); ok { + return x.RemoveDestinationFailed + } + return nil +} + +func (x *Event) GetStartDestinationFailed() *StartDestinationFailedEvent { + if x, ok := x.GetEventType().(*Event_StartDestinationFailed); ok { + return x.StartDestinationFailed + } + return nil +} + +func (x *Event) GetMediaServerStarted() *MediaServerStartedEvent { + if x, ok := x.GetEventType().(*Event_MediaServerStarted); ok { + return x.MediaServerStarted + } + return nil +} + +func (x *Event) GetOtherInstanceDetected() *OtherInstanceDetectedEvent { + if x, ok := x.GetEventType().(*Event_OtherInstanceDetected); ok { + return x.OtherInstanceDetected + } + return nil +} + +func (x *Event) GetFatalError() *FatalErrorEvent { + if x, ok := x.GetEventType().(*Event_FatalError); ok { + return x.FatalError + } + return nil +} + +type isEvent_EventType interface { + isEvent_EventType() +} + +type Event_AppStateChanged struct { + AppStateChanged *AppStateChangedEvent `protobuf:"bytes,1,opt,name=app_state_changed,json=appStateChanged,proto3,oneof"` +} + +type Event_DestinationStreamExited struct { + DestinationStreamExited *DestinationStreamExitedEvent `protobuf:"bytes,2,opt,name=destination_stream_exited,json=destinationStreamExited,proto3,oneof"` +} + +type Event_DestinationAdded struct { + DestinationAdded *DestinationAddedEvent `protobuf:"bytes,3,opt,name=destination_added,json=destinationAdded,proto3,oneof"` +} + +type Event_AddDestinationFailed struct { + AddDestinationFailed *AddDestinationFailedEvent `protobuf:"bytes,4,opt,name=add_destination_failed,json=addDestinationFailed,proto3,oneof"` +} + +type Event_DestinationRemoved struct { + DestinationRemoved *DestinationRemovedEvent `protobuf:"bytes,5,opt,name=destination_removed,json=destinationRemoved,proto3,oneof"` +} + +type Event_RemoveDestinationFailed struct { + RemoveDestinationFailed *RemoveDestinationFailedEvent `protobuf:"bytes,6,opt,name=remove_destination_failed,json=removeDestinationFailed,proto3,oneof"` +} + +type Event_StartDestinationFailed struct { + StartDestinationFailed *StartDestinationFailedEvent `protobuf:"bytes,7,opt,name=start_destination_failed,json=startDestinationFailed,proto3,oneof"` +} + +type Event_MediaServerStarted struct { + MediaServerStarted *MediaServerStartedEvent `protobuf:"bytes,8,opt,name=media_server_started,json=mediaServerStarted,proto3,oneof"` +} + +type Event_OtherInstanceDetected struct { + OtherInstanceDetected *OtherInstanceDetectedEvent `protobuf:"bytes,9,opt,name=other_instance_detected,json=otherInstanceDetected,proto3,oneof"` +} + +type Event_FatalError struct { + FatalError *FatalErrorEvent `protobuf:"bytes,10,opt,name=fatal_error,json=fatalError,proto3,oneof"` +} + +func (*Event_AppStateChanged) isEvent_EventType() {} + +func (*Event_DestinationStreamExited) isEvent_EventType() {} + +func (*Event_DestinationAdded) isEvent_EventType() {} + +func (*Event_AddDestinationFailed) isEvent_EventType() {} + +func (*Event_DestinationRemoved) isEvent_EventType() {} + +func (*Event_RemoveDestinationFailed) isEvent_EventType() {} + +func (*Event_StartDestinationFailed) isEvent_EventType() {} + +func (*Event_MediaServerStarted) isEvent_EventType() {} + +func (*Event_OtherInstanceDetected) isEvent_EventType() {} + +func (*Event_FatalError) isEvent_EventType() {} + +type Container struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + HealthState string `protobuf:"bytes,3,opt,name=health_state,json=healthState,proto3" json:"health_state,omitempty"` + CpuPercent float64 `protobuf:"fixed64,4,opt,name=cpu_percent,json=cpuPercent,proto3" json:"cpu_percent,omitempty"` + MemoryUsageBytes uint64 `protobuf:"varint,5,opt,name=memory_usage_bytes,json=memoryUsageBytes,proto3" json:"memory_usage_bytes,omitempty"` + RxRate int32 `protobuf:"varint,6,opt,name=rx_rate,json=rxRate,proto3" json:"rx_rate,omitempty"` + TxRate int32 `protobuf:"varint,7,opt,name=tx_rate,json=txRate,proto3" json:"tx_rate,omitempty"` + RxSince *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=rx_since,json=rxSince,proto3" json:"rx_since,omitempty"` + ImageName string `protobuf:"bytes,9,opt,name=image_name,json=imageName,proto3" json:"image_name,omitempty"` + PullStatus string `protobuf:"bytes,10,opt,name=pull_status,json=pullStatus,proto3" json:"pull_status,omitempty"` + PullProgress string `protobuf:"bytes,11,opt,name=pull_progress,json=pullProgress,proto3" json:"pull_progress,omitempty"` + PullPercent int32 `protobuf:"varint,12,opt,name=pull_percent,json=pullPercent,proto3" json:"pull_percent,omitempty"` + RestartCount int32 `protobuf:"varint,13,opt,name=restart_count,json=restartCount,proto3" json:"restart_count,omitempty"` + ExitCode *int32 `protobuf:"varint,14,opt,name=exit_code,json=exitCode,proto3,oneof" json:"exit_code,omitempty"` + Err string `protobuf:"bytes,15,opt,name=err,proto3" json:"err,omitempty"` +} + +func (x *Container) Reset() { + *x = Container{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Container) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Container) ProtoMessage() {} + +func (x *Container) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Container.ProtoReflect.Descriptor instead. +func (*Container) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{9} +} + +func (x *Container) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Container) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *Container) GetHealthState() string { + if x != nil { + return x.HealthState + } + return "" +} + +func (x *Container) GetCpuPercent() float64 { + if x != nil { + return x.CpuPercent + } + return 0 +} + +func (x *Container) GetMemoryUsageBytes() uint64 { + if x != nil { + return x.MemoryUsageBytes + } + return 0 +} + +func (x *Container) GetRxRate() int32 { + if x != nil { + return x.RxRate + } + return 0 +} + +func (x *Container) GetTxRate() int32 { + if x != nil { + return x.TxRate + } + return 0 +} + +func (x *Container) GetRxSince() *timestamppb.Timestamp { + if x != nil { + return x.RxSince + } + return nil +} + +func (x *Container) GetImageName() string { + if x != nil { + return x.ImageName + } + return "" +} + +func (x *Container) GetPullStatus() string { + if x != nil { + return x.PullStatus + } + return "" +} + +func (x *Container) GetPullProgress() string { + if x != nil { + return x.PullProgress + } + return "" +} + +func (x *Container) GetPullPercent() int32 { + if x != nil { + return x.PullPercent + } + return 0 +} + +func (x *Container) GetRestartCount() int32 { + if x != nil { + return x.RestartCount + } + return 0 +} + +func (x *Container) GetExitCode() int32 { + if x != nil && x.ExitCode != nil { + return *x.ExitCode + } + return 0 +} + +func (x *Container) GetErr() string { + if x != nil { + return x.Err + } + return "" +} + +type Source struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"` + Live bool `protobuf:"varint,2,opt,name=live,proto3" json:"live,omitempty"` + LiveChangedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=live_changed_at,json=liveChangedAt,proto3" json:"live_changed_at,omitempty"` + Tracks []string `protobuf:"bytes,4,rep,name=tracks,proto3" json:"tracks,omitempty"` + ExitReason string `protobuf:"bytes,5,opt,name=exit_reason,json=exitReason,proto3" json:"exit_reason,omitempty"` +} + +func (x *Source) Reset() { + *x = Source{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Source) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Source) ProtoMessage() {} + +func (x *Source) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Source.ProtoReflect.Descriptor instead. +func (*Source) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{10} +} + +func (x *Source) GetContainer() *Container { + if x != nil { + return x.Container + } + return nil +} + +func (x *Source) GetLive() bool { + if x != nil { + return x.Live + } + return false +} + +func (x *Source) GetLiveChangedAt() *timestamppb.Timestamp { + if x != nil { + return x.LiveChangedAt + } + return nil +} + +func (x *Source) GetTracks() []string { + if x != nil { + return x.Tracks + } + return nil +} + +func (x *Source) GetExitReason() string { + if x != nil { + return x.ExitReason + } + return "" +} + +type Destination struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Container *Container `protobuf:"bytes,1,opt,name=container,proto3" json:"container,omitempty"` + Status Destination_Status `protobuf:"varint,2,opt,name=status,proto3,enum=api.Destination_Status" json:"status,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *Destination) Reset() { + *x = Destination{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Destination) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Destination) ProtoMessage() {} + +func (x *Destination) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Destination.ProtoReflect.Descriptor instead. +func (*Destination) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{11} +} + +func (x *Destination) GetContainer() *Container { + if x != nil { + return x.Container + } + return nil +} + +func (x *Destination) GetStatus() Destination_Status { + if x != nil { + return x.Status + } + return Destination_STATUS_OFF_AIR +} + +func (x *Destination) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Destination) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type BuildInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GoVersion string `protobuf:"bytes,1,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + Commit string `protobuf:"bytes,3,opt,name=commit,proto3" json:"commit,omitempty"` + Date string `protobuf:"bytes,4,opt,name=date,proto3" json:"date,omitempty"` +} + +func (x *BuildInfo) Reset() { + *x = BuildInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BuildInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BuildInfo) ProtoMessage() {} + +func (x *BuildInfo) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BuildInfo.ProtoReflect.Descriptor instead. +func (*BuildInfo) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{12} +} + +func (x *BuildInfo) GetGoVersion() string { + if x != nil { + return x.GoVersion + } + return "" +} + +func (x *BuildInfo) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *BuildInfo) GetCommit() string { + if x != nil { + return x.Commit + } + return "" +} + +func (x *BuildInfo) GetDate() string { + if x != nil { + return x.Date + } + return "" +} + +type AppState struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Source *Source `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + Destinations []*Destination `protobuf:"bytes,2,rep,name=destinations,proto3" json:"destinations,omitempty"` + BuildInfo *BuildInfo `protobuf:"bytes,3,opt,name=build_info,json=buildInfo,proto3" json:"build_info,omitempty"` +} + +func (x *AppState) Reset() { + *x = AppState{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppState) ProtoMessage() {} + +func (x *AppState) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppState.ProtoReflect.Descriptor instead. +func (*AppState) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{13} +} + +func (x *AppState) GetSource() *Source { + if x != nil { + return x.Source + } + return nil +} + +func (x *AppState) GetDestinations() []*Destination { + if x != nil { + return x.Destinations + } + return nil +} + +func (x *AppState) GetBuildInfo() *BuildInfo { + if x != nil { + return x.BuildInfo + } + return nil +} + +type AppStateChangedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AppState *AppState `protobuf:"bytes,1,opt,name=app_state,json=appState,proto3" json:"app_state,omitempty"` +} + +func (x *AppStateChangedEvent) Reset() { + *x = AppStateChangedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AppStateChangedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AppStateChangedEvent) ProtoMessage() {} + +func (x *AppStateChangedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AppStateChangedEvent.ProtoReflect.Descriptor instead. +func (*AppStateChangedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{14} +} + +func (x *AppStateChangedEvent) GetAppState() *AppState { + if x != nil { + return x.AppState + } + return nil +} + +type DestinationStreamExitedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *DestinationStreamExitedEvent) Reset() { + *x = DestinationStreamExitedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DestinationStreamExitedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DestinationStreamExitedEvent) ProtoMessage() {} + +func (x *DestinationStreamExitedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DestinationStreamExitedEvent.ProtoReflect.Descriptor instead. +func (*DestinationStreamExitedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{15} +} + +func (x *DestinationStreamExitedEvent) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DestinationStreamExitedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type DestinationAddedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *DestinationAddedEvent) Reset() { + *x = DestinationAddedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DestinationAddedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DestinationAddedEvent) ProtoMessage() {} + +func (x *DestinationAddedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DestinationAddedEvent.ProtoReflect.Descriptor instead. +func (*DestinationAddedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{16} +} + +func (x *DestinationAddedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type AddDestinationFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *AddDestinationFailedEvent) Reset() { + *x = AddDestinationFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddDestinationFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddDestinationFailedEvent) ProtoMessage() {} + +func (x *AddDestinationFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddDestinationFailedEvent.ProtoReflect.Descriptor instead. +func (*AddDestinationFailedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{17} +} + +func (x *AddDestinationFailedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *AddDestinationFailedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type DestinationRemovedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` +} + +func (x *DestinationRemovedEvent) Reset() { + *x = DestinationRemovedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DestinationRemovedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DestinationRemovedEvent) ProtoMessage() {} + +func (x *DestinationRemovedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DestinationRemovedEvent.ProtoReflect.Descriptor instead. +func (*DestinationRemovedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{18} +} + +func (x *DestinationRemovedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type RemoveDestinationFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *RemoveDestinationFailedEvent) Reset() { + *x = RemoveDestinationFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveDestinationFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveDestinationFailedEvent) ProtoMessage() {} + +func (x *RemoveDestinationFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveDestinationFailedEvent.ProtoReflect.Descriptor instead. +func (*RemoveDestinationFailedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{19} +} + +func (x *RemoveDestinationFailedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *RemoveDestinationFailedEvent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type StartDestinationFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *StartDestinationFailedEvent) Reset() { + *x = StartDestinationFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartDestinationFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartDestinationFailedEvent) ProtoMessage() {} + +func (x *StartDestinationFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartDestinationFailedEvent.ProtoReflect.Descriptor instead. +func (*StartDestinationFailedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{20} +} + +func (x *StartDestinationFailedEvent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *StartDestinationFailedEvent) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type MediaServerStartedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RtmpUrl string `protobuf:"bytes,1,opt,name=rtmp_url,json=rtmpUrl,proto3" json:"rtmp_url,omitempty"` + RtmpsUrl string `protobuf:"bytes,2,opt,name=rtmps_url,json=rtmpsUrl,proto3" json:"rtmps_url,omitempty"` +} + +func (x *MediaServerStartedEvent) Reset() { + *x = MediaServerStartedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *MediaServerStartedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MediaServerStartedEvent) ProtoMessage() {} + +func (x *MediaServerStartedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MediaServerStartedEvent.ProtoReflect.Descriptor instead. +func (*MediaServerStartedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{21} +} + +func (x *MediaServerStartedEvent) GetRtmpUrl() string { + if x != nil { + return x.RtmpUrl + } + return "" +} + +func (x *MediaServerStartedEvent) GetRtmpsUrl() string { + if x != nil { + return x.RtmpsUrl + } + return "" +} + +type OtherInstanceDetectedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *OtherInstanceDetectedEvent) Reset() { + *x = OtherInstanceDetectedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OtherInstanceDetectedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OtherInstanceDetectedEvent) ProtoMessage() {} + +func (x *OtherInstanceDetectedEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OtherInstanceDetectedEvent.ProtoReflect.Descriptor instead. +func (*OtherInstanceDetectedEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{22} +} + +type FatalErrorEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *FatalErrorEvent) Reset() { + *x = FatalErrorEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FatalErrorEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FatalErrorEvent) ProtoMessage() {} + +func (x *FatalErrorEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FatalErrorEvent.ProtoReflect.Descriptor instead. +func (*FatalErrorEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{23} +} + +func (x *FatalErrorEvent) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_api_proto protoreflect.FileDescriptor + +var file_api_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, + 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x22, 0x63, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x12, 0x28, 0x0a, + 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x07, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc6, 0x03, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x12, 0x72, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x11, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x48, 0x00, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x10, 0x73, 0x74, 0x6f, 0x70, 0x5f, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, + 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x55, 0x0a, 0x15, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x48, 0x00, 0x52, 0x13, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x71, 0x75, 0x69, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x69, 0x74, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x04, 0x71, 0x75, 0x69, 0x74, 0x42, + 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2c, + 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2b, 0x0a, 0x17, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2a, 0x0a, 0x16, 0x53, 0x74, 0x6f, + 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, + 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x51, 0x75, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x22, 0xd8, 0x06, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, + 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x69, 0x74, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, + 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x13, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x18, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x14, 0x6d, 0x65, 0x64, + 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, + 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, 0x0a, 0x17, 0x6f, + 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, + 0x0c, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, 0x03, + 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x70, 0x75, 0x5f, 0x70, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x63, 0x70, 0x75, + 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, 0x72, + 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, + 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x17, + 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x74, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x73, 0x69, + 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x72, 0x78, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, + 0x0a, 0x0d, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x50, + 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x65, + 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, + 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, + 0x03, 0x65, 0x72, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x42, + 0x0c, 0x0a, 0x0a, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xc7, 0x01, + 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, + 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x0d, 0x6c, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, + 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x69, + 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x42, 0x0a, 0x06, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, + 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4c, 0x49, 0x56, 0x45, 0x10, 0x02, + 0x22, 0x70, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, + 0x0a, 0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x67, 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x0a, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x42, 0x0a, 0x14, 0x41, 0x70, 0x70, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x2a, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x52, 0x08, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x48, 0x0a, + 0x1c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, + 0x72, 0x6c, 0x22, 0x43, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2b, 0x0a, 0x17, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x75, 0x72, 0x6c, 0x22, 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x49, 0x0a, 0x1b, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x17, 0x4d, 0x65, 0x64, 0x69, 0x61, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x74, 0x6d, 0x70, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, + 0x09, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x55, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, 0x74, + 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x46, 0x61, 0x74, 0x61, + 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x41, 0x50, 0x49, 0x12, 0x2f, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, + 0x70, 0x65, 0x1a, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, + 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, + 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_api_proto_rawDescOnce sync.Once + file_api_proto_rawDescData = file_api_proto_rawDesc +) + +func file_api_proto_rawDescGZIP() []byte { + file_api_proto_rawDescOnce.Do(func() { + file_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_rawDescData) + }) + return file_api_proto_rawDescData +} + +var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_api_proto_goTypes = []interface{}{ + (Destination_Status)(0), // 0: api.Destination.Status + (*Envelope)(nil), // 1: api.Envelope + (*Command)(nil), // 2: api.Command + (*AddDestinationCommand)(nil), // 3: api.AddDestinationCommand + (*RemoveDestinationCommand)(nil), // 4: api.RemoveDestinationCommand + (*StartDestinationCommand)(nil), // 5: api.StartDestinationCommand + (*StopDestinationCommand)(nil), // 6: api.StopDestinationCommand + (*CloseOtherInstancesCommand)(nil), // 7: api.CloseOtherInstancesCommand + (*QuitCommand)(nil), // 8: api.QuitCommand + (*Event)(nil), // 9: api.Event + (*Container)(nil), // 10: api.Container + (*Source)(nil), // 11: api.Source + (*Destination)(nil), // 12: api.Destination + (*BuildInfo)(nil), // 13: api.BuildInfo + (*AppState)(nil), // 14: api.AppState + (*AppStateChangedEvent)(nil), // 15: api.AppStateChangedEvent + (*DestinationStreamExitedEvent)(nil), // 16: api.DestinationStreamExitedEvent + (*DestinationAddedEvent)(nil), // 17: api.DestinationAddedEvent + (*AddDestinationFailedEvent)(nil), // 18: api.AddDestinationFailedEvent + (*DestinationRemovedEvent)(nil), // 19: api.DestinationRemovedEvent + (*RemoveDestinationFailedEvent)(nil), // 20: api.RemoveDestinationFailedEvent + (*StartDestinationFailedEvent)(nil), // 21: api.StartDestinationFailedEvent + (*MediaServerStartedEvent)(nil), // 22: api.MediaServerStartedEvent + (*OtherInstanceDetectedEvent)(nil), // 23: api.OtherInstanceDetectedEvent + (*FatalErrorEvent)(nil), // 24: api.FatalErrorEvent + (*timestamppb.Timestamp)(nil), // 25: google.protobuf.Timestamp +} +var file_api_proto_depIdxs = []int32{ + 2, // 0: api.Envelope.command:type_name -> api.Command + 9, // 1: api.Envelope.event:type_name -> api.Event + 3, // 2: api.Command.add_destination:type_name -> api.AddDestinationCommand + 4, // 3: api.Command.remove_destination:type_name -> api.RemoveDestinationCommand + 5, // 4: api.Command.start_destination:type_name -> api.StartDestinationCommand + 6, // 5: api.Command.stop_destination:type_name -> api.StopDestinationCommand + 7, // 6: api.Command.close_other_instances:type_name -> api.CloseOtherInstancesCommand + 8, // 7: api.Command.quit:type_name -> api.QuitCommand + 15, // 8: api.Event.app_state_changed:type_name -> api.AppStateChangedEvent + 16, // 9: api.Event.destination_stream_exited:type_name -> api.DestinationStreamExitedEvent + 17, // 10: api.Event.destination_added:type_name -> api.DestinationAddedEvent + 18, // 11: api.Event.add_destination_failed:type_name -> api.AddDestinationFailedEvent + 19, // 12: api.Event.destination_removed:type_name -> api.DestinationRemovedEvent + 20, // 13: api.Event.remove_destination_failed:type_name -> api.RemoveDestinationFailedEvent + 21, // 14: api.Event.start_destination_failed:type_name -> api.StartDestinationFailedEvent + 22, // 15: api.Event.media_server_started:type_name -> api.MediaServerStartedEvent + 23, // 16: api.Event.other_instance_detected:type_name -> api.OtherInstanceDetectedEvent + 24, // 17: api.Event.fatal_error:type_name -> api.FatalErrorEvent + 25, // 18: api.Container.rx_since:type_name -> google.protobuf.Timestamp + 10, // 19: api.Source.container:type_name -> api.Container + 25, // 20: api.Source.live_changed_at:type_name -> google.protobuf.Timestamp + 10, // 21: api.Destination.container:type_name -> api.Container + 0, // 22: api.Destination.status:type_name -> api.Destination.Status + 11, // 23: api.AppState.source:type_name -> api.Source + 12, // 24: api.AppState.destinations:type_name -> api.Destination + 13, // 25: api.AppState.build_info:type_name -> api.BuildInfo + 14, // 26: api.AppStateChangedEvent.app_state:type_name -> api.AppState + 1, // 27: api.InternalAPI.Communicate:input_type -> api.Envelope + 1, // 28: api.InternalAPI.Communicate:output_type -> api.Envelope + 28, // [28:29] is the sub-list for method output_type + 27, // [27:28] is the sub-list for method input_type + 27, // [27:27] is the sub-list for extension type_name + 27, // [27:27] is the sub-list for extension extendee + 0, // [0:27] is the sub-list for field type_name +} + +func init() { file_api_proto_init() } +func file_api_proto_init() { + if File_api_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Envelope); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Command); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StopDestinationCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseOtherInstancesCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QuitCommand); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Event); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Container); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Source); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Destination); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BuildInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppState); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AppStateChangedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DestinationStreamExitedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DestinationAddedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddDestinationFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DestinationRemovedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveDestinationFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StartDestinationFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*MediaServerStartedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OtherInstanceDetectedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FatalErrorEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_api_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Envelope_Command)(nil), + (*Envelope_Event)(nil), + } + file_api_proto_msgTypes[1].OneofWrappers = []interface{}{ + (*Command_AddDestination)(nil), + (*Command_RemoveDestination)(nil), + (*Command_StartDestination)(nil), + (*Command_StopDestination)(nil), + (*Command_CloseOtherInstances)(nil), + (*Command_Quit)(nil), + } + file_api_proto_msgTypes[8].OneofWrappers = []interface{}{ + (*Event_AppStateChanged)(nil), + (*Event_DestinationStreamExited)(nil), + (*Event_DestinationAdded)(nil), + (*Event_AddDestinationFailed)(nil), + (*Event_DestinationRemoved)(nil), + (*Event_RemoveDestinationFailed)(nil), + (*Event_StartDestinationFailed)(nil), + (*Event_MediaServerStarted)(nil), + (*Event_OtherInstanceDetected)(nil), + (*Event_FatalError)(nil), + } + file_api_proto_msgTypes[9].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_api_proto_rawDesc, + NumEnums: 1, + NumMessages: 24, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_api_proto_goTypes, + DependencyIndexes: file_api_proto_depIdxs, + EnumInfos: file_api_proto_enumTypes, + MessageInfos: file_api_proto_msgTypes, + }.Build() + File_api_proto = out.File + file_api_proto_rawDesc = nil + file_api_proto_goTypes = nil + file_api_proto_depIdxs = nil +} diff --git a/internal/generated/grpc/proto/api_grpc.pb.go b/internal/generated/grpc/api_grpc.pb.go similarity index 91% rename from internal/generated/grpc/proto/api_grpc.pb.go rename to internal/generated/grpc/api_grpc.pb.go index 431ebaf..e7a22c2 100644 --- a/internal/generated/grpc/proto/api_grpc.pb.go +++ b/internal/generated/grpc/api_grpc.pb.go @@ -1,10 +1,10 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.6 -// source: proto/api.proto +// - protoc v6.30.1 +// source: api.proto -package proto +package grpc import ( context "context" @@ -65,19 +65,21 @@ func (x *internalAPICommunicateClient) Recv() (*Envelope, error) { } // InternalAPIServer is the server API for InternalAPI service. -// All implementations should embed UnimplementedInternalAPIServer +// All implementations must embed UnimplementedInternalAPIServer // for forward compatibility type InternalAPIServer interface { Communicate(InternalAPI_CommunicateServer) error + mustEmbedUnimplementedInternalAPIServer() } -// UnimplementedInternalAPIServer should be embedded to have forward compatible implementations. +// UnimplementedInternalAPIServer must be embedded to have forward compatible implementations. type UnimplementedInternalAPIServer struct { } func (UnimplementedInternalAPIServer) Communicate(InternalAPI_CommunicateServer) error { return status.Errorf(codes.Unimplemented, "method Communicate not implemented") } +func (UnimplementedInternalAPIServer) mustEmbedUnimplementedInternalAPIServer() {} // UnsafeInternalAPIServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to InternalAPIServer will @@ -131,5 +133,5 @@ var InternalAPI_ServiceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, - Metadata: "proto/api.proto", + Metadata: "api.proto", } diff --git a/internal/generated/grpc/proto/api.pb.go b/internal/generated/grpc/proto/api.pb.go deleted file mode 100644 index f308548..0000000 --- a/internal/generated/grpc/proto/api.pb.go +++ /dev/null @@ -1,1691 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.6 -// source: proto/api.proto - -package proto - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Envelope struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to Payload: - // - // *Envelope_Command - // *Envelope_Event - Payload isEnvelope_Payload `protobuf_oneof:"payload"` -} - -func (x *Envelope) Reset() { - *x = Envelope{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Envelope) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Envelope) ProtoMessage() {} - -func (x *Envelope) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Envelope.ProtoReflect.Descriptor instead. -func (*Envelope) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{0} -} - -func (m *Envelope) GetPayload() isEnvelope_Payload { - if m != nil { - return m.Payload - } - return nil -} - -func (x *Envelope) GetCommand() *Command { - if x, ok := x.GetPayload().(*Envelope_Command); ok { - return x.Command - } - return nil -} - -func (x *Envelope) GetEvent() *Event { - if x, ok := x.GetPayload().(*Envelope_Event); ok { - return x.Event - } - return nil -} - -type isEnvelope_Payload interface { - isEnvelope_Payload() -} - -type Envelope_Command struct { - Command *Command `protobuf:"bytes,1,opt,name=command,proto3,oneof"` -} - -type Envelope_Event struct { - Event *Event `protobuf:"bytes,2,opt,name=event,proto3,oneof"` -} - -func (*Envelope_Command) isEnvelope_Payload() {} - -func (*Envelope_Event) isEnvelope_Payload() {} - -type Command struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to CommandType: - // - // *Command_AddDestinaion - // *Command_RemoveDestination - // *Command_StartDestination - // *Command_StopDestination - // *Command_CloseOtherInstances - // *Command_Quit - CommandType isCommand_CommandType `protobuf_oneof:"command_type"` -} - -func (x *Command) Reset() { - *x = Command{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Command) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Command) ProtoMessage() {} - -func (x *Command) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Command.ProtoReflect.Descriptor instead. -func (*Command) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{1} -} - -func (m *Command) GetCommandType() isCommand_CommandType { - if m != nil { - return m.CommandType - } - return nil -} - -func (x *Command) GetAddDestinaion() *AddDestinationCommand { - if x, ok := x.GetCommandType().(*Command_AddDestinaion); ok { - return x.AddDestinaion - } - return nil -} - -func (x *Command) GetRemoveDestination() *RemoveDestinationCommand { - if x, ok := x.GetCommandType().(*Command_RemoveDestination); ok { - return x.RemoveDestination - } - return nil -} - -func (x *Command) GetStartDestination() *StartDestinationCommand { - if x, ok := x.GetCommandType().(*Command_StartDestination); ok { - return x.StartDestination - } - return nil -} - -func (x *Command) GetStopDestination() *StopDestinationCommand { - if x, ok := x.GetCommandType().(*Command_StopDestination); ok { - return x.StopDestination - } - return nil -} - -func (x *Command) GetCloseOtherInstances() *CloseOtherInstancesCommand { - if x, ok := x.GetCommandType().(*Command_CloseOtherInstances); ok { - return x.CloseOtherInstances - } - return nil -} - -func (x *Command) GetQuit() *QuitCommand { - if x, ok := x.GetCommandType().(*Command_Quit); ok { - return x.Quit - } - return nil -} - -type isCommand_CommandType interface { - isCommand_CommandType() -} - -type Command_AddDestinaion struct { - AddDestinaion *AddDestinationCommand `protobuf:"bytes,1,opt,name=add_destinaion,json=addDestinaion,proto3,oneof"` -} - -type Command_RemoveDestination struct { - RemoveDestination *RemoveDestinationCommand `protobuf:"bytes,2,opt,name=remove_destination,json=removeDestination,proto3,oneof"` -} - -type Command_StartDestination struct { - StartDestination *StartDestinationCommand `protobuf:"bytes,3,opt,name=start_destination,json=startDestination,proto3,oneof"` -} - -type Command_StopDestination struct { - StopDestination *StopDestinationCommand `protobuf:"bytes,4,opt,name=stop_destination,json=stopDestination,proto3,oneof"` -} - -type Command_CloseOtherInstances struct { - CloseOtherInstances *CloseOtherInstancesCommand `protobuf:"bytes,5,opt,name=close_other_instances,json=closeOtherInstances,proto3,oneof"` -} - -type Command_Quit struct { - Quit *QuitCommand `protobuf:"bytes,6,opt,name=quit,proto3,oneof"` -} - -func (*Command_AddDestinaion) isCommand_CommandType() {} - -func (*Command_RemoveDestination) isCommand_CommandType() {} - -func (*Command_StartDestination) isCommand_CommandType() {} - -func (*Command_StopDestination) isCommand_CommandType() {} - -func (*Command_CloseOtherInstances) isCommand_CommandType() {} - -func (*Command_Quit) isCommand_CommandType() {} - -type AddDestinationCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` -} - -func (x *AddDestinationCommand) Reset() { - *x = AddDestinationCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddDestinationCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddDestinationCommand) ProtoMessage() {} - -func (x *AddDestinationCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddDestinationCommand.ProtoReflect.Descriptor instead. -func (*AddDestinationCommand) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{2} -} - -func (x *AddDestinationCommand) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *AddDestinationCommand) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -type RemoveDestinationCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` -} - -func (x *RemoveDestinationCommand) Reset() { - *x = RemoveDestinationCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveDestinationCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveDestinationCommand) ProtoMessage() {} - -func (x *RemoveDestinationCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveDestinationCommand.ProtoReflect.Descriptor instead. -func (*RemoveDestinationCommand) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{3} -} - -func (x *RemoveDestinationCommand) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -type StartDestinationCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` -} - -func (x *StartDestinationCommand) Reset() { - *x = StartDestinationCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StartDestinationCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StartDestinationCommand) ProtoMessage() {} - -func (x *StartDestinationCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StartDestinationCommand.ProtoReflect.Descriptor instead. -func (*StartDestinationCommand) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{4} -} - -func (x *StartDestinationCommand) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -type StopDestinationCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` -} - -func (x *StopDestinationCommand) Reset() { - *x = StopDestinationCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StopDestinationCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StopDestinationCommand) ProtoMessage() {} - -func (x *StopDestinationCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StopDestinationCommand.ProtoReflect.Descriptor instead. -func (*StopDestinationCommand) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{5} -} - -func (x *StopDestinationCommand) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -type CloseOtherInstancesCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *CloseOtherInstancesCommand) Reset() { - *x = CloseOtherInstancesCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *CloseOtherInstancesCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*CloseOtherInstancesCommand) ProtoMessage() {} - -func (x *CloseOtherInstancesCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use CloseOtherInstancesCommand.ProtoReflect.Descriptor instead. -func (*CloseOtherInstancesCommand) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{6} -} - -type QuitCommand struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *QuitCommand) Reset() { - *x = QuitCommand{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *QuitCommand) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*QuitCommand) ProtoMessage() {} - -func (x *QuitCommand) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use QuitCommand.ProtoReflect.Descriptor instead. -func (*QuitCommand) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{7} -} - -type Event struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Types that are assignable to EventType: - // - // *Event_AppStateChanged - // *Event_DestinationStreamExited - // *Event_DestinationAdded - // *Event_AddDestinationFailed - // *Event_DestinationRemoved - // *Event_RemoveDestinationFailed - // *Event_StartDestinationFailed - // *Event_MediaServerStarted - // *Event_OtherInstanceDetected - // *Event_FatalError - EventType isEvent_EventType `protobuf_oneof:"event_type"` -} - -func (x *Event) Reset() { - *x = Event{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Event) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Event) ProtoMessage() {} - -func (x *Event) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Event.ProtoReflect.Descriptor instead. -func (*Event) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{8} -} - -func (m *Event) GetEventType() isEvent_EventType { - if m != nil { - return m.EventType - } - return nil -} - -func (x *Event) GetAppStateChanged() *AppStateChangedEvent { - if x, ok := x.GetEventType().(*Event_AppStateChanged); ok { - return x.AppStateChanged - } - return nil -} - -func (x *Event) GetDestinationStreamExited() *DestinationStreamExitedEvent { - if x, ok := x.GetEventType().(*Event_DestinationStreamExited); ok { - return x.DestinationStreamExited - } - return nil -} - -func (x *Event) GetDestinationAdded() *DestinationAddedEvent { - if x, ok := x.GetEventType().(*Event_DestinationAdded); ok { - return x.DestinationAdded - } - return nil -} - -func (x *Event) GetAddDestinationFailed() *AddDestinationFailedEvent { - if x, ok := x.GetEventType().(*Event_AddDestinationFailed); ok { - return x.AddDestinationFailed - } - return nil -} - -func (x *Event) GetDestinationRemoved() *DestinationRemovedEvent { - if x, ok := x.GetEventType().(*Event_DestinationRemoved); ok { - return x.DestinationRemoved - } - return nil -} - -func (x *Event) GetRemoveDestinationFailed() *RemoveDestinationFailedEvent { - if x, ok := x.GetEventType().(*Event_RemoveDestinationFailed); ok { - return x.RemoveDestinationFailed - } - return nil -} - -func (x *Event) GetStartDestinationFailed() *StartDestinationFailedEvent { - if x, ok := x.GetEventType().(*Event_StartDestinationFailed); ok { - return x.StartDestinationFailed - } - return nil -} - -func (x *Event) GetMediaServerStarted() *MediaServerStartedEvent { - if x, ok := x.GetEventType().(*Event_MediaServerStarted); ok { - return x.MediaServerStarted - } - return nil -} - -func (x *Event) GetOtherInstanceDetected() *OtherInstanceDetectedEvent { - if x, ok := x.GetEventType().(*Event_OtherInstanceDetected); ok { - return x.OtherInstanceDetected - } - return nil -} - -func (x *Event) GetFatalError() *FatalErrorEvent { - if x, ok := x.GetEventType().(*Event_FatalError); ok { - return x.FatalError - } - return nil -} - -type isEvent_EventType interface { - isEvent_EventType() -} - -type Event_AppStateChanged struct { - AppStateChanged *AppStateChangedEvent `protobuf:"bytes,1,opt,name=app_state_changed,json=appStateChanged,proto3,oneof"` -} - -type Event_DestinationStreamExited struct { - DestinationStreamExited *DestinationStreamExitedEvent `protobuf:"bytes,2,opt,name=destination_stream_exited,json=destinationStreamExited,proto3,oneof"` -} - -type Event_DestinationAdded struct { - DestinationAdded *DestinationAddedEvent `protobuf:"bytes,3,opt,name=destination_added,json=destinationAdded,proto3,oneof"` -} - -type Event_AddDestinationFailed struct { - AddDestinationFailed *AddDestinationFailedEvent `protobuf:"bytes,4,opt,name=add_destination_failed,json=addDestinationFailed,proto3,oneof"` -} - -type Event_DestinationRemoved struct { - DestinationRemoved *DestinationRemovedEvent `protobuf:"bytes,5,opt,name=destination_removed,json=destinationRemoved,proto3,oneof"` -} - -type Event_RemoveDestinationFailed struct { - RemoveDestinationFailed *RemoveDestinationFailedEvent `protobuf:"bytes,6,opt,name=remove_destination_failed,json=removeDestinationFailed,proto3,oneof"` -} - -type Event_StartDestinationFailed struct { - StartDestinationFailed *StartDestinationFailedEvent `protobuf:"bytes,7,opt,name=start_destination_failed,json=startDestinationFailed,proto3,oneof"` -} - -type Event_MediaServerStarted struct { - MediaServerStarted *MediaServerStartedEvent `protobuf:"bytes,8,opt,name=media_server_started,json=mediaServerStarted,proto3,oneof"` -} - -type Event_OtherInstanceDetected struct { - OtherInstanceDetected *OtherInstanceDetectedEvent `protobuf:"bytes,9,opt,name=other_instance_detected,json=otherInstanceDetected,proto3,oneof"` -} - -type Event_FatalError struct { - FatalError *FatalErrorEvent `protobuf:"bytes,10,opt,name=fatal_error,json=fatalError,proto3,oneof"` -} - -func (*Event_AppStateChanged) isEvent_EventType() {} - -func (*Event_DestinationStreamExited) isEvent_EventType() {} - -func (*Event_DestinationAdded) isEvent_EventType() {} - -func (*Event_AddDestinationFailed) isEvent_EventType() {} - -func (*Event_DestinationRemoved) isEvent_EventType() {} - -func (*Event_RemoveDestinationFailed) isEvent_EventType() {} - -func (*Event_StartDestinationFailed) isEvent_EventType() {} - -func (*Event_MediaServerStarted) isEvent_EventType() {} - -func (*Event_OtherInstanceDetected) isEvent_EventType() {} - -func (*Event_FatalError) isEvent_EventType() {} - -// TODO: complete -type AppStateChangedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *AppStateChangedEvent) Reset() { - *x = AppStateChangedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AppStateChangedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AppStateChangedEvent) ProtoMessage() {} - -func (x *AppStateChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AppStateChangedEvent.ProtoReflect.Descriptor instead. -func (*AppStateChangedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{9} -} - -type DestinationStreamExitedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` -} - -func (x *DestinationStreamExitedEvent) Reset() { - *x = DestinationStreamExitedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DestinationStreamExitedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DestinationStreamExitedEvent) ProtoMessage() {} - -func (x *DestinationStreamExitedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DestinationStreamExitedEvent.ProtoReflect.Descriptor instead. -func (*DestinationStreamExitedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{10} -} - -func (x *DestinationStreamExitedEvent) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *DestinationStreamExitedEvent) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -type DestinationAddedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` -} - -func (x *DestinationAddedEvent) Reset() { - *x = DestinationAddedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DestinationAddedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DestinationAddedEvent) ProtoMessage() {} - -func (x *DestinationAddedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DestinationAddedEvent.ProtoReflect.Descriptor instead. -func (*DestinationAddedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{11} -} - -func (x *DestinationAddedEvent) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -type AddDestinationFailedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` -} - -func (x *AddDestinationFailedEvent) Reset() { - *x = AddDestinationFailedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *AddDestinationFailedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*AddDestinationFailedEvent) ProtoMessage() {} - -func (x *AddDestinationFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use AddDestinationFailedEvent.ProtoReflect.Descriptor instead. -func (*AddDestinationFailedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{12} -} - -func (x *AddDestinationFailedEvent) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -func (x *AddDestinationFailedEvent) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -type DestinationRemovedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` -} - -func (x *DestinationRemovedEvent) Reset() { - *x = DestinationRemovedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *DestinationRemovedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DestinationRemovedEvent) ProtoMessage() {} - -func (x *DestinationRemovedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DestinationRemovedEvent.ProtoReflect.Descriptor instead. -func (*DestinationRemovedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{13} -} - -func (x *DestinationRemovedEvent) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -type RemoveDestinationFailedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` -} - -func (x *RemoveDestinationFailedEvent) Reset() { - *x = RemoveDestinationFailedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *RemoveDestinationFailedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*RemoveDestinationFailedEvent) ProtoMessage() {} - -func (x *RemoveDestinationFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use RemoveDestinationFailedEvent.ProtoReflect.Descriptor instead. -func (*RemoveDestinationFailedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{14} -} - -func (x *RemoveDestinationFailedEvent) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -func (x *RemoveDestinationFailedEvent) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -type StartDestinationFailedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` - Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` -} - -func (x *StartDestinationFailedEvent) Reset() { - *x = StartDestinationFailedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *StartDestinationFailedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*StartDestinationFailedEvent) ProtoMessage() {} - -func (x *StartDestinationFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use StartDestinationFailedEvent.ProtoReflect.Descriptor instead. -func (*StartDestinationFailedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{15} -} - -func (x *StartDestinationFailedEvent) GetUrl() string { - if x != nil { - return x.Url - } - return "" -} - -func (x *StartDestinationFailedEvent) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -type MediaServerStartedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - RtmpUrl string `protobuf:"bytes,1,opt,name=rtmp_url,json=rtmpUrl,proto3" json:"rtmp_url,omitempty"` - RtmpsUrl string `protobuf:"bytes,2,opt,name=rtmps_url,json=rtmpsUrl,proto3" json:"rtmps_url,omitempty"` -} - -func (x *MediaServerStartedEvent) Reset() { - *x = MediaServerStartedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *MediaServerStartedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*MediaServerStartedEvent) ProtoMessage() {} - -func (x *MediaServerStartedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use MediaServerStartedEvent.ProtoReflect.Descriptor instead. -func (*MediaServerStartedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{16} -} - -func (x *MediaServerStartedEvent) GetRtmpUrl() string { - if x != nil { - return x.RtmpUrl - } - return "" -} - -func (x *MediaServerStartedEvent) GetRtmpsUrl() string { - if x != nil { - return x.RtmpsUrl - } - return "" -} - -type OtherInstanceDetectedEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *OtherInstanceDetectedEvent) Reset() { - *x = OtherInstanceDetectedEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OtherInstanceDetectedEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OtherInstanceDetectedEvent) ProtoMessage() {} - -func (x *OtherInstanceDetectedEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use OtherInstanceDetectedEvent.ProtoReflect.Descriptor instead. -func (*OtherInstanceDetectedEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{17} -} - -type FatalErrorEvent struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` -} - -func (x *FatalErrorEvent) Reset() { - *x = FatalErrorEvent{} - if protoimpl.UnsafeEnabled { - mi := &file_proto_api_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *FatalErrorEvent) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*FatalErrorEvent) ProtoMessage() {} - -func (x *FatalErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_proto_api_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use FatalErrorEvent.ProtoReflect.Descriptor instead. -func (*FatalErrorEvent) Descriptor() ([]byte, []int) { - return file_proto_api_proto_rawDescGZIP(), []int{18} -} - -func (x *FatalErrorEvent) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -var File_proto_api_proto protoreflect.FileDescriptor - -var file_proto_api_proto_rawDesc = []byte{ - 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x22, 0x63, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, - 0x70, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x48, 0x00, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc4, 0x03, 0x0a, 0x07, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x43, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x5f, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, - 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x69, 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x12, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, - 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x11, 0x72, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x11, - 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x10, 0x73, 0x74, 0x6f, - 0x70, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x48, 0x00, 0x52, 0x0f, 0x73, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x15, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x6f, 0x74, 0x68, - 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, - 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x13, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, - 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x71, 0x75, - 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x51, - 0x75, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x04, 0x71, 0x75, - 0x69, 0x74, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x74, 0x79, - 0x70, 0x65, 0x22, 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, - 0x6c, 0x22, 0x2c, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, - 0x2b, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2a, 0x0a, 0x16, - 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, - 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x51, 0x75, 0x69, 0x74, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xd8, 0x06, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x47, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, - 0x78, 0x69, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, - 0x52, 0x17, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, - 0x64, 0x64, 0x65, 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x13, - 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, - 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5f, 0x0a, - 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x5c, - 0x0a, 0x18, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x14, - 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, 0x64, 0x69, - 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, - 0x0a, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x61, 0x74, - 0x61, 0x6c, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x42, 0x0c, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x22, 0x16, 0x0a, 0x14, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x48, 0x0a, 0x1c, 0x44, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, - 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, - 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x43, 0x0a, - 0x19, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, - 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x22, 0x2b, 0x0a, 0x17, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, - 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, - 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, - 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x45, 0x0a, 0x1b, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x51, - 0x0a, 0x17, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x74, 0x6d, - 0x70, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x74, 0x6d, - 0x70, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x55, 0x72, - 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, - 0x27, 0x0a, 0x0f, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x50, 0x49, 0x12, 0x2f, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, - 0x6e, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, - 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x1a, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, - 0x6c, 0x6f, 0x70, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x2e, - 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, - 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_proto_api_proto_rawDescOnce sync.Once - file_proto_api_proto_rawDescData = file_proto_api_proto_rawDesc -) - -func file_proto_api_proto_rawDescGZIP() []byte { - file_proto_api_proto_rawDescOnce.Do(func() { - file_proto_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_api_proto_rawDescData) - }) - return file_proto_api_proto_rawDescData -} - -var file_proto_api_proto_msgTypes = make([]protoimpl.MessageInfo, 19) -var file_proto_api_proto_goTypes = []interface{}{ - (*Envelope)(nil), // 0: api.Envelope - (*Command)(nil), // 1: api.Command - (*AddDestinationCommand)(nil), // 2: api.AddDestinationCommand - (*RemoveDestinationCommand)(nil), // 3: api.RemoveDestinationCommand - (*StartDestinationCommand)(nil), // 4: api.StartDestinationCommand - (*StopDestinationCommand)(nil), // 5: api.StopDestinationCommand - (*CloseOtherInstancesCommand)(nil), // 6: api.CloseOtherInstancesCommand - (*QuitCommand)(nil), // 7: api.QuitCommand - (*Event)(nil), // 8: api.Event - (*AppStateChangedEvent)(nil), // 9: api.AppStateChangedEvent - (*DestinationStreamExitedEvent)(nil), // 10: api.DestinationStreamExitedEvent - (*DestinationAddedEvent)(nil), // 11: api.DestinationAddedEvent - (*AddDestinationFailedEvent)(nil), // 12: api.AddDestinationFailedEvent - (*DestinationRemovedEvent)(nil), // 13: api.DestinationRemovedEvent - (*RemoveDestinationFailedEvent)(nil), // 14: api.RemoveDestinationFailedEvent - (*StartDestinationFailedEvent)(nil), // 15: api.StartDestinationFailedEvent - (*MediaServerStartedEvent)(nil), // 16: api.MediaServerStartedEvent - (*OtherInstanceDetectedEvent)(nil), // 17: api.OtherInstanceDetectedEvent - (*FatalErrorEvent)(nil), // 18: api.FatalErrorEvent -} -var file_proto_api_proto_depIdxs = []int32{ - 1, // 0: api.Envelope.command:type_name -> api.Command - 8, // 1: api.Envelope.event:type_name -> api.Event - 2, // 2: api.Command.add_destinaion:type_name -> api.AddDestinationCommand - 3, // 3: api.Command.remove_destination:type_name -> api.RemoveDestinationCommand - 4, // 4: api.Command.start_destination:type_name -> api.StartDestinationCommand - 5, // 5: api.Command.stop_destination:type_name -> api.StopDestinationCommand - 6, // 6: api.Command.close_other_instances:type_name -> api.CloseOtherInstancesCommand - 7, // 7: api.Command.quit:type_name -> api.QuitCommand - 9, // 8: api.Event.app_state_changed:type_name -> api.AppStateChangedEvent - 10, // 9: api.Event.destination_stream_exited:type_name -> api.DestinationStreamExitedEvent - 11, // 10: api.Event.destination_added:type_name -> api.DestinationAddedEvent - 12, // 11: api.Event.add_destination_failed:type_name -> api.AddDestinationFailedEvent - 13, // 12: api.Event.destination_removed:type_name -> api.DestinationRemovedEvent - 14, // 13: api.Event.remove_destination_failed:type_name -> api.RemoveDestinationFailedEvent - 15, // 14: api.Event.start_destination_failed:type_name -> api.StartDestinationFailedEvent - 16, // 15: api.Event.media_server_started:type_name -> api.MediaServerStartedEvent - 17, // 16: api.Event.other_instance_detected:type_name -> api.OtherInstanceDetectedEvent - 18, // 17: api.Event.fatal_error:type_name -> api.FatalErrorEvent - 0, // 18: api.InternalAPI.Communicate:input_type -> api.Envelope - 0, // 19: api.InternalAPI.Communicate:output_type -> api.Envelope - 19, // [19:20] is the sub-list for method output_type - 18, // [18:19] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name -} - -func init() { file_proto_api_proto_init() } -func file_proto_api_proto_init() { - if File_proto_api_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_proto_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Envelope); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Command); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddDestinationCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveDestinationCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartDestinationCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopDestinationCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CloseOtherInstancesCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*QuitCommand); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Event); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AppStateChangedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DestinationStreamExitedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DestinationAddedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddDestinationFailedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DestinationRemovedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveDestinationFailedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartDestinationFailedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MediaServerStartedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OtherInstanceDetectedEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_proto_api_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FatalErrorEvent); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_proto_api_proto_msgTypes[0].OneofWrappers = []interface{}{ - (*Envelope_Command)(nil), - (*Envelope_Event)(nil), - } - file_proto_api_proto_msgTypes[1].OneofWrappers = []interface{}{ - (*Command_AddDestinaion)(nil), - (*Command_RemoveDestination)(nil), - (*Command_StartDestination)(nil), - (*Command_StopDestination)(nil), - (*Command_CloseOtherInstances)(nil), - (*Command_Quit)(nil), - } - file_proto_api_proto_msgTypes[8].OneofWrappers = []interface{}{ - (*Event_AppStateChanged)(nil), - (*Event_DestinationStreamExited)(nil), - (*Event_DestinationAdded)(nil), - (*Event_AddDestinationFailed)(nil), - (*Event_DestinationRemoved)(nil), - (*Event_RemoveDestinationFailed)(nil), - (*Event_StartDestinationFailed)(nil), - (*Event_MediaServerStarted)(nil), - (*Event_OtherInstanceDetected)(nil), - (*Event_FatalError)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_proto_api_proto_rawDesc, - NumEnums: 0, - NumMessages: 19, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_proto_api_proto_goTypes, - DependencyIndexes: file_proto_api_proto_depIdxs, - MessageInfos: file_proto_api_proto_msgTypes, - }.Build() - File_proto_api_proto = out.File - file_proto_api_proto_rawDesc = nil - file_proto_api_proto_goTypes = nil - file_proto_api_proto_depIdxs = nil -} diff --git a/internal/server/protocol.go b/internal/server/protocol.go new file mode 100644 index 0000000..abb4e43 --- /dev/null +++ b/internal/server/protocol.go @@ -0,0 +1 @@ +package server diff --git a/internal/server/server.go b/internal/server/server.go index 7311bd2..239efe4 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,6 +1,6 @@ package server -import pb "git.netflux.io/rob/octoplex/internal/generated/grpc/proto" +import pb "git.netflux.io/rob/octoplex/internal/generated/grpc" type Server struct { pb.UnimplementedInternalAPIServer diff --git a/mise/config.toml b/mise/config.toml index 999624d..778ef6c 100644 --- a/mise/config.toml +++ b/mise/config.toml @@ -44,5 +44,5 @@ alias = "m" [tasks.generate_proto] description = "Generate gRPC files from proto" dir = "{{cwd}}" -run = "protoc --go_out=./generated/grpc --go-grpc_out=./generated/grpc proto/*.proto" +run = "protoc -I proto --go_out=paths=source_relative:internal/generated/grpc --go-grpc_out=paths=source_relative:internal/generated/grpc proto/api.proto" alias = "p" diff --git a/proto/api.proto b/proto/api.proto index 190ea64..c3fba15 100644 --- a/proto/api.proto +++ b/proto/api.proto @@ -2,7 +2,9 @@ syntax = "proto3"; package api; -option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc/proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "git.netflux.io/rob/octoplex/internal/generated/grpc"; service InternalAPI { rpc Communicate(stream Envelope) returns (stream Envelope); @@ -17,7 +19,7 @@ message Envelope { message Command { oneof command_type { - AddDestinationCommand add_destinaion = 1; + AddDestinationCommand add_destination = 1; RemoveDestinationCommand remove_destination = 2; StartDestinationCommand start_destination = 3; StopDestinationCommand stop_destination = 4; @@ -62,8 +64,61 @@ message Event { } } -// TODO: complete -message AppStateChangedEvent {} +message Container { + string id = 1; + string status = 2; + string health_state = 3; + double cpu_percent = 4; + uint64 memory_usage_bytes = 5; + int32 rx_rate = 6; + int32 tx_rate = 7; + google.protobuf.Timestamp rx_since = 8; + string image_name = 9; + string pull_status = 10; + string pull_progress = 11; + int32 pull_percent = 12; + int32 restart_count = 13; + optional int32 exit_code = 14; + string err = 15; +} + +message Source { + Container container = 1; + bool live = 2; + google.protobuf.Timestamp live_changed_at = 3; + repeated string tracks = 4; + string exit_reason = 5; +} + +message Destination { + enum Status { + STATUS_OFF_AIR = 0; + STATUS_STARTING = 1; + STATUS_LIVE = 2; + } + + Container container = 1; + Status status = 2; + string name = 3; + string url = 4; +} + +message BuildInfo { + string go_version = 1; + string version = 2; + string commit = 3; + string date = 4; +} + +message AppState { + Source source = 1; + repeated Destination destinations = 2; + BuildInfo build_info = 3; +} + +message AppStateChangedEvent { + AppState app_state = 1; +} message DestinationStreamExitedEvent { string name = 1; @@ -90,7 +145,7 @@ message RemoveDestinationFailedEvent { message StartDestinationFailedEvent { string url = 1; - string error = 2; + string message = 2; } message MediaServerStartedEvent { @@ -101,5 +156,5 @@ message MediaServerStartedEvent { message OtherInstanceDetectedEvent {} message FatalErrorEvent { - string error = 1; + string message = 1; } -- 2.47.2 From c6f21b194a741064b2dd03948f534dd8acc2b255 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Fri, 9 May 2025 06:47:27 +0200 Subject: [PATCH 05/23] fixup! wip: refactor: API --- cmd/client/main.go | 47 +++-- cmd/server/main.go | 9 +- internal/app/app.go | 12 +- internal/event/bus.go | 15 ++ internal/event/bus_test.go | 15 ++ internal/event/command.go | 2 + internal/generated/grpc/api.pb.go | 5 +- internal/protocol/command.go | 110 ++++++++++ internal/protocol/domain.go | 121 +++++++++++ .../{client/protocol.go => protocol/event.go} | 192 ++++++++++-------- internal/server/server.go | 81 +++++++- internal/terminal/terminal.go | 57 ++---- 12 files changed, 510 insertions(+), 156 deletions(-) create mode 100644 internal/protocol/command.go create mode 100644 internal/protocol/domain.go rename internal/{client/protocol.go => protocol/event.go} (51%) diff --git a/cmd/client/main.go b/cmd/client/main.go index 4afe0cf..d3e08c5 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -2,18 +2,19 @@ package main import ( "context" + "errors" "fmt" "log/slog" "os" "runtime/debug" - "time" - "git.netflux.io/rob/octoplex/internal/client" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" pb "git.netflux.io/rob/octoplex/internal/generated/grpc" + "git.netflux.io/rob/octoplex/internal/protocol" "git.netflux.io/rob/octoplex/internal/terminal" "golang.design/x/clipboard" + "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -30,6 +31,7 @@ var ( func main() { if err := run(); err != nil { os.Stderr.WriteString("Error: " + err.Error() + "\n") + os.Exit(1) } } @@ -37,6 +39,8 @@ func run() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + g, ctx := errgroup.WithContext(ctx) + // TODO: logger from config fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { @@ -68,20 +72,11 @@ func run() error { return fmt.Errorf("create gRPC stream: %w", err) } - go func() { - for evt := range bus.Register() { - if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: client.EventToProto(evt)}}); sendErr != nil { - logger.Error("Error sending event to gRPC API", "err", sendErr) - } - } - }() - - go func() { + g.Go(func() error { for { envelope, recErr := stream.Recv() if recErr != nil { - logger.Error("Error receiving envelope from gRPC API", "err", recErr) - continue + return fmt.Errorf("receive envelope: %w", recErr) } evt := envelope.GetEvent() @@ -90,21 +85,20 @@ func run() error { continue } - logger.Info("Received event from gRPC API", "event", evt) - // TODO: convert to domain event + logger.Debug("Received event", "type", fmt.Sprintf("%T", evt)) + bus.Send(protocol.EventFromProto(evt)) } - }() + }) ui, err := terminal.StartUI(ctx, terminal.StartParams{ EventBus: bus, Dispatcher: func(cmd event.Command) { - logger.Info("Command dispatched", "cmd", cmd) - if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: client.CommandToProto(cmd)}}); sendErr != nil { + logger.Info("Command dispatched", "cmd", cmd.Name()) + if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: protocol.CommandToProto(cmd)}}); sendErr != nil { logger.Error("Error sending command to gRPC API", "err", sendErr) } }, ClipboardAvailable: clipboardAvailable, - ConfigFilePath: "TODO", BuildInfo: domain.BuildInfo{ GoVersion: buildInfo.GoVersion, Version: version, @@ -118,7 +112,18 @@ func run() error { } defer ui.Close() - time.Sleep(10 * time.Minute) // Simulate long-running process + errUIClosed := errors.New("UI closed") + g.Go(func() error { + ui.Wait() + logger.Info("UI closed!") + return errUIClosed + }) - return nil + if err := g.Wait(); err == errUIClosed { + logger.Info("UI closed, exiting") + return nil + } else { + logger.Error("UI closed with error", "err", err) + return fmt.Errorf("errgroup.Wait: %w", err) + } } diff --git a/cmd/server/main.go b/cmd/server/main.go index 15b2ab2..2fdfcb9 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -125,7 +125,14 @@ func run() error { Logger: logger, }) - return app.Run(ctx) + if err := app.Run(ctx); err != nil { + if errors.Is(err, context.Canceled) && context.Cause(ctx) == errShutdown { + return errShutdown + } + return err + } + + return nil } // editConfigFile opens the config file in the user's editor. diff --git a/internal/app/app.go b/internal/app/app.go index 16aecea..49589c3 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -93,7 +93,7 @@ func (a *App) Run(ctx context.Context) error { } grpcServer := grpc.NewServer() grpcDone := make(chan error, 1) - pb.RegisterInternalAPIServer(grpcServer, server.Server{}) + pb.RegisterInternalAPIServer(grpcServer, server.New(a.DispatchAsync, a.eventBus, a.logger)) go func() { a.logger.Info("gRPC server started", "addr", grpcAddr) grpcDone <- grpcServer.Serve(lis) @@ -223,6 +223,11 @@ func (a *App) Dispatch(cmd event.Command) event.Event { return <-ch } +// DispatchAsync dispatches a command to be executed synchronously. +func (a *App) DispatchAsync(cmd event.Command) { + a.dispatchC <- cmd +} + // errExit is an error that indicates the app should exit. var errExit = errors.New("exit") @@ -250,6 +255,10 @@ func (a *App) handleCommand( } }() + if c, ok := cmd.(syncCommand); ok { + cmd = c.Command + } + switch c := cmd.(type) { case event.CommandAddDestination: newCfg := a.cfg @@ -263,6 +272,7 @@ func (a *App) handleCommand( } a.cfg = newCfg a.handleConfigUpdate(state) + a.logger.Info("Destination added", "url", c.URL) a.eventBus.Send(event.DestinationAddedEvent{URL: c.URL}) case event.CommandRemoveDestination: repl.StopDestination(c.URL) // no-op if not live diff --git a/internal/event/bus.go b/internal/event/bus.go index 0498c40..246f4ae 100644 --- a/internal/event/bus.go +++ b/internal/event/bus.go @@ -2,6 +2,7 @@ package event import ( "log/slog" + "slices" "sync" ) @@ -31,6 +32,20 @@ func (b *Bus) Register() <-chan Event { return ch } +func (b *Bus) Deregister(ch <-chan Event) { + b.mu.Lock() + defer b.mu.Unlock() + + b.consumers = slices.DeleteFunc(b.consumers, func(other chan Event) bool { + if ch == other { + close(other) + return true + } + + return false + }) +} + // Send sends an event to all registered consumers. func (b *Bus) Send(evt Event) { // The mutex is needed to ensure the backing array of b.consumers cannot be diff --git a/internal/event/bus_test.go b/internal/event/bus_test.go index cceae37..8c5bc78 100644 --- a/internal/event/bus_test.go +++ b/internal/event/bus_test.go @@ -6,6 +6,7 @@ import ( "git.netflux.io/rob/octoplex/internal/event" "git.netflux.io/rob/octoplex/internal/testhelpers" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBus(t *testing.T) { @@ -25,5 +26,19 @@ func TestBus(t *testing.T) { }() assert.Equal(t, evt, (<-ch1).(event.MediaServerStartedEvent)) + assert.Equal(t, evt, (<-ch1).(event.MediaServerStartedEvent)) + assert.Equal(t, evt, (<-ch2).(event.MediaServerStartedEvent)) + assert.Equal(t, evt, (<-ch2).(event.MediaServerStartedEvent)) + + bus.Deregister(ch1) + + _, ok := <-ch1 + assert.False(t, ok) + + select { + case <-ch2: + require.Fail(t, "ch2 should be blocking") + default: + } } diff --git a/internal/event/command.go b/internal/event/command.go index 22b0090..295a302 100644 --- a/internal/event/command.go +++ b/internal/event/command.go @@ -50,6 +50,8 @@ func (c CommandCloseOtherInstance) Name() string { } // CommandQuit quits the app. +// +// TODO: consider how this should look in a client/server architecture. type CommandQuit struct{} // Name implements the Command interface. diff --git a/internal/generated/grpc/api.pb.go b/internal/generated/grpc/api.pb.go index 70fcdcc..59f1571 100644 --- a/internal/generated/grpc/api.pb.go +++ b/internal/generated/grpc/api.pb.go @@ -7,11 +7,12 @@ package grpc import ( + reflect "reflect" + sync "sync" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" - reflect "reflect" - sync "sync" ) const ( diff --git a/internal/protocol/command.go b/internal/protocol/command.go new file mode 100644 index 0000000..9540e9d --- /dev/null +++ b/internal/protocol/command.go @@ -0,0 +1,110 @@ +package protocol + +import ( + "git.netflux.io/rob/octoplex/internal/event" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" +) + +// CommandToProto converts a command to a protobuf message. +func CommandToProto(command event.Command) *pb.Command { + switch evt := command.(type) { + case event.CommandAddDestination: + return buildAddDestinationCommand(evt) + case event.CommandRemoveDestination: + return buildRemoveDestinationCommand(evt) + case event.CommandStartDestination: + return buildStartDestinationCommand(evt) + case event.CommandStopDestination: + return buildStopDestinationCommand(evt) + case event.CommandCloseOtherInstance: + return buildCloseOtherInstanceCommand(evt) + case event.CommandQuit: + return buildQuitCommand(evt) + default: + panic("unknown command type") + } +} + +func buildAddDestinationCommand(cmd event.CommandAddDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_AddDestination{AddDestination: &pb.AddDestinationCommand{Name: cmd.DestinationName, Url: cmd.URL}}} +} + +func buildRemoveDestinationCommand(cmd event.CommandRemoveDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_RemoveDestination{RemoveDestination: &pb.RemoveDestinationCommand{Url: cmd.URL}}} +} + +func buildStartDestinationCommand(cmd event.CommandStartDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_StartDestination{StartDestination: &pb.StartDestinationCommand{Url: cmd.URL}}} +} + +func buildStopDestinationCommand(cmd event.CommandStopDestination) *pb.Command { + return &pb.Command{CommandType: &pb.Command_StopDestination{StopDestination: &pb.StopDestinationCommand{Url: cmd.URL}}} +} + +func buildCloseOtherInstanceCommand(event.CommandCloseOtherInstance) *pb.Command { + return &pb.Command{CommandType: &pb.Command_CloseOtherInstances{CloseOtherInstances: &pb.CloseOtherInstancesCommand{}}} +} + +func buildQuitCommand(event.CommandQuit) *pb.Command { + return &pb.Command{CommandType: &pb.Command_Quit{Quit: &pb.QuitCommand{}}} +} + +// CommandFromProto converts a protobuf message to a command. +func CommandFromProto(pbCmd *pb.Command) event.Command { + if pbCmd == nil || pbCmd.CommandType == nil { + panic("invalid or nil pb.Command") + } + + switch cmd := pbCmd.CommandType.(type) { + case *pb.Command_AddDestination: + return parseAddDestinationCommand(cmd.AddDestination) + case *pb.Command_RemoveDestination: + return parseRemoveDestinationCommand(cmd.RemoveDestination) + case *pb.Command_StartDestination: + return parseStartDestinationCommand(cmd.StartDestination) + case *pb.Command_StopDestination: + return parseStopDestinationCommand(cmd.StopDestination) + case *pb.Command_CloseOtherInstances: + return parseCloseOtherInstanceCommand(cmd.CloseOtherInstances) + case *pb.Command_Quit: + return parseQuitCommand(cmd.Quit) + default: + panic("unknown pb.Command type") + } +} + +func parseAddDestinationCommand(cmd *pb.AddDestinationCommand) event.Command { + if cmd == nil { + panic("nil AddDestinationCommand") + } + return event.CommandAddDestination{DestinationName: cmd.Name, URL: cmd.Url} +} + +func parseRemoveDestinationCommand(cmd *pb.RemoveDestinationCommand) event.Command { + if cmd == nil { + panic("nil RemoveDestinationCommand") + } + return event.CommandRemoveDestination{URL: cmd.Url} +} + +func parseStartDestinationCommand(cmd *pb.StartDestinationCommand) event.Command { + if cmd == nil { + panic("nil StartDestinationCommand") + } + return event.CommandStartDestination{URL: cmd.Url} +} + +func parseStopDestinationCommand(cmd *pb.StopDestinationCommand) event.Command { + if cmd == nil { + panic("nil StopDestinationCommand") + } + return event.CommandStopDestination{URL: cmd.Url} +} + +func parseCloseOtherInstanceCommand(_ *pb.CloseOtherInstancesCommand) event.Command { + return event.CommandCloseOtherInstance{} +} + +func parseQuitCommand(_ *pb.QuitCommand) event.Command { + return event.CommandQuit{} +} diff --git a/internal/protocol/domain.go b/internal/protocol/domain.go new file mode 100644 index 0000000..d906df7 --- /dev/null +++ b/internal/protocol/domain.go @@ -0,0 +1,121 @@ +package protocol + +import ( + "errors" + + "git.netflux.io/rob/octoplex/internal/domain" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func containerToProto(c domain.Container) *pb.Container { + var errString string + if c.Err != nil { + errString = c.Err.Error() + } + + var exitCode *int32 + if c.ExitCode != nil { + code := int32(*c.ExitCode) + exitCode = &code + } + + return &pb.Container{ + Id: c.ID, + Status: c.Status, + HealthState: c.HealthState, + CpuPercent: c.CPUPercent, + MemoryUsageBytes: c.MemoryUsageBytes, + RxRate: int32(c.RxRate), + TxRate: int32(c.TxRate), + RxSince: timestamppb.New(c.RxSince), + ImageName: c.ImageName, + PullStatus: c.PullStatus, + PullProgress: c.PullProgress, + PullPercent: int32(c.PullPercent), + RestartCount: int32(c.RestartCount), + ExitCode: exitCode, + Err: errString, + } +} +func protoToContainer(pbCont *pb.Container) domain.Container { + if pbCont == nil { + return domain.Container{} + } + + var exitCode *int + if pbCont.ExitCode != nil { + val := int(*pbCont.ExitCode) + exitCode = &val + } + + var err error + if pbCont.Err != "" { + err = errors.New(pbCont.Err) + } + + return domain.Container{ + ID: pbCont.Id, + Status: pbCont.Status, + HealthState: pbCont.HealthState, + CPUPercent: pbCont.CpuPercent, + MemoryUsageBytes: pbCont.MemoryUsageBytes, + RxRate: int(pbCont.RxRate), + TxRate: int(pbCont.TxRate), + RxSince: pbCont.RxSince.AsTime(), + ImageName: pbCont.ImageName, + PullStatus: pbCont.PullStatus, + PullProgress: pbCont.PullProgress, + PullPercent: int(pbCont.PullPercent), + RestartCount: int(pbCont.RestartCount), + ExitCode: exitCode, + Err: err, + } +} + +func destinationsToProto(inDests []domain.Destination) []*pb.Destination { + destinations := make([]*pb.Destination, 0, len(inDests)) + for _, d := range inDests { + destinations = append(destinations, destinationToProto(d)) + } + return destinations +} + +func destinationToProto(d domain.Destination) *pb.Destination { + return &pb.Destination{ + Container: containerToProto(d.Container), + Status: destinationStatusToProto(d.Status), + Name: d.Name, + Url: d.URL, + } +} + +func protoToDestinations(pbDests []*pb.Destination) []domain.Destination { + if pbDests == nil { + return nil + } + + dests := make([]domain.Destination, 0, len(pbDests)) + for _, pbDest := range pbDests { + if pbDest == nil { + continue + } + dests = append(dests, domain.Destination{ + Container: protoToContainer(pbDest.Container), + Status: domain.DestinationStatus(pbDest.Status), // direct cast, same underlying int + Name: pbDest.Name, + URL: pbDest.Url, + }) + } + return dests +} +func destinationStatusToProto(s domain.DestinationStatus) pb.Destination_Status { + switch s { + case domain.DestinationStatusStarting: + return pb.Destination_STATUS_STARTING + case domain.DestinationStatusLive: + return pb.Destination_STATUS_LIVE + default: + return pb.Destination_STATUS_OFF_AIR + } +} diff --git a/internal/client/protocol.go b/internal/protocol/event.go similarity index 51% rename from internal/client/protocol.go rename to internal/protocol/event.go index b210b87..8be3ccd 100644 --- a/internal/client/protocol.go +++ b/internal/protocol/event.go @@ -1,12 +1,12 @@ -// TODO: move protocol to a separate package -package client +package protocol import ( + "errors" + "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" - "google.golang.org/protobuf/types/known/timestamppb" - pb "git.netflux.io/rob/octoplex/internal/generated/grpc" + "google.golang.org/protobuf/types/known/timestamppb" ) // EventToProto converts an event to a protobuf message. @@ -37,26 +37,6 @@ func EventToProto(ev event.Event) *pb.Event { } } -// CommandToProto converts a command to a protobuf message. -func CommandToProto(command event.Command) *pb.Command { - switch evt := command.(type) { - case event.CommandAddDestination: - return buildAddDestinationCommand(evt) - case event.CommandRemoveDestination: - return buildRemoveDestinationCommand(evt) - case event.CommandStartDestination: - return buildStartDestinationCommand(evt) - case event.CommandStopDestination: - return buildStopDestinationCommand(evt) - case event.CommandCloseOtherInstance: - return buildCloseOtherInstanceCommand(evt) - case event.CommandQuit: - return buildQuitCommand(evt) - default: - panic("unknown command type") - } -} - func buildAppStateChangeEvent(evt event.AppStateChangedEvent) *pb.Event { return &pb.Event{ EventType: &pb.Event_AppStateChanged{ @@ -154,85 +134,119 @@ func buildMediaServerStartedEvent(evt event.MediaServerStartedEvent) *pb.Event { } } -func containerToProto(c domain.Container) *pb.Container { - var errString string - if c.Err != nil { - errString = c.Err.Error() +// EventFromProto converts a protobuf message to an event. +func EventFromProto(pbEv *pb.Event) event.Event { + if pbEv == nil || pbEv.EventType == nil { + panic("invalid or nil pb.Event") } - var exitCode *int32 - if c.ExitCode != nil { - code := int32(*c.ExitCode) - exitCode = &code - } - - return &pb.Container{ - Id: c.ID, - Status: c.Status, - HealthState: c.HealthState, - CpuPercent: c.CPUPercent, - MemoryUsageBytes: c.MemoryUsageBytes, - RxRate: int32(c.RxRate), - TxRate: int32(c.TxRate), - RxSince: timestamppb.New(c.RxSince), - ImageName: c.ImageName, - PullStatus: c.PullStatus, - PullProgress: c.PullProgress, - PullPercent: int32(c.PullPercent), - RestartCount: int32(c.RestartCount), - ExitCode: exitCode, - Err: errString, - } -} - -func destinationsToProto(inDests []domain.Destination) []*pb.Destination { - destinations := make([]*pb.Destination, 0, len(inDests)) - for _, d := range inDests { - destinations = append(destinations, destinationToProto(d)) - } - return destinations -} - -func destinationToProto(d domain.Destination) *pb.Destination { - return &pb.Destination{ - Container: containerToProto(d.Container), - Status: destinationStatusToProto(d.Status), - Name: d.Name, - Url: d.URL, - } -} - -func destinationStatusToProto(s domain.DestinationStatus) pb.Destination_Status { - switch s { - case domain.DestinationStatusStarting: - return pb.Destination_STATUS_STARTING - case domain.DestinationStatusLive: - return pb.Destination_STATUS_LIVE + switch evt := pbEv.EventType.(type) { + case *pb.Event_AppStateChanged: + return parseAppStateChangedEvent(evt.AppStateChanged) + case *pb.Event_DestinationAdded: + return parseDestinationAddedEvent(evt.DestinationAdded) + case *pb.Event_AddDestinationFailed: + return parseAddDestinationFailedEvent(evt.AddDestinationFailed) + case *pb.Event_DestinationStreamExited: + return parseDestinationStreamExitedEvent(evt.DestinationStreamExited) + case *pb.Event_StartDestinationFailed: + return parseStartDestinationFailedEvent(evt.StartDestinationFailed) + case *pb.Event_DestinationRemoved: + return parseDestinationRemovedEvent(evt.DestinationRemoved) + case *pb.Event_RemoveDestinationFailed: + return parseRemoveDestinationFailedEvent(evt.RemoveDestinationFailed) + case *pb.Event_FatalError: + return parseFatalErrorOccurredEvent(evt.FatalError) + case *pb.Event_OtherInstanceDetected: + return parseOtherInstanceDetectedEvent(evt.OtherInstanceDetected) + case *pb.Event_MediaServerStarted: + return parseMediaServerStartedEvent(evt.MediaServerStarted) default: - return pb.Destination_STATUS_OFF_AIR + panic("unknown pb.Event type") } } -func buildAddDestinationCommand(cmd event.CommandAddDestination) *pb.Command { - return &pb.Command{CommandType: &pb.Command_AddDestination{AddDestination: &pb.AddDestinationCommand{Url: cmd.URL}}} +func parseAppStateChangedEvent(evt *pb.AppStateChangedEvent) event.Event { + if evt == nil || evt.AppState == nil || evt.AppState.Source == nil { + panic("invalid AppStateChangedEvent") + } + + return event.AppStateChangedEvent{ + State: domain.AppState{ + Source: domain.Source{ + Container: protoToContainer(evt.AppState.Source.Container), + Live: evt.AppState.Source.Live, + LiveChangedAt: evt.AppState.Source.LiveChangedAt.AsTime(), + Tracks: evt.AppState.Source.Tracks, + ExitReason: evt.AppState.Source.ExitReason, + }, + Destinations: protoToDestinations(evt.AppState.Destinations), + BuildInfo: domain.BuildInfo{ + GoVersion: evt.AppState.BuildInfo.GoVersion, + Version: evt.AppState.BuildInfo.Version, + Commit: evt.AppState.BuildInfo.Commit, + Date: evt.AppState.BuildInfo.Date, + }, + }, + } } -func buildRemoveDestinationCommand(cmd event.CommandRemoveDestination) *pb.Command { - return &pb.Command{CommandType: &pb.Command_RemoveDestination{RemoveDestination: &pb.RemoveDestinationCommand{Url: cmd.URL}}} +func parseDestinationAddedEvent(evt *pb.DestinationAddedEvent) event.Event { + if evt == nil { + panic("nil DestinationAddedEvent") + } + return event.DestinationAddedEvent{URL: evt.Url} } -func buildStartDestinationCommand(cmd event.CommandStartDestination) *pb.Command { - return &pb.Command{CommandType: &pb.Command_StartDestination{StartDestination: &pb.StartDestinationCommand{Url: cmd.URL}}} +func parseAddDestinationFailedEvent(evt *pb.AddDestinationFailedEvent) event.Event { + if evt == nil { + panic("nil AddDestinationFailedEvent") + } + return event.AddDestinationFailedEvent{URL: evt.Url, Err: errors.New(evt.Error)} } -func buildStopDestinationCommand(cmd event.CommandStopDestination) *pb.Command { - return &pb.Command{CommandType: &pb.Command_StopDestination{StopDestination: &pb.StopDestinationCommand{Url: cmd.URL}}} +func parseDestinationStreamExitedEvent(evt *pb.DestinationStreamExitedEvent) event.Event { + if evt == nil { + panic("nil DestinationStreamExitedEvent") + } + return event.DestinationStreamExitedEvent{Name: evt.Name, Err: errors.New(evt.Error)} } -func buildCloseOtherInstanceCommand(event.CommandCloseOtherInstance) *pb.Command { - return &pb.Command{CommandType: &pb.Command_CloseOtherInstances{CloseOtherInstances: &pb.CloseOtherInstancesCommand{}}} +func parseStartDestinationFailedEvent(evt *pb.StartDestinationFailedEvent) event.Event { + if evt == nil { + panic("nil StartDestinationFailedEvent") + } + return event.StartDestinationFailedEvent{URL: evt.Url, Message: evt.Message} } -func buildQuitCommand(event.CommandQuit) *pb.Command { - return &pb.Command{CommandType: &pb.Command_Quit{Quit: &pb.QuitCommand{}}} +func parseDestinationRemovedEvent(evt *pb.DestinationRemovedEvent) event.Event { + if evt == nil { + panic("nil DestinationRemovedEvent") + } + return event.DestinationRemovedEvent{URL: evt.Url} +} + +func parseRemoveDestinationFailedEvent(evt *pb.RemoveDestinationFailedEvent) event.Event { + if evt == nil { + panic("nil RemoveDestinationFailedEvent") + } + return event.RemoveDestinationFailedEvent{URL: evt.Url, Err: errors.New(evt.Error)} +} + +func parseFatalErrorOccurredEvent(evt *pb.FatalErrorEvent) event.Event { + if evt == nil { + panic("nil FatalErrorEvent") + } + return event.FatalErrorOccurredEvent{Message: evt.Message} +} + +func parseOtherInstanceDetectedEvent(_ *pb.OtherInstanceDetectedEvent) event.Event { + return event.OtherInstanceDetectedEvent{} +} + +func parseMediaServerStartedEvent(evt *pb.MediaServerStartedEvent) event.Event { + if evt == nil { + panic("nil MediaServerStartedEvent") + } + return event.MediaServerStartedEvent{RTMPURL: evt.RtmpUrl, RTMPSURL: evt.RtmpsUrl} } diff --git a/internal/server/server.go b/internal/server/server.go index 239efe4..c07ac63 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -1,7 +1,86 @@ package server -import pb "git.netflux.io/rob/octoplex/internal/generated/grpc" +import ( + "context" + "errors" + "fmt" + "io" + + "git.netflux.io/rob/octoplex/internal/event" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" + "git.netflux.io/rob/octoplex/internal/protocol" + "github.com/sagikazarmark/slog-shim" + "golang.org/x/sync/errgroup" +) type Server struct { pb.UnimplementedInternalAPIServer + + dispatcher func(event.Command) + bus *event.Bus + logger *slog.Logger +} + +func New( + dispatcher func(event.Command), + bus *event.Bus, + logger *slog.Logger, +) *Server { + return &Server{ + dispatcher: dispatcher, + bus: bus, + logger: logger.With("component", "server"), + } +} + +func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error { + g, ctx := errgroup.WithContext(stream.Context()) + + g.Go(func() error { + eventsC := s.bus.Register() + defer s.bus.Deregister(eventsC) + + for { + select { + case evt := <-eventsC: + if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: protocol.EventToProto(evt)}}); err != nil { + return fmt.Errorf("send event: %w", err) + } + case <-ctx.Done(): + return ctx.Err() + } + } + }) + + g.Go(func() error { + for { + in, err := stream.Recv() + if err == io.EOF { + s.logger.Info("Client disconnected") + return err + } + + if err != nil { + return fmt.Errorf("receive message: %w", err) + } + + switch pbCmd := in.Payload.(type) { + case *pb.Envelope_Command: + cmd := protocol.CommandFromProto(pbCmd.Command) + s.logger.Info("Received command", "command", cmd.Name()) + s.dispatcher(cmd) + default: + return fmt.Errorf("expected command but got: %T", pbCmd) + } + } + }) + + if err := g.Wait(); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, context.Canceled) { + s.logger.Error("Error in gRPC stream handler, exiting", "err", err) + return fmt.Errorf("errgroup.Wait: %w", err) + } + + s.logger.Info("Client stream closed") + + return nil } diff --git a/internal/terminal/terminal.go b/internal/terminal/terminal.go index fd7cae9..dc0a289 100644 --- a/internal/terminal/terminal.go +++ b/internal/terminal/terminal.go @@ -44,9 +44,9 @@ type UI struct { eventBus *event.Bus dispatch func(event.Command) clipboardAvailable bool - configFilePath string rtmpURL, rtmpsURL string buildInfo domain.BuildInfo + doneC chan struct{} logger *slog.Logger // tview state @@ -99,7 +99,6 @@ type StartParams struct { Dispatcher func(event.Command) Logger *slog.Logger ClipboardAvailable bool - ConfigFilePath string BuildInfo domain.BuildInfo Screen *Screen // Screen may be nil. } @@ -211,7 +210,7 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) { eventBus: params.EventBus, dispatch: params.Dispatcher, clipboardAvailable: params.ClipboardAvailable, - configFilePath: params.ConfigFilePath, + doneC: make(chan struct{}, 1), buildInfo: params.BuildInfo, logger: params.Logger, app: app, @@ -262,32 +261,27 @@ func (ui *UI) renderAboutView() { ui.aboutView.AddItem(rtmpsURLView, 1, 0, false) } - ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]c[-] Copy config file path"), 1, 0, false) ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false) } func (ui *UI) run(ctx context.Context) { - defer func() { - // Ensure the application is stopped when the UI is closed. - ui.dispatch(event.CommandQuit{}) - }() - eventC := ui.eventBus.Register() + defer ui.eventBus.Deregister(eventC) - uiDone := make(chan struct{}) go func() { - defer func() { - uiDone <- struct{}{} - }() + defer close(ui.doneC) if err := ui.app.Run(); err != nil { - ui.logger.Error("tui application error", "err", err) + ui.logger.Error("Error in UI run loop, exiting", "err", err) } }() for { select { - case evt := <-eventC: + case evt, ok := <-eventC: + if !ok { + return + } ui.app.QueueUpdateDraw(func() { switch evt := evt.(type) { case event.AppStateChangedEvent: @@ -317,7 +311,7 @@ func (ui *UI) run(ctx context.Context) { }) case <-ctx.Done(): return - case <-uiDone: + case <-ui.doneC: return } } @@ -358,8 +352,6 @@ func (ui *UI) inputCaptureHandler(event *tcell.EventKey) *tcell.EventKey { return nil case ' ': ui.toggleDestination() - case 'c', 'C': - ui.copyConfigFilePathToClipboard(ui.clipboardAvailable, ui.configFilePath) case '?': ui.showAbout() case 'k': // tview vim bindings @@ -825,6 +817,11 @@ func (ui *UI) Close() { ui.app.Stop() } +// Wait waits for the terminal user interface to finish. +func (ui *UI) Wait() { + <-ui.doneC +} + func (ui *UI) addDestination() { const ( inputLen = 60 @@ -1006,28 +1003,6 @@ func (ui *UI) copySourceURLToClipboard(url string) { ) } -func (ui *UI) copyConfigFilePathToClipboard(clipboardAvailable bool, configFilePath string) { - var text string - if clipboardAvailable { - if configFilePath != "" { - clipboard.Write(clipboard.FmtText, []byte(configFilePath)) - text = "Configuration file path copied to clipboard:\n\n" + configFilePath - } else { - text = "Configuration file path not set" - } - } else { - text = "Copy to clipboard not available" - } - - ui.showModal( - pageNameModalClipboard, - text, - []string{"Ok"}, - false, - nil, - ) -} - func (ui *UI) confirmQuit() { ui.showModal( pageNameModalQuit, @@ -1036,7 +1011,7 @@ func (ui *UI) confirmQuit() { false, func(buttonIndex int, _ string) { if buttonIndex == 0 { - ui.dispatch(event.CommandQuit{}) + ui.app.Stop() } }, ) -- 2.47.2 From 888ac7d67d58e727922de2845972606264e2ba02 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sat, 10 May 2025 07:36:39 +0200 Subject: [PATCH 06/23] fixup! wip: refactor: API --- cmd/client/main.go | 14 +++--------- internal/terminal/terminal.go | 40 +++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/cmd/client/main.go b/cmd/client/main.go index d3e08c5..87276e0 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -2,7 +2,6 @@ package main import ( "context" - "errors" "fmt" "log/slog" "os" @@ -90,7 +89,7 @@ func run() error { } }) - ui, err := terminal.StartUI(ctx, terminal.StartParams{ + ui, err := terminal.NewUI(ctx, terminal.Params{ EventBus: bus, Dispatcher: func(cmd event.Command) { logger.Info("Command dispatched", "cmd", cmd.Name()) @@ -112,18 +111,11 @@ func run() error { } defer ui.Close() - errUIClosed := errors.New("UI closed") - g.Go(func() error { - ui.Wait() - logger.Info("UI closed!") - return errUIClosed - }) + g.Go(func() error { return ui.Run(ctx) }) - if err := g.Wait(); err == errUIClosed { - logger.Info("UI closed, exiting") + if err := g.Wait(); err == terminal.ErrUserClosed { return nil } else { - logger.Error("UI closed with error", "err", err) return fmt.Errorf("errgroup.Wait: %w", err) } } diff --git a/internal/terminal/terminal.go b/internal/terminal/terminal.go index dc0a289..78f9866 100644 --- a/internal/terminal/terminal.go +++ b/internal/terminal/terminal.go @@ -3,6 +3,7 @@ package terminal import ( "cmp" "context" + "errors" "fmt" "log/slog" "maps" @@ -46,7 +47,7 @@ type UI struct { clipboardAvailable bool rtmpURL, rtmpsURL string buildInfo domain.BuildInfo - doneC chan struct{} + appExitC chan error logger *slog.Logger // tview state @@ -92,9 +93,9 @@ type ScreenCapture struct { Width, Height int } -// StartParams contains the parameters for starting a new terminal user +// Params contains the parameters for starting a new terminal user // interface. -type StartParams struct { +type Params struct { EventBus *event.Bus Dispatcher func(event.Command) Logger *slog.Logger @@ -103,8 +104,9 @@ type StartParams struct { Screen *Screen // Screen may be nil. } -// StartUI starts the terminal user interface. -func StartUI(ctx context.Context, params StartParams) (*UI, error) { +// NewUI creates the user interface. Call [Run] on the *UI instance to block +// until it is completed. +func NewUI(ctx context.Context, params Params) (*UI, error) { app := tview.NewApplication() var screen tcell.Screen @@ -210,7 +212,7 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) { eventBus: params.EventBus, dispatch: params.Dispatcher, clipboardAvailable: params.ClipboardAvailable, - doneC: make(chan struct{}, 1), + appExitC: make(chan error, 1), buildInfo: params.BuildInfo, logger: params.Logger, app: app, @@ -236,8 +238,6 @@ func StartUI(ctx context.Context, params StartParams) (*UI, error) { app.SetInputCapture(ui.inputCaptureHandler) app.SetAfterDrawFunc(ui.afterDrawHandler) - go ui.run(ctx) - return ui, nil } @@ -264,23 +264,28 @@ func (ui *UI) renderAboutView() { ui.aboutView.AddItem(tview.NewTextView().SetDynamicColors(true).SetText("[grey]?[-] About"), 1, 0, false) } -func (ui *UI) run(ctx context.Context) { +var ErrUserClosed = errors.New("user closed UI") + +// Run runs the user interface. It always returns a non-nil error, which will +// be [ErrUserClosed] if the user voluntarily closed the UI. +func (ui *UI) Run(ctx context.Context) error { eventC := ui.eventBus.Register() defer ui.eventBus.Deregister(eventC) go func() { - defer close(ui.doneC) - - if err := ui.app.Run(); err != nil { + err := ui.app.Run() + if err != nil { ui.logger.Error("Error in UI run loop, exiting", "err", err) } + ui.appExitC <- err }() for { select { case evt, ok := <-eventC: if !ok { - return + // should never happen + return errors.New("event channel closed") } ui.app.QueueUpdateDraw(func() { switch evt := evt.(type) { @@ -307,12 +312,11 @@ func (ui *UI) run(ctx context.Context) { default: ui.logger.Warn("unhandled event", "event", evt) } - }) case <-ctx.Done(): - return - case <-ui.doneC: - return + return ctx.Err() + case err := <-ui.appExitC: + return cmp.Or(err, ErrUserClosed) } } } @@ -819,7 +823,7 @@ func (ui *UI) Close() { // Wait waits for the terminal user interface to finish. func (ui *UI) Wait() { - <-ui.doneC + <-ui.appExitC } func (ui *UI) addDestination() { -- 2.47.2 From 7706bb363f745a994727449ab09049a054e8d783 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sat, 10 May 2025 07:46:58 +0200 Subject: [PATCH 07/23] fixup! wip: refactor: API --- cmd/client/main.go | 71 +++--------------- internal/app/integration_helpers_test.go | 6 ++ internal/client/clientapp.go | 94 ++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 60 deletions(-) create mode 100644 internal/client/clientapp.go diff --git a/cmd/client/main.go b/cmd/client/main.go index 87276e0..e89b0ec 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -7,15 +7,10 @@ import ( "os" "runtime/debug" + "git.netflux.io/rob/octoplex/internal/client" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" - pb "git.netflux.io/rob/octoplex/internal/generated/grpc" - "git.netflux.io/rob/octoplex/internal/protocol" - "git.netflux.io/rob/octoplex/internal/terminal" "golang.design/x/clipboard" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) var ( @@ -38,8 +33,6 @@ func run() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - g, ctx := errgroup.WithContext(ctx) - // TODO: logger from config fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { @@ -47,8 +40,6 @@ func run() error { } logger := slog.New(slog.NewTextHandler(fptr, nil)) - bus := event.NewBus(logger) - var clipboardAvailable bool if err = clipboard.Init(); err != nil { logger.Warn("Clipboard not available", "err", err) @@ -61,61 +52,21 @@ func run() error { return fmt.Errorf("read build info: %w", err) } - conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) - if err != nil { - return fmt.Errorf("connect to gRPC server: %w", err) - } - apiClient := pb.NewInternalAPIClient(conn) - stream, err := apiClient.Communicate(ctx) - if err != nil { - return fmt.Errorf("create gRPC stream: %w", err) - } - - g.Go(func() error { - for { - envelope, recErr := stream.Recv() - if recErr != nil { - return fmt.Errorf("receive envelope: %w", recErr) - } - - evt := envelope.GetEvent() - if evt == nil { - logger.Error("Received envelope without event") - continue - } - - logger.Debug("Received event", "type", fmt.Sprintf("%T", evt)) - bus.Send(protocol.EventFromProto(evt)) - } - }) - - ui, err := terminal.NewUI(ctx, terminal.Params{ - EventBus: bus, - Dispatcher: func(cmd event.Command) { - logger.Info("Command dispatched", "cmd", cmd.Name()) - if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: protocol.CommandToProto(cmd)}}); sendErr != nil { - logger.Error("Error sending command to gRPC API", "err", sendErr) - } - }, - ClipboardAvailable: clipboardAvailable, - BuildInfo: domain.BuildInfo{ + app := client.NewApp( + event.NewBus(logger), + clipboardAvailable, + domain.BuildInfo{ GoVersion: buildInfo.GoVersion, Version: version, Commit: commit, Date: date, }, - Logger: logger.With("component", "ui"), - }) - if err != nil { - return fmt.Errorf("start terminal user interface: %w", err) - } - defer ui.Close() + logger, + ) - g.Go(func() error { return ui.Run(ctx) }) - - if err := g.Wait(); err == terminal.ErrUserClosed { - return nil - } else { - return fmt.Errorf("errgroup.Wait: %w", err) + if err := app.Run(ctx); err != nil { + return fmt.Errorf("run app: %w", err) } + + return nil } diff --git a/internal/app/integration_helpers_test.go b/internal/app/integration_helpers_test.go index 48c12a8..53f032f 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/app/integration_helpers_test.go @@ -49,6 +49,12 @@ func buildAppParams( } } +func buildClientServer( + t *testing.T, +) (*client.App, *app.App) { + +} + func setupSimulationScreen(t *testing.T) (tcell.SimulationScreen, chan<- terminal.ScreenCapture, func() []string) { t.Helper() diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go new file mode 100644 index 0000000..4a78174 --- /dev/null +++ b/internal/client/clientapp.go @@ -0,0 +1,94 @@ +package client + +import ( + "context" + "fmt" + + "git.netflux.io/rob/octoplex/internal/domain" + "git.netflux.io/rob/octoplex/internal/event" + pb "git.netflux.io/rob/octoplex/internal/generated/grpc" + "git.netflux.io/rob/octoplex/internal/protocol" + "git.netflux.io/rob/octoplex/internal/terminal" + "github.com/sagikazarmark/slog-shim" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type App struct { + bus *event.Bus + clipboardAvailable bool + buildInfo domain.BuildInfo + logger *slog.Logger +} + +func NewApp( + bus *event.Bus, + clipboardAvailable bool, + buildInfo domain.BuildInfo, + logger *slog.Logger, +) *App { + return &App{ + bus: bus, + clipboardAvailable: clipboardAvailable, + buildInfo: buildInfo, + logger: logger, + } +} + +func (a *App) Run(ctx context.Context) error { + g, ctx := errgroup.WithContext(ctx) + + conn, err := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return fmt.Errorf("connect to gRPC server: %w", err) + } + apiClient := pb.NewInternalAPIClient(conn) + stream, err := apiClient.Communicate(ctx) + if err != nil { + return fmt.Errorf("create gRPC stream: %w", err) + } + + g.Go(func() error { + for { + envelope, recErr := stream.Recv() + if recErr != nil { + return fmt.Errorf("receive envelope: %w", recErr) + } + + evt := envelope.GetEvent() + if evt == nil { + a.logger.Error("Received envelope without event") + continue + } + + a.logger.Debug("Received event", "type", fmt.Sprintf("%T", evt)) + a.bus.Send(protocol.EventFromProto(evt)) + } + }) + + ui, err := terminal.NewUI(ctx, terminal.Params{ + EventBus: a.bus, + Dispatcher: func(cmd event.Command) { + a.logger.Info("Command dispatched", "cmd", cmd.Name()) + if sendErr := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: protocol.CommandToProto(cmd)}}); sendErr != nil { + a.logger.Error("Error sending command to gRPC API", "err", sendErr) + } + }, + ClipboardAvailable: a.clipboardAvailable, + BuildInfo: a.buildInfo, + Logger: a.logger.With("component", "ui"), + }) + if err != nil { + return fmt.Errorf("start terminal user interface: %w", err) + } + defer ui.Close() + + g.Go(func() error { return ui.Run(ctx) }) + + if err := g.Wait(); err == terminal.ErrUserClosed { + return nil + } else { + return fmt.Errorf("errgroup.Wait: %w", err) + } +} -- 2.47.2 From aa6f50715d2c1a9452d336b3aeef90e34c312b66 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sat, 10 May 2025 08:15:23 +0200 Subject: [PATCH 08/23] fixup! wip: refactor: API --- cmd/client/main.go | 3 +- internal/app/app.go | 2 -- internal/app/integration_helpers_test.go | 39 ++++++++++++++++++------ internal/app/integration_test.go | 38 +++++++++-------------- internal/client/clientapp.go | 11 +++++-- 5 files changed, 54 insertions(+), 39 deletions(-) diff --git a/cmd/client/main.go b/cmd/client/main.go index e89b0ec..89b9303 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -9,7 +9,6 @@ import ( "git.netflux.io/rob/octoplex/internal/client" "git.netflux.io/rob/octoplex/internal/domain" - "git.netflux.io/rob/octoplex/internal/event" "golang.design/x/clipboard" ) @@ -53,7 +52,6 @@ func run() error { } app := client.NewApp( - event.NewBus(logger), clipboardAvailable, domain.BuildInfo{ GoVersion: buildInfo.GoVersion, @@ -61,6 +59,7 @@ func run() error { Commit: commit, Date: date, }, + nil, logger, ) diff --git a/internal/app/app.go b/internal/app/app.go index 49589c3..7c0de9c 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -43,7 +43,6 @@ type Params struct { ConfigService *config.Service DockerClient container.DockerClient ChanSize int - Screen *terminal.Screen // Screen may be nil. ClipboardAvailable bool ConfigFilePath string BuildInfo domain.BuildInfo @@ -61,7 +60,6 @@ func New(params Params) *App { eventBus: event.NewBus(params.Logger.With("component", "event_bus")), dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)), dockerClient: params.DockerClient, - screen: params.Screen, clipboardAvailable: params.ClipboardAvailable, configFilePath: params.ConfigFilePath, buildInfo: params.BuildInfo, diff --git a/internal/app/integration_helpers_test.go b/internal/app/integration_helpers_test.go index 53f032f..b24af28 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/app/integration_helpers_test.go @@ -15,6 +15,7 @@ import ( "time" "git.netflux.io/rob/octoplex/internal/app" + "git.netflux.io/rob/octoplex/internal/client" "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/container" "git.netflux.io/rob/octoplex/internal/domain" @@ -35,14 +36,8 @@ func buildAppParams( t.Helper() return app.Params{ - ConfigService: configService, - DockerClient: dockerClient, - Screen: &terminal.Screen{ - Screen: screen, - Width: 180, - Height: 25, - CaptureC: screenCaptureC, - }, + ConfigService: configService, + DockerClient: dockerClient, ClipboardAvailable: false, BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, Logger: logger, @@ -50,9 +45,35 @@ func buildAppParams( } func buildClientServer( - t *testing.T, + configService *config.Service, + dockerClient container.DockerClient, + screen tcell.SimulationScreen, + screenCaptureC chan<- terminal.ScreenCapture, + logger *slog.Logger, ) (*client.App, *app.App) { + buildInfo := domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"} + clientApp := client.NewApp( + false, + buildInfo, + &terminal.Screen{ + Screen: screen, + Width: 160, + Height: 25, + CaptureC: screenCaptureC, + }, + logger, + ) + // TODO: use buildAppParams + srvApp := app.New(app.Params{ + ConfigService: configService, + DockerClient: dockerClient, + ClipboardAvailable: false, + BuildInfo: buildInfo, + Logger: logger, + }) + + return clientApp, srvApp } func setupSimulationScreen(t *testing.T) (tcell.SimulationScreen, chan<- terminal.ScreenCapture, func() []string) { diff --git a/internal/app/integration_test.go b/internal/app/integration_test.go index fc85993..636071e 100644 --- a/internal/app/integration_test.go +++ b/internal/app/integration_test.go @@ -12,6 +12,7 @@ import ( "fmt" "net" "os" + "sync" "testing" "time" @@ -20,7 +21,6 @@ import ( "git.netflux.io/rob/octoplex/internal/container" "git.netflux.io/rob/octoplex/internal/container/mocks" "git.netflux.io/rob/octoplex/internal/domain" - "git.netflux.io/rob/octoplex/internal/terminal" "git.netflux.io/rob/octoplex/internal/testhelpers" "github.com/docker/docker/api/types/network" dockerclient "github.com/docker/docker/client" @@ -126,25 +126,19 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) { Destinations: []config.Destination{{Name: "Local server 1", URL: destURL1}}, }) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + var wg sync.WaitGroup - require.Equal(t, context.Canceled, app.New(app.Params{ - ConfigService: configService, - DockerClient: dockerClient, - Screen: &terminal.Screen{ - Screen: screen, - Width: 160, - Height: 25, - CaptureC: screenCaptureC, - }, - ClipboardAvailable: false, - BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, - Logger: logger, - }).Run(ctx)) + wg.Add(1) + go func() { + defer wg.Done() + assert.NoError(t, client.Run(ctx)) + }() + + wg.Add(1) + go func() { + defer wg.Done() + assert.NoError(t, server.Run(ctx)) }() require.EventuallyWithT( @@ -286,13 +280,9 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) { printScreen(t, getContents, "After stopping the first destination") - // TODO: - // - Source error - // - Additional features (copy URL, etc.) - cancel() - <-done + wg.Wait() } func TestIntegrationCustomHost(t *testing.T) { diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 4a78174..5163cd4 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -15,27 +15,33 @@ import ( "google.golang.org/grpc/credentials/insecure" ) +// App is the client application. type App struct { bus *event.Bus clipboardAvailable bool buildInfo domain.BuildInfo + screen *terminal.Screen logger *slog.Logger } +// NewApp creates a new App instance. +// +// TODO: params func NewApp( - bus *event.Bus, clipboardAvailable bool, buildInfo domain.BuildInfo, + screen *terminal.Screen, logger *slog.Logger, ) *App { return &App{ - bus: bus, + bus: event.NewBus(logger), clipboardAvailable: clipboardAvailable, buildInfo: buildInfo, logger: logger, } } +// Run starts the application, and blocks until it is closed. func (a *App) Run(ctx context.Context) error { g, ctx := errgroup.WithContext(ctx) @@ -77,6 +83,7 @@ func (a *App) Run(ctx context.Context) error { }, ClipboardAvailable: a.clipboardAvailable, BuildInfo: a.buildInfo, + Screen: a.screen, Logger: a.logger.With("component", "ui"), }) if err != nil { -- 2.47.2 From 116623f38613d1fe1584257896c59e008918560e Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sat, 10 May 2025 21:14:40 +0200 Subject: [PATCH 09/23] fixup! wip: refactor: API --- internal/app/app.go | 3 +++ internal/app/integration_test.go | 5 +++-- internal/client/clientapp.go | 1 + internal/server/server.go | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 7c0de9c..91e2b66 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -89,8 +89,11 @@ func (a *App) Run(ctx context.Context) error { if err != nil { log.Fatalf("failed to listen: %v", err) } + defer lis.Close() + grpcServer := grpc.NewServer() grpcDone := make(chan error, 1) + pb.RegisterInternalAPIServer(grpcServer, server.New(a.DispatchAsync, a.eventBus, a.logger)) go func() { a.logger.Info("gRPC server started", "addr", grpcAddr) diff --git a/internal/app/integration_test.go b/internal/app/integration_test.go index 636071e..a4b8f74 100644 --- a/internal/app/integration_test.go +++ b/internal/app/integration_test.go @@ -132,13 +132,14 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) { wg.Add(1) go func() { defer wg.Done() - assert.NoError(t, client.Run(ctx)) + assert.ErrorIs(t, server.Run(ctx), context.Canceled) }() wg.Add(1) go func() { defer wg.Done() - assert.NoError(t, server.Run(ctx)) + // May be a gRPC error, not context.Canceled: + assert.ErrorContains(t, client.Run(ctx), "context canceled") }() require.EventuallyWithT( diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 5163cd4..34162bb 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -37,6 +37,7 @@ func NewApp( bus: event.NewBus(logger), clipboardAvailable: clipboardAvailable, buildInfo: buildInfo, + screen: screen, logger: logger, } } diff --git a/internal/server/server.go b/internal/server/server.go index c07ac63..2ccfb54 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -76,7 +76,7 @@ func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error { }) if err := g.Wait(); err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, context.Canceled) { - s.logger.Error("Error in gRPC stream handler, exiting", "err", err) + s.logger.Error("Client stream closed with error", "err", err) return fmt.Errorf("errgroup.Wait: %w", err) } -- 2.47.2 From 59b0a060bacb278711547aa7cb144344b3e084c9 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sat, 10 May 2025 21:18:42 +0200 Subject: [PATCH 10/23] fixup! wip: refactor: API --- cmd/client/main.go | 12 ++++------ internal/app/integration_helpers_test.go | 11 ++++----- internal/client/clientapp.go | 29 ++++++++++++------------ 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/cmd/client/main.go b/cmd/client/main.go index 89b9303..5abc7e3 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -51,18 +51,16 @@ func run() error { return fmt.Errorf("read build info: %w", err) } - app := client.NewApp( - clipboardAvailable, - domain.BuildInfo{ + app := client.New(client.NewParams{ + ClipboardAvailable: clipboardAvailable, + BuildInfo: domain.BuildInfo{ GoVersion: buildInfo.GoVersion, Version: version, Commit: commit, Date: date, }, - nil, - logger, - ) - + Logger: logger, + }) if err := app.Run(ctx); err != nil { return fmt.Errorf("run app: %w", err) } diff --git a/internal/app/integration_helpers_test.go b/internal/app/integration_helpers_test.go index b24af28..fcad882 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/app/integration_helpers_test.go @@ -52,17 +52,16 @@ func buildClientServer( logger *slog.Logger, ) (*client.App, *app.App) { buildInfo := domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"} - clientApp := client.NewApp( - false, - buildInfo, - &terminal.Screen{ + clientApp := client.New(client.NewParams{ + BuildInfo: buildInfo, + Screen: &terminal.Screen{ Screen: screen, Width: 160, Height: 25, CaptureC: screenCaptureC, }, - logger, - ) + Logger: logger, + }) // TODO: use buildAppParams srvApp := app.New(app.Params{ diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 34162bb..572a5c4 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -24,21 +24,22 @@ type App struct { logger *slog.Logger } -// NewApp creates a new App instance. -// -// TODO: params -func NewApp( - clipboardAvailable bool, - buildInfo domain.BuildInfo, - screen *terminal.Screen, - logger *slog.Logger, -) *App { +// NewParams contains the parameters for the App. +type NewParams struct { + ClipboardAvailable bool + BuildInfo domain.BuildInfo + Screen *terminal.Screen + Logger *slog.Logger +} + +// New creates a new App instance. +func New(params NewParams) *App { return &App{ - bus: event.NewBus(logger), - clipboardAvailable: clipboardAvailable, - buildInfo: buildInfo, - screen: screen, - logger: logger, + bus: event.NewBus(params.Logger), + clipboardAvailable: params.ClipboardAvailable, + buildInfo: params.BuildInfo, + screen: params.Screen, + logger: params.Logger, } } -- 2.47.2 From c706a41acdcd81192cd9cbceb92f2dbfc85064e3 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sat, 10 May 2025 21:47:10 +0200 Subject: [PATCH 11/23] fixup! wip: refactor: API --- internal/app/integration_helpers_test.go | 24 ++++++++++++++ internal/app/integration_test.go | 40 ++++++------------------ 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/internal/app/integration_helpers_test.go b/internal/app/integration_helpers_test.go index fcad882..61c3c96 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/app/integration_helpers_test.go @@ -3,6 +3,7 @@ package app_test import ( + "context" "encoding/json" "fmt" "io" @@ -21,6 +22,7 @@ import ( "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/terminal" "github.com/gdamore/tcell/v2" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" ) @@ -75,6 +77,28 @@ func buildClientServer( return clientApp, srvApp } +func runClientServer( + ctx context.Context, + t *testing.T, + wg *sync.WaitGroup, + clientApp *client.App, + serverApp *app.App, +) { + wg.Add(1) + go func() { + defer wg.Done() + assert.ErrorIs(t, serverApp.Run(ctx), context.Canceled) + }() + + wg.Add(1) + go func() { + defer wg.Done() + // May be a gRPC error, not context.Canceled: + assert.ErrorContains(t, clientApp.Run(ctx), "context canceled") + }() + +} + func setupSimulationScreen(t *testing.T) (tcell.SimulationScreen, chan<- terminal.ScreenCapture, func() []string) { t.Helper() diff --git a/internal/app/integration_test.go b/internal/app/integration_test.go index a4b8f74..bfc0f55 100644 --- a/internal/app/integration_test.go +++ b/internal/app/integration_test.go @@ -128,19 +128,7 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) { client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) var wg sync.WaitGroup - - wg.Add(1) - go func() { - defer wg.Done() - assert.ErrorIs(t, server.Run(ctx), context.Canceled) - }() - - wg.Add(1) - go func() { - defer wg.Done() - // May be a gRPC error, not context.Canceled: - assert.ErrorContains(t, client.Run(ctx), "context canceled") - }() + runClientServer(ctx, t, &wg, client, server) require.EventuallyWithT( t, @@ -304,14 +292,9 @@ func TestIntegrationCustomHost(t *testing.T) { }) screen, screenCaptureC, getContents := setupSimulationScreen(t) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + var wg sync.WaitGroup + runClientServer(ctx, t, &wg, client, server) time.Sleep(time.Second) sendKey(t, screen, tcell.KeyF1, ' ') @@ -351,7 +334,7 @@ func TestIntegrationCustomHost(t *testing.T) { cancel() - <-done + wg.Wait() } func TestIntegrationCustomTLSCerts(t *testing.T) { @@ -375,14 +358,9 @@ func TestIntegrationCustomTLSCerts(t *testing.T) { }) screen, screenCaptureC, getContents := setupSimulationScreen(t) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + var wg sync.WaitGroup + runClientServer(ctx, t, &wg, client, server) require.EventuallyWithT( t, @@ -417,7 +395,7 @@ func TestIntegrationCustomTLSCerts(t *testing.T) { cancel() - <-done + wg.Wait() } func TestIntegrationRestartDestination(t *testing.T) { -- 2.47.2 From b8a77d9c6c7130e88d5a96ddb27c8331bb48345d Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sun, 11 May 2025 06:12:44 +0200 Subject: [PATCH 12/23] fixup! wip: refactor: API --- internal/app/integration_helpers_test.go | 55 +++--- internal/app/integration_test.go | 229 ++++------------------- internal/client/clientapp.go | 2 +- 3 files changed, 65 insertions(+), 221 deletions(-) diff --git a/internal/app/integration_helpers_test.go b/internal/app/integration_helpers_test.go index 61c3c96..fc90f04 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/app/integration_helpers_test.go @@ -22,30 +22,10 @@ import ( "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/terminal" "github.com/gdamore/tcell/v2" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" ) -func buildAppParams( - t *testing.T, - configService *config.Service, - dockerClient container.DockerClient, - screen tcell.SimulationScreen, - screenCaptureC chan<- terminal.ScreenCapture, - logger *slog.Logger, -) app.Params { - t.Helper() - - return app.Params{ - ConfigService: configService, - DockerClient: dockerClient, - ClipboardAvailable: false, - BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, - Logger: logger, - } -} - func buildClientServer( configService *config.Service, dockerClient container.DockerClient, @@ -54,7 +34,8 @@ func buildClientServer( logger *slog.Logger, ) (*client.App, *app.App) { buildInfo := domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"} - clientApp := client.New(client.NewParams{ + + client := client.New(client.NewParams{ BuildInfo: buildInfo, Screen: &terminal.Screen{ Screen: screen, @@ -65,8 +46,7 @@ func buildClientServer( Logger: logger, }) - // TODO: use buildAppParams - srvApp := app.New(app.Params{ + server := app.New(app.Params{ ConfigService: configService, DockerClient: dockerClient, ClipboardAvailable: false, @@ -74,29 +54,44 @@ func buildClientServer( Logger: logger, }) - return clientApp, srvApp + return client, server +} + +type clientServerResult struct { + errClient error + errServer error } func runClientServer( ctx context.Context, - t *testing.T, - wg *sync.WaitGroup, + _ *testing.T, clientApp *client.App, serverApp *app.App, -) { +) <-chan clientServerResult { + ch := make(chan clientServerResult, 1) + + var wg sync.WaitGroup + var clientErr, srvErr error + wg.Add(1) go func() { defer wg.Done() - assert.ErrorIs(t, serverApp.Run(ctx), context.Canceled) + srvErr = serverApp.Run(ctx) }() wg.Add(1) go func() { defer wg.Done() - // May be a gRPC error, not context.Canceled: - assert.ErrorContains(t, clientApp.Run(ctx), "context canceled") + clientErr = clientApp.Run(ctx) }() + go func() { + wg.Wait() + + ch <- clientServerResult{errClient: clientErr, errServer: srvErr} + }() + + return ch } func setupSimulationScreen(t *testing.T) (tcell.SimulationScreen, chan<- terminal.ScreenCapture, func() []string) { diff --git a/internal/app/integration_test.go b/internal/app/integration_test.go index bfc0f55..df5e13b 100644 --- a/internal/app/integration_test.go +++ b/internal/app/integration_test.go @@ -8,26 +8,19 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" - "errors" "fmt" - "net" "os" - "sync" "testing" "time" - "git.netflux.io/rob/octoplex/internal/app" "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/container" - "git.netflux.io/rob/octoplex/internal/container/mocks" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/testhelpers" - "github.com/docker/docker/api/types/network" dockerclient "github.com/docker/docker/client" "github.com/docker/docker/errdefs" "github.com/gdamore/tcell/v2" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -127,8 +120,7 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) { }) client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) - var wg sync.WaitGroup - runClientServer(ctx, t, &wg, client, server) + ch := runClientServer(ctx, t, client, server) require.EventuallyWithT( t, @@ -271,7 +263,10 @@ func testIntegration(t *testing.T, mediaServerConfig config.MediaServerSource) { cancel() - wg.Wait() + result := <-ch + // May be a gRPC error, not context.Canceled: + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationCustomHost(t *testing.T) { @@ -293,8 +288,7 @@ func TestIntegrationCustomHost(t *testing.T) { screen, screenCaptureC, getContents := setupSimulationScreen(t) client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) - var wg sync.WaitGroup - runClientServer(ctx, t, &wg, client, server) + ch := runClientServer(ctx, t, client, server) time.Sleep(time.Second) sendKey(t, screen, tcell.KeyF1, ' ') @@ -334,7 +328,10 @@ func TestIntegrationCustomHost(t *testing.T) { cancel() - wg.Wait() + result := <-ch + // May be a gRPC error, not context.Canceled: + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationCustomTLSCerts(t *testing.T) { @@ -359,8 +356,7 @@ func TestIntegrationCustomTLSCerts(t *testing.T) { screen, screenCaptureC, getContents := setupSimulationScreen(t) client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) - var wg sync.WaitGroup - runClientServer(ctx, t, &wg, client, server) + ch := runClientServer(ctx, t, client, server) require.EventuallyWithT( t, @@ -395,7 +391,9 @@ func TestIntegrationCustomTLSCerts(t *testing.T) { cancel() - wg.Wait() + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationRestartDestination(t *testing.T) { @@ -434,14 +432,8 @@ func TestIntegrationRestartDestination(t *testing.T) { }}, }) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) require.EventuallyWithT( t, @@ -553,7 +545,9 @@ func TestIntegrationRestartDestination(t *testing.T) { cancel() - <-done + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationStartDestinationFailed(t *testing.T) { @@ -571,14 +565,8 @@ func TestIntegrationStartDestinationFailed(t *testing.T) { Destinations: []config.Destination{{Name: "Example server", URL: "rtmp://rtmp.example.com/live"}}, }) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) require.EventuallyWithT( t, @@ -627,7 +615,9 @@ func TestIntegrationStartDestinationFailed(t *testing.T) { cancel() - <-done + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationDestinationValidations(t *testing.T) { @@ -644,14 +634,8 @@ func TestIntegrationDestinationValidations(t *testing.T) { Sources: config.Sources{MediaServer: config.MediaServerSource{StreamKey: "live", RTMP: config.RTMPSource{Enabled: true}}}, }) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) require.EventuallyWithT( t, @@ -755,7 +739,9 @@ func TestIntegrationDestinationValidations(t *testing.T) { printScreen(t, getContents, "After entering a duplicate destination URL") cancel() - <-done + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationStartupCheck(t *testing.T) { @@ -786,14 +772,8 @@ func TestIntegrationStartupCheck(t *testing.T) { configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) screen, screenCaptureC, getContents := setupSimulationScreen(t) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) require.EventuallyWithT( t, @@ -837,136 +817,9 @@ func TestIntegrationStartupCheck(t *testing.T) { printScreen(t, getContents, "After starting the mediaserver") cancel() - <-done -} - -func TestIntegrationMediaServerError(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) - defer cancel() - - lis, err := net.Listen("tcp", ":1935") - require.NoError(t, err) - t.Cleanup(func() { lis.Close() }) - - logger := testhelpers.NewTestLogger(t).With("component", "integration") - dockerClient, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation()) - require.NoError(t, err) - - configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) - screen, screenCaptureC, getContents := setupSimulationScreen(t) - - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.EqualError( - t, - app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx), - "media server exited", - ) - }() - - require.EventuallyWithT( - t, - func(c *assert.CollectT) { - assert.True(c, contentsIncludes(getContents(), "Server process exited unexpectedly."), "expected to see title") - assert.True(c, contentsIncludes(getContents(), "address already in use"), "expected to see message") - }, - waitTime, - time.Second, - "expected to see media server error modal", - ) - printScreen(t, getContents, "Ater displaying the media server error modal") - - // Quit the app, this should cause the done channel to receive. - sendKey(t, screen, tcell.KeyEnter, ' ') - - <-done -} - -func TestIntegrationDockerClientError(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) - defer cancel() - - logger := testhelpers.NewTestLogger(t).With("component", "integration") - - var dockerClient mocks.DockerClient - dockerClient.EXPECT().NetworkCreate(mock.Anything, mock.Anything, mock.Anything).Return(network.CreateResponse{}, errors.New("boom")) - - configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) - screen, screenCaptureC, getContents := setupSimulationScreen(t) - - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.EqualError( - t, - app.New(buildAppParams(t, configService, &dockerClient, screen, screenCaptureC, logger)).Run(ctx), - "create container client: network create: boom", - ) - }() - - require.EventuallyWithT( - t, - func(c *assert.CollectT) { - assert.True(c, contentsIncludes(getContents(), "An error occurred:"), "expected to see error message") - assert.True(c, contentsIncludes(getContents(), "create container client: network create: boom"), "expected to see message") - }, - waitTime, - time.Second, - "expected to see fatal error modal", - ) - printScreen(t, getContents, "Ater displaying the fatal error modal") - - // Quit the app, this should cause the done channel to receive. - sendKey(t, screen, tcell.KeyEnter, ' ') - - <-done -} - -func TestIntegrationDockerConnectionError(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) - defer cancel() - - logger := testhelpers.NewTestLogger(t).With("component", "integration") - dockerClient, err := dockerclient.NewClientWithOpts(dockerclient.WithHost("http://docker.example.com")) - require.NoError(t, err) - - configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) - screen, screenCaptureC, getContents := setupSimulationScreen(t) - - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - err := app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx) - require.ErrorContains(t, err, "dial tcp: lookup docker.example.com") - require.ErrorContains(t, err, "no such host") - }() - - require.EventuallyWithT( - t, - func(c *assert.CollectT) { - assert.True(c, contentsIncludes(getContents(), "An error occurred:"), "expected to see error message") - assert.True(c, contentsIncludes(getContents(), "Could not connect to Docker. Is Docker installed"), "expected to see message") - }, - waitTime, - time.Second, - "expected to see fatal error modal", - ) - printScreen(t, getContents, "Ater displaying the fatal error modal") - - // Quit the app, this should cause the done channel to receive. - sendKey(t, screen, tcell.KeyEnter, ' ') - - <-done + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) } func TestIntegrationCopyURLs(t *testing.T) { @@ -1036,14 +889,8 @@ func TestIntegrationCopyURLs(t *testing.T) { configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: tc.mediaServerConfig}}) screen, screenCaptureC, getContents := setupSimulationScreen(t) - done := make(chan struct{}) - go func() { - defer func() { - done <- struct{}{} - }() - - require.Equal(t, context.Canceled, app.New(buildAppParams(t, configService, dockerClient, screen, screenCaptureC, logger)).Run(ctx)) - }() + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) time.Sleep(3 * time.Second) printScreen(t, getContents, "Ater loading the app") @@ -1064,7 +911,9 @@ func TestIntegrationCopyURLs(t *testing.T) { cancel() - <-done + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorIs(t, result.errServer, context.Canceled) }) } } diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 572a5c4..6fbc7e7 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -70,7 +70,7 @@ func (a *App) Run(ctx context.Context) error { continue } - a.logger.Debug("Received event", "type", fmt.Sprintf("%T", evt)) + a.logger.Debug("Received event") a.bus.Send(protocol.EventFromProto(evt)) } }) -- 2.47.2 From 977b6fe7d7a98d155b330c1845da97c6a7904bd4 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sun, 11 May 2025 06:14:17 +0200 Subject: [PATCH 13/23] fixup! wip: refactor: API --- internal/event/bus.go | 2 +- internal/event/events.go | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/event/bus.go b/internal/event/bus.go index 246f4ae..3dc64de 100644 --- a/internal/event/bus.go +++ b/internal/event/bus.go @@ -58,7 +58,7 @@ func (b *Bus) Send(evt Event) { select { case ch <- evt: default: - b.logger.Warn("Event dropped", "name", evt.name()) + b.logger.Warn("Event dropped", "name", evt.EventName()) } } } diff --git a/internal/event/events.go b/internal/event/events.go index a43bae9..d80f830 100644 --- a/internal/event/events.go +++ b/internal/event/events.go @@ -19,7 +19,7 @@ const ( // Event represents something which happened in the appllication. type Event interface { - name() Name + EventName() Name } // AppStateChangedEvent is emitted when the application state changes. @@ -27,7 +27,7 @@ type AppStateChangedEvent struct { State domain.AppState } -func (e AppStateChangedEvent) name() Name { +func (e AppStateChangedEvent) EventName() Name { return EventNameAppStateChanged } @@ -36,7 +36,7 @@ type DestinationAddedEvent struct { URL string } -func (e DestinationAddedEvent) name() Name { +func (e DestinationAddedEvent) EventName() Name { return EventNameDestinationAdded } @@ -46,7 +46,7 @@ type AddDestinationFailedEvent struct { Err error } -func (e AddDestinationFailedEvent) name() Name { +func (e AddDestinationFailedEvent) EventName() Name { return EventNameAddDestinationFailed } @@ -56,7 +56,7 @@ type DestinationStreamExitedEvent struct { Err error } -func (e DestinationStreamExitedEvent) name() Name { +func (e DestinationStreamExitedEvent) EventName() Name { return EventNameDestinationStreamExited } @@ -66,7 +66,7 @@ type StartDestinationFailedEvent struct { Message string } -func (e StartDestinationFailedEvent) name() Name { +func (e StartDestinationFailedEvent) EventName() Name { return EventNameStartDestinationFailed } @@ -76,7 +76,7 @@ type DestinationRemovedEvent struct { URL string } -func (e DestinationRemovedEvent) name() Name { +func (e DestinationRemovedEvent) EventName() Name { return EventNameDestinationRemoved } @@ -87,7 +87,7 @@ type RemoveDestinationFailedEvent struct { Err error } -func (e RemoveDestinationFailedEvent) name() Name { +func (e RemoveDestinationFailedEvent) EventName() Name { return EventNameRemoveDestinationFailed } @@ -100,11 +100,11 @@ type FatalErrorOccurredEvent struct { // OtherInstanceDetectedEvent is emitted when the app launches and detects another instance. type OtherInstanceDetectedEvent struct{} -func (e OtherInstanceDetectedEvent) name() Name { +func (e OtherInstanceDetectedEvent) EventName() Name { return EventNameOtherInstanceDetected } -func (e FatalErrorOccurredEvent) name() Name { +func (e FatalErrorOccurredEvent) EventName() Name { return "fatal_error_occurred" } @@ -114,6 +114,6 @@ type MediaServerStartedEvent struct { RTMPSURL string } -func (e MediaServerStartedEvent) name() Name { +func (e MediaServerStartedEvent) EventName() Name { return "media_server_started" } -- 2.47.2 From d0d96dd1d93d1fa84b559546c9684a8921ba545b Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sun, 11 May 2025 06:15:20 +0200 Subject: [PATCH 14/23] fixup! wip: refactor: API --- internal/client/clientapp.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 6fbc7e7..eec1380 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -64,14 +64,15 @@ func (a *App) Run(ctx context.Context) error { return fmt.Errorf("receive envelope: %w", recErr) } - evt := envelope.GetEvent() - if evt == nil { + pbEvt := envelope.GetEvent() + if pbEvt == nil { a.logger.Error("Received envelope without event") continue } - a.logger.Debug("Received event") - a.bus.Send(protocol.EventFromProto(evt)) + evt := protocol.EventFromProto(pbEvt) + a.logger.Debug("Received event from gRPC stream", "event", evt) + a.bus.Send(evt) } }) -- 2.47.2 From c5724dfe5974777e6ddbfba06ac931ac529fc731 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sun, 11 May 2025 07:55:06 +0200 Subject: [PATCH 15/23] fixup! wip: refactor: API --- cmd/server/main.go | 28 +++++++------- internal/app/app.go | 47 +++++++++--------------- internal/app/integration_helpers_test.go | 12 ++---- 3 files changed, 36 insertions(+), 51 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index 2fdfcb9..107877d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -10,7 +10,7 @@ import ( "os" "os/exec" "os/signal" - "runtime/debug" + "runtime" "syscall" "git.netflux.io/rob/octoplex/internal/app" @@ -88,7 +88,6 @@ func run() error { return fmt.Errorf("build logger: %w", err) } - // When running in headless mode tview doesn't handle SIGINT for us. ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) @@ -107,24 +106,25 @@ func run() error { return fmt.Errorf("new docker client: %w", err) } - buildInfo, ok := debug.ReadBuildInfo() - if !ok { - return fmt.Errorf("read build info: %w", err) - } - app := app.New(app.Params{ ConfigService: configService, DockerClient: dockerClient, ConfigFilePath: configService.Path(), - BuildInfo: domain.BuildInfo{ - GoVersion: buildInfo.GoVersion, - Version: version, - Commit: commit, - Date: date, - }, - Logger: logger, + Logger: logger, }) + logger.Info( + "Starting application", + "version", + cmp.Or(version, "devel"), + "commit", + cmp.Or(commit, "unknown"), + "date", + cmp.Or(date, "unknown"), + "go_version", + runtime.Version(), + ) + if err := app.Run(ctx); err != nil { if errors.Is(err, context.Canceled) && context.Cause(ctx) == errShutdown { return errShutdown diff --git a/internal/app/app.go b/internal/app/app.go index 91e2b66..720c6bf 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -19,34 +19,27 @@ import ( "git.netflux.io/rob/octoplex/internal/mediaserver" "git.netflux.io/rob/octoplex/internal/replicator" "git.netflux.io/rob/octoplex/internal/server" - "git.netflux.io/rob/octoplex/internal/terminal" "github.com/docker/docker/client" "google.golang.org/grpc" ) // App is an instance of the app. type App struct { - cfg config.Config - configService *config.Service - eventBus *event.Bus - dispatchC chan event.Command - dockerClient container.DockerClient - screen *terminal.Screen // Screen may be nil. - clipboardAvailable bool - configFilePath string - buildInfo domain.BuildInfo - logger *slog.Logger + cfg config.Config + configService *config.Service + eventBus *event.Bus + dispatchC chan event.Command + dockerClient container.DockerClient + logger *slog.Logger } // Params holds the parameters for running the application. type Params struct { - ConfigService *config.Service - DockerClient container.DockerClient - ChanSize int - ClipboardAvailable bool - ConfigFilePath string - BuildInfo domain.BuildInfo - Logger *slog.Logger + ConfigService *config.Service + DockerClient container.DockerClient + ChanSize int + ConfigFilePath string + Logger *slog.Logger } // defaultChanSize is the default size of the dispatch channel. @@ -55,15 +48,12 @@ const defaultChanSize = 64 // New creates a new application instance. func New(params Params) *App { return &App{ - cfg: params.ConfigService.Current(), - configService: params.ConfigService, - eventBus: event.NewBus(params.Logger.With("component", "event_bus")), - dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)), - dockerClient: params.DockerClient, - clipboardAvailable: params.ClipboardAvailable, - configFilePath: params.ConfigFilePath, - buildInfo: params.BuildInfo, - logger: params.Logger, + cfg: params.ConfigService.Current(), + configService: params.ConfigService, + eventBus: event.NewBus(params.Logger.With("component", "event_bus")), + dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)), + dockerClient: params.DockerClient, + logger: params.Logger, } } @@ -78,8 +68,7 @@ func (a *App) Run(ctx context.Context) error { return errors.New("config: either sources.mediaServer.rtmp.enabled or sources.mediaServer.rtmps.enabled must be set") } - // doFatalError publishes a fatal error to the event bus, waiting for the - // user to acknowledge it if not in headless mode. + // doFatalError publishes a fatal error to the event bus. doFatalError := func(msg string) { a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg}) } diff --git a/internal/app/integration_helpers_test.go b/internal/app/integration_helpers_test.go index fc90f04..e08b832 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/app/integration_helpers_test.go @@ -33,10 +33,8 @@ func buildClientServer( screenCaptureC chan<- terminal.ScreenCapture, logger *slog.Logger, ) (*client.App, *app.App) { - buildInfo := domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"} - client := client.New(client.NewParams{ - BuildInfo: buildInfo, + BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, Screen: &terminal.Screen{ Screen: screen, Width: 160, @@ -47,11 +45,9 @@ func buildClientServer( }) server := app.New(app.Params{ - ConfigService: configService, - DockerClient: dockerClient, - ClipboardAvailable: false, - BuildInfo: buildInfo, - Logger: logger, + ConfigService: configService, + DockerClient: dockerClient, + Logger: logger, }) return client, server -- 2.47.2 From 5aa1be2066b8b5943ea03c946dc5fbabb76e21a8 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Sun, 11 May 2025 08:11:05 +0200 Subject: [PATCH 16/23] fixup! wip: refactor: API --- cmd/server/main.go | 4 ++-- go.mod | 2 +- internal/client/clientapp.go | 2 +- internal/{app => client}/integration_helpers_test.go | 10 +++++----- internal/{app => client}/integration_test.go | 2 +- internal/{app => client}/testdata/mediamtx.yml | 0 internal/{app => client}/testdata/openssl.cnf | 0 internal/{app => client}/testdata/server.crt | 0 internal/{app => client}/testdata/server.key | 0 internal/server/{server.go => grpc.go} | 7 +++++-- internal/{app/app.go => server/serverapp.go} | 5 ++--- internal/{app/app_test.go => server/serverapp_test.go} | 2 +- 12 files changed, 18 insertions(+), 16 deletions(-) rename internal/{app => client}/integration_helpers_test.go (97%) rename internal/{app => client}/integration_test.go (99%) rename internal/{app => client}/testdata/mediamtx.yml (100%) rename internal/{app => client}/testdata/openssl.cnf (100%) rename internal/{app => client}/testdata/server.crt (100%) rename internal/{app => client}/testdata/server.key (100%) rename internal/server/{server.go => grpc.go} (92%) rename internal/{app/app.go => server/serverapp.go} (98%) rename internal/{app/app_test.go => server/serverapp_test.go} (99%) diff --git a/cmd/server/main.go b/cmd/server/main.go index 107877d..630c864 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -13,9 +13,9 @@ import ( "runtime" "syscall" - "git.netflux.io/rob/octoplex/internal/app" "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/domain" + "git.netflux.io/rob/octoplex/internal/server" dockerclient "github.com/docker/docker/client" ) @@ -106,7 +106,7 @@ func run() error { return fmt.Errorf("new docker client: %w", err) } - app := app.New(app.Params{ + app := server.New(server.Params{ ConfigService: configService, DockerClient: dockerClient, ConfigFilePath: configService.Path(), diff --git a/go.mod b/go.mod index 81d5088..76ee69f 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.35.0 golang.design/x/clipboard v0.7.0 + golang.org/x/sync v0.13.0 google.golang.org/grpc v1.69.4 google.golang.org/protobuf v1.36.3 gopkg.in/yaml.v3 v3.0.1 @@ -98,7 +99,6 @@ require ( golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect - golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/term v0.31.0 // indirect golang.org/x/text v0.24.0 // indirect diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index eec1380..7cfa00b 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -3,13 +3,13 @@ package client import ( "context" "fmt" + "log/slog" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/event" pb "git.netflux.io/rob/octoplex/internal/generated/grpc" "git.netflux.io/rob/octoplex/internal/protocol" "git.netflux.io/rob/octoplex/internal/terminal" - "github.com/sagikazarmark/slog-shim" "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" diff --git a/internal/app/integration_helpers_test.go b/internal/client/integration_helpers_test.go similarity index 97% rename from internal/app/integration_helpers_test.go rename to internal/client/integration_helpers_test.go index e08b832..13d2fc5 100644 --- a/internal/app/integration_helpers_test.go +++ b/internal/client/integration_helpers_test.go @@ -1,6 +1,6 @@ //go:build integration -package app_test +package client_test import ( "context" @@ -15,11 +15,11 @@ import ( "testing" "time" - "git.netflux.io/rob/octoplex/internal/app" "git.netflux.io/rob/octoplex/internal/client" "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/container" "git.netflux.io/rob/octoplex/internal/domain" + "git.netflux.io/rob/octoplex/internal/server" "git.netflux.io/rob/octoplex/internal/terminal" "github.com/gdamore/tcell/v2" "github.com/stretchr/testify/require" @@ -32,7 +32,7 @@ func buildClientServer( screen tcell.SimulationScreen, screenCaptureC chan<- terminal.ScreenCapture, logger *slog.Logger, -) (*client.App, *app.App) { +) (*client.App, *server.App) { client := client.New(client.NewParams{ BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, Screen: &terminal.Screen{ @@ -44,7 +44,7 @@ func buildClientServer( Logger: logger, }) - server := app.New(app.Params{ + server := server.New(server.Params{ ConfigService: configService, DockerClient: dockerClient, Logger: logger, @@ -62,7 +62,7 @@ func runClientServer( ctx context.Context, _ *testing.T, clientApp *client.App, - serverApp *app.App, + serverApp *server.App, ) <-chan clientServerResult { ch := make(chan clientServerResult, 1) diff --git a/internal/app/integration_test.go b/internal/client/integration_test.go similarity index 99% rename from internal/app/integration_test.go rename to internal/client/integration_test.go index df5e13b..51a5f62 100644 --- a/internal/app/integration_test.go +++ b/internal/client/integration_test.go @@ -1,6 +1,6 @@ //go:build integration -package app_test +package client_test import ( "cmp" diff --git a/internal/app/testdata/mediamtx.yml b/internal/client/testdata/mediamtx.yml similarity index 100% rename from internal/app/testdata/mediamtx.yml rename to internal/client/testdata/mediamtx.yml diff --git a/internal/app/testdata/openssl.cnf b/internal/client/testdata/openssl.cnf similarity index 100% rename from internal/app/testdata/openssl.cnf rename to internal/client/testdata/openssl.cnf diff --git a/internal/app/testdata/server.crt b/internal/client/testdata/server.crt similarity index 100% rename from internal/app/testdata/server.crt rename to internal/client/testdata/server.crt diff --git a/internal/app/testdata/server.key b/internal/client/testdata/server.key similarity index 100% rename from internal/app/testdata/server.key rename to internal/client/testdata/server.key diff --git a/internal/server/server.go b/internal/server/grpc.go similarity index 92% rename from internal/server/server.go rename to internal/server/grpc.go index 2ccfb54..b28bbf6 100644 --- a/internal/server/server.go +++ b/internal/server/grpc.go @@ -5,14 +5,16 @@ import ( "errors" "fmt" "io" + "log/slog" "git.netflux.io/rob/octoplex/internal/event" pb "git.netflux.io/rob/octoplex/internal/generated/grpc" "git.netflux.io/rob/octoplex/internal/protocol" - "github.com/sagikazarmark/slog-shim" "golang.org/x/sync/errgroup" ) +// Server is the gRPC server that handles incoming commands and outgoing +// events. type Server struct { pb.UnimplementedInternalAPIServer @@ -21,7 +23,8 @@ type Server struct { logger *slog.Logger } -func New( +// newServer creates a new gRPC server. +func newServer( dispatcher func(event.Command), bus *event.Bus, logger *slog.Logger, diff --git a/internal/app/app.go b/internal/server/serverapp.go similarity index 98% rename from internal/app/app.go rename to internal/server/serverapp.go index 720c6bf..ded01e8 100644 --- a/internal/app/app.go +++ b/internal/server/serverapp.go @@ -1,4 +1,4 @@ -package app +package server import ( "cmp" @@ -18,7 +18,6 @@ import ( pb "git.netflux.io/rob/octoplex/internal/generated/grpc" "git.netflux.io/rob/octoplex/internal/mediaserver" "git.netflux.io/rob/octoplex/internal/replicator" - "git.netflux.io/rob/octoplex/internal/server" "github.com/docker/docker/client" "google.golang.org/grpc" ) @@ -83,7 +82,7 @@ func (a *App) Run(ctx context.Context) error { grpcServer := grpc.NewServer() grpcDone := make(chan error, 1) - pb.RegisterInternalAPIServer(grpcServer, server.New(a.DispatchAsync, a.eventBus, a.logger)) + pb.RegisterInternalAPIServer(grpcServer, newServer(a.DispatchAsync, a.eventBus, a.logger)) go func() { a.logger.Info("gRPC server started", "addr", grpcAddr) grpcDone <- grpcServer.Serve(lis) diff --git a/internal/app/app_test.go b/internal/server/serverapp_test.go similarity index 99% rename from internal/app/app_test.go rename to internal/server/serverapp_test.go index c9985ef..5c74a39 100644 --- a/internal/app/app_test.go +++ b/internal/server/serverapp_test.go @@ -1,4 +1,4 @@ -package app +package server import ( "testing" -- 2.47.2 From 7a3f1335c1060f4418f2ea575e695c4232503ee5 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 12 May 2025 09:00:43 +0200 Subject: [PATCH 17/23] fixup! wip: refactor: API --- go.mod | 4 ++ go.sum | 8 +++ main.go | 220 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 232 insertions(+) create mode 100644 main.go diff --git a/go.mod b/go.mod index 76ee69f..f78242b 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect @@ -68,6 +69,7 @@ require ( github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/zerolog v1.33.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shirou/gopsutil/v3 v3.23.12 // indirect @@ -83,7 +85,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/urfave/cli/v2 v2.27.6 // indirect github.com/vektra/mockery/v2 v2.52.2 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect diff --git a/go.sum b/go.sum index f8574d0..df6f745 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -142,6 +144,8 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= @@ -187,8 +191,12 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= +github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vektra/mockery/v2 v2.52.2 h1:8QfPKUIrq8P3Cs7G79Iu4Byd5wdhGCE0quIS27x7rQo= github.com/vektra/mockery/v2 v2.52.2/go.mod h1:zGDY/f6bip0Yh13GQ5j7xa43fuEoYBa4ICHEaihisHw= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/main.go b/main.go new file mode 100644 index 0000000..6559f83 --- /dev/null +++ b/main.go @@ -0,0 +1,220 @@ +package main + +import ( + "cmp" + "context" + "errors" + "fmt" + "io" + "log/slog" + "os" + "os/signal" + "runtime" + "runtime/debug" + "syscall" + + "git.netflux.io/rob/octoplex/internal/client" + "git.netflux.io/rob/octoplex/internal/config" + "git.netflux.io/rob/octoplex/internal/domain" + "git.netflux.io/rob/octoplex/internal/server" + dockerclient "github.com/docker/docker/client" + "github.com/urfave/cli/v2" + "golang.design/x/clipboard" + "golang.org/x/sync/errgroup" +) + +var ( + // version is the version of the application. + version string + // commit is the commit hash of the application. + commit string + // date is the date of the build. + date string +) + +var errShutdown = errors.New("shutdown") + +func main() { + // when server is running: + // server log goes to wherever config defined it + // client log does not exist + // when client is running: + // server log does not exist + // client log goes to ??? but for now octoplex.log + // when both are running: + // server log goes to wherever config defined it + // client logs goes to ??? but for now octoplex.log + app := &cli.App{ + Name: "Octoplex", + Usage: "Octoplex is a live video restreamer for Docker.", + Commands: []*cli.Command{ + { + Name: "client", + Usage: "Run the client", + Flags: []cli.Flag{ /* client flags */ }, + Action: func(c *cli.Context) error { + return runClient(c) + }, + }, + { + Name: "server", + Usage: "Run the server", + Flags: []cli.Flag{ /* server flags */ }, + Action: func(c *cli.Context) error { + return runServer(c, true) + }, + }, + { + Name: "run", + Usage: "Run server and client together (testing)", + Flags: []cli.Flag{ /* optional combined flags */ }, + Action: func(c *cli.Context) error { + return runClientAndServer(c) + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func runClient(_ *cli.Context) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // TODO: logger from config + fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return fmt.Errorf("open log file: %w", err) + } + logger := slog.New(slog.NewTextHandler(fptr, nil)) + + var clipboardAvailable bool + if err = clipboard.Init(); err != nil { + logger.Warn("Clipboard not available", "err", err) + } else { + clipboardAvailable = true + } + + buildInfo, ok := debug.ReadBuildInfo() + if !ok { + return fmt.Errorf("read build info: %w", err) + } + + app := client.New(client.NewParams{ + ClipboardAvailable: clipboardAvailable, + BuildInfo: domain.BuildInfo{ + GoVersion: buildInfo.GoVersion, + Version: version, + Commit: commit, + Date: date, + }, + Logger: logger, + }) + if err := app.Run(ctx); err != nil { + return fmt.Errorf("run app: %w", err) + } + + return nil +} + +func runServer(_ *cli.Context, stderrAvailable bool) error { + ctx, cancel := context.WithCancelCause(context.Background()) + defer cancel(nil) + + configService, err := config.NewDefaultService() + if err != nil { + return fmt.Errorf("build config service: %w", err) + } + + cfg, err := configService.ReadOrCreateConfig() + if err != nil { + return fmt.Errorf("read or create config: %w", err) + } + + // TODO: improve logger API + // Currently it's a bit complicated because we can only use stdout - the + // preferred destination - if the client is not running. Otherwise we + // fallback to the legacy configuration but this should be bought more + // in-line with the client/server split. + var w io.Writer + if stderrAvailable { + w = os.Stdout + } else if !cfg.LogFile.Enabled { + w = io.Discard + } else { + w, err = os.OpenFile(cfg.LogFile.GetPath(), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return fmt.Errorf("error opening log file: %w", err) + } + } + + var handlerOpts slog.HandlerOptions + if os.Getenv("OCTO_DEBUG") != "" { + handlerOpts.Level = slog.LevelDebug + } + logger := slog.New(slog.NewTextHandler(w, &handlerOpts)) + + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-ch + logger.Info("Received interrupt signal, exiting") + signal.Stop(ch) + cancel(errShutdown) + }() + + dockerClient, err := dockerclient.NewClientWithOpts( + dockerclient.FromEnv, + dockerclient.WithAPIVersionNegotiation(), + ) + if err != nil { + return fmt.Errorf("new docker client: %w", err) + } + + app := server.New(server.Params{ + ConfigService: configService, + DockerClient: dockerClient, + ConfigFilePath: configService.Path(), + Logger: logger, + }) + + logger.Info( + "Starting server", + "version", + cmp.Or(version, "devel"), + "commit", + cmp.Or(commit, "unknown"), + "date", + cmp.Or(date, "unknown"), + "go_version", + runtime.Version(), + ) + + if err := app.Run(ctx); err != nil { + if errors.Is(err, context.Canceled) && context.Cause(ctx) == errShutdown { + return errShutdown + } + return err + } + + return nil +} + +func runClientAndServer(c *cli.Context) error { + g, _ := errgroup.WithContext(c.Context) + + g.Go(func() error { + return runClient(c) + }) + + g.Go(func() error { + return runServer(c, false) + }) + + return g.Wait() +} -- 2.47.2 From eaccb17f03979be9ec2d13da8a5ea43c14694c0c Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 12 May 2025 19:30:28 +0200 Subject: [PATCH 18/23] fixup! wip: refactor: API --- cmd/client/main.go | 69 ---------------- cmd/server/main.go | 194 --------------------------------------------- main.go | 83 +++++++++++-------- 3 files changed, 52 insertions(+), 294 deletions(-) delete mode 100644 cmd/client/main.go delete mode 100644 cmd/server/main.go diff --git a/cmd/client/main.go b/cmd/client/main.go deleted file mode 100644 index 5abc7e3..0000000 --- a/cmd/client/main.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log/slog" - "os" - "runtime/debug" - - "git.netflux.io/rob/octoplex/internal/client" - "git.netflux.io/rob/octoplex/internal/domain" - "golang.design/x/clipboard" -) - -var ( - // version is the version of the application. - version string - // commit is the commit hash of the application. - commit string - // date is the date of the build. - date string -) - -func main() { - if err := run(); err != nil { - os.Stderr.WriteString("Error: " + err.Error() + "\n") - os.Exit(1) - } -} - -func run() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // TODO: logger from config - fptr, err := os.OpenFile("octoplex.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - if err != nil { - return fmt.Errorf("open log file: %w", err) - } - logger := slog.New(slog.NewTextHandler(fptr, nil)) - - var clipboardAvailable bool - if err = clipboard.Init(); err != nil { - logger.Warn("Clipboard not available", "err", err) - } else { - clipboardAvailable = true - } - - buildInfo, ok := debug.ReadBuildInfo() - if !ok { - return fmt.Errorf("read build info: %w", err) - } - - app := client.New(client.NewParams{ - ClipboardAvailable: clipboardAvailable, - BuildInfo: domain.BuildInfo{ - GoVersion: buildInfo.GoVersion, - Version: version, - Commit: commit, - Date: date, - }, - Logger: logger, - }) - if err := app.Run(ctx); err != nil { - return fmt.Errorf("run app: %w", err) - } - - return nil -} diff --git a/cmd/server/main.go b/cmd/server/main.go deleted file mode 100644 index 630c864..0000000 --- a/cmd/server/main.go +++ /dev/null @@ -1,194 +0,0 @@ -package main - -import ( - "cmp" - "context" - "errors" - "flag" - "fmt" - "log/slog" - "os" - "os/exec" - "os/signal" - "runtime" - "syscall" - - "git.netflux.io/rob/octoplex/internal/config" - "git.netflux.io/rob/octoplex/internal/domain" - "git.netflux.io/rob/octoplex/internal/server" - dockerclient "github.com/docker/docker/client" -) - -var ( - // version is the version of the application. - version string - // commit is the commit hash of the application. - commit string - // date is the date of the build. - date string -) - -var errShutdown = errors.New("shutdown") - -func main() { - var exitStatus int - - if err := run(); errors.Is(err, errShutdown) { - exitStatus = 130 - } else if err != nil { - exitStatus = 1 - _, _ = os.Stderr.WriteString("Error: " + err.Error() + "\n") - } - - os.Exit(exitStatus) -} - -func run() error { - ctx, cancel := context.WithCancelCause(context.Background()) - defer cancel(nil) - - configService, err := config.NewDefaultService() - if err != nil { - return fmt.Errorf("build config service: %w", err) - } - - help := flag.Bool("h", false, "Show help") - - flag.Parse() - - if *help { - printUsage() - return nil - } - - if narg := flag.NArg(); narg > 1 { - printUsage() - return fmt.Errorf("too many arguments") - } else if narg == 1 { - switch flag.Arg(0) { - case "edit-config": - return editConfigFile(configService) - case "print-config": - return printConfigPath(configService.Path()) - case "version": - return printVersion() - case "help": - printUsage() - return nil - } - } - - cfg, err := configService.ReadOrCreateConfig() - if err != nil { - return fmt.Errorf("read or create config: %w", err) - } - - logger, err := buildLogger(cfg.LogFile) - if err != nil { - return fmt.Errorf("build logger: %w", err) - } - - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - - go func() { - <-ch - logger.Info("Received interrupt signal, exiting") - signal.Stop(ch) - cancel(errShutdown) - }() - - dockerClient, err := dockerclient.NewClientWithOpts( - dockerclient.FromEnv, - dockerclient.WithAPIVersionNegotiation(), - ) - if err != nil { - return fmt.Errorf("new docker client: %w", err) - } - - app := server.New(server.Params{ - ConfigService: configService, - DockerClient: dockerClient, - ConfigFilePath: configService.Path(), - Logger: logger, - }) - - logger.Info( - "Starting application", - "version", - cmp.Or(version, "devel"), - "commit", - cmp.Or(commit, "unknown"), - "date", - cmp.Or(date, "unknown"), - "go_version", - runtime.Version(), - ) - - if err := app.Run(ctx); err != nil { - if errors.Is(err, context.Canceled) && context.Cause(ctx) == errShutdown { - return errShutdown - } - return err - } - - return nil -} - -// editConfigFile opens the config file in the user's editor. -func editConfigFile(configService *config.Service) error { - if _, err := configService.ReadOrCreateConfig(); err != nil { - return fmt.Errorf("read or create config: %w", err) - } - - editor := os.Getenv("EDITOR") - if editor == "" { - editor = "vi" - } - binary, err := exec.LookPath(editor) - if err != nil { - return fmt.Errorf("look path: %w", err) - } - - fmt.Fprintf(os.Stderr, "Editing config file: %s\n", configService.Path()) - fmt.Println(binary) - - if err := syscall.Exec(binary, []string{"--", configService.Path()}, os.Environ()); err != nil { - return fmt.Errorf("exec: %w", err) - } - - return nil -} - -// printConfigPath prints the path to the config file to stderr. -func printConfigPath(configPath string) error { - fmt.Fprintln(os.Stderr, configPath) - return nil -} - -// printVersion prints the version of the application to stderr. -func printVersion() error { - fmt.Fprintf(os.Stderr, "%s version %s\n", domain.AppName, cmp.Or(version, "0.0.0-dev")) - return nil -} - -func printUsage() { - os.Stderr.WriteString("Usage: octoplex [command]\n\n") - os.Stderr.WriteString("Commands:\n\n") - os.Stderr.WriteString(" edit-config Edit the config file\n") - os.Stderr.WriteString(" print-config Print the path to the config file\n") - os.Stderr.WriteString(" version Print the version of the application\n") - os.Stderr.WriteString(" help Print this help message\n") - os.Stderr.WriteString("\n") - os.Stderr.WriteString("Additionally, Octoplex can be configured with the following environment variables:\n\n") - os.Stderr.WriteString(" OCTO_DEBUG Enables debug logging if set\n") -} - -// buildLogger builds the logger, which may be a no-op logger. -func buildLogger(cfg config.LogFile) (*slog.Logger, error) { - var handlerOpts slog.HandlerOptions - if os.Getenv("OCTO_DEBUG") != "" { - handlerOpts.Level = slog.LevelDebug - } - return slog.New(slog.NewTextHandler(os.Stderr, &handlerOpts)), nil -} diff --git a/main.go b/main.go index 6559f83..fea522d 100644 --- a/main.go +++ b/main.go @@ -32,18 +32,17 @@ var ( date string ) -var errShutdown = errors.New("shutdown") +type errInterrupt struct{} + +func (e errInterrupt) Error() string { + return "interrupt signal received" +} + +func (e errInterrupt) ExitCode() int { + return 130 +} func main() { - // when server is running: - // server log goes to wherever config defined it - // client log does not exist - // when client is running: - // server log does not exist - // client log goes to ??? but for now octoplex.log - // when both are running: - // server log goes to wherever config defined it - // client logs goes to ??? but for now octoplex.log app := &cli.App{ Name: "Octoplex", Usage: "Octoplex is a live video restreamer for Docker.", @@ -53,7 +52,7 @@ func main() { Usage: "Run the client", Flags: []cli.Flag{ /* client flags */ }, Action: func(c *cli.Context) error { - return runClient(c) + return runClient(c.Context, c) }, }, { @@ -61,7 +60,7 @@ func main() { Usage: "Run the server", Flags: []cli.Flag{ /* server flags */ }, Action: func(c *cli.Context) error { - return runServer(c, true) + return runServer(c.Context, c, serverConfig{stderrAvailable: true, handleSigInt: true}) }, }, { @@ -81,8 +80,8 @@ func main() { } } -func runClient(_ *cli.Context) error { - ctx, cancel := context.WithCancel(context.Background()) +func runClient(ctx context.Context, _ *cli.Context) error { + ctx, cancel := context.WithCancel(ctx) defer cancel() // TODO: logger from config @@ -91,6 +90,7 @@ func runClient(_ *cli.Context) error { return fmt.Errorf("open log file: %w", err) } logger := slog.New(slog.NewTextHandler(fptr, nil)) + logger.Info("Starting client", "version", cmp.Or(version, "devel"), "commit", cmp.Or(commit, "unknown"), "date", cmp.Or(date, "unknown"), "go_version", runtime.Version()) var clipboardAvailable bool if err = clipboard.Init(); err != nil { @@ -121,8 +121,13 @@ func runClient(_ *cli.Context) error { return nil } -func runServer(_ *cli.Context, stderrAvailable bool) error { - ctx, cancel := context.WithCancelCause(context.Background()) +type serverConfig struct { + stderrAvailable bool + handleSigInt bool +} + +func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) error { + ctx, cancel := context.WithCancelCause(ctx) defer cancel(nil) configService, err := config.NewDefaultService() @@ -141,7 +146,7 @@ func runServer(_ *cli.Context, stderrAvailable bool) error { // fallback to the legacy configuration but this should be bought more // in-line with the client/server split. var w io.Writer - if stderrAvailable { + if serverCfg.stderrAvailable { w = os.Stdout } else if !cfg.LogFile.Enabled { w = io.Discard @@ -158,15 +163,17 @@ func runServer(_ *cli.Context, stderrAvailable bool) error { } logger := slog.New(slog.NewTextHandler(w, &handlerOpts)) - ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) + if serverCfg.handleSigInt { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-ch - logger.Info("Received interrupt signal, exiting") - signal.Stop(ch) - cancel(errShutdown) - }() + go func() { + <-ch + logger.Info("Received interrupt signal, exiting") + signal.Stop(ch) + cancel(errInterrupt{}) + }() + } dockerClient, err := dockerclient.NewClientWithOpts( dockerclient.FromEnv, @@ -196,8 +203,8 @@ func runServer(_ *cli.Context, stderrAvailable bool) error { ) if err := app.Run(ctx); err != nil { - if errors.Is(err, context.Canceled) && context.Cause(ctx) == errShutdown { - return errShutdown + if errors.Is(err, context.Canceled) && errors.Is(context.Cause(ctx), errInterrupt{}) { + return context.Cause(ctx) } return err } @@ -206,15 +213,29 @@ func runServer(_ *cli.Context, stderrAvailable bool) error { } func runClientAndServer(c *cli.Context) error { - g, _ := errgroup.WithContext(c.Context) + errNoErr := errors.New("no error") + + g, ctx := errgroup.WithContext(c.Context) g.Go(func() error { - return runClient(c) + if err := runClient(ctx, c); err != nil { + return err + } + + return errNoErr }) g.Go(func() error { - return runServer(c, false) + if err := runServer(ctx, c, serverConfig{stderrAvailable: false, handleSigInt: false}); err != nil { + return err + } + + return errNoErr }) - return g.Wait() + if err := g.Wait(); err == errNoErr { + return nil + } else { + return err + } } -- 2.47.2 From e3e469edec0b6bd890115253a6bc219a00e67409 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 12 May 2025 19:32:24 +0200 Subject: [PATCH 19/23] fixup! wip: refactor: API --- go.mod | 2 +- go.sum | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/go.mod b/go.mod index f78242b..2e9d901 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/rivo/tview v0.0.0-20250330220935-949945f8d922 github.com/stretchr/testify v1.10.0 github.com/testcontainers/testcontainers-go v0.35.0 + github.com/urfave/cli/v2 v2.27.6 golang.design/x/clipboard v0.7.0 golang.org/x/sync v0.13.0 google.golang.org/grpc v1.69.4 @@ -85,7 +86,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/urfave/cli/v2 v2.27.6 // indirect github.com/vektra/mockery/v2 v2.52.2 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect diff --git a/go.sum b/go.sum index df6f745..219f718 100644 --- a/go.sum +++ b/go.sum @@ -144,7 +144,6 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -- 2.47.2 From 311c100d8973b7c5545725596f2ddf95bbca9e9f Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 12 May 2025 19:42:50 +0200 Subject: [PATCH 20/23] fixup! wip: refactor: API --- .goreleaser.yaml | 3 +-- internal/client/clientapp.go | 3 +++ internal/event/bus.go | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 055a56b..25294e9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -6,8 +6,7 @@ before: - go generate ./... builds: - - main: ./cmd/server - env: + - env: # - CGO_ENABLED=0 goos: - linux diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 7cfa00b..796735c 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -44,6 +44,9 @@ func New(params NewParams) *App { } // Run starts the application, and blocks until it is closed. +// +// It returns nil if the application was closed by the user, or an error if it +// closed for any other reason. func (a *App) Run(ctx context.Context) error { g, ctx := errgroup.WithContext(ctx) diff --git a/internal/event/bus.go b/internal/event/bus.go index 3dc64de..a585acd 100644 --- a/internal/event/bus.go +++ b/internal/event/bus.go @@ -32,6 +32,7 @@ func (b *Bus) Register() <-chan Event { return ch } +// Deregister deregisters a consumer for all events. func (b *Bus) Deregister(ch <-chan Event) { b.mu.Lock() defer b.mu.Unlock() -- 2.47.2 From 32701499e7baa2f5601180cbff4345cfd19136d6 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 12 May 2025 21:13:16 +0200 Subject: [PATCH 21/23] fixup! wip: refactor: API --- internal/client/integration_helpers_test.go | 22 ++++++------ internal/client/integration_test.go | 39 +++++++++++++++++++++ internal/server/grpc.go | 26 ++++++++++++++ internal/server/serverapp.go | 36 +++++++++++++++---- 4 files changed, 105 insertions(+), 18 deletions(-) diff --git a/internal/client/integration_helpers_test.go b/internal/client/integration_helpers_test.go index 13d2fc5..7f5a73c 100644 --- a/internal/client/integration_helpers_test.go +++ b/internal/client/integration_helpers_test.go @@ -5,6 +5,7 @@ package client_test import ( "context" "encoding/json" + "errors" "fmt" "io" "log/slog" @@ -24,6 +25,7 @@ import ( "github.com/gdamore/tcell/v2" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" + "golang.org/x/sync/errgroup" ) func buildClientServer( @@ -37,7 +39,7 @@ func buildClientServer( BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, Screen: &terminal.Screen{ Screen: screen, - Width: 160, + Width: 180, Height: 25, CaptureC: screenCaptureC, }, @@ -66,23 +68,21 @@ func runClientServer( ) <-chan clientServerResult { ch := make(chan clientServerResult, 1) - var wg sync.WaitGroup + g, ctx := errgroup.WithContext(ctx) var clientErr, srvErr error - wg.Add(1) - go func() { - defer wg.Done() + g.Go(func() error { srvErr = serverApp.Run(ctx) - }() + return errors.New("server closed") + }) - wg.Add(1) - go func() { - defer wg.Done() + g.Go(func() error { clientErr = clientApp.Run(ctx) - }() + return errors.New("client closed") + }) go func() { - wg.Wait() + _ = g.Wait() ch <- clientServerResult{errClient: clientErr, errServer: srvErr} }() diff --git a/internal/client/integration_test.go b/internal/client/integration_test.go index 51a5f62..c31b3ea 100644 --- a/internal/client/integration_test.go +++ b/internal/client/integration_test.go @@ -9,6 +9,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "net" "os" "testing" "time" @@ -822,6 +823,44 @@ func TestIntegrationStartupCheck(t *testing.T) { assert.ErrorIs(t, result.errServer, context.Canceled) } +func TestIntegrationMediaServerError(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) + defer cancel() + + lis, err := net.Listen("tcp", ":1935") + require.NoError(t, err) + t.Cleanup(func() { lis.Close() }) + + logger := testhelpers.NewTestLogger(t).With("component", "integration") + dockerClient, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv, dockerclient.WithAPIVersionNegotiation()) + require.NoError(t, err) + + configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) + screen, screenCaptureC, getContents := setupSimulationScreen(t) + + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) + + require.EventuallyWithT( + t, + func(c *assert.CollectT) { + assert.True(c, contentsIncludes(getContents(), "Server process exited unexpectedly."), "expected to see title") + assert.True(c, contentsIncludes(getContents(), "address already in use"), "expected to see message") + }, + waitTime, + time.Second, + "expected to see media server error modal", + ) + printScreen(t, getContents, "Ater displaying the media server error modal") + + // Quit the app: + sendKey(t, screen, tcell.KeyEnter, ' ') + + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorContains(t, result.errServer, "media server exited") +} + func TestIntegrationCopyURLs(t *testing.T) { type binding struct { key tcell.Key diff --git a/internal/server/grpc.go b/internal/server/grpc.go index b28bbf6..1c68bba 100644 --- a/internal/server/grpc.go +++ b/internal/server/grpc.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log/slog" + "sync" "git.netflux.io/rob/octoplex/internal/event" pb "git.netflux.io/rob/octoplex/internal/generated/grpc" @@ -13,6 +14,10 @@ import ( "golang.org/x/sync/errgroup" ) +// APIListenerCountDeltaFunc is a function that is called when the number of +// API clients increments or decrements. +type APIClientCountDeltaFunc func(delta int) + // Server is the gRPC server that handles incoming commands and outgoing // events. type Server struct { @@ -21,6 +26,9 @@ type Server struct { dispatcher func(event.Command) bus *event.Bus logger *slog.Logger + + mu sync.Mutex + clientCount int } // newServer creates a new gRPC server. @@ -56,6 +64,16 @@ func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error { }) g.Go(func() error { + s.mu.Lock() + s.clientCount++ + s.mu.Unlock() + + defer func() { + s.mu.Lock() + s.clientCount-- + s.mu.Unlock() + }() + for { in, err := stream.Recv() if err == io.EOF { @@ -87,3 +105,11 @@ func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error { return nil } + +// GetClientCount returns the number of connected clients. +func (s *Server) GetClientCount() int { + s.mu.Lock() + defer s.mu.Unlock() + + return s.clientCount +} diff --git a/internal/server/serverapp.go b/internal/server/serverapp.go index ded01e8..7e1f63d 100644 --- a/internal/server/serverapp.go +++ b/internal/server/serverapp.go @@ -67,11 +67,6 @@ func (a *App) Run(ctx context.Context) error { return errors.New("config: either sources.mediaServer.rtmp.enabled or sources.mediaServer.rtmps.enabled must be set") } - // doFatalError publishes a fatal error to the event bus. - doFatalError := func(msg string) { - a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg}) - } - const grpcAddr = ":50051" lis, err := net.Listen("tcp", grpcAddr) if err != nil { @@ -81,13 +76,40 @@ func (a *App) Run(ctx context.Context) error { grpcServer := grpc.NewServer() grpcDone := make(chan error, 1) - - pb.RegisterInternalAPIServer(grpcServer, newServer(a.DispatchAsync, a.eventBus, a.logger)) + internalAPI := newServer(a.DispatchAsync, a.eventBus, a.logger) + pb.RegisterInternalAPIServer(grpcServer, internalAPI) go func() { a.logger.Info("gRPC server started", "addr", grpcAddr) grpcDone <- grpcServer.Serve(lis) }() + // emptyUI is a dummy function that sets the UI state to an empty state, and + // re-renders the screen. + // + // This is a workaround for a weird interaction between tview and + // tcell.SimulationScreen which leads to newly-added pages not rendering if + // the UI is not re-rendered for a second time. + // It is only needed for integration tests when rendering modals before the + // main loop starts. It would be nice to remove this but the risk/impact on + // non-test code is pretty low. + emptyUI := func() { + a.eventBus.Send(event.AppStateChangedEvent{State: domain.AppState{}}) + } + + // doFatalError publishes a fatal error to the event bus. It will block until + // the user acknowledges it if there is 1 or more clients connected to the + // internal API. + doFatalError := func(msg string) { + a.eventBus.Send(event.FatalErrorOccurredEvent{Message: msg}) + + if internalAPI.GetClientCount() == 0 { + return + } + + emptyUI() + <-a.dispatchC + } + containerClient, err := container.NewClient(ctx, a.dockerClient, a.logger.With("component", "container_client")) if err != nil { err = fmt.Errorf("create container client: %w", err) -- 2.47.2 From 5565331630c1f15aef1d07dcad246fc8e0206a2f Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Tue, 13 May 2025 07:11:18 +0200 Subject: [PATCH 22/23] fixup! wip: refactor: API --- internal/client/clientapp.go | 63 +- internal/client/integration_helpers_test.go | 1 + internal/client/integration_test.go | 74 ++ internal/generated/grpc/api.pb.go | 752 ++++++++++++-------- internal/server/grpc.go | 36 + internal/server/protocol.go | 1 - internal/server/serverapp.go | 9 + main.go | 21 +- proto/api.proto | 6 + 9 files changed, 635 insertions(+), 328 deletions(-) delete mode 100644 internal/server/protocol.go diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index 796735c..c291209 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -60,25 +60,6 @@ func (a *App) Run(ctx context.Context) error { return fmt.Errorf("create gRPC stream: %w", err) } - g.Go(func() error { - for { - envelope, recErr := stream.Recv() - if recErr != nil { - return fmt.Errorf("receive envelope: %w", recErr) - } - - pbEvt := envelope.GetEvent() - if pbEvt == nil { - a.logger.Error("Received envelope without event") - continue - } - - evt := protocol.EventFromProto(pbEvt) - a.logger.Debug("Received event from gRPC stream", "event", evt) - a.bus.Send(evt) - } - }) - ui, err := terminal.NewUI(ctx, terminal.Params{ EventBus: a.bus, Dispatcher: func(cmd event.Command) { @@ -99,9 +80,53 @@ func (a *App) Run(ctx context.Context) error { g.Go(func() error { return ui.Run(ctx) }) + // After the UI is available, perform a handshake with the server. + // Ordering is important here. We want to ensure that the UI is ready to + // react to events received from the server. Performing the handshake ensures + // the client has received at least one event. + if err := a.doHandshake(stream); err != nil { + return fmt.Errorf("do handshake: %w", err) + } + + g.Go(func() error { + for { + envelope, recErr := stream.Recv() + if recErr != nil { + return fmt.Errorf("receive envelope: %w", recErr) + } + + pbEvt := envelope.GetEvent() + if pbEvt == nil { + a.logger.Error("Received envelope without event") + continue + } + + evt := protocol.EventFromProto(pbEvt) + a.logger.Debug("Received event from gRPC stream", "event", evt.EventName(), "payload", evt) + a.bus.Send(evt) + } + }) + if err := g.Wait(); err == terminal.ErrUserClosed { return nil } else { return fmt.Errorf("errgroup.Wait: %w", err) } } + +func (a *App) doHandshake(stream pb.InternalAPI_CommunicateClient) error { + env, err := stream.Recv() + if err != nil { + return fmt.Errorf("receive ready event: %w", err) + } + + if evt := env.GetEvent(); evt == nil || evt.GetInternalApiReady() == nil { + return fmt.Errorf("expected ready event but got: %T", env) + } + + if err = stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: &pb.Command{CommandType: &pb.Command_StartInternalStream{}}}}); err != nil { + return fmt.Errorf("send start command: %w", err) + } + + return nil +} diff --git a/internal/client/integration_helpers_test.go b/internal/client/integration_helpers_test.go index 7f5a73c..dc416ef 100644 --- a/internal/client/integration_helpers_test.go +++ b/internal/client/integration_helpers_test.go @@ -49,6 +49,7 @@ func buildClientServer( server := server.New(server.Params{ ConfigService: configService, DockerClient: dockerClient, + WaitForClient: true, Logger: logger, }) diff --git a/internal/client/integration_test.go b/internal/client/integration_test.go index c31b3ea..d4bf0d2 100644 --- a/internal/client/integration_test.go +++ b/internal/client/integration_test.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "errors" "fmt" "net" "os" @@ -16,12 +17,15 @@ import ( "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/container" + "git.netflux.io/rob/octoplex/internal/container/mocks" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/testhelpers" + "github.com/docker/docker/api/types/network" dockerclient "github.com/docker/docker/client" "github.com/docker/docker/errdefs" "github.com/gdamore/tcell/v2" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" @@ -861,6 +865,76 @@ func TestIntegrationMediaServerError(t *testing.T) { assert.ErrorContains(t, result.errServer, "media server exited") } +func TestIntegrationDockerClientError(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) + defer cancel() + + logger := testhelpers.NewTestLogger(t).With("component", "integration") + + var dockerClient mocks.DockerClient + dockerClient.EXPECT().NetworkCreate(mock.Anything, mock.Anything, mock.Anything).Return(network.CreateResponse{}, errors.New("boom")) + + configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) + screen, screenCaptureC, getContents := setupSimulationScreen(t) + + client, server := buildClientServer(configService, &dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) + + require.EventuallyWithT( + t, + func(c *assert.CollectT) { + assert.True(c, contentsIncludes(getContents(), "An error occurred:"), "expected to see error message") + assert.True(c, contentsIncludes(getContents(), "create container client: network create: boom"), "expected to see message") + }, + waitTime, + time.Second, + "expected to see fatal error modal", + ) + printScreen(t, getContents, "Ater displaying the fatal error modal") + + // Quit the app: + sendKey(t, screen, tcell.KeyEnter, ' ') + + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.EqualError(t, result.errServer, "create container client: network create: boom") +} + +func TestIntegrationDockerConnectionError(t *testing.T) { + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Minute) + defer cancel() + + logger := testhelpers.NewTestLogger(t).With("component", "integration") + dockerClient, err := dockerclient.NewClientWithOpts(dockerclient.WithHost("http://docker.example.com")) + require.NoError(t, err) + + configService := setupConfigService(t, config.Config{Sources: config.Sources{MediaServer: config.MediaServerSource{RTMP: config.RTMPSource{Enabled: true}}}}) + screen, screenCaptureC, getContents := setupSimulationScreen(t) + + client, server := buildClientServer(configService, dockerClient, screen, screenCaptureC, logger) + ch := runClientServer(ctx, t, client, server) + + require.EventuallyWithT( + t, + func(c *assert.CollectT) { + assert.True(c, contentsIncludes(getContents(), "An error occurred:"), "expected to see error message") + assert.True(c, contentsIncludes(getContents(), "Could not connect to Docker. Is Docker installed"), "expected to see message") + }, + waitTime, + time.Second, + "expected to see fatal error modal", + ) + printScreen(t, getContents, "Ater displaying the fatal error modal") + + // Quit the app: + sendKey(t, screen, tcell.KeyEnter, ' ') + + result := <-ch + assert.ErrorContains(t, result.errClient, "context canceled") + assert.ErrorContains(t, result.errServer, "dial tcp: lookup docker.example.com") + assert.ErrorContains(t, result.errServer, "no such host") +} + func TestIntegrationCopyURLs(t *testing.T) { type binding struct { key tcell.Key diff --git a/internal/generated/grpc/api.pb.go b/internal/generated/grpc/api.pb.go index 59f1571..dcc1c10 100644 --- a/internal/generated/grpc/api.pb.go +++ b/internal/generated/grpc/api.pb.go @@ -7,12 +7,11 @@ package grpc import ( - reflect "reflect" - sync "sync" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" ) const ( @@ -68,7 +67,7 @@ func (x Destination_Status) Number() protoreflect.EnumNumber { // Deprecated: Use Destination_Status.Descriptor instead. func (Destination_Status) EnumDescriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{11, 0} + return file_api_proto_rawDescGZIP(), []int{12, 0} } type Envelope struct { @@ -165,6 +164,7 @@ type Command struct { // *Command_StopDestination // *Command_CloseOtherInstances // *Command_Quit + // *Command_StartInternalStream CommandType isCommand_CommandType `protobuf_oneof:"command_type"` } @@ -249,6 +249,13 @@ func (x *Command) GetQuit() *QuitCommand { return nil } +func (x *Command) GetStartInternalStream() *StartInternalStreamCommand { + if x, ok := x.GetCommandType().(*Command_StartInternalStream); ok { + return x.StartInternalStream + } + return nil +} + type isCommand_CommandType interface { isCommand_CommandType() } @@ -277,6 +284,10 @@ type Command_Quit struct { Quit *QuitCommand `protobuf:"bytes,6,opt,name=quit,proto3,oneof"` } +type Command_StartInternalStream struct { + StartInternalStream *StartInternalStreamCommand `protobuf:"bytes,7,opt,name=start_internal_stream,json=startInternalStream,proto3,oneof"` +} + func (*Command_AddDestination) isCommand_CommandType() {} func (*Command_RemoveDestination) isCommand_CommandType() {} @@ -289,6 +300,8 @@ func (*Command_CloseOtherInstances) isCommand_CommandType() {} func (*Command_Quit) isCommand_CommandType() {} +func (*Command_StartInternalStream) isCommand_CommandType() {} + type AddDestinationCommand struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -561,6 +574,44 @@ func (*QuitCommand) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{7} } +type StartInternalStreamCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *StartInternalStreamCommand) Reset() { + *x = StartInternalStreamCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StartInternalStreamCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StartInternalStreamCommand) ProtoMessage() {} + +func (x *StartInternalStreamCommand) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StartInternalStreamCommand.ProtoReflect.Descriptor instead. +func (*StartInternalStreamCommand) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{8} +} + type Event struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -578,13 +629,14 @@ type Event struct { // *Event_MediaServerStarted // *Event_OtherInstanceDetected // *Event_FatalError + // *Event_InternalApiReady EventType isEvent_EventType `protobuf_oneof:"event_type"` } func (x *Event) Reset() { *x = Event{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -597,7 +649,7 @@ func (x *Event) String() string { func (*Event) ProtoMessage() {} func (x *Event) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -610,7 +662,7 @@ func (x *Event) ProtoReflect() protoreflect.Message { // Deprecated: Use Event.ProtoReflect.Descriptor instead. func (*Event) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{8} + return file_api_proto_rawDescGZIP(), []int{9} } func (m *Event) GetEventType() isEvent_EventType { @@ -690,6 +742,13 @@ func (x *Event) GetFatalError() *FatalErrorEvent { return nil } +func (x *Event) GetInternalApiReady() *InternalAPIReadyEvent { + if x, ok := x.GetEventType().(*Event_InternalApiReady); ok { + return x.InternalApiReady + } + return nil +} + type isEvent_EventType interface { isEvent_EventType() } @@ -734,6 +793,10 @@ type Event_FatalError struct { FatalError *FatalErrorEvent `protobuf:"bytes,10,opt,name=fatal_error,json=fatalError,proto3,oneof"` } +type Event_InternalApiReady struct { + InternalApiReady *InternalAPIReadyEvent `protobuf:"bytes,11,opt,name=internal_api_ready,json=internalApiReady,proto3,oneof"` +} + func (*Event_AppStateChanged) isEvent_EventType() {} func (*Event_DestinationStreamExited) isEvent_EventType() {} @@ -754,6 +817,8 @@ func (*Event_OtherInstanceDetected) isEvent_EventType() {} func (*Event_FatalError) isEvent_EventType() {} +func (*Event_InternalApiReady) isEvent_EventType() {} + type Container struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -779,7 +844,7 @@ type Container struct { func (x *Container) Reset() { *x = Container{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -792,7 +857,7 @@ func (x *Container) String() string { func (*Container) ProtoMessage() {} func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -805,7 +870,7 @@ func (x *Container) ProtoReflect() protoreflect.Message { // Deprecated: Use Container.ProtoReflect.Descriptor instead. func (*Container) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{9} + return file_api_proto_rawDescGZIP(), []int{10} } func (x *Container) GetId() string { @@ -928,7 +993,7 @@ type Source struct { func (x *Source) Reset() { *x = Source{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[10] + mi := &file_api_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -941,7 +1006,7 @@ func (x *Source) String() string { func (*Source) ProtoMessage() {} func (x *Source) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[10] + mi := &file_api_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -954,7 +1019,7 @@ func (x *Source) ProtoReflect() protoreflect.Message { // Deprecated: Use Source.ProtoReflect.Descriptor instead. func (*Source) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{10} + return file_api_proto_rawDescGZIP(), []int{11} } func (x *Source) GetContainer() *Container { @@ -1006,7 +1071,7 @@ type Destination struct { func (x *Destination) Reset() { *x = Destination{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[11] + mi := &file_api_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1019,7 +1084,7 @@ func (x *Destination) String() string { func (*Destination) ProtoMessage() {} func (x *Destination) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[11] + mi := &file_api_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1032,7 +1097,7 @@ func (x *Destination) ProtoReflect() protoreflect.Message { // Deprecated: Use Destination.ProtoReflect.Descriptor instead. func (*Destination) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{11} + return file_api_proto_rawDescGZIP(), []int{12} } func (x *Destination) GetContainer() *Container { @@ -1077,7 +1142,7 @@ type BuildInfo struct { func (x *BuildInfo) Reset() { *x = BuildInfo{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[12] + mi := &file_api_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1090,7 +1155,7 @@ func (x *BuildInfo) String() string { func (*BuildInfo) ProtoMessage() {} func (x *BuildInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[12] + mi := &file_api_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1103,7 +1168,7 @@ func (x *BuildInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use BuildInfo.ProtoReflect.Descriptor instead. func (*BuildInfo) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{12} + return file_api_proto_rawDescGZIP(), []int{13} } func (x *BuildInfo) GetGoVersion() string { @@ -1147,7 +1212,7 @@ type AppState struct { func (x *AppState) Reset() { *x = AppState{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[13] + mi := &file_api_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1160,7 +1225,7 @@ func (x *AppState) String() string { func (*AppState) ProtoMessage() {} func (x *AppState) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[13] + mi := &file_api_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1173,7 +1238,7 @@ func (x *AppState) ProtoReflect() protoreflect.Message { // Deprecated: Use AppState.ProtoReflect.Descriptor instead. func (*AppState) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{13} + return file_api_proto_rawDescGZIP(), []int{14} } func (x *AppState) GetSource() *Source { @@ -1208,7 +1273,7 @@ type AppStateChangedEvent struct { func (x *AppStateChangedEvent) Reset() { *x = AppStateChangedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[14] + mi := &file_api_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1221,7 +1286,7 @@ func (x *AppStateChangedEvent) String() string { func (*AppStateChangedEvent) ProtoMessage() {} func (x *AppStateChangedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[14] + mi := &file_api_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1234,7 +1299,7 @@ func (x *AppStateChangedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AppStateChangedEvent.ProtoReflect.Descriptor instead. func (*AppStateChangedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{14} + return file_api_proto_rawDescGZIP(), []int{15} } func (x *AppStateChangedEvent) GetAppState() *AppState { @@ -1256,7 +1321,7 @@ type DestinationStreamExitedEvent struct { func (x *DestinationStreamExitedEvent) Reset() { *x = DestinationStreamExitedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[15] + mi := &file_api_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1269,7 +1334,7 @@ func (x *DestinationStreamExitedEvent) String() string { func (*DestinationStreamExitedEvent) ProtoMessage() {} func (x *DestinationStreamExitedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[15] + mi := &file_api_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1282,7 +1347,7 @@ func (x *DestinationStreamExitedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DestinationStreamExitedEvent.ProtoReflect.Descriptor instead. func (*DestinationStreamExitedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{15} + return file_api_proto_rawDescGZIP(), []int{16} } func (x *DestinationStreamExitedEvent) GetName() string { @@ -1310,7 +1375,7 @@ type DestinationAddedEvent struct { func (x *DestinationAddedEvent) Reset() { *x = DestinationAddedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[16] + mi := &file_api_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1323,7 +1388,7 @@ func (x *DestinationAddedEvent) String() string { func (*DestinationAddedEvent) ProtoMessage() {} func (x *DestinationAddedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[16] + mi := &file_api_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1336,7 +1401,7 @@ func (x *DestinationAddedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DestinationAddedEvent.ProtoReflect.Descriptor instead. func (*DestinationAddedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{16} + return file_api_proto_rawDescGZIP(), []int{17} } func (x *DestinationAddedEvent) GetUrl() string { @@ -1358,7 +1423,7 @@ type AddDestinationFailedEvent struct { func (x *AddDestinationFailedEvent) Reset() { *x = AddDestinationFailedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[17] + mi := &file_api_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1371,7 +1436,7 @@ func (x *AddDestinationFailedEvent) String() string { func (*AddDestinationFailedEvent) ProtoMessage() {} func (x *AddDestinationFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[17] + mi := &file_api_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1384,7 +1449,7 @@ func (x *AddDestinationFailedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDestinationFailedEvent.ProtoReflect.Descriptor instead. func (*AddDestinationFailedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{17} + return file_api_proto_rawDescGZIP(), []int{18} } func (x *AddDestinationFailedEvent) GetUrl() string { @@ -1412,7 +1477,7 @@ type DestinationRemovedEvent struct { func (x *DestinationRemovedEvent) Reset() { *x = DestinationRemovedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[18] + mi := &file_api_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1425,7 +1490,7 @@ func (x *DestinationRemovedEvent) String() string { func (*DestinationRemovedEvent) ProtoMessage() {} func (x *DestinationRemovedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[18] + mi := &file_api_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1438,7 +1503,7 @@ func (x *DestinationRemovedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use DestinationRemovedEvent.ProtoReflect.Descriptor instead. func (*DestinationRemovedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{18} + return file_api_proto_rawDescGZIP(), []int{19} } func (x *DestinationRemovedEvent) GetUrl() string { @@ -1460,7 +1525,7 @@ type RemoveDestinationFailedEvent struct { func (x *RemoveDestinationFailedEvent) Reset() { *x = RemoveDestinationFailedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[19] + mi := &file_api_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1473,7 +1538,7 @@ func (x *RemoveDestinationFailedEvent) String() string { func (*RemoveDestinationFailedEvent) ProtoMessage() {} func (x *RemoveDestinationFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[19] + mi := &file_api_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1486,7 +1551,7 @@ func (x *RemoveDestinationFailedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveDestinationFailedEvent.ProtoReflect.Descriptor instead. func (*RemoveDestinationFailedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{19} + return file_api_proto_rawDescGZIP(), []int{20} } func (x *RemoveDestinationFailedEvent) GetUrl() string { @@ -1515,7 +1580,7 @@ type StartDestinationFailedEvent struct { func (x *StartDestinationFailedEvent) Reset() { *x = StartDestinationFailedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[20] + mi := &file_api_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1528,7 +1593,7 @@ func (x *StartDestinationFailedEvent) String() string { func (*StartDestinationFailedEvent) ProtoMessage() {} func (x *StartDestinationFailedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[20] + mi := &file_api_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1541,7 +1606,7 @@ func (x *StartDestinationFailedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use StartDestinationFailedEvent.ProtoReflect.Descriptor instead. func (*StartDestinationFailedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{20} + return file_api_proto_rawDescGZIP(), []int{21} } func (x *StartDestinationFailedEvent) GetUrl() string { @@ -1570,7 +1635,7 @@ type MediaServerStartedEvent struct { func (x *MediaServerStartedEvent) Reset() { *x = MediaServerStartedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[21] + mi := &file_api_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1583,7 +1648,7 @@ func (x *MediaServerStartedEvent) String() string { func (*MediaServerStartedEvent) ProtoMessage() {} func (x *MediaServerStartedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[21] + mi := &file_api_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1596,7 +1661,7 @@ func (x *MediaServerStartedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use MediaServerStartedEvent.ProtoReflect.Descriptor instead. func (*MediaServerStartedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{21} + return file_api_proto_rawDescGZIP(), []int{22} } func (x *MediaServerStartedEvent) GetRtmpUrl() string { @@ -1622,7 +1687,7 @@ type OtherInstanceDetectedEvent struct { func (x *OtherInstanceDetectedEvent) Reset() { *x = OtherInstanceDetectedEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[22] + mi := &file_api_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1635,7 +1700,7 @@ func (x *OtherInstanceDetectedEvent) String() string { func (*OtherInstanceDetectedEvent) ProtoMessage() {} func (x *OtherInstanceDetectedEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[22] + mi := &file_api_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1648,7 +1713,7 @@ func (x *OtherInstanceDetectedEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use OtherInstanceDetectedEvent.ProtoReflect.Descriptor instead. func (*OtherInstanceDetectedEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{22} + return file_api_proto_rawDescGZIP(), []int{23} } type FatalErrorEvent struct { @@ -1662,7 +1727,7 @@ type FatalErrorEvent struct { func (x *FatalErrorEvent) Reset() { *x = FatalErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[23] + mi := &file_api_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1675,7 +1740,7 @@ func (x *FatalErrorEvent) String() string { func (*FatalErrorEvent) ProtoMessage() {} func (x *FatalErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[23] + mi := &file_api_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1688,7 +1753,7 @@ func (x *FatalErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use FatalErrorEvent.ProtoReflect.Descriptor instead. func (*FatalErrorEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{23} + return file_api_proto_rawDescGZIP(), []int{24} } func (x *FatalErrorEvent) GetMessage() string { @@ -1698,6 +1763,44 @@ func (x *FatalErrorEvent) GetMessage() string { return "" } +type InternalAPIReadyEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *InternalAPIReadyEvent) Reset() { + *x = InternalAPIReadyEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *InternalAPIReadyEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InternalAPIReadyEvent) ProtoMessage() {} + +func (x *InternalAPIReadyEvent) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InternalAPIReadyEvent.ProtoReflect.Descriptor instead. +func (*InternalAPIReadyEvent) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{25} +} + var File_api_proto protoreflect.FileDescriptor var file_api_proto_rawDesc = []byte{ @@ -1710,7 +1813,7 @@ var file_api_proto_rawDesc = []byte{ 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x70, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0xc6, 0x03, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x9d, 0x04, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, @@ -1737,198 +1840,211 @@ var file_api_proto_rawDesc = []byte{ 0x48, 0x00, 0x52, 0x13, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x71, 0x75, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x69, 0x74, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x04, 0x71, 0x75, 0x69, 0x74, 0x42, - 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, - 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, - 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2c, - 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x04, 0x71, 0x75, 0x69, 0x74, 0x12, + 0x55, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, + 0x00, 0x52, 0x13, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2c, 0x0a, 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x75, 0x72, 0x6c, 0x22, 0x2b, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x22, 0x2a, 0x0a, 0x16, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2b, 0x0a, 0x17, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x2a, 0x0a, 0x16, 0x53, 0x74, 0x6f, - 0x70, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, - 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x51, 0x75, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x22, 0xd8, 0x06, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, - 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, - 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x69, 0x74, - 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, - 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x13, 0x64, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x72, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x18, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x14, 0x6d, 0x65, 0x64, - 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, + 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x51, 0x75, + 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xa4, 0x07, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x47, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x5f, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x17, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, + 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, + 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, + 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, + 0x5f, 0x0a, 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x12, 0x5c, 0x0a, 0x18, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, + 0x0a, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, 0x0a, 0x17, 0x6f, - 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x65, - 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, - 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x5f, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x42, - 0x0c, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, 0x03, - 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, - 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x70, 0x75, 0x5f, 0x70, 0x65, - 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x63, 0x70, 0x75, - 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, 0x72, - 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, - 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x17, - 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, - 0x06, 0x74, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x73, 0x69, - 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x72, 0x78, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x1d, - 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, - 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, - 0x0a, 0x0d, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x50, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, - 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, - 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, 0x65, - 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, - 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, 0x10, 0x0a, - 0x03, 0x65, 0x72, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x42, - 0x0c, 0x0a, 0x0a, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xc7, 0x01, - 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, + 0x12, 0x59, 0x0a, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, + 0x61, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, + 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4a, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, + 0x50, 0x49, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x70, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, + 0x42, 0x0c, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, + 0x03, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x70, 0x75, 0x5f, 0x70, + 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0a, 0x63, 0x70, + 0x75, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, + 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x78, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x74, 0x78, 0x52, 0x61, 0x74, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x73, + 0x69, 0x6e, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x72, 0x78, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x12, + 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, + 0x0a, 0x0b, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x75, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x23, 0x0a, 0x0d, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x75, 0x6c, 0x6c, 0x50, 0x72, 0x6f, 0x67, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x75, 0x6c, 0x6c, 0x5f, 0x70, 0x65, 0x72, + 0x63, 0x65, 0x6e, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x70, 0x75, 0x6c, 0x6c, + 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x74, 0x61, + 0x72, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, + 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x09, + 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x48, + 0x00, 0x52, 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, 0x10, + 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, + 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x22, 0xc7, + 0x01, 0x0a, 0x06, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x76, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x6c, + 0x69, 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0d, 0x6c, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, + 0x69, 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x0f, 0x6c, 0x69, - 0x76, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x0d, 0x6c, 0x69, 0x76, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, - 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x72, - 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x69, - 0x74, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, - 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x42, 0x0a, 0x06, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, - 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4c, 0x49, 0x56, 0x45, 0x10, 0x02, - 0x22, 0x70, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, - 0x0a, 0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x67, 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, - 0x74, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x0a, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, - 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x42, 0x0a, 0x14, 0x41, 0x70, 0x70, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x2a, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x52, 0x08, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x48, 0x0a, - 0x1c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x75, + 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x42, 0x0a, + 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x4f, 0x46, 0x46, 0x5f, 0x41, 0x49, 0x52, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x01, + 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x4c, 0x49, 0x56, 0x45, 0x10, + 0x02, 0x22, 0x70, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, + 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x08, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2d, 0x0a, 0x0a, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x42, 0x0a, 0x14, 0x41, 0x70, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x52, 0x08, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x22, 0x48, + 0x0a, 0x1c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x29, 0x0a, 0x15, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x75, 0x72, 0x6c, 0x22, 0x43, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x72, 0x6c, 0x22, 0x43, 0x0a, 0x19, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, - 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2b, 0x0a, 0x17, 0x44, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x75, 0x72, 0x6c, 0x22, 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x49, 0x0a, 0x1b, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, - 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x17, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x74, 0x6d, 0x70, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, - 0x09, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x55, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, 0x74, - 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x46, 0x61, 0x74, 0x61, - 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x41, 0x50, 0x49, 0x12, 0x2f, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, - 0x70, 0x65, 0x1a, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, - 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, - 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, - 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x2b, 0x0a, 0x17, 0x44, 0x65, 0x73, 0x74, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x46, 0x0a, 0x1c, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x49, 0x0a, + 0x1b, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x18, + 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x51, 0x0a, 0x17, 0x4d, 0x65, 0x64, 0x69, + 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x74, 0x6d, 0x70, 0x55, 0x72, 0x6c, 0x12, 0x1b, + 0x0a, 0x09, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x72, 0x74, 0x6d, 0x70, 0x73, 0x55, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x4f, + 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x46, 0x61, 0x74, + 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x41, 0x50, 0x49, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x32, + 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x50, 0x49, 0x12, 0x2f, + 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x1a, 0x0d, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, + 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, + 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1944,7 +2060,7 @@ func file_api_proto_rawDescGZIP() []byte { } var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_api_proto_goTypes = []interface{}{ (Destination_Status)(0), // 0: api.Destination.Status (*Envelope)(nil), // 1: api.Envelope @@ -1955,59 +2071,63 @@ var file_api_proto_goTypes = []interface{}{ (*StopDestinationCommand)(nil), // 6: api.StopDestinationCommand (*CloseOtherInstancesCommand)(nil), // 7: api.CloseOtherInstancesCommand (*QuitCommand)(nil), // 8: api.QuitCommand - (*Event)(nil), // 9: api.Event - (*Container)(nil), // 10: api.Container - (*Source)(nil), // 11: api.Source - (*Destination)(nil), // 12: api.Destination - (*BuildInfo)(nil), // 13: api.BuildInfo - (*AppState)(nil), // 14: api.AppState - (*AppStateChangedEvent)(nil), // 15: api.AppStateChangedEvent - (*DestinationStreamExitedEvent)(nil), // 16: api.DestinationStreamExitedEvent - (*DestinationAddedEvent)(nil), // 17: api.DestinationAddedEvent - (*AddDestinationFailedEvent)(nil), // 18: api.AddDestinationFailedEvent - (*DestinationRemovedEvent)(nil), // 19: api.DestinationRemovedEvent - (*RemoveDestinationFailedEvent)(nil), // 20: api.RemoveDestinationFailedEvent - (*StartDestinationFailedEvent)(nil), // 21: api.StartDestinationFailedEvent - (*MediaServerStartedEvent)(nil), // 22: api.MediaServerStartedEvent - (*OtherInstanceDetectedEvent)(nil), // 23: api.OtherInstanceDetectedEvent - (*FatalErrorEvent)(nil), // 24: api.FatalErrorEvent - (*timestamppb.Timestamp)(nil), // 25: google.protobuf.Timestamp + (*StartInternalStreamCommand)(nil), // 9: api.StartInternalStreamCommand + (*Event)(nil), // 10: api.Event + (*Container)(nil), // 11: api.Container + (*Source)(nil), // 12: api.Source + (*Destination)(nil), // 13: api.Destination + (*BuildInfo)(nil), // 14: api.BuildInfo + (*AppState)(nil), // 15: api.AppState + (*AppStateChangedEvent)(nil), // 16: api.AppStateChangedEvent + (*DestinationStreamExitedEvent)(nil), // 17: api.DestinationStreamExitedEvent + (*DestinationAddedEvent)(nil), // 18: api.DestinationAddedEvent + (*AddDestinationFailedEvent)(nil), // 19: api.AddDestinationFailedEvent + (*DestinationRemovedEvent)(nil), // 20: api.DestinationRemovedEvent + (*RemoveDestinationFailedEvent)(nil), // 21: api.RemoveDestinationFailedEvent + (*StartDestinationFailedEvent)(nil), // 22: api.StartDestinationFailedEvent + (*MediaServerStartedEvent)(nil), // 23: api.MediaServerStartedEvent + (*OtherInstanceDetectedEvent)(nil), // 24: api.OtherInstanceDetectedEvent + (*FatalErrorEvent)(nil), // 25: api.FatalErrorEvent + (*InternalAPIReadyEvent)(nil), // 26: api.InternalAPIReadyEvent + (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp } var file_api_proto_depIdxs = []int32{ 2, // 0: api.Envelope.command:type_name -> api.Command - 9, // 1: api.Envelope.event:type_name -> api.Event + 10, // 1: api.Envelope.event:type_name -> api.Event 3, // 2: api.Command.add_destination:type_name -> api.AddDestinationCommand 4, // 3: api.Command.remove_destination:type_name -> api.RemoveDestinationCommand 5, // 4: api.Command.start_destination:type_name -> api.StartDestinationCommand 6, // 5: api.Command.stop_destination:type_name -> api.StopDestinationCommand 7, // 6: api.Command.close_other_instances:type_name -> api.CloseOtherInstancesCommand 8, // 7: api.Command.quit:type_name -> api.QuitCommand - 15, // 8: api.Event.app_state_changed:type_name -> api.AppStateChangedEvent - 16, // 9: api.Event.destination_stream_exited:type_name -> api.DestinationStreamExitedEvent - 17, // 10: api.Event.destination_added:type_name -> api.DestinationAddedEvent - 18, // 11: api.Event.add_destination_failed:type_name -> api.AddDestinationFailedEvent - 19, // 12: api.Event.destination_removed:type_name -> api.DestinationRemovedEvent - 20, // 13: api.Event.remove_destination_failed:type_name -> api.RemoveDestinationFailedEvent - 21, // 14: api.Event.start_destination_failed:type_name -> api.StartDestinationFailedEvent - 22, // 15: api.Event.media_server_started:type_name -> api.MediaServerStartedEvent - 23, // 16: api.Event.other_instance_detected:type_name -> api.OtherInstanceDetectedEvent - 24, // 17: api.Event.fatal_error:type_name -> api.FatalErrorEvent - 25, // 18: api.Container.rx_since:type_name -> google.protobuf.Timestamp - 10, // 19: api.Source.container:type_name -> api.Container - 25, // 20: api.Source.live_changed_at:type_name -> google.protobuf.Timestamp - 10, // 21: api.Destination.container:type_name -> api.Container - 0, // 22: api.Destination.status:type_name -> api.Destination.Status - 11, // 23: api.AppState.source:type_name -> api.Source - 12, // 24: api.AppState.destinations:type_name -> api.Destination - 13, // 25: api.AppState.build_info:type_name -> api.BuildInfo - 14, // 26: api.AppStateChangedEvent.app_state:type_name -> api.AppState - 1, // 27: api.InternalAPI.Communicate:input_type -> api.Envelope - 1, // 28: api.InternalAPI.Communicate:output_type -> api.Envelope - 28, // [28:29] is the sub-list for method output_type - 27, // [27:28] is the sub-list for method input_type - 27, // [27:27] is the sub-list for extension type_name - 27, // [27:27] is the sub-list for extension extendee - 0, // [0:27] is the sub-list for field type_name + 9, // 8: api.Command.start_internal_stream:type_name -> api.StartInternalStreamCommand + 16, // 9: api.Event.app_state_changed:type_name -> api.AppStateChangedEvent + 17, // 10: api.Event.destination_stream_exited:type_name -> api.DestinationStreamExitedEvent + 18, // 11: api.Event.destination_added:type_name -> api.DestinationAddedEvent + 19, // 12: api.Event.add_destination_failed:type_name -> api.AddDestinationFailedEvent + 20, // 13: api.Event.destination_removed:type_name -> api.DestinationRemovedEvent + 21, // 14: api.Event.remove_destination_failed:type_name -> api.RemoveDestinationFailedEvent + 22, // 15: api.Event.start_destination_failed:type_name -> api.StartDestinationFailedEvent + 23, // 16: api.Event.media_server_started:type_name -> api.MediaServerStartedEvent + 24, // 17: api.Event.other_instance_detected:type_name -> api.OtherInstanceDetectedEvent + 25, // 18: api.Event.fatal_error:type_name -> api.FatalErrorEvent + 26, // 19: api.Event.internal_api_ready:type_name -> api.InternalAPIReadyEvent + 27, // 20: api.Container.rx_since:type_name -> google.protobuf.Timestamp + 11, // 21: api.Source.container:type_name -> api.Container + 27, // 22: api.Source.live_changed_at:type_name -> google.protobuf.Timestamp + 11, // 23: api.Destination.container:type_name -> api.Container + 0, // 24: api.Destination.status:type_name -> api.Destination.Status + 12, // 25: api.AppState.source:type_name -> api.Source + 13, // 26: api.AppState.destinations:type_name -> api.Destination + 14, // 27: api.AppState.build_info:type_name -> api.BuildInfo + 15, // 28: api.AppStateChangedEvent.app_state:type_name -> api.AppState + 1, // 29: api.InternalAPI.Communicate:input_type -> api.Envelope + 1, // 30: api.InternalAPI.Communicate:output_type -> api.Envelope + 30, // [30:31] is the sub-list for method output_type + 29, // [29:30] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name } func init() { file_api_proto_init() } @@ -2113,7 +2233,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Event); i { + switch v := v.(*StartInternalStreamCommand); i { case 0: return &v.state case 1: @@ -2125,7 +2245,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Container); i { + switch v := v.(*Event); i { case 0: return &v.state case 1: @@ -2137,7 +2257,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Source); i { + switch v := v.(*Container); i { case 0: return &v.state case 1: @@ -2149,7 +2269,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Destination); i { + switch v := v.(*Source); i { case 0: return &v.state case 1: @@ -2161,7 +2281,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BuildInfo); i { + switch v := v.(*Destination); i { case 0: return &v.state case 1: @@ -2173,7 +2293,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AppState); i { + switch v := v.(*BuildInfo); i { case 0: return &v.state case 1: @@ -2185,7 +2305,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AppStateChangedEvent); i { + switch v := v.(*AppState); i { case 0: return &v.state case 1: @@ -2197,7 +2317,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DestinationStreamExitedEvent); i { + switch v := v.(*AppStateChangedEvent); i { case 0: return &v.state case 1: @@ -2209,7 +2329,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DestinationAddedEvent); i { + switch v := v.(*DestinationStreamExitedEvent); i { case 0: return &v.state case 1: @@ -2221,7 +2341,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddDestinationFailedEvent); i { + switch v := v.(*DestinationAddedEvent); i { case 0: return &v.state case 1: @@ -2233,7 +2353,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DestinationRemovedEvent); i { + switch v := v.(*AddDestinationFailedEvent); i { case 0: return &v.state case 1: @@ -2245,7 +2365,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveDestinationFailedEvent); i { + switch v := v.(*DestinationRemovedEvent); i { case 0: return &v.state case 1: @@ -2257,7 +2377,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartDestinationFailedEvent); i { + switch v := v.(*RemoveDestinationFailedEvent); i { case 0: return &v.state case 1: @@ -2269,7 +2389,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MediaServerStartedEvent); i { + switch v := v.(*StartDestinationFailedEvent); i { case 0: return &v.state case 1: @@ -2281,7 +2401,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OtherInstanceDetectedEvent); i { + switch v := v.(*MediaServerStartedEvent); i { case 0: return &v.state case 1: @@ -2293,6 +2413,18 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OtherInstanceDetectedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*FatalErrorEvent); i { case 0: return &v.state @@ -2304,6 +2436,18 @@ func file_api_proto_init() { return nil } } + file_api_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*InternalAPIReadyEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_api_proto_msgTypes[0].OneofWrappers = []interface{}{ (*Envelope_Command)(nil), @@ -2316,8 +2460,9 @@ func file_api_proto_init() { (*Command_StopDestination)(nil), (*Command_CloseOtherInstances)(nil), (*Command_Quit)(nil), + (*Command_StartInternalStream)(nil), } - file_api_proto_msgTypes[8].OneofWrappers = []interface{}{ + file_api_proto_msgTypes[9].OneofWrappers = []interface{}{ (*Event_AppStateChanged)(nil), (*Event_DestinationStreamExited)(nil), (*Event_DestinationAdded)(nil), @@ -2328,15 +2473,16 @@ func file_api_proto_init() { (*Event_MediaServerStarted)(nil), (*Event_OtherInstanceDetected)(nil), (*Event_FatalError)(nil), + (*Event_InternalApiReady)(nil), } - file_api_proto_msgTypes[9].OneofWrappers = []interface{}{} + file_api_proto_msgTypes[10].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_proto_rawDesc, NumEnums: 1, - NumMessages: 24, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/server/grpc.go b/internal/server/grpc.go index 1c68bba..16d1f7c 100644 --- a/internal/server/grpc.go +++ b/internal/server/grpc.go @@ -7,6 +7,7 @@ import ( "io" "log/slog" "sync" + "time" "git.netflux.io/rob/octoplex/internal/event" pb "git.netflux.io/rob/octoplex/internal/generated/grpc" @@ -29,6 +30,7 @@ type Server struct { mu sync.Mutex clientCount int + clientC chan struct{} } // newServer creates a new gRPC server. @@ -40,6 +42,7 @@ func newServer( return &Server{ dispatcher: dispatcher, bus: bus, + clientC: make(chan struct{}, 1), logger: logger.With("component", "server"), } } @@ -47,6 +50,24 @@ func newServer( func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error { g, ctx := errgroup.WithContext(stream.Context()) + // perform handshake: + if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: &pb.Event{EventType: &pb.Event_InternalApiReady{}}}}); err != nil { + return fmt.Errorf("send ready event: %w", err) + } + startStreamCmd, err := stream.Recv() + if err != nil { + return fmt.Errorf("receive start stream command: %w", err) + } + if startStreamCmd.GetCommand() == nil || startStreamCmd.GetCommand().GetStartInternalStream() == nil { + return fmt.Errorf("expected start stream command but got: %T", startStreamCmd) + } + + // Notify that a client has connected and completed the handshake. + select { + case s.clientC <- struct{}{}: + default: + } + g.Go(func() error { eventsC := s.bus.Register() defer s.bus.Deregister(eventsC) @@ -113,3 +134,18 @@ func (s *Server) GetClientCount() int { return s.clientCount } + +const waitForClientTimeout = 10 * time.Second + +// WaitForClient waits for _any_ client to connect and complete the handshake. +// It times out if no client has connected after 10 seconds. +func (s *Server) WaitForClient(ctx context.Context) error { + select { + case <-s.clientC: + return nil + case <-time.After(waitForClientTimeout): + return errors.New("timeout") + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/internal/server/protocol.go b/internal/server/protocol.go deleted file mode 100644 index abb4e43..0000000 --- a/internal/server/protocol.go +++ /dev/null @@ -1 +0,0 @@ -package server diff --git a/internal/server/serverapp.go b/internal/server/serverapp.go index 7e1f63d..675c241 100644 --- a/internal/server/serverapp.go +++ b/internal/server/serverapp.go @@ -29,6 +29,7 @@ type App struct { eventBus *event.Bus dispatchC chan event.Command dockerClient container.DockerClient + waitForClient bool logger *slog.Logger } @@ -38,6 +39,7 @@ type Params struct { DockerClient container.DockerClient ChanSize int ConfigFilePath string + WaitForClient bool Logger *slog.Logger } @@ -52,6 +54,7 @@ func New(params Params) *App { eventBus: event.NewBus(params.Logger.With("component", "event_bus")), dispatchC: make(chan event.Command, cmp.Or(params.ChanSize, defaultChanSize)), dockerClient: params.DockerClient, + waitForClient: params.WaitForClient, logger: params.Logger, } } @@ -83,6 +86,12 @@ func (a *App) Run(ctx context.Context) error { grpcDone <- grpcServer.Serve(lis) }() + if a.waitForClient { + if err = internalAPI.WaitForClient(ctx); err != nil { + return fmt.Errorf("wait for client: %w", err) + } + } + // emptyUI is a dummy function that sets the UI state to an empty state, and // re-renders the screen. // diff --git a/main.go b/main.go index fea522d..4696a94 100644 --- a/main.go +++ b/main.go @@ -32,12 +32,16 @@ var ( date string ) +// errInterrupt is an error type that indicates an interrupt signal was +// received. type errInterrupt struct{} +// Error implements the error interface. func (e errInterrupt) Error() string { return "interrupt signal received" } +// ExitCode implements the ExitCoder interface. func (e errInterrupt) ExitCode() int { return 130 } @@ -50,7 +54,6 @@ func main() { { Name: "client", Usage: "Run the client", - Flags: []cli.Flag{ /* client flags */ }, Action: func(c *cli.Context) error { return runClient(c.Context, c) }, @@ -58,15 +61,17 @@ func main() { { Name: "server", Usage: "Run the server", - Flags: []cli.Flag{ /* server flags */ }, Action: func(c *cli.Context) error { - return runServer(c.Context, c, serverConfig{stderrAvailable: true, handleSigInt: true}) + return runServer(c.Context, c, serverConfig{ + stderrAvailable: true, + handleSigInt: true, + waitForClient: false, + }) }, }, { Name: "run", Usage: "Run server and client together (testing)", - Flags: []cli.Flag{ /* optional combined flags */ }, Action: func(c *cli.Context) error { return runClientAndServer(c) }, @@ -124,6 +129,7 @@ func runClient(ctx context.Context, _ *cli.Context) error { type serverConfig struct { stderrAvailable bool handleSigInt bool + waitForClient bool } func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) error { @@ -187,6 +193,7 @@ func runServer(ctx context.Context, _ *cli.Context, serverCfg serverConfig) erro ConfigService: configService, DockerClient: dockerClient, ConfigFilePath: configService.Path(), + WaitForClient: serverCfg.waitForClient, Logger: logger, }) @@ -226,7 +233,11 @@ func runClientAndServer(c *cli.Context) error { }) g.Go(func() error { - if err := runServer(ctx, c, serverConfig{stderrAvailable: false, handleSigInt: false}); err != nil { + if err := runServer(ctx, c, serverConfig{ + stderrAvailable: false, + handleSigInt: false, + waitForClient: true, + }); err != nil { return err } diff --git a/proto/api.proto b/proto/api.proto index c3fba15..c059881 100644 --- a/proto/api.proto +++ b/proto/api.proto @@ -25,6 +25,7 @@ message Command { StopDestinationCommand stop_destination = 4; CloseOtherInstancesCommand close_other_instances = 5; QuitCommand quit = 6; + StartInternalStreamCommand start_internal_stream = 7; } } @@ -49,6 +50,8 @@ message CloseOtherInstancesCommand {} message QuitCommand {} +message StartInternalStreamCommand {} + message Event { oneof event_type { AppStateChangedEvent app_state_changed = 1; @@ -61,6 +64,7 @@ message Event { MediaServerStartedEvent media_server_started = 8; OtherInstanceDetectedEvent other_instance_detected = 9; FatalErrorEvent fatal_error = 10; + InternalAPIReadyEvent internal_api_ready = 11; } } @@ -158,3 +162,5 @@ message OtherInstanceDetectedEvent {} message FatalErrorEvent { string message = 1; } + +message InternalAPIReadyEvent {} -- 2.47.2 From f006187894c8ae61d0a8cfe56ba2bcac2f56e5e3 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Tue, 13 May 2025 07:17:05 +0200 Subject: [PATCH 23/23] fixup! wip: refactor: API --- internal/client/clientapp.go | 15 +- internal/generated/grpc/api.pb.go | 227 +++++++++++++++--------------- internal/server/grpc.go | 14 +- proto/api.proto | 8 +- 4 files changed, 131 insertions(+), 133 deletions(-) diff --git a/internal/client/clientapp.go b/internal/client/clientapp.go index c291209..3352506 100644 --- a/internal/client/clientapp.go +++ b/internal/client/clientapp.go @@ -115,17 +115,16 @@ func (a *App) Run(ctx context.Context) error { } func (a *App) doHandshake(stream pb.InternalAPI_CommunicateClient) error { + if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: &pb.Command{CommandType: &pb.Command_StartHandshake{}}}}); err != nil { + return fmt.Errorf("send start handshake command: %w", err) + } + env, err := stream.Recv() if err != nil { - return fmt.Errorf("receive ready event: %w", err) + return fmt.Errorf("receive handshake completed event: %w", err) } - - if evt := env.GetEvent(); evt == nil || evt.GetInternalApiReady() == nil { - return fmt.Errorf("expected ready event but got: %T", env) - } - - if err = stream.Send(&pb.Envelope{Payload: &pb.Envelope_Command{Command: &pb.Command{CommandType: &pb.Command_StartInternalStream{}}}}); err != nil { - return fmt.Errorf("send start command: %w", err) + if evt := env.GetEvent(); evt == nil || evt.GetHandshakeCompleted() == nil { + return fmt.Errorf("expected handshake completed event but got: %T", env) } return nil diff --git a/internal/generated/grpc/api.pb.go b/internal/generated/grpc/api.pb.go index dcc1c10..58929f7 100644 --- a/internal/generated/grpc/api.pb.go +++ b/internal/generated/grpc/api.pb.go @@ -164,7 +164,7 @@ type Command struct { // *Command_StopDestination // *Command_CloseOtherInstances // *Command_Quit - // *Command_StartInternalStream + // *Command_StartHandshake CommandType isCommand_CommandType `protobuf_oneof:"command_type"` } @@ -249,9 +249,9 @@ func (x *Command) GetQuit() *QuitCommand { return nil } -func (x *Command) GetStartInternalStream() *StartInternalStreamCommand { - if x, ok := x.GetCommandType().(*Command_StartInternalStream); ok { - return x.StartInternalStream +func (x *Command) GetStartHandshake() *StartHandshakeCommand { + if x, ok := x.GetCommandType().(*Command_StartHandshake); ok { + return x.StartHandshake } return nil } @@ -284,8 +284,8 @@ type Command_Quit struct { Quit *QuitCommand `protobuf:"bytes,6,opt,name=quit,proto3,oneof"` } -type Command_StartInternalStream struct { - StartInternalStream *StartInternalStreamCommand `protobuf:"bytes,7,opt,name=start_internal_stream,json=startInternalStream,proto3,oneof"` +type Command_StartHandshake struct { + StartHandshake *StartHandshakeCommand `protobuf:"bytes,7,opt,name=start_handshake,json=startHandshake,proto3,oneof"` } func (*Command_AddDestination) isCommand_CommandType() {} @@ -300,7 +300,7 @@ func (*Command_CloseOtherInstances) isCommand_CommandType() {} func (*Command_Quit) isCommand_CommandType() {} -func (*Command_StartInternalStream) isCommand_CommandType() {} +func (*Command_StartHandshake) isCommand_CommandType() {} type AddDestinationCommand struct { state protoimpl.MessageState @@ -574,14 +574,14 @@ func (*QuitCommand) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{7} } -type StartInternalStreamCommand struct { +type StartHandshakeCommand struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } -func (x *StartInternalStreamCommand) Reset() { - *x = StartInternalStreamCommand{} +func (x *StartHandshakeCommand) Reset() { + *x = StartHandshakeCommand{} if protoimpl.UnsafeEnabled { mi := &file_api_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -589,13 +589,13 @@ func (x *StartInternalStreamCommand) Reset() { } } -func (x *StartInternalStreamCommand) String() string { +func (x *StartHandshakeCommand) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartInternalStreamCommand) ProtoMessage() {} +func (*StartHandshakeCommand) ProtoMessage() {} -func (x *StartInternalStreamCommand) ProtoReflect() protoreflect.Message { +func (x *StartHandshakeCommand) ProtoReflect() protoreflect.Message { mi := &file_api_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -607,8 +607,8 @@ func (x *StartInternalStreamCommand) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartInternalStreamCommand.ProtoReflect.Descriptor instead. -func (*StartInternalStreamCommand) Descriptor() ([]byte, []int) { +// Deprecated: Use StartHandshakeCommand.ProtoReflect.Descriptor instead. +func (*StartHandshakeCommand) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{8} } @@ -629,7 +629,7 @@ type Event struct { // *Event_MediaServerStarted // *Event_OtherInstanceDetected // *Event_FatalError - // *Event_InternalApiReady + // *Event_HandshakeCompleted EventType isEvent_EventType `protobuf_oneof:"event_type"` } @@ -742,9 +742,9 @@ func (x *Event) GetFatalError() *FatalErrorEvent { return nil } -func (x *Event) GetInternalApiReady() *InternalAPIReadyEvent { - if x, ok := x.GetEventType().(*Event_InternalApiReady); ok { - return x.InternalApiReady +func (x *Event) GetHandshakeCompleted() *HandshakeCompletedEvent { + if x, ok := x.GetEventType().(*Event_HandshakeCompleted); ok { + return x.HandshakeCompleted } return nil } @@ -793,8 +793,8 @@ type Event_FatalError struct { FatalError *FatalErrorEvent `protobuf:"bytes,10,opt,name=fatal_error,json=fatalError,proto3,oneof"` } -type Event_InternalApiReady struct { - InternalApiReady *InternalAPIReadyEvent `protobuf:"bytes,11,opt,name=internal_api_ready,json=internalApiReady,proto3,oneof"` +type Event_HandshakeCompleted struct { + HandshakeCompleted *HandshakeCompletedEvent `protobuf:"bytes,11,opt,name=handshake_completed,json=handshakeCompleted,proto3,oneof"` } func (*Event_AppStateChanged) isEvent_EventType() {} @@ -817,7 +817,7 @@ func (*Event_OtherInstanceDetected) isEvent_EventType() {} func (*Event_FatalError) isEvent_EventType() {} -func (*Event_InternalApiReady) isEvent_EventType() {} +func (*Event_HandshakeCompleted) isEvent_EventType() {} type Container struct { state protoimpl.MessageState @@ -1763,14 +1763,14 @@ func (x *FatalErrorEvent) GetMessage() string { return "" } -type InternalAPIReadyEvent struct { +type HandshakeCompletedEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } -func (x *InternalAPIReadyEvent) Reset() { - *x = InternalAPIReadyEvent{} +func (x *HandshakeCompletedEvent) Reset() { + *x = HandshakeCompletedEvent{} if protoimpl.UnsafeEnabled { mi := &file_api_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1778,13 +1778,13 @@ func (x *InternalAPIReadyEvent) Reset() { } } -func (x *InternalAPIReadyEvent) String() string { +func (x *HandshakeCompletedEvent) String() string { return protoimpl.X.MessageStringOf(x) } -func (*InternalAPIReadyEvent) ProtoMessage() {} +func (*HandshakeCompletedEvent) ProtoMessage() {} -func (x *InternalAPIReadyEvent) ProtoReflect() protoreflect.Message { +func (x *HandshakeCompletedEvent) ProtoReflect() protoreflect.Message { mi := &file_api_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -1796,8 +1796,8 @@ func (x *InternalAPIReadyEvent) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use InternalAPIReadyEvent.ProtoReflect.Descriptor instead. -func (*InternalAPIReadyEvent) Descriptor() ([]byte, []int) { +// Deprecated: Use HandshakeCompletedEvent.ProtoReflect.Descriptor instead. +func (*HandshakeCompletedEvent) Descriptor() ([]byte, []int) { return file_api_proto_rawDescGZIP(), []int{25} } @@ -1813,7 +1813,7 @@ var file_api_proto_rawDesc = []byte{ 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x0a, 0x07, 0x70, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x9d, 0x04, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x8d, 0x04, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x45, 0x0a, 0x0f, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, @@ -1841,12 +1841,11 @@ var file_api_proto_rawDesc = []byte{ 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x71, 0x75, 0x69, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x51, 0x75, 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x04, 0x71, 0x75, 0x69, 0x74, 0x12, - 0x55, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, - 0x00, 0x52, 0x13, 0x73, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x45, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, + 0x6b, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x48, 0x61, 0x6e, + 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3d, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, @@ -1862,66 +1861,66 @@ var file_api_proto_rawDesc = []byte{ 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x22, 0x1c, 0x0a, 0x1a, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x0d, 0x0a, 0x0b, 0x51, 0x75, - 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x74, 0x61, - 0x72, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xa4, 0x07, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x47, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x5f, 0x65, 0x78, 0x69, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x48, 0x00, 0x52, 0x17, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, - 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, - 0x0a, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, - 0x5f, 0x0a, 0x19, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, - 0x12, 0x5c, 0x0a, 0x18, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, - 0x0a, 0x14, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, - 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, + 0x69, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x22, 0xa9, 0x07, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, + 0x61, 0x70, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x70, + 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x53, 0x74, 0x61, 0x74, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x78, 0x69, 0x74, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x17, 0x64, + 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x45, 0x78, 0x69, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x11, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x10, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x64, 0x64, 0x65, + 0x64, 0x12, 0x56, 0x0a, 0x16, 0x61, 0x64, 0x64, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x14, 0x61, 0x64, 0x64, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x13, 0x64, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x73, + 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x12, 0x5f, 0x0a, 0x19, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x48, 0x00, 0x52, 0x17, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x18, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, + 0x00, 0x52, 0x16, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x14, 0x6d, 0x65, 0x64, + 0x69, 0x61, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, - 0x12, 0x59, 0x0a, 0x17, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x48, 0x00, 0x52, 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, - 0x61, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4a, 0x0a, 0x12, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x5f, 0x61, 0x70, 0x69, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, - 0x50, 0x49, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x70, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, 0x0a, 0x17, 0x6f, + 0x74, 0x68, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, 0x0b, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x4f, 0x0a, 0x13, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x5f, 0x63, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x12, 0x68, 0x61, + 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0xfd, 0x03, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, @@ -2035,16 +2034,16 @@ var file_api_proto_rawDesc = []byte{ 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x2b, 0x0a, 0x0f, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x17, 0x0a, 0x15, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x41, 0x50, 0x49, 0x52, 0x65, 0x61, 0x64, 0x79, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x32, - 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x50, 0x49, 0x12, 0x2f, - 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, - 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x1a, 0x0d, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, - 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, 0x2e, 0x69, - 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, 0x2f, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, - 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, + 0x61, 0x6b, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x32, 0x3e, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x50, 0x49, + 0x12, 0x2f, 0x0a, 0x0b, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, + 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x1a, 0x0d, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x6e, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x28, 0x01, 0x30, + 0x01, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x2e, 0x6e, 0x65, 0x74, 0x66, 0x6c, 0x75, 0x78, + 0x2e, 0x69, 0x6f, 0x2f, 0x72, 0x6f, 0x62, 0x2f, 0x6f, 0x63, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x78, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2071,7 +2070,7 @@ var file_api_proto_goTypes = []interface{}{ (*StopDestinationCommand)(nil), // 6: api.StopDestinationCommand (*CloseOtherInstancesCommand)(nil), // 7: api.CloseOtherInstancesCommand (*QuitCommand)(nil), // 8: api.QuitCommand - (*StartInternalStreamCommand)(nil), // 9: api.StartInternalStreamCommand + (*StartHandshakeCommand)(nil), // 9: api.StartHandshakeCommand (*Event)(nil), // 10: api.Event (*Container)(nil), // 11: api.Container (*Source)(nil), // 12: api.Source @@ -2088,7 +2087,7 @@ var file_api_proto_goTypes = []interface{}{ (*MediaServerStartedEvent)(nil), // 23: api.MediaServerStartedEvent (*OtherInstanceDetectedEvent)(nil), // 24: api.OtherInstanceDetectedEvent (*FatalErrorEvent)(nil), // 25: api.FatalErrorEvent - (*InternalAPIReadyEvent)(nil), // 26: api.InternalAPIReadyEvent + (*HandshakeCompletedEvent)(nil), // 26: api.HandshakeCompletedEvent (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp } var file_api_proto_depIdxs = []int32{ @@ -2100,7 +2099,7 @@ var file_api_proto_depIdxs = []int32{ 6, // 5: api.Command.stop_destination:type_name -> api.StopDestinationCommand 7, // 6: api.Command.close_other_instances:type_name -> api.CloseOtherInstancesCommand 8, // 7: api.Command.quit:type_name -> api.QuitCommand - 9, // 8: api.Command.start_internal_stream:type_name -> api.StartInternalStreamCommand + 9, // 8: api.Command.start_handshake:type_name -> api.StartHandshakeCommand 16, // 9: api.Event.app_state_changed:type_name -> api.AppStateChangedEvent 17, // 10: api.Event.destination_stream_exited:type_name -> api.DestinationStreamExitedEvent 18, // 11: api.Event.destination_added:type_name -> api.DestinationAddedEvent @@ -2111,7 +2110,7 @@ var file_api_proto_depIdxs = []int32{ 23, // 16: api.Event.media_server_started:type_name -> api.MediaServerStartedEvent 24, // 17: api.Event.other_instance_detected:type_name -> api.OtherInstanceDetectedEvent 25, // 18: api.Event.fatal_error:type_name -> api.FatalErrorEvent - 26, // 19: api.Event.internal_api_ready:type_name -> api.InternalAPIReadyEvent + 26, // 19: api.Event.handshake_completed:type_name -> api.HandshakeCompletedEvent 27, // 20: api.Container.rx_since:type_name -> google.protobuf.Timestamp 11, // 21: api.Source.container:type_name -> api.Container 27, // 22: api.Source.live_changed_at:type_name -> google.protobuf.Timestamp @@ -2233,7 +2232,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartInternalStreamCommand); i { + switch v := v.(*StartHandshakeCommand); i { case 0: return &v.state case 1: @@ -2437,7 +2436,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InternalAPIReadyEvent); i { + switch v := v.(*HandshakeCompletedEvent); i { case 0: return &v.state case 1: @@ -2460,7 +2459,7 @@ func file_api_proto_init() { (*Command_StopDestination)(nil), (*Command_CloseOtherInstances)(nil), (*Command_Quit)(nil), - (*Command_StartInternalStream)(nil), + (*Command_StartHandshake)(nil), } file_api_proto_msgTypes[9].OneofWrappers = []interface{}{ (*Event_AppStateChanged)(nil), @@ -2473,7 +2472,7 @@ func file_api_proto_init() { (*Event_MediaServerStarted)(nil), (*Event_OtherInstanceDetected)(nil), (*Event_FatalError)(nil), - (*Event_InternalApiReady)(nil), + (*Event_HandshakeCompleted)(nil), } file_api_proto_msgTypes[10].OneofWrappers = []interface{}{} type x struct{} diff --git a/internal/server/grpc.go b/internal/server/grpc.go index 16d1f7c..bcc9dbf 100644 --- a/internal/server/grpc.go +++ b/internal/server/grpc.go @@ -51,15 +51,15 @@ func (s *Server) Communicate(stream pb.InternalAPI_CommunicateServer) error { g, ctx := errgroup.WithContext(stream.Context()) // perform handshake: - if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: &pb.Event{EventType: &pb.Event_InternalApiReady{}}}}); err != nil { - return fmt.Errorf("send ready event: %w", err) - } - startStreamCmd, err := stream.Recv() + startHandshakeCmd, err := stream.Recv() if err != nil { - return fmt.Errorf("receive start stream command: %w", err) + return fmt.Errorf("receive start handshake command: %w", err) } - if startStreamCmd.GetCommand() == nil || startStreamCmd.GetCommand().GetStartInternalStream() == nil { - return fmt.Errorf("expected start stream command but got: %T", startStreamCmd) + if startHandshakeCmd.GetCommand() == nil || startHandshakeCmd.GetCommand().GetStartHandshake() == nil { + return fmt.Errorf("expected start handshake command but got: %T", startHandshakeCmd) + } + if err := stream.Send(&pb.Envelope{Payload: &pb.Envelope_Event{Event: &pb.Event{EventType: &pb.Event_HandshakeCompleted{}}}}); err != nil { + return fmt.Errorf("send handshake completed event: %w", err) } // Notify that a client has connected and completed the handshake. diff --git a/proto/api.proto b/proto/api.proto index c059881..67d25ca 100644 --- a/proto/api.proto +++ b/proto/api.proto @@ -25,7 +25,7 @@ message Command { StopDestinationCommand stop_destination = 4; CloseOtherInstancesCommand close_other_instances = 5; QuitCommand quit = 6; - StartInternalStreamCommand start_internal_stream = 7; + StartHandshakeCommand start_handshake = 7; } } @@ -50,7 +50,7 @@ message CloseOtherInstancesCommand {} message QuitCommand {} -message StartInternalStreamCommand {} +message StartHandshakeCommand {}; message Event { oneof event_type { @@ -64,7 +64,7 @@ message Event { MediaServerStartedEvent media_server_started = 8; OtherInstanceDetectedEvent other_instance_detected = 9; FatalErrorEvent fatal_error = 10; - InternalAPIReadyEvent internal_api_ready = 11; + HandshakeCompletedEvent handshake_completed = 11; } } @@ -163,4 +163,4 @@ message FatalErrorEvent { string message = 1; } -message InternalAPIReadyEvent {} +message HandshakeCompletedEvent {} -- 2.47.2