feat: incr sync version.
This commit is contained in:
1178
go/chao-sdk-core/internal/conversation_msg/conversation.go
Normal file
1178
go/chao-sdk-core/internal/conversation_msg/conversation.go
Normal file
File diff suppressed because it is too large
Load Diff
1064
go/chao-sdk-core/internal/conversation_msg/conversation_msg.go
Normal file
1064
go/chao-sdk-core/internal/conversation_msg/conversation_msg.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,714 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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/model_struct"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
func (c *Conversation) Work(c2v common.Cmd2Value) {
|
||||
log.ZDebug(c2v.Ctx, "NotificationCmd start", "cmd", c2v.Cmd, "value", c2v.Value)
|
||||
defer log.ZDebug(c2v.Ctx, "NotificationCmd end", "cmd", c2v.Cmd, "value", c2v.Value)
|
||||
switch c2v.Cmd {
|
||||
case constant.CmdNewMsgCome:
|
||||
c.doMsgNew(c2v)
|
||||
case constant.CmdSuperGroupMsgCome:
|
||||
case constant.CmdUpdateConversation:
|
||||
c.doUpdateConversation(c2v)
|
||||
case constant.CmdUpdateMessage:
|
||||
c.doUpdateMessage(c2v)
|
||||
case constant.CmSyncReactionExtensions:
|
||||
case constant.CmdNotification:
|
||||
c.doNotificationNew(c2v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conversation) doNotificationNew(c2v common.Cmd2Value) {
|
||||
ctx := c2v.Ctx
|
||||
allMsg := c2v.Value.(sdk_struct.CmdNewMsgComeToConversation).Msgs
|
||||
syncFlag := c2v.Value.(sdk_struct.CmdNewMsgComeToConversation).SyncFlag
|
||||
switch syncFlag {
|
||||
case constant.MsgSyncBegin:
|
||||
c.startTime = time.Now()
|
||||
c.ConversationListener().OnSyncServerStart()
|
||||
if err := c.SyncAllConversationHashReadSeqs(ctx); err != nil {
|
||||
log.ZError(ctx, "SyncConversationHashReadSeqs err", err)
|
||||
}
|
||||
//clear SubscriptionStatusMap
|
||||
c.user.OnlineStatusCache.DeleteAll()
|
||||
for _, syncFunc := range []func(c context.Context) error{
|
||||
c.user.SyncLoginUserInfo,
|
||||
c.friend.SyncAllBlackList, c.friend.SyncAllFriendApplication, c.friend.SyncAllSelfFriendApplication,
|
||||
c.group.SyncAllAdminGroupApplication, c.group.SyncAllSelfGroupApplication, c.user.SyncAllCommand,
|
||||
} {
|
||||
go func(syncFunc func(c context.Context) error) {
|
||||
_ = syncFunc(ctx)
|
||||
}(syncFunc)
|
||||
}
|
||||
|
||||
syncFunctions := []func(c context.Context) error{
|
||||
c.group.SyncAllJoinedGroupsAndMembers, c.friend.IncrSyncFriends,
|
||||
}
|
||||
|
||||
for _, syncFunc := range syncFunctions {
|
||||
funcName := runtime.FuncForPC(reflect.ValueOf(syncFunc).Pointer()).Name()
|
||||
startTime := time.Now()
|
||||
err := syncFunc(ctx)
|
||||
duration := time.Since(startTime)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, fmt.Sprintf("%s sync err", funcName), err, "duration", duration)
|
||||
} else {
|
||||
log.ZDebug(ctx, fmt.Sprintf("%s completed successfully", funcName), "duration", duration)
|
||||
}
|
||||
}
|
||||
case constant.MsgSyncFailed:
|
||||
c.ConversationListener().OnSyncServerFailed()
|
||||
case constant.MsgSyncEnd:
|
||||
log.ZDebug(ctx, "MsgSyncEnd", "time", time.Since(c.startTime).Milliseconds())
|
||||
defer c.ConversationListener().OnSyncServerFinish()
|
||||
go c.SyncAllConversations(ctx)
|
||||
}
|
||||
|
||||
for conversationID, msgs := range allMsg {
|
||||
log.ZDebug(ctx, "notification handling", "conversationID", conversationID, "msgs", msgs)
|
||||
if len(msgs.Msgs) != 0 {
|
||||
lastMsg := msgs.Msgs[len(msgs.Msgs)-1]
|
||||
log.ZDebug(ctx, "SetNotificationSeq", "conversationID", conversationID, "seq", lastMsg.Seq)
|
||||
if lastMsg.Seq != 0 {
|
||||
if err := c.db.SetNotificationSeq(ctx, conversationID, lastMsg.Seq); err != nil {
|
||||
log.ZError(ctx, "SetNotificationSeq err", err, "conversationID", conversationID, "lastMsg", lastMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range msgs.Msgs {
|
||||
switch {
|
||||
case v.ContentType == constant.ConversationChangeNotification:
|
||||
c.DoConversationChangedNotification(ctx, v)
|
||||
case v.ContentType == constant.ConversationPrivateChatNotification:
|
||||
c.DoConversationIsPrivateChangedNotification(ctx, v)
|
||||
case v.ContentType == constant.ConversationUnreadNotification:
|
||||
var tips sdkws.ConversationHasReadTips
|
||||
_ = json.Unmarshal(v.Content, &tips)
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: tips.ConversationID, Action: constant.UnreadCountSetZero}})
|
||||
c.db.DeleteConversationUnreadMessageList(ctx, tips.ConversationID, tips.UnreadCountTime)
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{tips.ConversationID}}})
|
||||
continue
|
||||
case v.ContentType == constant.BusinessNotification:
|
||||
c.business.DoNotification(ctx, v)
|
||||
continue
|
||||
case v.ContentType == constant.RevokeNotification:
|
||||
c.doRevokeMsg(ctx, v)
|
||||
case v.ContentType == constant.ClearConversationNotification:
|
||||
c.doClearConversations(ctx, v)
|
||||
case v.ContentType == constant.DeleteMsgsNotification:
|
||||
c.doDeleteMsgs(ctx, v)
|
||||
case v.ContentType == constant.HasReadReceipt:
|
||||
c.doReadDrawing(ctx, v)
|
||||
}
|
||||
|
||||
switch v.SessionType {
|
||||
case constant.SingleChatType:
|
||||
if v.ContentType > constant.FriendNotificationBegin && v.ContentType < constant.FriendNotificationEnd {
|
||||
c.friend.DoNotification(ctx, v)
|
||||
} else if v.ContentType > constant.UserNotificationBegin && v.ContentType < constant.UserNotificationEnd {
|
||||
c.user.DoNotification(ctx, v)
|
||||
} else if datautil.Contain(v.ContentType, constant.GroupApplicationRejectedNotification, constant.GroupApplicationAcceptedNotification, constant.JoinGroupApplicationNotification) {
|
||||
c.group.DoNotification(ctx, v)
|
||||
} else if v.ContentType > constant.SignalingNotificationBegin && v.ContentType < constant.SignalingNotificationEnd {
|
||||
|
||||
continue
|
||||
}
|
||||
case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
if v.ContentType > constant.GroupNotificationBegin && v.ContentType < constant.GroupNotificationEnd {
|
||||
c.group.DoNotification(ctx, v)
|
||||
} else if v.ContentType > constant.SignalingNotificationBegin && v.ContentType < constant.SignalingNotificationEnd {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Conversation) doDeleteConversation(c2v common.Cmd2Value) {
|
||||
node := c2v.Value.(common.DeleteConNode)
|
||||
ctx := c2v.Ctx
|
||||
// Mark messages related to this conversation for deletion
|
||||
err := c.db.UpdateMessageStatusBySourceID(context.Background(), node.SourceID, constant.MsgStatusHasDeleted, int32(node.SessionType))
|
||||
if err != nil {
|
||||
log.ZError(ctx, "setMessageStatusBySourceID", err)
|
||||
return
|
||||
}
|
||||
// Reset the session information, empty session
|
||||
err = c.db.ResetConversation(ctx, node.ConversationID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "ResetConversation err:", err)
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{"", constant.TotalUnreadMessageChanged, ""}})
|
||||
}
|
||||
|
||||
func (c *Conversation) getConversationLatestMsgClientID(latestMsg string) string {
|
||||
msg := &sdk_struct.MsgStruct{}
|
||||
if err := json.Unmarshal([]byte(latestMsg), msg); err != nil {
|
||||
log.ZError(context.Background(), "getConversationLatestMsgClientID", err, "latestMsg", latestMsg)
|
||||
}
|
||||
return msg.ClientMsgID
|
||||
}
|
||||
|
||||
func (c *Conversation) doUpdateConversation(c2v common.Cmd2Value) {
|
||||
ctx := c2v.Ctx
|
||||
node := c2v.Value.(common.UpdateConNode)
|
||||
switch node.Action {
|
||||
case constant.AddConOrUpLatMsg:
|
||||
var list []*model_struct.LocalConversation
|
||||
lc := node.Args.(model_struct.LocalConversation)
|
||||
oc, err := c.db.GetConversation(ctx, lc.ConversationID)
|
||||
if err == nil {
|
||||
// log.Info("this is old conversation", *oc)
|
||||
if lc.LatestMsgSendTime >= oc.LatestMsgSendTime || c.getConversationLatestMsgClientID(lc.LatestMsg) == c.getConversationLatestMsgClientID(oc.LatestMsg) { // The session update of asynchronous messages is subject to the latest sending time
|
||||
err := c.db.UpdateColumnsConversation(ctx, node.ConID, map[string]interface{}{"latest_msg_send_time": lc.LatestMsgSendTime, "latest_msg": lc.LatestMsg})
|
||||
if err != nil {
|
||||
log.ZError(ctx, "updateConversationLatestMsgModel", err, "conversationID", node.ConID)
|
||||
} else {
|
||||
oc.LatestMsgSendTime = lc.LatestMsgSendTime
|
||||
oc.LatestMsg = lc.LatestMsg
|
||||
list = append(list, oc)
|
||||
c.ConversationListener().OnConversationChanged(utils.StructToJsonString(list))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// log.Info("this is new conversation", lc)
|
||||
err4 := c.db.InsertConversation(ctx, &lc)
|
||||
if err4 != nil {
|
||||
// log.Error("internal", "insert new conversation err:", err4.Error())
|
||||
} else {
|
||||
list = append(list, &lc)
|
||||
c.ConversationListener().OnNewConversation(utils.StructToJsonString(list))
|
||||
}
|
||||
}
|
||||
|
||||
case constant.UnreadCountSetZero:
|
||||
if err := c.db.UpdateColumnsConversation(ctx, node.ConID, map[string]interface{}{"unread_count": 0}); err != nil {
|
||||
log.ZError(ctx, "updateConversationUnreadCountModel err", err, "conversationID", node.ConID)
|
||||
} else {
|
||||
totalUnreadCount, err := c.db.GetTotalUnreadMsgCountDB(ctx)
|
||||
if err == nil {
|
||||
c.ConversationListener().OnTotalUnreadMessageCountChanged(totalUnreadCount)
|
||||
} else {
|
||||
log.ZError(ctx, "getTotalUnreadMsgCountDB err", err)
|
||||
}
|
||||
|
||||
}
|
||||
// case ConChange:
|
||||
// err, list := u.getAllConversationListModel()
|
||||
// if err != nil {
|
||||
// sdkLog("getAllConversationListModel database err:", err.Error())
|
||||
// } else {
|
||||
// if list == nil {
|
||||
// u.ConversationListenerx.OnConversationChanged(structToJsonString([]ConversationStruct{}))
|
||||
// } else {
|
||||
// u.ConversationListenerx.OnConversationChanged(structToJsonString(list))
|
||||
//
|
||||
// }
|
||||
// }
|
||||
case constant.IncrUnread:
|
||||
err := c.db.IncrConversationUnreadCount(ctx, node.ConID)
|
||||
if err != nil {
|
||||
// log.Error("internal", "incrConversationUnreadCount database err:", err.Error())
|
||||
return
|
||||
}
|
||||
case constant.TotalUnreadMessageChanged:
|
||||
totalUnreadCount, err := c.db.GetTotalUnreadMsgCountDB(ctx)
|
||||
if err != nil {
|
||||
// log.Error("internal", "TotalUnreadMessageChanged database err:", err.Error())
|
||||
} else {
|
||||
c.ConversationListener().OnTotalUnreadMessageCountChanged(totalUnreadCount)
|
||||
}
|
||||
case constant.UpdateConFaceUrlAndNickName:
|
||||
var lc model_struct.LocalConversation
|
||||
st := node.Args.(common.SourceIDAndSessionType)
|
||||
log.ZInfo(ctx, "UpdateConFaceUrlAndNickName", "st", st)
|
||||
switch st.SessionType {
|
||||
case constant.SingleChatType:
|
||||
lc.UserID = st.SourceID
|
||||
lc.ConversationID = c.getConversationIDBySessionType(st.SourceID, constant.SingleChatType)
|
||||
lc.ConversationType = constant.SingleChatType
|
||||
case constant.SuperGroupChatType:
|
||||
conversationID, conversationType, err := c.getConversationTypeByGroupID(ctx, st.SourceID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lc.GroupID = st.SourceID
|
||||
lc.ConversationID = conversationID
|
||||
lc.ConversationType = conversationType
|
||||
case constant.NotificationChatType:
|
||||
lc.UserID = st.SourceID
|
||||
lc.ConversationID = c.getConversationIDBySessionType(st.SourceID, constant.NotificationChatType)
|
||||
lc.ConversationType = constant.NotificationChatType
|
||||
default:
|
||||
log.ZError(ctx, "not support sessionType", nil, "sessionType", st.SessionType)
|
||||
return
|
||||
}
|
||||
lc.ShowName = st.Nickname
|
||||
lc.FaceURL = st.FaceURL
|
||||
err := c.db.UpdateConversation(ctx, &lc)
|
||||
if err != nil {
|
||||
// log.Error("internal", "setConversationFaceUrlAndNickName database err:", err.Error())
|
||||
return
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: lc.ConversationID, Action: constant.ConChange, Args: []string{lc.ConversationID}}})
|
||||
|
||||
case constant.UpdateLatestMessageChange:
|
||||
conversationID := node.ConID
|
||||
var latestMsg sdk_struct.MsgStruct
|
||||
l, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "getConversationLatestMsgModel err", err, "conversationID", conversationID)
|
||||
} else {
|
||||
err := json.Unmarshal([]byte(l.LatestMsg), &latestMsg)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "latestMsg,Unmarshal err", err)
|
||||
} else {
|
||||
latestMsg.IsRead = true
|
||||
newLatestMessage := utils.StructToJsonString(latestMsg)
|
||||
err = c.db.UpdateColumnsConversation(ctx, node.ConID, map[string]interface{}{"latest_msg_send_time": latestMsg.SendTime, "latest_msg": newLatestMessage})
|
||||
if err != nil {
|
||||
log.ZError(ctx, "updateConversationLatestMsgModel err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
case constant.ConChange:
|
||||
conversationIDs := node.Args.([]string)
|
||||
conversations, err := c.db.GetMultipleConversationDB(ctx, conversationIDs)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "getMultipleConversationModel err", err)
|
||||
} else {
|
||||
var newCList []*model_struct.LocalConversation
|
||||
for _, v := range conversations {
|
||||
if v.LatestMsgSendTime != 0 {
|
||||
newCList = append(newCList, v)
|
||||
}
|
||||
}
|
||||
c.ConversationListener().OnConversationChanged(utils.StructToJsonStringDefault(newCList))
|
||||
}
|
||||
case constant.NewCon:
|
||||
cidList := node.Args.([]string)
|
||||
cLists, err := c.db.GetMultipleConversationDB(ctx, cidList)
|
||||
if err != nil {
|
||||
// log.Error("internal", "getMultipleConversationModel err :", err.Error())
|
||||
} else {
|
||||
if cLists != nil {
|
||||
// log.Info("internal", "getMultipleConversationModel success :", cLists)
|
||||
c.ConversationListener().OnNewConversation(utils.StructToJsonString(cLists))
|
||||
}
|
||||
}
|
||||
case constant.ConChangeDirect:
|
||||
cidList := node.Args.(string)
|
||||
c.ConversationListener().OnConversationChanged(cidList)
|
||||
|
||||
case constant.NewConDirect:
|
||||
cidList := node.Args.(string)
|
||||
// log.Debug("internal", "NewConversation", cidList)
|
||||
c.ConversationListener().OnNewConversation(cidList)
|
||||
|
||||
case constant.ConversationLatestMsgHasRead:
|
||||
hasReadMsgList := node.Args.(map[string][]string)
|
||||
var result []*model_struct.LocalConversation
|
||||
var latestMsg sdk_struct.MsgStruct
|
||||
var lc model_struct.LocalConversation
|
||||
for conversationID, msgIDList := range hasReadMsgList {
|
||||
LocalConversation, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
// log.Error("internal", "get conversation err", err.Error(), conversationID)
|
||||
continue
|
||||
}
|
||||
err = utils.JsonStringToStruct(LocalConversation.LatestMsg, &latestMsg)
|
||||
if err != nil {
|
||||
// log.Error("internal", "JsonStringToStruct err", err.Error(), conversationID)
|
||||
continue
|
||||
}
|
||||
if utils.IsContain(latestMsg.ClientMsgID, msgIDList) {
|
||||
latestMsg.IsRead = true
|
||||
lc.ConversationID = conversationID
|
||||
lc.LatestMsg = utils.StructToJsonString(latestMsg)
|
||||
LocalConversation.LatestMsg = utils.StructToJsonString(latestMsg)
|
||||
err := c.db.UpdateConversation(ctx, &lc)
|
||||
if err != nil {
|
||||
// log.Error("internal", "UpdateConversation database err:", err.Error())
|
||||
continue
|
||||
} else {
|
||||
result = append(result, LocalConversation)
|
||||
}
|
||||
}
|
||||
}
|
||||
if result != nil {
|
||||
// log.Info("internal", "getMultipleConversationModel success :", result)
|
||||
c.ConversationListener().OnNewConversation(utils.StructToJsonString(result))
|
||||
}
|
||||
case constant.SyncConversation:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conversation) doUpdateMessage(c2v common.Cmd2Value) {
|
||||
node := c2v.Value.(common.UpdateMessageNode)
|
||||
ctx := c2v.Ctx
|
||||
switch node.Action {
|
||||
case constant.UpdateMsgFaceUrlAndNickName:
|
||||
args := node.Args.(common.UpdateMessageInfo)
|
||||
switch args.SessionType {
|
||||
case constant.SingleChatType:
|
||||
if args.UserID == c.loginUserID {
|
||||
conversationIDList, err := c.db.GetAllSingleConversationIDList(ctx)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetAllSingleConversationIDList err", err)
|
||||
return
|
||||
} else {
|
||||
log.ZDebug(ctx, "get single conversationID list", "conversationIDList", conversationIDList)
|
||||
for _, conversationID := range conversationIDList {
|
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
conversationID := c.getConversationIDBySessionType(args.UserID, constant.SingleChatType)
|
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err)
|
||||
}
|
||||
|
||||
}
|
||||
case constant.SuperGroupChatType:
|
||||
conversationID := c.getConversationIDBySessionType(args.GroupID, constant.SuperGroupChatType)
|
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err)
|
||||
}
|
||||
case constant.NotificationChatType:
|
||||
conversationID := c.getConversationIDBySessionType(args.UserID, constant.NotificationChatType)
|
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err)
|
||||
}
|
||||
default:
|
||||
log.ZError(ctx, "not support sessionType", nil, "args", args)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// funcation (c *Conversation) doSyncReactionExtensions(c2v common.Cmd2Value) {
|
||||
// if c.ConversationListener == nil {
|
||||
// // log.Error("internal", "not set conversationListener")
|
||||
// return
|
||||
// }
|
||||
// node := c2v.Value.(common.SyncReactionExtensionsNode)
|
||||
// ctx := mcontext.NewCtx(node.OperationID)
|
||||
// switch node.Action {
|
||||
// case constant.SyncMessageListReactionExtensions:
|
||||
// args := node.Args.(syncReactionExtensionParams)
|
||||
// // log.Error(node.OperationID, "come SyncMessageListReactionExtensions", args)
|
||||
// var reqList []server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// for _, v := range args.MessageList {
|
||||
// var temp server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// temp.ClientMsgID = v.ClientMsgID
|
||||
// temp.MsgFirstModifyTime = v.MsgFirstModifyTime
|
||||
// reqList = append(reqList, temp)
|
||||
// }
|
||||
// var apiReq server_api_params.GetMessageListReactionExtensionsReq
|
||||
// apiReq.SourceID = args.SourceID
|
||||
// apiReq.TypeKeyList = args.TypeKeyList
|
||||
// apiReq.SessionType = args.SessionType
|
||||
// apiReq.MessageReactionKeyList = reqList
|
||||
// apiReq.IsExternalExtensions = args.IsExternalExtension
|
||||
// apiReq.OperationID = node.OperationID
|
||||
// apiResp, err := util.CallApi[server_api_params.GetMessageListReactionExtensionsResp](ctx, constant.GetMessageListReactionExtensionsRouter, &apiReq)
|
||||
// if err != nil {
|
||||
// // log.NewError(node.OperationID, utils.GetSelfFuncName(), "getMessageListReactionExtensions err:", err.Error())
|
||||
// return
|
||||
// }
|
||||
// // for _, result := range apiResp {
|
||||
// // log.Warn(node.OperationID, "api return reslut is:", result.ClientMsgID, result.ReactionExtensionList)
|
||||
// // }
|
||||
// onLocal := funcation(data []*model_struct.LocalChatLogReactionExtensions) []*server_api_params.SingleMessageExtensionResult {
|
||||
// var result []*server_api_params.SingleMessageExtensionResult
|
||||
// for _, v := range data {
|
||||
// temp := new(server_api_params.SingleMessageExtensionResult)
|
||||
// tempMap := make(map[string]*sdkws.KeyValue)
|
||||
// _ = json.Unmarshal(v.LocalReactionExtensions, &tempMap)
|
||||
// if len(args.TypeKeyList) != 0 {
|
||||
// for s, _ := range tempMap {
|
||||
// if !utils.IsContain(s, args.TypeKeyList) {
|
||||
// delete(tempMap, s)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// temp.ReactionExtensionList = tempMap
|
||||
// temp.ClientMsgID = v.ClientMsgID
|
||||
// result = append(result, temp)
|
||||
// }
|
||||
// return result
|
||||
// }(args.ExtendMessageList)
|
||||
// var onServer []*server_api_params.SingleMessageExtensionResult
|
||||
// for _, v := range *apiResp {
|
||||
// if v.ErrCode == 0 {
|
||||
// onServer = append(onServer, v)
|
||||
// }
|
||||
// }
|
||||
// aInBNot, _, sameA, _ := common.CheckReactionExtensionsDiff(onServer, onLocal)
|
||||
// for _, v := range aInBNot {
|
||||
// // log.Error(node.OperationID, "come InsertMessageReactionExtension", args, v.ClientMsgID)
|
||||
// if len(v.ReactionExtensionList) > 0 {
|
||||
// temp := model_struct.LocalChatLogReactionExtensions{ClientMsgID: v.ClientMsgID, LocalReactionExtensions: []byte(utils.StructToJsonString(v.ReactionExtensionList))}
|
||||
// err := c.db.InsertMessageReactionExtension(ctx, &temp)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "InsertMessageReactionExtension err:", err.Error())
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// var changedKv []*sdkws.KeyValue
|
||||
// for _, value := range v.ReactionExtensionList {
|
||||
// changedKv = append(changedKv, value)
|
||||
// }
|
||||
// if len(changedKv) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsChanged(v.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// }
|
||||
// }
|
||||
// // for _, result := range sameA {
|
||||
// // log.ZWarn(ctx, "result", result.ReactionExtensionList, result.ClientMsgID)
|
||||
// // }
|
||||
// for _, v := range sameA {
|
||||
// // log.Error(node.OperationID, "come sameA", v.ClientMsgID, v.ReactionExtensionList)
|
||||
// tempMap := make(map[string]*sdkws.KeyValue)
|
||||
// for _, extensions := range args.ExtendMessageList {
|
||||
// if v.ClientMsgID == extensions.ClientMsgID {
|
||||
// _ = json.Unmarshal(extensions.LocalReactionExtensions, &tempMap)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if len(v.ReactionExtensionList) == 0 {
|
||||
// err := c.db.DeleteMessageReactionExtension(ctx, v.ClientMsgID)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "DeleteMessageReactionExtension err:", err.Error())
|
||||
// continue
|
||||
// }
|
||||
// var deleteKeyList []string
|
||||
// for key, _ := range tempMap {
|
||||
// deleteKeyList = append(deleteKeyList, key)
|
||||
// }
|
||||
// if len(deleteKeyList) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsDeleted(v.ClientMsgID, utils.StructToJsonString(deleteKeyList))
|
||||
// }
|
||||
// } else {
|
||||
// deleteKeyList, changedKv := funcation(local, server map[string]*sdkws.KeyValue) ([]string, []*sdkws.KeyValue) {
|
||||
// var deleteKeyList []string
|
||||
// var changedKv []*sdkws.KeyValue
|
||||
// for k, v := range local {
|
||||
// ia, ok := server[k]
|
||||
// if ok {
|
||||
// //服务器不同的kv
|
||||
// if ia.Value != v.Value {
|
||||
// changedKv = append(changedKv, ia)
|
||||
// }
|
||||
// } else {
|
||||
// //服务器已经没有kv
|
||||
// deleteKeyList = append(deleteKeyList, k)
|
||||
// }
|
||||
// }
|
||||
// //从服务器新增的kv
|
||||
// for k, v := range server {
|
||||
// _, ok := local[k]
|
||||
// if !ok {
|
||||
// changedKv = append(changedKv, v)
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// return deleteKeyList, changedKv
|
||||
// }(tempMap, v.ReactionExtensionList)
|
||||
// extendMsg := model_struct.LocalChatLogReactionExtensions{ClientMsgID: v.ClientMsgID, LocalReactionExtensions: []byte(utils.StructToJsonString(v.ReactionExtensionList))}
|
||||
// err = c.db.UpdateMessageReactionExtension(ctx, &extendMsg)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "UpdateMessageReactionExtension err:", err.Error())
|
||||
// continue
|
||||
// }
|
||||
// if len(deleteKeyList) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsDeleted(v.ClientMsgID, utils.StructToJsonString(deleteKeyList))
|
||||
// }
|
||||
// if len(changedKv) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsChanged(v.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// }
|
||||
// }
|
||||
// //err := c.db.GetAndUpdateMessageReactionExtension(v.ClientMsgID, v.ReactionExtensionList)
|
||||
// //if err != nil {
|
||||
// // log.Error(node.OperationID, "GetAndUpdateMessageReactionExtension err:", err.Error())
|
||||
// // continue
|
||||
// //}
|
||||
// //var changedKv []*server_api_params.KeyValue
|
||||
// //for _, value := range v.ReactionExtensionList {
|
||||
// // changedKv = append(changedKv, value)
|
||||
// //}
|
||||
// //if len(changedKv) > 0 {
|
||||
// // c.msgListener.OnRecvMessageExtensionsChanged(v.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// //}
|
||||
// }
|
||||
// case constant.SyncMessageListTypeKeyInfo:
|
||||
// messageList := node.Args.([]*sdk_struct.MsgStruct)
|
||||
// var sourceID string
|
||||
// var sessionType int32
|
||||
// var reqList []server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// var temp server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// for _, v := range messageList {
|
||||
// //todo syncMessage must sync
|
||||
// message, err := c.db.GetMessage(ctx, "", v.ClientMsgID)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "GetMessageController err:", err.Error(), *v)
|
||||
// continue
|
||||
// }
|
||||
// temp.ClientMsgID = message.ClientMsgID
|
||||
// temp.MsgFirstModifyTime = message.MsgFirstModifyTime
|
||||
// reqList = append(reqList, temp)
|
||||
// switch message.SessionType {
|
||||
// case constant.SingleChatType:
|
||||
// sourceID = message.SendID + message.RecvID
|
||||
// case constant.NotificationChatType:
|
||||
// sourceID = message.RecvID
|
||||
// case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
// sourceID = message.RecvID
|
||||
// }
|
||||
// sessionType = message.SessionType
|
||||
// }
|
||||
// var apiReq server_api_params.GetMessageListReactionExtensionsReq
|
||||
// apiReq.SourceID = sourceID
|
||||
// apiReq.SessionType = sessionType
|
||||
// apiReq.MessageReactionKeyList = reqList
|
||||
// apiReq.OperationID = node.OperationID
|
||||
// //var apiResp server_api_params.GetMessageListReactionExtensionsResp
|
||||
//
|
||||
// apiResp, err := util.CallApi[server_api_params.GetMessageListReactionExtensionsResp](ctx, constant.GetMessageListReactionExtensionsRouter, &apiReq)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "GetMessageListReactionExtensions from server err:", err.Error(), apiReq)
|
||||
// return
|
||||
// }
|
||||
// var messageChangedList []*messageKvList
|
||||
// for _, v := range *apiResp {
|
||||
// if v.ErrCode == 0 {
|
||||
// var changedKv []*sdkws.KeyValue
|
||||
// var prefixTypeKey []string
|
||||
// extendMsg, _ := c.db.GetMessageReactionExtension(ctx, v.ClientMsgID)
|
||||
// localKV := make(map[string]*sdkws.KeyValue)
|
||||
// _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &localKV)
|
||||
// for typeKey, value := range v.ReactionExtensionList {
|
||||
// oldValue, ok := localKV[typeKey]
|
||||
// if ok {
|
||||
// if !cmp.Equal(value, oldValue) {
|
||||
// localKV[typeKey] = value
|
||||
// prefixTypeKey = append(prefixTypeKey, getPrefixTypeKey(typeKey))
|
||||
// changedKv = append(changedKv, value)
|
||||
// }
|
||||
// } else {
|
||||
// localKV[typeKey] = value
|
||||
// prefixTypeKey = append(prefixTypeKey, getPrefixTypeKey(typeKey))
|
||||
// changedKv = append(changedKv, value)
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// extendMsg.LocalReactionExtensions = []byte(utils.StructToJsonString(localKV))
|
||||
// _ = c.db.UpdateMessageReactionExtension(ctx, extendMsg)
|
||||
// if len(changedKv) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsChanged(extendMsg.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// }
|
||||
// prefixTypeKey = utils.RemoveRepeatedStringInList(prefixTypeKey)
|
||||
// if len(prefixTypeKey) > 0 && c.msgKvListener != nil {
|
||||
// var result []*sdk.SingleTypeKeyInfoSum
|
||||
// oneMessageChanged := new(messageKvList)
|
||||
// oneMessageChanged.ClientMsgID = extendMsg.ClientMsgID
|
||||
// for _, v := range prefixTypeKey {
|
||||
// singleResult := new(sdk.SingleTypeKeyInfoSum)
|
||||
// singleResult.TypeKey = v
|
||||
// for typeKey, value := range localKV {
|
||||
// if strings.HasPrefix(typeKey, v) {
|
||||
// singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo)
|
||||
// err := json.Unmarshal([]byte(value.Value), singleTypeKeyInfo)
|
||||
// if err != nil {
|
||||
// continue
|
||||
// }
|
||||
// if _, ok := singleTypeKeyInfo.InfoList[c.loginUserID]; ok {
|
||||
// singleResult.IsContainSelf = true
|
||||
// }
|
||||
// for _, info := range singleTypeKeyInfo.InfoList {
|
||||
// v := *info
|
||||
// singleResult.InfoList = append(singleResult.InfoList, &v)
|
||||
// }
|
||||
// singleResult.Counter += singleTypeKeyInfo.Counter
|
||||
// }
|
||||
// }
|
||||
// result = append(result, singleResult)
|
||||
// }
|
||||
// oneMessageChanged.ChangedKvList = result
|
||||
// messageChangedList = append(messageChangedList, oneMessageChanged)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if len(messageChangedList) > 0 && c.msgKvListener != nil {
|
||||
// c.msgKvListener.OnMessageKvInfoChanged(utils.StructToJsonString(messageChangedList))
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
func (c *Conversation) DoConversationChangedNotification(ctx context.Context, msg *sdkws.MsgData) {
|
||||
//var notification sdkws.ConversationChangedNotification
|
||||
tips := &sdkws.ConversationUpdateTips{}
|
||||
if err := utils.UnmarshalNotificationElem(msg.Content, tips); err != nil {
|
||||
log.ZError(ctx, "UnmarshalNotificationElem err", err, "msg", msg)
|
||||
return
|
||||
}
|
||||
|
||||
c.SyncConversations(ctx, tips.ConversationIDList)
|
||||
|
||||
}
|
||||
|
||||
func (c *Conversation) DoConversationIsPrivateChangedNotification(ctx context.Context, msg *sdkws.MsgData) {
|
||||
tips := &sdkws.ConversationSetPrivateTips{}
|
||||
if err := utils.UnmarshalNotificationElem(msg.Content, tips); err != nil {
|
||||
log.ZError(ctx, "UnmarshalNotificationElem err", err, "msg", msg)
|
||||
return
|
||||
}
|
||||
|
||||
c.SyncConversations(ctx, []string{tips.ConversationID})
|
||||
|
||||
}
|
||||
57
go/chao-sdk-core/internal/conversation_msg/convert.go
Normal file
57
go/chao-sdk-core/internal/conversation_msg/convert.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
|
||||
|
||||
pbConversation "github.com/openimsdk/protocol/conversation"
|
||||
)
|
||||
|
||||
func ServerConversationToLocal(conversation *pbConversation.Conversation) *model_struct.LocalConversation {
|
||||
return &model_struct.LocalConversation{
|
||||
ConversationID: conversation.ConversationID,
|
||||
ConversationType: conversation.ConversationType,
|
||||
UserID: conversation.UserID,
|
||||
GroupID: conversation.GroupID,
|
||||
RecvMsgOpt: conversation.RecvMsgOpt,
|
||||
GroupAtType: conversation.GroupAtType,
|
||||
IsPinned: conversation.IsPinned,
|
||||
BurnDuration: conversation.BurnDuration,
|
||||
IsPrivateChat: conversation.IsPrivateChat,
|
||||
AttachedInfo: conversation.AttachedInfo,
|
||||
Ex: conversation.Ex,
|
||||
MsgDestructTime: conversation.MsgDestructTime,
|
||||
IsMsgDestruct: conversation.IsMsgDestruct,
|
||||
}
|
||||
}
|
||||
|
||||
func LocalConversationToServer(conversation *model_struct.LocalConversation) *pbConversation.Conversation {
|
||||
return &pbConversation.Conversation{
|
||||
ConversationID: conversation.ConversationID,
|
||||
ConversationType: conversation.ConversationType,
|
||||
UserID: conversation.UserID,
|
||||
GroupID: conversation.GroupID,
|
||||
RecvMsgOpt: conversation.RecvMsgOpt,
|
||||
GroupAtType: conversation.GroupAtType,
|
||||
IsPinned: conversation.IsPinned,
|
||||
BurnDuration: conversation.BurnDuration,
|
||||
IsPrivateChat: conversation.IsPrivateChat,
|
||||
AttachedInfo: conversation.AttachedInfo,
|
||||
MsgDestructTime: conversation.MsgDestructTime,
|
||||
Ex: conversation.Ex,
|
||||
IsMsgDestruct: conversation.IsMsgDestruct,
|
||||
}
|
||||
}
|
||||
492
go/chao-sdk-core/internal/conversation_msg/create_message.go
Normal file
492
go/chao-sdk-core/internal/conversation_msg/create_message.go
Normal file
@@ -0,0 +1,492 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
||||
"github.com/openimsdk/tools/log"
|
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *Conversation) CreateTextMessage(ctx context.Context, text string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.TextElem = &sdk_struct.TextElem{Content: text}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateAdvancedTextMessage(ctx context.Context, text string, messageEntities []*sdk_struct.MessageEntity) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.AdvancedText)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.AdvancedTextElem = &sdk_struct.AdvancedTextElem{
|
||||
Text: text,
|
||||
MessageEntityList: messageEntities,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (c *Conversation) CreateTextAtMessage(ctx context.Context, text string, userIDList []string, usersInfo []*sdk_struct.AtInfo, qs *sdk_struct.MsgStruct) (*sdk_struct.MsgStruct, error) {
|
||||
if text == "" {
|
||||
return nil, errors.New("text can not be empty")
|
||||
}
|
||||
if len(userIDList) > 10 {
|
||||
return nil, sdkerrs.ErrArgs
|
||||
}
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.AtText)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Avoid nested references
|
||||
if qs != nil {
|
||||
if qs.ContentType == constant.Quote {
|
||||
qs.ContentType = constant.Text
|
||||
qs.TextElem = &sdk_struct.TextElem{Content: qs.QuoteElem.Text}
|
||||
}
|
||||
}
|
||||
s.AtTextElem = &sdk_struct.AtTextElem{
|
||||
Text: text,
|
||||
AtUserList: userIDList,
|
||||
AtUsersInfo: usersInfo,
|
||||
QuoteMessage: qs,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateLocationMessage(ctx context.Context, description string, longitude, latitude float64) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.LocationElem = &sdk_struct.LocationElem{
|
||||
Description: description,
|
||||
Longitude: longitude,
|
||||
Latitude: latitude,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (c *Conversation) CreateCustomMessage(ctx context.Context, data, extension string, description string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Custom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.CustomElem = &sdk_struct.CustomElem{
|
||||
Data: data,
|
||||
Extension: extension,
|
||||
Description: description,
|
||||
}
|
||||
return &s, nil
|
||||
|
||||
}
|
||||
func (c *Conversation) CreateQuoteMessage(ctx context.Context, text string, qs *sdk_struct.MsgStruct) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Quote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Avoid nested references
|
||||
if qs.ContentType == constant.Quote {
|
||||
qs.ContentType = constant.Text
|
||||
qs.TextElem = &sdk_struct.TextElem{Content: qs.QuoteElem.Text}
|
||||
}
|
||||
s.QuoteElem = &sdk_struct.QuoteElem{
|
||||
Text: text,
|
||||
QuoteMessage: qs,
|
||||
}
|
||||
return &s, nil
|
||||
|
||||
}
|
||||
func (c *Conversation) CreateAdvancedQuoteMessage(ctx context.Context, text string, qs *sdk_struct.MsgStruct, messageEntities []*sdk_struct.MessageEntity) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Quote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Avoid nested references
|
||||
if qs.ContentType == constant.Quote {
|
||||
//qs.Content = qs.QuoteElem.Text
|
||||
qs.ContentType = constant.Text
|
||||
qs.TextElem = &sdk_struct.TextElem{Content: qs.QuoteElem.Text}
|
||||
}
|
||||
s.QuoteElem = &sdk_struct.QuoteElem{
|
||||
Text: text,
|
||||
QuoteMessage: qs,
|
||||
MessageEntityList: messageEntities,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (c *Conversation) CreateCardMessage(ctx context.Context, card *sdk_struct.CardElem) (*sdk_struct.MsgStruct,
|
||||
error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Card)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.CardElem = card
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (c *Conversation) CreateVideoMessageFromFullPath(ctx context.Context, videoFullPath string, videoType string,
|
||||
duration int64, snapshotFullPath string) (*sdk_struct.MsgStruct, error) {
|
||||
dstFile := utils.FileTmpPath(videoFullPath, c.DataDir) //a->b
|
||||
written, err := utils.CopyFile(videoFullPath, dstFile)
|
||||
if err != nil {
|
||||
//log.Error("internal", "open file failed: ", err, videoFullPath)
|
||||
return nil, err
|
||||
}
|
||||
log.ZDebug(ctx, "videoFullPath dstFile", "videoFullPath", videoFullPath,
|
||||
"dstFile", dstFile, "written", written)
|
||||
|
||||
dstFile = utils.FileTmpPath(snapshotFullPath, c.DataDir) //a->b
|
||||
sWritten, err := utils.CopyFile(snapshotFullPath, dstFile)
|
||||
if err != nil {
|
||||
//log.Error("internal", "open file failed: ", err, snapshotFullPath)
|
||||
return nil, err
|
||||
}
|
||||
log.ZDebug(ctx, "snapshotFullPath dstFile", "snapshotFullPath", snapshotFullPath,
|
||||
"dstFile", dstFile, "sWritten", sWritten)
|
||||
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Video)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem = &sdk_struct.VideoElem{
|
||||
VideoPath: videoFullPath,
|
||||
VideoType: videoType,
|
||||
Duration: duration,
|
||||
}
|
||||
if snapshotFullPath == "" {
|
||||
s.VideoElem.SnapshotPath = ""
|
||||
} else {
|
||||
s.VideoElem.SnapshotPath = snapshotFullPath
|
||||
}
|
||||
fi, err := os.Stat(s.VideoElem.VideoPath)
|
||||
if err != nil {
|
||||
//log.Error("internal", "get file Attributes error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem.VideoSize = fi.Size()
|
||||
if snapshotFullPath != "" {
|
||||
imageInfo, err := getImageInfo(s.VideoElem.SnapshotPath)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "getImageInfo err:", err, "snapshotFullPath", snapshotFullPath)
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem.SnapshotHeight = imageInfo.Height
|
||||
s.VideoElem.SnapshotWidth = imageInfo.Width
|
||||
s.VideoElem.SnapshotSize = imageInfo.Size
|
||||
}
|
||||
return &s, nil
|
||||
|
||||
}
|
||||
func (c *Conversation) CreateFileMessageFromFullPath(ctx context.Context, fileFullPath string, fileName string) (*sdk_struct.MsgStruct, error) {
|
||||
dstFile := utils.FileTmpPath(fileFullPath, c.DataDir)
|
||||
_, err := utils.CopyFile(fileFullPath, dstFile)
|
||||
if err != nil {
|
||||
//log.Error("internal", "open file failed: ", err.Error(), fileFullPath)
|
||||
return nil, err
|
||||
|
||||
}
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := os.Stat(fileFullPath)
|
||||
if err != nil {
|
||||
//log.Error("internal", "get file Attributes error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.FileElem = &sdk_struct.FileElem{
|
||||
FilePath: fileFullPath,
|
||||
FileName: fileName,
|
||||
FileSize: fi.Size(),
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateImageMessageFromFullPath(ctx context.Context, imageFullPath string) (*sdk_struct.MsgStruct, error) {
|
||||
dstFile := utils.FileTmpPath(imageFullPath, c.DataDir) //a->b
|
||||
_, err := utils.CopyFile(imageFullPath, dstFile)
|
||||
if err != nil {
|
||||
//log.Error(operationID, "open file failed: ", err, imageFullPath)
|
||||
return nil, err
|
||||
}
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Picture)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageInfo, err := getImageInfo(imageFullPath)
|
||||
if err != nil {
|
||||
//log.Error(operationID, "getImageInfo err:", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.PictureElem = &sdk_struct.PictureElem{
|
||||
SourcePath: imageFullPath,
|
||||
SourcePicture: &sdk_struct.PictureBaseInfo{
|
||||
Width: imageInfo.Width,
|
||||
Height: imageInfo.Height,
|
||||
Type: imageInfo.Type,
|
||||
},
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateSoundMessageFromFullPath(ctx context.Context, soundPath string, duration int64) (*sdk_struct.MsgStruct, error) {
|
||||
dstFile := utils.FileTmpPath(soundPath, c.DataDir) //a->b
|
||||
_, err := utils.CopyFile(soundPath, dstFile)
|
||||
if err != nil {
|
||||
//log.Error("internal", "open file failed: ", err, soundPath)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Sound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := os.Stat(soundPath)
|
||||
if err != nil {
|
||||
//log.Error("internal", "getSoundInfo err:", err.Error(), s.SoundElem.SoundPath)
|
||||
return nil, err
|
||||
}
|
||||
s.SoundElem = &sdk_struct.SoundElem{
|
||||
SoundPath: soundPath,
|
||||
Duration: duration,
|
||||
DataSize: fi.Size(),
|
||||
SoundType: strings.Replace(filepath.Ext(fi.Name()), ".", "", 1),
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateImageMessage(ctx context.Context, imagePath string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Picture)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := c.DataDir + imagePath
|
||||
//path := imagePath
|
||||
imageInfo, err := getImageInfo(path)
|
||||
if err != nil {
|
||||
//log.Error("internal", "get imageInfo err", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.PictureElem = &sdk_struct.PictureElem{
|
||||
SourcePath: path,
|
||||
SourcePicture: &sdk_struct.PictureBaseInfo{
|
||||
Width: imageInfo.Width,
|
||||
Height: imageInfo.Height,
|
||||
Type: imageInfo.Type,
|
||||
},
|
||||
}
|
||||
return &s, nil
|
||||
|
||||
}
|
||||
func (c *Conversation) CreateImageMessageByURL(ctx context.Context, sourcePath string, sourcePicture, bigPicture, snapshotPicture sdk_struct.PictureBaseInfo) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Picture)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.PictureElem = &sdk_struct.PictureElem{
|
||||
SourcePath: sourcePath,
|
||||
SourcePicture: &sourcePicture,
|
||||
BigPicture: &bigPicture,
|
||||
SnapshotPicture: &snapshotPicture,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateSoundMessageByURL(ctx context.Context, soundElem *sdk_struct.SoundBaseInfo) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Sound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.SoundElem = &sdk_struct.SoundElem{
|
||||
UUID: soundElem.UUID,
|
||||
SoundPath: soundElem.SoundPath,
|
||||
SourceURL: soundElem.SourceURL,
|
||||
DataSize: soundElem.DataSize,
|
||||
Duration: soundElem.Duration,
|
||||
SoundType: soundElem.SoundType,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateSoundMessage(ctx context.Context, soundPath string, duration int64) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Sound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := c.DataDir + soundPath
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
//log.Error("internal", "get sound info err", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.SoundElem = &sdk_struct.SoundElem{
|
||||
SoundPath: path,
|
||||
Duration: duration,
|
||||
DataSize: fi.Size(),
|
||||
}
|
||||
if typ := strings.Replace(filepath.Ext(fi.Name()), ".", "", 1); typ != "" {
|
||||
s.SoundElem.SoundType = "audio/" + strings.ToLower(typ)
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateVideoMessageByURL(ctx context.Context, videoElem sdk_struct.VideoBaseInfo) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Video)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem = &sdk_struct.VideoElem{
|
||||
VideoPath: videoElem.VideoPath,
|
||||
VideoUUID: videoElem.VideoUUID,
|
||||
VideoURL: videoElem.VideoURL,
|
||||
VideoType: videoElem.VideoType,
|
||||
VideoSize: videoElem.VideoSize,
|
||||
Duration: videoElem.Duration,
|
||||
SnapshotPath: videoElem.SnapshotPath,
|
||||
SnapshotUUID: videoElem.SnapshotUUID,
|
||||
SnapshotSize: videoElem.SnapshotSize,
|
||||
SnapshotURL: videoElem.SnapshotURL,
|
||||
SnapshotWidth: videoElem.SnapshotWidth,
|
||||
SnapshotHeight: videoElem.SnapshotHeight,
|
||||
SnapshotType: videoElem.SnapshotType,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateVideoMessage(ctx context.Context, videoPath string, videoType string, duration int64, snapshotPath string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Video)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem = &sdk_struct.VideoElem{}
|
||||
s.VideoElem.VideoPath = c.DataDir + videoPath
|
||||
s.VideoElem.VideoType = videoType
|
||||
s.VideoElem.Duration = duration
|
||||
if snapshotPath == "" {
|
||||
s.VideoElem.SnapshotPath = ""
|
||||
} else {
|
||||
s.VideoElem.SnapshotPath = c.DataDir + snapshotPath
|
||||
}
|
||||
fi, err := os.Stat(s.VideoElem.VideoPath)
|
||||
if err != nil {
|
||||
log.ZDebug(ctx, "get video file error", "videoPath", videoPath, "snapshotPath", snapshotPath)
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem.VideoSize = fi.Size()
|
||||
if snapshotPath != "" {
|
||||
imageInfo, err := getImageInfo(s.VideoElem.SnapshotPath)
|
||||
if err != nil {
|
||||
//log.Error("internal", "get snapshot info ", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.VideoElem.SnapshotHeight = imageInfo.Height
|
||||
s.VideoElem.SnapshotWidth = imageInfo.Width
|
||||
s.VideoElem.SnapshotSize = imageInfo.Size
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateFileMessageByURL(ctx context.Context, fileElem sdk_struct.FileBaseInfo) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.FileElem = &sdk_struct.FileElem{
|
||||
FilePath: fileElem.FilePath,
|
||||
UUID: fileElem.UUID,
|
||||
SourceURL: fileElem.SourceURL,
|
||||
FileName: fileElem.FileName,
|
||||
FileSize: fileElem.FileSize,
|
||||
FileType: fileElem.FileType,
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateFileMessage(ctx context.Context, filePath string, fileName string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{FileElem: &sdk_struct.FileElem{}}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.FileElem.FilePath = c.DataDir + filePath
|
||||
s.FileElem.FileName = fileName
|
||||
fi, err := os.Stat(s.FileElem.FilePath)
|
||||
if err != nil {
|
||||
//log.Error("internal", "get file message err", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
s.FileElem.FileSize = fi.Size()
|
||||
s.Content = utils.StructToJsonString(s.FileElem)
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateMergerMessage(ctx context.Context, messages []*sdk_struct.MsgStruct, title string, summaries []string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{MergeElem: &sdk_struct.MergeElem{}}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Merger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.MergeElem.AbstractList = summaries
|
||||
s.MergeElem.Title = title
|
||||
s.MergeElem.MultiMessage = messages
|
||||
s.Content = utils.StructToJsonString(s.MergeElem)
|
||||
return &s, nil
|
||||
}
|
||||
func (c *Conversation) CreateFaceMessage(ctx context.Context, index int, data string) (*sdk_struct.MsgStruct, error) {
|
||||
s := sdk_struct.MsgStruct{FaceElem: &sdk_struct.FaceElem{}}
|
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Face)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.FaceElem.Data = data
|
||||
s.FaceElem.Index = index
|
||||
s.Content = utils.StructToJsonString(s.FaceElem)
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (c *Conversation) CreateForwardMessage(ctx context.Context, s *sdk_struct.MsgStruct) (*sdk_struct.MsgStruct, error) {
|
||||
if s.Status != constant.MsgStatusSendSuccess {
|
||||
log.ZError(ctx, "only send success message can be Forward",
|
||||
errors.New("only send success message can be Forward"))
|
||||
return nil, errors.New("only send success message can be Forward")
|
||||
}
|
||||
err := c.initBasicInfo(ctx, s, constant.UserMsgType, s.ContentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Forward message seq is set to 0
|
||||
s.Seq = 0
|
||||
s.Status = constant.MsgStatusSendSuccess
|
||||
return s, nil
|
||||
}
|
||||
245
go/chao-sdk-core/internal/conversation_msg/delete.go
Normal file
245
go/chao-sdk-core/internal/conversation_msg/delete.go
Normal file
@@ -0,0 +1,245 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
|
||||
"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/sdkerrs"
|
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
pbMsg "github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
// Delete the local and server
|
||||
// Delete the local, do not change the server data
|
||||
// To delete the server, you need to change the local message status to delete
|
||||
func (c *Conversation) clearConversationFromLocalAndSvr(ctx context.Context, conversationID string, f func(ctx context.Context, conversationID string) error) error {
|
||||
_, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Use conversationID to remove conversations and messages from the server first
|
||||
err = c.clearConversationMsgFromSvr(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.clearConversationAndDeleteAllMsg(ctx, conversationID, false, f); err != nil {
|
||||
return err
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{conversationID}}})
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conversation) clearConversationAndDeleteAllMsg(ctx context.Context, conversationID string, markDelete bool, f func(ctx context.Context, conversationID string) error) error {
|
||||
err := c.getConversationMaxSeqAndSetHasRead(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if markDelete {
|
||||
err = c.db.MarkDeleteConversationAllMessages(ctx, conversationID)
|
||||
} else {
|
||||
err = c.db.DeleteConversationAllMessages(ctx, conversationID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZDebug(ctx, "reset conversation", "conversationID", conversationID)
|
||||
err = f(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// To delete session information, delete the server first, and then invoke the interface.
|
||||
// The client receives a callback to delete all local information.
|
||||
func (c *Conversation) clearConversationMsgFromSvr(ctx context.Context, conversationID string) error {
|
||||
var apiReq pbMsg.ClearConversationsMsgReq
|
||||
apiReq.UserID = c.loginUserID
|
||||
apiReq.ConversationIDs = []string{conversationID}
|
||||
return util.ApiPost(ctx, constant.ClearConversationMsgRouter, &apiReq, nil)
|
||||
}
|
||||
|
||||
// Delete all messages
|
||||
func (c *Conversation) deleteAllMsgFromLocalAndSvr(ctx context.Context) error {
|
||||
// Delete the server first (high error rate), then delete it.
|
||||
err := c.deleteAllMessageFromSvr(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.deleteAllMsgFromLocal(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete all server messages
|
||||
func (c *Conversation) deleteAllMessageFromSvr(ctx context.Context) error {
|
||||
var apiReq pbMsg.UserClearAllMsgReq
|
||||
apiReq.UserID = c.loginUserID
|
||||
err := util.ApiPost(ctx, constant.ClearAllMsgRouter, &apiReq, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete all messages from the local
|
||||
func (c *Conversation) deleteAllMsgFromLocal(ctx context.Context, markDelete bool) error {
|
||||
conversations, err := c.db.GetAllConversationListDB(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var successCids []string
|
||||
log.ZDebug(ctx, "deleteAllMsgFromLocal", "conversations", conversations, "markDelete", markDelete)
|
||||
for _, v := range conversations {
|
||||
if err := c.clearConversationAndDeleteAllMsg(ctx, v.ConversationID, markDelete, c.db.ClearConversation); err != nil {
|
||||
log.ZError(ctx, "clearConversation err", err, "conversationID", v.ConversationID)
|
||||
continue
|
||||
}
|
||||
successCids = append(successCids, v.ConversationID)
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: successCids}})
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}})
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// Delete a message from the local
|
||||
func (c *Conversation) deleteMessage(ctx context.Context, conversationID string, clientMsgID string) error {
|
||||
if err := c.deleteMessageFromSvr(ctx, conversationID, clientMsgID); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.deleteMessageFromLocal(ctx, conversationID, clientMsgID)
|
||||
}
|
||||
|
||||
// The user deletes part of the message from the server
|
||||
func (c *Conversation) deleteMessageFromSvr(ctx context.Context, conversationID string, clientMsgID string) error {
|
||||
_, err := c.db.GetMessage(ctx, conversationID, clientMsgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localMessage, err := c.db.GetMessage(ctx, conversationID, clientMsgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if localMessage.Status == constant.MsgStatusSendFailed {
|
||||
log.ZInfo(ctx, "delete msg status is send failed, do not need delete", "msg", localMessage)
|
||||
return nil
|
||||
}
|
||||
if localMessage.Seq == 0 {
|
||||
log.ZInfo(ctx, "delete msg seq is 0, try again", "msg", localMessage)
|
||||
return sdkerrs.ErrMsgHasNoSeq
|
||||
}
|
||||
var apiReq pbMsg.DeleteMsgsReq
|
||||
apiReq.UserID = c.loginUserID
|
||||
apiReq.Seqs = []int64{localMessage.Seq}
|
||||
apiReq.ConversationID = conversationID
|
||||
return util.ApiPost(ctx, constant.DeleteMsgsRouter, &apiReq, nil)
|
||||
}
|
||||
|
||||
// Delete messages from local
|
||||
func (c *Conversation) deleteMessageFromLocal(ctx context.Context, conversationID string, clientMsgID string) error {
|
||||
s, err := c.db.GetMessage(ctx, conversationID, clientMsgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.db.DeleteConversationMsgs(ctx, conversationID, []string{clientMsgID}); err != nil {
|
||||
return err
|
||||
}
|
||||
if !s.IsRead && s.SendID != c.loginUserID {
|
||||
if err := c.db.DecrConversationUnreadCount(ctx, conversationID, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversationID, Action: constant.ConChange, Args: []string{conversationID}}})
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}})
|
||||
}
|
||||
conversation, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var latestMsg sdk_struct.MsgStruct
|
||||
utils.JsonStringToStruct(conversation.LatestMsg, &latestMsg)
|
||||
if latestMsg.ClientMsgID == clientMsgID {
|
||||
log.ZDebug(ctx, "latesetMsg deleted", "seq", latestMsg.Seq, "clientMsgID", latestMsg.ClientMsgID)
|
||||
msgs, err := c.db.GetMessageListNoTime(ctx, conversationID, 1, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
latestMsgSendTime := latestMsg.SendTime
|
||||
latestMsgStr := ""
|
||||
if len(msgs) > 0 {
|
||||
copier.Copy(&latestMsg, msgs[0])
|
||||
err := c.msgConvert(&latestMsg)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "parsing data error", err, latestMsg)
|
||||
}
|
||||
latestMsgStr = utils.StructToJsonString(latestMsg)
|
||||
latestMsgSendTime = latestMsg.SendTime
|
||||
}
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"latest_msg": latestMsgStr, "latest_msg_send_time": latestMsgSendTime}); err != nil {
|
||||
return err
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{conversationID}}})
|
||||
}
|
||||
c.msgListener().OnMsgDeleted(utils.StructToJsonString(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conversation) doDeleteMsgs(ctx context.Context, msg *sdkws.MsgData) {
|
||||
tips := sdkws.DeleteMsgsTips{}
|
||||
utils.UnmarshalNotificationElem(msg.Content, &tips)
|
||||
log.ZDebug(ctx, "doDeleteMsgs", "seqs", tips.Seqs)
|
||||
for _, v := range tips.Seqs {
|
||||
msg, err := c.db.GetMessageBySeq(ctx, tips.ConversationID, v)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetMessageBySeq err", err, "conversationID", tips.ConversationID, "seq", v)
|
||||
continue
|
||||
}
|
||||
var s sdk_struct.MsgStruct
|
||||
copier.Copy(&s, msg)
|
||||
err = c.msgConvert(&s)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "parsing data error", err, "msg", msg)
|
||||
}
|
||||
if err := c.deleteMessageFromLocal(ctx, tips.ConversationID, msg.ClientMsgID); err != nil {
|
||||
log.ZError(ctx, "deleteMessageFromLocal err", err, "conversationID", tips.ConversationID, "seq", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conversation) doClearConversations(ctx context.Context, msg *sdkws.MsgData) {
|
||||
tips := sdkws.ClearConversationTips{}
|
||||
utils.UnmarshalNotificationElem(msg.Content, &tips)
|
||||
log.ZDebug(ctx, "doClearConversations", "tips", tips)
|
||||
for _, v := range tips.ConversationIDs {
|
||||
if err := c.clearConversationAndDeleteAllMsg(ctx, v, false, c.db.ClearConversation); err != nil {
|
||||
log.ZError(ctx, "clearConversation err", err, "conversationID", v)
|
||||
}
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: tips.ConversationIDs}})
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}})
|
||||
}
|
||||
217
go/chao-sdk-core/internal/conversation_msg/entering.go
Normal file
217
go/chao-sdk-core/internal/conversation_msg/entering.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
_ int = iota
|
||||
stateCodeSuccess
|
||||
stateCodeEnd
|
||||
)
|
||||
|
||||
const (
|
||||
inputStatesSendTime = time.Second * 10 // input status sending interval time
|
||||
inputStatesTimeout = inputStatesSendTime + inputStatesSendTime/2 // input status timeout
|
||||
inputStatesMsgTimeout = inputStatesSendTime / 2 // message sending timeout
|
||||
)
|
||||
|
||||
func newTyping(c *Conversation) *typing {
|
||||
e := &typing{
|
||||
conv: c,
|
||||
send: cache.New(inputStatesSendTime, inputStatesTimeout),
|
||||
state: cache.New(inputStatesTimeout, inputStatesTimeout),
|
||||
}
|
||||
e.platformIDs = make([]int32, 0, len(constant.PlatformID2Name))
|
||||
e.platformIDSet = make(map[int32]struct{})
|
||||
for id := range constant.PlatformID2Name {
|
||||
e.platformIDSet[int32(id)] = struct{}{}
|
||||
e.platformIDs = append(e.platformIDs, int32(id))
|
||||
}
|
||||
datautil.Sort(e.platformIDs, true)
|
||||
e.state.OnEvicted(func(key string, val interface{}) {
|
||||
var data inputStatesKey
|
||||
if err := json.Unmarshal([]byte(key), &data); err != nil {
|
||||
return
|
||||
}
|
||||
e.changes(data.ConversationID, data.UserID)
|
||||
})
|
||||
return e
|
||||
}
|
||||
|
||||
type typing struct {
|
||||
send *cache.Cache
|
||||
state *cache.Cache
|
||||
|
||||
conv *Conversation
|
||||
|
||||
platformIDs []int32
|
||||
platformIDSet map[int32]struct{}
|
||||
}
|
||||
|
||||
func (e *typing) ChangeInputStates(ctx context.Context, conversationID string, focus bool) error {
|
||||
if conversationID == "" {
|
||||
return errs.ErrArgs.WrapMsg("conversationID can't be empty")
|
||||
}
|
||||
conversation, err := e.conv.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := conversation.ConversationID
|
||||
if focus {
|
||||
if val, ok := e.send.Get(key); ok {
|
||||
if val.(int) == stateCodeSuccess {
|
||||
log.ZDebug(ctx, "typing stateCodeSuccess", "conversationID", conversationID, "focus", focus)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
e.send.SetDefault(key, stateCodeSuccess)
|
||||
} else {
|
||||
if val, ok := e.send.Get(key); ok {
|
||||
if val.(int) == stateCodeEnd {
|
||||
log.ZDebug(ctx, "typing stateCodeEnd", "conversationID", conversationID, "focus", focus)
|
||||
return nil
|
||||
}
|
||||
e.send.SetDefault(key, stateCodeEnd)
|
||||
} else {
|
||||
log.ZDebug(ctx, "typing send not found", "conversationID", conversationID, "focus", focus)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, inputStatesMsgTimeout)
|
||||
defer cancel()
|
||||
if err := e.sendMsg(ctx, conversation, focus); err != nil {
|
||||
e.send.Delete(key)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *typing) sendMsg(ctx context.Context, conversation *model_struct.LocalConversation, focus bool) error {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
err := e.conv.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Typing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.RecvID = conversation.UserID
|
||||
s.GroupID = conversation.GroupID
|
||||
s.SessionType = conversation.ConversationType
|
||||
var typingElem sdk_struct.TypingElem
|
||||
if focus {
|
||||
typingElem.MsgTips = "yes"
|
||||
} else {
|
||||
typingElem.MsgTips = "no"
|
||||
}
|
||||
s.Content = utils.StructToJsonString(typingElem)
|
||||
options := make(map[string]bool, 6)
|
||||
utils.SetSwitchFromOptions(options, constant.IsHistory, false)
|
||||
utils.SetSwitchFromOptions(options, constant.IsPersistent, false)
|
||||
utils.SetSwitchFromOptions(options, constant.IsSenderSync, false)
|
||||
utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, false)
|
||||
utils.SetSwitchFromOptions(options, constant.IsSenderConversationUpdate, false)
|
||||
utils.SetSwitchFromOptions(options, constant.IsUnreadCount, false)
|
||||
utils.SetSwitchFromOptions(options, constant.IsOfflinePush, false)
|
||||
var wsMsgData sdkws.MsgData
|
||||
copier.Copy(&wsMsgData, s)
|
||||
wsMsgData.Content = []byte(s.Content)
|
||||
wsMsgData.CreateTime = s.CreateTime
|
||||
wsMsgData.Options = options
|
||||
var sendMsgResp sdkws.UserSendMsgResp
|
||||
err = e.conv.LongConnMgr.SendReqWaitResp(ctx, &wsMsgData, constant.SendMsg, &sendMsgResp)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "typing msg to server failed", err, "message", s)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type inputStatesKey struct {
|
||||
ConversationID string `json:"cid,omitempty"`
|
||||
UserID string `json:"uid,omitempty"`
|
||||
PlatformID int32 `json:"pid,omitempty"`
|
||||
}
|
||||
|
||||
func (e *typing) getStateKey(conversationID string, userID string, platformID int32) string {
|
||||
data, err := json.Marshal(inputStatesKey{ConversationID: conversationID, UserID: userID, PlatformID: platformID})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (e *typing) onNewMsg(ctx context.Context, msg *sdkws.MsgData) {
|
||||
var enteringElem sdk_struct.TypingElem
|
||||
if err := json.Unmarshal(msg.Content, &enteringElem); err != nil {
|
||||
log.ZError(ctx, "typing onNewMsg Unmarshal failed", err, "message", msg)
|
||||
return
|
||||
}
|
||||
if msg.SendID == e.conv.loginUserID {
|
||||
return
|
||||
}
|
||||
if _, ok := e.platformIDSet[msg.SenderPlatformID]; !ok {
|
||||
return
|
||||
}
|
||||
now := time.Now().UnixMilli()
|
||||
expirationTimestamp := msg.SendTime + int64(inputStatesSendTime/time.Millisecond)
|
||||
if msg.SendTime > now || expirationTimestamp <= now {
|
||||
return
|
||||
}
|
||||
var sourceID string
|
||||
if msg.GroupID == "" {
|
||||
sourceID = msg.SendID
|
||||
} else {
|
||||
sourceID = msg.GroupID
|
||||
}
|
||||
conversationID := e.conv.getConversationIDBySessionType(sourceID, int(msg.SessionType))
|
||||
key := e.getStateKey(conversationID, msg.SendID, msg.SenderPlatformID)
|
||||
if enteringElem.MsgTips == "yes" {
|
||||
d := time.Duration(expirationTimestamp-now) * time.Millisecond
|
||||
if v, t, ok := e.state.GetWithExpiration(key); ok {
|
||||
if t.UnixMilli() >= expirationTimestamp {
|
||||
return
|
||||
}
|
||||
e.state.Set(key, v, d)
|
||||
} else {
|
||||
e.state.Set(key, struct{}{}, d)
|
||||
e.changes(conversationID, msg.SendID)
|
||||
}
|
||||
} else {
|
||||
if _, ok := e.state.Get(key); ok {
|
||||
e.state.Delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type InputStatesChangedData struct {
|
||||
ConversationID string `json:"conversationID"`
|
||||
UserID string `json:"userID"`
|
||||
PlatformIDs []int32 `json:"platformIDs"`
|
||||
}
|
||||
|
||||
func (e *typing) changes(conversationID string, userID string) {
|
||||
data := InputStatesChangedData{ConversationID: conversationID, UserID: userID, PlatformIDs: e.GetInputStates(conversationID, userID)}
|
||||
e.conv.ConversationListener().OnConversationUserInputStatusChanged(utils.StructToJsonString(data))
|
||||
}
|
||||
|
||||
func (e *typing) GetInputStates(conversationID string, userID string) []int32 {
|
||||
platformIDs := make([]int32, 0, 1)
|
||||
for _, platformID := range e.platformIDs {
|
||||
key := e.getStateKey(conversationID, userID, platformID)
|
||||
if _, ok := e.state.Get(key); ok {
|
||||
platformIDs = append(platformIDs, platformID)
|
||||
}
|
||||
}
|
||||
return platformIDs
|
||||
}
|
||||
32
go/chao-sdk-core/internal/conversation_msg/image.go
Normal file
32
go/chao-sdk-core/internal/conversation_msg/image.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package conversation_msg
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
_ "golang.org/x/image/webp"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getImageInfo(filePath string) (*sdk_struct.ImageInfo, error) {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, errs.WrapMsg(err, "image file open err")
|
||||
}
|
||||
defer file.Close()
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, format, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, errs.WrapMsg(err, "image file decode err")
|
||||
}
|
||||
size := img.Bounds().Max
|
||||
return &sdk_struct.ImageInfo{Width: int32(size.X), Height: int32(size.Y), Type: "image/" + format, Size: info.Size()}, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import "sync"
|
||||
|
||||
type MaxSeqRecorder struct {
|
||||
seqs map[string]int64
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMaxSeqRecorder() MaxSeqRecorder {
|
||||
m := make(map[string]int64)
|
||||
return MaxSeqRecorder{seqs: m}
|
||||
}
|
||||
|
||||
func (m *MaxSeqRecorder) Get(conversationID string) int64 {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
return m.seqs[conversationID]
|
||||
}
|
||||
|
||||
func (m *MaxSeqRecorder) Set(conversationID string, seq int64) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.seqs[conversationID] = seq
|
||||
}
|
||||
|
||||
func (m *MaxSeqRecorder) Incr(conversationID string, num int64) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
m.seqs[conversationID] += num
|
||||
}
|
||||
|
||||
func (m *MaxSeqRecorder) IsNewMsg(conversationID string, seq int64) bool {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
currentSeq := m.seqs[conversationID]
|
||||
return seq > currentSeq
|
||||
}
|
||||
347
go/chao-sdk-core/internal/conversation_msg/message_check.go
Normal file
347
go/chao-sdk-core/internal/conversation_msg/message_check.go
Normal file
@@ -0,0 +1,347 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct"
|
||||
sdk "github.com/openimsdk/openim-sdk-core/v3/pkg/sdk_params_callback"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
|
||||
"github.com/openimsdk/tools/log"
|
||||
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
)
|
||||
|
||||
// 检测其内部连续性,如果不连续,则向前补齐,获取这一组消息的最大最小seq,以及需要补齐的seq列表长度
|
||||
func (c *Conversation) messageBlocksInternalContinuityCheck(ctx context.Context, conversationID string, notStartTime, isReverse bool, count int,
|
||||
startTime int64, list *[]*model_struct.LocalChatLog, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) (max, min int64, length int) {
|
||||
var lostSeqListLength int
|
||||
maxSeq, minSeq, haveSeqList := c.getMaxAndMinHaveSeqList(*list)
|
||||
log.ZDebug(ctx, "getMaxAndMinHaveSeqList is:", "maxSeq", maxSeq, "minSeq", minSeq, "haveSeqList", haveSeqList)
|
||||
if maxSeq != 0 && minSeq != 0 {
|
||||
successiveSeqList := func(max, min int64) (seqList []int64) {
|
||||
for i := min; i <= max; i++ {
|
||||
seqList = append(seqList, i)
|
||||
}
|
||||
return seqList
|
||||
}(maxSeq, minSeq)
|
||||
lostSeqList := utils.DifferenceSubset(successiveSeqList, haveSeqList)
|
||||
lostSeqListLength = len(lostSeqList)
|
||||
log.ZDebug(ctx, "get lost seqList is :", "maxSeq", maxSeq, "minSeq", minSeq, "lostSeqList", lostSeqList, "length:", lostSeqListLength)
|
||||
if lostSeqListLength > 0 {
|
||||
var pullSeqList []int64
|
||||
if lostSeqListLength <= constant.PullMsgNumForReadDiffusion {
|
||||
pullSeqList = lostSeqList
|
||||
} else {
|
||||
pullSeqList = lostSeqList[lostSeqListLength-constant.PullMsgNumForReadDiffusion : lostSeqListLength]
|
||||
}
|
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, pullSeqList, notStartTime, isReverse, count, startTime, list, messageListCallback)
|
||||
}
|
||||
|
||||
}
|
||||
return maxSeq, minSeq, lostSeqListLength
|
||||
}
|
||||
|
||||
// 检测消息块之间的连续性,如果不连续,则向前补齐,返回块之间是否连续,bool
|
||||
func (c *Conversation) messageBlocksBetweenContinuityCheck(ctx context.Context, lastMinSeq, maxSeq int64, conversationID string,
|
||||
notStartTime, isReverse bool, count int, startTime int64, list *[]*model_struct.LocalChatLog, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) bool {
|
||||
if lastMinSeq != 0 {
|
||||
log.ZDebug(ctx, "get lost LastMinSeq is :", "lastMinSeq", lastMinSeq, "thisMaxSeq", maxSeq)
|
||||
if maxSeq != 0 {
|
||||
if maxSeq+1 != lastMinSeq {
|
||||
startSeq := int64(lastMinSeq) - constant.PullMsgNumForReadDiffusion
|
||||
if startSeq <= maxSeq {
|
||||
startSeq = int64(maxSeq) + 1
|
||||
}
|
||||
successiveSeqList := func(max, min int64) (seqList []int64) {
|
||||
for i := min; i <= max; i++ {
|
||||
seqList = append(seqList, i)
|
||||
}
|
||||
return seqList
|
||||
}(lastMinSeq-1, startSeq)
|
||||
log.ZDebug(ctx, "get lost successiveSeqList is :", "successiveSeqList", successiveSeqList, "length:", len(successiveSeqList))
|
||||
if len(successiveSeqList) > 0 {
|
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, successiveSeqList, notStartTime, isReverse, count, startTime, list, messageListCallback)
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 根据最小seq向前补齐消息,由服务器告诉拉取消息结果是否到底,如果网络,则向前补齐,获取这一组消息的最大最小seq,以及需要补齐的seq列表长度
|
||||
func (c *Conversation) messageBlocksEndContinuityCheck(ctx context.Context, minSeq int64, conversationID string, notStartTime,
|
||||
isReverse bool, count int, startTime int64, list *[]*model_struct.LocalChatLog, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) {
|
||||
if minSeq != 0 {
|
||||
seqList := func(seq int64) (seqList []int64) {
|
||||
startSeq := seq - constant.PullMsgNumForReadDiffusion
|
||||
if startSeq <= 0 {
|
||||
startSeq = 1
|
||||
}
|
||||
log.ZDebug(ctx, "pull start is ", "start seq", startSeq)
|
||||
for i := startSeq; i < seq; i++ {
|
||||
seqList = append(seqList, i)
|
||||
}
|
||||
return seqList
|
||||
}(minSeq)
|
||||
log.ZDebug(ctx, "pull seqList is ", "seqList", seqList, "len", len(seqList))
|
||||
|
||||
if len(seqList) > 0 {
|
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, seqList, notStartTime, isReverse, count, startTime, list, messageListCallback)
|
||||
}
|
||||
|
||||
} else {
|
||||
//local don't have messages,本地无消息,但是服务器最大消息不为0
|
||||
seqList := []int64{0, 0}
|
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, seqList, notStartTime, isReverse, count, startTime, list, messageListCallback)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
func (c *Conversation) getMaxAndMinHaveSeqList(messages []*model_struct.LocalChatLog) (max, min int64, seqList []int64) {
|
||||
for i := 0; i < len(messages); i++ {
|
||||
if messages[i].Seq != 0 {
|
||||
seqList = append(seqList, messages[i].Seq)
|
||||
}
|
||||
if messages[i].Seq != 0 && min == 0 && max == 0 {
|
||||
min = messages[i].Seq
|
||||
max = messages[i].Seq
|
||||
}
|
||||
if messages[i].Seq < min && messages[i].Seq != 0 {
|
||||
min = messages[i].Seq
|
||||
}
|
||||
if messages[i].Seq > max {
|
||||
max = messages[i].Seq
|
||||
|
||||
}
|
||||
}
|
||||
return max, min, seqList
|
||||
}
|
||||
|
||||
// 1、保证单次拉取消息量低于sdk单次从服务器拉取量
|
||||
// 2、块中连续性检测
|
||||
// 3、块之间连续性检测
|
||||
func (c *Conversation) pullMessageAndReGetHistoryMessages(ctx context.Context, conversationID string, seqList []int64,
|
||||
notStartTime, isReverse bool, count int, startTime int64, list *[]*model_struct.LocalChatLog,
|
||||
messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) {
|
||||
existedSeqList, err := c.db.GetAlreadyExistSeqList(ctx, conversationID, seqList)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetAlreadyExistSeqList err", err, "conversationID", conversationID,
|
||||
"seqList", seqList)
|
||||
return
|
||||
}
|
||||
if len(existedSeqList) == len(seqList) {
|
||||
log.ZDebug(ctx, "do not pull message", "seqList", seqList, "existedSeqList", existedSeqList)
|
||||
return
|
||||
}
|
||||
newSeqList := utils.DifferenceSubset(seqList, existedSeqList)
|
||||
if len(newSeqList) == 0 {
|
||||
log.ZDebug(ctx, "do not pull message", "seqList", seqList, "existedSeqList", existedSeqList,
|
||||
"newSeqList", newSeqList)
|
||||
return
|
||||
}
|
||||
var pullMsgResp sdkws.PullMessageBySeqsResp
|
||||
var pullMsgReq sdkws.PullMessageBySeqsReq
|
||||
pullMsgReq.UserID = c.loginUserID
|
||||
var seqRange sdkws.SeqRange
|
||||
seqRange.ConversationID = conversationID
|
||||
seqRange.Begin = newSeqList[0]
|
||||
seqRange.End = newSeqList[len(newSeqList)-1]
|
||||
seqRange.Num = int64(len(newSeqList))
|
||||
pullMsgReq.SeqRanges = append(pullMsgReq.SeqRanges, &seqRange)
|
||||
log.ZDebug(ctx, "conversation pull message, ", "req", pullMsgReq)
|
||||
if notStartTime && !c.LongConnMgr.IsConnected() {
|
||||
return
|
||||
}
|
||||
err = c.SendReqWaitResp(ctx, &pullMsgReq, constant.PullMsgBySeqList, &pullMsgResp)
|
||||
if err != nil {
|
||||
errHandle(newSeqList, list, err, messageListCallback)
|
||||
log.ZDebug(ctx, "pullmsg SendReqWaitResp failed", err, "req")
|
||||
} else {
|
||||
log.ZDebug(ctx, "syncMsgFromServerSplit pull msg", "resp", pullMsgResp)
|
||||
if pullMsgResp.Msgs == nil {
|
||||
log.ZWarn(ctx, "syncMsgFromServerSplit pull msg is null", errors.New("pull message is null"),
|
||||
"req", pullMsgReq)
|
||||
return
|
||||
}
|
||||
if v, ok := pullMsgResp.Msgs[conversationID]; ok {
|
||||
c.pullMessageIntoTable(ctx, pullMsgResp.Msgs, conversationID)
|
||||
messageListCallback.IsEnd = v.IsEnd
|
||||
|
||||
if notStartTime {
|
||||
*list, err = c.db.GetMessageListNoTime(ctx, conversationID, count, isReverse)
|
||||
} else {
|
||||
*list, err = c.db.GetMessageList(ctx, conversationID, count, startTime, isReverse)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
func errHandle(seqList []int64, list *[]*model_struct.LocalChatLog, err error, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) {
|
||||
messageListCallback.ErrCode = 100
|
||||
messageListCallback.ErrMsg = err.Error()
|
||||
var result []*model_struct.LocalChatLog
|
||||
needPullMaxSeq := seqList[len(seqList)-1]
|
||||
for _, chatLog := range *list {
|
||||
if chatLog.Seq == 0 || chatLog.Seq > needPullMaxSeq {
|
||||
temp := chatLog
|
||||
result = append(result, temp)
|
||||
} else {
|
||||
if chatLog.Seq <= needPullMaxSeq {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
*list = result
|
||||
}
|
||||
func (c *Conversation) pullMessageIntoTable(ctx context.Context, pullMsgData map[string]*sdkws.PullMsgs, conversationID string) {
|
||||
insertMsg := make(map[string][]*model_struct.LocalChatLog, 20)
|
||||
updateMsg := make(map[string][]*model_struct.LocalChatLog, 30)
|
||||
var insertMessage, selfInsertMessage, othersInsertMessage []*model_struct.LocalChatLog
|
||||
var updateMessage []*model_struct.LocalChatLog
|
||||
var exceptionMsg []*model_struct.LocalErrChatLog
|
||||
|
||||
log.ZDebug(ctx, "do Msg come here, len: ", "msg length", len(pullMsgData))
|
||||
for conversationID, msgs := range pullMsgData {
|
||||
for _, v := range msgs.Msgs {
|
||||
log.ZDebug(ctx, "msg detail", "msg", v, "conversationID", conversationID)
|
||||
msg := c.msgDataToLocalChatLog(v)
|
||||
//When the message has been marked and deleted by the cloud, it is directly inserted locally without any conversation and message update.
|
||||
if msg.Status == constant.MsgStatusHasDeleted {
|
||||
insertMessage = append(insertMessage, msg)
|
||||
continue
|
||||
}
|
||||
msg.Status = constant.MsgStatusSendSuccess
|
||||
// log.Info(operationID, "new msg, seq, ServerMsgID, ClientMsgID", msg.Seq, msg.ServerMsgID, msg.ClientMsgID)
|
||||
//De-analyze data
|
||||
if msg.ClientMsgID == "" {
|
||||
exceptionMsg = append(exceptionMsg, c.msgDataToLocalErrChatLog(msg))
|
||||
continue
|
||||
}
|
||||
if v.SendID == c.loginUserID { //seq
|
||||
// Messages sent by myself //if sent through this terminal
|
||||
m, err := c.db.GetMessage(ctx, conversationID, msg.ClientMsgID)
|
||||
if err == nil {
|
||||
log.ZInfo(ctx, "have message", "msg", msg)
|
||||
if m.Seq == 0 {
|
||||
updateMessage = append(updateMessage, msg)
|
||||
|
||||
} else {
|
||||
exceptionMsg = append(exceptionMsg, c.msgDataToLocalErrChatLog(msg))
|
||||
}
|
||||
} else { // send through other terminal
|
||||
log.ZInfo(ctx, "sync message", "msg", msg)
|
||||
selfInsertMessage = append(selfInsertMessage, msg)
|
||||
}
|
||||
} else { //Sent by others
|
||||
if oldMessage, err := c.db.GetMessage(ctx, conversationID, msg.ClientMsgID); err != nil { //Deduplication operation
|
||||
othersInsertMessage = append(othersInsertMessage, msg)
|
||||
|
||||
} else {
|
||||
if oldMessage.Seq == 0 {
|
||||
updateMessage = append(updateMessage, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insertMsg[conversationID] = append(insertMessage, c.faceURLAndNicknameHandle(ctx, selfInsertMessage, othersInsertMessage, conversationID)...)
|
||||
updateMsg[conversationID] = updateMessage
|
||||
}
|
||||
|
||||
//update message
|
||||
if err6 := c.messageController.BatchUpdateMessageList(ctx, updateMsg); err6 != nil {
|
||||
log.ZError(ctx, "sync seq normal message err :", err6)
|
||||
}
|
||||
b3 := utils.GetCurrentTimestampByMill()
|
||||
//Normal message storage
|
||||
_ = c.messageController.BatchInsertMessageList(ctx, insertMsg)
|
||||
b4 := utils.GetCurrentTimestampByMill()
|
||||
log.ZDebug(ctx, "BatchInsertMessageListController, ", "cost time", b4-b3)
|
||||
|
||||
//Exception message storage
|
||||
for _, v := range exceptionMsg {
|
||||
log.ZWarn(ctx, "exceptionMsg show: ", nil, "msg", *v)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 拉取的消息都需要经过块内部连续性检测以及块和上一块之间的连续性检测不连续则补,补齐的过程中如果出现任何异常只给seq从大到小到断层
|
||||
// 拉取消息不满量,获取服务器中该群最大seq以及用户对于此群最小seq,本地该群的最小seq,如果本地的不为0并且小于等于服务器最小的,说明已经到底部
|
||||
// 如果本地的为0,可以理解为初始化的时候,数据还未同步,或者异常情况,如果服务器最大seq-服务器最小seq>=0说明还未到底部,否则到底部
|
||||
|
||||
func (c *Conversation) faceURLAndNicknameHandle(ctx context.Context, self, others []*model_struct.LocalChatLog, conversationID string) []*model_struct.LocalChatLog {
|
||||
lc, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return append(self, others...)
|
||||
}
|
||||
switch lc.ConversationType {
|
||||
case constant.SingleChatType:
|
||||
c.singleHandle(ctx, self, others, lc)
|
||||
case constant.SuperGroupChatType:
|
||||
c.groupHandle(ctx, self, others, lc)
|
||||
}
|
||||
return append(self, others...)
|
||||
}
|
||||
func (c *Conversation) singleHandle(ctx context.Context, self, others []*model_struct.LocalChatLog, lc *model_struct.LocalConversation) {
|
||||
userInfo, err := c.db.GetLoginUser(ctx, c.loginUserID)
|
||||
if err == nil {
|
||||
for _, chatLog := range self {
|
||||
chatLog.SenderFaceURL = userInfo.FaceURL
|
||||
chatLog.SenderNickname = userInfo.Nickname
|
||||
}
|
||||
}
|
||||
if lc.FaceURL != "" && lc.ShowName != "" {
|
||||
for _, chatLog := range others {
|
||||
chatLog.SenderFaceURL = lc.FaceURL
|
||||
chatLog.SenderNickname = lc.ShowName
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
func (c *Conversation) groupHandle(ctx context.Context, self, others []*model_struct.LocalChatLog, lc *model_struct.LocalConversation) {
|
||||
allMessage := append(self, others...)
|
||||
localGroupMemberInfo, err := c.group.GetSpecifiedGroupMembersInfo(ctx, lc.GroupID, datautil.Slice(allMessage, func(e *model_struct.LocalChatLog) string {
|
||||
return e.SendID
|
||||
}))
|
||||
if err != nil {
|
||||
log.ZError(ctx, "get group member info err", err)
|
||||
return
|
||||
}
|
||||
groupMap := datautil.SliceToMap(localGroupMemberInfo, func(e *model_struct.LocalGroupMember) string {
|
||||
return e.UserID
|
||||
})
|
||||
for _, chatLog := range allMessage {
|
||||
if g, ok := groupMap[chatLog.SendID]; ok {
|
||||
if g.FaceURL != "" && g.Nickname != "" {
|
||||
chatLog.SenderFaceURL = g.FaceURL
|
||||
chatLog.SenderNickname = g.Nickname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
go/chao-sdk-core/internal/conversation_msg/message_controller.go
Normal file
124
go/chao-sdk-core/internal/conversation_msg/message_controller.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
|
||||
"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/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
type MessageController struct {
|
||||
db db_interface.DataBase
|
||||
ch chan common.Cmd2Value
|
||||
}
|
||||
|
||||
func NewMessageController(db db_interface.DataBase, ch chan common.Cmd2Value) *MessageController {
|
||||
return &MessageController{db: db, ch: ch}
|
||||
}
|
||||
func (m *MessageController) BatchUpdateMessageList(ctx context.Context, updateMsg map[string][]*model_struct.LocalChatLog) error {
|
||||
if updateMsg == nil {
|
||||
return nil
|
||||
}
|
||||
for conversationID, messages := range updateMsg {
|
||||
conversation, err := m.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetConversation err", err, "conversationID", conversationID)
|
||||
continue
|
||||
}
|
||||
latestMsg := &sdk_struct.MsgStruct{}
|
||||
if err := json.Unmarshal([]byte(conversation.LatestMsg), latestMsg); err != nil {
|
||||
log.ZError(ctx, "Unmarshal err", err, "conversationID",
|
||||
conversationID, "latestMsg", conversation.LatestMsg)
|
||||
continue
|
||||
}
|
||||
for _, v := range messages {
|
||||
v1 := new(model_struct.LocalChatLog)
|
||||
v1.ClientMsgID = v.ClientMsgID
|
||||
v1.Seq = v.Seq
|
||||
v1.Status = v.Status
|
||||
v1.RecvID = v.RecvID
|
||||
v1.SessionType = v.SessionType
|
||||
v1.ServerMsgID = v.ServerMsgID
|
||||
v1.SendTime = v.SendTime
|
||||
err := m.db.UpdateMessage(ctx, conversationID, v1)
|
||||
if err != nil {
|
||||
return utils.Wrap(err, "BatchUpdateMessageList failed")
|
||||
}
|
||||
if latestMsg.ClientMsgID == v.ClientMsgID {
|
||||
latestMsg.ServerMsgID = v.ServerMsgID
|
||||
latestMsg.Seq = v.Seq
|
||||
latestMsg.SendTime = v.SendTime
|
||||
conversation.LatestMsg = utils.StructToJsonString(latestMsg)
|
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ConID: conversation.ConversationID,
|
||||
Action: constant.AddConOrUpLatMsg, Args: *conversation}, m.ch)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MessageController) BatchInsertMessageList(ctx context.Context, insertMsg map[string][]*model_struct.LocalChatLog) error {
|
||||
if insertMsg == nil {
|
||||
return nil
|
||||
}
|
||||
for conversationID, messages := range insertMsg {
|
||||
if len(messages) == 0 {
|
||||
continue
|
||||
}
|
||||
err := m.db.BatchInsertMessageList(ctx, conversationID, messages)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "insert GetMessage detail err:", err, "conversationID", conversationID, "messages", messages)
|
||||
for _, v := range messages {
|
||||
e := m.db.InsertMessage(ctx, conversationID, v)
|
||||
if e != nil {
|
||||
log.ZError(ctx, "InsertMessage err", err, "conversationID", conversationID, "message", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conversation) PullMessageBySeqs(ctx context.Context, seqs []*sdkws.SeqRange) (*sdkws.PullMessageBySeqsResp, error) {
|
||||
return util.CallApi[sdkws.PullMessageBySeqsResp](ctx, constant.PullUserMsgBySeqRouter, sdkws.PullMessageBySeqsReq{UserID: c.loginUserID, SeqRanges: seqs})
|
||||
}
|
||||
func (m *MessageController) SearchMessageByContentTypeAndKeyword(ctx context.Context, contentType []int, keywordList []string,
|
||||
keywordListMatchType int, startTime, endTime int64) (result []*model_struct.LocalChatLog, err error) {
|
||||
var list []*model_struct.LocalChatLog
|
||||
conversationIDList, err := m.db.GetAllConversationIDList(ctx)
|
||||
for _, v := range conversationIDList {
|
||||
sList, err := m.db.SearchMessageByContentTypeAndKeyword(ctx, contentType, v, keywordList, keywordListMatchType, startTime, endTime)
|
||||
if err != nil {
|
||||
// TODO: log.Error(operationID, "search message in group err", err.Error(), v)
|
||||
continue
|
||||
}
|
||||
list = append(list, sList...)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
102
go/chao-sdk-core/internal/conversation_msg/progress.go
Normal file
102
go/chao-sdk-core/internal/conversation_msg/progress.go
Normal file
@@ -0,0 +1,102 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/file"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
func NewUploadFileCallback(ctx context.Context, progress func(progress int), msg *sdk_struct.MsgStruct, conversationID string, db db_interface.DataBase) file.UploadFileCallback {
|
||||
if msg.AttachedInfoElem == nil {
|
||||
msg.AttachedInfoElem = &sdk_struct.AttachedInfoElem{}
|
||||
}
|
||||
if msg.AttachedInfoElem.Progress == nil {
|
||||
msg.AttachedInfoElem.Progress = &sdk_struct.UploadProgress{}
|
||||
}
|
||||
return &msgUploadFileCallback{ctx: ctx, progress: progress, msg: msg, db: db, conversationID: conversationID}
|
||||
}
|
||||
|
||||
type msgUploadFileCallback struct {
|
||||
ctx context.Context
|
||||
db db_interface.DataBase
|
||||
msg *sdk_struct.MsgStruct
|
||||
conversationID string
|
||||
value int
|
||||
progress func(progress int)
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) Open(size int64) {
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) PartSize(partSize int64, num int) {
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) HashPartProgress(index int, size int64, partHash string) {
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) HashPartComplete(partsHash string, fileHash string) {
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) UploadID(uploadID string) {
|
||||
c.msg.AttachedInfoElem.Progress.UploadID = uploadID
|
||||
data, err := json.Marshal(c.msg.AttachedInfoElem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := c.db.UpdateColumnsMessage(c.ctx, c.conversationID, c.msg.ClientMsgID, map[string]any{"attached_info": string(data)}); err != nil {
|
||||
log.ZError(c.ctx, "update PutProgress message attached info failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) UploadPartComplete(index int, partSize int64, partHash string) {
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) UploadComplete(fileSize int64, streamSize int64, storageSize int64) {
|
||||
c.msg.AttachedInfoElem.Progress.Save = storageSize
|
||||
c.msg.AttachedInfoElem.Progress.Current = streamSize
|
||||
c.msg.AttachedInfoElem.Progress.Total = fileSize
|
||||
data, err := json.Marshal(c.msg.AttachedInfoElem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := c.db.UpdateColumnsMessage(c.ctx, c.conversationID, c.msg.ClientMsgID, map[string]any{"attached_info": string(data)}); err != nil {
|
||||
log.ZError(c.ctx, "update PutProgress message attached info failed", err)
|
||||
}
|
||||
value := int(float64(streamSize) / float64(fileSize) * 100)
|
||||
if c.value < value {
|
||||
c.value = value
|
||||
c.progress(value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *msgUploadFileCallback) Complete(size int64, url string, typ int) {
|
||||
if c.value != 100 {
|
||||
c.progress(100)
|
||||
}
|
||||
c.msg.AttachedInfoElem.Progress = nil
|
||||
data, err := json.Marshal(c.msg.AttachedInfoElem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := c.db.UpdateColumnsMessage(c.ctx, c.conversationID, c.msg.ClientMsgID, map[string]any{"attached_info": string(data)}); err != nil {
|
||||
log.ZError(c.ctx, "update PutComplete message attached info failed", err)
|
||||
}
|
||||
}
|
||||
281
go/chao-sdk-core/internal/conversation_msg/read_drawing.go
Normal file
281
go/chao-sdk-core/internal/conversation_msg/read_drawing.go
Normal file
@@ -0,0 +1,281 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
|
||||
"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/model_struct"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
|
||||
pbMsg "github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
func (c *Conversation) markMsgAsRead2Svr(ctx context.Context, conversationID string, seqs []int64) error {
|
||||
req := &pbMsg.MarkMsgsAsReadReq{UserID: c.loginUserID, ConversationID: conversationID, Seqs: seqs}
|
||||
return util.ApiPost(ctx, constant.MarkMsgsAsReadRouter, req, nil)
|
||||
}
|
||||
|
||||
func (c *Conversation) markConversationAsReadSvr(ctx context.Context, conversationID string, hasReadSeq int64, seqs []int64) error {
|
||||
req := &pbMsg.MarkConversationAsReadReq{UserID: c.loginUserID, ConversationID: conversationID, HasReadSeq: hasReadSeq, Seqs: seqs}
|
||||
return util.ApiPost(ctx, constant.MarkConversationAsRead, req, nil)
|
||||
}
|
||||
|
||||
func (c *Conversation) setConversationHasReadSeq(ctx context.Context, conversationID string, hasReadSeq int64) error {
|
||||
req := &pbMsg.SetConversationHasReadSeqReq{UserID: c.loginUserID, ConversationID: conversationID, HasReadSeq: hasReadSeq}
|
||||
return util.ApiPost(ctx, constant.SetConversationHasReadSeq, req, nil)
|
||||
}
|
||||
|
||||
func (c *Conversation) getConversationMaxSeqAndSetHasRead(ctx context.Context, conversationID string) error {
|
||||
maxSeq, err := c.db.GetConversationNormalMsgSeq(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if maxSeq == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := c.setConversationHasReadSeq(ctx, conversationID, maxSeq); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"has_read_seq": maxSeq}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mark a conversation's all message as read
|
||||
func (c *Conversation) markConversationMessageAsRead(ctx context.Context, conversationID string) error {
|
||||
conversation, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conversation.UnreadCount == 0 {
|
||||
return sdkerrs.ErrUnreadCount
|
||||
}
|
||||
// get the maximum sequence number of messages in the table that are not sent by oneself
|
||||
peerUserMaxSeq, err := c.db.GetConversationPeerNormalMsgSeq(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// get the maximum sequence number of messages in the table
|
||||
maxSeq, err := c.db.GetConversationNormalMsgSeq(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch conversation.ConversationType {
|
||||
case constant.SingleChatType:
|
||||
msgs, err := c.db.GetUnreadMessage(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZDebug(ctx, "get unread message", "msgs", len(msgs))
|
||||
msgIDs, seqs := c.getAsReadMsgMapAndList(ctx, msgs)
|
||||
if len(seqs) == 0 {
|
||||
log.ZWarn(ctx, "seqs is empty", nil, "conversationID", conversationID)
|
||||
return nil
|
||||
}
|
||||
log.ZDebug(ctx, "markConversationMessageAsRead", "conversationID", conversationID, "seqs",
|
||||
seqs, "peerUserMaxSeq", peerUserMaxSeq, "maxSeq", maxSeq)
|
||||
if err := c.markConversationAsReadSvr(ctx, conversationID, maxSeq, seqs); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = c.db.MarkConversationMessageAsReadDB(ctx, conversationID, msgIDs)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "MarkConversationMessageAsRead err", err, "conversationID", conversationID, "msgIDs", msgIDs)
|
||||
}
|
||||
case constant.SuperGroupChatType, constant.NotificationChatType:
|
||||
log.ZDebug(ctx, "markConversationMessageAsRead", "conversationID", conversationID, "peerUserMaxSeq", peerUserMaxSeq, "maxSeq", maxSeq)
|
||||
if err := c.markConversationAsReadSvr(ctx, conversationID, maxSeq, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"unread_count": 0}); err != nil {
|
||||
log.ZError(ctx, "UpdateColumnsConversation err", err, "conversationID", conversationID)
|
||||
}
|
||||
log.ZDebug(ctx, "update columns sucess")
|
||||
c.unreadChangeTrigger(ctx, conversationID, peerUserMaxSeq == maxSeq)
|
||||
return nil
|
||||
}
|
||||
|
||||
// mark a conversation's message as read by seqs
|
||||
func (c *Conversation) markMessagesAsReadByMsgID(ctx context.Context, conversationID string, msgIDs []string) error {
|
||||
_, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgs, err := c.db.GetMessagesByClientMsgIDs(ctx, conversationID, msgIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var hasReadSeq = msgs[0].Seq
|
||||
maxSeq, err := c.db.GetConversationNormalMsgSeq(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
markAsReadMsgIDs, seqs := c.getAsReadMsgMapAndList(ctx, msgs)
|
||||
log.ZDebug(ctx, "msgs len", "markAsReadMsgIDs", len(markAsReadMsgIDs), "seqs", seqs)
|
||||
if len(seqs) == 0 {
|
||||
log.ZWarn(ctx, "seqs is empty", nil, "conversationID", conversationID)
|
||||
return nil
|
||||
}
|
||||
if err := c.markMsgAsRead2Svr(ctx, conversationID, seqs); err != nil {
|
||||
return err
|
||||
}
|
||||
decrCount, err := c.db.MarkConversationMessageAsReadDB(ctx, conversationID, markAsReadMsgIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.db.DecrConversationUnreadCount(ctx, conversationID, decrCount); err != nil {
|
||||
log.ZError(ctx, "decrConversationUnreadCount err", err, "conversationID", conversationID,
|
||||
"decrCount", decrCount)
|
||||
}
|
||||
c.unreadChangeTrigger(ctx, conversationID, hasReadSeq == maxSeq && msgs[0].SendID != c.loginUserID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conversation) getAsReadMsgMapAndList(ctx context.Context,
|
||||
msgs []*model_struct.LocalChatLog) (asReadMsgIDs []string, seqs []int64) {
|
||||
for _, msg := range msgs {
|
||||
if !msg.IsRead && msg.SendID != c.loginUserID {
|
||||
if msg.Seq == 0 {
|
||||
log.ZWarn(ctx, "exception seq", errors.New("exception message "), "msg", msg)
|
||||
} else {
|
||||
asReadMsgIDs = append(asReadMsgIDs, msg.ClientMsgID)
|
||||
seqs = append(seqs, msg.Seq)
|
||||
}
|
||||
} else {
|
||||
log.ZWarn(ctx, "msg can't marked as read", nil, "msg", msg)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Conversation) unreadChangeTrigger(ctx context.Context, conversationID string, latestMsgIsRead bool) {
|
||||
if latestMsgIsRead {
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversationID,
|
||||
Action: constant.UpdateLatestMessageChange, Args: []string{conversationID}}, Ctx: ctx})
|
||||
}
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversationID,
|
||||
Action: constant.ConChange, Args: []string{conversationID}}, Ctx: ctx})
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged},
|
||||
Ctx: ctx})
|
||||
}
|
||||
|
||||
func (c *Conversation) doUnreadCount(ctx context.Context, conversation *model_struct.LocalConversation, hasReadSeq int64, seqs []int64) {
|
||||
if conversation.ConversationType == constant.SingleChatType {
|
||||
if len(seqs) != 0 {
|
||||
_, err := c.db.MarkConversationMessageAsReadBySeqs(ctx, conversation.ConversationID, seqs)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "MarkConversationMessageAsReadBySeqs err", err, "conversationID", conversation.ConversationID, "seqs", seqs)
|
||||
}
|
||||
} else {
|
||||
log.ZWarn(ctx, "seqs is empty", nil, "conversationID", conversation.ConversationID, "hasReadSeq", hasReadSeq)
|
||||
}
|
||||
if hasReadSeq > conversation.HasReadSeq {
|
||||
decrUnreadCount := hasReadSeq - conversation.HasReadSeq
|
||||
if err := c.db.DecrConversationUnreadCount(ctx, conversation.ConversationID, decrUnreadCount); err != nil {
|
||||
log.ZError(ctx, "DecrConversationUnreadCount err", err, "conversationID", conversation.ConversationID, "decrUnreadCount", decrUnreadCount)
|
||||
}
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversation.ConversationID, map[string]interface{}{"has_read_seq": hasReadSeq}); err != nil {
|
||||
log.ZError(ctx, "UpdateColumnsConversation err", err, "conversationID", conversation.ConversationID)
|
||||
}
|
||||
}
|
||||
latestMsg := &sdk_struct.MsgStruct{}
|
||||
if err := json.Unmarshal([]byte(conversation.LatestMsg), latestMsg); err != nil {
|
||||
log.ZError(ctx, "Unmarshal err", err, "conversationID", conversation.ConversationID, "latestMsg", conversation.LatestMsg)
|
||||
}
|
||||
if (!latestMsg.IsRead) && datautil.Contain(latestMsg.Seq, seqs...) {
|
||||
latestMsg.IsRead = true
|
||||
conversation.LatestMsg = utils.StructToJsonString(&latestMsg)
|
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ConID: conversation.ConversationID, Action: constant.AddConOrUpLatMsg, Args: *conversation}, c.GetCh())
|
||||
}
|
||||
} else {
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversation.ConversationID, map[string]interface{}{"unread_count": 0}); err != nil {
|
||||
log.ZError(ctx, "UpdateColumnsConversation err", err, "conversationID", conversation.ConversationID)
|
||||
}
|
||||
}
|
||||
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversation.ConversationID, Action: constant.ConChange, Args: []string{conversation.ConversationID}}})
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}})
|
||||
|
||||
}
|
||||
|
||||
func (c *Conversation) doReadDrawing(ctx context.Context, msg *sdkws.MsgData) {
|
||||
tips := &sdkws.MarkAsReadTips{}
|
||||
err := utils.UnmarshalNotificationElem(msg.Content, tips)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "UnmarshalNotificationElem err", err, "msg", msg)
|
||||
return
|
||||
}
|
||||
log.ZDebug(ctx, "do readDrawing", "tips", tips)
|
||||
conversation, err := c.db.GetConversation(ctx, tips.ConversationID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetConversation err", err, "conversationID", tips.ConversationID)
|
||||
return
|
||||
}
|
||||
if tips.MarkAsReadUserID != c.loginUserID {
|
||||
if len(tips.Seqs) == 0 {
|
||||
return
|
||||
}
|
||||
messages, err := c.db.GetMessagesBySeqs(ctx, tips.ConversationID, tips.Seqs)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetMessagesBySeqs err", err, "conversationID", tips.ConversationID, "seqs", tips.Seqs)
|
||||
return
|
||||
}
|
||||
if conversation.ConversationType == constant.SingleChatType {
|
||||
latestMsg := &sdk_struct.MsgStruct{}
|
||||
if err := json.Unmarshal([]byte(conversation.LatestMsg), latestMsg); err != nil {
|
||||
log.ZError(ctx, "Unmarshal err", err, "conversationID", tips.ConversationID, "latestMsg", conversation.LatestMsg)
|
||||
}
|
||||
var successMsgIDs []string
|
||||
for _, message := range messages {
|
||||
attachInfo := sdk_struct.AttachedInfoElem{}
|
||||
_ = utils.JsonStringToStruct(message.AttachedInfo, &attachInfo)
|
||||
attachInfo.HasReadTime = msg.SendTime
|
||||
message.AttachedInfo = utils.StructToJsonString(attachInfo)
|
||||
message.IsRead = true
|
||||
if err = c.db.UpdateMessage(ctx, tips.ConversationID, message); err != nil {
|
||||
log.ZError(ctx, "UpdateMessage err", err, "conversationID", tips.ConversationID, "message", message)
|
||||
} else {
|
||||
if latestMsg.ClientMsgID == message.ClientMsgID {
|
||||
latestMsg.IsRead = message.IsRead
|
||||
conversation.LatestMsg = utils.StructToJsonString(latestMsg)
|
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ConID: conversation.ConversationID, Action: constant.AddConOrUpLatMsg, Args: *conversation}, c.GetCh())
|
||||
|
||||
}
|
||||
successMsgIDs = append(successMsgIDs, message.ClientMsgID)
|
||||
}
|
||||
}
|
||||
var messageReceiptResp = []*sdk_struct.MessageReceipt{{UserID: tips.MarkAsReadUserID, MsgIDList: successMsgIDs,
|
||||
SessionType: conversation.ConversationType, ReadTime: msg.SendTime}}
|
||||
c.msgListener().OnRecvC2CReadReceipt(utils.StructToJsonString(messageReceiptResp))
|
||||
}
|
||||
} else {
|
||||
c.doUnreadCount(ctx, conversation, tips.HasReadSeq, tips.Seqs)
|
||||
}
|
||||
}
|
||||
203
go/chao-sdk-core/internal/conversation_msg/revoke.go
Normal file
203
go/chao-sdk-core/internal/conversation_msg/revoke.go
Normal file
@@ -0,0 +1,203 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util"
|
||||
"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/model_struct"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct"
|
||||
"github.com/openimsdk/tools/utils/timeutil"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
pbMsg "github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
func (c *Conversation) doRevokeMsg(ctx context.Context, msg *sdkws.MsgData) {
|
||||
var tips sdkws.RevokeMsgTips
|
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil {
|
||||
log.ZError(ctx, "unmarshal failed", err, "msg", msg)
|
||||
return
|
||||
}
|
||||
log.ZDebug(ctx, "do revokeMessage", "tips", &tips)
|
||||
c.revokeMessage(ctx, &tips)
|
||||
}
|
||||
|
||||
func (c *Conversation) revokeMessage(ctx context.Context, tips *sdkws.RevokeMsgTips) {
|
||||
revokedMsg, err := c.db.GetMessageBySeq(ctx, tips.ConversationID, tips.Seq)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetMessageBySeq failed", err, "tips", &tips)
|
||||
return
|
||||
}
|
||||
var revokerRole int32
|
||||
var revokerNickname string
|
||||
if tips.IsAdminRevoke || tips.SesstionType == constant.SingleChatType {
|
||||
_, userName, err := c.getUserNameAndFaceURL(ctx, tips.RevokerUserID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetUserNameAndFaceURL failed", err, "tips", &tips)
|
||||
} else {
|
||||
log.ZDebug(ctx, "revoker user name", "userName", userName)
|
||||
}
|
||||
revokerNickname = userName
|
||||
} else if tips.SesstionType == constant.SuperGroupChatType {
|
||||
conversation, err := c.db.GetConversation(ctx, tips.ConversationID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetConversation failed", err, "conversationID", tips.ConversationID)
|
||||
return
|
||||
}
|
||||
groupMember, err := c.db.GetGroupMemberInfoByGroupIDUserID(ctx, conversation.GroupID, tips.RevokerUserID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetGroupMemberInfoByGroupIDUserID failed", err, "tips", &tips)
|
||||
} else {
|
||||
log.ZDebug(ctx, "revoker member name", "groupMember", groupMember)
|
||||
revokerRole = groupMember.RoleLevel
|
||||
revokerNickname = groupMember.Nickname
|
||||
}
|
||||
}
|
||||
m := sdk_struct.MessageRevoked{
|
||||
RevokerID: tips.RevokerUserID,
|
||||
RevokerRole: revokerRole,
|
||||
ClientMsgID: revokedMsg.ClientMsgID,
|
||||
RevokerNickname: revokerNickname,
|
||||
RevokeTime: tips.RevokeTime,
|
||||
SourceMessageSendTime: revokedMsg.SendTime,
|
||||
SourceMessageSendID: revokedMsg.SendID,
|
||||
SourceMessageSenderNickname: revokedMsg.SenderNickname,
|
||||
SessionType: tips.SesstionType,
|
||||
Seq: tips.Seq,
|
||||
Ex: revokedMsg.Ex,
|
||||
IsAdminRevoke: tips.IsAdminRevoke,
|
||||
}
|
||||
// log.ZDebug(ctx, "callback revokeMessage", "m", m)
|
||||
var n sdk_struct.NotificationElem
|
||||
n.Detail = utils.StructToJsonString(m)
|
||||
if err := c.db.UpdateMessageBySeq(ctx, tips.ConversationID, &model_struct.LocalChatLog{Seq: tips.Seq,
|
||||
Content: utils.StructToJsonString(n), ContentType: constant.RevokeNotification}); err != nil {
|
||||
log.ZError(ctx, "UpdateMessageBySeq failed", err, "tips", &tips)
|
||||
return
|
||||
}
|
||||
conversation, err := c.db.GetConversation(ctx, tips.ConversationID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetConversation failed", err, "tips", &tips)
|
||||
return
|
||||
}
|
||||
var latestMsg sdk_struct.MsgStruct
|
||||
utils.JsonStringToStruct(conversation.LatestMsg, &latestMsg)
|
||||
log.ZDebug(ctx, "latestMsg", "latestMsg", &latestMsg, "seq", tips.Seq)
|
||||
if latestMsg.Seq <= tips.Seq {
|
||||
var newLatesetMsg sdk_struct.MsgStruct
|
||||
msgs, err := c.db.GetMessageListNoTime(ctx, tips.ConversationID, 1, false)
|
||||
if err != nil || len(msgs) == 0 {
|
||||
log.ZError(ctx, "GetMessageListNoTime failed", err, "tips", &tips)
|
||||
return
|
||||
}
|
||||
log.ZDebug(ctx, "latestMsg is revoked", "seq", tips.Seq, "msg", msgs[0])
|
||||
copier.Copy(&newLatesetMsg, msgs[0])
|
||||
err = c.msgConvert(&newLatesetMsg)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "parsing data error", err, latestMsg)
|
||||
} else {
|
||||
log.ZDebug(ctx, "revoke update conversatoin", "msg", utils.StructToJsonString(newLatesetMsg))
|
||||
if err := c.db.UpdateColumnsConversation(ctx, tips.ConversationID, map[string]interface{}{"latest_msg": utils.StructToJsonString(newLatesetMsg),
|
||||
"latest_msg_send_time": newLatesetMsg.SendTime}); err != nil {
|
||||
log.ZError(ctx, "UpdateColumnsConversation failed", err, "newLatesetMsg", newLatesetMsg)
|
||||
} else {
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{tips.ConversationID}}})
|
||||
}
|
||||
}
|
||||
}
|
||||
c.msgListener().OnNewRecvMessageRevoked(utils.StructToJsonString(m))
|
||||
msgList, err := c.db.SearchAllMessageByContentType(ctx, conversation.ConversationID, constant.Quote)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "SearchAllMessageByContentType failed", err, "tips", &tips)
|
||||
return
|
||||
}
|
||||
for _, v := range msgList {
|
||||
c.quoteMsgRevokeHandle(ctx, tips.ConversationID, v, m)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conversation) quoteMsgRevokeHandle(ctx context.Context, conversationID string, v *model_struct.LocalChatLog, revokedMsg sdk_struct.MessageRevoked) {
|
||||
s := sdk_struct.MsgStruct{}
|
||||
_ = utils.JsonStringToStruct(v.Content, &s.QuoteElem)
|
||||
|
||||
if s.QuoteElem.QuoteMessage == nil {
|
||||
return
|
||||
}
|
||||
if s.QuoteElem.QuoteMessage.ClientMsgID != revokedMsg.ClientMsgID {
|
||||
return
|
||||
}
|
||||
s.QuoteElem.QuoteMessage.Content = utils.StructToJsonString(revokedMsg)
|
||||
s.QuoteElem.QuoteMessage.ContentType = constant.RevokeNotification
|
||||
v.Content = utils.StructToJsonString(s.QuoteElem)
|
||||
if err := c.db.UpdateMessageBySeq(ctx, conversationID, v); err != nil {
|
||||
log.ZError(ctx, "UpdateMessage failed", err, "v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conversation) revokeOneMessage(ctx context.Context, conversationID, clientMsgID string) error {
|
||||
conversation, err := c.db.GetConversation(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
message, err := c.db.GetMessage(ctx, conversationID, clientMsgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if message.Status != constant.MsgStatusSendSuccess {
|
||||
return errors.New("only send success message can be revoked")
|
||||
}
|
||||
switch conversation.ConversationType {
|
||||
case constant.SingleChatType:
|
||||
if message.SendID != c.loginUserID {
|
||||
return errors.New("only send by yourself message can be revoked")
|
||||
}
|
||||
case constant.SuperGroupChatType:
|
||||
if message.SendID != c.loginUserID {
|
||||
groupAdmins, err := c.db.GetGroupMemberOwnerAndAdminDB(ctx, conversation.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var isAdmin bool
|
||||
for _, member := range groupAdmins {
|
||||
if member.UserID == c.loginUserID {
|
||||
isAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isAdmin {
|
||||
return errors.New("only group admin can revoke message")
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := util.ApiPost(ctx, constant.RevokeMsgRouter, pbMsg.RevokeMsgReq{ConversationID: conversationID, Seq: message.Seq, UserID: c.loginUserID}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
c.revokeMessage(ctx, &sdkws.RevokeMsgTips{
|
||||
ConversationID: conversationID,
|
||||
Seq: message.Seq,
|
||||
RevokerUserID: c.loginUserID,
|
||||
RevokeTime: timeutil.GetCurrentTimestampBySecond(),
|
||||
SesstionType: conversation.ConversationType,
|
||||
ClientMsgID: clientMsgID,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
1180
go/chao-sdk-core/internal/conversation_msg/sdk.go
Normal file
1180
go/chao-sdk-core/internal/conversation_msg/sdk.go
Normal file
File diff suppressed because it is too large
Load Diff
148
go/chao-sdk-core/internal/conversation_msg/sync.go
Normal file
148
go/chao-sdk-core/internal/conversation_msg/sync.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// 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 conversation_msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/model_struct"
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
func (c *Conversation) SyncConversationsAndTriggerCallback(ctx context.Context, conversationsOnServer []*model_struct.LocalConversation) error {
|
||||
conversationsOnLocal, err := c.db.GetAllConversations(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.batchAddFaceURLAndName(ctx, conversationsOnServer...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = c.conversationSyncer.Sync(ctx, conversationsOnServer, conversationsOnLocal, func(ctx context.Context, state int, server, local *model_struct.LocalConversation) error {
|
||||
if state == syncer.Update || state == syncer.Insert {
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: server.ConversationID, Action: constant.ConChange, Args: []string{server.ConversationID}}})
|
||||
}
|
||||
return nil
|
||||
}, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conversation) SyncConversations(ctx context.Context, conversationIDs []string) error {
|
||||
conversationsOnServer, err := c.getServerConversationsByIDs(ctx, conversationIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SyncConversationsAndTriggerCallback(ctx, conversationsOnServer)
|
||||
}
|
||||
|
||||
func (c *Conversation) SyncAllConversations(ctx context.Context) error {
|
||||
ccTime := time.Now()
|
||||
conversationsOnServer, err := c.getServerConversationList(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZDebug(ctx, "get server cost time", "cost time", time.Since(ccTime), "conversation on server", conversationsOnServer)
|
||||
return c.SyncConversationsAndTriggerCallback(ctx, conversationsOnServer)
|
||||
}
|
||||
|
||||
func (c *Conversation) SyncAllConversationHashReadSeqs(ctx context.Context) error {
|
||||
log.ZDebug(ctx, "start SyncConversationHashReadSeqs")
|
||||
seqs, err := c.getServerHasReadAndMaxSeqs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(seqs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var conversationChangedIDs []string
|
||||
var conversationIDsNeedSync []string
|
||||
|
||||
conversationsOnLocal, err := c.db.GetAllConversations(ctx)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "get all conversations err", err)
|
||||
return err
|
||||
}
|
||||
conversationsOnLocalMap := datautil.SliceToMap(conversationsOnLocal, func(e *model_struct.LocalConversation) string {
|
||||
return e.ConversationID
|
||||
})
|
||||
for conversationID, v := range seqs {
|
||||
var unreadCount int32
|
||||
c.maxSeqRecorder.Set(conversationID, v.MaxSeq)
|
||||
if v.MaxSeq-v.HasReadSeq < 0 {
|
||||
unreadCount = 0
|
||||
log.ZWarn(ctx, "unread count is less than 0", nil, "conversationID",
|
||||
conversationID, "maxSeq", v.MaxSeq, "hasReadSeq", v.HasReadSeq)
|
||||
} else {
|
||||
unreadCount = int32(v.MaxSeq - v.HasReadSeq)
|
||||
}
|
||||
if conversation, ok := conversationsOnLocalMap[conversationID]; ok {
|
||||
if conversation.UnreadCount != unreadCount || conversation.HasReadSeq != v.HasReadSeq {
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"unread_count": unreadCount, "has_read_seq": v.HasReadSeq}); err != nil {
|
||||
log.ZWarn(ctx, "UpdateColumnsConversation err", err, "conversationID", conversationID)
|
||||
continue
|
||||
}
|
||||
conversationChangedIDs = append(conversationChangedIDs, conversationID)
|
||||
}
|
||||
} else {
|
||||
conversationIDsNeedSync = append(conversationIDsNeedSync, conversationID)
|
||||
}
|
||||
|
||||
}
|
||||
if len(conversationIDsNeedSync) > 0 {
|
||||
conversationsOnServer, err := c.getServerConversationsByIDs(ctx, conversationIDsNeedSync)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "getServerConversationsByIDs err", err, "conversationIDs", conversationIDsNeedSync)
|
||||
return err
|
||||
}
|
||||
if err := c.batchAddFaceURLAndName(ctx, conversationsOnServer...); err != nil {
|
||||
log.ZWarn(ctx, "batchAddFaceURLAndName err", err, "conversationsOnServer", conversationsOnServer)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, conversation := range conversationsOnServer {
|
||||
var unreadCount int32
|
||||
v, ok := seqs[conversation.ConversationID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if v.MaxSeq-v.HasReadSeq < 0 {
|
||||
unreadCount = 0
|
||||
log.ZWarn(ctx, "unread count is less than 0", nil, "server seq", v, "conversation", conversation)
|
||||
} else {
|
||||
unreadCount = int32(v.MaxSeq - v.HasReadSeq)
|
||||
}
|
||||
conversation.UnreadCount = unreadCount
|
||||
conversation.HasReadSeq = v.HasReadSeq
|
||||
}
|
||||
err = c.db.BatchInsertConversationList(ctx, conversationsOnServer)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "BatchInsertConversationList err", err, "conversationsOnServer", conversationsOnServer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
log.ZDebug(ctx, "update conversations", "conversations", conversationChangedIDs)
|
||||
if len(conversationChangedIDs) > 0 {
|
||||
common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{Action: constant.ConChange, Args: conversationChangedIDs}, c.GetCh())
|
||||
common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}, c.GetCh())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user