// myChatConversations provides functions to generate and retrieve a user's chat conversations // These conversations can be filtered and sorted based on the recipient's qualities // Examples: Sort recipients by age, filter recipients who do not fulfill our desires package myChatConversations import "seekia/internal/appMemory" import "seekia/internal/encoding" import "seekia/internal/helpers" import "seekia/internal/identity" import "seekia/internal/messaging/myChatFilters" import "seekia/internal/messaging/myChatMessages" import "seekia/internal/messaging/myReadStatus" import "seekia/internal/myBlockedUsers" import "seekia/internal/myDatastores/myMapList" import "seekia/internal/myIdentity" import "seekia/internal/mySettings" import "seekia/internal/profiles/viewableProfiles" import "slices" import "sync" import "errors" // This mutex will be locked whenever we update chat conversations var updatingMyChatConversationsMutex sync.Mutex var myMateChatConversationsMapListDatastore *myMapList.MyMapList var myModeratorChatConversationsMapListDatastore *myMapList.MyMapList // This function must be called whenever an app user signs in func InitializeMyChatConversationsDatastores()error{ updatingMyChatConversationsMutex.Lock() defer updatingMyChatConversationsMutex.Unlock() newMyMateChatConversationsMapListDatastore, err := myMapList.CreateNewMapList("MyMateChatConversations") if (err != nil) { return err } newMyModeratorChatConversationsMapListDatastore, err := myMapList.CreateNewMapList("MyModeratorChatConversations") if (err != nil) { return err } myMateChatConversationsMapListDatastore = newMyMateChatConversationsMapListDatastore myModeratorChatConversationsMapListDatastore = newMyModeratorChatConversationsMapListDatastore return nil } func getMyConversationsMapListDatastore(identityType string)(*myMapList.MyMapList, error) { if (identityType == "Mate"){ return myMateChatConversationsMapListDatastore, nil } if (identityType == "Moderator"){ return myModeratorChatConversationsMapListDatastore, nil } return nil, errors.New("getMyConversationsMapListDatastore called with invalid identity type: " + identityType) } // This function checks if conversations are generated and sorted func GetMyChatConversationsReadyStatus(identityType string, networkType byte)(bool, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, errors.New("GetMyChatConversationsReadyStatus called with invalid networkType: " + networkTypeString) } generatedStatus, err := getMyChatConversationsGeneratedStatus(identityType) if (err != nil) { return false, err } if (generatedStatus == false) { return false, nil } exists, sortedStatus, err := mySettings.GetSetting(identityType + "ChatConversationsSortedStatus") if (err != nil) { return false, err } if (exists == false || sortedStatus != "Yes") { return false, nil } exists, conversationsNetworkTypeString, err := mySettings.GetSetting(identityType + "ChatConversationsNetworkType") if (err != nil) { return false, err } if (exists == false){ // This should not happen, because ...ChatConversationsNetworkType is set whenever conversations are generated return false, errors.New("mySettings is missing " + identityType + "ChatConversationsNetworkType when " + identityType + "ChatConversationsGeneratedStatus exists.") } conversationsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(conversationsNetworkTypeString) if (err != nil) { return false, errors.New("MySettings contains invalid " + identityType + "ChatConversationsNetworkType: " + conversationsNetworkTypeString) } if (conversationsNetworkType != networkType) { // Conversations must have been generated for a different networkType. // This should not happen, because we should only call this function using our current appNetworkType // Whenever we change network types, we reset the ChatConversationsGeneratedStatus to No. //TODO: Log this. err := mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "No") if (err != nil) { return false, err } return false, nil } return true, nil } // Our conversations will need a refresh any time a new message sent to the user's inboxes is downloaded func CheckIfMyChatConversationsNeedRefresh(identityType string)(bool, error){ exists, needsRefresh, err := mySettings.GetSetting(identityType + "ChatConversationsNeedRefreshYesNo") if (err != nil) { return true, err } if (exists == true && needsRefresh == "No") { return false, nil } return true, nil } func getMyChatConversationsGeneratedStatus(identityType string)(bool, error){ chatMessagesReady, err := myChatMessages.GetMyChatMessagesReadyStatus(identityType) if (err != nil) { return false, err } if (chatMessagesReady == false) { return false, nil } exists, readyStatus, err := mySettings.GetSetting(identityType + "ChatConversationsGeneratedStatus") if (err != nil) { return false, err } if (exists == false || readyStatus != "Yes"){ return false, nil } return true, nil } // This function returns the ready (generated and sorted) chat conversations map list //Outputs: // -bool: Chat conversations are ready // -[]map[string]string // -error func GetMyChatConversationsMapList(identityType string, networkType byte)(bool, []map[string]string, error){ if (identityType != "Mate" && identityType != "Moderator"){ return false, nil, errors.New("GetMyChatConversationsMapList called with invalid identityType: " + identityType) } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, nil, errors.New("GetMyChatConversationsMapList called with invalid networkType: " + networkTypeString) } conversationsReady, err := GetMyChatConversationsReadyStatus(identityType, networkType) if (err != nil) { return false, nil, err } if (conversationsReady == false) { return false, nil, nil } myReadyConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType) if (err != nil) { return false, nil, err } myReadyChatConversationsMapList, err := myReadyConversationsMapListDatastore.GetMapList() if (err != nil) { return false, nil, err } return true, myReadyChatConversationsMapList, nil } //Outputs: // -bool: Conversations ready // -string: Number of conversations // -error func GetNumberOfConversations(identityType string, networkType byte)(bool, int, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, 0, errors.New("GetNumberOfConversations called with invalid networkType: " + networkTypeString) } conversationsReady, conversationsMap, err := GetMyChatConversationsMapList(identityType, networkType) if (err != nil) { return false, 0, err } if (conversationsReady == false){ return false, 0, nil } numberOfConversations := len(conversationsMap) return true, numberOfConversations, nil } func GetConversationsSortByAttribute(identityType string) (string, error){ exists, currentAttribute, err := mySettings.GetSetting(identityType + "ChatConversations_SortByAttribute") if (err != nil) { return "", err } if (exists == false){ return "MatchScore", nil } return currentAttribute, nil } func GetConversationsSortDirection(identityType string) (string, error){ exists, sortDirection, err := mySettings.GetSetting(identityType + "ChatConversations_SortDirection") if (err != nil) { return "", err } if (exists == false){ return "Descending", nil } if (sortDirection != "Ascending" && sortDirection != "Descending"){ return "", errors.New("MySettings malformed: Invalid ChatConversations_SortDirection: " + sortDirection) } return sortDirection, nil } //Outputs: // -bool: Conversations ready // -string: Number of unread conversations // -error func GetNumberOfUnreadConversations(identityType string, networkType byte)(bool, int, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, 0, errors.New("GetNumberOfUnreadConversations called with invalid networkType: " + networkTypeString) } conversationsReady, conversationsMapList, err := GetMyChatConversationsMapList(identityType, networkType) if (err != nil) { return false, 0, err } if (conversationsReady == false){ return false, 0, nil } numberOfUnreadConversations := 0 for _, conversationMap := range conversationsMapList { myIdentityHashString, exists := conversationMap["MyIdentityHash"] if (exists == false) { return false, 0, errors.New("Invalid conversation map: Item missing MyIdentityHash") } theirIdentityHashString, exists := conversationMap["TheirIdentityHash"] if (exists == false) { return false, 0, errors.New("Invalid conversation map: Item missing TheirIdentityHash") } myIdentityHash, myIdentityType, err := identity.ReadIdentityHashString(myIdentityHashString) if (err != nil){ return false, 0, errors.New("Invalid conversation map: Item contains invalid MyIdentityHash: " + myIdentityHashString) } theirIdentityHash, theirIdentityType, err := identity.ReadIdentityHashString(theirIdentityHashString) if (err != nil){ return false, 0, errors.New("Invalid conversation map: Item contains invalid TheirIdentityHash: " + theirIdentityHashString) } if (myIdentityType != theirIdentityType){ return false, 0, errors.New("Invalid conversation map: Item contains mismatched My and Their identityTypes.") } if (myIdentityType != identityType){ return false, 0, errors.New("GetMyChatConversationsMapList returning conversation map for different identityType participants.") } readUnreadStatus, err := myReadStatus.GetConversationReadUnreadStatus(myIdentityHash, theirIdentityHash, networkType) if (err != nil) { return false, 0, err } if (readUnreadStatus == "Unread"){ numberOfUnreadConversations += 1 } } return true, numberOfUnreadConversations, nil } //Outputs: // -bool: Build encountered error // -string: Error encountered // -bool: Build is stopped (will be stopped if user went to different page) // -bool: Conversations are ready // -float64: Percentage Progress (0 - 1) // -error func GetChatConversationsBuildStatus(identityType string, networkType byte)(bool, string, bool, bool, float64, error){ isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return false, "", false, false, 0, errors.New("GetChatConversationsBuildStatus called with invalid networkType: " + networkTypeString) } exists, encounteredError := appMemory.GetMemoryEntry(identityType + "ChatConversationsBuildEncounteredError") if (exists == false){ // No build exists. A build has not been started since Seekia was started return false, "", false, false, 0, nil } if (encounteredError == "Yes"){ exists, errorEncountered := appMemory.GetMemoryEntry(identityType + "ChatConversationsBuildError") if (exists == false){ return false, "", false, false, 0, errors.New("Chat conversations build error encountered error is yes, but no error exists.") } return true, errorEncountered, false, false, 0, nil } isStopped := CheckIfBuildMyConversationsIsStopped() if (isStopped == true){ return false, "", true, false, 0, nil } conversationsReadyBool, err := GetMyChatConversationsReadyStatus(identityType, networkType) if (err != nil) { return false, "", false, false, 0, err } if (conversationsReadyBool == true){ return false, "", false, true, 1, nil } exists, currentPercentageString := appMemory.GetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus") if (exists == false){ // No build exists. A build has not been started since Seekia was started return false, "", false, false, 0, nil } currentPercentageFloat, err := helpers.ConvertStringToFloat64(currentPercentageString) if (err != nil){ return false, "", false, false, 0, errors.New("ChatConversationsReadyProgressStatus is invalid: Not a float: " + currentPercentageString) } return false, "", false, false, currentPercentageFloat, nil } func CheckIfBuildMyConversationsIsStopped()bool{ exists, buildStoppedStatus := appMemory.GetMemoryEntry("StopBuildMyConversationsYesNo") if (exists == false || buildStoppedStatus != "No") { return true } return false } // This function will cancel the current build (if one is running) // It will then start updating our chat messages and conversations list func StartUpdatingMyConversations(identityType string, networkType byte) error{ if (identityType != "Mate" && identityType != "Moderator") { return errors.New("StartUpdatingMyConversations called with invalid identity type: " + identityType) } isValid := helpers.VerifyNetworkType(networkType) if (isValid == false){ networkTypeString := helpers.ConvertByteToString(networkType) return errors.New("StartUpdatingMyConversations called with invalid networkType: " + networkTypeString) } networkTypeString := helpers.ConvertByteToString(networkType) appMemory.SetMemoryEntry("StopBuildMyConversationsYesNo", "Yes") // We wait for any existing build to stop updatingMyChatConversationsMutex.Lock() appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildEncounteredError", "No") appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildError", "") appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", "0") appMemory.SetMemoryEntry("StopBuildMyConversationsYesNo", "No") updateMyChatConversations := func()error{ myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash(identityType) if (err != nil) { return err } if (myIdentityExists == false){ // Our identity does not exist. // We first update our myChatMessagesMapList ready status by running the below function updateProgressFunction := func(_ int)error{ return nil } _, err := myChatMessages.GetUpdatedMyChatMessagesMapList(identityType, networkType, updateProgressFunction) if (err != nil) { return err } // Now we overwrite our conversation datastores with empty map lists. // Using the DeleteMapList function achieves this. myChatConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType) if (err != nil) { return err } err = myChatConversationsMapListDatastore.DeleteMapList() if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "Yes") if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsNetworkType", networkTypeString) if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsSortedStatus", "Yes") if (err != nil) { return err } appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", "1") err = mySettings.SetSetting(identityType + "ChatConversationsNeedRefreshYesNo", "No") if (err != nil) { return err } return nil } isStopped := CheckIfBuildMyConversationsIsStopped() if (isStopped == true) { // User has moved to a different page. Stop generating. return nil } getConversationsNeedToBeGeneratedStatus := func()(bool, error){ messagesReadyStatus, err := myChatMessages.GetMyChatMessagesReadyStatus(identityType) if (err != nil) { return false, err } if (messagesReadyStatus == false){ return true, nil } conversationsGenerated, err := getMyChatConversationsGeneratedStatus(identityType) if (err != nil) { return false, err } if (conversationsGenerated == false){ return true, nil } exists, chatConversationsNetworkTypeString, err := mySettings.GetSetting(identityType + "ChatConversationsNetworkType") if (err != nil) { return false, err } if (exists == false){ // This should not happen, because MatchesNetworkType is set to Yes whenever matches are generated return false, errors.New("...ChatConversationsNetworkType missing when ...ChatConversationsGeneratedStatus exists.") } chatConversationsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(chatConversationsNetworkTypeString) if (err != nil) { return false, errors.New("mySettings contains invalid ...ChatConversationsNetworkType: " + chatConversationsNetworkTypeString) } if (chatConversationsNetworkType != networkType){ // This should not happen, because ...ChatConversationsGeneratedStatus should be set to No whenever app network // type is changed, and StartUpdatingMyConversations should only be called with the current app network type. return true, nil } return false, nil } conversationsNeedToBeGenerated, err := getConversationsNeedToBeGeneratedStatus() if (err != nil) { return err } if (conversationsNeedToBeGenerated == true){ // We generate conversations err := mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "No") if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsSortedStatus", "No") if (err != nil) { return err } updatePercentageProgressFunction := func(input int)error{ // Input is a value between 0-100 // We reduce it down to a value between 0-50 newProgressInt, err := helpers.ScaleIntProportionally(true, input, 0, 100, 0, 50) if (err != nil) { return err } newPercentageProgressFloat := float64(newProgressInt)/100 newPercentageProgressString := helpers.ConvertFloat64ToString(newPercentageProgressFloat) appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", newPercentageProgressString) return nil } myChatMessagesMapList, err := myChatMessages.GetUpdatedMyChatMessagesMapList(identityType, networkType, updatePercentageProgressFunction) if (err != nil) { return err } appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", ".50") // Conversations map stores data as TheirIdentityHash -> MostRecentMessageTime conversationsMap := make(map[[16]byte]int64) for _, messageMap := range myChatMessagesMapList { isStopped := CheckIfBuildMyConversationsIsStopped() if (isStopped == true) { // User has moved to a different page. Stop generating. return nil } messageMyIdentityHashString, exists := messageMap["MyIdentityHash"] if (exists == false){ return errors.New("Malformed message map: missing MyIdentityHash.") } messageMyIdentityHash, messageMyIdentityHashIdentityType, err := identity.ReadIdentityHashString(messageMyIdentityHashString) if (err != nil){ return errors.New("Malformed message map: contains invalid MyIdentityHash: " + messageMyIdentityHashString) } if (messageMyIdentityHashIdentityType != identityType){ return errors.New("GetUpdatedMyChatMessagesMapList returning message with different identity type.") } if (messageMyIdentityHash != myIdentityHash){ // This should not happen, because our old identity's messages should have been deleted. return errors.New("GetUpdatedMyChatMessagesMapList returning map list containing message from a different identityHash than our current one for provided identityType.") } messageTheirIdentityHashString, exists := messageMap["TheirIdentityHash"] if (exists == false){ return errors.New("Malformed message map: Missing TheirIdentityHash.") } messageTheirIdentityHash, theirIdentityType, err := identity.ReadIdentityHashString(messageTheirIdentityHashString) if (err != nil) { return errors.New("Malformed message map: Contains invalid TheirIdentityHash: " + messageTheirIdentityHashString) } if (theirIdentityType != identityType){ return errors.New("myChatMessagesMapList contains message map between different identity types.") } messageNetworkType, exists := messageMap["NetworkType"] if (exists == false){ return errors.New("myChatMessagesMapList returning messageMap missing NetworkType.") } if (messageNetworkType != networkTypeString){ return errors.New("myChatMessagesMapList returning messageMap with different NetworkType") } messageCreationTimeString, exists := messageMap["CreationTime"] if (exists == false){ return errors.New("Malformed message map: Missing CreationTime.") } currentMessageCreationTimeInt64, err := helpers.ConvertStringToInt64(messageCreationTimeString) if (err != nil) { return errors.New("Malformed message map: Contains invalid CreationTime: " + messageCreationTimeString) } existingMostRecentMessageCreationTime, exists := conversationsMap[messageTheirIdentityHash] if (exists == false || currentMessageCreationTimeInt64 > existingMostRecentMessageCreationTime){ conversationsMap[messageTheirIdentityHash] = currentMessageCreationTimeInt64 } } appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", ".60") myIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(myIdentityHash) if (err != nil){ myIdentityHashHex := encoding.EncodeBytesToHexString(myIdentityHash[:]) return errors.New("GetMyIdentityHash returning invalid myIdentityHash: " + myIdentityHashHex) } newConversationsMapList := make([]map[string]string, 0) for theirIdentityHash, conversationMostRecentMessageTime := range conversationsMap { isStopped := CheckIfBuildMyConversationsIsStopped() if (isStopped == true) { // User has moved to a different page. Stop generating. return nil } theyAreBlocked, _, _, _, err := myBlockedUsers.CheckIfUserIsBlocked(theirIdentityHash) if (err != nil) { return err } if (theyAreBlocked == true) { continue } userPassesChatFilters, err := myChatFilters.CheckIfUserPassesAllMyChatFilters(theirIdentityHash, networkType) if (err != nil) { return err } if (userPassesChatFilters == false){ continue } theirIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(theirIdentityHash) if (err != nil){ theirIdentityHashHex := encoding.EncodeBytesToHexString(theirIdentityHash[:]) return errors.New("conversationsMap contains invalid theirIdentityHash: " + theirIdentityHashHex) } mostRecentMessageCreationTimeString := helpers.ConvertInt64ToString(conversationMostRecentMessageTime) newConversationMap := map[string]string{ "MyIdentityHash": myIdentityHashString, "TheirIdentityHash": theirIdentityHashString, "NetworkType": networkTypeString, "MostRecentMessageTime": mostRecentMessageCreationTimeString, } newConversationsMapList = append(newConversationsMapList, newConversationMap) } myChatConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType) if (err != nil) { return err } err = myChatConversationsMapListDatastore.OverwriteMapList(newConversationsMapList) if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsGeneratedStatus", "Yes") if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsNetworkType", networkTypeString) if (err != nil) { return err } } isStopped = CheckIfBuildMyConversationsIsStopped() if (isStopped == true) { // User has moved to a different page. Stop generating. return nil } appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", ".70") conversationsReady, err := GetMyChatConversationsReadyStatus(identityType, networkType) if (err != nil) { return err } if (conversationsReady == false){ // Now we sort conversations. currentSortByAttribute, err := GetConversationsSortByAttribute(identityType) if (err != nil) { return err } currentSortDirection, err := GetConversationsSortDirection(identityType) if (err != nil) { return err } myChatConversationsMapListDatastore, err := getMyConversationsMapListDatastore(identityType) if (err != nil) { return err } currentConversationsMapList, err := myChatConversationsMapListDatastore.GetMapList() if (err != nil) { return err } // We use this map to make sure there are no duplicate recipients // This should never happen, unless the user's stored map list was edited or there is a bug recipientsMap := make(map[[16]byte]struct{}) // Map structure: Their Identity Hash -> Sort By Attribute Value recipientAttributeValuesMap := make(map[string]float64) getAllowUnknownViewableStatusBool := func()bool{ if (identityType == "Mate"){ return false } return true } allowUnknownViewableStatusBool := getAllowUnknownViewableStatusBool() maximumIndex := len(currentConversationsMapList) - 1 for index, conversationMap := range currentConversationsMapList{ conversationMyIdentityHashString, exists := conversationMap["MyIdentityHash"] if (exists == false) { return errors.New("Malformed conversation map during sort: item missing MyIdentityHash.") } conversationMyIdentityHash, conversationMyIdentityHashIdentityType, err := identity.ReadIdentityHashString(conversationMyIdentityHashString) if (err != nil){ return errors.New("Malformed conversation map during sort: contains invalid MyIdentityHash: " + conversationMyIdentityHashString) } if (conversationMyIdentityHashIdentityType != identityType){ return errors.New("Malformed conversation map during sort: contains conversation with different identity type.") } if (conversationMyIdentityHash != myIdentityHash){ // This should not happen, because our conversations should be regenerated whenever we change our identity return errors.New("Malformed conversation map during sort: Contains different identity hash.") } theirIdentityHashString, exists := conversationMap["TheirIdentityHash"] if (exists == false) { return errors.New("ChatConversations map list item missing TheirIdentityHash") } theirIdentityHash, theirIdentityType, err := identity.ReadIdentityHashString(theirIdentityHashString) if (err != nil){ return errors.New("ChatConversations map list item contains invalid theirIdentityHash: " + theirIdentityHashString) } if (theirIdentityType != identityType){ return errors.New("ChatConversations map list item contains invalid theirIdentityHash: Different identityType: " + theirIdentityType) } _, exists = recipientsMap[theirIdentityHash] if (exists == true){ return errors.New("ChatConversations map list is malformed: Contains two conversation maps with the same recipient") } recipientsMap[theirIdentityHash] = struct{}{} profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(theirIdentityHash, networkType, currentSortByAttribute, true, allowUnknownViewableStatusBool, true) if (err != nil) { return err } if (profileExists == true && attributeExists == true){ attributeValueFloat, err := helpers.ConvertStringToFloat64(attributeValue) if (err != nil) { return errors.New("User attribute cannot be converted to float during conversation build: " + attributeValue) } recipientAttributeValuesMap[theirIdentityHashString] = attributeValueFloat } isStopped := CheckIfBuildMyConversationsIsStopped() if (isStopped == true){ // User has moved to a different page. Stop generating. return nil } newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 70, 85) if (err != nil) { return err } newProgressFloat := float64(newScaledPercentageInt)/100 newProgressString := helpers.ConvertFloat64ToString(newProgressFloat) appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", newProgressString) } compareConversationMapsFunction := func(conversationMapA map[string]string, conversationMapB map[string]string) int { identityHashA, exists := conversationMapA["TheirIdentityHash"] if (exists == false) { panic("Malformed conversations map list: Item missing TheirIdentityHash") } identityHashB, exists := conversationMapB["TheirIdentityHash"] if (exists == false) { panic("Malformed conversations map list: Item missing TheirIdentityHash") } if (identityHashA == identityHashB){ panic("Malformed conversations map list: Two conversations contain the same TheirIdentityHash.") } attributeValueA, attributeValueAExists := recipientAttributeValuesMap[identityHashA] attributeValueB, attributeValueBExists := recipientAttributeValuesMap[identityHashB] if (attributeValueAExists == false && attributeValueBExists == false){ // We don't know the attribute value for either recipient // We sort recipients in unicode order if (identityHashA < identityHashB){ return -1 } return 1 } else if (attributeValueAExists == true && attributeValueBExists == false){ // We sort unknown attribute recipient conversations to the back of the list return -1 } else if (attributeValueAExists == false && attributeValueBExists == true){ return 1 } // Both recipient attribute values exist if (attributeValueA == attributeValueB){ // If values are equal, we want the result to be the same with each refresh // We sort the identity hashses in unicode order if (identityHashA < identityHashB){ return -1 } return 1 } if (attributeValueA < attributeValueB){ if (currentSortDirection == "Ascending"){ return -1 } return 1 } if (currentSortDirection == "Ascending"){ return 1 } return -1 } slices.SortFunc(currentConversationsMapList, compareConversationMapsFunction) err = myChatConversationsMapListDatastore.OverwriteMapList(currentConversationsMapList) if (err != nil) { return err } err = mySettings.SetSetting(identityType + "ChatConversationsSortedStatus", "Yes") if (err != nil) { return err } } appMemory.SetMemoryEntry(identityType + "ChatConversationsReadyProgressStatus", "1") err = mySettings.SetSetting(identityType + "ChatConversationsNeedRefreshYesNo", "No") if (err != nil) { return err } return nil } updateFunction := func(){ err := updateMyChatConversations() if (err != nil){ appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildEncounteredError", "Yes") appMemory.SetMemoryEntry(identityType + "ChatConversationsBuildError", err.Error()) } updatingMyChatConversationsMutex.Unlock() } go updateFunction() return nil }