Merge branch 'v2.3.0release' of github.com:OpenIMSDK/Open-IM-Server into v2.3.0release

pull/351/head
wangchuxiao 2 years ago
commit 88bcb35a14

@ -227,7 +227,7 @@ func main() {
if config.Config.Api.ListenIP != "" { if config.Config.Api.ListenIP != "" {
address = config.Config.Api.ListenIP + ":" + strconv.Itoa(*ginPort) address = config.Config.Api.ListenIP + ":" + strconv.Itoa(*ginPort)
} }
fmt.Println("start api server, address: ", address, "\n") fmt.Println("start api server, address: ", address, "OpenIM version: ", constant.CurrentVersion, "\n")
err := r.Run(address) err := r.Run(address)
if err != nil { if err != nil {
log.Error("", "api run failed ", address, err.Error()) log.Error("", "api run failed ", address, err.Error())

@ -2,6 +2,7 @@ package main
import ( import (
"Open_IM/internal/cms_api" "Open_IM/internal/cms_api"
"Open_IM/pkg/common/constant"
"Open_IM/pkg/utils" "Open_IM/pkg/utils"
"flag" "flag"
"fmt" "fmt"
@ -24,6 +25,6 @@ func main() {
address = config.Config.Api.ListenIP + ":" + strconv.Itoa(*ginPort) address = config.Config.Api.ListenIP + ":" + strconv.Itoa(*ginPort)
} }
address = config.Config.CmsApi.ListenIP + ":" + strconv.Itoa(*ginPort) address = config.Config.CmsApi.ListenIP + ":" + strconv.Itoa(*ginPort)
fmt.Println("start cms api server, address: ", address, "\n") fmt.Println("start cms api server, address: ", address, "OpenIM version: ", constant.CurrentVersion, "\n")
router.Run(address) router.Run(address)
} }

@ -71,7 +71,7 @@ func main() {
address = config.Config.Api.ListenIP + ":" + strconv.Itoa(*ginPort) address = config.Config.Api.ListenIP + ":" + strconv.Itoa(*ginPort)
} }
address = config.Config.CmsApi.ListenIP + ":" + strconv.Itoa(*ginPort) address = config.Config.CmsApi.ListenIP + ":" + strconv.Itoa(*ginPort)
fmt.Println("start demo api server address: ", address, "\n") fmt.Println("start demo api server address: ", address, "OpenIM version: ", constant.CurrentVersion, "\n")
go register.OnboardingProcessRoutine() go register.OnboardingProcessRoutine()
go register.ImportFriendRoutine() go register.ImportFriendRoutine()
err := r.Run(address) err := r.Run(address)

@ -21,7 +21,7 @@ func main() {
flag.Parse() flag.Parse()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
fmt.Println("start rpc/msg_gateway server, port: ", *rpcPort, *wsPort, *prometheusPort, "\n") fmt.Println("start rpc/msg_gateway server, port: ", *rpcPort, *wsPort, *prometheusPort, "OpenIM version: ", constant.CurrentVersion, "\n")
gate.Init(*rpcPort, *wsPort) gate.Init(*rpcPort, *wsPort)
gate.Run(*prometheusPort) gate.Run(*prometheusPort)
wg.Wait() wg.Wait()

@ -17,7 +17,7 @@ func main() {
flag.Parse() flag.Parse()
log.NewPrivateLog(constant.LogFileName) log.NewPrivateLog(constant.LogFileName)
logic.Init() logic.Init()
fmt.Println("start msg_transfer server \n") fmt.Println("start msg_transfer server ", "OpenIM version: ", constant.CurrentVersion, "\n")
logic.Run(*prometheusPort) logic.Run(*prometheusPort)
wg.Wait() wg.Wait()
} }

@ -18,7 +18,7 @@ func main() {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
log.NewPrivateLog(constant.LogFileName) log.NewPrivateLog(constant.LogFileName)
fmt.Println("start push rpc server, port: ", *rpcPort, "\n") fmt.Println("start push rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
logic.Init(*rpcPort) logic.Init(*rpcPort)
logic.Run(*prometheusPort) logic.Run(*prometheusPort)
wg.Wait() wg.Wait()

@ -3,6 +3,7 @@ package main
import ( import (
rpcMessageCMS "Open_IM/internal/rpc/admin_cms" rpcMessageCMS "Open_IM/internal/rpc/admin_cms"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port") rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.AdminCmsPrometheusPort[0], "adminCMSPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.AdminCmsPrometheusPort[0], "adminCMSPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start cms rpc server, port: ", *rpcPort, "\n") fmt.Println("start cms rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := rpcMessageCMS.NewAdminCMSServer(*rpcPort) rpcServer := rpcMessageCMS.NewAdminCMSServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
rpcAuth "Open_IM/internal/rpc/auth" rpcAuth "Open_IM/internal/rpc/auth"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "RpcToken default listen port 10800") rpcPort := flag.Int("port", defaultPorts[0], "RpcToken default listen port 10800")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.AuthPrometheusPort[0], "authPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.AuthPrometheusPort[0], "authPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start auth rpc server, port: ", *rpcPort, "\n") fmt.Println("start auth rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := rpcAuth.NewRpcAuthServer(*rpcPort) rpcServer := rpcAuth.NewRpcAuthServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
rpcCache "Open_IM/internal/rpc/cache" rpcCache "Open_IM/internal/rpc/cache"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
@ -14,7 +15,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "RpcToken default listen port 10800") rpcPort := flag.Int("port", defaultPorts[0], "RpcToken default listen port 10800")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.CachePrometheusPort[0], "cachePrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.CachePrometheusPort[0], "cachePrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start cache rpc server, port: ", *rpcPort, "\n") fmt.Println("start cache rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := rpcCache.NewCacheServer(*rpcPort) rpcServer := rpcCache.NewCacheServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
rpcConversation "Open_IM/internal/rpc/conversation" rpcConversation "Open_IM/internal/rpc/conversation"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "RpcConversation default listen port 11300") rpcPort := flag.Int("port", defaultPorts[0], "RpcConversation default listen port 11300")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.ConversationPrometheusPort[0], "conversationPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.ConversationPrometheusPort[0], "conversationPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start conversation rpc server, port: ", *rpcPort, "\n") fmt.Println("start conversation rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := rpcConversation.NewRpcConversationServer(*rpcPort) rpcServer := rpcConversation.NewRpcConversationServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
"Open_IM/internal/rpc/friend" "Open_IM/internal/rpc/friend"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "get RpcFriendPort from cmd,default 12000 as port") rpcPort := flag.Int("port", defaultPorts[0], "get RpcFriendPort from cmd,default 12000 as port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.FriendPrometheusPort[0], "friendPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.FriendPrometheusPort[0], "friendPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start friend rpc server, port: ", *rpcPort, "\n") fmt.Println("start friend rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := friend.NewFriendServer(*rpcPort) rpcServer := friend.NewFriendServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
"Open_IM/internal/rpc/group" "Open_IM/internal/rpc/group"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "get RpcGroupPort from cmd,default 16000 as port") rpcPort := flag.Int("port", defaultPorts[0], "get RpcGroupPort from cmd,default 16000 as port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.GroupPrometheusPort[0], "groupPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.GroupPrometheusPort[0], "groupPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start group rpc server, port: ", *rpcPort, "\n") fmt.Println("start group rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := group.NewGroupServer(*rpcPort) rpcServer := group.NewGroupServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
"Open_IM/internal/rpc/msg" "Open_IM/internal/rpc/msg"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port") rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.MessagePrometheusPort[0], "msgPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.MessagePrometheusPort[0], "msgPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start msg rpc server, port: ", *rpcPort, "\n") fmt.Println("start msg rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := msg.NewRpcChatServer(*rpcPort) rpcServer := msg.NewRpcChatServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
rpc "Open_IM/internal/rpc/office" rpc "Open_IM/internal/rpc/office"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port") rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.OfficePrometheusPort[0], "officePrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.OfficePrometheusPort[0], "officePrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start office rpc server, port: ", *rpcPort, "\n") fmt.Println("start office rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := rpc.NewOfficeServer(*rpcPort) rpcServer := rpc.NewOfficeServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
"Open_IM/internal/rpc/organization" "Open_IM/internal/rpc/organization"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "get RpcOrganizationPort from cmd,default 11200 as port") rpcPort := flag.Int("port", defaultPorts[0], "get RpcOrganizationPort from cmd,default 11200 as port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.OrganizationPrometheusPort[0], "organizationPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.OrganizationPrometheusPort[0], "organizationPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start organization rpc server, port: ", *rpcPort, "\n") fmt.Println("start organization rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := organization.NewServer(*rpcPort) rpcServer := organization.NewServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package main
import ( import (
"Open_IM/internal/rpc/user" "Open_IM/internal/rpc/user"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
promePkg "Open_IM/pkg/common/prometheus" promePkg "Open_IM/pkg/common/prometheus"
"flag" "flag"
"fmt" "fmt"
@ -13,7 +14,7 @@ func main() {
rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port") rpcPort := flag.Int("port", defaultPorts[0], "rpc listening port")
prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.UserPrometheusPort[0], "userPrometheusPort default listen port") prometheusPort := flag.Int("prometheus_port", config.Config.Prometheus.UserPrometheusPort[0], "userPrometheusPort default listen port")
flag.Parse() flag.Parse()
fmt.Println("start user rpc server, port: ", *rpcPort, "\n") fmt.Println("start user rpc server, port: ", *rpcPort, "OpenIM version: ", constant.CurrentVersion, "\n")
rpcServer := user.NewUserServer(*rpcPort) rpcServer := user.NewUserServer(*rpcPort)
go func() { go func() {
err := promePkg.StartPromeSrv(*prometheusPort) err := promePkg.StartPromeSrv(*prometheusPort)

@ -3,6 +3,7 @@ package admin
import ( import (
apiStruct "Open_IM/pkg/cms_api_struct" apiStruct "Open_IM/pkg/cms_api_struct"
"Open_IM/pkg/common/config" "Open_IM/pkg/common/config"
"Open_IM/pkg/common/constant"
"Open_IM/pkg/common/log" "Open_IM/pkg/common/log"
"Open_IM/pkg/grpc-etcdv3/getcdv3" "Open_IM/pkg/grpc-etcdv3/getcdv3"
pbAdmin "Open_IM/pkg/proto/admin_cms" pbAdmin "Open_IM/pkg/proto/admin_cms"
@ -25,6 +26,7 @@ var (
) )
func init() { func init() {
log.NewPrivateLog(constant.LogFileName)
operationID := utils.OperationIDGenerator() operationID := utils.OperationIDGenerator()
log.NewInfo(operationID, utils.GetSelfFuncName(), "minio config: ", config.Config.Credential.Minio) log.NewInfo(operationID, utils.GetSelfFuncName(), "minio config: ", config.Config.Credential.Minio)
var initUrl string var initUrl string

@ -336,3 +336,5 @@ const LogFileName = "OpenIM.log"
const StatisticsTimeInterval = 60 const StatisticsTimeInterval = 60
const MaxNotificationNum = 100 const MaxNotificationNum = 100
const CurrentVersion = "v2.3.3"

@ -31,25 +31,26 @@ func initMysqlDB() {
var err1 error var err1 error
db, err := gorm.Open(mysql.Open(dsn), nil) db, err := gorm.Open(mysql.Open(dsn), nil)
if err != nil { if err != nil {
fmt.Println("0", "Open failed ", err.Error(), dsn) fmt.Println("Open failed ", err.Error(), dsn)
} }
if err != nil { if err != nil {
time.Sleep(time.Duration(30) * time.Second) time.Sleep(time.Duration(30) * time.Second)
db, err1 = gorm.Open(mysql.Open(dsn), nil) db, err1 = gorm.Open(mysql.Open(dsn), nil)
if err1 != nil { if err1 != nil {
fmt.Println("0", "Open failed ", err1.Error(), dsn) fmt.Println("Open failed ", err1.Error(), dsn)
panic(err1.Error()) panic(err1.Error())
} }
} }
fmt.Println("init db", db)
//Check the database and table during initialization //Check the database and table during initialization
sql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci;", config.Config.Mysql.DBDatabaseName) sql := fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci;", config.Config.Mysql.DBDatabaseName)
fmt.Println("exec sql: ", sql, " begin")
err = db.Exec(sql).Error err = db.Exec(sql).Error
if err != nil { if err != nil {
fmt.Println("0", "Exec failed ", err.Error(), sql) fmt.Println("Exec failed ", err.Error(), sql)
panic(err.Error()) panic(err.Error())
} }
fmt.Println(sql) fmt.Println("exec sql: ", sql, " end")
dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local", dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&loc=Local",
config.Config.Mysql.DBUserName, config.Config.Mysql.DBPassword, config.Config.Mysql.DBAddress[0], config.Config.Mysql.DBDatabaseName) config.Config.Mysql.DBUserName, config.Config.Mysql.DBPassword, config.Config.Mysql.DBAddress[0], config.Config.Mysql.DBDatabaseName)
@ -66,20 +67,20 @@ func initMysqlDB() {
Logger: newLogger, Logger: newLogger,
}) })
if err != nil { if err != nil {
fmt.Println("0", "Open failed ", err.Error(), dsn) fmt.Println("Open failed ", err.Error(), dsn)
panic(err.Error()) panic(err.Error())
} }
fmt.Println("init db2", db)
sqlDB, err := db.DB() sqlDB, err := db.DB()
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
fmt.Println("init sqlDB", sqlDB)
sqlDB.SetConnMaxLifetime(time.Second * time.Duration(config.Config.Mysql.DBMaxLifeTime)) sqlDB.SetConnMaxLifetime(time.Second * time.Duration(config.Config.Mysql.DBMaxLifeTime))
sqlDB.SetMaxOpenConns(config.Config.Mysql.DBMaxOpenConns) sqlDB.SetMaxOpenConns(config.Config.Mysql.DBMaxOpenConns)
sqlDB.SetMaxIdleConns(config.Config.Mysql.DBMaxIdleConns) sqlDB.SetMaxIdleConns(config.Config.Mysql.DBMaxIdleConns)
fmt.Println("open db ok ", dsn) fmt.Println("open mysql ok ", dsn)
db.AutoMigrate( db.AutoMigrate(
&Register{}, &Register{},
&Friend{}, &Friend{},

@ -2,7 +2,6 @@ package utils
import ( import (
"errors" "errors"
"fmt"
"net" "net"
) )
@ -11,14 +10,12 @@ var ServerIP = ""
func GetLocalIP() (string, error) { func GetLocalIP() (string, error) {
addrs, err := net.InterfaceAddrs() addrs, err := net.InterfaceAddrs()
if err != nil { if err != nil {
return "", err return "", err
} }
for _, address := range addrs { for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil { if ipnet.IP.To4() != nil {
fmt.Println(ipnet.IP.String())
return ipnet.IP.String(), nil return ipnet.IP.String(), nil
} }
} }

Loading…
Cancel
Save