551 lines
18 KiB
Go
551 lines
18 KiB
Go
|
|
// myMatches provides functions to generate and retrieve a user's mate matches
|
|
// Matches are the users whom fulfill a user's desires.
|
|
|
|
package myMatches
|
|
|
|
import "seekia/internal/appMemory"
|
|
import "seekia/internal/badgerDatabase"
|
|
import "seekia/internal/desires/myMateDesires"
|
|
import "seekia/internal/helpers"
|
|
import "seekia/internal/identity"
|
|
import "seekia/internal/myBlockedUsers"
|
|
import "seekia/internal/myDatastores/myList"
|
|
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 are updating matches.
|
|
var updatingMatchesMutex sync.Mutex
|
|
|
|
var myMatchesListDatastore *myList.MyList
|
|
|
|
// This function must be called whenever an app user signs in
|
|
func InitializeMyMatchesDatastores()error{
|
|
|
|
updatingMatchesMutex.Lock()
|
|
defer updatingMatchesMutex.Unlock()
|
|
|
|
newMyMatchesListDatastore, err := myList.CreateNewList("MyMatches")
|
|
if (err != nil) { return err }
|
|
|
|
myMatchesListDatastore = newMyMatchesListDatastore
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetMatchesSortByAttribute()(string, error){
|
|
|
|
exists, currentAttribute, err := mySettings.GetSetting("MatchesSortByAttribute")
|
|
if (err != nil) { return "", err }
|
|
if (exists == false){
|
|
return "MatchScore", nil
|
|
}
|
|
|
|
return currentAttribute, nil
|
|
}
|
|
|
|
|
|
func GetMatchesSortDirection()(string, error){
|
|
|
|
exists, currentDirection, err := mySettings.GetSetting("MatchesSortDirection")
|
|
if (err != nil) { return "", err }
|
|
if (exists == false){
|
|
return "Descending", nil
|
|
}
|
|
if (currentDirection != "Ascending" && currentDirection != "Descending"){
|
|
return "", errors.New("MySettings malformed: Invalid MatchesSortDirection: " + currentDirection)
|
|
}
|
|
|
|
return currentDirection, nil
|
|
}
|
|
|
|
|
|
func GetMatchesReadyStatus(networkType byte)(bool, error) {
|
|
|
|
isValid := helpers.VerifyNetworkType(networkType)
|
|
if (isValid == false){
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
|
return false, errors.New("GetMatchesReadyStatus called with invalid networkType: " + networkTypeString)
|
|
}
|
|
|
|
exists, matchesGeneratedStatus, err := mySettings.GetSetting("MatchesGeneratedStatus")
|
|
if (err != nil) { return false, err }
|
|
if (exists == false || matchesGeneratedStatus != "Yes") {
|
|
return false, nil
|
|
}
|
|
|
|
exists, matchesSortedStatus, err := mySettings.GetSetting("MatchesSortedStatus")
|
|
if (err != nil) { return false, err }
|
|
if (exists == false || matchesSortedStatus != "Yes") {
|
|
return false, nil
|
|
}
|
|
|
|
exists, matchesNetworkTypeString, err := mySettings.GetSetting("MatchesNetworkType")
|
|
if (err != nil) { return false, err }
|
|
if (exists == false){
|
|
// This should not happen, because Matches network type is created whenever matches are generated
|
|
return false, errors.New("mySettings missing MatchesNetworkType when MatchesGeneratedStatus exists.")
|
|
}
|
|
|
|
matchesNetworkType, err := helpers.ConvertNetworkTypeStringToByte(matchesNetworkTypeString)
|
|
if (err != nil) {
|
|
return false, errors.New("mySettings contains invalid MatchesNetworkType: " + matchesNetworkTypeString)
|
|
}
|
|
if (matchesNetworkType != networkType){
|
|
// Matches were generated for a different networkType
|
|
// This should never happen, because we will always set MatchesGeneratedStatus to No when we switch network types,
|
|
// and we will always call the GetMatchesReadyStatus function with the current appNetworkType
|
|
|
|
//TODO: Log this.
|
|
|
|
err := mySettings.SetSetting("MatchesGeneratedStatus", "No")
|
|
if (err != nil) { return false, err }
|
|
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Will need a refresh any time a new mate profile is downloaded
|
|
func CheckIfMyMatchesNeedRefresh()(bool, error){
|
|
|
|
exists, needsRefresh, err := mySettings.GetSetting("MatchesNeedRefreshYesNo")
|
|
if (err != nil) { return true, err }
|
|
if (exists == true && needsRefresh == "No"){
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// This function should only be called once matches are ready (generated and sorted)
|
|
//Outputs:
|
|
// -bool: Matches are ready
|
|
// -[][16]byte: List of sorted matches
|
|
// -error
|
|
func GetMyMatchesList(networkType byte)(bool, [][16]byte, error) {
|
|
|
|
isValid := helpers.VerifyNetworkType(networkType)
|
|
if (isValid == false){
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
|
return false, nil, errors.New("GetMyMatchesList called with invalid networkType: " + networkTypeString)
|
|
}
|
|
|
|
matchesReady, err := GetMatchesReadyStatus(networkType)
|
|
if (err != nil) { return false, nil, err }
|
|
if (matchesReady == false){
|
|
return false, nil, nil
|
|
}
|
|
|
|
myMatchIdentityHashesList, err := myMatchesListDatastore.GetList()
|
|
if (err != nil) { return false, nil, err }
|
|
|
|
myMatchesList := make([][16]byte, 0, len(myMatchIdentityHashesList))
|
|
|
|
for _, matchIdentityHashString := range myMatchIdentityHashesList{
|
|
|
|
matchIdentityHash, identityType, err := identity.ReadIdentityHashString(matchIdentityHashString)
|
|
if (err != nil){
|
|
return false, nil, errors.New("myMatchesListDatastore contains invalid match identity hash: " + matchIdentityHashString)
|
|
}
|
|
if (identityType != "Mate"){
|
|
return false, nil, errors.New("myMatchesListDatastore contains non-Mate match identity hash.")
|
|
}
|
|
|
|
myMatchesList = append(myMatchesList, matchIdentityHash)
|
|
}
|
|
|
|
return true, myMatchesList, nil
|
|
}
|
|
|
|
// This function returns the number of matches
|
|
// It can be called before the matches are ready (after being generated, but before being sorted)
|
|
func GetNumberOfGeneratedMatches()(int, error){
|
|
|
|
myMatchesList, err := myMatchesListDatastore.GetList()
|
|
if (err != nil) { return 0, err }
|
|
|
|
numberOfGeneratedMatches := len(myMatchesList)
|
|
|
|
return numberOfGeneratedMatches, nil
|
|
}
|
|
|
|
//Outputs:
|
|
// -bool: Build encountered error
|
|
// -string: Error encountered
|
|
// -bool: Build is stopped (will be stopped if user went to different page)
|
|
// -bool: Matches are ready
|
|
// -float64: Progress status (0 - 1)
|
|
// -error
|
|
func GetMyMatchesBuildStatus(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("GetMyMatchesBuildStatus called with invalid networkType: " + networkTypeString)
|
|
}
|
|
|
|
exists, encounteredError := appMemory.GetMemoryEntry("MatchesBuildEncounteredError")
|
|
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("MatchesBuildError")
|
|
if (exists == false){
|
|
return false, "", false, false, 0, errors.New("Matches build encountered error is yes, but no error exists.")
|
|
}
|
|
|
|
return true, errorEncountered, false, false, 0, nil
|
|
}
|
|
|
|
isStopped := CheckIfBuildMatchesIsStopped()
|
|
if (isStopped == true){
|
|
return false, "", true, false, 0, nil
|
|
}
|
|
|
|
matchesReadyBool, err := GetMatchesReadyStatus(networkType)
|
|
if (err != nil) { return false, "", false, false, 0, err }
|
|
if (matchesReadyBool == true){
|
|
return false, "", false, true, 1, nil
|
|
}
|
|
|
|
exists, currentPercentageString := appMemory.GetMemoryEntry("MatchesReadyProgressPercentage")
|
|
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("MatchesReadyProgressPercentage is not a float: " + currentPercentageString)
|
|
}
|
|
|
|
return false, "", false, false, currentPercentageFloat, nil
|
|
}
|
|
|
|
|
|
// True == Stop building matches
|
|
// False = Don't stop building matches
|
|
func CheckIfBuildMatchesIsStopped()bool{
|
|
|
|
exists, stopBuildMatchesYesNo := appMemory.GetMemoryEntry("StopBuildMatchesYesNo")
|
|
if (exists == false || stopBuildMatchesYesNo != "No"){
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// This function will cancel the current build (if one is running)
|
|
// It will then start updating our matches
|
|
func StartUpdatingMyMatches(networkType byte)error{
|
|
|
|
isValid := helpers.VerifyNetworkType(networkType)
|
|
if (isValid == false){
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
|
return errors.New("StartUpdatingMyMatches called with invalid networkType: " + networkTypeString)
|
|
}
|
|
|
|
appMemory.SetMemoryEntry("StopBuildMatchesYesNo", "Yes")
|
|
|
|
// We wait for any existing builds to stop.
|
|
updatingMatchesMutex.Lock()
|
|
|
|
appMemory.SetMemoryEntry("MatchesReadyProgressPercentage", "0")
|
|
appMemory.SetMemoryEntry("MatchesBuildEncounteredError", "No")
|
|
appMemory.SetMemoryEntry("MatchesBuildError", "")
|
|
|
|
appMemory.SetMemoryEntry("StopBuildMatchesYesNo", "No")
|
|
|
|
updateMatches := func()error{
|
|
|
|
appMemory.SetMemoryEntry("MatchesReadyProgressPercentage", "0")
|
|
|
|
getMatchesNeedToBeGeneratedStatus := func()(bool, error){
|
|
|
|
exists, matchesGeneratedStatus, err := mySettings.GetSetting("MatchesGeneratedStatus")
|
|
if (err != nil) { return false, err }
|
|
if (exists == false || matchesGeneratedStatus != "Yes"){
|
|
return true, nil
|
|
}
|
|
|
|
exists, matchesNetworkTypeString, err := mySettings.GetSetting("MatchesNetworkType")
|
|
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("MatchesNetworkType missing when MatchesGeneratedStatus exists.")
|
|
}
|
|
|
|
matchesNetworkType, err := helpers.ConvertNetworkTypeStringToByte(matchesNetworkTypeString)
|
|
if (err != nil) {
|
|
return false, errors.New("mySettings contains invalid MatchesNetworkType: " + matchesNetworkTypeString)
|
|
}
|
|
if (matchesNetworkType != networkType){
|
|
// This should not happen, because MatchesGeneratedStatus should be set to No whenever app network type is changed,
|
|
// and StartUpdatingMyMatches should only be called with the current app network type.
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
matchesNeedToBeGenerated, err := getMatchesNeedToBeGeneratedStatus()
|
|
if (err != nil) { return err }
|
|
if (matchesNeedToBeGenerated == true){
|
|
|
|
err := mySettings.SetSetting("MatchesSortedStatus", "No")
|
|
if (err != nil) { return err }
|
|
|
|
// We use below to make sure we do not add ourselves as a match
|
|
myIdentityExists, myIdentityHash, err := myIdentity.GetMyIdentityHash("Mate")
|
|
if (err != nil) { return err }
|
|
|
|
mateIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Mate")
|
|
if (err != nil) { return err }
|
|
|
|
maximumIndex := len(mateIdentityHashesList) - 1
|
|
|
|
// This is a list of match identity hashes
|
|
matchesList := make([]string, 0)
|
|
|
|
for index, peerIdentityHash := range mateIdentityHashesList{
|
|
|
|
stopBuildMatchesYesNo := CheckIfBuildMatchesIsStopped()
|
|
if (stopBuildMatchesYesNo == true){
|
|
// User has gone to a different page, match generation was stopped
|
|
return nil
|
|
}
|
|
|
|
progressPercentage, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 0, 50)
|
|
if (err != nil) { return err }
|
|
|
|
progressFloat := float64(progressPercentage)/100
|
|
matchesReadyProgressPercentage := helpers.ConvertFloat64ToString(progressFloat)
|
|
|
|
appMemory.SetMemoryEntry("MatchesReadyProgressPercentage", matchesReadyProgressPercentage)
|
|
|
|
if (myIdentityExists == true && peerIdentityHash == myIdentityHash){
|
|
// We never show ourselves as a match
|
|
continue
|
|
}
|
|
|
|
userIsBlocked, _, _, _, err := myBlockedUsers.CheckIfUserIsBlocked(peerIdentityHash)
|
|
if (err != nil) { return err }
|
|
if (userIsBlocked == true){
|
|
continue
|
|
}
|
|
|
|
profileExists, _, getAnyUserProfileAttributeFunction, err := viewableProfiles.GetRetrieveAnyNewestViewableUserProfileAttributeFunction(peerIdentityHash, networkType, true, false, true)
|
|
if (err != nil) { return err }
|
|
if (profileExists == false) {
|
|
// Profile must have been deleted or it is not viewable
|
|
// User cannot be a match, because they have no viewable profile.
|
|
continue
|
|
}
|
|
|
|
exists, _, userIsDisabled, err := getAnyUserProfileAttributeFunction("Disabled")
|
|
if (err != nil) { return err }
|
|
if (exists == true && userIsDisabled == "Yes"){
|
|
// User's newest viewable profile is disabled.
|
|
continue
|
|
}
|
|
|
|
profilePassesDesires, err := myMateDesires.CheckIfMateProfilePassesAllMyDesires(false, "", getAnyUserProfileAttributeFunction)
|
|
if (err != nil) { return err }
|
|
if (profilePassesDesires == true){
|
|
|
|
peerIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(peerIdentityHash)
|
|
if (err != nil) { return err }
|
|
|
|
matchesList = append(matchesList, peerIdentityHashString)
|
|
}
|
|
}
|
|
|
|
err = myMatchesListDatastore.OverwriteList(matchesList)
|
|
if (err != nil) { return err }
|
|
|
|
err = mySettings.SetSetting("MatchesGeneratedStatus", "Yes")
|
|
if (err != nil) { return err }
|
|
|
|
networkTypeString := helpers.ConvertByteToString(networkType)
|
|
|
|
err = mySettings.SetSetting("MatchesNetworkType", networkTypeString)
|
|
if (err != nil) { return err }
|
|
|
|
err = mySettings.SetSetting("MatchesViewIndex", "0")
|
|
if (err != nil) { return err }
|
|
}
|
|
|
|
stopBuildMatchesBool := CheckIfBuildMatchesIsStopped()
|
|
if (stopBuildMatchesBool == true){
|
|
return nil
|
|
}
|
|
|
|
appMemory.SetMemoryEntry("MatchesReadyProgressPercentage", "0.50")
|
|
|
|
exists, matchesSortedStatus, err := mySettings.GetSetting("MatchesSortedStatus")
|
|
if (err != nil) { return err }
|
|
if (exists == false || matchesSortedStatus != "Yes"){
|
|
|
|
// Now we sort matches.
|
|
|
|
myMatchesList, err := myMatchesListDatastore.GetList()
|
|
if (err != nil) { return err }
|
|
|
|
currentSortBy, err := GetMatchesSortByAttribute()
|
|
if (err != nil) { return err }
|
|
currentSortDirection, err := GetMatchesSortDirection()
|
|
if (err != nil) { return err }
|
|
|
|
// We use this map to make sure there are no duplicate matches
|
|
// This should never happen, unless the user's stored list was edited or there is a bug
|
|
allMatchesMap := make(map[[16]byte]struct{})
|
|
|
|
// Map structure: Match Identity Hash -> Sort By Attribute Value
|
|
matchAttributeValuesMap := make(map[string]float64)
|
|
|
|
maximumIndex := len(myMatchesList) - 1
|
|
|
|
for index, matchIdentityHashString := range myMatchesList{
|
|
|
|
matchIdentityHash, _, err := identity.ReadIdentityHashString(matchIdentityHashString)
|
|
if (err != nil){
|
|
return errors.New("myMatchesList contains invalid identity hash during sort: " + matchIdentityHashString)
|
|
}
|
|
|
|
_, exists := allMatchesMap[matchIdentityHash]
|
|
if (exists == true){
|
|
return errors.New("myMatchesList contains duplicate match.")
|
|
}
|
|
allMatchesMap[matchIdentityHash] = struct{}{}
|
|
|
|
profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(matchIdentityHash, networkType, currentSortBy, true, false, false)
|
|
if (err != nil) { return err }
|
|
if (profileExists == false){
|
|
// Profile must have been deleted, or it became unviewable
|
|
// The gui will let the user know this when they navigate to the profile
|
|
// The user will be placed towards the end of the sort, with all of the other No Response users
|
|
continue
|
|
}
|
|
if (attributeExists == true){
|
|
attributeValueFloat, err := helpers.ConvertStringToFloat64(attributeValue)
|
|
if (err != nil) {
|
|
return errors.New("Mate profile attribute cannot be converted to float during myMatches sort: " + attributeValue)
|
|
}
|
|
|
|
matchAttributeValuesMap[matchIdentityHashString] = attributeValueFloat
|
|
}
|
|
|
|
isStopped := CheckIfBuildMatchesIsStopped()
|
|
if (isStopped == true){
|
|
// User has gone to a different page, match generation was stopped
|
|
return nil
|
|
}
|
|
|
|
newScaledPercentageInt, err := helpers.ScaleNumberProportionally(true, index, 0, maximumIndex, 50, 80)
|
|
if (err != nil) { return err }
|
|
|
|
newProgressFloat := float64(newScaledPercentageInt)/100
|
|
|
|
newProgressString := helpers.ConvertFloat64ToString(newProgressFloat)
|
|
|
|
appMemory.SetMemoryEntry("MatchesReadyProgressPercentage", newProgressString)
|
|
}
|
|
|
|
compareMatchesFunction := func(identityHashA string, identityHashB string) int {
|
|
|
|
if (identityHashA == identityHashB){
|
|
panic("Duplicate match identity hashes called during sort.")
|
|
}
|
|
|
|
attributeValueA, attributeValueAExists := matchAttributeValuesMap[identityHashA]
|
|
|
|
attributeValueB, attributeValueBExists := matchAttributeValuesMap[identityHashB]
|
|
|
|
if (attributeValueAExists == false && attributeValueBExists == false){
|
|
|
|
// We don't know the attribute value for either match
|
|
// We sort matches in unicode order
|
|
if (identityHashA < identityHashB){
|
|
return -1
|
|
}
|
|
return 1
|
|
|
|
} else if (attributeValueAExists == true && attributeValueBExists == false){
|
|
|
|
// We sort unknown attribute matches to the back of the list
|
|
|
|
return -1
|
|
|
|
} else if (attributeValueAExists == false && attributeValueBExists == true){
|
|
|
|
return 1
|
|
}
|
|
|
|
// Both attribute values exist
|
|
|
|
if (attributeValueA == attributeValueB){
|
|
// We sort identity hashes 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(myMatchesList, compareMatchesFunction)
|
|
|
|
err = myMatchesListDatastore.OverwriteList(myMatchesList)
|
|
if (err != nil) { return err }
|
|
|
|
err = mySettings.SetSetting("MatchesSortedStatus", "Yes")
|
|
if (err != nil) { return err }
|
|
}
|
|
|
|
appMemory.SetMemoryEntry("MatchesReadyProgressPercentage", "1")
|
|
|
|
err = mySettings.SetSetting("MatchesNeedRefreshYesNo", "No")
|
|
if (err != nil) { return err }
|
|
|
|
return nil
|
|
}
|
|
|
|
updateFunction := func(){
|
|
|
|
err := updateMatches()
|
|
if (err != nil) {
|
|
appMemory.SetMemoryEntry("MatchesBuildEncounteredError", "Yes")
|
|
appMemory.SetMemoryEntry("MatchesBuildError", err.Error())
|
|
}
|
|
|
|
updatingMatchesMutex.Unlock()
|
|
}
|
|
|
|
go updateFunction()
|
|
|
|
return nil
|
|
}
|
|
|
|
|
|
|
|
|
|
|