From d5a7d1d9a416b5a83daecb57f2f66da0cd5faaf0 Mon Sep 17 00:00:00 2001 From: Marvin Preuss Date: Thu, 5 Aug 2021 09:43:35 +0200 Subject: [PATCH] wip: timer --- .drone.yml | 13 +- api/proto/v1/schnutibox.proto | 23 ++ assets/web/swagger-ui/schnutibox.swagger.json | 68 ++++++ buf.lock | 6 +- cmd/root.go | 36 +++- cmd/timer.go | 13 ++ internal/metrics/metrics.go | 11 + pkg/api/v1/schnutibox.pb.go | 198 ++++++++++++++--- pkg/api/v1/schnutibox.pb.gw.go | 200 ++++++++++++++++++ pkg/api/v1/schnutibox_grpc.pb.go | 120 +++++++++++ pkg/mpc/mpc.go | 43 +--- pkg/run/run.go | 73 ++++--- pkg/timer/timer.go | 45 ++++ pkg/watcher/watcher.go | 58 +++++ pkg/web/web.go | 30 ++- scripts/putio/main.go | 3 + 16 files changed, 819 insertions(+), 121 deletions(-) create mode 100644 cmd/timer.go create mode 100644 pkg/timer/timer.go create mode 100644 pkg/watcher/watcher.go create mode 100644 scripts/putio/main.go diff --git a/.drone.yml b/.drone.yml index 0c0882d..cf82083 100644 --- a/.drone.yml +++ b/.drone.yml @@ -33,12 +33,13 @@ steps: - name: swap path: /SWAP commands: - - apk add --no-cache openssh-client - - wget https://raw.githubusercontent.com/xsteadfastx/docker-qemu-alpine/main/ssh - - chmod 600 ssh + - apk add --no-cache openssh-client curl + - mkdir ~/.ssh + - curl https://raw.githubusercontent.com/xsteadfastx/docker-qemu-alpine/main/ssh -o ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa - wget -O /usr/local/bin/don https://git.xsfx.dev/attachments/8f8f4dbb-8254-448a-a549-552f8b96cb26 - chmod +x /usr/local/bin/don - - don -t 15m -r 15s -c "ssh -i ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@qemu-alpine" + - don -t 15m -r 15s -c "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@qemu-alpine" - cat scripts/rpi-image-test/build.sh | ssh -i ssh -o StrictHostKeyChecking=no root@qemu-alpine - ssh -i ssh -o StrictHostKeyChecking=no root@qemu-alpine poweroff - ls -lah /SWAP @@ -55,8 +56,6 @@ steps: - make test-integration depends_on: - test - - docker - - qemu-alpine - create-image - name: create-torrent @@ -108,8 +107,6 @@ steps: depends_on: - test - lint - - prepare-image - - docker when: event: - tag diff --git a/api/proto/v1/schnutibox.proto b/api/proto/v1/schnutibox.proto index d8991b3..c2b0ca7 100644 --- a/api/proto/v1/schnutibox.proto +++ b/api/proto/v1/schnutibox.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package schnutibox.v1; option go_package = "go.xsfx.dev/schnutibox/pkg/api/v1"; import "google/api/annotations.proto"; +import "google/protobuf/duration.proto"; service IdentifierService { rpc Identify (IdentifyRequest) returns (IdentifyResponse) { @@ -20,3 +21,25 @@ message IdentifyResponse { string name = 1; repeated string uris = 2; } + +service TimerService { + rpc Create(Timer) returns (Timer) { + option (google.api.http) = { + post: "/v1/timer/set" + body: "*" + }; + } + + rpc Get(TimerEmpty) returns (Timer) { + option (google.api.http) = { + get: "/v1/timer/get" + }; + } +} + +message Timer { + google.protobuf.Duration duration = 1; + google.protobuf.Duration current = 2; +} + +message TimerEmpty {} diff --git a/assets/web/swagger-ui/schnutibox.swagger.json b/assets/web/swagger-ui/schnutibox.swagger.json index 6db4a42..426f04a 100644 --- a/assets/web/swagger-ui/schnutibox.swagger.json +++ b/assets/web/swagger-ui/schnutibox.swagger.json @@ -7,6 +7,9 @@ "tags": [ { "name": "IdentifierService" + }, + { + "name": "TimerService" } ], "consumes": [ @@ -47,6 +50,60 @@ "IdentifierService" ] } + }, + "/v1/timer/get": { + "get": { + "operationId": "TimerService_Get", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1Timer" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "TimerService" + ] + } + }, + "/v1/timer/set": { + "post": { + "operationId": "TimerService_Create", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1Timer" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1Timer" + } + } + ], + "tags": [ + "TimerService" + ] + } } }, "definitions": { @@ -101,6 +158,17 @@ } } } + }, + "v1Timer": { + "type": "object", + "properties": { + "duration": { + "type": "string" + }, + "current": { + "type": "string" + } + } } } } diff --git a/buf.lock b/buf.lock index f546ab3..6f6d154 100644 --- a/buf.lock +++ b/buf.lock @@ -4,6 +4,6 @@ deps: owner: beta repository: googleapis branch: main - commit: 2e73676eef8642dfba4ed782b7c8d6fe - digest: b1-vB11w98W2vFtEP4Veknm56Pi6DU6MpOuocESiOzvbqw= - create_time: 2021-04-26T14:55:30.644663Z + commit: 1c473ad9220a49bca9320f4cc690eba5 + digest: b1-unlhrcI3tnJd0JEGuOb692LZ_tY_gCGq6mK1bgCn1Pg= + create_time: 2021-06-23T20:16:47.788079Z diff --git a/cmd/root.go b/cmd/root.go index 994a21a..78d467b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,4 +1,4 @@ -//nolint:exhaustivestruct,gochecknoglobals,gochecknoinits +//nolint:exhaustivestruct,gochecknoglobals,gochecknoinits,gomnd package cmd import ( @@ -27,7 +27,12 @@ var rootCmd = &cobra.Command{ } // init initializes the command line interface. +// +// nolint:funlen func init() { + // Root. + rootCmd.PersistentFlags().Bool("pprof", false, "Enables pprof for debugging") + // Run. rootCmd.AddCommand(runCmd) runCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file") @@ -36,6 +41,8 @@ func init() { log.Fatal().Err(err).Msg("missing flag") } + runCmd.Flags().Bool("ignore-reader", false, "Ignoring that the reader is missing") + // Prepare. rootCmd.AddCommand(prepareCmd) prepareCmd.Flags().BoolVar(&prepare.Cfg.ReadOnly, "read-only", false, "Setup read-only system") @@ -57,12 +64,9 @@ func init() { if err := webCmd.MarkFlagRequired("config"); err != nil { log.Fatal().Err(err).Msg("missing flag") } -} -// initConfig loads the config file. -// fatal defines if config parsing should end in a fatal error or not. -func initConfig(fatal bool) { - logger := log.With().Str("config", cfgFile).Logger() + // Timer. + rootCmd.AddCommand(timerCmd) // Defaults. viper.SetDefault("box.hostname", "localhost") @@ -71,6 +75,8 @@ func initConfig(fatal bool) { viper.SetDefault("mpd.hostname", "localhost") viper.SetDefault("mpd.port", 6600) viper.SetDefault("reader.dev", "/dev/hidraw0") + viper.SetDefault("reader.ignore", false) + viper.SetDefault("pprof", false) // Environment handling. viper.SetEnvPrefix("SCHNUTIBOX") @@ -78,10 +84,24 @@ func initConfig(fatal bool) { viper.AutomaticEnv() // Flags. - if err := viper.BindPFlag("reader.dev", prepareCmd.Flags().Lookup("rfid-reader")); err != nil { - logger.Fatal().Err(err).Msg("could not bind flag") + if err := viper.BindPFlag("pprof", rootCmd.PersistentFlags().Lookup("pprof")); err != nil { + log.Fatal().Err(err).Msg("could not bind flag") } + if err := viper.BindPFlag("reader.dev", prepareCmd.Flags().Lookup("rfid-reader")); err != nil { + log.Fatal().Err(err).Msg("could not bind flag") + } + + if err := viper.BindPFlag("reader.ignore", runCmd.Flags().Lookup("ignore-reader")); err != nil { + log.Fatal().Err(err).Msg("could not bind flag") + } +} + +// initConfig loads the config file. +// fatal defines if config parsing should end in a fatal error or not. +func initConfig(fatal bool) { + logger := log.With().Str("config", cfgFile).Logger() + // Parse config file. if cfgFile != "" { viper.SetConfigFile(cfgFile) diff --git a/cmd/timer.go b/cmd/timer.go new file mode 100644 index 0000000..66277f7 --- /dev/null +++ b/cmd/timer.go @@ -0,0 +1,13 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "go.xsfx.dev/schnutibox/pkg/timer" +) + +// nolint:gochecknoglobals +var timerCmd = &cobra.Command{ + Use: "timer", + Short: "Handling timer", + Run: timer.Run, +} diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index fdcf604..ddc4e2c 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -31,6 +31,15 @@ var Play = promauto.NewGaugeVec( []string{"rfid", "name"}, ) +// Seconds tracks the seconds a play was played. +var Seconds = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "schnutibox_play_seconds_total", + Help: "play seconds metrics", + }, + []string{"rfid", "name"}, +) + // BoxErrors counts schnutibox errors. var BoxErrors = promauto.NewCounter( prometheus.CounterOpts{ @@ -56,10 +65,12 @@ func tracksEqual(a, b []string) bool { } // Set sets `1` on play gauge if item is playing, a `0` on every other play. +// It also raises the counter for played seconds of a play. func Set(uris []string, state string) { for r, p := range Plays { if tracksEqual(uris, p.Uris) && state == "play" { Play.WithLabelValues(r, p.Name).Set(1) + Seconds.WithLabelValues(r, p.Name).Inc() } else { Play.WithLabelValues(r, p.Name).Set(0) } diff --git a/pkg/api/v1/schnutibox.pb.go b/pkg/api/v1/schnutibox.pb.go index 94befbd..795fd15 100644 --- a/pkg/api/v1/schnutibox.pb.go +++ b/pkg/api/v1/schnutibox.pb.go @@ -7,6 +7,7 @@ package v1 import ( + duration "github.com/golang/protobuf/ptypes/duration" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -123,30 +124,144 @@ func (x *IdentifyResponse) GetUris() []string { return nil } +type Timer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Duration *duration.Duration `protobuf:"bytes,1,opt,name=duration,proto3" json:"duration,omitempty"` + Current *duration.Duration `protobuf:"bytes,2,opt,name=current,proto3" json:"current,omitempty"` +} + +func (x *Timer) Reset() { + *x = Timer{} + if protoimpl.UnsafeEnabled { + mi := &file_schnutibox_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Timer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Timer) ProtoMessage() {} + +func (x *Timer) ProtoReflect() protoreflect.Message { + mi := &file_schnutibox_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 Timer.ProtoReflect.Descriptor instead. +func (*Timer) Descriptor() ([]byte, []int) { + return file_schnutibox_proto_rawDescGZIP(), []int{2} +} + +func (x *Timer) GetDuration() *duration.Duration { + if x != nil { + return x.Duration + } + return nil +} + +func (x *Timer) GetCurrent() *duration.Duration { + if x != nil { + return x.Current + } + return nil +} + +type TimerEmpty struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *TimerEmpty) Reset() { + *x = TimerEmpty{} + if protoimpl.UnsafeEnabled { + mi := &file_schnutibox_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TimerEmpty) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimerEmpty) ProtoMessage() {} + +func (x *TimerEmpty) ProtoReflect() protoreflect.Message { + mi := &file_schnutibox_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 TimerEmpty.ProtoReflect.Descriptor instead. +func (*TimerEmpty) Descriptor() ([]byte, []int) { + return file_schnutibox_proto_rawDescGZIP(), []int{3} +} + var File_schnutibox_proto protoreflect.FileDescriptor var file_schnutibox_proto_rawDesc = []byte{ 0x0a, 0x10, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, - 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x21, 0x0a, 0x0f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x3a, 0x0a, 0x10, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x72, - 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, 0x32, 0x79, - 0x0a, 0x11, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, - 0x1e, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x3a, 0x01, 0x2a, 0x42, 0x23, 0x5a, 0x21, 0x67, 0x6f, 0x2e, - 0x78, 0x73, 0x66, 0x78, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, - 0x62, 0x6f, 0x78, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x75, 0x72, 0x69, 0x73, 0x22, 0x73, + 0x0a, 0x05, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x33, + 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x22, 0x0c, 0x0a, 0x0a, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x32, 0x79, 0x0a, 0x11, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x64, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x79, 0x12, 0x1e, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, + 0x76, 0x31, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, + 0x76, 0x31, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31, + 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x3a, 0x01, 0x2a, 0x32, 0xad, 0x01, 0x0a, + 0x0c, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4e, 0x0a, + 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, + 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x1a, 0x14, 0x2e, + 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x72, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x4d, 0x0a, + 0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, + 0x78, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x14, 0x2e, 0x73, 0x63, 0x68, 0x6e, 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x72, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x12, 0x0d, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x74, 0x42, 0x23, 0x5a, 0x21, + 0x67, 0x6f, 0x2e, 0x78, 0x73, 0x66, 0x78, 0x2e, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x63, 0x68, 0x6e, + 0x75, 0x74, 0x69, 0x62, 0x6f, 0x78, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -161,19 +276,28 @@ func file_schnutibox_proto_rawDescGZIP() []byte { return file_schnutibox_proto_rawDescData } -var file_schnutibox_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_schnutibox_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_schnutibox_proto_goTypes = []interface{}{ - (*IdentifyRequest)(nil), // 0: schnutibox.v1.IdentifyRequest - (*IdentifyResponse)(nil), // 1: schnutibox.v1.IdentifyResponse + (*IdentifyRequest)(nil), // 0: schnutibox.v1.IdentifyRequest + (*IdentifyResponse)(nil), // 1: schnutibox.v1.IdentifyResponse + (*Timer)(nil), // 2: schnutibox.v1.Timer + (*TimerEmpty)(nil), // 3: schnutibox.v1.TimerEmpty + (*duration.Duration)(nil), // 4: google.protobuf.Duration } var file_schnutibox_proto_depIdxs = []int32{ - 0, // 0: schnutibox.v1.IdentifierService.Identify:input_type -> schnutibox.v1.IdentifyRequest - 1, // 1: schnutibox.v1.IdentifierService.Identify:output_type -> schnutibox.v1.IdentifyResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 4, // 0: schnutibox.v1.Timer.duration:type_name -> google.protobuf.Duration + 4, // 1: schnutibox.v1.Timer.current:type_name -> google.protobuf.Duration + 0, // 2: schnutibox.v1.IdentifierService.Identify:input_type -> schnutibox.v1.IdentifyRequest + 2, // 3: schnutibox.v1.TimerService.Create:input_type -> schnutibox.v1.Timer + 3, // 4: schnutibox.v1.TimerService.Get:input_type -> schnutibox.v1.TimerEmpty + 1, // 5: schnutibox.v1.IdentifierService.Identify:output_type -> schnutibox.v1.IdentifyResponse + 2, // 6: schnutibox.v1.TimerService.Create:output_type -> schnutibox.v1.Timer + 2, // 7: schnutibox.v1.TimerService.Get:output_type -> schnutibox.v1.Timer + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_schnutibox_proto_init() } @@ -206,6 +330,30 @@ func file_schnutibox_proto_init() { return nil } } + file_schnutibox_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Timer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_schnutibox_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TimerEmpty); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -213,9 +361,9 @@ func file_schnutibox_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_schnutibox_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, - NumServices: 1, + NumServices: 2, }, GoTypes: file_schnutibox_proto_goTypes, DependencyIndexes: file_schnutibox_proto_depIdxs, diff --git a/pkg/api/v1/schnutibox.pb.gw.go b/pkg/api/v1/schnutibox.pb.gw.go index 643fde9..2e064a2 100644 --- a/pkg/api/v1/schnutibox.pb.gw.go +++ b/pkg/api/v1/schnutibox.pb.gw.go @@ -65,6 +65,58 @@ func local_request_IdentifierService_Identify_0(ctx context.Context, marshaler r } +func request_TimerService_Create_0(ctx context.Context, marshaler runtime.Marshaler, client TimerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Timer + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Create(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TimerService_Create_0(ctx context.Context, marshaler runtime.Marshaler, server TimerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Timer + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Create(ctx, &protoReq) + return msg, metadata, err + +} + +func request_TimerService_Get_0(ctx context.Context, marshaler runtime.Marshaler, client TimerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TimerEmpty + var metadata runtime.ServerMetadata + + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TimerService_Get_0(ctx context.Context, marshaler runtime.Marshaler, server TimerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TimerEmpty + var metadata runtime.ServerMetadata + + msg, err := server.Get(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterIdentifierServiceHandlerServer registers the http handlers for service IdentifierService to "mux". // UnaryRPC :call IdentifierServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -97,6 +149,61 @@ func RegisterIdentifierServiceHandlerServer(ctx context.Context, mux *runtime.Se return nil } +// RegisterTimerServiceHandlerServer registers the http handlers for service TimerService to "mux". +// UnaryRPC :call TimerServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterTimerServiceHandlerFromEndpoint instead. +func RegisterTimerServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server TimerServiceServer) error { + + mux.Handle("POST", pattern_TimerService_Create_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/schnutibox.v1.TimerService/Create") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TimerService_Create_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TimerService_Create_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_TimerService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/schnutibox.v1.TimerService/Get") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TimerService_Get_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TimerService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + // RegisterIdentifierServiceHandlerFromEndpoint is same as RegisterIdentifierServiceHandler but // automatically dials to "endpoint" and closes the connection when "ctx" gets done. func RegisterIdentifierServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { @@ -165,3 +272,96 @@ var ( var ( forward_IdentifierService_Identify_0 = runtime.ForwardResponseMessage ) + +// RegisterTimerServiceHandlerFromEndpoint is same as RegisterTimerServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterTimerServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterTimerServiceHandler(ctx, mux, conn) +} + +// RegisterTimerServiceHandler registers the http handlers for service TimerService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterTimerServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterTimerServiceHandlerClient(ctx, mux, NewTimerServiceClient(conn)) +} + +// RegisterTimerServiceHandlerClient registers the http handlers for service TimerService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "TimerServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "TimerServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "TimerServiceClient" to call the correct interceptors. +func RegisterTimerServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client TimerServiceClient) error { + + mux.Handle("POST", pattern_TimerService_Create_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/schnutibox.v1.TimerService/Create") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TimerService_Create_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TimerService_Create_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_TimerService_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/schnutibox.v1.TimerService/Get") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TimerService_Get_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TimerService_Get_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_TimerService_Create_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "timer", "set"}, "")) + + pattern_TimerService_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "timer", "get"}, "")) +) + +var ( + forward_TimerService_Create_0 = runtime.ForwardResponseMessage + + forward_TimerService_Get_0 = runtime.ForwardResponseMessage +) diff --git a/pkg/api/v1/schnutibox_grpc.pb.go b/pkg/api/v1/schnutibox_grpc.pb.go index 690c64a..e7bcd9b 100644 --- a/pkg/api/v1/schnutibox_grpc.pb.go +++ b/pkg/api/v1/schnutibox_grpc.pb.go @@ -97,3 +97,123 @@ var IdentifierService_ServiceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, Metadata: "schnutibox.proto", } + +// TimerServiceClient is the client API for TimerService 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 TimerServiceClient interface { + Create(ctx context.Context, in *Timer, opts ...grpc.CallOption) (*Timer, error) + Get(ctx context.Context, in *TimerEmpty, opts ...grpc.CallOption) (*Timer, error) +} + +type timerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTimerServiceClient(cc grpc.ClientConnInterface) TimerServiceClient { + return &timerServiceClient{cc} +} + +func (c *timerServiceClient) Create(ctx context.Context, in *Timer, opts ...grpc.CallOption) (*Timer, error) { + out := new(Timer) + err := c.cc.Invoke(ctx, "/schnutibox.v1.TimerService/Create", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *timerServiceClient) Get(ctx context.Context, in *TimerEmpty, opts ...grpc.CallOption) (*Timer, error) { + out := new(Timer) + err := c.cc.Invoke(ctx, "/schnutibox.v1.TimerService/Get", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TimerServiceServer is the server API for TimerService service. +// All implementations should embed UnimplementedTimerServiceServer +// for forward compatibility +type TimerServiceServer interface { + Create(context.Context, *Timer) (*Timer, error) + Get(context.Context, *TimerEmpty) (*Timer, error) +} + +// UnimplementedTimerServiceServer should be embedded to have forward compatible implementations. +type UnimplementedTimerServiceServer struct { +} + +func (UnimplementedTimerServiceServer) Create(context.Context, *Timer) (*Timer, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (UnimplementedTimerServiceServer) Get(context.Context, *TimerEmpty) (*Timer, error) { + return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") +} + +// UnsafeTimerServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TimerServiceServer will +// result in compilation errors. +type UnsafeTimerServiceServer interface { + mustEmbedUnimplementedTimerServiceServer() +} + +func RegisterTimerServiceServer(s grpc.ServiceRegistrar, srv TimerServiceServer) { + s.RegisterService(&TimerService_ServiceDesc, srv) +} + +func _TimerService_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Timer) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TimerServiceServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/schnutibox.v1.TimerService/Create", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TimerServiceServer).Create(ctx, req.(*Timer)) + } + return interceptor(ctx, in, info, handler) +} + +func _TimerService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TimerEmpty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TimerServiceServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/schnutibox.v1.TimerService/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TimerServiceServer).Get(ctx, req.(*TimerEmpty)) + } + return interceptor(ctx, in, info, handler) +} + +// TimerService_ServiceDesc is the grpc.ServiceDesc for TimerService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TimerService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "schnutibox.v1.TimerService", + HandlerType: (*TimerServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Create", + Handler: _TimerService_Create_Handler, + }, + { + MethodName: "Get", + Handler: _TimerService_Get_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "schnutibox.proto", +} diff --git a/pkg/mpc/mpc.go b/pkg/mpc/mpc.go index 68491fc..daf73a4 100644 --- a/pkg/mpc/mpc.go +++ b/pkg/mpc/mpc.go @@ -7,13 +7,11 @@ import ( "github.com/fhs/gompd/v2/mpd" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "go.xsfx.dev/schnutibox/internal/config" "go.xsfx.dev/schnutibox/internal/metrics" ) const ( - tickerTime = time.Second timeout = 5 * time.Second timeoutWait = time.Second / 2 ) @@ -30,7 +28,7 @@ func Conn() (*mpd.Client, error) { default: c, err := mpd.Dial("tcp", fmt.Sprintf("%s:%d", config.Cfg.MPD.Hostname, config.Cfg.MPD.Port)) if err != nil { - log.Error().Err(err).Msg("could not connect") + // log.Error().Err(err).Msg("could not connect") time.Sleep(timeoutWait) @@ -71,45 +69,6 @@ func PlaylistURIS() ([]string, error) { return uris, nil } -func Watcher() { - log.Debug().Msg("starting watch") - - ticker := time.NewTicker(tickerTime) - - go func() { - for { - <-ticker.C - - m, err := Conn() - if err != nil { - log.Error().Err(err).Msg("could not connect") - - continue - } - - uris, err := PlaylistURIS() - if err != nil { - log.Error().Err(err).Msg("could not get playlist uris") - metrics.BoxErrors.Inc() - - continue - } - - // Gettings MPD state. - s, err := m.Status() - if err != nil { - log.Error().Err(err).Msg("could not get status") - metrics.BoxErrors.Inc() - - continue - } - - // Sets the metrics. - metrics.Set(uris, s["state"]) - } - }() -} - func Stop(logger zerolog.Logger) error { logger.Info().Msg("trying to stop playback") diff --git a/pkg/run/run.go b/pkg/run/run.go index 0f1b14a..ec53865 100644 --- a/pkg/run/run.go +++ b/pkg/run/run.go @@ -3,9 +3,11 @@ package run import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/viper" "go.xsfx.dev/schnutibox/internal/config" "go.xsfx.dev/schnutibox/pkg/mpc" "go.xsfx.dev/schnutibox/pkg/rfid" + "go.xsfx.dev/schnutibox/pkg/watcher" "go.xsfx.dev/schnutibox/pkg/web" ) @@ -16,50 +18,57 @@ func Run(cmd *cobra.Command, args []string) { r := rfid.NewRFID(config.Cfg, idChan) if err := r.Run(); err != nil { - log.Fatal().Err(err).Msg("could not start RFID reader") + if !viper.GetBool("reader.ignore") { + log.Fatal().Err(err).Msg("could not start RFID reader") + } + + log.Warn().Err(err).Msg("could not start RFID reader. ignoring...") } // Stating watcher. - mpc.Watcher() + watcher.Run() - go func() { - var id string + // nolint:nestif + if !viper.GetBool("reader.ignore") { + go func() { + var id string - for { - // Wating for a scanned tag. - id = <-idChan - logger := log.With().Str("id", id).Logger() - logger.Info().Msg("received id") + for { + // Wating for a scanned tag. + id = <-idChan + logger := log.With().Str("id", id).Logger() + logger.Info().Msg("received id") - // Check of stop tag was detected. - if id == config.Cfg.Meta.Stop { - logger.Info().Msg("stopping") + // Check of stop tag was detected. + if id == config.Cfg.Meta.Stop { + logger.Info().Msg("stopping") - if err := mpc.Stop(logger); err != nil { - logger.Error().Err(err).Msg("could not stop") + if err := mpc.Stop(logger); err != nil { + logger.Error().Err(err).Msg("could not stop") + } + + if err := mpc.Clear(logger); err != nil { + logger.Error().Err(err).Msg("could not clear") + } + + continue } - if err := mpc.Clear(logger); err != nil { - logger.Error().Err(err).Msg("could not clear") + // Check if there is a track for the ID. + tracks, ok := config.Cfg.Tracks[id] + if !ok { + logger.Error().Msg("could not find track for ID") + + continue } - continue + // Try to play track. + if err := mpc.Play(logger, id, tracks.Name, tracks.Uris); err != nil { + logger.Error().Err(err).Msg("could not play track") + } } - - // Check if there is a track for the ID. - tracks, ok := config.Cfg.Tracks[id] - if !ok { - logger.Error().Msg("could not find track for ID") - - continue - } - - // Try to play track. - if err := mpc.Play(logger, id, tracks.Name, tracks.Uris); err != nil { - logger.Error().Err(err).Msg("could not play track") - } - } - }() + }() + } // Running web interface. Blocking. web.Run(cmd, args) diff --git a/pkg/timer/timer.go b/pkg/timer/timer.go new file mode 100644 index 0000000..2b2aa51 --- /dev/null +++ b/pkg/timer/timer.go @@ -0,0 +1,45 @@ +package timer + +import ( + "github.com/golang/protobuf/ptypes/duration" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + api "go.xsfx.dev/schnutibox/pkg/api/v1" +) + +// nolint:gochecknoglobals +var T = &api.Timer{} + +func Timer() { + if T.Duration != nil { + // Initialize the current object. + if T.Current == nil { + T.Current = &duration.Duration{} + T.Current.Seconds = T.Duration.Seconds + } + + switch { + // There is some timing going on. + case T.Duration.Seconds != 0 && T.Current.Seconds != 0: + log.Debug(). + Int64("current", T.Current.Seconds). + Int64("duration", T.Duration.Seconds). + Msg("timer is running") + + if T.Current.Seconds > 0 { + T.Current.Seconds -= 1 + + return + } + + // No timer is running... so setting the duration to 0. + // TODO: Needs to do something actually! + case T.Current.Seconds == 0 && T.Duration.Seconds != 0: + log.Debug().Msg("stoping timer") + + T.Duration.Seconds = 0 + } + } +} + +func Run(cmd *cobra.Command, args []string) {} diff --git a/pkg/watcher/watcher.go b/pkg/watcher/watcher.go new file mode 100644 index 0000000..6ea32ca --- /dev/null +++ b/pkg/watcher/watcher.go @@ -0,0 +1,58 @@ +package watcher + +import ( + "time" + + "github.com/rs/zerolog/log" + "go.xsfx.dev/schnutibox/internal/metrics" + "go.xsfx.dev/schnutibox/pkg/mpc" + "go.xsfx.dev/schnutibox/pkg/timer" +) + +const tickerTime = time.Second + +// Run runs actions after tickerTime is over, over again and again. +// Right now its mostly used for setting metrics. +func Run() { + log.Debug().Msg("starting watch") + + ticker := time.NewTicker(tickerTime) + + go func() { + for { + <-ticker.C + + // Timer. + go timer.Timer() + + // Metrics. + go func() { + m, err := mpc.Conn() + if err != nil { + // log.Error().Err(err).Msg("could not connect") + return + } + + uris, err := mpc.PlaylistURIS() + if err != nil { + // log.Error().Err(err).Msg("could not get playlist uris") + metrics.BoxErrors.Inc() + + return + } + + // Gettings MPD state. + s, err := m.Status() + if err != nil { + // log.Error().Err(err).Msg("could not get status") + metrics.BoxErrors.Inc() + + return + } + + // Sets the metrics. + metrics.Set(uris, s["state"]) + }() + } + }() +} diff --git a/pkg/web/web.go b/pkg/web/web.go index b5ad764..9cb29e1 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -5,17 +5,20 @@ import ( "fmt" "html/template" "net/http" + "net/http/pprof" "strings" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/spf13/viper" "go.xsfx.dev/logginghandler" assets "go.xsfx.dev/schnutibox/assets/web" "go.xsfx.dev/schnutibox/internal/config" api "go.xsfx.dev/schnutibox/pkg/api/v1" "go.xsfx.dev/schnutibox/pkg/sselog" + "go.xsfx.dev/schnutibox/pkg/timer" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "google.golang.org/grpc" @@ -53,11 +56,11 @@ func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Ha }), &http2.Server{}) } -type server struct{} +type identifyServer struct{} // Identify searches in tracks config for entries and returns them. // nolint:goerr113 -func (s server) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.IdentifyResponse, error) { +func (i identifyServer) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.IdentifyResponse, error) { r := &api.IdentifyResponse{} if in.Id == "" { @@ -75,11 +78,27 @@ func (s server) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.Ide return r, nil } +type timerServer struct{} + +func (t timerServer) Create(ctx context.Context, req *api.Timer) (*api.Timer, error) { + timer.T = req + + return timer.T, nil +} + +// Get just returns the status of the timer. +func (t timerServer) Get(ctx context.Context, req *api.TimerEmpty) (*api.Timer, error) { + return timer.T, nil +} + func gw(s *grpc.Server, conn string) *runtime.ServeMux { ctx := context.Background() gopts := []grpc.DialOption{grpc.WithInsecure()} - api.RegisterIdentifierServiceServer(s, server{}) + api.RegisterIdentifierServiceServer(s, identifyServer{}) + api.RegisterTimerServiceServer(s, timerServer{}) + + // Adds reflections. reflection.Register(s) gwmux := runtime.NewServeMux() @@ -118,6 +137,11 @@ func Run(command *cobra.Command, args []string) { mux.Handle("/api/", http.StripPrefix("/api", gw(grpcServer, lh))) + // PPROF. + if viper.GetBool("pprof") { + mux.HandleFunc("/debug/pprof/", pprof.Index) + } + // Serving http. log.Info().Msgf("serving HTTP on %s...", lh) diff --git a/scripts/putio/main.go b/scripts/putio/main.go new file mode 100644 index 0000000..38dd16d --- /dev/null +++ b/scripts/putio/main.go @@ -0,0 +1,3 @@ +package main + +func main() {}