seekia/internal/genetics/myAnalyses/myAnalyses.go

1061 lines
38 KiB
Go

// myAnalyses provides functions to manage genome analyses for People and Couples
// Analyses are created using the createPersonGeneticAnalysis and createCoupleGeneticAnalysis packagees
package myAnalyses
import "seekia/internal/appMemory"
import "seekia/internal/appValues"
import "seekia/internal/encoding"
import "seekia/internal/helpers"
import "seekia/internal/localFilesystem"
import "seekia/internal/myDatastores/myMapList"
import "seekia/internal/genetics/createCoupleGeneticAnalysis"
import "seekia/internal/genetics/createPersonGeneticAnalysis"
import "seekia/internal/genetics/geneticAnalysis"
import "seekia/internal/genetics/prepareRawGenomes"
import "seekia/internal/genetics/readGeneticAnalysis"
import "seekia/internal/genetics/myGenomes"
import "path/filepath"
import "strings"
import "time"
import "sync"
import "errors"
// This will be locked anytime analyses are being added/deleted to the myMapList datastores
var updatingMyAnalysesMutex sync.Mutex
var myPersonAnalysesMapListDatastore *myMapList.MyMapList
var myCoupleAnalysesMapListDatastore *myMapList.MyMapList
// This function must be called whenever an app user signs in
func InitializeMyAnalysesDatastores()error{
updatingMyAnalysesMutex.Lock()
defer updatingMyAnalysesMutex.Unlock()
newMyPersonAnalysesMapListDatastore, err := myMapList.CreateNewMapList("MyPersonAnalyses")
if (err != nil) { return err }
newMyCoupleAnalysesMapListDatastore, err := myMapList.CreateNewMapList("MyCoupleAnalyses")
if (err != nil) { return err }
myPersonAnalysesMapListDatastore = newMyPersonAnalysesMapListDatastore
myCoupleAnalysesMapListDatastore = newMyCoupleAnalysesMapListDatastore
return nil
}
// This function must be called whenever an app user signs in
func CreateMyAnalysesFolder() error{
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return err }
myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
_, err = localFilesystem.CreateFolder(myAnalysesFolderPath)
if (err != nil) { return err }
return nil
}
// This function should be called whenever an app user signs in
func PruneOldAnalyses()error{
updatingMyAnalysesMutex.Lock()
defer updatingMyAnalysesMutex.Unlock()
//TODO: This function should delete analyses we don't need anymore
// These include analyses for people who dont exist anymore, and analyses for people/couples whom have newer analyses
return nil
}
func DeleteAllAnalysesForPerson(personIdentifier string)error{
updatingMyAnalysesMutex.Lock()
defer updatingMyAnalysesMutex.Unlock()
mapToDelete := map[string]string{
"PersonIdentifer": personIdentifier,
}
err := myPersonAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
if (err != nil) { return err }
mapToDelete = map[string]string{
"Person1Identifier": personIdentifier,
}
err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
if (err != nil) { return err }
mapToDelete = map[string]string{
"Person2Identifier": personIdentifier,
}
err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
if (err != nil) { return err }
return nil
}
func DeleteAllAnalysesForCouple(inputPerson1Identifier string, inputPerson2Identifier string)error{
updatingMyAnalysesMutex.Lock()
defer updatingMyAnalysesMutex.Unlock()
person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
if (err != nil) { return err }
mapToDelete := map[string]string{
"Person1Identifier": person1Identifier,
"Person2Identifier": person2Identifier,
}
err = myCoupleAnalysesMapListDatastore.DeleteMapListItems(mapToDelete)
if (err != nil) { return err }
return nil
}
// This function is used to sort the person identifiers, so that each pair of identifiers will only map to a single couple
// Person1 and Person2 will always follow this order within a couple analysis
// The sorting method has no significance
func GetPeopleIdentifiersSortedForCouple(person1Identifier string, person2Identifier string)(string, string, error){
if (person1Identifier == person2Identifier){
return "", "", errors.New("GetPeopleIdentifiersSortedForCouple called with identical person identifiers: " + person1Identifier)
}
if (person1Identifier < person2Identifier){
return person1Identifier, person2Identifier, nil
}
return person2Identifier, person1Identifier, nil
}
//Outputs:
// -bool: Any analysis exists
// -string: Newest analysis identifier
// -int64: Time newest analysis was performed
// -[][16]byte: List of genomes analyzed
// -bool: Newer analysis version available
// -This is not the same as new genomes being available. New version can be for the same genomes.
// -To fully determine if the analysis is up to date, we must check if new (or less) genomes are available to analyse for the person
// -error
func GetPersonNewestGeneticAnalysisInfo(personIdentifier string)(bool, string, int64, [][16]byte, bool, error){
lookupMap := map[string]string{
"PersonIdentifier": personIdentifier,
}
anyItemsFound, analysisItemsMapList, err := myPersonAnalysesMapListDatastore.GetMapListItems(lookupMap)
if (err != nil) { return false, "", 0, nil, false, err }
if (anyItemsFound == false){
return false, "", 0, nil, false, nil
}
// Below is the newest analysis version, which would update if the user had updated their Seekia client
appAnalysisVersion := appValues.GetGeneticAnalysisVersion()
newestAnalysisFound := false
newestAnalysisCreatedTime := int64(0)
newestAnalysisIdentifier := ""
newestAnalysisAnalyzedGenomesList := make([][16]byte, 0)
newerAnalysisVersionAvailable := false
for _, analysisMap := range analysisItemsMapList{
currentPersonIdentifier, exists := analysisMap["PersonIdentifier"]
if (exists == false) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing PersonIdentifier")
}
if (personIdentifier != currentPersonIdentifier){
return false, "", 0, nil, false, errors.New("GetMapListItems returning map with different personIdentifier.")
}
timeCreated, exists := analysisMap["TimeCreated"]
if (exists == false) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing TimeCreated")
}
timeCreatedInt64, err := helpers.ConvertStringToInt64(timeCreated)
if (err != nil) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid timeCreated: " + timeCreated)
}
if (newestAnalysisFound == true && newestAnalysisCreatedTime > timeCreatedInt64){
continue
}
newestAnalysisFound = true
analysisIdentifier, exists := analysisMap["AnalysisIdentifier"]
if (exists == false) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisIdentifier")
}
analyzedGenomes, exists := analysisMap["AnalyzedGenomes"]
if (exists == false) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalyzedGenomes")
}
analyzedGenomesList := strings.Split(analyzedGenomes, "+")
analyzedGenomesArrayList := make([][16]byte, 0)
for _, genomeIdentifierHex := range analyzedGenomesList{
genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
if (err != nil){
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalyzedGenomes: " + analyzedGenomes)
}
analyzedGenomesArrayList = append(analyzedGenomesArrayList, genomeIdentifier)
}
analysisVersion, exists := analysisMap["AnalysisVersion"]
if (exists == false) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisVersion")
}
analysisVersionInt, err := helpers.ConvertStringToInt(analysisVersion)
if (err != nil) {
return false, "", 0, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalysisVersion: " + analysisVersion)
}
if (appAnalysisVersion > analysisVersionInt){
// This analysis is not of the newest analysis version
// The user should run a new analysis on the same genomes to get the newest, most up to date and informative results
newerAnalysisVersionAvailable = true
} else {
newerAnalysisVersionAvailable = false
}
newestAnalysisCreatedTime = timeCreatedInt64
newestAnalysisIdentifier = analysisIdentifier
newestAnalysisAnalyzedGenomesList = analyzedGenomesArrayList
}
if (newestAnalysisFound == false){
return false, "", 0, nil, false, errors.New("GetMapListItems not returning any items when anyItemsFound == true.")
}
return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisAnalyzedGenomesList, newerAnalysisVersionAvailable, nil
}
//Outputs:
// -bool: Analysis exists
// -string: Newest analysis identifier
// -int64: Time newest analysis was performed
// -[][16]byte: Person A list of genomes analyzed
// -[][16]byte: Person B list of genomes analyzed
// -bool: Newer analysis version available
// -This is not the same as new genomes being available. New version can be for the same genomes.
// -To fully determine if the analysis is up to date, we must check if new (or less) genomes are available to analyse for the person
// -error
func GetCoupleNewestGeneticAnalysisInfo(inputPerson1Identifier string, inputPerson2Identifier string)(bool, string, int64, [][16]byte, [][16]byte, bool, error){
person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
if (err != nil) { return false, "", 0, nil, nil, false, err }
lookupMap := map[string]string{
"Person1Identifier": person1Identifier,
"Person2Identifier": person2Identifier,
}
anyItemsFound, analysisItemsMapList, err := myCoupleAnalysesMapListDatastore.GetMapListItems(lookupMap)
if (err != nil) { return false, "", 0, nil, nil, false, err }
if (anyItemsFound == false){
return false, "", 0, nil, nil, false, nil
}
// Below is the newest analysis version, which would update if the user had updated their Seekia client
appAnalysisVersion := appValues.GetGeneticAnalysisVersion()
newestAnalysisFound := false
newestAnalysisCreatedTime := int64(0)
newestAnalysisIdentifier := ""
newestAnalysisPerson1AnalyzedGenomesList := [][16]byte{}
newestAnalysisPerson2AnalyzedGenomesList := [][16]byte{}
newerAnalysisVersionAvailable := false
for _, analysisMap := range analysisItemsMapList{
currentPerson1Identifier, exists := analysisMap["Person1Identifier"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person1Identifier")
}
if (person1Identifier != currentPerson1Identifier){
return false, "", 0, nil, nil, false, errors.New("GetMapListItems returning map with different person1Identifier.")
}
currentPerson2Identifier, exists := analysisMap["Person2Identifier"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person2Identifier")
}
if (person2Identifier != currentPerson2Identifier){
return false, "", 0, nil, nil, false, errors.New("GetMapListItems returning map with different person2Identifier.")
}
timeCreated, exists := analysisMap["TimeCreated"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing TimeCreated")
}
timeCreatedInt64, err := helpers.ConvertStringToInt64(timeCreated)
if (err != nil) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid timeCreated: " + timeCreated)
}
if (newestAnalysisFound == true && newestAnalysisCreatedTime > timeCreatedInt64){
continue
}
newestAnalysisFound = true
analysisIdentifier, exists := analysisMap["AnalysisIdentifier"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisIdentifier")
}
person1AnalyzedGenomes, exists := analysisMap["Person1AnalyzedGenomes"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person1AnalyzedGenomes")
}
person2AnalyzedGenomes, exists := analysisMap["Person2AnalyzedGenomes"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing Person2AnalyzedGenomes")
}
person1AnalyzedGenomesList := strings.Split(person1AnalyzedGenomes, "+")
person2AnalyzedGenomesList := strings.Split(person2AnalyzedGenomes, "+")
person1AnalyzedGenomesArrayList := make([][16]byte, 0)
person2AnalyzedGenomesArrayList := make([][16]byte, 0)
for _, genomeIdentifierHex := range person1AnalyzedGenomesList{
genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
if (err != nil){
return false, "", 0, nil, nil, false, errors.New("Malformed myCoupleAnalysesMapListDatastore: Item contains invalid Person1AnalyzedGenomes: " + person1AnalyzedGenomes)
}
person1AnalyzedGenomesArrayList = append(person1AnalyzedGenomesArrayList, genomeIdentifier)
}
for _, genomeIdentifierHex := range person2AnalyzedGenomesList{
genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
if (err != nil){
return false, "", 0, nil, nil, false, errors.New("Malformed myCoupleAnalysesMapListDatastore: Item contains invalid Person2AnalyzedGenomes: " + person1AnalyzedGenomes)
}
person2AnalyzedGenomesArrayList = append(person2AnalyzedGenomesArrayList, genomeIdentifier)
}
analysisVersion, exists := analysisMap["AnalysisVersion"]
if (exists == false) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item missing AnalysisVersion")
}
analysisVersionInt, err := helpers.ConvertStringToInt(analysisVersion)
if (err != nil) {
return false, "", 0, nil, nil, false, errors.New("Malformed myAnalysesMapList: Item contains invalid AnalysisVersion: " + analysisVersion)
}
if (appAnalysisVersion > analysisVersionInt){
// This analysis is not of the newest analysis version
// The user should run a new analysis on the same genomes to get the newest, most up to date and informative results
newerAnalysisVersionAvailable = true
} else {
newerAnalysisVersionAvailable = false
}
newestAnalysisCreatedTime = timeCreatedInt64
newestAnalysisIdentifier = analysisIdentifier
newestAnalysisPerson1AnalyzedGenomesList = person1AnalyzedGenomesArrayList
newestAnalysisPerson2AnalyzedGenomesList = person2AnalyzedGenomesArrayList
}
if (newestAnalysisFound == false){
return false, "", 0, nil, nil, false, errors.New("GetMapListItems not returning any items when anyItemsFound == true.")
}
// We have to return the person1/Person2 analyzed genomes list in the same order that they came in
if (inputPerson1Identifier == person1Identifier){
// No swapping happened.
return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisPerson1AnalyzedGenomesList, newestAnalysisPerson2AnalyzedGenomesList, newerAnalysisVersionAvailable, nil
}
// We swap the person1/Person2 analyzed genomes lists
return true, newestAnalysisIdentifier, newestAnalysisCreatedTime, newestAnalysisPerson2AnalyzedGenomesList, newestAnalysisPerson1AnalyzedGenomesList, newerAnalysisVersionAvailable, nil
}
// This function should only be used to retrieve an existing analysis
//Outputs:
// -bool: Analysis found
// -geneticAnalysis.PersonAnalysis Analysis object
// -error
func GetPersonGeneticAnalysis(analysisIdentifier string)(bool, geneticAnalysis.PersonAnalysis, error){
analysisFound, analysisFileString, err := getGeneticAnalysisFileString(analysisIdentifier)
if (err != nil) { return false, geneticAnalysis.PersonAnalysis{}, err }
if (analysisFound == false){
return false, geneticAnalysis.PersonAnalysis{}, nil
}
analysisObject, err := readGeneticAnalysis.ReadPersonGeneticAnalysisString(analysisFileString)
if (err != nil) { return false, geneticAnalysis.PersonAnalysis{}, err }
return true, analysisObject, nil
}
// This function should only be used to retrieve an existing analysis
//Outputs:
// -bool: Analysis found
// -geneticAnalysis.CoupleAnalysis Analysis object
// -error
func GetCoupleGeneticAnalysis(analysisIdentifier string)(bool, geneticAnalysis.CoupleAnalysis, error){
analysisFound, analysisFileString, err := getGeneticAnalysisFileString(analysisIdentifier)
if (err != nil) { return false, geneticAnalysis.CoupleAnalysis{}, err }
if (analysisFound == false){
return false, geneticAnalysis.CoupleAnalysis{}, nil
}
analysisObject, err := readGeneticAnalysis.ReadCoupleGeneticAnalysisString(analysisFileString)
if (err != nil) { return false, geneticAnalysis.CoupleAnalysis{}, err }
return true, analysisObject, nil
}
func getGeneticAnalysisFileString(analysisIdentifier string)(bool, string, error){
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return false, "", err }
analysisFileName := analysisIdentifier + ".messagepack"
analysisFilePath := filepath.Join(userDirectory, "MyAnalyses", analysisFileName)
fileExists, fileBytes, err := localFilesystem.GetFileContents(analysisFilePath)
if (err != nil) { return false, "", err }
if (fileExists == false){
return false, "", nil
}
fileString := string(fileBytes)
return true, fileString, nil
}
// This map keeps track of current person analyses being generated
// Map structure: Person identifier -> Process identifier
var personAnalysisProcessesMap map[string]string = make(map[string]string)
var personAnalysisProcessesMapMutex sync.RWMutex
// This map keeps track of current couple analyses being generated
// Map structure: Person1Identifier + "+" + Person2Identifier -> Process identifier
var coupleAnalysisProcessesMap map[string]string = make(map[string]string)
var coupleAnalysisProcessesMapMutex sync.RWMutex
// Returns the process identifier for the current running genetic analyses for the person
// Outputs:
// -bool: Any process found (The process may be complete)
// -string: Process identifier
// -error
func GetPersonGeneticAnalysisProcessIdentifier(personIdentifier string)(bool, string, error){
personAnalysisProcessesMapMutex.RLock()
processIdentifier, exists := personAnalysisProcessesMap[personIdentifier]
personAnalysisProcessesMapMutex.RUnlock()
if (exists == false){
return false, "", nil
}
return true, processIdentifier, nil
}
// Returns the process identifier for the current running genetic analyses for the couple
// Outputs:
// -bool: Any process found (The process may be complete)
// -string: Process identifier
// -error
func GetCoupleGeneticAnalysisProcessIdentifier(inputPerson1Identifier string, inputPerson2Identifier string)(bool, string, error){
person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
if (err != nil) { return false, "", err }
coupleIdentifier := person1Identifier + "+" + person2Identifier
coupleAnalysisProcessesMapMutex.RLock()
processIdentifier, exists := coupleAnalysisProcessesMap[coupleIdentifier]
coupleAnalysisProcessesMapMutex.RUnlock()
if (exists == false){
return false, "", nil
}
return true, processIdentifier, nil
}
//Outputs:
// -bool: Process found
// -bool: Process is complete
// -bool: Process encountered error
// -error: Error encountered by process
// -int: Process percentage complete
// -error
func GetAnalysisProcessInfo(processIdentifier string)(bool, bool, bool, error, int, error){
processExists, processPercentageComplete := appMemory.GetMemoryEntry(processIdentifier + "_ProgressPercentageComplete")
if (processExists == false){
// This should not happen...
return false, false, false, nil, 0, nil
}
exists, errorEncountered := appMemory.GetMemoryEntry(processIdentifier + "_ErrorEncountered")
if (exists == true){
errorEncounteredError := errors.New(errorEncountered)
return true, true, true, errorEncounteredError, 100, nil
}
if (processPercentageComplete == "100"){
return true, true, false, nil, 100, nil
}
processPercentageCompleteInt, err := helpers.ConvertStringToInt(processPercentageComplete)
if (err != nil) {
return false, false, false, nil, 0, errors.New("Invalid processPercentageComplete for genetic analysis process: " + processPercentageComplete)
}
if (processPercentageCompleteInt < 0 || processPercentageCompleteInt > 100){
return false, false, false, nil, 0, errors.New("Invalid processPercentageComplete for genetic analysis process: " + processPercentageComplete)
}
return true, false, false, nil, processPercentageCompleteInt, nil
}
func StopProcess(processIdentifier string){
appMemory.SetMemoryEntry(processIdentifier + "_StopProcess", "Yes")
appMemory.SetMemoryEntry(processIdentifier + "_ProgressPercentageComplete", "100")
}
// This funciton will stop any currently running processes for this person and start generating a new genetic analysis
//Outputs:
// -string: New process identifier
// -error
func StartCreateNewPersonGeneticAnalysis(personIdentifier string)(string, error){
personAnalysisProcessesMapMutex.Lock()
existingProcessIdentifier, exists := personAnalysisProcessesMap[personIdentifier]
if (exists == true){
progressExists, processPercentageComplete := appMemory.GetMemoryEntry(existingProcessIdentifier + "_ProgressPercentageComplete")
if (progressExists == false){
personAnalysisProcessesMapMutex.Unlock()
return "", errors.New("personAnalysisProcessesMap process percentageComplete value not found.")
}
if (processPercentageComplete != "100"){
// There is another process running.
// We will stop the current process for this person
StopProcess(existingProcessIdentifier)
}
}
newProcessIdentifier, err := helpers.GetNewRandomHexString(18)
if (err != nil) { return "", err }
personAnalysisProcessesMap[personIdentifier] = newProcessIdentifier
personAnalysisProcessesMapMutex.Unlock()
updatePercentageCompleteFunction := func(newPercentage int)error{
if (newPercentage < 0 || newPercentage > 100){
return errors.New("Invalid person analysis generation progress percentage.")
}
newPercentageString := helpers.ConvertIntToString(newPercentage)
appMemory.SetMemoryEntry(newProcessIdentifier + "_ProgressPercentageComplete", newPercentageString)
return nil
}
err = updatePercentageCompleteFunction(0)
if (err != nil) { return "", err }
createNewPersonGeneticAnalysis := func()error{
checkIfProcessIsStopped := func()bool{
exists, _ := appMemory.GetMemoryEntry(newProcessIdentifier + "_StopProcess")
if (exists == true){
return true
}
return false
}
personGenomesMapList, err := myGenomes.GetAllPersonGenomesMapList(personIdentifier)
if (err != nil){ return err }
if (len(personGenomesMapList) == 0){
return errors.New("Cannot create person genetic analysis: No genomes found for Person.")
}
genomeIdentifiersList := make([]string, 0, len(personGenomesMapList))
genomesList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0, len(personGenomesMapList))
finalIndex := len(personGenomesMapList) - 1
for index, genomeMap := range personGenomesMapList{
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, index, 0, finalIndex, 0, 10)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(newPercentageProgress)
if (err != nil) { return err }
currentPersonIdentifier, exists := genomeMap["PersonIdentifier"]
if (exists == false) {
return errors.New("PersonGenomesMapList malformed: Item missing PersonIdentifier")
}
if (personIdentifier != currentPersonIdentifier){
return errors.New("GetAllPersonGenomesMapList returning genome for different person.")
}
genomeIdentifierHex, exists := genomeMap["GenomeIdentifier"]
if (exists == false) {
return errors.New("PersonGenomesMapList malformed: Item missing GenomeIdentifier")
}
genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
if (err != nil) { return err }
genomeRawDataString, err := myGenomes.GetGenomeRawDataString(genomeIdentifier)
if (err != nil) { return err }
rawGenomeIsValid, rawGenomeWithMetadata, err := prepareRawGenomes.CreateRawGenomeWithMetadataObject(genomeIdentifier, genomeRawDataString)
if (err != nil) { return err }
if (rawGenomeIsValid == false){
return errors.New("myGenomes contains invalid rawGenomeDataString: " + genomeIdentifierHex)
}
genomesList = append(genomesList, rawGenomeWithMetadata)
genomeIdentifiersList = append(genomeIdentifiersList, genomeIdentifierHex)
}
analysisUpdatePercentageCompleteFunction := func(inputProgress int)error{
newPercentageProgress, err := helpers.ScaleNumberProportionally(true, inputProgress, 0, 100, 10, 10)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(newPercentageProgress)
if (err != nil) { return err }
return nil
}
processCompleted, newGeneticAnalysisString, err := createPersonGeneticAnalysis.CreatePersonGeneticAnalysis(genomesList, analysisUpdatePercentageCompleteFunction, checkIfProcessIsStopped)
if (err != nil) { return err }
if (processCompleted == false){
// User stopped the analysis mid-way
return nil
}
analyzedGenomesListString := strings.Join(genomeIdentifiersList, "+")
analysisIdentifier, err := helpers.GetNewRandomHexString(17)
if (err != nil) { return err }
currentTime := time.Now().Unix()
currentTimeString := helpers.ConvertInt64ToString(currentTime)
currentAnalysisVersion := appValues.GetGeneticAnalysisVersion()
currentAnalysisVersionString := helpers.ConvertIntToString(currentAnalysisVersion)
newAnalysisMap := map[string]string{
"PersonIdentifier": personIdentifier,
"AnalysisIdentifier": analysisIdentifier,
"TimeCreated": currentTimeString,
"AnalyzedGenomes": analyzedGenomesListString,
"AnalysisVersion": currentAnalysisVersionString,
}
updatingMyAnalysesMutex.Lock()
defer updatingMyAnalysesMutex.Unlock()
err = myPersonAnalysesMapListDatastore.AddMapListItem(newAnalysisMap)
if (err != nil) { return err }
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return err }
myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
analysisFileName := analysisIdentifier + ".messagepack"
err = localFilesystem.CreateOrOverwriteFile([]byte(newGeneticAnalysisString), myAnalysesFolderPath, analysisFileName)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(100)
if (err != nil) { return err }
personAnalysisProcessesMapMutex.Lock()
delete(personAnalysisProcessesMap, personIdentifier)
personAnalysisProcessesMapMutex.Unlock()
return nil
}
runAnalysis := func(){
err := createNewPersonGeneticAnalysis()
if (err != nil){
appMemory.SetMemoryEntry(newProcessIdentifier + "_ProgressPercentageComplete", "100")
appMemory.SetMemoryEntry(newProcessIdentifier + "_ErrorEncountered", err.Error())
return
}
}
go runAnalysis()
return newProcessIdentifier, nil
}
// This will stop any currently running processes for this couple and start generating a new genetic analysis
//Outputs:
// -string: New process identifier
// -error
func StartCreateNewCoupleGeneticAnalysis(inputPerson1Identifier string, inputPerson2Identifier string)(string, error){
person1Identifier, person2Identifier, err := GetPeopleIdentifiersSortedForCouple(inputPerson1Identifier, inputPerson2Identifier)
if (err != nil) { return "", err }
coupleIdentifier := person1Identifier + "+" + person2Identifier
coupleAnalysisProcessesMapMutex.Lock()
existingProcessIdentifier, exists := coupleAnalysisProcessesMap[coupleIdentifier]
if (exists == true){
progressExists, processPercentageComplete := appMemory.GetMemoryEntry(existingProcessIdentifier + "_ProgressPercentageComplete")
if (progressExists == false){
coupleAnalysisProcessesMapMutex.Unlock()
return "", errors.New("coupleAnalysisProcessesMap process percentageComplete value not found.")
}
if (processPercentageComplete != "100"){
// There is another process running.
// We will stop the current process for this person
StopProcess(existingProcessIdentifier)
}
}
newCoupleProcessIdentifier, err := helpers.GetNewRandomHexString(18)
if (err != nil) { return "", err }
coupleAnalysisProcessesMap[coupleIdentifier] = newCoupleProcessIdentifier
coupleAnalysisProcessesMapMutex.Unlock()
updatePercentageCompleteFunction := func(newPercentage int)error{
if (newPercentage < 0 || newPercentage > 100){
return errors.New("Invalid couple analysis generation progress percentage.")
}
newPercentageString := helpers.ConvertIntToString(newPercentage)
appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ProgressPercentageComplete", newPercentageString)
return nil
}
err = updatePercentageCompleteFunction(0)
if (err != nil) { return "", err }
createNewCoupleGeneticAnalysis := func()error{
checkIfProcessIsStopped := func()bool{
exists, _ := appMemory.GetMemoryEntry(newCoupleProcessIdentifier + "_StopProcess")
if (exists == true){
return true
}
return false
}
// We need both person1 and person2 to have an analysis before performing the couple analysis
// We will see if an analysis is already running for each person
// We will either start a new analysis for each person or monitor the existing one until it is done
personIdentifiersList := []string{person1Identifier, person2Identifier}
for index, personIdentifier := range personIdentifiersList{
// This function will return the percentage progress that this person's analysis will use of the entire couple analysis
getPercentageRangeForPersonAnalysis := func()(int, int){
if (index == 0){
return 0, 33
}
return 33, 66
}
personPercentageRangeStart, personPercentageRangeEnd := getPercentageRangeForPersonAnalysis()
// Outputs:
// -bool: Person needs update
// -string: Person process identifier
// -error
getPersonProcessIdentifier := func()(bool, string, error){
runningProcessFound, runningProcessIdentifier, err := GetPersonGeneticAnalysisProcessIdentifier(personIdentifier)
if (err != nil) { return false, "", err }
if (runningProcessFound == true){
// This person has a running analysis. We will show the progress in our current process
return true, runningProcessIdentifier, nil
}
// We check if the person needs a new analysis.
getPersonNewAnalysisNeededBool := func()(bool, error){
anyPersonAnalysisFound, _, _, newestPersonAnalysisListOfGenomesAnalyzed, newerAnalysisVersionAvailable, err := GetPersonNewestGeneticAnalysisInfo(personIdentifier)
if (err != nil) { return false, err }
if (anyPersonAnalysisFound == false){
// No analysis exists. We need to create a new analysis
return true, nil
}
if (newerAnalysisVersionAvailable == true){
// New analysis version is available.
return true, nil
}
allPersonRawGenomeIdentifiersList, err := myGenomes.GetAllPersonRawGenomeIdentifiersList(person1Identifier)
if (err != nil) { return false, err }
genomesAreIdentical := helpers.CheckIfTwoListsContainIdenticalItems(allPersonRawGenomeIdentifiersList, newestPersonAnalysisListOfGenomesAnalyzed)
if (genomesAreIdentical == false){
// New genome exists/a genome was deleted. A new analysis is needed
return true, nil
}
// Analysis is up to date. Nothing to do.
return false, nil
}
personNewAnalysisIsNeeded, err := getPersonNewAnalysisNeededBool()
if (err != nil) { return false, "", err }
if (personNewAnalysisIsNeeded == false){
return false, "", nil
}
// We start a new analysis
newProcessIdentifier, err := StartCreateNewPersonGeneticAnalysis(personIdentifier)
if (err != nil) { return false, "", err }
return true, newProcessIdentifier, nil
}
personNeedsUpdate, personProcessIdentifier, err := getPersonProcessIdentifier()
if (err != nil) { return err }
if (personNeedsUpdate == false){
// No analysis needed
err := updatePercentageCompleteFunction(personPercentageRangeEnd)
if (err != nil) { return err }
continue
}
for{
processFound, processIsComplete, processEncounteredError, errorEncounteredByProcess, processPercentageComplete, err := GetAnalysisProcessInfo(personProcessIdentifier)
if (err != nil){ return err }
if (processFound == false){
return errors.New("Person process not found after being found already.")
}
if (processIsComplete == true){
if (processEncounteredError == true){
return errorEncounteredByProcess
}
err := updatePercentageCompleteFunction(personPercentageRangeEnd)
if (err != nil) { return err }
break
}
personPercentageComplete, err := helpers.ScaleNumberProportionally(true, processPercentageComplete, 0, 100, personPercentageRangeStart, personPercentageRangeEnd)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(personPercentageComplete)
if (err != nil) { return err }
time.Sleep(100 * time.Millisecond)
}
}
// Both people's analyses are complete.
// The percentage progress is at 66%
// Now we perform the couple analysis
//Outputs:
// -[]prepareRawGenomes.RawGenomeWithMetadata: Genome Identifier -> Genome raw data string
// -[]string: Person raw genome identifiers list
// -error
getPersonGenomesList := func(personIdentifier string)([]prepareRawGenomes.RawGenomeWithMetadata, []string, error){
personGenomesMapList, err := myGenomes.GetAllPersonGenomesMapList(personIdentifier)
if (err != nil){ return nil, nil, err }
if (len(personGenomesMapList) == 0){
return nil, nil, errors.New("Cannot create person genetic analysis: No genomes found for Person.")
}
genomeIdentifiersList := make([]string, 0, len(personGenomesMapList))
// Map structure: Genome identifier -> Genome raw data string
genomesList := make([]prepareRawGenomes.RawGenomeWithMetadata, 0, len(personGenomesMapList))
for _, genomeMap := range personGenomesMapList{
currentPersonIdentifier, exists := genomeMap["PersonIdentifier"]
if (exists == false) {
return nil, nil, errors.New("PersonGenomesMapList malformed: Item missing PersonIdentifier")
}
if (personIdentifier != currentPersonIdentifier){
return nil, nil, errors.New("GetAllPersonGenomesMapList returning genome for different person.")
}
genomeIdentifierHex, exists := genomeMap["GenomeIdentifier"]
if (exists == false) {
return nil, nil, errors.New("PersonGenomesMapList malformed: Item missing GenomeIdentifier")
}
genomeIdentifier, err := encoding.DecodeHexStringTo16ByteArray(genomeIdentifierHex)
if (err != nil) { return nil, nil, err }
genomeRawDataString, err := myGenomes.GetGenomeRawDataString(genomeIdentifier)
if (err != nil) { return nil, nil, err }
rawGenomeIsValid, rawGenomeWithMetadata, err := prepareRawGenomes.CreateRawGenomeWithMetadataObject(genomeIdentifier, genomeRawDataString)
if (err != nil) { return nil, nil, err }
if (rawGenomeIsValid == false){
return nil, nil, errors.New("myGenomes contains invalid rawGenomeDataString: " + genomeIdentifierHex)
}
genomesList = append(genomesList, rawGenomeWithMetadata)
genomeIdentifiersList = append(genomeIdentifiersList, genomeIdentifierHex)
}
return genomesList, genomeIdentifiersList, nil
}
person1GenomesList, person1RawGenomeIdentifiersList, err := getPersonGenomesList(person1Identifier)
if (err != nil) { return err }
updatePercentageCompleteFunction(70)
person2GenomesList, person2RawGenomeIdentifiersList, err := getPersonGenomesList(person2Identifier)
if (err != nil) { return err }
updatePercentageCompleteFunction(74)
updateCoupleAnalysisPercentageCompleteFunction := func(newPercentage int)error{
personPercentageComplete, err := helpers.ScaleNumberProportionally(true, newPercentage, 0, 100, 74, 100)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(personPercentageComplete)
if (err != nil) { return err }
return nil
}
processCompleted, newGeneticAnalysisString, err := createCoupleGeneticAnalysis.CreateCoupleGeneticAnalysis(person1GenomesList, person2GenomesList, updateCoupleAnalysisPercentageCompleteFunction, checkIfProcessIsStopped)
if (err != nil) { return err }
if (processCompleted == false){
// User stopped the analysis mid-way
return nil
}
person1AnalyzedGenomesListString := strings.Join(person1RawGenomeIdentifiersList, "+")
person2AnalyzedGenomesListString := strings.Join(person2RawGenomeIdentifiersList, "+")
analysisIdentifier, err := helpers.GetNewRandomHexString(17)
if (err != nil) { return err }
currentTime := time.Now().Unix()
currentTimeString := helpers.ConvertInt64ToString(currentTime)
currentAnalysisVersion := appValues.GetGeneticAnalysisVersion()
currentAnalysisVersionString := helpers.ConvertIntToString(currentAnalysisVersion)
newAnalysisMap := map[string]string{
"Person1Identifier": person1Identifier,
"Person2Identifier": person2Identifier,
"AnalysisIdentifier": analysisIdentifier,
"TimeCreated": currentTimeString,
"Person1AnalyzedGenomes": person1AnalyzedGenomesListString,
"Person2AnalyzedGenomes": person2AnalyzedGenomesListString,
"AnalysisVersion": currentAnalysisVersionString,
}
updatingMyAnalysesMutex.Lock()
defer updatingMyAnalysesMutex.Unlock()
err = myCoupleAnalysesMapListDatastore.AddMapListItem(newAnalysisMap)
if (err != nil) { return err }
userDirectory, err := localFilesystem.GetAppUserFolderPath()
if (err != nil) { return err }
myAnalysesFolderPath := filepath.Join(userDirectory, "MyAnalyses")
analysisFileName := analysisIdentifier + ".messagepack"
err = localFilesystem.CreateOrOverwriteFile([]byte(newGeneticAnalysisString), myAnalysesFolderPath, analysisFileName)
if (err != nil) { return err }
err = updatePercentageCompleteFunction(100)
if (err != nil) { return err }
coupleAnalysisProcessesMapMutex.Lock()
delete(coupleAnalysisProcessesMap, coupleIdentifier)
coupleAnalysisProcessesMapMutex.Unlock()
return nil
}
runAnalysis := func(){
err := createNewCoupleGeneticAnalysis()
if (err != nil){
appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ProgressPercentageComplete", "100")
appMemory.SetMemoryEntry(newCoupleProcessIdentifier + "_ErrorEncountered", err.Error())
return
}
}
go runAnalysis()
return newCoupleProcessIdentifier, nil
}