// 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.ScaleIntProportionally(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.ScaleIntProportionally(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(identityHash1 string, identityHash2 string)int{ if (identityHash1 == identityHash2){ panic("compareHostsFunction called with duplicate hosts.") } attributeValue1, attributeValue1Exists := hostAttributeValuesMap[identityHash1] attributeValue2, attributeValue2Exists := hostAttributeValuesMap[identityHash2] if (attributeValue1Exists == false && attributeValue2Exists == false){ // We don't know the attribute value for either host // We sort hosts in unicode order if (identityHash1 < identityHash2){ return -1 } return 1 } else if (attributeValue1Exists == true && attributeValue2Exists == false){ // We sort unknown attribute hosts to the back of the list return -1 } else if (attributeValue1Exists == false && attributeValue2Exists == true){ return 1 } // Both attribute values exist if (attributeValue1 == attributeValue2){ // We sort identity hashes in unicode order if (identityHash1 < identityHash2){ return -1 } return 1 } if (attributeValue1 < attributeValue2){ 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 }