// myAnalyses provides functions to manage genome analyses for People and Couples // Analyses are created using the createGeneticAnalysis package 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/createGeneticAnalysis" 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 := createGeneticAnalysis.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 := createGeneticAnalysis.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 }