seekia/internal/moderation/viewedModerators/viewedModerators.go

600 lines
20 KiB
Go

// viewedModerators provides functions to generate and retrieve the viewedModerators list
// This list is used to browse moderators on the View Moderators page
package viewedModerators
import "seekia/internal/appMemory"
import "seekia/internal/badgerDatabase"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/identity"
import "seekia/internal/myDatastores/myList"
import "seekia/internal/myDatastores/myMap"
import "seekia/internal/mySettings"
import "seekia/internal/profiles/viewableProfiles"
import "slices"
import "errors"
import "sync"
// This mutex will be locked whenever the moderators list is being updated
var updatingModeratorsMutex sync.Mutex
var viewedModeratorsListDatastore *myList.MyList
var viewedModeratorsFiltersMapDatastore *myMap.MyMap
// This function must be called whenever an app user signs in
func InitializeViewedModeratorsDatastores()error{
updatingModeratorsMutex.Lock()
defer updatingModeratorsMutex.Unlock()
newViewedModeratorsListDatastore, err := myList.CreateNewList("ViewedModerators")
if (err != nil) { return err }
newViewedModeratorsFiltersMapDatastore, err := myMap.CreateNewMap("ViewedModeratorsFilters")
if (err != nil){ return err }
viewedModeratorsListDatastore = newViewedModeratorsListDatastore
viewedModeratorsFiltersMapDatastore = newViewedModeratorsFiltersMapDatastore
return nil
}
func GetViewedModeratorsSortByAttribute()(string, error){
exists, currentAttribute, err := mySettings.GetSetting("ViewedModeratorsSortByAttribute")
if (err != nil) { return "", err }
if (exists == false){
return "IdentityScore", nil
}
return currentAttribute, nil
}
func GetViewedModeratorsSortDirection()(string, error){
exists, sortDirection, err := mySettings.GetSetting("ViewedModeratorsSortDirection")
if (err != nil) { return "", err }
if (exists == false){
return "Descending", nil
}
if (sortDirection != "Ascending" && sortDirection != "Descending"){
return "", errors.New("mySettings malformed: Contains invalid ViewedModeratorsSortDirection: " + sortDirection)
}
return sortDirection, nil
}
// Will need a refresh any time a new moderator profile is downloaded
func CheckIfViewedModeratorsNeedsRefresh()(bool, error){
exists, needsRefresh, err := mySettings.GetSetting("ViewedModeratorsNeedsRefreshYesNo")
if (err != nil) { return true, err }
if (exists == true && needsRefresh == "No") {
return false, nil
}
return true, nil
}
func GetViewedModeratorsAreReadyStatus(networkType byte)(bool, error){
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return false, errors.New("GetViewedModeratorsAreReadyStatus called with invalid networkType: " + networkTypeString)
}
exists, moderatorsGeneratedStatus, err := mySettings.GetSetting("ViewedModeratorsGeneratedStatus")
if (err != nil) { return false, err }
if (exists == false || moderatorsGeneratedStatus != "Yes"){
return false, nil
}
exists, moderatorsSortedStatus, err := mySettings.GetSetting("ViewedModeratorsSortedStatus")
if (err != nil) { return false, err }
if (exists == false || moderatorsSortedStatus != "Yes"){
return false, nil
}
exists, viewedModeratorsNetworkTypeString, err := mySettings.GetSetting("ViewedModeratorsNetworkType")
if (err != nil) { return false, err }
if (exists == false){
// This should not happen, because ViewedModeratorsNetworkType is created whenever viewedModerators are generated
return false, errors.New("mySettings missing ViewedModeratorsNetworkType when ViewedModeratorsGeneratedStatus exists.")
}
viewedModeratorsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(viewedModeratorsNetworkTypeString)
if (err != nil) {
return false, errors.New("mySettings contains invalid ViewedModeratorsNetworkType: " + viewedModeratorsNetworkTypeString)
}
if (viewedModeratorsNetworkType != networkType){
// ViewedModerators were generated for a different networkType
// This should never happen, because we will always set ViewedModeratorsGeneratedStatus to No when we switch network types,
// and we will always call the GetViewedModeratorsAreReadyStatus function with the current appNetworkType
//TODO: Log this.
err := mySettings.SetSetting("ViewedModeratorsGeneratedStatus", "No")
if (err != nil) { return false, err }
return false, nil
}
return true, nil
}
//Outputs:
// -bool: List is ready
// -[][16]byte: Viewed moderators list
// -error
func GetViewedModeratorsList(networkType byte)(bool, [][16]byte, error){
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return false, nil, errors.New("GetViewedModeratorsList called with invalid networkType: " + networkTypeString)
}
areReady, err := GetViewedModeratorsAreReadyStatus(networkType)
if (err != nil) { return false, nil, err }
if (areReady == false){
return false, nil, nil
}
readyModeratorIdentityHashesList, err := viewedModeratorsListDatastore.GetList()
if (err != nil) { return false, nil, err }
viewedModeratorsList := make([][16]byte, 0, len(readyModeratorIdentityHashesList))
for _, moderatorIdentityHashString := range readyModeratorIdentityHashesList{
moderatorIdentityHash, identityType, err := identity.ReadIdentityHashString(moderatorIdentityHashString)
if (err != nil){
return false, nil, errors.New("sortedViewedModeratorsListDatastore contains invalid identityHash: " + moderatorIdentityHashString)
}
if (identityType != "Moderator"){
return false, nil, errors.New("sortedViewedModeratorsListDatastore contains non-Moderator identity hash: " + identityType)
}
viewedModeratorsList = append(viewedModeratorsList, moderatorIdentityHash)
}
return true, viewedModeratorsList, nil
}
// This function returns the number of viewed moderators
// It can be called before the list is ready (after being generated, but before being sorted)
func GetNumberOfGeneratedViewedModerators()(int, error){
currentViewedModeratorsList, err := viewedModeratorsListDatastore.GetList()
if (err != nil) { return 0, err }
numberOfModerators := len(currentViewedModeratorsList)
return numberOfModerators, nil
}
//Outputs:
// -bool: Build encountered error
// -string: Error encountered
// -bool: Build is stopped (will be stopped if user went to different page)
// -bool: Viewed moderators are ready
// -float64: Percentage Progress (0 - 1)
// -error
func GetViewedModeratorsBuildStatus(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("GetViewedModeratorsBuildStatus called with invalid networkType: " + networkTypeString)
}
exists, encounteredError := appMemory.GetMemoryEntry("ViewedModeratorsBuildEncounteredError")
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("ViewedModeratorsBuildError")
if (exists == false){
return false, "", false, false, 0, errors.New("ViewedModerators build encountered error is yes, but no error exists.")
}
return true, errorEncountered, false, false, 0, nil
}
isStopped := CheckIfBuildViewedModeratorsIsStopped()
if (isStopped == true){
return false, "", true, false, 0, nil
}
viewedModeratorsReadyBool, err := GetViewedModeratorsAreReadyStatus(networkType)
if (err != nil) { return false, "", false, false, 0, err }
if (viewedModeratorsReadyBool == true){
return false, "", false, true, 1, nil
}
exists, currentPercentageString := appMemory.GetMemoryEntry("ViewedModeratorsReadyProgressStatus")
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("ViewedModeratorsReadyProgressStatus is invalid: Not a float: " + currentPercentageString)
}
return false, "", false, false, currentPercentageFloat, nil
}
func CheckIfBuildViewedModeratorsIsStopped()bool{
exists, buildStoppedStatus := appMemory.GetMemoryEntry("StopBuildViewedModeratorsYesNo")
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 viewed moderators
func StartUpdatingViewedModerators(networkType byte)error{
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return errors.New("StartUpdatingViewedModerators called with invalid networkType: " + networkTypeString)
}
appMemory.SetMemoryEntry("StopBuildViewedModeratorsYesNo", "Yes")
// We wait for any existing build to stop
updatingModeratorsMutex.Lock()
appMemory.SetMemoryEntry("ViewedModeratorsBuildEncounteredError", "No")
appMemory.SetMemoryEntry("ViewedModeratorsBuildError", "")
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", "0")
appMemory.SetMemoryEntry("StopBuildViewedModeratorsYesNo", "No")
updateViewedModerators := func()error{
getViewedModeratorsNeedToBeGeneratedStatus := func()(bool, error){
exists, moderatorsGeneratedStatus, err := mySettings.GetSetting("ViewedModeratorsGeneratedStatus")
if (err != nil) { return false, err }
if (exists == false || moderatorsGeneratedStatus != "Yes"){
return true, nil
}
exists, viewedModeratorsNetworkTypeString, err := mySettings.GetSetting("ViewedModeratorsNetworkType")
if (err != nil) { return false, err }
if (exists == false){
// This should not happen, because ViewedModeratorsNetworkType is set to Yes whenever viewed moderators are generated
return false, errors.New("ViewedModeratorsNetworkType missing when ViewedModeratorsGeneratedStatus exists.")
}
viewedModeratorsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(viewedModeratorsNetworkTypeString)
if (err != nil) {
return false, errors.New("mySettings contains invalid ViewedModeratorsNetworkType: " + viewedModeratorsNetworkTypeString)
}
if (viewedModeratorsNetworkType != networkType){
// This should not happen, because ViewedModeratorsGeneratedStatus should be set to No whenever app network type is changed,
// and StartUpdatingViewedModerators should only be called with the current app network type.
return true, nil
}
return false, nil
}
viewedModeratorsNeedToBeGenerated, err := getViewedModeratorsNeedToBeGeneratedStatus()
if (err != nil) { return err }
if (viewedModeratorsNeedToBeGenerated == true){
err := mySettings.SetSetting("ViewedModeratorsSortedStatus", "No")
if (err != nil) { return err }
// We use a map to avoid duplicates
allModeratorsMap := make(map[[16]byte]struct{})
allModeratorIdentityHashesList, err := badgerDatabase.GetAllProfileIdentityHashes("Moderator")
if (err != nil) { return err }
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".05")
for _, moderatorIdentityHash := range allModeratorIdentityHashesList{
allModeratorsMap[moderatorIdentityHash] = struct{}{}
}
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".10")
// We also get all reviewed moderator identity hashes
// This is because moderators who are banned will have their profiles deleted
allReviewedIdentityHashes, err := badgerDatabase.GetAllReviewedIdentityHashes()
if (err != nil) { return err }
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".15")
for _, identityHash := range allReviewedIdentityHashes{
identityType, err := identity.GetIdentityTypeFromIdentityHash(identityHash)
if (err != nil) { return err }
if (identityType == "Moderator"){
allModeratorsMap[identityHash] = struct{}{}
}
}
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", ".20")
numberOfModerators := len(allModeratorsMap)
generatedModeratorIdentityHashesList := make([]string, 0)
index := 0
for moderatorIdentityHash, _ := range allModeratorsMap{
moderatorPassesFilters, err := checkIfModeratorPassesFilters(moderatorIdentityHash, networkType)
if (err != nil) { return err }
if (moderatorPassesFilters == true){
moderatorIdentityHashString, _, err := identity.EncodeIdentityHashBytesToString(moderatorIdentityHash)
if (err != nil) {
moderatorIdentityHashHex := encoding.EncodeBytesToHexString(moderatorIdentityHash[:])
return errors.New("allModeratorsMap contains invalid identity hash: " + moderatorIdentityHashHex)
}
generatedModeratorIdentityHashesList = append(generatedModeratorIdentityHashesList, moderatorIdentityHashString)
}
isStopped := CheckIfBuildViewedModeratorsIsStopped()
if (isStopped == true){
return nil
}
progressPercentage, err := helpers.ScaleIntProportionally(true, index, 0, numberOfModerators-1, 20, 50)
if (err != nil) { return err }
progressFloat := float64(progressPercentage)/100
moderatorsReadyProgressPercentage := helpers.ConvertFloat64ToString(progressFloat)
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", moderatorsReadyProgressPercentage)
index += 1
}
err = viewedModeratorsListDatastore.OverwriteList(generatedModeratorIdentityHashesList)
if (err != nil) { return err }
err = mySettings.SetSetting("ViewedModeratorsGeneratedStatus", "Yes")
if (err != nil) { return err }
networkTypeString := helpers.ConvertByteToString(networkType)
err = mySettings.SetSetting("ViewedModeratorsNetworkType", networkTypeString)
if (err != nil) { return err }
}
isStopped := CheckIfBuildViewedModeratorsIsStopped()
if (isStopped == true){
return nil
}
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", "0.50")
moderatorsReadyStatus, err := GetViewedModeratorsAreReadyStatus(networkType)
if (err != nil) { return err }
if (moderatorsReadyStatus == false){
// Now we sort moderators
currentSortByAttribute, err := GetViewedModeratorsSortByAttribute()
if (err != nil) { return err }
currentSortDirection, err := GetViewedModeratorsSortDirection()
if (err != nil) { return err }
currentViewedModeratorsList, err := viewedModeratorsListDatastore.GetList()
if (err != nil) { return err }
// We use this map to make sure there are no duplicate moderators
// This should never happen, unless the user's stored list was edited or there is a bug
allModeratorsMap := make(map[[16]byte]struct{})
// Map structure: Moderator Identity Hash String -> Sort By Attribute Value
moderatorAttributeValuesMap := make(map[string]float64)
maximumIndex := len(currentViewedModeratorsList) - 1
for index, moderatorIdentityHashString := range currentViewedModeratorsList{
moderatorIdentityHash, identityType, err := identity.ReadIdentityHashString(moderatorIdentityHashString)
if (err != nil) {
return errors.New("currentViewedModeratorsList contains invalid moderator identity hash: " + moderatorIdentityHashString)
}
if (identityType != "Moderator"){
return errors.New("currentViewedModeratorsList contains non-moderator identity hash: " + identityType)
}
_, exists := allModeratorsMap[moderatorIdentityHash]
if (exists == true){
return errors.New("currentViewedModeratorsList contains duplicate identity hash.")
}
allModeratorsMap[moderatorIdentityHash] = struct{}{}
profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(moderatorIdentityHash, networkType, currentSortByAttribute, true, true, true)
if (err != nil) { return err }
if (profileExists == true && attributeExists == true){
attributeValueFloat, err := helpers.ConvertStringToFloat64(attributeValue)
if (err != nil) {
return errors.New("Viewed moderators attribute cannot be converted to float: " + currentSortByAttribute)
}
moderatorAttributeValuesMap[moderatorIdentityHashString] = attributeValueFloat
}
isStopped := CheckIfBuildViewedModeratorsIsStopped()
if (isStopped == true){
return nil
}
newScaledPercentageInt, err := helpers.ScaleIntProportionally(true, index, 0, maximumIndex, 50, 80)
if (err != nil) { return err }
newProgressFloat := float64(newScaledPercentageInt)/100
newProgressString := helpers.ConvertFloat64ToString(newProgressFloat)
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", newProgressString)
}
compareModeratorsFunction := func(identityHashA string, identityHashB string)int{
if (identityHashA == identityHashB){
panic("Duplicate moderator identity hashes called during sort.")
}
attributeValueA, attributeValueAExists := moderatorAttributeValuesMap[identityHashA]
attributeValueB, attributeValueBExists := moderatorAttributeValuesMap[identityHashB]
if (attributeValueAExists == false && attributeValueBExists == false){
// We don't know the attribute value for either moderator
// We sort moderators in unicode order
if (identityHashA < identityHashB){
return -1
}
return 1
} else if (attributeValueAExists == true && attributeValueBExists == false){
// We sort unknown attribute moderators 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(currentViewedModeratorsList, compareModeratorsFunction)
err = viewedModeratorsListDatastore.OverwriteList(currentViewedModeratorsList)
if (err != nil) { return err }
err = mySettings.SetSetting("ViewedModeratorsSortedStatus", "Yes")
if (err != nil) { return err }
}
appMemory.SetMemoryEntry("ViewedModeratorsReadyProgressStatus", "1")
err = mySettings.SetSetting("ViewedModeratorsNeedsRefreshYesNo", "No")
if (err != nil) { return err }
return nil
}
updateFunction := func(){
err := updateViewedModerators()
if (err != nil) {
appMemory.SetMemoryEntry("ViewedModeratorsBuildEncounteredError", "Yes")
appMemory.SetMemoryEntry("ViewedModeratorsBuildError", err.Error())
}
updatingModeratorsMutex.Unlock()
}
go updateFunction()
return nil
}
func checkIfModeratorPassesFilters(inputModeratorIdentityHash [16]byte, networkType byte)(bool, error){
//TODO
return true, nil
}
func GetNumberOfActiveModeratorFilters()(int, error){
numberOfActiveFilters := 0
//TODO
return numberOfActiveFilters, nil
}
func SetModeratorFilterOnOffStatus(filterName string, filterStatus bool)error{
filterOnOffStatusString := helpers.ConvertBoolToYesOrNoString(filterStatus)
err := viewedModeratorsFiltersMapDatastore.SetMapEntry(filterName, filterOnOffStatusString)
if (err != nil) { return err }
err = mySettings.SetSetting("ViewedModeratorsGeneratedStatus", "No")
if (err != nil) { return err }
return nil
}
func GetModeratorFilterOnOffStatus(filterName string)(bool, error){
filterStatusExists, currentFilterStatus, err := viewedModeratorsFiltersMapDatastore.GetMapEntry(filterName)
if (err != nil) { return false, err }
if (filterStatusExists == false){
return false, nil
}
filterOnOffStatusBool, err := helpers.ConvertYesOrNoStringToBool(currentFilterStatus)
if (err != nil) { return false, err }
return filterOnOffStatusBool, nil
}