Changing password and use cache when uploading
This commit is contained in:
@@ -8,6 +8,7 @@ database:
|
|||||||
username: root
|
username: root
|
||||||
features:
|
features:
|
||||||
allow_register: false
|
allow_register: false
|
||||||
|
password_hash_cost: 16
|
||||||
path:
|
path:
|
||||||
cache: "/var/osc/cache"
|
cache: "/var/osc/cache"
|
||||||
storage: "/var/osc/storage"
|
storage: "/var/osc/storage"
|
||||||
@@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -31,7 +32,8 @@ type DatabaseConfiguration struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FeaturesConfiguration struct {
|
type FeaturesConfiguration struct {
|
||||||
AllowRegister bool `yaml:"allow_register"`
|
AllowRegister bool `yaml:"allow_register"`
|
||||||
|
PasswordHashCost *int `yaml:"password_hash_cost"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentConfig *Configuration
|
var currentConfig *Configuration
|
||||||
@@ -47,6 +49,22 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error: %s", err)
|
log.Fatalf("error: %s", err)
|
||||||
}
|
}
|
||||||
|
checkConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConfig() {
|
||||||
|
if currentConfig.Features.PasswordHashCost == nil {
|
||||||
|
currentConfig.Features.PasswordHashCost = new(int)
|
||||||
|
*currentConfig.Features.PasswordHashCost = bcrypt.DefaultCost
|
||||||
|
} else if *currentConfig.Features.PasswordHashCost < bcrypt.MinCost && *currentConfig.Features.PasswordHashCost > bcrypt.MaxCost {
|
||||||
|
log.Fatalf("password_hash_cost is not on the supported range (%d < x < %d)", bcrypt.MinCost, bcrypt.MaxCost)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(currentConfig.Path.Storage); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(currentConfig.Path.Cache); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Database() *DatabaseConfiguration {
|
func Database() *DatabaseConfiguration {
|
||||||
|
|||||||
@@ -1,31 +1,23 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"mime/multipart"
|
|
||||||
"opensavecloudserver/config"
|
"opensavecloudserver/config"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
locks map[int]GameUploadToken
|
|
||||||
mu sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
|
|
||||||
|
const adminRole string = "admin"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
locks = make(map[int]GameUploadToken)
|
|
||||||
dbConfig := config.Database()
|
dbConfig := config.Database()
|
||||||
var err error
|
var err error
|
||||||
connectionString := ""
|
connectionString := ""
|
||||||
@@ -55,12 +47,6 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Minute)
|
|
||||||
clearLocks()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserByUsername get a user by the username
|
// UserByUsername get a user by the username
|
||||||
@@ -70,6 +56,9 @@ func UserByUsername(username string) (*User, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if user.Role == adminRole {
|
||||||
|
user.IsAdmin = true
|
||||||
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +69,9 @@ func UserById(userId int) (*User, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if user.Role == adminRole {
|
||||||
|
user.IsAdmin = true
|
||||||
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +84,16 @@ func AddUser(username string, password []byte) error {
|
|||||||
return db.Save(user).Error
|
return db.Save(user).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAdmin register a user and set his role to admin
|
||||||
|
func AddAdmin(username string, password []byte) error {
|
||||||
|
user := &User{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
Role: adminRole,
|
||||||
|
}
|
||||||
|
return db.Save(user).Error
|
||||||
|
}
|
||||||
|
|
||||||
// GameInfoById return information of a game
|
// GameInfoById return information of a game
|
||||||
func GameInfoById(userId, gameId int) (*Game, error) {
|
func GameInfoById(userId, gameId int) (*Game, error) {
|
||||||
var game *Game
|
var game *Game
|
||||||
@@ -128,51 +130,6 @@ func CreateGame(userId int, name string) (*Game, error) {
|
|||||||
return game, nil
|
return game, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AskForUpload Create a lock for upload a new revision of a game
|
|
||||||
func AskForUpload(userId, gameId int) (*GameUploadToken, error) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
_, err := GameInfoById(userId, gameId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, ok := locks[gameId]; !ok {
|
|
||||||
token := uuid.New()
|
|
||||||
lock := GameUploadToken{
|
|
||||||
GameId: gameId,
|
|
||||||
UploadToken: token.String(),
|
|
||||||
}
|
|
||||||
locks[gameId] = lock
|
|
||||||
return &lock, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("game already locked")
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckUploadToken(uploadToken string) (int, bool) {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
for _, lock := range locks {
|
|
||||||
if lock.UploadToken == uploadToken {
|
|
||||||
return lock.GameId, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func UploadSave(file multipart.File, game *Game) error {
|
|
||||||
filePath := path.Join(config.Path().Storage, game.PathStorage)
|
|
||||||
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = io.Copy(f, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateGameRevision(game *Game, hash string) error {
|
func UpdateGameRevision(game *Game, hash string) error {
|
||||||
game.Revision += 1
|
game.Revision += 1
|
||||||
if game.Hash == nil {
|
if game.Hash == nil {
|
||||||
@@ -191,24 +148,20 @@ func UpdateGameRevision(game *Game, hash string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnlockGame(gameId int) {
|
// ChangePassword change the password of the user, the param 'password' must be the clear password
|
||||||
mu.Lock()
|
func ChangePassword(userId int, password []byte) error {
|
||||||
defer mu.Unlock()
|
user, err := UserById(userId)
|
||||||
delete(locks, gameId)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
// clearLocks clear lock of zombi upload
|
|
||||||
func clearLocks() {
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
now := time.Now()
|
|
||||||
toUnlock := make([]int, 0)
|
|
||||||
for gameId, lock := range locks {
|
|
||||||
if lock.Expire.After(now) {
|
|
||||||
toUnlock = append(toUnlock, gameId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, gameId := range toUnlock {
|
hashedPassword, err := bcrypt.GenerateFromPassword(password, *config.Features().PasswordHashCost)
|
||||||
delete(locks, gameId)
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
user.Password = hashedPassword
|
||||||
|
err = db.Save(user).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import "time"
|
|||||||
type User struct {
|
type User struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
Role string `json:"role"`
|
||||||
Password []byte `json:"-"`
|
Password []byte `json:"-"`
|
||||||
|
IsAdmin bool `json:"is_admin" gorm:"-:all"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
@@ -18,9 +20,3 @@ type Game struct {
|
|||||||
UserId int `json:"-"`
|
UserId int `json:"-"`
|
||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GameUploadToken struct {
|
|
||||||
GameId int `json:"-"`
|
|
||||||
UploadToken string `json:"upload_token"`
|
|
||||||
Expire time.Time `json:"expire"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/config"
|
"opensavecloudserver/config"
|
||||||
"opensavecloudserver/database"
|
"opensavecloudserver/database"
|
||||||
|
"opensavecloudserver/upload"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -27,6 +28,11 @@ type LockError struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NewPassword struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
VerifyPassword string `json:"verify_password"`
|
||||||
|
}
|
||||||
|
|
||||||
// CreateGame create a game entry to the database
|
// CreateGame create a game entry to the database
|
||||||
func CreateGame(w http.ResponseWriter, r *http.Request) {
|
func CreateGame(w http.ResponseWriter, r *http.Request) {
|
||||||
userId, err := userIdFromContext(r.Context())
|
userId, err := userIdFromContext(r.Context())
|
||||||
@@ -119,7 +125,7 @@ func AskForUpload(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token, err := database.AskForUpload(userId, gameInfo.GameId)
|
token, err := upload.AskForUpload(userId, gameInfo.GameId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ok(LockError{Message: err.Error()}, w, r)
|
ok(LockError{Message: err.Error()}, w, r)
|
||||||
return
|
return
|
||||||
@@ -141,7 +147,7 @@ func UploadSave(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer database.UnlockGame(gameId)
|
defer upload.UnlockGame(gameId)
|
||||||
hash := r.Header.Get("X-Game-Save-Hash")
|
hash := r.Header.Get("X-Game-Save-Hash")
|
||||||
if utf8.RuneCountInString(hash) == 0 {
|
if utf8.RuneCountInString(hash) == 0 {
|
||||||
badRequest("The header X-Game-Save-Hash is missing", w, r)
|
badRequest("The header X-Game-Save-Hash is missing", w, r)
|
||||||
@@ -160,7 +166,7 @@ func UploadSave(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
err = database.UploadSave(file, game)
|
err = upload.UploadSave(file, game)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
internalServerError(w, r)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@@ -194,7 +200,7 @@ func Download(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer database.UnlockGame(gameId)
|
defer upload.UnlockGame(gameId)
|
||||||
game, err := database.GameInfoById(userId, gameId)
|
game, err := database.GameInfoById(userId, gameId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internalServerError(w, r)
|
internalServerError(w, r)
|
||||||
@@ -237,3 +243,41 @@ func UserInformation(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
ok(user, w, r)
|
ok(user, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ChangePassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userId, err := userIdFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newPassword := new(NewPassword)
|
||||||
|
err = json.Unmarshal(body, newPassword)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if newPassword.Password != newPassword.VerifyPassword {
|
||||||
|
badRequest("password are not the same", w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = database.ChangePassword(userId, []byte(newPassword.Password))
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
payload := &successMessage{
|
||||||
|
Message: "Password changed",
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Status: 200,
|
||||||
|
}
|
||||||
|
ok(payload, w, r)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"opensavecloudserver/authentication"
|
"opensavecloudserver/authentication"
|
||||||
"opensavecloudserver/config"
|
"opensavecloudserver/config"
|
||||||
"opensavecloudserver/database"
|
"opensavecloudserver/upload"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContextKey string
|
type ContextKey string
|
||||||
@@ -39,6 +39,7 @@ func Serve() {
|
|||||||
r.Route("/user", func(secureRouter chi.Router) {
|
r.Route("/user", func(secureRouter chi.Router) {
|
||||||
secureRouter.Use(authMiddleware)
|
secureRouter.Use(authMiddleware)
|
||||||
secureRouter.Get("/information", UserInformation)
|
secureRouter.Get("/information", UserInformation)
|
||||||
|
secureRouter.Post("/passwd", ChangePassword)
|
||||||
})
|
})
|
||||||
r.Route("/game", func(secureRouter chi.Router) {
|
r.Route("/game", func(secureRouter chi.Router) {
|
||||||
secureRouter.Use(authMiddleware)
|
secureRouter.Use(authMiddleware)
|
||||||
@@ -86,7 +87,7 @@ func uploadMiddleware(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
header := r.Header.Get("X-Upload-Key")
|
header := r.Header.Get("X-Upload-Key")
|
||||||
if len(header) > 0 {
|
if len(header) > 0 {
|
||||||
if gameId, ok := database.CheckUploadToken(header); ok {
|
if gameId, ok := upload.CheckUploadToken(header); ok {
|
||||||
ctx := context.WithValue(r.Context(), GameIdKey, gameId)
|
ctx := context.WithValue(r.Context(), GameIdKey, gameId)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
|||||||
133
upload/upload.go
Normal file
133
upload/upload.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package upload
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"opensavecloudserver/config"
|
||||||
|
"opensavecloudserver/database"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
locks map[int]GameUploadToken
|
||||||
|
mu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type GameUploadToken struct {
|
||||||
|
GameId int `json:"-"`
|
||||||
|
UploadToken string `json:"upload_token"`
|
||||||
|
Expire time.Time `json:"expire"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
locks = make(map[int]GameUploadToken)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
clearLocks()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AskForUpload Create a lock for upload a new revision of a game
|
||||||
|
func AskForUpload(userId, gameId int) (*GameUploadToken, error) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
_, err := database.GameInfoById(userId, gameId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, ok := locks[gameId]; !ok {
|
||||||
|
token := uuid.New()
|
||||||
|
lock := GameUploadToken{
|
||||||
|
GameId: gameId,
|
||||||
|
UploadToken: token.String(),
|
||||||
|
}
|
||||||
|
locks[gameId] = lock
|
||||||
|
return &lock, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("game already locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckUploadToken(uploadToken string) (int, bool) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
for _, lock := range locks {
|
||||||
|
if lock.UploadToken == uploadToken {
|
||||||
|
return lock.GameId, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func UploadSave(file multipart.File, game *database.Game) error {
|
||||||
|
filePath := path.Join(config.Path().Cache, string(rune(game.UserId)))
|
||||||
|
if _, err := os.Stat(filePath); err != nil {
|
||||||
|
err = os.Mkdir(filePath, 0766)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filePath = path.Join(filePath, game.PathStorage)
|
||||||
|
f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = io.Copy(f, file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = moveToStorage(filePath, game)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveToStorage(cachePath string, game *database.Game) error {
|
||||||
|
filePath := path.Join(config.Path().Storage, string(rune(game.UserId)))
|
||||||
|
if _, err := os.Stat(filePath); err != nil {
|
||||||
|
err = os.Mkdir(filePath, 0766)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filePath = path.Join(filePath, game.PathStorage)
|
||||||
|
if _, err := os.Stat(filePath); err == nil {
|
||||||
|
if err = os.Remove(filePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.Rename(cachePath, filePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnlockGame(gameId int) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
delete(locks, gameId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearLocks clear lock of zombi upload
|
||||||
|
func clearLocks() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
now := time.Now()
|
||||||
|
toUnlock := make([]int, 0)
|
||||||
|
for gameId, lock := range locks {
|
||||||
|
if lock.Expire.After(now) {
|
||||||
|
toUnlock = append(toUnlock, gameId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, gameId := range toUnlock {
|
||||||
|
delete(locks, gameId)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user