From 69ff8ae8963da503ffa11c166f03c6cf201e31ac Mon Sep 17 00:00:00 2001 From: sergystepanov Date: Mon, 20 Sep 2021 10:17:59 +0300 Subject: [PATCH] Update cloud data storage functionality (#349) Add Oracle Data Storage support for cloud saves. --- .github/workflows/cd/cloudretro.io/worker.env | 2 + configs/config.yaml | 11 ++ go.mod | 33 +++--- go.sum | 100 ++++++++-------- pkg/config/storage/config.go | 6 + pkg/config/worker/config.go | 2 + pkg/emulator/emulator.go | 6 +- pkg/emulator/libretro/nanoarch/naemulator.go | 41 ++----- .../libretro/nanoarch/nanoarch_test.go | 7 +- pkg/emulator/libretro/nanoarch/storage.go | 16 +++ pkg/storage/google.go | 74 ++++++++++++ .../google_test.go} | 10 +- pkg/storage/noop.go | 19 ++++ pkg/storage/oracle.go | 107 ++++++++++++++++++ pkg/storage/oracle_test.go | 54 +++++++++ pkg/storage/storage.go | 6 + pkg/webrtc/connection.go | 52 +++++---- pkg/worker/cloud-storage/storage.go | 97 ---------------- pkg/worker/handlers.go | 28 ++++- pkg/worker/internalhandlers.go | 2 +- pkg/worker/room/room.go | 55 +++++---- pkg/worker/room/room_test.go | 5 +- 22 files changed, 470 insertions(+), 263 deletions(-) create mode 100644 pkg/config/storage/config.go create mode 100644 pkg/emulator/libretro/nanoarch/storage.go create mode 100644 pkg/storage/google.go rename pkg/{worker/cloud-storage/storage_test.go => storage/google_test.go} (75%) create mode 100644 pkg/storage/noop.go create mode 100644 pkg/storage/oracle.go create mode 100644 pkg/storage/oracle_test.go create mode 100644 pkg/storage/storage.go delete mode 100644 pkg/worker/cloud-storage/storage.go diff --git a/.github/workflows/cd/cloudretro.io/worker.env b/.github/workflows/cd/cloudretro.io/worker.env index 606ec637..99c1bf07 100644 --- a/.github/workflows/cd/cloudretro.io/worker.env +++ b/.github/workflows/cd/cloudretro.io/worker.env @@ -1,3 +1,5 @@ +CLOUD_GAME_STORAGE_KEY=https://objectstorage.eu-frankfurt-1.oraclecloud.com/p/VVlJPTNcP28wlnBXtA0ezjD5fTut2T01qz5TVwdNejQc6OR1DF5VYYjKTTP2HIlL/n/frmb1qagq0wf/b/cloud-retro-st-001/o/ +CLOUD_GAME_STORAGE_PROVIDER=oracle CLOUD_GAME_WORKER_NETWORK_COORDINATORADDRESS=cloudretro.io CLOUD_GAME_WORKER_NETWORK_PUBLICADDRESS=cloudretro.io CLOUD_GAME_WORKER_NETWORK_SECURE=true diff --git a/configs/config.yaml b/configs/config.yaml index 3fa0d0b8..22fb1fdb 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -203,6 +203,17 @@ encoder: # (experimental) withoutGame: false +storage: + # cloud storage provider: + # - empty (No op storage stub) + # - google [Google Cloud Storage](https://cloud.google.com/storage) + # - oracle [Oracle Object Storage](https://www.oracle.com/cloud/storage/object-storage.html) + provider: + # this value contains arbitrary key attribute: + # - google: not used (see: https://cloud.google.com/docs/authentication/production#create_service_account) + # - oracle: pre-authenticated URL (see: https://docs.oracle.com/en-us/iaas/Content/Object/Tasks/usingpreauthenticatedrequests.htm) + key: + webrtc: # turn off default Pion interceptors for performance reasons # (experimental) diff --git a/go.mod b/go.mod index 5578a13f..903710a2 100644 --- a/go.mod +++ b/go.mod @@ -3,35 +3,36 @@ module github.com/giongto35/cloud-game/v2 go 1.13 require ( - cloud.google.com/go v0.91.1 // indirect - cloud.google.com/go/storage v1.16.0 + cloud.google.com/go/storage v1.16.1 github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 - github.com/fsnotify/fsnotify v1.4.9 - github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f + github.com/fsnotify/fsnotify v1.5.1 + github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 github.com/gofrs/flock v0.8.1 github.com/gofrs/uuid v4.0.0+incompatible - github.com/golang/glog v0.0.0-20210429001901-424d2337a529 + github.com/golang/glog v1.0.0 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/gorilla/websocket v1.4.2 github.com/kkyr/fig v0.3.0 + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect github.com/pion/ice/v2 v2.1.12 // indirect - github.com/pion/interceptor v0.0.15 - github.com/pion/rtp v1.7.1 + github.com/pion/interceptor v0.0.19 + github.com/pion/rtcp v1.2.8 // indirect + github.com/pion/rtp v1.7.2 github.com/pion/srtp/v2 v2.0.5 // indirect github.com/pion/webrtc/v3 v3.0.32 github.com/prometheus/client_golang v1.11.0 github.com/prometheus/common v0.30.0 // indirect - github.com/prometheus/procfs v0.7.2 // indirect + github.com/prometheus/procfs v0.7.3 // indirect github.com/spf13/pflag v1.0.5 - github.com/veandco/go-sdl2 v0.4.8 - golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e + github.com/veandco/go-sdl2 v0.4.10 + golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d - golang.org/x/mod v0.5.0 // indirect - golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect - golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 // indirect - golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 // indirect + golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect + golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/api v0.54.0 // indirect - google.golang.org/genproto v0.0.0-20210816143620-e15ff196659d // indirect + google.golang.org/api v0.57.0 // indirect + google.golang.org/genproto v0.0.0-20210916144049-3192f974c780 // indirect ) diff --git a/go.sum b/go.sum index 33f69722..5d95ee94 100644 --- a/go.sum +++ b/go.sum @@ -21,10 +21,10 @@ cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.91.1 h1:w+u8ttN/QtYrpvgXNUd2G6kwqrqCIQbkINlXQjHP1ek= -cloud.google.com/go v0.91.1/go.mod h1:V358WZfbFQkmC3gv5XCxzZq2e3h7OGvQR0IXtj77ylI= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1 h1:DwuSvDZ1pTYGbXo8yOJevCTr3BoBlE+OVkHAKiYQUXc= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -42,8 +42,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.16.0 h1:1UwAux2OZP4310YXg5ohqBEpV16Y93uZG4+qOX7K2Kg= -cloud.google.com/go/storage v1.16.0/go.mod h1:ieKBmUyzcftN5tbxwnXClMKH00CfcQ+xL6NN0r5QfmE= +cloud.google.com/go/storage v1.16.1 h1:sMEIc4wxvoY3NXG7Rn9iP7jb/2buJgWR1vNXCR/UPfs= +cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -63,8 +63,9 @@ github.com/cavaliercoder/grab v1.0.1-0.20201108051000-98a5bfe305ec/go.mod h1:NbX github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -87,11 +88,12 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU= -github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= +github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 h1:8q7+xl2D2qHPLTII1t4vSMNP2VKwDcn+Avf2WXvdB1A= +github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -109,8 +111,8 @@ github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPh github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= -github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -180,17 +182,16 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -204,7 +205,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= @@ -221,8 +221,9 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -237,8 +238,9 @@ github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= -github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0= github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg= github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8= @@ -247,21 +249,23 @@ github.com/pion/ice/v2 v2.1.10/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnp github.com/pion/ice/v2 v2.1.12 h1:ZDBuZz+fEI7iDifZCYFVzI4p0Foy0YhdSSZ87ZtRcRE= github.com/pion/ice/v2 v2.1.12/go.mod h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU= github.com/pion/interceptor v0.0.13/go.mod h1:svsW2QoLHLoGLUr4pDoSopGBEWk8FZwlfxId/OKRKzo= -github.com/pion/interceptor v0.0.15 h1:pQFkBUL8akUHiGoFr+pM94Q/15x7sLFh0K3Nj+DCC6s= -github.com/pion/interceptor v0.0.15/go.mod h1:pg3J253eGi5bqyKzA74+ej5Y19ez2jkWANVnF+Z9Dfk= +github.com/pion/interceptor v0.0.19 h1:NkxrKHVH7ulrkVHTcZRJubgsF1oJeLQUvMsX1Kqm8to= +github.com/pion/interceptor v0.0.19/go.mod h1:mv0Q0oPHxjRY8xz5v85G6aIqb1Tb0G0mxrZOaewHiVo= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo= github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0= +github.com/pion/rtcp v1.2.7/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= +github.com/pion/rtcp v1.2.8 h1:Cys8X6r0xxU65ESTmXkqr8eU1Q1Wx+lNkoZCUH4zD7E= +github.com/pion/rtcp v1.2.8/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo= github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= -github.com/pion/rtp v1.7.1 h1:hCaxfVgPGt13eF/Tu9RhVn04c+dAcRZmhdDWqUE13oY= -github.com/pion/rtp v1.7.1/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= +github.com/pion/rtp v1.7.2 h1:HCDKDCixh7PVjkQTsqHAbk1lg+bx059EHxcnyl42dYs= +github.com/pion/rtp v1.7.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko= github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0= github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY= github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s= @@ -307,8 +311,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.2 h1:zE6zJjRS9S916ptrZ326OU0++1XRwHgxkvCFflxx6Fo= -github.com/prometheus/procfs v0.7.2/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= @@ -327,8 +331,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/veandco/go-sdl2 v0.4.8 h1:A26KeX6R1CGt/BQGEov6oxYmVGMMEWDVqTvK1tXvahE= -github.com/veandco/go-sdl2 v0.4.8/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= +github.com/veandco/go-sdl2 v0.4.10 h1:8QoD2bhWl7SbQDflIAUYWfl9Vq+mT8/boJFAUzAScgY= +github.com/veandco/go-sdl2 v0.4.10/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -350,8 +354,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= -golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 h1:3erb+vDS8lU1sxfDHF4/hhWyaXnhIaO+7RgL4fDZORA= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -377,7 +381,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= @@ -390,8 +393,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 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= @@ -437,8 +438,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -451,11 +452,10 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o= -golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -524,8 +524,10 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 h1:ikCpsnYR+Ew0vu99XlDp55lGgDJdIMx3f4a18jfse/s= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210915083310-ed5796bab164 h1:7ZDGnxgHAMw7thfC5bEos0RDAccZKxioiWBhfIe+tvw= +golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -591,7 +593,6 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= @@ -621,12 +622,12 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.49.0/go.mod h1:BECiH72wsfwUvOVn3+btPD5WHi0LzavZReBndi42L18= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7SrU= -google.golang.org/api v0.54.0 h1:ECJUVngj71QI6XEm7b1sAf8BljU5inEhMbKPR8Lxhhk= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -679,18 +680,19 @@ google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210624174822-c5cf32407d0a/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210811021853-ddbe55d93216/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210816143620-e15ff196659d h1:fPtHPeysWvGVJwQFKu3B7H2DB2sOEsW7UTayKkWESKw= -google.golang.org/genproto v0.0.0-20210816143620-e15ff196659d/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210916144049-3192f974c780 h1:RE6jTVCXBKZ7U9atSg8N3bsjRvvUujhEPspbEhdyy8s= +google.golang.org/genproto v0.0.0-20210916144049-3192f974c780/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/pkg/config/storage/config.go b/pkg/config/storage/config.go new file mode 100644 index 00000000..fd528c4d --- /dev/null +++ b/pkg/config/storage/config.go @@ -0,0 +1,6 @@ +package storage + +type Storage struct { + Provider string + Key string +} diff --git a/pkg/config/worker/config.go b/pkg/config/worker/config.go index ccd5ea10..c0aaaf3a 100644 --- a/pkg/config/worker/config.go +++ b/pkg/config/worker/config.go @@ -11,6 +11,7 @@ import ( "github.com/giongto35/cloud-game/v2/pkg/config/encoder" "github.com/giongto35/cloud-game/v2/pkg/config/monitoring" "github.com/giongto35/cloud-game/v2/pkg/config/shared" + "github.com/giongto35/cloud-game/v2/pkg/config/storage" webrtcConfig "github.com/giongto35/cloud-game/v2/pkg/config/webrtc" "github.com/giongto35/cloud-game/v2/pkg/environment" flag "github.com/spf13/pflag" @@ -20,6 +21,7 @@ type Config struct { Encoder encoder.Encoder Emulator emulator.Emulator Environment shared.Environment + Storage storage.Storage Worker Worker Webrtc webrtcConfig.Webrtc } diff --git a/pkg/emulator/emulator.go b/pkg/emulator/emulator.go index f9fe0a79..9a8d57f1 100644 --- a/pkg/emulator/emulator.go +++ b/pkg/emulator/emulator.go @@ -2,7 +2,7 @@ package emulator import "github.com/giongto35/cloud-game/v2/pkg/emulator/image" -// CloudEmulator is the interface of cloud emulator. Currently NES emulator and RetroArch implements this in codebase +// CloudEmulator is the interface of cloud emulator. type CloudEmulator interface { // LoadMeta returns meta data of emulator. Refer below LoadMeta(path string) Metadata @@ -12,8 +12,8 @@ type CloudEmulator interface { SetViewport(width int, height int) // GetViewport debug encoder image GetViewport() interface{} - // SaveGame save game state, saveExtraFunc is callback to do extra step. Ex: save to google cloud - SaveGame(saveExtraFunc func() error) error + // SaveGame save game state + SaveGame() error // LoadGame load game state LoadGame() error // GetHashPath returns the path emulator will save state to diff --git a/pkg/emulator/libretro/nanoarch/naemulator.go b/pkg/emulator/libretro/nanoarch/naemulator.go index 284668f5..82157142 100644 --- a/pkg/emulator/libretro/nanoarch/naemulator.go +++ b/pkg/emulator/libretro/nanoarch/naemulator.go @@ -50,7 +50,6 @@ void coreLog_cgo(enum retro_log_level level, const char *msg); */ import "C" -// naEmulator implements CloudEmulator type naEmulator struct { sync.Mutex @@ -71,16 +70,6 @@ type naEmulator struct { done chan struct{} } -type Storage struct { - // save path without the dir slash in the end - Path string - // contains the name of the main save file - // e.g. abc<...>293.dat - // needed for Google Cloud save/restore which - // doesn't support multiple files - MainSave string -} - // VideoExporter produces image frame to unix socket type VideoExporter struct { sock net.Conn @@ -190,7 +179,11 @@ func (na *naEmulator) SetViewport(width int, height int) { } func (na *naEmulator) Start() { - na.playGame() + err := na.LoadGame() + if err != nil { + log.Printf("error: couldn't load a save, %v", err) + } + ticker := time.NewTicker(time.Second / time.Duration(na.meta.Fps)) for range ticker.C { @@ -211,23 +204,10 @@ func (na *naEmulator) Start() { } } -func (na *naEmulator) playGame() { - // When start game, we also try loading if there was a saved state - na.LoadGame() -} - -func (na *naEmulator) SaveGame(saveExtraFunc func() error) error { +func (na *naEmulator) SaveGame() error { if na.roomID != "" { - err := na.Save() - if err != nil { - return err - } - err = saveExtraFunc() - if err != nil { - return err - } + return na.Save() } - return nil } @@ -235,11 +215,9 @@ func (na *naEmulator) LoadGame() error { if na.roomID != "" { err := na.Load() if err != nil { - log.Println("Error: Cannot load", err) return err } } - return nil } @@ -247,13 +225,12 @@ func (na *naEmulator) ToggleMultitap() error { if na.roomID != "" { toggleMultitap() } - return nil } -func (na *naEmulator) GetHashPath() string { return na.storage.Path + "/" + na.storage.MainSave } +func (na *naEmulator) GetHashPath() string { return na.storage.GetSavePath() } -func (na *naEmulator) GetSRAMPath() string { return na.storage.Path + "/" + na.roomID + ".srm" } +func (na *naEmulator) GetSRAMPath() string { return na.storage.GetSRAMPath() } func (*naEmulator) GetViewport() interface{} { return outputImg diff --git a/pkg/emulator/libretro/nanoarch/nanoarch_test.go b/pkg/emulator/libretro/nanoarch/nanoarch_test.go index 8f7f7383..10bab92d 100644 --- a/pkg/emulator/libretro/nanoarch/nanoarch_test.go +++ b/pkg/emulator/libretro/nanoarch/nanoarch_test.go @@ -69,7 +69,7 @@ func GetEmulatorMock(room string, system string) *EmulatorMock { store := Storage{ Path: os.TempDir(), - MainSave: room + ".dat", + MainSave: room, } // an emu @@ -134,6 +134,11 @@ func (emu *EmulatorMock) loadRom(game string) { fmt.Printf("%v %v\n", emu.paths.cores, emu.core) coreLoad(emulator.Metadata{LibPath: emu.paths.cores + emu.core}) coreLoadGame(emu.paths.games + game) + + if emu.canvas.Rect.Dx() == 0 || emu.canvas.Rect.Dy() == 0 { + emu.canvas = image.NewRGBA(image.Rect(0, 0, emu.meta.BaseWidth, emu.meta.BaseHeight)) + outputImg = emu.canvas + } } // shutdownEmulator closes the emulator and cleans its resources. diff --git a/pkg/emulator/libretro/nanoarch/storage.go b/pkg/emulator/libretro/nanoarch/storage.go new file mode 100644 index 00000000..724ced3c --- /dev/null +++ b/pkg/emulator/libretro/nanoarch/storage.go @@ -0,0 +1,16 @@ +package nanoarch + +import "path/filepath" + +type Storage struct { + // save path without the dir slash in the end + Path string + // contains the name of the main save file + // e.g. abc<...>293.dat + // needed for Google Cloud save/restore which + // doesn't support multiple files + MainSave string +} + +func (s *Storage) GetSavePath() string { return filepath.Join(s.Path, s.MainSave+".dat") } +func (s *Storage) GetSRAMPath() string { return filepath.Join(s.Path, s.MainSave+".srm") } diff --git a/pkg/storage/google.go b/pkg/storage/google.go new file mode 100644 index 00000000..404b4c04 --- /dev/null +++ b/pkg/storage/google.go @@ -0,0 +1,74 @@ +package storage + +import ( + "context" + "errors" + "io" + "io/ioutil" + "log" + "os" + + "cloud.google.com/go/storage" +) + +type GoogleCloudClient struct { + bucket *storage.BucketHandle + ctx context.Context +} + +// NewGoogleCloudClient returns a Google Cloud Storage client or nil if the client is not initialized. +func NewGoogleCloudClient() (*GoogleCloudClient, error) { + ctx := context.Background() + client, err := storage.NewClient(ctx) + if err != nil { + log.Printf("warn: failed to create Google Cloud Storage client: %v", err) + return nil, err + } + bucket := client.Bucket("game-save") + return &GoogleCloudClient{bucket: bucket}, nil +} + +// Save saves a file to GCS. +func (c *GoogleCloudClient) Save(name string, srcFile string) (err error) { + // Bypass if client is nil + if c == nil { + return nil + } + + reader, err := os.Open(srcFile) + if err != nil { + return err + } + + wc := c.bucket.Object(name).NewWriter(c.ctx) + if _, err = io.Copy(wc, reader); err != nil { + return err + } + if err := wc.Close(); err != nil { + return err + } + + return nil +} + +// Load loads file from GCS. +func (c *GoogleCloudClient) Load(name string) (data []byte, err error) { + // Bypass if client is nil + if c == nil { + return nil, errors.New("cloud storage was not initialized") + } + + rc, err := c.bucket.Object(name).NewReader(c.ctx) + if err != nil { + return nil, err + } + defer func() { + _ = rc.Close() + }() + + data, err = ioutil.ReadAll(rc) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/worker/cloud-storage/storage_test.go b/pkg/storage/google_test.go similarity index 75% rename from pkg/worker/cloud-storage/storage_test.go rename to pkg/storage/google_test.go index cd0e774e..3989b554 100644 --- a/pkg/worker/cloud-storage/storage_test.go +++ b/pkg/storage/google_test.go @@ -8,7 +8,7 @@ import ( ) func TestSaveGame(t *testing.T) { - client := NewInitClient() + client, _ := NewGoogleCloudClient() if client == nil { t.Skip("Cloud storage is not initialized") } @@ -18,17 +18,19 @@ func TestSaveGame(t *testing.T) { if err != nil { t.Errorf("Temp dir is not accessable %v", err) } - defer os.Remove(file.Name()) + defer func(name string) { + err = os.Remove(name) + }(file.Name()) if err = ioutil.WriteFile(file.Name(), data, 0644); err != nil { t.Errorf("File is not writable %v", err) } - err = client.SaveFile("Test", file.Name()) + err = client.Save("Test", file.Name()) if err != nil { log.Panic(err) } - loadData, err := client.LoadFile("Test") + loadData, err := client.Load("Test") if err != nil { log.Panic(err) } diff --git a/pkg/storage/noop.go b/pkg/storage/noop.go new file mode 100644 index 00000000..342aab29 --- /dev/null +++ b/pkg/storage/noop.go @@ -0,0 +1,19 @@ +package storage + +import "errors" + +type NoopCloudStorage struct{} + +var noopErr = errors.New("an empty storage stub") + +func NewNoopCloudStorage() (*NoopCloudStorage, error) { + return nil, noopErr +} + +func (n *NoopCloudStorage) Save(_ string, _ string) (err error) { + return nil +} + +func (n *NoopCloudStorage) Load(_ string) (data []byte, err error) { + return nil, noopErr +} diff --git a/pkg/storage/oracle.go b/pkg/storage/oracle.go new file mode 100644 index 00000000..19bec618 --- /dev/null +++ b/pkg/storage/oracle.go @@ -0,0 +1,107 @@ +package storage + +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "errors" + "fmt" + "io/ioutil" + "net/http" + "time" +) + +type OracleDataStorageClient struct { + accessURL string + client *http.Client +} + +// NewOracleDataStorageClient returns either a new Oracle Data Storage +// client or some error in case of failure. +// Oracle infrastructure access is based on pre-authenticated requests, +// see: https://docs.oracle.com/en-us/iaas/Content/Object/Tasks/usingpreauthenticatedrequests.htm +// +// It follows broken Google Cloud Storage client design. +func NewOracleDataStorageClient(accessURL string) (*OracleDataStorageClient, error) { + if accessURL == "" { + return nil, errors.New("pre-authenticated request was not specified") + } + return &OracleDataStorageClient{ + accessURL: accessURL, + client: &http.Client{ + Timeout: 10 * time.Second, + }, + }, nil +} + +func (s *OracleDataStorageClient) Save(name string, localPath string) (err error) { + if s == nil { + return nil + } + + dat, err := ioutil.ReadFile(localPath) + if err != nil { + return err + } + + req, err := http.NewRequest("PUT", s.accessURL+name, bytes.NewBuffer(dat)) + if err != nil { + return err + } + + resp, err := s.client.Do(req) + if err != nil { + return err + } + defer func() { + _ = resp.Body.Close() + }() + if resp.StatusCode != 200 { + return errors.New(resp.Status) + } + + dstMD5 := resp.Header.Get("Opc-Content-Md5") + srcMD5 := base64.StdEncoding.EncodeToString(md5Hash(dat)) + if dstMD5 != srcMD5 { + return fmt.Errorf("MD5 mismatch %v != %v", srcMD5, dstMD5) + } + + return nil +} + +func (s *OracleDataStorageClient) Load(name string) (data []byte, err error) { + if s == nil { + return nil, errors.New("cloud storage was not initialized") + } + + res, err := s.client.Get(s.accessURL + name) + if err != nil { + return nil, err + } + defer func() { + _ = res.Body.Close() + }() + + if res.StatusCode != 200 { + return nil, errors.New(res.Status) + } + + dat, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + dstMD5 := res.Header.Get("Content-Md5") + srcMD5 := base64.StdEncoding.EncodeToString(md5Hash(dat)) + if dstMD5 != srcMD5 { + return nil, fmt.Errorf("MD5 mismatch %v != %v", srcMD5, dstMD5) + } + + return dat, nil +} + +func md5Hash(data []byte) []byte { + hash := md5.New() + hash.Write(data) + return hash.Sum(nil) +} diff --git a/pkg/storage/oracle_test.go b/pkg/storage/oracle_test.go new file mode 100644 index 00000000..45d3ff73 --- /dev/null +++ b/pkg/storage/oracle_test.go @@ -0,0 +1,54 @@ +package storage + +import ( + "io/ioutil" + "net/http" + "os" + "strings" + "testing" +) + +type rtFunc func(req *http.Request) *http.Response + +func (f rtFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req), nil } + +func newTestClient(fn rtFunc) *http.Client { + return &http.Client{ + Transport: fn, + } +} + +func TestOracleSave(t *testing.T) { + client, err := NewOracleDataStorageClient("test-url/") + client.client = newTestClient(func(req *http.Request) *http.Response { + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(strings.NewReader("")), + Header: map[string][]string{ + "Opc-Content-Md5": {"CY9rzUYh03PK3k6DJie09g=="}, + }, + } + }) + + tempFile, err := ioutil.TempFile("", "oracle_test.file") + if err != nil { + t.Errorf("%v", err) + } + defer func() { + _ = tempFile.Close() + err := os.Remove(tempFile.Name()) + if err != nil { + t.Errorf("%v", err) + } + }() + + _, err = tempFile.WriteString("test") + if err != nil { + return + } + + err = client.Save("oracle_test.file", tempFile.Name()) + if err != nil { + t.Errorf("can't save, err: %v", err) + } +} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go new file mode 100644 index 00000000..36cfa8eb --- /dev/null +++ b/pkg/storage/storage.go @@ -0,0 +1,6 @@ +package storage + +type CloudStorage interface { + Save(name string, localPath string) (err error) + Load(name string) (data []byte, err error) +} diff --git a/pkg/webrtc/connection.go b/pkg/webrtc/connection.go index 7b46709f..f7577d37 100644 --- a/pkg/webrtc/connection.go +++ b/pkg/webrtc/connection.go @@ -3,11 +3,11 @@ package webrtc import ( conf "github.com/giongto35/cloud-game/v2/pkg/config/webrtc" "github.com/pion/interceptor" - . "github.com/pion/webrtc/v3" + pion "github.com/pion/webrtc/v3" ) -func NewInterceptedPeerConnection(conf conf.Webrtc, ics []interceptor.Interceptor, vCodec string) (*PeerConnection, error) { - m := &MediaEngine{} +func NewInterceptedPeerConnection(conf conf.Webrtc, ics []interceptor.Interceptor, vCodec string) (*pion.PeerConnection, error) { + m := &pion.MediaEngine{} //if err := m.RegisterDefaultCodecs(); err != nil { // return nil, err //} @@ -18,7 +18,7 @@ func NewInterceptedPeerConnection(conf conf.Webrtc, ics []interceptor.Intercepto i := &interceptor.Registry{} if !conf.DisableDefaultInterceptors { - if err := RegisterDefaultInterceptors(m, i); err != nil { + if err := pion.RegisterDefaultInterceptors(m, i); err != nil { return nil, err } } @@ -26,79 +26,83 @@ func NewInterceptedPeerConnection(conf conf.Webrtc, ics []interceptor.Intercepto i.Add(itc) } - settingEngine := SettingEngine{} + settingEngine := pion.SettingEngine{} if conf.IcePorts.Min > 0 && conf.IcePorts.Max > 0 { if err := settingEngine.SetEphemeralUDPPortRange(conf.IcePorts.Min, conf.IcePorts.Max); err != nil { return nil, err } } if conf.IceIpMap != "" { - settingEngine.SetNAT1To1IPs([]string{conf.IceIpMap}, ICECandidateTypeHost) + settingEngine.SetNAT1To1IPs([]string{conf.IceIpMap}, pion.ICECandidateTypeHost) } - peerConf := Configuration{ICEServers: []ICEServer{}} + peerConf := pion.Configuration{ICEServers: []pion.ICEServer{}} for _, server := range conf.IceServers { - peerConf.ICEServers = append(peerConf.ICEServers, ICEServer{ + peerConf.ICEServers = append(peerConf.ICEServers, pion.ICEServer{ URLs: []string{server.Url}, Username: server.Username, Credential: server.Credential, }) } - api := NewAPI(WithMediaEngine(m), WithInterceptorRegistry(i), WithSettingEngine(settingEngine)) + api := pion.NewAPI( + pion.WithMediaEngine(m), + pion.WithInterceptorRegistry(i), + pion.WithSettingEngine(settingEngine), + ) return api.NewPeerConnection(peerConf) } // RegisterCodecs registers the default codecs supported by WebRTC. -func RegisterCodecs(m *MediaEngine, vCodec string) error { - audioRTPCodecParameters := []RTPCodecParameters{ +func RegisterCodecs(m *pion.MediaEngine, vCodec string) error { + audioRTPCodecParameters := []pion.RTPCodecParameters{ { - RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeOpus, ClockRate: 48000, Channels: 2}, + RTPCodecCapability: pion.RTPCodecCapability{MimeType: pion.MimeTypeOpus, ClockRate: 48000, Channels: 2}, PayloadType: 111, }, } for _, codec := range audioRTPCodecParameters { - if err := m.RegisterCodec(codec, RTPCodecTypeAudio); err != nil { + if err := m.RegisterCodec(codec, pion.RTPCodecTypeAudio); err != nil { return err } } - videoRTCPFeedback := []RTCPFeedback{ + videoRTCPFeedback := []pion.RTCPFeedback{ {"goog-remb", ""}, {"ccm", "fir"}, {"nack", ""}, {"nack", "pli"}, } - video := RTPCodecCapability{MimeType: vCodec, ClockRate: 90000, RTCPFeedback: videoRTCPFeedback} - var videoRTPCodecParameters []RTPCodecParameters - if vCodec == MimeTypeH264 { - videoRTPCodecParameters = []RTPCodecParameters{ - {RTPCodecCapability: RTPCodecCapability{ + video := pion.RTPCodecCapability{MimeType: vCodec, ClockRate: 90000, RTCPFeedback: videoRTCPFeedback} + var videoRTPCodecParameters []pion.RTPCodecParameters + if vCodec == pion.MimeTypeH264 { + videoRTPCodecParameters = []pion.RTPCodecParameters{ + {RTPCodecCapability: pion.RTPCodecCapability{ MimeType: video.MimeType, ClockRate: video.ClockRate, RTCPFeedback: video.RTCPFeedback, //SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", }, PayloadType: 102}, - {RTPCodecCapability: RTPCodecCapability{ + {RTPCodecCapability: pion.RTPCodecCapability{ MimeType: video.MimeType, ClockRate: video.ClockRate, RTCPFeedback: video.RTCPFeedback, SDPFmtpLine: "level-asymmetry-allowed=1;profile-level-id=42e01f", }, PayloadType: 108}, {RTPCodecCapability: video, PayloadType: 123}, - {RTPCodecCapability: RTPCodecCapability{ + {RTPCodecCapability: pion.RTPCodecCapability{ MimeType: video.MimeType, ClockRate: video.ClockRate, RTCPFeedback: video.RTCPFeedback, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", }, PayloadType: 125}, - {RTPCodecCapability: RTPCodecCapability{ + {RTPCodecCapability: pion.RTPCodecCapability{ MimeType: video.MimeType, ClockRate: video.ClockRate, RTCPFeedback: video.RTCPFeedback, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", }, PayloadType: 127}, } } else { - videoRTPCodecParameters = []RTPCodecParameters{ + videoRTPCodecParameters = []pion.RTPCodecParameters{ {RTPCodecCapability: video, PayloadType: 96}, } } for _, codec := range videoRTPCodecParameters { - if err := m.RegisterCodec(codec, RTPCodecTypeVideo); err != nil { + if err := m.RegisterCodec(codec, pion.RTPCodecTypeVideo); err != nil { return err } } diff --git a/pkg/worker/cloud-storage/storage.go b/pkg/worker/cloud-storage/storage.go deleted file mode 100644 index 07f46b3d..00000000 --- a/pkg/worker/cloud-storage/storage.go +++ /dev/null @@ -1,97 +0,0 @@ -package storage - -import ( - "context" - "errors" - "io" - "io/ioutil" - "log" - "os" - - "cloud.google.com/go/storage" -) - -// TODO: Add interface, abstract out Gstorage -type Client struct { - bucket *storage.BucketHandle - gclient *storage.Client -} - -// NewInitClient returns nil of client is not initialized -func NewInitClient() *Client { - bucketName := "game-save" - - client, err := NewClient(bucketName) - if err != nil { - log.Printf("Warn: Failed to create client: %v", err) - } else { - log.Println("Online storage is initialized") - } - - return client -} - -// NewClient inits a new Client accessing to GCP -func NewClient(bucketName string) (*Client, error) { - ctx := context.Background() - - // Sets your Google Cloud Platform project ID. - - // Creates a client. - gclient, err := storage.NewClient(ctx) - if err != nil { - return nil, err - } - - // Creates a Bucket instance. - bucket := gclient.Bucket(bucketName) - - return &Client{ - bucket: bucket, - gclient: gclient, - }, nil -} - -// Savefile save srcFile to GCP -func (c *Client) SaveFile(name string, srcFile string) (err error) { - // Bypass if client is nil - if c == nil { - return nil - } - - reader, err := os.Open(srcFile) - if err != nil { - return err - } - - // Copy source file to GCP - wc := c.bucket.Object(name).NewWriter(context.Background()) - if _, err = io.Copy(wc, reader); err != nil { - return err - } - if err := wc.Close(); err != nil { - return err - } - - return nil -} - -// Loadfile loads file from GCP -func (c *Client) LoadFile(name string) (data []byte, err error) { - // Bypass if client is nil - if c == nil { - return nil, errors.New("cloud storage was not initialized") - } - - rc, err := c.bucket.Object(name).NewReader(context.Background()) - if err != nil { - return nil, err - } - defer rc.Close() - - data, err = ioutil.ReadAll(rc) - if err != nil { - return nil, err - } - return data, nil -} diff --git a/pkg/worker/handlers.go b/pkg/worker/handlers.go index 9954bd8a..1b33c95f 100644 --- a/pkg/worker/handlers.go +++ b/pkg/worker/handlers.go @@ -15,8 +15,8 @@ import ( "github.com/giongto35/cloud-game/v2/pkg/games" "github.com/giongto35/cloud-game/v2/pkg/network/websocket" "github.com/giongto35/cloud-game/v2/pkg/service" + "github.com/giongto35/cloud-game/v2/pkg/storage" "github.com/giongto35/cloud-game/v2/pkg/webrtc" - storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage" "github.com/giongto35/cloud-game/v2/pkg/worker/room" ) @@ -32,17 +32,14 @@ type Handler struct { // global ID of the current server serverID string // onlineStorage is client accessing to online storage (GCP) - onlineStorage *storage.Client + onlineStorage storage.CloudStorage // sessions handles all sessions server is handler (key is sessionID) sessions map[string]*Session } -// NewHandler returns a new server func NewHandler(conf worker.Config, address string) *Handler { - // Create offline storage folder createOfflineStorage(conf.Emulator.Storage) - // Init online storage - onlineStorage := storage.NewInitClient() + onlineStorage := initCloudStorage(conf) return &Handler{ address: address, cfg: conf, @@ -92,6 +89,25 @@ func (h *Handler) Prepare() { } } +func initCloudStorage(conf worker.Config) storage.CloudStorage { + var st storage.CloudStorage + var err error + switch conf.Storage.Provider { + case "google": + st, err = storage.NewGoogleCloudClient() + case "oracle": + st, err = storage.NewOracleDataStorageClient(conf.Storage.Key) + case "coordinator": + default: + st, _ = storage.NewNoopCloudStorage() + } + if err != nil { + log.Printf("Switching to noop cloud save") + st, _ = storage.NewNoopCloudStorage() + } + return st +} + func newCoordinatorConnection(host string, conf worker.Worker, addr string) (*CoordinatorClient, error) { scheme := "ws" if conf.Network.Secure { diff --git a/pkg/worker/internalhandlers.go b/pkg/worker/internalhandlers.go index 58755b3a..d2d628f6 100644 --- a/pkg/worker/internalhandlers.go +++ b/pkg/worker/internalhandlers.go @@ -160,7 +160,7 @@ func (h *Handler) handleGameSave() cws.PacketHandler { } err := room.SaveGame() if err != nil { - log.Println("[!] Cannot save game state: ", err) + log.Printf("error, cannot save game: %v", err) req.Data = "error" } } else { diff --git a/pkg/worker/room/room.go b/pkg/worker/room/room.go index 65039dc8..c407d52a 100644 --- a/pkg/worker/room/room.go +++ b/pkg/worker/room/room.go @@ -3,6 +3,7 @@ package room import ( "bytes" "encoding/gob" + "errors" "fmt" "io" "io/ioutil" @@ -19,8 +20,8 @@ import ( "github.com/giongto35/cloud-game/v2/pkg/encoder" "github.com/giongto35/cloud-game/v2/pkg/games" "github.com/giongto35/cloud-game/v2/pkg/session" + "github.com/giongto35/cloud-game/v2/pkg/storage" "github.com/giongto35/cloud-game/v2/pkg/webrtc" - storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage" ) // Room is a game session. multi webRTC sessions can connect to a same game. @@ -51,7 +52,7 @@ type Room struct { // Director is emulator director emulator.CloudEmulator // Cloud storage to store room state online - onlineStorage *storage.Client + onlineStorage storage.CloudStorage vPipe *encoder.VideoPipe } @@ -119,7 +120,7 @@ func NewVideoImporter(roomID string) chan nanoarch.GameFrame { } // NewRoom creates a new room -func NewRoom(roomID string, game games.GameMetadata, onlineStorage *storage.Client, cfg worker.Config) *Room { +func NewRoom(roomID string, game games.GameMetadata, onlineStorage storage.CloudStorage, cfg worker.Config) *Room { if roomID == "" { roomID = session.GenerateRoomID(game.Name) } @@ -146,13 +147,13 @@ func NewRoom(roomID string, game games.GameMetadata, onlineStorage *storage.Clie go func(game games.GameMetadata, roomID string) { store := nanoarch.Storage{ Path: cfg.Emulator.Storage, - MainSave: roomID + ".dat", + MainSave: roomID, } // Check room is on local or fetch from server - log.Printf("Check %s on online storage: %v", roomID, isGameOnLocal(store.MainSave)) - if err := room.saveOnlineRoomToLocal(roomID, store.MainSave); err != nil { - log.Printf("Warn: Room %s is not in online storage, error %s", roomID, err) + log.Printf("Check for %s in the online storage", roomID) + if err := room.saveOnlineRoomToLocal(roomID, store.GetSavePath()); err != nil { + log.Printf("warn: room %s is not in the online storage, error %s", roomID, err) } // If not then load room or create room from local. @@ -227,8 +228,13 @@ func resizeToAspect(ratio float64, sw int, sh int) (dw int, dh int) { } func isGameOnLocal(path string) bool { - _, err := os.Open(path) - return err == nil + file, err := os.Open(path) + if err == nil { + defer func() { + _ = file.Close() + }() + } + return !errors.Is(err, os.ErrNotExist) } func (r *Room) AddConnectionToRoom(peerconnection *webrtc.WebRTC) { @@ -353,45 +359,38 @@ func (r *Room) Close() { func (r *Room) isRoomExisted() bool { // Check if room is in online storage - _, err := r.onlineStorage.LoadFile(r.ID) + _, err := r.onlineStorage.Load(r.ID) if err == nil { return true } return isGameOnLocal(r.director.GetHashPath()) } -// SaveGame will save game to local and trigger a callback to store game on onlineStorage, so the game can be accessed later +// SaveGame writes save state on the disk as well as +// uploads it to a cloud storage. func (r *Room) SaveGame() error { - onlineSaveFunc := func() error { - // Try to save the game to gCloud - if err := r.onlineStorage.SaveFile(r.ID, r.director.GetHashPath()); err != nil { - return err - } - - return nil - } - // TODO: Move to game view - if err := r.director.SaveGame(onlineSaveFunc); err != nil { + if err := r.director.SaveGame(); err != nil { return err } - + if err := r.onlineStorage.Save(r.ID, r.director.GetHashPath()); err != nil { + return err + } + log.Printf("success, cloud save") return nil } // saveOnlineRoomToLocal save online room to local. // !Supports only one file of main save state. func (r *Room) saveOnlineRoomToLocal(roomID string, savePath string) error { - log.Println("Check if game is on cloud storage") - // If the game is not on local server - // Try to load from gcloud - data, err := r.onlineStorage.LoadFile(roomID) + data, err := r.onlineStorage.Load(roomID) if err != nil { return err } - // Save the data fetched from gcloud to local server + // Save the data fetched from a cloud provider to the local server if data != nil { - _ = ioutil.WriteFile(savePath, data, 0644) + err = ioutil.WriteFile(savePath, data, 0644) + log.Printf("successfully downloaded cloud save") } return nil } diff --git a/pkg/worker/room/room_test.go b/pkg/worker/room/room_test.go index 1f5ee55e..3264e2c6 100644 --- a/pkg/worker/room/room_test.go +++ b/pkg/worker/room/room_test.go @@ -23,8 +23,8 @@ import ( "github.com/giongto35/cloud-game/v2/pkg/emulator/libretro/manager/remotehttp" "github.com/giongto35/cloud-game/v2/pkg/encoder" "github.com/giongto35/cloud-game/v2/pkg/games" + "github.com/giongto35/cloud-game/v2/pkg/storage" "github.com/giongto35/cloud-game/v2/pkg/thread" - storage "github.com/giongto35/cloud-game/v2/pkg/worker/cloud-storage" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" "golang.org/x/image/math/fixed" @@ -238,7 +238,8 @@ func getRoomMock(cfg roomMockConfig) roomMock { } conf.Encoder.Video.Codec = string(cfg.vCodec) - room := NewRoom(cfg.roomName, cfg.game, storage.NewInitClient(), conf) + cloudStore, _ := storage.NewNoopCloudStorage() + room := NewRoom(cfg.roomName, cfg.game, cloudStore, conf) // loop-wait the room initialization var init sync.WaitGroup