1060 lines
38 KiB
Go
1060 lines
38 KiB
Go
|
|
// 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
|
|
}
|
|
|
|
|
|
|