You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
15 KiB
343 lines
15 KiB
// Copyright © 2023 OpenIM SDK. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package group
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
|
|
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/common"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/page"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer"
|
|
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
|
"github.com/openimsdk/protocol/group"
|
|
"github.com/openimsdk/protocol/sdkws"
|
|
"github.com/openimsdk/tools/log"
|
|
"github.com/openimsdk/tools/utils/datautil"
|
|
)
|
|
|
|
func NewGroup(loginUserID string, db db_interface.DataBase,
|
|
conversationCh chan common.Cmd2Value) *Group {
|
|
g := &Group{
|
|
loginUserID: loginUserID,
|
|
db: db,
|
|
conversationCh: conversationCh,
|
|
}
|
|
g.initSyncer()
|
|
return g
|
|
}
|
|
|
|
// //utils.GetCurrentTimestampByMill()
|
|
type Group struct {
|
|
listener func() open_im_sdk_callback.OnGroupListener
|
|
loginUserID string
|
|
db db_interface.DataBase
|
|
groupSyncer *syncer.Syncer[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string]
|
|
groupMemberSyncer *syncer.Syncer[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string]
|
|
groupRequestSyncer *syncer.Syncer[*model_struct.LocalGroupRequest, syncer.NoResp, [2]string]
|
|
groupAdminRequestSyncer *syncer.Syncer[*model_struct.LocalAdminGroupRequest, syncer.NoResp, [2]string]
|
|
joinedSuperGroupCh chan common.Cmd2Value
|
|
heartbeatCmdCh chan common.Cmd2Value
|
|
|
|
conversationCh chan common.Cmd2Value
|
|
// memberSyncMutex sync.RWMutex
|
|
|
|
listenerForService open_im_sdk_callback.OnListenerForService
|
|
}
|
|
|
|
func (g *Group) initSyncer() {
|
|
g.groupSyncer = syncer.New2[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](
|
|
syncer.WithInsert[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, value *model_struct.LocalGroup) error {
|
|
return g.db.InsertGroup(ctx, value)
|
|
}),
|
|
syncer.WithDelete[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, value *model_struct.LocalGroup) error {
|
|
if err := g.db.DeleteGroupAllMembers(ctx, value.GroupID); err != nil {
|
|
return err
|
|
}
|
|
if err := g.db.DeleteVersionSync(ctx, g.groupAndMemberVersionTableName(), value.GroupID); err != nil {
|
|
return err
|
|
}
|
|
return g.db.DeleteGroup(ctx, value.GroupID)
|
|
}),
|
|
syncer.WithUpdate[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, server, local *model_struct.LocalGroup) error {
|
|
log.ZInfo(ctx, "groupSyncer trigger update function", "groupID", server.GroupID, "server", server, "local", local)
|
|
return g.db.UpdateGroup(ctx, server)
|
|
}),
|
|
syncer.WithUUID[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(value *model_struct.LocalGroup) string {
|
|
return value.GroupID
|
|
}),
|
|
syncer.WithNotice[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, state int, server, local *model_struct.LocalGroup) error {
|
|
switch state {
|
|
case syncer.Insert:
|
|
// when a user kicked to the group and invited to the group again, group info maybe updated,
|
|
// so conversation info need to be updated
|
|
g.listener().OnJoinedGroupAdded(utils.StructToJsonString(server))
|
|
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{
|
|
Action: constant.UpdateConFaceUrlAndNickName,
|
|
Args: common.SourceIDAndSessionType{
|
|
SourceID: server.GroupID, SessionType: constant.SuperGroupChatType,
|
|
FaceURL: server.FaceURL, Nickname: server.GroupName,
|
|
},
|
|
}, g.conversationCh)
|
|
case syncer.Delete:
|
|
g.listener().OnJoinedGroupDeleted(utils.StructToJsonString(local))
|
|
case syncer.Update:
|
|
log.ZInfo(ctx, "groupSyncer trigger update", "groupID",
|
|
server.GroupID, "data", server, "isDismissed", server.Status == constant.GroupStatusDismissed)
|
|
if server.Status == constant.GroupStatusDismissed {
|
|
if err := g.db.DeleteGroupAllMembers(ctx, server.GroupID); err != nil {
|
|
log.ZError(ctx, "delete group all members failed", err)
|
|
}
|
|
g.listener().OnGroupDismissed(utils.StructToJsonString(server))
|
|
} else {
|
|
g.listener().OnGroupInfoChanged(utils.StructToJsonString(server))
|
|
if server.GroupName != local.GroupName || local.FaceURL != server.FaceURL {
|
|
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{
|
|
Action: constant.UpdateConFaceUrlAndNickName,
|
|
Args: common.SourceIDAndSessionType{
|
|
SourceID: server.GroupID, SessionType: constant.SuperGroupChatType,
|
|
FaceURL: server.FaceURL, Nickname: server.GroupName,
|
|
},
|
|
}, g.conversationCh)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}),
|
|
syncer.WithBatchInsert[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, values []*model_struct.LocalGroup) error {
|
|
return g.db.BatchInsertGroup(ctx, values)
|
|
}),
|
|
syncer.WithDeleteAll[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, _ string) error {
|
|
return g.db.DeleteAllGroup(ctx)
|
|
}),
|
|
syncer.WithBatchPageReq[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(entityID string) page.PageReq {
|
|
return &group.GetJoinedGroupListReq{FromUserID: entityID,
|
|
Pagination: &sdkws.RequestPagination{ShowNumber: 100}}
|
|
}),
|
|
syncer.WithBatchPageRespConvertFunc[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(resp *group.GetJoinedGroupListResp) []*model_struct.LocalGroup {
|
|
return datautil.Batch(ServerGroupToLocalGroup, resp.Groups)
|
|
}),
|
|
syncer.WithReqApiRouter[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](constant.GetJoinedGroupListRouter),
|
|
)
|
|
|
|
g.groupMemberSyncer = syncer.New2[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](
|
|
syncer.WithInsert[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, value *model_struct.LocalGroupMember) error {
|
|
return g.db.InsertGroupMember(ctx, value)
|
|
}),
|
|
syncer.WithDelete[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, value *model_struct.LocalGroupMember) error {
|
|
return g.db.DeleteGroupMember(ctx, value.GroupID, value.UserID)
|
|
}),
|
|
syncer.WithUpdate[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, server, local *model_struct.LocalGroupMember) error {
|
|
return g.db.UpdateGroupMember(ctx, server)
|
|
}),
|
|
syncer.WithUUID[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(value *model_struct.LocalGroupMember) [2]string {
|
|
return [...]string{value.GroupID, value.UserID}
|
|
}),
|
|
syncer.WithNotice[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, state int, server, local *model_struct.LocalGroupMember) error {
|
|
switch state {
|
|
case syncer.Insert:
|
|
g.listener().OnGroupMemberAdded(utils.StructToJsonString(server))
|
|
// When a user is kicked and invited to the group again, group member info will be updated.
|
|
_ = common.TriggerCmdUpdateMessage(ctx,
|
|
common.UpdateMessageNode{
|
|
Action: constant.UpdateMsgFaceUrlAndNickName,
|
|
Args: common.UpdateMessageInfo{
|
|
SessionType: constant.SuperGroupChatType, UserID: server.UserID, FaceURL: server.FaceURL,
|
|
Nickname: server.Nickname, GroupID: server.GroupID,
|
|
},
|
|
}, g.conversationCh)
|
|
case syncer.Delete:
|
|
g.listener().OnGroupMemberDeleted(utils.StructToJsonString(local))
|
|
case syncer.Update:
|
|
g.listener().OnGroupMemberInfoChanged(utils.StructToJsonString(server))
|
|
if server.Nickname != local.Nickname || server.FaceURL != local.FaceURL {
|
|
_ = common.TriggerCmdUpdateMessage(ctx,
|
|
common.UpdateMessageNode{
|
|
Action: constant.UpdateMsgFaceUrlAndNickName,
|
|
Args: common.UpdateMessageInfo{
|
|
SessionType: constant.SuperGroupChatType, UserID: server.UserID, FaceURL: server.FaceURL,
|
|
Nickname: server.Nickname, GroupID: server.GroupID,
|
|
},
|
|
}, g.conversationCh)
|
|
}
|
|
}
|
|
return nil
|
|
}),
|
|
syncer.WithBatchInsert[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, values []*model_struct.LocalGroupMember) error {
|
|
return g.db.BatchInsertGroupMember(ctx, values)
|
|
}),
|
|
syncer.WithDeleteAll[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, groupID string) error {
|
|
return g.db.DeleteGroupAllMembers(ctx, groupID)
|
|
}),
|
|
syncer.WithBatchPageReq[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(entityID string) page.PageReq {
|
|
return &group.GetGroupMemberListReq{GroupID: entityID, Pagination: &sdkws.RequestPagination{ShowNumber: 100}}
|
|
}),
|
|
syncer.WithBatchPageRespConvertFunc[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(resp *group.GetGroupMemberListResp) []*model_struct.LocalGroupMember {
|
|
return datautil.Batch(ServerGroupMemberToLocalGroupMember, resp.Members)
|
|
}),
|
|
syncer.WithReqApiRouter[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](constant.GetGroupMemberListRouter),
|
|
)
|
|
|
|
g.groupRequestSyncer = syncer.New[*model_struct.LocalGroupRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalGroupRequest) error {
|
|
return g.db.InsertGroupRequest(ctx, value)
|
|
}, func(ctx context.Context, value *model_struct.LocalGroupRequest) error {
|
|
return g.db.DeleteGroupRequest(ctx, value.GroupID, value.UserID)
|
|
}, func(ctx context.Context, server, local *model_struct.LocalGroupRequest) error {
|
|
return g.db.UpdateGroupRequest(ctx, server)
|
|
}, func(value *model_struct.LocalGroupRequest) [2]string {
|
|
return [...]string{value.GroupID, value.UserID}
|
|
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalGroupRequest) error {
|
|
switch state {
|
|
case syncer.Insert:
|
|
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server))
|
|
case syncer.Update:
|
|
switch server.HandleResult {
|
|
case constant.FriendResponseAgree:
|
|
g.listener().OnGroupApplicationAccepted(utils.StructToJsonString(server))
|
|
case constant.FriendResponseRefuse:
|
|
g.listener().OnGroupApplicationRejected(utils.StructToJsonString(server))
|
|
default:
|
|
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server))
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
g.groupAdminRequestSyncer = syncer.New[*model_struct.LocalAdminGroupRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalAdminGroupRequest) error {
|
|
return g.db.InsertAdminGroupRequest(ctx, value)
|
|
}, func(ctx context.Context, value *model_struct.LocalAdminGroupRequest) error {
|
|
return g.db.DeleteAdminGroupRequest(ctx, value.GroupID, value.UserID)
|
|
}, func(ctx context.Context, server, local *model_struct.LocalAdminGroupRequest) error {
|
|
return g.db.UpdateAdminGroupRequest(ctx, server)
|
|
}, func(value *model_struct.LocalAdminGroupRequest) [2]string {
|
|
return [...]string{value.GroupID, value.UserID}
|
|
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalAdminGroupRequest) error {
|
|
switch state {
|
|
case syncer.Insert:
|
|
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server))
|
|
case syncer.Update:
|
|
switch server.HandleResult {
|
|
case constant.FriendResponseAgree:
|
|
g.listener().OnGroupApplicationAccepted(utils.StructToJsonString(server))
|
|
case constant.FriendResponseRefuse:
|
|
g.listener().OnGroupApplicationRejected(utils.StructToJsonString(server))
|
|
default:
|
|
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server))
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
}
|
|
|
|
func (g *Group) SetGroupListener(listener func() open_im_sdk_callback.OnGroupListener) {
|
|
g.listener = listener
|
|
}
|
|
|
|
func (g *Group) SetListenerForService(listener open_im_sdk_callback.OnListenerForService) {
|
|
g.listenerForService = listener
|
|
}
|
|
|
|
func (g *Group) GetGroupOwnerIDAndAdminIDList(ctx context.Context, groupID string) (ownerID string, adminIDList []string, err error) {
|
|
localGroup, err := g.db.GetGroupInfoByGroupID(ctx, groupID)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
adminIDList, err = g.db.GetGroupAdminID(ctx, groupID)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return localGroup.OwnerUserID, adminIDList, nil
|
|
}
|
|
|
|
func (g *Group) GetGroupInfoFromLocal2Svr(ctx context.Context, groupID string) (*model_struct.LocalGroup, error) {
|
|
localGroup, err := g.db.GetGroupInfoByGroupID(ctx, groupID)
|
|
if err == nil {
|
|
return localGroup, nil
|
|
}
|
|
svrGroup, err := g.getGroupsInfoFromSvr(ctx, []string{groupID})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(svrGroup) == 0 {
|
|
return nil, sdkerrs.ErrGroupIDNotFound.WrapMsg("server not this group")
|
|
}
|
|
return ServerGroupToLocalGroup(svrGroup[0]), nil
|
|
}
|
|
|
|
func (g *Group) GetGroupsInfoFromLocal2Svr(ctx context.Context, groupIDs ...string) (map[string]*model_struct.LocalGroup, error) {
|
|
groupMap := make(map[string]*model_struct.LocalGroup)
|
|
if len(groupIDs) == 0 {
|
|
return groupMap, nil
|
|
}
|
|
groups, err := g.db.GetGroups(ctx, groupIDs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var groupIDsNeedSync []string
|
|
localGroupIDs := datautil.Slice(groups, func(group *model_struct.LocalGroup) string {
|
|
return group.GroupID
|
|
})
|
|
for _, groupID := range groupIDs {
|
|
if !datautil.Contain(groupID, localGroupIDs...) {
|
|
groupIDsNeedSync = append(groupIDsNeedSync, groupID)
|
|
}
|
|
}
|
|
|
|
if len(groupIDsNeedSync) > 0 {
|
|
svrGroups, err := g.getGroupsInfoFromSvr(ctx, groupIDsNeedSync)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, svrGroup := range svrGroups {
|
|
groups = append(groups, ServerGroupToLocalGroup(svrGroup))
|
|
}
|
|
}
|
|
for _, group := range groups {
|
|
groupMap[group.GroupID] = group
|
|
}
|
|
return groupMap, nil
|
|
}
|
|
|
|
func (g *Group) getGroupsInfoFromSvr(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) {
|
|
resp, err := util.CallApi[group.GetGroupsInfoResp](ctx, constant.GetGroupsInfoRouter, &group.GetGroupsInfoReq{GroupIDs: groupIDs})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.GroupInfos, nil
|
|
}
|
|
|
|
func (g *Group) getGroupAbstractInfoFromSvr(ctx context.Context, groupIDs []string) (*group.GetGroupAbstractInfoResp, error) {
|
|
return util.CallApi[group.GetGroupAbstractInfoResp](ctx, constant.GetGroupAbstractInfoRouter, &group.GetGroupAbstractInfoReq{GroupIDs: groupIDs})
|
|
}
|
|
|
|
func (g *Group) GetJoinedDiffusionGroupIDListFromSvr(ctx context.Context) ([]string, error) {
|
|
groups, err := g.GetServerJoinGroup(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var groupIDs []string
|
|
for _, g := range groups {
|
|
if g.GroupType == constant.WorkingGroup {
|
|
groupIDs = append(groupIDs, g.GroupID)
|
|
}
|
|
}
|
|
return groupIDs, nil
|
|
}
|
|
|