// 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}}) }