seekia/internal/network/viewedHosts/viewedHosts.go

570 lines
18 KiB
Go
Raw Normal View History

// viewedHosts provides functions to generate and retrieve the viewedHosts list
// This list is used to browse hosts on the View Hosts page
package viewedHosts
import "seekia/internal/appMemory"
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/network/enabledHosts"
import "seekia/internal/profiles/viewableProfiles"
import "slices"
import "sync"
import "errors"
// This mutex will be locked whenever the viewed hosts list is being updated
var updatingViewedHostsMutex sync.Mutex
var viewedHostsListDatastore *myList.MyList
var viewedHostsFiltersMapDatastore *myMap.MyMap
// This function must be called whenever an app user signs in
func InitializeViewedHostsDatastores()error{
updatingViewedHostsMutex.Lock()
defer updatingViewedHostsMutex.Unlock()
newViewedHostsListDatastore, err := myList.CreateNewList("ViewedHosts")
if (err != nil) { return err }
newViewedHostsFiltersMapDatastore, err := myMap.CreateNewMap("ViewedHostsFilters")
if (err != nil) { return err }
viewedHostsListDatastore = newViewedHostsListDatastore
viewedHostsFiltersMapDatastore = newViewedHostsFiltersMapDatastore
return nil
}
func GetViewedHostsSortByAttribute()(string, error){
exists, currentAttribute, err := mySettings.GetSetting("ViewedHostsSortByAttribute")
if (err != nil) { return "", err }
if (exists == false){
return "BanAdvocates", nil
}
return currentAttribute, nil
}
func GetViewedHostsSortDirection()(string, error){
exists, sortDirection, err := mySettings.GetSetting("ViewedHostsSortDirection")
if (err != nil) { return "", err }
if (exists == false){
return "Descending", nil
}
if (sortDirection != "Ascending" && sortDirection != "Descending"){
return "", errors.New("MySettings contains invalid ViewedHostsSortDirection: " + sortDirection)
}
return sortDirection, nil
}
// Will need a refresh any time a new host profile is downloaded
func CheckIfViewedHostsNeedsRefresh()(bool, error){
exists, needsRefresh, err := mySettings.GetSetting("ViewedHostsNeedsRefreshYesNo")
if (err != nil) { return true, err }
if (exists == true && needsRefresh == "No") {
return false, nil
}
return true, nil
}
func GetViewedHostsAreReadyStatus(networkType byte)(bool, error){
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return false, errors.New("GetViewedHostsAreReadyStatus called with invalid networkType: " + networkTypeString)
}
exists, hostsGeneratedStatus, err := mySettings.GetSetting("ViewedHostsGeneratedStatus")
if (err != nil) { return false, err }
if (exists == false || hostsGeneratedStatus != "Yes"){
return false, nil
}
exists, hostsSortedStatus, err := mySettings.GetSetting("ViewedHostsSortedStatus")
if (err != nil) { return false, err }
if (exists == false || hostsSortedStatus != "Yes"){
return false, nil
}
exists, viewedHostsNetworkTypeString, err := mySettings.GetSetting("ViewedHostsNetworkType")
if (err != nil) { return false, err }
if (exists == false){
// This should not happen, because ViewedHostsNetworkType is created whenever viewedHosts are generated
return false, errors.New("mySettings missing ViewedHostsNetworkType when ViewedHostsGeneratedStatus exists.")
}
viewedHostsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(viewedHostsNetworkTypeString)
if (err != nil) {
return false, errors.New("mySettings contains invalid ViewedHostsNetworkType: " + viewedHostsNetworkTypeString)
}
if (viewedHostsNetworkType != networkType){
// ViewedHosts were generated for a different networkType
// This should never happen, because we will always set ViewedHostsGeneratedStatus to No when we switch network types,
// and we will always call the GetViewedHostsAreReadyStatus function with the current appNetworkType
//TODO: Log this.
err := mySettings.SetSetting("ViewedHostsGeneratedStatus", "No")
if (err != nil) { return false, err }
return false, nil
}
return true, nil
}
// Outputs:
// -bool: Viewed hosts list is ready
// -[][16]byte: Viewed host identity hashes list
// -error
func GetViewedHostsList(networkType byte)(bool, [][16]byte, error){
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return false, nil, errors.New("GetViewedHostsList called with invalid networkType: " + networkTypeString)
}
areReady, err := GetViewedHostsAreReadyStatus(networkType)
if (err != nil) { return false, nil, err }
if (areReady == false){
return false, nil, nil
}
readyHostIdentityHashesList, err := viewedHostsListDatastore.GetList()
if (err != nil) { return false, nil, err }
viewedHostsList := make([][16]byte, 0, len(readyHostIdentityHashesList))
for _, hostIdentityHashString := range readyHostIdentityHashesList{
hostIdentityHash, identityType, err := identity.ReadIdentityHashString(hostIdentityHashString)
if (err != nil){
return false, nil, errors.New("viewedHostsListDatastore contains invalid identity hash: " + hostIdentityHashString)
}
if (identityType != "Host"){
return false, nil, errors.New("viewedHostsListDatastore contains non-Host identity hash: " + identityType)
}
viewedHostsList = append(viewedHostsList, hostIdentityHash)
}
return true, viewedHostsList, nil
}
// This function returns the number of viewed hosts
// It can be called before the list is ready (after being generated, but before being sorted)
func GetNumberOfGeneratedViewedHosts()(int, error){
currentViewedHostsList, err := viewedHostsListDatastore.GetList()
if (err != nil) { return 0, err }
lengthInt := len(currentViewedHostsList)
return lengthInt, nil
}
//Outputs:
// -bool: Build encountered error
// -string: Error encountered
// -bool: Build is stopped (will be stopped if user went to different page)
// -bool: Viewed hosts are ready
// -float64: Progress status (0 - 1)
// -error
func GetViewedHostsBuildStatus(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("GetViewedHostsBuildStatus called with invalid networkType: " + networkTypeString)
}
exists, encounteredError := appMemory.GetMemoryEntry("ViewedHostsBuildEncounteredError")
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("ViewedHostsBuildError")
if (exists == false){
return false, "", false, false, 0, errors.New("Viewed Hosts build encountered error is yes, but no error exists.")
}
return true, errorEncountered, false, false, 0, nil
}
isStopped := CheckIfBuildViewedHostsIsStopped()
if (isStopped == true){
return false, "", true, false, 0, nil
}
hostsAreReadyBool, err := GetViewedHostsAreReadyStatus(networkType)
if (err != nil) { return false, "", false, false, 0, err }
if (hostsAreReadyBool == true){
return false, "", false, true, 1, nil
}
exists, currentPercentageString := appMemory.GetMemoryEntry("ViewedHostsReadyProgressStatus")
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("ViewedHostsReadyProgressStatus is not a float: " + currentPercentageString)
}
return false, "", false, false, currentPercentageFloat, nil
}
func CheckIfBuildViewedHostsIsStopped()bool{
exists, buildStoppedStatus := appMemory.GetMemoryEntry("StopBuildViewedHostsYesNo")
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 hosts
func StartUpdatingViewedHosts(networkType byte)error{
isValid := helpers.VerifyNetworkType(networkType)
if (isValid == false){
networkTypeString := helpers.ConvertByteToString(networkType)
return errors.New("StartUpdatingViewedHosts called with invalid networkType: " + networkTypeString)
}
appMemory.SetMemoryEntry("StopBuildViewedHostsYesNo", "Yes")
// We wait for any existing build to stop
updatingViewedHostsMutex.Lock()
appMemory.SetMemoryEntry("ViewedHostsBuildEncounteredError", "No")
appMemory.SetMemoryEntry("ViewedHostsBuildError", "")
appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", "0")
appMemory.SetMemoryEntry("StopBuildViewedHostsYesNo", "No")
updateViewedHosts := func()error{
getViewedHostsNeedToBeGeneratedStatus := func()(bool, error){
exists, hostsGeneratedStatus, err := mySettings.GetSetting("ViewedHostsGeneratedStatus")
if (err != nil) { return false, err }
if (exists == false || hostsGeneratedStatus != "Yes"){
return true, nil
}
exists, viewedHostsNetworkTypeString, err := mySettings.GetSetting("ViewedHostsNetworkType")
if (err != nil) { return false, err }
if (exists == false){
// This should not happen, because ViewedHostsNetworkType is set to Yes whenever viewed hosts are generated
return false, errors.New("ViewedHostsNetworkType missing when ViewedHostsGeneratedStatus exists.")
}
viewedHostsNetworkType, err := helpers.ConvertNetworkTypeStringToByte(viewedHostsNetworkTypeString)
if (err != nil) {
return false, errors.New("mySettings contains invalid ViewedHostsNetworkType: " + viewedHostsNetworkTypeString)
}
if (viewedHostsNetworkType != networkType){
// This should not happen, because ViewedHostsGeneratedStatus should be set to No whenever app network type is changed,
// and StartUpdatingViewedHosts should only be called with the current app network type.
return true, nil
}
return false, nil
}
viewedHostsNeedToBeGenerated, err := getViewedHostsNeedToBeGeneratedStatus()
if (err != nil) { return err }
if (viewedHostsNeedToBeGenerated == true){
err := mySettings.SetSetting("ViewedHostsSortedStatus", "No")
if (err != nil) { return err }
enabledHostsList, err := enabledHosts.GetEnabledHostsList(false, networkType)
if (err != nil) { return err }
numberOfEnabledHosts := len(enabledHostsList)
generatedHostIdentityHashesList := make([]string, 0)
for index, hostIdentityHash := range enabledHostsList{
hostPassesFilters, err := checkIfHostPassesFilters(hostIdentityHash)
if (err != nil) { return err }
if (hostPassesFilters == true){
hostIdentityHashString, identityType, err := identity.EncodeIdentityHashBytesToString(hostIdentityHash)
if (err != nil) {
hostIdentityHashHex := encoding.EncodeBytesToHexString(hostIdentityHash[:])
return errors.New("enabledHostsList contains invalid identity hash: " + hostIdentityHashHex)
}
if (identityType != "Host"){
return errors.New("enabledHostsList contains non-host identity hash: " + identityType)
}
generatedHostIdentityHashesList = append(generatedHostIdentityHashesList, hostIdentityHashString)
}
isStopped := CheckIfBuildViewedHostsIsStopped()
if (isStopped == true){
return nil
}
progressPercentage, err := helpers.ScaleNumberProportionally(true, index, 0, numberOfEnabledHosts-1, 0, 50)
if (err != nil) { return err }
progressFloat := float64(progressPercentage)/100
hostsReadyProgressPercentage := helpers.ConvertFloat64ToString(progressFloat)
appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", hostsReadyProgressPercentage)
}
err = viewedHostsListDatastore.OverwriteList(generatedHostIdentityHashesList)
if (err != nil) { return err }
err = mySettings.SetSetting("ViewedHostsGeneratedStatus", "Yes")
if (err != nil) { return err }
networkTypeString := helpers.ConvertByteToString(networkType)
err = mySettings.SetSetting("ViewedHostsNetworkType", networkTypeString)
if (err != nil) { return err }
}
appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", "0.50")
isStopped := CheckIfBuildViewedHostsIsStopped()
if (isStopped == true){
return nil
}
hostsReadyStatus, err := GetViewedHostsAreReadyStatus(networkType)
if (err != nil) { return err }
if (hostsReadyStatus == false){
// Now we sort hosts
currentSortByAttribute, err := GetViewedHostsSortByAttribute()
if (err != nil) { return err }
currentSortDirection, err := GetViewedHostsSortDirection()
if (err != nil) { return err }
currentViewedHostsList, err := viewedHostsListDatastore.GetList()
if (err != nil) { return err }
// We use this map to make sure there are no duplicate hosts
// This should never happen, unless the user's stored list was edited or there is a bug
allHostsMap := make(map[[16]byte]struct{})
// Map structure: Host Identity Hash -> Sort By Attribute Value
hostAttributeValuesMap := make(map[string]float64)
maximumIndex := len(currentViewedHostsList) - 1
for index, hostIdentityHashString := range currentViewedHostsList{
hostIdentityHash, identityType, err := identity.ReadIdentityHashString(hostIdentityHashString)
if (err != nil){
return errors.New("currentViewedHostsList contains invalid identity hash: " + hostIdentityHashString)
}
if (identityType != "Host"){
return errors.New("currentViewedHostsList contains invalid non-Host identity: " + identityType)
}
_, exists := allHostsMap[hostIdentityHash]
if (exists == true){
return errors.New("currentViewedHostsList contains duplicate host identity hash.")
}
allHostsMap[hostIdentityHash] = struct{}{}
profileExists, _, attributeExists, attributeValue, err := viewableProfiles.GetAnyAttributeFromNewestViewableUserProfile(hostIdentityHash, 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 hosts attribute cannot be converted to float.")
}
hostAttributeValuesMap[hostIdentityHashString] = attributeValueFloat
}
isStopped := CheckIfBuildViewedHostsIsStopped()
if (isStopped == true){
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("ViewedHostsReadyProgressStatus", newProgressString)
}
compareHostsFunction := func(identityHashA string, identityHashB string)int{
if (identityHashA == identityHashB){
panic("compareHostsFunction called with duplicate hosts.")
}
attributeValueA, attributeValueAExists := hostAttributeValuesMap[identityHashA]
attributeValueB, attributeValueBExists := hostAttributeValuesMap[identityHashB]
if (attributeValueAExists == false && attributeValueBExists == false){
// We don't know the attribute value for either host
// We sort hosts in unicode order
if (identityHashA < identityHashB){
return -1
}
return 1
} else if (attributeValueAExists == true && attributeValueBExists == false){
// We sort unknown attribute hosts 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(currentViewedHostsList, compareHostsFunction)
err = viewedHostsListDatastore.OverwriteList(currentViewedHostsList)
if (err != nil) { return err }
err = mySettings.SetSetting("ViewedHostsSortedStatus", "Yes")
if (err != nil) { return err }
}
appMemory.SetMemoryEntry("ViewedHostsReadyProgressStatus", "1")
err = mySettings.SetSetting("ViewedHostsNeedsRefreshYesNo", "No")
if (err != nil) { return err }
return nil
}
updateFunction := func(){
err := updateViewedHosts()
if (err != nil){
appMemory.SetMemoryEntry("ViewedHostsBuildEncounteredError", "Yes")
appMemory.SetMemoryEntry("ViewedHostsBuildError", err.Error())
}
updatingViewedHostsMutex.Unlock()
}
go updateFunction()
return nil
}
func checkIfHostPassesFilters(inputHostIdentityHash [16]byte)(bool, error){
//TODO
return true, nil
}
func GetNumberOfActiveHostFilters()(int, error){
numberOfActiveFilters := 0
//TODO
return numberOfActiveFilters, nil
}
func SetHostFilterOnOffStatus(filterName string, filterOnOffStatus bool)error{
filterOnOffStatusString := helpers.ConvertBoolToYesOrNoString(filterOnOffStatus)
err := viewedHostsFiltersMapDatastore.SetMapEntry(filterName, filterOnOffStatusString)
if (err != nil) { return err }
err = mySettings.SetSetting("ViewedHostsGeneratedStatus", "No")
if (err != nil) { return err }
return nil
}
func GetHostFilterOnOffStatus(filterName string)(bool, error){
filterStatusExists, currentFilterStatus, err := viewedHostsFiltersMapDatastore.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
}