From 202979d346039262fa03ed233f100fe8e9a85608 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 22 Dec 2020 14:37:45 -0800 Subject: [PATCH] move to mage for build --- .gitignore | 4 + Makefile | 98 ------------- README.md | 2 +- bootstrap.sh | 12 ++ go.mod | 5 +- go.sum | 12 ++ magefile.go | 341 ++++++++++++++++++++++++++++++++++++++++++++++ proto/model.proto | 7 + 8 files changed, 381 insertions(+), 100 deletions(-) delete mode 100644 Makefile create mode 100755 bootstrap.sh create mode 100644 magefile.go diff --git a/.gitignore b/.gitignore index 88bc6616d..36f9183f8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,10 @@ # Test binary, built with `go test -c` *.test +# checksums of file modification time +.checksumgo +proto/.checksumproto + # Output of the go coverage tool, specifically when used with LiteIDE *.out livekit-server diff --git a/Makefile b/Makefile deleted file mode 100644 index d18471376..000000000 --- a/Makefile +++ /dev/null @@ -1,98 +0,0 @@ - -# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) -ifeq (,$(shell go env GOBIN)) -GOBIN=$(shell go env GOPATH)/bin -else -GOBIN=$(shell go env GOBIN) -endif - - -build: server client - -client: generate - @{ \ - echo "building client" ;\ - mkdir -p bin ;\ - cd cmd/cli ;\ - go build -i -o ../../bin/livekit-cli ;\ - } - -server: generate - @{ \ - echo "building server" ;\ - mkdir -p bin ;\ - cd cmd/server ;\ - go build -i -o ../../bin/livekit-server ;\ - } - -generate: wire mockgen - @{ \ - echo "wiring" ;\ - cd cmd/server ;\ - $(WIRE) ;\ - cd ../.. ;\ - echo "updating mocks" ;\ - $(MOCKGEN) -source pkg/rtc/interfaces.go -destination pkg/rtc/mock_test.go -package rtc ;\ - } - -test: generate - go test ./... - -GO_TARGET=proto/livekit -proto: protoc protoc-gen-go twirp-gen - @{ \ - mkdir -p $(GO_TARGET) ;\ - protoc --go_out=$(GO_TARGET) --twirp_out=$(GO_TARGET) \ - --go_opt=paths=source_relative \ - --twirp_opt=paths=source_relative \ - --plugin=$(PROTOC_GEN_GO) \ - -I=proto \ - proto/room.proto proto/model.proto ;\ - protoc --go_out=$(GO_TARGET) \ - --go_opt=paths=source_relative \ - -I=proto \ - proto/rtc.proto ;\ - } - -protoc: -ifeq (, $(shell which protoc)) - echo "protoc is required, and is not installed" -endif - -protoc-gen-go: -ifeq (, $(shell which protoc-gen-go)) - @{ \ - echo "installing go protobuf plugin" ;\ - go install google.golang.org/protobuf/cmd/protoc-gen-go ;\ - } -endif - -twirp-gen: -ifeq (, $(shell which protoc-gen-twirp)) - @{ \ - echo "installing twirp protobuf plugin" ;\ - go install github.com/twitchtv/twirp/protoc-gen-twirp ;\ - } -endif - -wire: -ifeq (, $(shell which wire)) - @{ \ - echo "installing wire" ;\ - go install github.com/google/wire/cmd/wire ;\ - } -WIRE=$(GOBIN)/wire -else -WIRE=$(shell which wire) -endif - -mockgen: -ifeq (, $(shell which mockgen)) - @{ \ - echo "installing mockgen" ;\ - go get github.com/golang/mock/mockgen ;\ - } -MOCKGEN=$(GOBIN)/mockgen -else -MOCKGEN=$(shell which mockgen) -endif diff --git a/README.md b/README.md index 3813fa565..2517e55b2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Ensure Go 1.14+ is installed, and GOPATH/bin is in your PATH. -Run `make proto && make +Run `./bootstrap` to install mage, then `mage` to build the project ## CLI diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 000000000..adcca2814 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +cd /tmp +git clone https://github.com/magefile/mage +cd mage +go run bootstrap.go +rm -rf mage + +if ! command -v mage &> /dev/null +then + echo "Ensure ${GOPATH}/bin is in your \$PATH" +fi diff --git a/go.mod b/go.mod index 69ddd74ef..57e9f61ad 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,13 @@ go 1.15 require ( github.com/golang/mock v1.4.4 github.com/golang/protobuf v1.4.3 + github.com/google/subcommands v1.2.0 // indirect github.com/google/uuid v1.1.2 github.com/google/wire v0.4.0 github.com/gorilla/websocket v1.4.2 github.com/lithammer/shortuuid/v3 v3.0.4 github.com/lunixbochs/vtclean v1.0.0 // indirect + github.com/magefile/mage v1.10.0 github.com/magiconair/properties v1.8.1 github.com/manifoldco/promptui v0.8.0 github.com/pion/interceptor v0.0.5 @@ -26,7 +28,8 @@ require ( github.com/urfave/cli/v2 v2.2.0 github.com/urfave/negroni v1.0.0 go.uber.org/zap v1.16.0 - golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 // indirect + golang.org/x/mod v0.4.0 // indirect + golang.org/x/tools v0.0.0-20201222163215-f2e330f49058 // indirect google.golang.org/protobuf v1.25.0 gopkg.in/yaml.v2 v2.3.0 ) diff --git a/go.sum b/go.sum index 795c0165d..5381f1004 100644 --- a/go.sum +++ b/go.sum @@ -143,7 +143,10 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/subcommands v1.0.1 h1:/eqq+otEXm5vhfBrbREPCSVQbvofip6kIz+mX5TUH7k= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= @@ -225,6 +228,8 @@ github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQN github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g= +github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= @@ -430,6 +435,7 @@ github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= @@ -486,6 +492,8 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -509,6 +517,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7 h1:3uJsdck53FDIpWwLeAXlia9p4C8j0BO2xZrqzKpL0D8= @@ -526,6 +535,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -598,6 +608,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200815165600-90abf76919f3 h1:0aScV/0rLmANzEYIhjCOi2pTvDyhZNduBUMD2q3iqs4= golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201222163215-f2e330f49058 h1:zSPRinxiz6krIAZie/STjID5lW/bkAvl+xz2cquNQEA= +golang.org/x/tools v0.0.0-20201222163215-f2e330f49058/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/magefile.go b/magefile.go new file mode 100644 index 000000000..93696472e --- /dev/null +++ b/magefile.go @@ -0,0 +1,341 @@ +// +build mage + +package main + +import ( + "crypto/sha1" + "fmt" + "go/build" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + + "github.com/magefile/mage/mg" + log "github.com/pion/ion-log" +) + +const ( + protoChecksumFile = ".checksumproto" + goChecksumFile = ".checksumgo" +) + +// Default target to run when none is specified +// If not set, running mage will list available targets +var Default = Build +var checksummer = NewChecksummer(".", goChecksumFile, ".go") + +func init() { + checksummer.IgnoredFiles = []string{ + "cmd/server/wire_gen.go", + "pkg/rtc/mock_test.go", + } +} + +// explicitly reinstall all deps +func Deps() error { + return installTools(true) +} + +// regenerate protobuf +func Proto() error { + protoChecksummer := NewChecksummer("proto", protoChecksumFile, ".proto") + if !protoChecksummer.IsChanged() { + return nil + } + + fmt.Println("generating protobuf") + target := "proto/livekit" + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + + protoc, err := getToolPath("protoc") + if err != nil { + return err + } + protoc_go_path, err := getToolPath("protoc-gen-go") + if err != nil { + return err + } + twirp_path, err := getToolPath("protoc-gen-twirp") + + // generate model and room + cmd := exec.Command(protoc, + "--go_out", target, + "--twirp_out", target, + "--go_opt=paths=source_relative", + "--twirp_opt=paths=source_relative", + "--plugin=go="+protoc_go_path, + "--plugin=twirp="+twirp_path, + "-I=proto", + "proto/room.proto", + "proto/model.proto", + ) + if err := cmd.Run(); err != nil { + return err + } + + // generate rtc + cmd = exec.Command(protoc, + "--go_out", target, + "--go_opt=paths=source_relative", + "--plugin=go="+protoc_go_path, + "-I=proto", + "proto/rtc.proto", + ) + if err := cmd.Run(); err != nil { + return err + } + + protoChecksummer.WriteChecksum() + return nil +} + +// builds LiveKit server and cli +func Build() error { + mg.Deps(Proto, generate) + if !checksummer.IsChanged() { + fmt.Println("up to date") + return nil + } + + fmt.Println("building...") + if err := os.MkdirAll("bin", 0755); err != nil { + return err + } + cmd := exec.Command("go", "build", "-i", "-o", "../../bin/livekit-server") + cmd.Dir = "cmd/server" + connectStd(cmd) + if err := cmd.Run(); err != nil { + return err + } + + cmd = exec.Command("go", "build", "-i", "-o", "../../bin/livekit-cli") + cmd.Dir = "cmd/cli" + connectStd(cmd) + if err := cmd.Run(); err != nil { + return err + } + + checksummer.WriteChecksum() + return nil +} + +// cleans up builds +func Clean() { + fmt.Println("cleaning...") + os.RemoveAll("bin") + os.Remove(protoChecksumFile) + os.Remove(goChecksumFile) +} + +// code generation +func generate() error { + mg.Deps(installDeps) + if !checksummer.IsChanged() { + return nil + } + + fmt.Println("wiring...") + wire, err := getToolPath("wire") + if err != nil { + return err + } + cmd := exec.Command(wire) + cmd.Dir = "cmd/server" + connectStd(cmd) + if err := cmd.Run(); err != nil { + return err + } + + //cmd = exec.Command(wire) + //cmd.Dir = "cmd/cli" + //connectStd(cmd) + //if err := cmd.Run(); err != nil { + // return err + //} + + fmt.Println("updating mocks...") + mockgen, err := getToolPath("mockgen") + if err != nil { + return err + } + + cmd = exec.Command(mockgen, "-source", "pkg/rtc/interfaces.go", "-destination", "pkg/rtc/mock_test.go", "-package", "rtc") + connectStd(cmd) + return cmd.Run() +} + +// implicitly install deps +func installDeps() error { + return installTools(false) +} + +func installTools(force bool) error { + if _, err := getToolPath("protoc"); err != nil { + return fmt.Errorf("protoc is required but is not found") + } + + tools := []string{ + "google.golang.org/protobuf/cmd/protoc-gen-go", + "github.com/twitchtv/twirp/protoc-gen-twirp", + "github.com/google/wire/cmd/wire", + "github.com/golang/mock/mockgen", + } + for _, t := range tools { + if err := installTool(t, force); err != nil { + return err + } + } + return nil +} + +func installTool(url string, force bool) error { + name := filepath.Base(url) + if !force { + _, err := getToolPath(name) + if err == nil { + // already installed + return nil + } + } + + fmt.Printf("installing %s\n", name) + cmd := exec.Command("go", "get", "-u", url) + connectStd(cmd) + if err := cmd.Run(); err != nil { + return err + } + + // check + _, err := getToolPath(name) + return err +} + +// helpers + +func getToolPath(name string) (string, error) { + if p, err := exec.LookPath(name); err == nil { + return p, nil + } + // check under gopath + gopath := os.Getenv("GOPATH") + if gopath == "" { + gopath = build.Default.GOPATH + } + p := filepath.Join(gopath, "bin", name) + if _, err := os.Stat(p); err != nil { + return "", err + } + return p, nil +} + +func connectStd(cmd *exec.Cmd) { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr +} + +// A helper checksum library that generates a fast, non-portable checksum over a directory of files +// it's designed as a quick way to bypass +type Checksummer struct { + dir string + file string + checksum string + allExts bool + extMap map[string]bool + IgnoredFiles []string +} + +func NewChecksummer(dir string, checksumfile string, exts ...string) *Checksummer { + c := &Checksummer{ + dir: dir, + file: checksumfile, + extMap: make(map[string]bool), + } + if len(exts) == 0 { + c.allExts = true + } else { + for _, ext := range exts { + c.extMap[ext] = true + } + } + + return c +} + +func (c *Checksummer) IsChanged() bool { + // default changed + if err := c.computeChecksum(); err != nil { + log.Errorf("could not compute checksum: %v", err) + return true + } + // read + existing, err := c.ReadChecksum() + if err != nil { + // may not be there + return true + } + + return existing != c.checksum +} + +func (c *Checksummer) ReadChecksum() (string, error) { + b, err := ioutil.ReadFile(filepath.Join(c.dir, c.file)) + if err != nil { + return "", err + } + return string(b), nil +} + +func (c *Checksummer) WriteChecksum() error { + if err := c.computeChecksum(); err != nil { + return err + } + return ioutil.WriteFile(filepath.Join(c.dir, c.file), []byte(c.checksum), 0644) +} + +func (c *Checksummer) computeChecksum() error { + if c.checksum != "" { + return nil + } + + entries := make([]string, 0) + ignoredMap := make(map[string]bool) + for _, f := range c.IgnoredFiles { + ignoredMap[f] = true + } + err := filepath.Walk(c.dir, func(path string, info os.FileInfo, err error) error { + if path == c.dir { + return nil + } + if strings.HasPrefix(info.Name(), ".") { + if info.IsDir() { + return filepath.SkipDir + } else { + return nil + } + } + if info.IsDir() { + entries = append(entries, fmt.Sprintf("%s %s", path, info.ModTime().String())) + } else if !ignoredMap[path] && (c.allExts || c.extMap[filepath.Ext(info.Name())]) { + entries = append(entries, fmt.Sprintf("%s %d %d", path, info.Size(), info.ModTime().Unix())) + } + return nil + }) + if err != nil { + return err + } + + sort.Strings(entries) + + h := sha1.New() + for _, e := range entries { + h.Write([]byte(e)) + } + c.checksum = fmt.Sprintf("%x", h.Sum(nil)) + + return nil +} diff --git a/proto/model.proto b/proto/model.proto index 3a4a7061f..eb680a207 100644 --- a/proto/model.proto +++ b/proto/model.proto @@ -59,3 +59,10 @@ message TrackInfo { Type type = 2; string name = 3; } + +message DataMessage { + oneof value { + string text = 1; + bytes binary = 2; + } +}