diff --git a/Config/DefaultDTFluxAPI.ini b/Config/DefaultDTFluxAPI.ini index ed9b8f0..a6e6533 100644 --- a/Config/DefaultDTFluxAPI.ini +++ b/Config/DefaultDTFluxAPI.ini @@ -1,3 +1,4 @@ [CoreRedirects] +ClassRedirects=(OldName="/Script/DTFluxAPI.DTHttpServerObject",NewName="/Script/DTFluxAPI.DTFluxHttpServerObject") -+StructRedirects=(OldName="/Script/DTFluxAPI.DTHttpServerParams",NewName="/Script/DTFluxAPI.DTFluxHttpServerParams") \ No newline at end of file ++StructRedirects=(OldName="/Script/DTFluxAPI.DTHttpServerParams",NewName="/Script/DTFluxAPI.DTFluxHttpServerParams") ++ClassRedirects=(OldName="/Script/DTFluxAPI.MyClass",NewName="/Script/DTFluxAPI.DTFluxDataStorage") \ No newline at end of file diff --git a/DOCS.md b/DOCS.md new file mode 100644 index 0000000..5d776e4 --- /dev/null +++ b/DOCS.md @@ -0,0 +1,75 @@ +# DTFluxAPI plugin doc + +## DESCRIPTION + +This document describe the usage of the plugin. + +## USAGE +### Plugin Project Settings + +#### Race Result API + +- Race Result Port (int) : Port of the Race Result API (Local Server ONLY) +- Race Result Url (FString) : Race Result API URL (Local Server ONLY) +- Access Is Local (bool) : Race Result API only in local Mode (prevent polling limits) +- RaceResultSessionID (FString) : Race result session ID +- Start List Access Token (FString) : Token to access Start List endpoint +- General Classification Access Token (FString) : Token to access General Classification endpoint +- Live Stage Results Access Token (FString) : Token to access Live Stage Results endpoint + +#### Chrono Proxy + +- ProxyAddress +- ProxyRootPath +- ProxyPort + +#### Server Config + +- InPort (int) : Listening port of the embedded server +- Endpoints (FString[] ) : Array of endpoints to be served (Wil be modified in the future) + +#### Objects provided + +- EDTFluxProxyRoute : Routes type for the Proxy +- EDTFluxAPIRoute : Routes type for the API Race Result +- FSearchFilters : struct that contain a ContestId, a StageId and a gender to filter api searches + +#### Functions provided + +All the function provided by the Project Settings are available both in blueprint and in c++ + +- FString GetAPIPath(APIRouteType, FString Filters ) +- GetAPIPathFiltered(APIRouteType, FSearchFilters Filters ) +- GetProxyPath( ProxyRouteType, int ContestId, int StageId) + + +### Model + +### DataStorage + + +#### Public Data + +##### Collections + +###### Chrono + +- Chrono of each stage (count down) +- Current Stage and current contest + +###### Participant + +- Current progression (pourcentage done in stage -> number of checkpoints done) +- + + + + +### Subsystem + + +##### Delegates : + +- + +- diff --git a/Source/DTFluxAPI/DTFluxAPI.Build.cs b/Source/DTFluxAPI/DTFluxAPI.Build.cs index 771917a..7bbf7f7 100644 --- a/Source/DTFluxAPI/DTFluxAPI.Build.cs +++ b/Source/DTFluxAPI/DTFluxAPI.Build.cs @@ -11,7 +11,7 @@ public class DTFluxAPI : ModuleRules PublicDependencyModuleNames.AddRange( new string[] { - "Core" + "Core", } ); @@ -24,6 +24,9 @@ public class DTFluxAPI : ModuleRules "SlateCore", "HTTPServer", "HTTP", + "Networking", + "WebSockets", + "WebSocketNetworking", "DeveloperToolSettings", "DeveloperSettings", "Json", diff --git a/Source/DTFluxAPI/Private/DTFluxDataStorage/DTFluxDataStorage.cpp b/Source/DTFluxAPI/Private/DTFluxDataStorage/DTFluxDataStorage.cpp new file mode 100644 index 0000000..e864580 --- /dev/null +++ b/Source/DTFluxAPI/Private/DTFluxDataStorage/DTFluxDataStorage.cpp @@ -0,0 +1,331 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "DTFluxDataStorage/DTFluxDataStorage.h" + +// #include "AsyncTreeDifferences.h" +#include "DTFluxAPILog.h" +#include "DTFluxModel/DTFluxModel.h" + +bool UDTFluxDataStorage::UpdateDataStorage(const FString JsonPayload) +{ + UE_LOG(LogDTFluxAPI, Log, TEXT("UDPATE DataStorage with data : %s"), *JsonPayload); + // Single json value (any of supported json types e.g. + // object with properties, array, bool) at top level of json + TSharedPtr JsonValue; + + // Create a reader pointer to read the json data + TSharedRef> Reader = TJsonReaderFactory<>::Create(JsonPayload); + if (FJsonSerializer::Deserialize(Reader, JsonValue)) { + // Get the value of the json object by field name + TSharedPtr Json = JsonValue->AsObject(); + FString Type = Json->GetStringField(TEXT("type")); + TArray> Datas = Json->GetArrayField(TEXT("datas")); + // UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : %s"), *Type); + if(Type.Contains("race-datas")) + { + UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"race-datas\"")); + // Contests is empty; + if(Contests.Num() == 0) + { + FDTFluxContest Contest; + for(const auto& Data : Datas) + { + TSharedPtr ContestData = Data->AsObject(); + Contest.Id = ContestData->GetIntegerField(TEXT("id")); + Contest.Name = ContestData->GetStringField(TEXT("name")); + Contest.SetDate(ContestData->GetStringField(TEXT("date"))); + TArray Splits; + TArray> SplitDatas = ContestData->GetArrayField(TEXT("splits")); + for(const auto& SplitData : SplitDatas) + { + FDTFluxSplit Split; + Split.Id = SplitData->AsObject()->GetIntegerField(TEXT("id")); + Split.Name = SplitData->AsObject()->GetStringField(TEXT("name")); + Splits.Add(Split); + } + TArray> StagesData = ContestData->GetArrayField(TEXT("stages")); + Contest.AddStage(StagesData, Splits); + } + } + + } + else if(Type.Contains("team-list")) + { + UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"team-list\"")); + + } + else if(Type.Contains("contest-ranking")) + { + int ContestId = Json->GetIntegerField(TEXT("contestID")); + UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"contest-ranking\"")); + + } + else if(Type.Contains("stage-ranking")) + { + int ContestId = Json->GetIntegerField(TEXT("contestID")); + int StageId = Json->GetIntegerField(TEXT("stageID")); + int SplitID = -1; + if(Json->HasField(TEXT("splitID"))) + { + SplitID = Json->GetIntegerField(TEXT("splitID")); + if(SplitID == -1) + { + // we have all splits gaps from the request + } + } + UE_LOG(LogDTFluxAPI, Log, TEXT("DTFlux-Response-Type : \"stage-ranking\"")); + + }else if(Type.Contains("split-sensor")) + { + UE_LOG(LogDTFluxAPI, Log, TEXT("split-sensor received")); + // Request New Ranking + // + } + + } + return true; +} + + + +TArray UDTFluxDataStorage::GetStages(const int ContestId) +{ + TArray Stages; + for(const auto& Contest : Contests) + { + Stages.Append(Contest.Stages); + } + return Stages; +} + +bool UDTFluxDataStorage::GetContest(FDTFluxContest& OutContest, const int& ContestId) +{ + // Current contest requested + if(ContestId == -1) + { + FDateTime Now = FDateTime::Now(); + for(auto& Contest : Contests) + { + for( auto& Stage : Contest.Stages) + { + if(Stage.StartTime >= Now && Stage.EndTime <= Now) + { + //We have a winner + OutContest = Contest; + return true; + } + } + } + } + else + { + for( auto& Contest : Contests) + { + if(Contest.Id == ContestId) + { + OutContest = Contest; + return true; + } + } + } + return false; +} + +bool UDTFluxDataStorage::GetStage(FDTFluxStage& CurrentStage, const int& StageId) +{ + // Current contest requested + if(StageId == -1) + { + FDateTime Now = FDateTime::Now(); + for(auto& Contest : Contests) + { + for( auto& Stage : Contest.Stages) + { + if(StageId <= -1) + { + if(Stage.StartTime >= Now && Stage.EndTime <= Now) + { + //We have a winner for current stage + CurrentStage = Stage; + return true; + } + }else + { + if(Stage.Id == StageId) + { + //We have a winner for the search stage + CurrentStage = Stage; + return true; + } + } + + } + } + } + + return false; +} + +TArray UDTFluxDataStorage::GetParticipants(const int ContestId) +{ + TArray Participants; + for(const auto& Contest : Contests) + { + if (ContestId <= -1) + { + Participants.Append(Contest.Participants); + } + else if(ContestId == Contest.Id) + { + Participants.Append(Contest.Participants); + } + + } + return Participants; +} + +FDTFluxParticipant UDTFluxDataStorage::GetParticipant(const int ContestID, const int ParticipantBib) +{ + return GetParticipants(ContestID)[ParticipantBib]; +} + +TArray UDTFluxDataStorage::GetStageRanking(const int ContestId, const int StageId) +{ + if(Contests.Num() > (ContestId -1)) + { + FDTFluxContest Contest = Contests[ContestId - 1]; + if(Contest.Stages.Num() > (StageId -1)) + { + FDTFluxStage Stage = Contest.Stages[StageId - 1]; + return Stage.StageRanking; + } + } + return TArray(); +} + +void UDTFluxDataStorage::AddOrUpdateContest(const FDTFluxContestResponse& ContestResponse) +{ + FDTFluxContest Contest; + bool NewContest = false; + if(!Contests.IsEmpty() ) + { + for(auto& OldContest: Contests) + { + if(OldContest.Id == ContestResponse.Id) + { + Contest = OldContest; + NewContest = false; + break; + }else + { + NewContest = true; + } + } + }else + { + NewContest = true; + } + Contest.Id = ContestResponse.Id; + Contest.Name = ContestResponse.Name; + TArray Splits; + for(auto Split: ContestResponse.Splits) + { + FDTFluxSplit S; + S.Id = Split.Id; + S.Name = Split.Name; + Splits.Add(S); + } + for(auto StageResp : ContestResponse.Stages ) + { + FDTFluxStage Stage; + Stage.Id = StageResp.Id; + Stage.Name = StageResp.Name; + FDateTime::Parse(StageResp.StartTime, Stage.StartTime); + FDateTime::Parse(StageResp.EndTime, Stage.EndTime); + Stage.Splits = Splits; + Contest.Stages.Add(Stage); + } + if(NewContest) + { + Contests.Add(Contest); + } + // UE_LOG(LogDTFluxAPI, Log, TEXT("Contest DUMP %s")); + +} + +void UDTFluxDataStorage::AddOrUpdateParticipant(const FDTFluxTeamListItemResponse& TeamListItemResponse) +{ + // UE_LOG(LogDTFluxAPI, Log, TEXT("About to process Participant %s BIB : %d"), *TeamListItemResponse.LastName, TeamListItemResponse.Bib); + FDTFluxParticipant Participant; + Participant.Bib = TeamListItemResponse.Bib; + Participant.Category = TeamListItemResponse.Category; + Participant.Club = TeamListItemResponse.Club; + Participant.Elite = TeamListItemResponse.Elite; + Participant.Person1.Gender = TeamListItemResponse.Gender; + Participant.Person1.FirstName = TeamListItemResponse.FirstName; + Participant.Person1.LastName = TeamListItemResponse.LastName; + // TODO ??? + // Participant.Person2.Gender = TeamListItemResponse.Gender2; + Participant.Person2.FirstName = TeamListItemResponse.FirstName2; + Participant.Person2.LastName = TeamListItemResponse.LastName2; + Participant.Status = TeamListItemResponse.Status; + for(auto& Contest: Contests) + { + if(Contest.Id == TeamListItemResponse.ContestId) + { + Contest.AddParticipant(Participant); + return; + } + } + // UE_LOG(LogDTFluxAPI, Error, TEXT(" Participant %s with BIB : %d Has no valid Contest associated. Got %d. This participant wil not be registered."), + // *Participant.Person1.LastName, Participant.Bib, TeamListItemResponse.ContestId); + +} + + +void UDTFluxDataStorage::AddSplitSensorResult(FDTFluxSplitSensorItemResponse Response) +{ + // Send SplitSensor Result to BP + FDTFluxStage CurrentStage; + if(GetStage(CurrentStage, Response.StageID)) + { + // this is an empty stage + if(CurrentStage.Id == -1 ) + { + + } + } + +} + +void UDTFluxDataStorage::GoToNextStage() +{ + // If Number of stages is less or equal to the current stageID + if(IsInitialized()) + { + if(Contests[CurrentContestId].Stages.Num() -1 <= CurrentStageId) + { + CurrentStageId += 1; + }else + { + ResetStageId(); + ChangeCurrentContest(); + } + } + +} + +void UDTFluxDataStorage::ChangeCurrentContest() +{ + // Contest Are initialized + if(IsInitialized()) + { + if(CurrentContestId < Contests.Num() -1) + { + // last Contest + CurrentContestId = 0; + } + + } +} diff --git a/Source/DTFluxAPI/Private/DTFluxModel/DTFluxModel.cpp b/Source/DTFluxAPI/Private/DTFluxModel/DTFluxModel.cpp index cb85533..721ab3f 100644 --- a/Source/DTFluxAPI/Private/DTFluxModel/DTFluxModel.cpp +++ b/Source/DTFluxAPI/Private/DTFluxModel/DTFluxModel.cpp @@ -2,3 +2,93 @@ #include "DTFluxModel/DTFluxModel.h" + +void FDTFluxSplit::Dump() const +{ + UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] Split ID : %i, Name:%s"), Id, *Name); + +} + +bool FDTFluxStage::SetStartTime(const FDateTime& ContestDate, const FString& TimeString) +{ + TArray TimeTokensStart; + TimeString.ParseIntoArray(TimeTokensStart, TEXT(":")); + const int32 HoursStart = FCString::Atoi(*TimeTokensStart[0]); + const int32 MinutesStart = FCString::Atoi(*TimeTokensStart[1]); + const int32 SecondsStart = FCString::Atoi(*TimeTokensStart[2]); + StartTime = FDateTime(ContestDate.GetYear(), ContestDate.GetMonth(), ContestDate.GetDay(), + HoursStart, MinutesStart, SecondsStart); + UE_LOG(LogDTFluxAPI, Log, TEXT("Setting StartTime For %s to %s"), *Name, *StartTime.ToString()); + return true; +} + +bool FDTFluxStage::SetEndTime(const FDateTime& ContestDate, const FString& TimeString) +{ + TArray TimeTokens; + TimeString.ParseIntoArray(TimeTokens, TEXT(":")); + const int32 Hours = FCString::Atoi(*TimeTokens[0]); + const int32 Minutes = FCString::Atoi(*TimeTokens[1]); + const int32 Seconds = FCString::Atoi(*TimeTokens[2]); + EndTime = FDateTime(ContestDate.GetYear(), ContestDate.GetMonth(), ContestDate.GetDay(), + Hours, Minutes, Seconds); + UE_LOG(LogDTFluxAPI, Log, TEXT("Setting EndTime For %s to %s"), *Name, *StartTime.ToString()); + return true; +} + +bool FDTFluxStage::UpdateStageRanking(TArray> Data) +{ + return true; +} + +bool FDTFluxStage::AddSplit(TArray> SplitData) +{ + return true; +} + +void FDTFluxStage::Dump() const +{ + UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] Id : %i, Name : %s"), Id, *Name); + UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] StartTime : %s, EndTime : %s"), *StartTime.ToString(), *EndTime.ToString()); + UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] Splits [")); + for(const auto& Split : Splits) + { + Split.Dump(); + } + UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxStage DUMP] ]")); + + +} + +bool FDTFluxContest::AddStage( const TArray> StagesData, TArray Splits) +{ + for (const auto& StageData : StagesData) + { + FDTFluxStage Stage; + Stage.Id = StageData->AsObject()->GetIntegerField(TEXT("id")); + Stage.Name = StageData->AsObject()->GetStringField(TEXT("name")); + FString StartTime = StageData->AsObject()->GetStringField(TEXT("startTime")); + FString EndTime = StageData->AsObject()->GetStringField(TEXT("endTime")); + Stage.SetStartTime(Date, StartTime); + Stage.SetEndTime(Date, EndTime); + Stage.Splits = Splits; + Stages.Add(Stage); + Stage.Dump(); + } + return true; +} + +bool FDTFluxContest::SetDate(const FString& StringDate) +{ + + TArray Tokens; + StringDate.ParseIntoArray(Tokens, TEXT("-")); + if(Tokens.Num() != 3) + { + return false; + } + const int32 Year = FCString::Atoi(*Tokens[0]); + const int32 Month = FCString::Atoi(*Tokens[1]); + const int32 Day = FCString::Atoi(*Tokens[2]); + Date = FDateTime(Year, Month, Day); + return true; +} diff --git a/Source/DTFluxAPI/Private/DTFluxProjectSettings/DTFluxProjectSettings.cpp b/Source/DTFluxAPI/Private/DTFluxProjectSettings/DTFluxProjectSettings.cpp index ada8355..8bd688c 100644 --- a/Source/DTFluxAPI/Private/DTFluxProjectSettings/DTFluxProjectSettings.cpp +++ b/Source/DTFluxAPI/Private/DTFluxProjectSettings/DTFluxProjectSettings.cpp @@ -3,71 +3,20 @@ #include "DTFluxProjectSettings/DTFluxProjectSettings.h" -FString UDTFluxProjectSettings::GetAPIPath(const TEnumAsByte RouteType, const FString& Filters) const -{ - FString ApiAccessRoute; - UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Filters in GetApiPath settings\nGot %s"), *Filters); - - if (bAccessIsLocal) - { - // http://localhost/_246158/api/8O8JMI2739JS58R8KRJGJEZUSQXF807O - ApiAccessRoute += RaceResultPort != 80 ? FString::Printf(TEXT("%s:%i/_%s/api/"), *ProxyUrl, RaceResultPort, *APIToken) : - FString::Printf(TEXT("%s/_%s/api/"), *ProxyUrl, *APIToken); - } - else - { - // https://api.raceresult.com/246158/8O8JMI2739JS58R8KRJGJEZUSQXF807O - ApiAccessRoute += FString::Printf(TEXT("https://api.raceresult.com/%s/"), *APIToken); - } - switch(RouteType) - { - case (EDTFluxAPIRoute::Results): - return ApiAccessRoute + LiveStageResultsAccessToken + Filters; - break; - case (EDTFluxAPIRoute::FinalClassification): - return ApiAccessRoute + GeneralClassificationAccessToken + Filters; - break; - - default: - return ApiAccessRoute + StartListAccessToken + Filters; - } -} - -FString UDTFluxProjectSettings::GetAPIPathFiltered(const TEnumAsByte RouteType, - const FSearchFilters& Filters) const -{ - const FString Filter = Filters.GetFilter(); - UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Filters in Project settings\nGot %s"), *Filter); - return GetAPIPath(RouteType, Filter ); -} - -FString UDTFluxProjectSettings::GetProxyPath(const TEnumAsByte RouteType, - const int& InContest, const int& InStage ) const -{ - switch(RouteType) - { - case (EDTFluxProxyRoute::ProxyRankingContest): - return FString::Printf(TEXT("%s:%i%s/ranking/contest/%i/"), *ProxyAddress, ProxyPort, *ProxyRootPath, InContest); - break; - case (EDTFluxProxyRoute::ProxyRankingStage): - return FString::Printf(TEXT("%s:%i%s/ranking/contest/%i/stage?id=%i"), - *ProxyAddress, ProxyPort, *ProxyRootPath, InContest, InStage); - break; - case (EDTFluxProxyRoute::ProxyTeams): - return FString::Printf(TEXT("%s:%i%s/teams/"), *ProxyAddress, ProxyPort, *ProxyRootPath); - break; - default : - return FString::Printf(TEXT("%s:%i%s/race/datas/"), *ProxyAddress, ProxyPort, *ProxyRootPath); - break; - } -} - const UDTFluxProjectSettings* UDTFluxProjectSettings::GetDTFluxAPIProjectSettings() { return GetDefault(); } +void UDTFluxProjectSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + FString ChangeKey = PropertyChangedEvent.Property->GetName(); + UE_LOG(LogDTFluxAPI, Log, TEXT("Settings %s has changed"), *ChangeKey); + OnProjectSettingsModified.Broadcast(ChangeKey, GetDTFluxAPIProjectSettings()); +} + UDTFluxProjectSettings::UDTFluxProjectSettings() { - SectionName = "DTFlux Settings"; + CategoryName = "DTFlux Settings"; } diff --git a/Source/DTFluxAPI/Private/DTFluxSubsystem/DTFluxSubsystem.cpp b/Source/DTFluxAPI/Private/DTFluxSubsystem/DTFluxSubsystem.cpp index 8dcc7aa..39b34f8 100644 --- a/Source/DTFluxAPI/Private/DTFluxSubsystem/DTFluxSubsystem.cpp +++ b/Source/DTFluxAPI/Private/DTFluxSubsystem/DTFluxSubsystem.cpp @@ -2,330 +2,536 @@ #include "DTFluxSubsystem/DTFluxSubsystem.h" #include "DTFluxProjectSettings/DTFluxProjectSettings.h" +#include "DTFluxWebSocket/DTFluxWebsocketServer.h" + #include "DTFluxModel/DTFluxModel.h" #include "HttpServerModule.h" #include "HttpRouteHandle.h" #include "DTFluxAPILog.h" +#include "DTFluxDataStorage/DTFluxDataStorage.h" #include "IHttpRouter.h" #include "HttpModule.h" #include "JsonObjectConverter.h" #include "Interfaces/IHttpResponse.h" - -// TODO: Not implemented -bool UDTFluxSubsystem::OnRequest(const FHttpServerRequest& Request) +// DEPRECATED : Now in WS +FString FDTFluxSubsystemAPISettings::GetRaceDataEndpoint(const FDTFluxSubsystemAPISettings* Settings) { - return true; + if(Settings) + { + FString RaceDataEndpoint = + FString::Printf(TEXT("%s/%p"), *Settings->GetProxyBaseEndpoint(), Settings->ProxyEndpoints.FindKey("race-datas")); + UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Race Data -> %s"), *RaceDataEndpoint); + return RaceDataEndpoint; + } + return FString(""); } -// TODO: Not implemented -void UDTFluxSubsystem::HandleRequest(const FString& Route,const FHttpServerRequest& Request, FHttpResultCallback OnComplete) +// DEPRECATED : Now in WS +FString FDTFluxSubsystemAPISettings::GetContestRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId) { - // creating payload string - FString ReqPayload; - if (Request.Body.Num() > 0) + if(Settings) { - const std::string RawBody((char*)Request.Body.GetData(), Request.Body.Num()); - ReqPayload = UTF8_TO_TCHAR(RawBody.c_str()); + FString Ranking = *Settings->ProxyEndpoints.FindKey("ranking"); + const TCHAR* ContestIDTmpl = *FString("{:ContestID}"); + const TCHAR* ContestIDValue = *FString(TEXT("%i"),ContestId); + FString ContestRanking = Ranking.Replace(ContestIDTmpl, ContestIDValue ); + FString ContestRankingEndpoint = Settings->GetProxyBaseEndpoint() + ContestRanking; + UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Contest Ranking -> %s"), *ContestRankingEndpoint); + return ContestRankingEndpoint; } - TSharedPtr JsonPayload; - if(!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(ReqPayload), JsonPayload)) + return FString(""); +} +// DEPRECATED : Now in WS +FString FDTFluxSubsystemAPISettings::GetStageRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId, + const int StageId) +{ + if(Settings) { - UE_LOG(LogDTFluxAPI, Error, TEXT("unable to parse JSON Payload\n%s"), *ReqPayload); + FString StageRanking = GetContestRankingEndpoint(Settings, ContestId); + StageRanking = FString::Printf(TEXT("%s/stage/%i/"), *StageRanking, StageId); + UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Stage Ranking -> %s"), *StageRanking); + return StageRanking; } - //Checking path - - UE_LOG(LogDTFluxAPI,Log, TEXT("Request Received for Route %s"), *Route); - - // EventStart - if(Route == TEXT("/event/start")) - { - FDTFluxStartStagePayload StartStagePayload; - FJsonObjectConverter::JsonObjectToUStruct(JsonPayload.ToSharedRef(), &StartStagePayload, 0, 0); - OnEventStartReceived.Broadcast(StartStagePayload); + return FString(""); +} +// DEPRECATED : Now in WS +FString FDTFluxSubsystemAPISettings::GetStageRankingFilteredEndpoint(const FDTFluxSubsystemAPISettings* Settings, + const int ContestId, const int StageId, const FString SplitName) +{ + if (Settings){ + FString StageRanking = GetStageRankingEndpoint(Settings, ContestId, StageId); + StageRanking = FString::Printf(TEXT("%s?splitname=%s"), *StageRanking, *SplitName); + UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Stage Ranking with Splitname -> %s"), *StageRanking); + return StageRanking; } - else if(Route == TEXT("/team/create")) - { - // /team/create route - } - else if(Route == TEXT("/team/update")) - { - // /team/create route - } else if(Route == TEXT("/team/create")) - { - // /team/create route - } - // Default Route - else - { - - } - - //Preparing Response Header - TUniquePtr Response = CreateHttpServerResponse(); - - // Adding Response Body - FDTFluxResponseBody RespBody; - RespBody.Success = TEXT("OK"); - std::string RespBody_c; - RespBody_c = TCHAR_TO_UTF8(*RespBody.Deserialize()); - Response->Body.Append((const uint8*)RespBody_c.c_str(), RespBody_c.length()); - // Return the response - OnComplete(MoveTemp(Response)); - + return FString(""); } -void UDTFluxSubsystem::OnUpdateStartList(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +// DEPRECATED : Now in WS +FString FDTFluxSubsystemAPISettings::GetTeamsEndpoint(const FDTFluxSubsystemAPISettings* Settings) { - if (!bWasSuccessful || !Response.IsValid()) + if(Settings) { - UE_LOG(LogDTFluxAPI, Error, TEXT("RaceResult Request failed")); - return; + FString TeamsEndpoint = + FString::Printf(TEXT("%s/%p"), *Settings->GetProxyBaseEndpoint(), Settings->ProxyEndpoints.FindKey("teams")); + UE_LOG(LogDTFluxAPI, Log, TEXT("Proxy Teams -> %s"), *TeamsEndpoint ); + return TeamsEndpoint; } - // Tricks because Payload root is an array - const FString ModifiedData = FString::Printf(TEXT("{\"Participants\":%s}"), *Response->GetContentAsString()); - TSharedPtr Payload; - if(!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(ModifiedData), Payload)) - { - UE_LOG(LogDTFluxAPI, Error, TEXT("unable to parse JSON Payload****")); - }else - { - FDTFluxStartListPayload StartList; - if (FJsonObjectConverter::JsonObjectToUStruct(Payload.ToSharedRef(), &StartList, 0, 0)) - { - UE_LOG(LogDTFluxAPI, Log, TEXT("Success Reading")) - - for(const auto& Participant : StartList.Participants) - { - // Creating a new Contest - if(!Contests.Contests.Contains(Participant.ContestName)) - { - FDTFluxContest NewContest; - NewContest.ContestID = Participant.ContestID; - NewContest.ContestName = Participant.ContestName; - Contests.Contests.Add(Participant.ContestName, NewContest); - } - FDTFluxContest* CurrentContest = Contests.Contests.Find(Participant.ContestName); - - FDTFluxTeam Team; - Team.Bib = Participant.Bib; - FDTFluxParticipant ContestParticipant; - ContestParticipant.FirstName = Participant.FirstName; - ContestParticipant.LastName = Participant.LastName; - ContestParticipant.Gender = Participant.Gender; - ContestParticipant.Club = Participant.Club; - ContestParticipant.Category = Participant.Category; - - Team.Participants.Add(ContestParticipant); - // if we have LastName2 it is a two personne Team - if(Participant.LastName2 != "") - { - FDTFluxParticipant ContestParticipant2; - ContestParticipant2.FirstName = Participant.FirstName2; - ContestParticipant2.LastName = Participant.LastName2; - ContestParticipant2.Gender = Participant.Gender2; - ContestParticipant2.Club = Participant.Club2; - ContestParticipant2.Category = Participant.Category; - Team.Participants.Add(ContestParticipant); - Team.TeamName = Participant.TeamName; - UE_LOG(LogDTFluxAPI, Log, TEXT("Participant is a team : TeamName \"%s\" "), *Team.TeamName); - } - else - { - Team.TeamName = Participant.FirstName + TEXT(" ") + Participant.LastName.ToUpper(); - } - // check if Participant already exists - - if(!CurrentContest->TeamAlreadyExist(Team)) - { - // Add it to the TeamList - CurrentContest->AddTeam(Team); - UE_LOG(LogDTFluxAPI, Log, TEXT("Adding Team \"%s\" "), *Team.TeamName); - } - - } - UE_LOG(LogDTFluxAPI, Log, TEXT("Contest has now %i elements"), Contests.Contests.Num()); - - - } - else - { - UE_LOG(LogDTFluxAPI, Error, TEXT("Error Deserializing Payload \n*%s*\n"), *ModifiedData); - - } - } - for(const auto& Element : Contests.Contests) - { - UE_LOG(LogDTFluxAPI, Log, TEXT("Contest %s"), *Element.Key); - FDTFluxContest Contest = Element.Value; - UE_LOG(LogDTFluxAPI, Log, TEXT("Number of participants in this contest %i"), Contest.TeamParticipants.Num()); - - } - + return FString(""); } -TUniquePtr UDTFluxSubsystem::CreateHttpServerResponse() const -{ - // Create a Response to be returned by the server - TUniquePtr Response = MakeUnique(); - Response->Code = EHttpServerResponseCodes::Ok; - // Response Header - Response->Headers.Add(TEXT("Content-Type"), { TEXT("application/json;charset=utf-8") }); - Response->Headers.Add(TEXT("Access-Control-Allow-Origin"), { TEXT("*") }); - Response->Headers.Add(TEXT("Access-Control-Allow-Methods"), { TEXT("GET,POST,PUT,PATCH,DELETE,OPTIONS") }); - Response->Headers.Add(TEXT("Access-Control-Allow-Headers"), { TEXT("Origin,X-Requested-With,Content-Type,Accept") }); - Response->Headers.Add(TEXT("Access-Control-Max-Age"), { TEXT("600") }); - Response->Headers.Add(TEXT("Access-Control-Allow-Credentials"), { TEXT("true") }); - Response->Headers.Add(TEXT("Server"), { TEXT("UE 5.4.1 EMBBEDED") }); +/**** + * DTFlux subsystem + ****/ - return Response; -} void UDTFluxSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); - mSettings = UDTFluxProjectSettings::GetDTFluxAPIProjectSettings(); - if(mSettings) - { - // Setting up request - HttpRequest = &FHttpModule::Get(); - HttpRouter = FHttpServerModule::Get().GetHttpRouter(mSettings->InPort); - if(!HttpRouter) - { - UE_LOG(LogDTFluxAPI, Type::Error, TEXT("Invalid Http Router for port : %i"), mSettings->InPort) - } - // Setting Up Routes - for(const TArray Routes = mSettings->Endpoints; const FString& Route : Routes) - { - const FHttpRequestHandler OptionRequestHandler = FHttpRequestHandler::CreateLambda([this](const FHttpServerRequest &Request, const FHttpResultCallback& OnComplete) - { - OnComplete(CreateHttpServerResponse()); - return true; - }); - FHttpRouteHandle OptionRouteHandle = HttpRouter->BindRoute(Route, EHttpServerRequestVerbs::VERB_OPTIONS, OptionRequestHandler); - HttpMountedMap.Add(Route + TEXT("HTTPOption"), OptionRouteHandle); + const UDTFluxProjectSettings* Settings = GetSettings(); + LoadConfig(Settings); + WsClient = NewObject(this, UDTFluxWebSocketClient::StaticClass()); + WsClient->OnConnectionConnected.AddDynamic(this, &UDTFluxSubsystem::WsConnected ); + WsClient->OnConnectionClosed.AddDynamic(this, &UDTFluxSubsystem::WsConnectionClosed ); + WsClient->OnConnectionError.AddDynamic(this, &UDTFluxSubsystem::WsConnectionError ); + WsClient->OnReceivedMessage.AddDynamic(this, &UDTFluxSubsystem::WsReceivedMessage ); + UE_LOG(LogDTFluxAPI, Log, TEXT("Trying to connect to %s:%i"), *SubSettings.WebsocketAddress, SubSettings.WebsocketPort); + WsClient->Connect(SubSettings.WebsocketAddress, SubSettings.WebsocketPort); - const FHttpRequestHandler RequestHandler = FHttpRequestHandler::CreateLambda([this, Route](const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete) - { - // Processing Request to Raw OnRequestReceived firing - FDTFluxHttpServerHeaders Headers; - for(const auto &Header: Request.Headers) - { - Headers.Headers.Add(Header.Key, FString::Join(Header.Value, TEXT(","))); - } - FDTFluxHttpServerParams Params; - Params.Params = Request.QueryParams; - - FDTFluxHttpServerBody Payload; - Payload.ReqBody = Request.Body; + DataStorage = NewObject(); + FDateTime Now = FDateTime::Now(); + FDateTime Send1Min = Now + FTimespan::FromMinutes(1); + UE_LOG(LogDTFluxAPI, Log, TEXT("TEST timer timeSpan Duration : %s"), *Send1Min.ToString()); - // Raw broadcasting Received event - OnRequestReceived.Broadcast(Headers, Params, Payload); - HandleRequest(Route, Request, OnComplete); - return true; - }); - // Binding Routes - FHttpRouteHandle RouteHandle = HttpRouter->BindRoute(Route, EHttpServerRequestVerbs::VERB_POST, RequestHandler); - HttpMountedMap.Add(Route, RouteHandle); - } - - } + // SetTimerEvent( Send1Min ); + // WsServer Event binding } void UDTFluxSubsystem::Deinitialize() { - StopServer(); - UE_LOG(LogDTFluxAPI, Log, TEXT("Route Num %i"), HttpMountedMap.Num()); - for( auto const& Route: HttpMountedMap) + if(WsClient) { - HttpRouter->UnbindRoute(Route.Value); + UE_LOG(LogDTFluxAPI, Log, TEXT("WsClient not null")); } - HttpMountedMap.Empty(); - HttpRouter.Reset(); + else + UE_LOG(LogDTFluxAPI, Log, TEXT("WsClient has been GC'ed")); + Super::Deinitialize(); } -TArray UDTFluxSubsystem::GetMountedRoutes() const +bool UDTFluxSubsystem::ReloadSubsystem() { - TArray Mounted = TArray(); - for(const auto Route : HttpMountedMap) + return Reconnect(); +} + +bool UDTFluxSubsystem::Reconnect() +{ + bool Result = WsClient->Close(); + if(!WsClient->IsConnected()) + return WsClient->Connect( SubSettings.WebsocketAddress, SubSettings.WebsocketPort); + return false; +} + +void UDTFluxSubsystem::LoadConfig(const UDTFluxProjectSettings* Settings) +{ + SubSettings.WebsocketPort = Settings->WebsocketServerPort; + SubSettings.WebsocketAddress = Settings->WebsocketServerAddress; + SubSettings.ProxyAddress = Settings->ProxyAddress; + SubSettings.ProxyPort = Settings->ProxyPort; + TMap SettingsEndpoints; + SettingsEndpoints.Add(FString("race-data"), Settings->ProxyRaceDataEndpoint); + SettingsEndpoints.Add(FString("contest-ranking"), Settings->ProxyRankingEndpoint); + SettingsEndpoints.Add(FString("stage-ranking"), Settings->ProxyRankingEndpoint); + SettingsEndpoints.Add(FString("team-list"), Settings->ProxyTeamsEndpoint); + SubSettings.ProxyEndpoints = SettingsEndpoints; +} + +// Get project Settings +const UDTFluxProjectSettings* UDTFluxSubsystem::GetSettings() +{ + if(const UDTFluxProjectSettings* Settings = UDTFluxProjectSettings::GetDTFluxAPIProjectSettings()) + return Settings; + else { - Mounted.Add(Route.Key); + UE_LOG(LogDTFluxAPI, Error, TEXT("Unable to get DTFlux API settings")); + return nullptr; } - return Mounted; } -void UDTFluxSubsystem::StartServer() +// tick function +void UDTFluxSubsystem::Tick(float DeltaTime) { - if(bIsListening){return ;} - FHttpServerModule::Get().StartAllListeners(); - bIsListening = true; - OnServerListening.Broadcast(); -} - -TArray UDTFluxSubsystem::GetParticipantsByContestId(const int ContestId) -{ - if(Contests.Contests.Num() != 0) + if(Timer.Num() > 0) { - FString ContestName = Contests.GetContestName(ContestId); - UE_LOG(LogDTFluxAPI, Log, TEXT("Getting Participants for Contest %s"), *ContestName); - return GetParticipantsByContestName(ContestName); + TArray Done; + for(auto const& El : Timer) + { + FDateTime Dt = FDateTime::Now(); + if(Dt >= El.Key) + { + El.Value.Execute(TEXT("Tick")); + OnTimerTriggered.Broadcast(); + UE_LOG(LogDTFluxAPI, Log, TEXT("Execution")); + UE_LOG(LogDTFluxAPI, Log, TEXT("TICK : exec time: %lld == %lld"), El.Key.GetTicks(), Dt.GetTicks()); + Done.Add(El.Key); + } + + } + if(Done.Num() > 0) + { + UE_LOG(LogDTFluxAPI, Log, TEXT("TICK : Cleaning %i"), Done.Num()); + for(auto const& ToDelete: Done) + { + Timer.Remove(ToDelete); + } + } + // UE_LOG(LogDTFluxAPI, Log, TEXT("TICK : Timer Length=%i"), Timer.Num()); + } +} + +// TODO: IMPLEMENT THIS METHOD +void UDTFluxSubsystem::WsSplitSensorReceivedInternal() +{ +} +// TODO: IMPLEMENT THIS METHOD +void UDTFluxSubsystem::WsTeamUpdateReceivedInternal() +{ +} +// TODO: IMPLEMENT THIS METHOD +void UDTFluxSubsystem::WsStatusUpdateReceivedInternal() +{ +} + +void UDTFluxSubsystem::RequestRaceDatas() +{ + WsClient->SendMessage(TEXT("{\"path\": \"race-datas\"}")); +} + +void UDTFluxSubsystem::RequestTeamList() +{ + WsClient->SendMessage(TEXT("{\"path\": \"team-list\"}")); +} + +void UDTFluxSubsystem::RequestContestRanking(const int ContestId) +{ + const FString Request = FString::Printf(TEXT("{\"path\": \"contest-ranking\", \"contestID\" : %i}"), ContestId); + WsClient->SendMessage(Request); +} + +void UDTFluxSubsystem::RequestStageRanking(const int ContestId, const int StageId) +{ + const FString Request = FString::Printf(TEXT("{\"path\": \"stage-ranking\", \"contestID\" : %i, \"stageID\" : %i}"), ContestId, StageId); + WsClient->SendMessage(Request); +} + +void UDTFluxSubsystem::RequestSplitGaps(const int ContestId, const int StageId, const int SplitId) +{ + const FString Request = + FString::Printf(TEXT("{\"path\": \"stage-ranking\", \"contestID\" : %i, \"stageID\" : %i, \"splitID\" : %i}"), + ContestId, StageId, SplitId); + + WsClient->SendMessage(Request); +} + +void UDTFluxSubsystem::UpdateRaceData() +{ + RequestRaceDatas(); +} + +void UDTFluxSubsystem::UpdateTeamList() +{ + RequestTeamList(); +} + +void UDTFluxSubsystem::UpdateTeam() +{ +} + + + +void UDTFluxSubsystem::UpdateContestRanking(const int ContestID) +{ + RequestContestRanking(ContestID); +} + +void UDTFluxSubsystem::UpdateStageRanking(const int ContestID, const int StageID, const int SplitID) +{ + if(SplitID == -1) + { + RequestStageRanking(ContestID, StageID); } else { - UE_LOG(LogDTFluxAPI, Error, TEXT("No Contest Yet !!!!")); - TArray EmptyTeam; - return EmptyTeam; + RequestSplitGaps(ContestID, StageID, SplitID); } - } -TArray UDTFluxSubsystem::GetParticipantsByContestName(const FString ContestName) + + + +EDTFluxResponseType UDTFluxSubsystem::FindResponseType(const FString& MessageReceived) { - if(Contests.Contests.Num() != 0) + EDTFluxResponseType ResponseType = UnknownResponse; + TSharedPtr JsonValue; + + // Create a reader pointer to read the json data + TSharedRef> Reader = TJsonReaderFactory<>::Create(MessageReceived); + if (FJsonSerializer::Deserialize(Reader, JsonValue)) { - return Contests.Contests[ContestName].TeamParticipants; + // Get the value of the json object by field name + TSharedPtr Json = JsonValue->AsObject(); + //Test + FString Type = Json->GetStringField(TEXT("type")); + if(Type == "") + { + // UE_LOG(LogDTFluxAPI, Log, TEXT("Type tupe does not exist")); + return EDTFluxResponseType::UnknownResponse; + } + if(Type.Contains("race-datas")) + { + // TODO : check if object data are valid + FDTFluxRaceDataResponse RaceDataResponse; + if(!FJsonObjectConverter::JsonObjectToUStruct + (Json.ToSharedRef(), &RaceDataResponse)) + { + UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid \"race-data\" object"), *MessageReceived); + return EDTFluxResponseType::UnknownResponse; + } + UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s is valid race-data object"), *MessageReceived); + ProcessRaceDataResponse(RaceDataResponse); + return EDTFluxResponseType::RaceData; + } + if(Type.Contains("constest-ranking")) + { + FDTFluxContestRankingResponse ContestRankingResponse; + if(!FJsonObjectConverter::JsonObjectToUStruct + (Json.ToSharedRef(), &ContestRankingResponse)) + { + UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid \"contest-ranking\" object"), *MessageReceived); + } + // TODO : check if object data are valid + UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s is valid \"contest-ranking\" object"), *MessageReceived); + ProcessContestRankingResponse(ContestRankingResponse); + return EDTFluxResponseType::ContestRanking; + } + if(Type.Contains("stage-ranking")) + { + FDTFluxStageRankingResponse StageRankingResponse; + if(!FJsonObjectConverter::JsonObjectToUStruct + (Json.ToSharedRef(), &StageRankingResponse)) + { + UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid \"stage-ranking\" object"), *MessageReceived); + } + UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s is valid \"stage-ranking\" object"), *MessageReceived); + if(StageRankingResponse.SplitID == -1) + { + ProcessSplitRankingResponse(StageRankingResponse); + return EDTFluxResponseType::SplitRanking; + + } + ProcessStageRankingResponse(StageRankingResponse); + return EDTFluxResponseType::StageRanking; + } + if(Type.Contains("team-list")) + { + FDTFluxTeamListResponse TeamListResponse; + if( !FJsonObjectConverter::JsonObjectToUStruct + (Json.ToSharedRef(), &TeamListResponse)) + { + UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid team-list object"), *MessageReceived) + return EDTFluxResponseType::UnknownResponse; + } + UE_LOG(LogDTFluxAPI, Log, TEXT("Received team-list data")); + ProcessTeamListResponse(TeamListResponse); + // TODO : check if object data are valid + return EDTFluxResponseType::TeamList; + } + if(Type.Contains("team-update")) + { + // TODO : check if object data are valid + return EDTFluxResponseType::TeamUpdate; + } + if(Type.Contains("split-sensor")) + { + // TODO : check if object data are valid + FDTFluxSplitSensorResponse SplitSensorResponse; + if( !FJsonObjectConverter::JsonObjectToUStruct + (Json.ToSharedRef(), &SplitSensorResponse)) + { + UE_LOG(LogDTFluxAPI, Error, TEXT("Message %s is not a valid split-sensor data"), *MessageReceived) + return EDTFluxResponseType::UnknownResponse; + } + UE_LOG(LogDTFluxAPI, Log, TEXT("Received split-sensor data")); + // send the array to DataStorage; + return EDTFluxResponseType::SplitSensor; + } + if(Type.Contains("status-update")) + { + // TODO : check if object data are valid + return EDTFluxResponseType::StatusUpdate; + } } - else + + return ResponseType; +} + +/*** + * Timer handling + ***/ +void UDTFluxSubsystem::BroadcastTimerEvent() +{ + OnTimerTriggered.Broadcast(); + UE_LOG(LogDTFluxAPI, Log, TEXT("TEST timer trigerred at : %s"), *FDateTime::Now().ToString()); + +} + + +void UDTFluxSubsystem::SetTimerEvent(const FDateTime& When) +{ + FTimespan TimeSpan = FDateTime::Now() - When; + UE_LOG(LogDTFluxAPI, Log, TEXT("TEST timer timeSpan Duration : %s"), *TimeSpan.GetDuration().ToString()); +} + + +bool UDTFluxSubsystem::AddTimer(FDateTime Time, FOnTimer NewTimer) +{ + Timer.Add(Time, NewTimer); + return true; +} + + +/** + * END TIMER HANDLING + ***/ + +void UDTFluxSubsystem::WsConnected() +{ + OnWsConnected.Broadcast(); + UE_LOG(LogDTFluxAPI, Log, TEXT("Ws Connected")); +} + +void UDTFluxSubsystem::WsReceivedMessage( const FString& MessageReceived) +{ + OnWsIncomingData.Broadcast(MessageReceived); + // UE_LOG(LogDTFluxAPI, Log, TEXT("Ws ReceivedMessage %s"), *MessageReceived); + // Find Data Object Type + EDTFluxResponseType Type = FindResponseType(MessageReceived); + switch(Type) { - UE_LOG(LogDTFluxAPI, Error, TEXT("No Contest Yet !!!!")); - TArray EmptyTeam; - return EmptyTeam; + case EDTFluxResponseType::TeamList: + break; + case EDTFluxResponseType::RaceData: + break; + case EDTFluxResponseType::ContestRanking: + break; + case EDTFluxResponseType::StageRanking: + break; + case EDTFluxResponseType::SplitRanking: + break; + case EDTFluxResponseType::TeamUpdate: + break; + default: + break; } + // Let datastorage Know that we received something + // DataStorage->UpdateDataStorage(MessageReceived); } -FString UDTFluxSubsystem::GetContestName(const int ContestId) +void UDTFluxSubsystem::WsConnectionClosed(const FString& Reason) { - if(Contests.GetContestName(ContestId) != "") + OnWsClosed.Broadcast(Reason); + UE_LOG(LogDTFluxAPI, Log, TEXT("Ws ConnectionClosed with reason %s"), *Reason); +} + +void UDTFluxSubsystem::WsConnectionError(const FString& Error) +{ + OnWsError.Broadcast(Error); + UE_LOG(LogDTFluxAPI, Log, TEXT("Ws Error %s"), *Error); +} + +bool UDTFluxSubsystem::IsConnected() const +{ + return WsClient->IsConnected(); +} + +void UDTFluxSubsystem::ProcessTeamListResponse(const FDTFluxTeamListResponse& TeamListResponse) +{ + for( const auto& TeamListItemResponse : TeamListResponse.Datas) { - return Contests.GetContestName(ContestId); + UE_LOG(LogDTFluxAPI, Log, TEXT("Sending Participant %s with Bib %d to DataStorage"), *TeamListItemResponse.LastName, TeamListItemResponse.Bib); + + DataStorage->AddOrUpdateParticipant(TeamListItemResponse); } - return TEXT(""); + for(auto& Contest : DataStorage->Contests) + { + Contest.DumpParticipant(); + } + // UE_LOG(LogDTFluxAPI, Log, TEXT("New Particpant list Size %d"), DataStorage->GetParticipants().Num()) + + } -void UDTFluxSubsystem::UpdateStartList() +void UDTFluxSubsystem::ProcessRaceDataResponse(const FDTFluxRaceDataResponse& DataResponse) { - const TSharedRef Req = HttpRequest->CreateRequest(); - Req->SetVerb("GET"); - Req->SetURL(mSettings->GetAPIPath(EDTFluxAPIRoute::Starters)); - Req->SetHeader(TEXT("Content-Type"), TEXT("application/json")); - Req->OnProcessRequestComplete().BindUObject(this, &UDTFluxSubsystem::OnUpdateStartList); + for(const auto ContestResponse : DataResponse.Datas) + { + DataStorage->AddOrUpdateContest(ContestResponse); + } + + UE_LOG(LogDTFluxAPI, Log, TEXT("New Contest Size %d"), DataStorage->Contests.Num()) - Req->ProcessRequest(); } -void UDTFluxSubsystem::UpdateClassification(const int& ContestId, const int& StageId) + +void UDTFluxSubsystem::ProcessContestRankingResponse(const FDTFluxContestRankingResponse& ContestRankingResponse) +{ + TArray NewRankings; + + for(const auto& TeamContestRankingResponse : ContestRankingResponse.Datas) + { + FDTFluxContestRanking NewRankingEl; + NewRankingEl.Participant = DataStorage->GetParticipant(ContestRankingResponse.ContestID, TeamContestRankingResponse.Bib); + NewRankingEl.Rank = TeamContestRankingResponse.Rank; + NewRankingEl.Gap = TeamContestRankingResponse.Gap; + NewRankingEl.Time = TeamContestRankingResponse.Time; + NewRankings.Add(NewRankingEl); + } +} + +void UDTFluxSubsystem::ProcessStageRankingResponse(const FDTFluxStageRankingResponse& StageRankingResponse) { } -TArray UDTFluxSubsystem::GetClassification(const int& ContestId, const int& StageId) +void UDTFluxSubsystem::ProcessSplitRankingResponse(const FDTFluxStageRankingResponse& SplitRankingResponse) { - return TArray(); } -void UDTFluxSubsystem::StopServer() +void UDTFluxSubsystem::ProcessTeamUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse) { - FHttpServerModule::Get().StopAllListeners(); - bIsListening = false; - OnServerStopped.Broadcast(); } + +void UDTFluxSubsystem::ProcessStatusUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse) +{ + +} + +void UDTFluxSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorResponse& SplitSensorResponse) +{ +} + +TSharedPtr UDTFluxSubsystem::GetData(EDTFluxResponseType Type, const FString& Message) +{ + TSharedPtr JsonValue; + TSharedPtr Object; + + // Create a reader pointer to read the json data + TSharedRef> Reader = TJsonReaderFactory<>::Create(Message); + if (FJsonSerializer::Deserialize(Reader, JsonValue)) + { + + } + return Object; +} + + diff --git a/Source/DTFluxAPI/Private/DTFluxWebSocket/DTFluxWebsocketClient.cpp b/Source/DTFluxAPI/Private/DTFluxWebSocket/DTFluxWebsocketClient.cpp new file mode 100644 index 0000000..cb5f96b --- /dev/null +++ b/Source/DTFluxAPI/Private/DTFluxWebSocket/DTFluxWebsocketClient.cpp @@ -0,0 +1,120 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "DTFluxWebSocket/DTFluxWebsocketClient.h" +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "DTFluxWebSocket/DTFluxWebsocketClient.h" +#include "IWebSocket.h" +#include "WebSocketsModule.h" +#include "DTFluxAPILog.h" + + + +// UDTFluxWebSocketClient::~UDTFluxWebSocketClient() +// { +// +// } + +void UDTFluxWebSocketClient::Initialize() +{ +} + +// void UDTFluxWebSocketClient::BeginDestroy() +// { +// // if(Ws->IsConnected()) +// // Ws->Close(); +// UObject::BeginDestroy(); +// } + +bool UDTFluxWebSocketClient::Connect(const FString URL, const int Port) +{ + FString ServerUrl = FString::Printf(TEXT("%s:%i/"), *URL, Port); + + UE_LOG(LogDTFluxAPI, Log, TEXT("[DTFluxWebsocketClient %s"), *ServerUrl); + Ws = FWebSocketsModule::Get().CreateWebSocket(ServerUrl); + Ws->OnConnected().AddLambda([this]() + { + if(IsValid(this) == false) return; + OnConnectionConnectedInternal(); + }); + + Ws->OnClosed().AddLambda([this](int32 StatusCode, const FString& Reason, bool bWasClean) + { + if(IsValid(this) == false) return; + OnConnectionClosedInternal(Reason); + }); + + Ws->OnConnectionError().AddLambda([this](const FString& Error) + { + if(IsValid(this) == false) return; + OnConnectionErrorInternal(Error); + }); + + Ws->OnMessage().AddLambda([this](const FString& MessageString) + { + if(IsValid(this) == false) return; + OnReceivedMessageInternal(MessageString); + }); + Ws->Connect(); + if(Ws->IsConnected()) + { + UE_LOG(LogDTFluxAPI, Log, TEXT("Listening")); + return true; + } + + return false; +} + +void UDTFluxWebSocketClient::Disconnect() +{ + Ws->Close(); +} + +bool UDTFluxWebSocketClient::SendMessage(const FString Message, const bool Broadcast) +{ + if(Ws->IsConnected() == false) return false; + Ws->Send(Message); + return true; +} + +void UDTFluxWebSocketClient::OnConnectionConnectedInternal() const +{ + OnConnectionConnected.Broadcast(); + UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketsSubsystem, [Connected]")); +} + +void UDTFluxWebSocketClient::OnConnectionErrorInternal(const FString& Error) const +{ + OnConnectionError.Broadcast( Error); + UE_LOG(LogDTFluxAPI, Error, TEXT("WebSocketsSubsystem, [Error] : %s"), *Error); +} + +void UDTFluxWebSocketClient::OnConnectionClosedInternal(const FString& Reason) const +{ + OnConnectionClosed.Broadcast(Reason); + UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketsSubsystem, [Closed], Reason : %s"), *Reason); + +} + +void UDTFluxWebSocketClient::OnReceivedMessageInternal(const FString& Message) const +{ + OnReceivedMessage.Broadcast(Message); + // UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketsSubsystem, [Message Reveived], Message : %s"), *Message); + +} + +bool UDTFluxWebSocketClient::Close() const +{ + if (Ws->IsConnected()) + Ws->Close(); + return Ws->IsConnected(); +} + +bool UDTFluxWebSocketClient::IsConnected() +{ + return Ws->IsConnected(); +} + + diff --git a/Source/DTFluxAPI/Private/DTFluxWebSocket/DTFluxWebsocketServer.cpp b/Source/DTFluxAPI/Private/DTFluxWebSocket/DTFluxWebsocketServer.cpp new file mode 100644 index 0000000..20ee74d --- /dev/null +++ b/Source/DTFluxAPI/Private/DTFluxWebSocket/DTFluxWebsocketServer.cpp @@ -0,0 +1,246 @@ +// // Fill out your copyright notice in the Description page of Project Settings. +// +// +// #include "DTFluxWebSocket/DTFluxWebsocketServer.h" +// +// #include +// +// #include "IWebSocket.h" +// #include "WebSocketsModule.h" +// #include "INetworkingWebSocket.h" +// #include "IWebSocketNetworkingModule.h" +// #include "WebSocketNetworkingDelegates.h" +// #include "DTFluxAPILog.h" +// #include "IWebSocketServer.h" +// +// +// UDTFluxWebsocketServer::UDTFluxWebsocketServer() +// { +// Ws = UDTFluxWebsocketServer::GetServer(); +// } +// +// UDTFluxWebsocketServer::~UDTFluxWebsocketServer() +// { +// +// } +// +// bool UDTFluxWebsocketServer::ConnectToChannel(const FString URL, const int Port) +// { +// //try to get a WsServer +// if(!Ws) +// Ws = UDTFluxWebsocketServer::GetServer(); +// if(Ws) +// { +// FWebSocketClientConnectedCallBack ClientConnectedCb; +// ClientConnectedCb.BindUObject(this, &UDTFluxWebsocketServer::OnConnectionConnectedInternal); +// if(Ws->Init((uint32) Port, ClientConnectedCb, FString("127.0.0.1")) ) +// { +// UE_LOG(LogDTFluxAPI, Log, TEXT("Server listening on %s:%i"),*URL, Port); +// if (Ws.IsValid()) +// UE_LOG(LogDTFluxAPI, Log, TEXT("Server pointer Is Valid %s"), *Ws->Info()); +// +// return true; +// } +// else +// { +// UE_LOG(LogDTFluxAPI, Error, TEXT("Server Init() failed")); +// Ws.Reset(); +// ClientConnectedCb.Unbind(); +// return false; +// } +// } +// UE_LOG(LogDTFluxAPI, Error, TEXT("Unable to get a WsServer Object")); +// +// return false; +// } +// +// void UDTFluxWebsocketServer::LeaveChannel() +// { +// bIsConnected = false; +// Ws = nullptr; +// } +// +// bool UDTFluxWebsocketServer::SendMessageToAll(const FString Message) +// { +// if(!Ws) +// { +// UE_LOG(LogDTFluxAPI, Error, TEXT("WebSocketServer Not Up")); +// return false; +// } +// if(Clients.Num() <= 0) +// { +// UE_LOG(LogDTFluxAPI, Log, TEXT("No Clients Yet !!!")); +// return false; +// } +// int i = 0; +// for(const auto& Client : Clients) +// { +// const TCHAR* SerializedChar = Message.GetCharArray().GetData(); +// int32 Size = FCString::Strlen(SerializedChar); +// int32 Sent = 0; +// uint8* Data = (uint8*)TCHAR_TO_UTF8(SerializedChar); +// if(Client->Send(Data, Size)) +// { +// UE_LOG(LogDTFluxAPI, Log, TEXT("Message %s sent to client n°%i"), *Message, i); +// i++; +// } +// } +// if(i != 0) +// { +// UE_LOG(LogDTFluxAPI, Log, TEXT("%i Messages sent"), i); +// return true; +// } +// UE_LOG(LogDTFluxAPI, Error, TEXT("Message has not been sent")); +// return false; +// } +// +// void UDTFluxWebsocketServer::OnConnectionConnectedInternal(INetworkingWebSocket* ClientWebSocket) +// { +// OnConnectionConnected.Broadcast(ChannelName); +// FWebSocketPacketReceivedCallBack PacketReceivedCallBack; +// PacketReceivedCallBack.BindUObject(this, &UDTFluxWebsocketServer::OnReceivedMessageInternal); +// ClientWebSocket->SetReceiveCallBack(PacketReceivedCallBack); +// FWebSocketInfoCallBack InfoCallBack; +// InfoCallBack.BindLambda([this, ClientWebSocket]() +// { +// int i = 0; +// +// for(const auto& Client : Clients) +// { +// int Removed = Clients.Remove(Client); +// if(Removed) +// UE_LOG(LogDTFluxAPI, Log, TEXT("Client Disconnected %i"), i); +// i++; +// } +// }); +// ClientWebSocket->SetSocketClosedCallBack(InfoCallBack); +// FWebSocketInfoCallBack InfoCallBackError; +// InfoCallBackError.BindUObject(this, &UDTFluxWebsocketServer::OnConnectionErrorInternal); +// ClientWebSocket->SetErrorCallBack(InfoCallBackError); +// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketServer', [Connected], Channel: %s"), *ChannelName); +// } +// +// void UDTFluxWebsocketServer::OnConnectionErrorInternal() +// { +// UE_LOG(LogDTFluxAPI, Error, TEXT("WebSocketServer'")); +// OnConnectionError.Broadcast(TEXT("Channel"), TEXT("UNKNOWN ERROR")); +// +// } +// +// void UDTFluxWebsocketServer::OnReceivedMessageInternal(void* Data, int32 Count) +// { +// FString Message; +// if (Count == 0) // nothing to process +// { +// return; +// } +// const uint8* DataRef = reinterpret_cast(Data); +// const TArray MessageData(DataRef, Count); +// const FString JSonData = UTF8_TO_TCHAR(MessageData.GetData()); +// OnReceivedMessage.Broadcast(ChannelName, Message); +// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketServer, [Message Reveived]\nMessage : %s"), *Message); +// +// } +// +// TUniquePtr UDTFluxWebsocketServer::GetServer() +// { +// return FModuleManager::Get().LoadModuleChecked(TEXT("WebSocketNetworking")). +// CreateServer(); +// } +// +// void UDTFluxWebsocketServer::Close() +// { +// UE_LOG(LogDTFluxAPI, Log, TEXT("WebSocketServer, [Closed()]")); +// +// } + + +#include "DTFluxWebSocket/DTFluxWebsocketServer.h" +//Fill out your copyright notice in the Description page of Project Settings. + +#include "INetworkingWebSocket.h" +#include "IWebSocketNetworkingModule.h" +#include "WebSocketNetworkingDelegates.h" +#include "DTFluxAPILog.h" + + +UDTFluxServerWebSocket::UDTFluxServerWebSocket() +{ +} + +UDTFluxServerWebSocket::~UDTFluxServerWebSocket() +{ +} + +void UDTFluxServerWebSocket::Init(const int& Port, const FString& Url) +{ + ServerWebSocket = FModuleManager::Get().LoadModuleChecked(TEXT("WebSocketNetworking")).CreateServer(); + + FWebSocketClientConnectedCallBack CallBack; + CallBack.BindLambda([this](INetworkingWebSocket* Client) + { + + FGuid uuid = FGuid::NewGuid(); + ConnectedClients.Add(uuid, Client); + }); + + if (!ServerWebSocket->Init(Port, CallBack, Url)) + { + UE_LOG(LogDTFluxAPI, Error, TEXT("ServerWebSocket Init FAIL")); + ServerWebSocket.Reset(); + CallBack.Unbind(); + return; + } + UE_LOG(LogDTFluxAPI, Log, TEXT("ServerWebSocket Connected to port %d"), Port); + + + TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this](float Time) + { + if (ServerWebSocket) + { + ServerWebSocket->Tick(); + return true; + } + else + { + return false; + } + })); +} + +void UDTFluxServerWebSocket::BeginDestroy() +{ + Super::BeginDestroy(); + Close(); + + if (TickHandle.IsValid()) + { + FTSTicker::GetCoreTicker().RemoveTicker(TickHandle); + TickHandle.Reset(); + } +} + +void UDTFluxServerWebSocket::OnWebSocketClientConnected(INetworkingWebSocket* ClientWebSocket) +{ + FWebSocketPacketReceivedCallBack CallBack; + CallBack.BindUObject(this, &UDTFluxServerWebSocket::ReceivedRawPacket); + UE_LOG(LogDTFluxAPI, Log, TEXT("ServerWebSocket Connected")); + + ClientWebSocket->SetReceiveCallBack(CallBack); +} + +void UDTFluxServerWebSocket::ReceivedRawPacket(void* Data, int32 Count) +{ + if (Count == 0) // nothing to process + { + return; + } + + const uint8* DataRef = reinterpret_cast(Data); + + const TArray MessageData(DataRef, Count); + + const FString JSonData = UTF8_TO_TCHAR(MessageData.GetData()); + UE_LOG(LogDTFluxAPI, Log, TEXT("ServerWebSocket received %s"), *JSonData); + OnJsonRecieved.Broadcast(JSonData); +} \ No newline at end of file diff --git a/Source/DTFluxAPI/Public/DTFluxDataStorage/DTFluxDataStorage.h b/Source/DTFluxAPI/Public/DTFluxDataStorage/DTFluxDataStorage.h new file mode 100644 index 0000000..9e7580c --- /dev/null +++ b/Source/DTFluxAPI/Public/DTFluxDataStorage/DTFluxDataStorage.h @@ -0,0 +1,96 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "DTFluxModel/DTFluxModel.h" +#include "UObject/Object.h" +#include "DTFluxDataStorage.generated.h" + +/** + * + */ + + +struct FDTFluxStageRanking; +struct FDTFluxTeam; +struct FDTFluxParticipant; +struct FDTFluxStage; +struct FDTFluxContest; + +UCLASS(BlueprintType, Category="DTFlux|Datastorage") +class DTFLUXAPI_API UDTFluxDataStorage : public UObject +{ + GENERATED_BODY() + friend FDTFluxContest; + friend FDTFluxStage; + +public: + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + bool UpdateDataStorage(const FString JsonPayload); + + UPROPERTY(BlueprintReadOnly, Category="DTFlux|DataStorage") + TArray Contests; + UPROPERTY(BlueprintReadOnly, Category="DTFlux|DataStorage") + int CurrentStageId = 0; + UPROPERTY(BlueprintReadOnly, Category="DTFlux|DataStorage") + int CurrentContestId = 0; + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + int CurrentContest() + { + if(IsInitialized()) + { + return CurrentContestId; + } + return -1; + }; + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + bool GetContest(FDTFluxContest& OutContest, const int& ContestId); + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + TArray GetStages(const int ContestId); + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + bool GetStage( FDTFluxStage& Stage,const int& StageId = -1); + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + TArray GetParticipants(const int ContestId = -1); + UFUNCTION() + FDTFluxParticipant GetParticipant(const int ContestID, const int ParticipantBib); + UFUNCTION(BlueprintCallable, Category="DTFlux|DataStorage") + TArray GetStageRanking(const int ContestId, const int StageId); + + UFUNCTION(BlueprintType, Category="DTFlux|Datastorage") + void AddOrUpdateContest(const FDTFluxContestResponse& ContestResponse); + UFUNCTION(BlueprintType, Category="DTFlux|Datastorage") + void AddOrUpdateParticipant(const FDTFluxTeamListItemResponse& TeamListItemResponse); + + UFUNCTION(BlueprintCallable, Category="DTFlux") + bool IsInitialized() + { + return Contests.Num() < 0; + } + + UFUNCTION(BlueprintCallable, Category="DTFlux|Datastorage") + bool GetParticipantByBib(const int Bib, FDTFluxParticipant& OutParticipant) + { + for(auto& Contest : Contests) + { + if(Contest.GetParticipant(Bib, OutParticipant)) + { + return true; + } + } + return false; + } + + + UFUNCTION(BlueprintCallable, Category="DTFlux") + void AddSplitSensorResult(FDTFluxSplitSensorItemResponse Response); + UFUNCTION(BlueprintCallable, Category="DTFlux") + void ResetStageId(){ CurrentStageId = 0; } + UFUNCTION(BlueprintCallable, Category="DTFlux") + void SetCurrentStage(int NewId){ CurrentStageId = NewId; } + UFUNCTION(BlueprintCallable, Category="DTFlux") + void GoToNextStage(); + UFUNCTION(BlueprintCallable, Category="DTFlux") + void ChangeCurrentContest(); + +}; diff --git a/Source/DTFluxAPI/Public/DTFluxModel/DTFluxModel.h b/Source/DTFluxAPI/Public/DTFluxModel/DTFluxModel.h index 3183ddd..6d013e7 100644 --- a/Source/DTFluxAPI/Public/DTFluxModel/DTFluxModel.h +++ b/Source/DTFluxAPI/Public/DTFluxModel/DTFluxModel.h @@ -3,421 +3,488 @@ #pragma once #include "CoreMinimal.h" +#include "DTFluxAPILog.h" #include "UObject/Object.h" #include "DTFluxModel.generated.h" -// Forward declarations -struct FDTFluxTeam; - USTRUCT(BlueprintType, Category="DTFlux|Model") -struct DTFLUXAPI_API FDTFLuxStartStageData +struct DTFLUXAPI_API FDTFluxPerson { GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Type; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Contest; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Stage; - - // Maybe this one can be made DATETIME - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString RealStartTime; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct DTFLUXAPI_API FDTFluxStartStagePayload -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Description; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Trigger; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Type; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Datas; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct DTFLUXAPI_API FDTFluxStartListItemPayload -{ - GENERATED_BODY() - - // "Report": "Start List", - // "ContestID": 0, - // "ContestName": "", - // "Bib": 1039, - // "Firstname": "Mystère", - // "Lastname": "Mystère", - // "Gender": "", - // "Club": "", - // "Firstname2": "", - // "Lastname2": "", - // "Gender2": "", - // "Club2": "", - // "TeamName": "", - // "Category": "", - // "Elite": false - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int ContestID; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString ContestName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Bib; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") FString FirstName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") FString LastName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") FString Gender; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Club; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString FirstName2; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString LastName2; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Gender2; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Club2; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString TeamName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Category; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - bool Elite; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FString FunctionLine1 = TEXT(""); + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FString FunctionLine2 = TEXT(""); }; -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct DTFLUXAPI_API FDTFluxStartListPayload -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Participants; - -}; - USTRUCT(BlueprintType, Category="DTFlux|Model") struct DTFLUXAPI_API FDTFluxParticipant { GENERATED_BODY() - public: - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString FirstName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString LastName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Club; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Gender; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + int Bib = -1; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FDTFluxPerson Person1; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FDTFluxPerson Person2; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") FString Category; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct DTFLUXAPI_API FDTFluxTeam -{ - GENERATED_BODY() - -public: - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Participants; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString TeamName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Bib; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FDTFluxContest -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int ContestID; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString ContestName; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray TeamParticipants; - - bool AddTeam(FDTFluxTeam Team) + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FString Club; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + bool Elite; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FString Status; + bool IsTeam() { - TeamParticipants.Add(Team); - return true; + return (Person2.FirstName != ""); } - bool TeamAlreadyExist(FDTFluxTeam Team) +}; + + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxContestRanking +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FDTFluxParticipant Participant; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + int Rank; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString Gap; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString Time; +}; + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxStageRanking +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FDTFluxParticipant Participant; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + int Rank; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString Gap; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString TimeSwim; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString TimeTransition; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString TimeRun; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString TimeStart; +}; + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxSplitGapItem +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FDTFluxParticipant Participant; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + int Gap; +}; +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxSplitGap +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + TArray SplitGapItems; +}; + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxSplit +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + int Id = -1; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model") + FString Name; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + TArray SplitGaps; + + void Dump() const; +}; + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxStage +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + int Id; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FString Name; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FDateTime StartTime; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FDateTime EndTime; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + TArray Splits; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + TArray StageRanking; + bool SetStartTime(const FDateTime& ContestDate, const FString& TimeString); + bool SetEndTime(const FDateTime& ContestDate, const FString& TimeString); + bool UpdateStageRanking(TArray> StageRankingData); + bool AddSplit(TArray> SplitData); + void Dump() const; + +protected: +}; + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxContest +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + int Id = -1; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FString Name; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + TArray Participants; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + TArray Stages; + UPROPERTY(BlueprintReadWrite, Category="DTFlux|model") + FDateTime Date; + + bool AddStage( const TArray> StagesData, const TArray Splits); + bool SetDate(const FString& StringDate); + void AddParticipant(const FDTFluxParticipant& Participant) { - for(const auto& MyTeam : TeamParticipants) + if(Participants.IsEmpty()) { - // Compare Bib - if(MyTeam.Bib == Team.Bib ) + Participants.Add(Participant); + return; + } + int Index = 0; + FDTFluxParticipant ToUpdate; + bool Update = false; + for(auto P : Participants) + { + if(P.Bib == Participant.Bib) { + ToUpdate = P; + Update = true; + break; + } + else + { + Index++; + } + } + if(Update) + { + Participants.RemoveAt(Index); + } + Participants.Add(Participant); + }; + + bool GetParticipant(const int Bib, FDTFluxParticipant& OutParticipant) + { + for (auto& Participant : Participants) + { + if(Participant.Bib == Bib) + { + OutParticipant = Participant; return true; } } return false; } + + void DumpParticipant() + { + int Num = 0; + for(const auto& Participant: Participants ) + { + UE_LOG(LogDTFluxAPI, Log, TEXT("DUMP Participant : Name -> %s Bib %d"), *Participant.Person1.FirstName, Participant.Bib); + Num ++; + } + UE_LOG(LogDTFluxAPI, Log, TEXT("DUMP Participant : In Contest with ID %d there are %d Participant(s)"), Id, Num); + + }; +}; + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxSplitSensorItemResponse +{ + GENERATED_BODY() + UPROPERTY() + int Bib; + UPROPERTY() + FString Type = "split-sensor-item"; + UPROPERTY() + int ContestID; + UPROPERTY() + int StageID; + UPROPERTY() + int SplitID; + UPROPERTY() + FString Time; +}; + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxSplitSensorResponse +{ + GENERATED_BODY() + UPROPERTY() + FString Type = "split-sensor"; + UPROPERTY() + TArray Datas; +}; + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxTeamListItemResponse +{ + GENERATED_BODY() +public: + UPROPERTY() + FString Type = "team-list-item"; + UPROPERTY() + int ContestId; + UPROPERTY() + int Bib; + UPROPERTY() + FString FirstName; + UPROPERTY() + FString LastName; + UPROPERTY() + FString FirstName2 = ""; + UPROPERTY() + FString LastName2 = ""; + UPROPERTY() + FString Team = ""; + UPROPERTY() + FString Gender; + UPROPERTY() + bool Elite; + UPROPERTY() + FString Category; + UPROPERTY() + FString Status; + UPROPERTY() + FString Club; }; USTRUCT(BlueprintType, Category="DTFlux|Model") -struct DTFLUXAPI_API FDTFluxContestList +struct DTFLUXAPI_API FDTFluxTeamListResponse { GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TMapContests; - - int GetContestId(const FString& ContestName) const - { - const FDTFluxContest* Contest = Contests.Find(ContestName); - if(Contest) - { - return Contest->ContestID; - } - return -1; - } - FString GetContestName(const int& ContestId) const - { - for (const auto& Contest : Contests) - { - if (Contest.Value.ContestID == ContestId) - { - return Contest.Key; - } - } - return FString(); - } +public: + UPROPERTY() + FString Type = "team-list"; + UPROPERTY() + TArray Datas; + }; -UCLASS(Blueprintable, Category="DTFlux|Model") -class DTFLUXAPI_API UDTFluxContestBPFn : public UBlueprintFunctionLibrary +USTRUCT() +struct DTFLUXAPI_API FDTFluxTeamUpdateResponse +{ + GENERATED_BODY() + UPROPERTY() + FString Type = "team-update"; + UPROPERTY() + TArray Datas; + +}; + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FStageResponse +{ + GENERATED_BODY() +public: + UPROPERTY() + FString Type = "stage-response-data"; + UPROPERTY() + int Id; + UPROPERTY() + FString Name; + UPROPERTY() + FString StartTime; + UPROPERTY() + FString EndTime; +}; + + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FSplitResponse +{ + GENERATED_BODY() +public: + UPROPERTY() + FString Type = "split-response-data"; + UPROPERTY() + int Id; + UPROPERTY() + FString Name; +}; + +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxContestResponse +{ + GENERATED_BODY() +public: + UPROPERTY() + FString Type = "contest"; + UPROPERTY() + int Id; + UPROPERTY() + FString Name; + UPROPERTY() + FString Date; + UPROPERTY() + TArray Stages; + UPROPERTY() + TArray Splits; +}; + +USTRUCT() +struct DTFLUXAPI_API FDTFluxTeamContestRankingResponse { GENERATED_BODY() public: - UFUNCTION(BlueprintPure, Category = "DT Http Server|Params") - static int GetContestId(const FDTFluxContestList& ContestList, const FString& ContestName) - { - return ContestList.GetContestId(ContestName); - } - UFUNCTION(BlueprintPure, Category = "DT Http Server|Params") - static FString GetContestName(const FDTFluxContestList& ContestList, const int& ContestId) - { - return ContestList.GetContestName(ContestId); - } + UPROPERTY() + FString Type = "team-contest-ranking"; + UPROPERTY() + int Bib; + UPROPERTY() + int Rank; + UPROPERTY() + FString Time; + UPROPERTY(); + FString Gap; }; -// TeamListItem Response from proxy Containing Team definition -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FResponseTeamListItem +USTRUCT() +struct DTFLUXAPI_API FDTFluxContestRankingResponse { GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Contest; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) + +public: + UPROPERTY() + FString Type = "contest-ranking"; + UPROPERTY() + int ContestID; + UPROPERTY() + TArray Datas; +}; + + +USTRUCT() +struct DTFLUXAPI_API FDTFluxTeamStageRankingResponse +{ + GENERATED_BODY() + +public: + UPROPERTY() + FString Type = "team-stage-ranking"; + UPROPERTY() int Bib; + UPROPERTY() + int Rank; + UPROPERTY() + FString Time; + UPROPERTY(); + FString Gap; + UPROPERTY() + FString TimeSwim; + UPROPERTY(); + FString TimeTransition; + UPROPERTY() + FString TimeRun; + UPROPERTY(); + FString TimeStart; +}; - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString FirstName; +USTRUCT() +struct DTFLUXAPI_API FDTFluxStageRankingResponse +{ + GENERATED_BODY() - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString LastName; +public: + UPROPERTY() + FString Type = "stage-ranking"; + UPROPERTY() + int ContestID; + UPROPERTY() + int StageID; + UPROPERTY() + int SplitID; + UPROPERTY() + TArray Datas; +}; - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Gender; +USTRUCT(BlueprintType, Category="DTFlux|Model") +struct DTFLUXAPI_API FDTFluxRaceDataResponse +{ + GENERATED_BODY() +public: - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - bool Elite; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Club; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Team; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Category; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) + UPROPERTY() + FString Type = "race-datas"; + UPROPERTY() + TArray Datas; +}; + +USTRUCT() +struct DTFLUXAPI_API FDTFluxStatusTeamUpdateResponse +{ + GENERATED_BODY() + UPROPERTY() + FString Type = "status-team-update"; + UPROPERTY() + int Bib; + UPROPERTY() FString Status; }; -// TeamList Response from proxy Containing List of Team -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseTeamList + +USTRUCT() +struct DTFLUXAPI_API FDTFluxStatusUpdateResponse { GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Datas; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseStageListItem -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Id; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Name; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString StartTime; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString EndTime; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseSplitsListItem -{ - GENERATED_BODY() - - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Id; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Name; + UPROPERTY() + FString Type = "status-update"; + TArray Datas; }; -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseRaceListItem +UCLASS(BlueprintType, Category="DTFlux|Model|Helpers") +class DTFLUXAPI_API UDTFluxModelHelper : public UBlueprintFunctionLibrary { GENERATED_BODY() - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Id; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Name; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Date; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Stages; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Splits; - +public: + UFUNCTION(BlueprintCallable, Category="DTFlux|Model") + static bool IsParticipantATeam(const FDTFluxParticipant& Participant) + { + return Participant.Person2.FirstName != ""; + } }; -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseContestRankingListItem -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Bib; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Rank; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Time; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Gap; - -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseContestRankingList -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int ContestId; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Datas; -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseStageRankingListItem -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Bib; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int Rank; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Time; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString Gap; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString TimeSwim; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString TimeTransition; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString TimeRun; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - FString TimeStart; - -}; - -USTRUCT(BlueprintType, Category="DTFlux|Model") -struct FProxyResponseStageRankingList -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int ContestId; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - int StageId; - - UPROPERTY(Blueprintable, Category="DTFlux|Model", BlueprintReadOnly) - TArray Datas; - -}; \ No newline at end of file diff --git a/Source/DTFluxAPI/Public/DTFluxProjectSettings/DTFluxProjectSettings.h b/Source/DTFluxAPI/Public/DTFluxProjectSettings/DTFluxProjectSettings.h index 0c40be1..468e65e 100644 --- a/Source/DTFluxAPI/Public/DTFluxProjectSettings/DTFluxProjectSettings.h +++ b/Source/DTFluxAPI/Public/DTFluxProjectSettings/DTFluxProjectSettings.h @@ -7,87 +7,11 @@ #include "DTFluxAPILog.h" #include "DTFluxProjectSettings.generated.h" -UENUM() -enum EDTFluxAPIRoute: uint8 -{ - Root UMETA(DisplayName="API Root"), - Results UMETA(DisplayName="Results Route"), - Starters UMETA(DisplayName="Starters List Route"), - FinalClassification UMETA(DisplayName="Final Classification Route"), +DECLARE_MULTICAST_DELEGATE_TwoParams(OnDTFluxProjectSettingsModified, FString, const UDTFluxProjectSettings* ); -}; - - -UENUM() -enum EDTFluxProxyRoute : uint8 -{ - ProxyRaceData UMETA(DisplayName="Proxy Race Data"), - ProxyRankingContest UMETA(DisplayName="Proxy Ranking Contest Data"), - ProxyRankingStage UMETA(DisplayName="Proxy Ranking Stage Data"), - ProxyTeams UMETA(DisplayName="Proxy Teams Data"), -}; - -USTRUCT(BlueprintType) -struct FSearchFilters -{ - GENERATED_BODY() - - UPROPERTY(Blueprintable, Category="DTFlux|Config", EditAnywhere) - int ContestId = 0; - - UPROPERTY(Blueprintable, Category="DTFlux|Config", EditAnywhere) - int StageId = 0; - - UPROPERTY(Blueprintable, Category="DTFlux|Config", EditAnywhere) - int GenderId = 0; - - FString GetFilter() const - { - if(ContestId == 0 && StageId == 0 && GenderId == 0) - { - UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Gender Test -> %i"), GenderId); - return FString(); - } - TArray Filters; - Filters.Add(FString("?")); - if (ContestId > 0) - { - Filters.Add(FString::Printf(TEXT("Contest=%i"), ContestId) ); - } - if (StageId > 0) - { - Filters.Add(FString::Printf(TEXT("SelectorResult=%i"), StageId) ); - } - if (GenderId > 0) - { - switch(GenderId) - { - case (2): - // default is male - UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Gender Female -> %i"), GenderId); - - Filters.Add(FString(TEXT("filter=[Gender]=\"f\"") ) ); - break; - default: - // default is male - UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Gender male -> %i"), GenderId); - Filters.Add(FString(TEXT("filter=[Gender]=\"m\"") ) ); - break; - } - } - for(const auto& Element : Filters) - { - UE_LOG(LogDTFluxAPI, Type::Log, TEXT("Element : %s"), *Element); - - } - FString FinalFilters = FString::Join(Filters, TEXT("&")); - FinalFilters.RemoveAt(1); - return FinalFilters; - }; -}; /** - * + * DTFlux project settings */ UCLASS(Blueprintable, Config=Engine, DefaultConfig, meta=(DisplayName="DTFlux Project Settings")) class DTFLUXAPI_API UDTFluxProjectSettings : public UDeveloperSettings @@ -96,21 +20,16 @@ class DTFLUXAPI_API UDTFluxProjectSettings : public UDeveloperSettings public: - - UPROPERTY(Category="DTFlux|Server Config", Config, EditAnywhere, BlueprintReadOnly) - int InPort = 8080; + OnDTFluxProjectSettingsModified OnProjectSettingsModified; UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly) int RaceResultPort = 80; - UPROPERTY(Category="DTFlux|Server Config", Config, EditAnywhere, BlueprintReadOnly) - TArray Endpoints; + UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly) + FString RaceResultUrl = "http://localhost"; UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly) - FString ProxyUrl = "http://localhost"; - - UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly) - FString APIToken; + FString RaceResultSessionID; UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly) bool bAccessIsLocal = true; @@ -123,28 +42,47 @@ public: UPROPERTY(Category="DTFlux|Config|Race Result API", Config, EditAnywhere, BlueprintReadOnly) FString LiveStageResultsAccessToken ; - +// Proxy + + UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) + int WebsocketServerPort = 3000; + + UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) + FString WebsocketServerAddress = "ws://127.0.0.1"; + UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) FString ProxyAddress = "http://localhost"; + + UPROPERTY(Category="DTFlux|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) + int ProxyPort = 8000; UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) FString ProxyRootPath = "/endpoints"; + UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) + FString ProxyRaceDataEndpoint; + UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) + FString ProxyRankingEndpoint; + UPROPERTY(Category="DTFlux|Config|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) + FString ProxyTeamsEndpoint; + - UPROPERTY(Category="DTFlux|Chrono Proxy", Config, EditAnywhere, BlueprintReadOnly) - int ProxyPort = 8000; + //Server Config ****NOT USED**** - UFUNCTION(BlueprintCallable, Category="DTFlux|Config") - FString GetAPIPath(const TEnumAsByte RouteType, const FString& Filters = TEXT("") ) const; - UFUNCTION(BlueprintCallable, Category="DTFlux|Config") - FString GetAPIPathFiltered(const TEnumAsByte RouteType, const FSearchFilters& Filters ) const; - - UFUNCTION(BlueprintCallable, Category="DTFlux|Config") - FString GetProxyPath(const TEnumAsByte RouteType, const int& InContest = -1, const int& InStage = -1) const; + UPROPERTY(Category="DTFlux|Server Config", Config, EditAnywhere, BlueprintReadOnly) + int InPort = 8080; + UFUNCTION(BlueprintCallable, Category="DTFlux|Config") static const UDTFluxProjectSettings* GetDTFluxAPIProjectSettings(); + + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + protected: diff --git a/Source/DTFluxAPI/Public/DTFluxSubsystem/DTFluxSubsystem.h b/Source/DTFluxAPI/Public/DTFluxSubsystem/DTFluxSubsystem.h index 2e3e920..1318518 100644 --- a/Source/DTFluxAPI/Public/DTFluxSubsystem/DTFluxSubsystem.h +++ b/Source/DTFluxAPI/Public/DTFluxSubsystem/DTFluxSubsystem.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" #include "Runtime/Engine/Public/Subsystems/EngineSubsystem.h" +#include "DTFluxWebSocket/DTFluxWebsocketServer.h" + #include "HttpServerRequest.h" #include "HttpResultCallback.h" #include "HttpRouteHandle.h" @@ -11,11 +13,13 @@ #include "DTFluxUtils/DTFluxHttpServerStruct.h" #include "DTFluxAPILog.h" +#include "DTFluxDataStorage/DTFluxDataStorage.h" #include "DTFluxModel/DTFluxModel.h" - +#include "DTFluxWebSocket/DTFluxWebsocketClient.h" #include "DTFluxSubsystem.generated.h" +class UDTFluxDataStorage; class UDTFluxProjectSettings; class IHttpRouter; @@ -35,6 +39,19 @@ enum EDTFluxResponseErrorCode Internal_Error UMETA(DisplayName="Internal Server Error") }; +UENUM(BlueprintType, Category="DTFlux|Subsystem") +enum EDTFluxResponseType: uint8 +{ + UnknownResponse = 0 UMETA(DisplayName="UnknownResponse"), + RaceData = 1 UMETA(DisplayName="RaceData"), + ContestRanking = 2 UMETA(DisplayName="ContestRanking"), + StageRanking = 3 UMETA(DisplayName="StageRanking"), + SplitRanking = 4 UMETA(DisplayName="SplitRanking"), + TeamList = 5 UMETA(DisplayName="TeamList"), + TeamUpdate = 6 UMETA(DisplayName="TeamUpdate"), + SplitSensor = 7 UMETA(DisplayName="SplitSensor"), + StatusUpdate = 8 UMETA(DisplayName="StatusUpdate"), +}; USTRUCT(BlueprintType, Category="DTFlux|Server") struct FDTFluxResponseBody @@ -62,92 +79,178 @@ FString Deserialize() }; +USTRUCT() +struct FDTFluxSubsystemAPISettings +{ + GENERATED_BODY() +public: + FString WebsocketAddress = "ws://localhost"; + int WebsocketPort = 3000; + FString ProxyAddress = "http://localhost"; + int ProxyPort = 80; + //TODO : Maybe we must make a dedicated struct with enum to make endpoints more clean. + TMap ProxyEndpoints; + static FString GetRaceDataEndpoint(const FDTFluxSubsystemAPISettings* Settings); + static FString GetContestRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId); + static FString GetStageRankingEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId, const int StageId); + static FString GetStageRankingFilteredEndpoint(const FDTFluxSubsystemAPISettings* Settings, const int ContestId, const int StageId, const FString SplitName); + static FString GetTeamsEndpoint(const FDTFluxSubsystemAPISettings* Settings); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FDTFluxOnRequestReceived, FDTFluxHttpServerHeaders, HttpServerHeaders, - FDTFluxHttpServerParams, HttpServerParams, FDTFluxHttpServerBody, HttpRequestBody); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDTFluxOnServerListening); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDTFluxOnServerStopped); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDTFluxOnEventStartReceived, FDTFluxStartStagePayload, Payload); +private: + FString GetProxyBaseEndpoint() const + { + return FString::Printf(TEXT("%s:%i"), *ProxyAddress, ProxyPort); + }; +}; +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWsConnected); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWsIncomingData, FString, JsonData); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWsError, FString, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWsClosed, FString, Reason); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTimerTriggered); +DECLARE_DYNAMIC_DELEGATE_OneParam(FOnTimer, FString, TimerName); /** * DTFlux API Subsystem * - * This Subsystem Mount HTTP routes to be listened and an HTTP poller to retrieve basic information. + * This Subsystem set up a Websocket server and Listen to incoming events and poll some http request to Proxy when needed. + * it handles a datastore where data are being saved and present to blueprint or actors. */ -UCLASS() -class DTFLUXAPI_API UDTFluxSubsystem : public UEngineSubsystem +UCLASS(BlueprintType, Category="DTFlux|API Subsystem") +class DTFLUXAPI_API UDTFluxSubsystem : public UEngineSubsystem, public FTickableGameObject { GENERATED_BODY() private: - bool bIsListening = false; - const UDTFluxProjectSettings* mSettings; - - bool OnRequest(const FHttpServerRequest& Request); + static const UDTFluxProjectSettings* GetSettings(); + int WebSocketPort = 0; + FDTFluxSubsystemAPISettings SubSettings; - void HandleRequest(const FString& Route, const FHttpServerRequest& Request, FHttpResultCallback OnComplete); - // // StartList Payload; - // FDTFluxStartListPayload StartList; - // Contest storage - FDTFluxContestList Contests; - - void OnUpdateStartList(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); + UPROPERTY() + UDTFluxWebSocketClient* WsClient; + UPROPERTY() + UDTFluxDataStorage* DataStorage; + virtual void Tick(float DeltaTime) override; + virtual bool IsTickableInEditor() const override + { + return true; + } + virtual TStatId GetStatId() const override + { + RETURN_QUICK_DECLARE_CYCLE_STAT(UDTFluxSubsystem, STATGROUP_Tickables); + } + + + protected: + UFUNCTION() + void WsSplitSensorReceivedInternal(); + UFUNCTION() + void WsTeamUpdateReceivedInternal(); + UFUNCTION() + void WsStatusUpdateReceivedInternal(); + UFUNCTION() + void RequestRaceDatas(); + UFUNCTION() + void RequestTeamList(); + UFUNCTION() + void RequestContestRanking(const int ContestId); + UFUNCTION() + void RequestStageRanking(const int ContestId, const int StageId); + UFUNCTION() + void RequestSplitGaps(const int ContestId, const int StageId, const int SplitId); + UPROPERTY() + FDateTime TestTimer; + UFUNCTION() + void BroadcastTimerEvent(); + UPROPERTY() + TMap Timer; - TMap HttpMountedMap; - TSharedPtr HttpRouter; - // Stop The Server - void StopServer(); - // Create the server response - TUniquePtr CreateHttpServerResponse() const; - FHttpModule* HttpRequest; - - public: /** Implement this for initialization of instances of the system */ virtual void Initialize(FSubsystemCollectionBase& Collection) override; /** Implement this for deinitialization of instances of the system */ virtual void Deinitialize() override; - - UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events") - FDTFluxOnRequestReceived OnRequestReceived; - - UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events") - FDTFluxOnServerListening OnServerListening; - - UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events") - FDTFluxOnServerStopped OnServerStopped; - - UPROPERTY(BlueprintAssignable, Category="DTFlux|Subsystem|Events") - FDTFluxOnEventStartReceived OnEventStartReceived; + UFUNCTION(BlueprintCallable, Category="DTFluxAPI | Subsytem") + bool ReloadSubsystem(); + + void LoadConfig(const UDTFluxProjectSettings* Settings); + + UPROPERTY(BlueprintAssignable, Category="DTFlux|Events") + FOnTimerTriggered OnTimerTriggered; + UPROPERTY(BlueprintAssignable, Category="DTFlux|Events") + FOnWsIncomingData OnWsIncomingData; + UPROPERTY(BlueprintAssignable, Category="DTFlux|Events") + FOnWsConnected OnWsConnected; + UPROPERTY(BlueprintAssignable, Category="DTFlux|Events") + FOnWsError OnWsError; + UPROPERTY(BlueprintAssignable, Category="DTFlux|Events") + FOnWsClosed OnWsClosed; + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|WebSocket") + bool Reconnect(); + + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|WebSocket") + bool AddTimer(FDateTime Time, FOnTimer NewTimer); UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") - TArray GetMountedRoutes() const; + void SetTimerEvent(const FDateTime& When); UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") - void StartServer(); - - UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Events") - TArray GetParticipantsByContestId(const int ContestId ); + void UpdateRaceData(); + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") + void UpdateTeamList(); + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") + void UpdateTeam(); + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") + void UpdateContestRanking(const int ContestID); + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") + void UpdateStageRanking(const int ContestID, const int StageID, const int SplitID = -1); - UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Events") - TArray GetParticipantsByContestName(const FString ContestName); + UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem") + UDTFluxDataStorage* GetDataStorage() + { + return DataStorage; + }; - UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Events") - FString GetContestName(const int ContestId); - UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Race Result Call") - void UpdateStartList(); + UFUNCTION() + void ProcessTeamListResponse(const FDTFluxTeamListResponse& TeamListResponse); + UFUNCTION() + void ProcessRaceDataResponse(const FDTFluxRaceDataResponse& DataResponse); + UFUNCTION() + void ProcessContestRankingResponse(const FDTFluxContestRankingResponse& ContestRankingResponse); + UFUNCTION() + void ProcessStageRankingResponse(const FDTFluxStageRankingResponse& StageRankingResponse); + UFUNCTION() + void ProcessSplitRankingResponse(const FDTFluxStageRankingResponse& StageRankingResponse); + UFUNCTION() + void ProcessTeamUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse); + UFUNCTION() + void ProcessStatusUpdateResponse(const FDTFluxTeamUpdateResponse& TeamUpdateResponse); + UFUNCTION() + void ProcessSplitSensor(const FDTFluxSplitSensorResponse& SplitSensorResponse); - UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Race Result Call") - void UpdateClassification(const int& ContestId, const int& StageId = -1); + TSharedPtr GetData(EDTFluxResponseType Type, const FString& Message); - UFUNCTION(BlueprintCallable, Category="DTFlux|Subsystem|Race Result Call") - TArray GetClassification(const int& ContestId, const int& StageId = -1); + UFUNCTION() + EDTFluxResponseType FindResponseType(const FString& MessageReceived); + + UFUNCTION() + void WsConnected(); + UFUNCTION() + void WsReceivedMessage(const FString& MessageReceived); + UFUNCTION() + void WsConnectionClosed(const FString& Reason); + UFUNCTION() + void WsConnectionError(const FString& Error); + + UFUNCTION(BlueprintCallable, Category="DTFlux|subsystem") + bool IsConnected() const; + + }; diff --git a/Source/DTFluxAPI/Public/DTFluxWebSocket/DTFluxWebsocketClient.h b/Source/DTFluxAPI/Public/DTFluxWebSocket/DTFluxWebsocketClient.h new file mode 100644 index 0000000..a65b544 --- /dev/null +++ b/Source/DTFluxAPI/Public/DTFluxWebSocket/DTFluxWebsocketClient.h @@ -0,0 +1,62 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "IWebSocket.h" +#include "UObject/Object.h" +#include "DTFluxWebsocketClient.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConnectionConnected); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionClosed, const FString&, Reason); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionError, const FString&, Error); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMessage, const FString&, Message); + + +UCLASS(Blueprintable, Category="DTFlux|Websocket") +class DTFLUXAPI_API UDTFluxWebSocketClient : public UObject +{ + GENERATED_BODY() + +public: + + // UDTFluxWebSocketClient() = default; + void Initialize(); + // virtual void BeginDestroy() override; + // virtual ~UDTFluxWebSocketClient() override; + + UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket") + bool Connect(const FString URL, const int Port ); + + UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket") + void Disconnect(); + + UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket") + bool SendMessage(const FString Message, const bool Broadcast = false); + + UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") + FOnConnectionConnected OnConnectionConnected; + + UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") + FOnConnectionClosed OnConnectionClosed; + + UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") + FOnConnectionError OnConnectionError; + + UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") + FOnReceivedMessage OnReceivedMessage; + + bool Close() const; + bool IsConnected(); + +protected: + + +private: + void OnConnectionConnectedInternal() const; + void OnConnectionErrorInternal(const FString& Error) const; + void OnConnectionClosedInternal(const FString& Reason) const; + void OnReceivedMessageInternal(const FString& Message) const; + + TSharedPtr Ws; +}; diff --git a/Source/DTFluxAPI/Public/DTFluxWebSocket/DTFluxWebsocketServer.h b/Source/DTFluxAPI/Public/DTFluxWebSocket/DTFluxWebsocketServer.h new file mode 100644 index 0000000..5dafc3b --- /dev/null +++ b/Source/DTFluxAPI/Public/DTFluxWebSocket/DTFluxWebsocketServer.h @@ -0,0 +1,131 @@ +// // Fill out your copyright notice in the Description page of Project Settings. +// +// #pragma once +// +// #include "CoreMinimal.h" +// #include "UObject/Object.h" +// #include "IWebSocketServer.h" +// #include "DTFluxWebsocketServer.generated.h" +// +// +// +// DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionConnected, const FString&, Channel); +// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnConnectionClosed, const FString&, Channel, const FString&, Reason); +// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnConnectionError, const FString&, Channel, const FString&, Error); +// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnReceivedMessage, const FString&, Channel, const FString&, Message); +// class INetworkingWebSocket; +// +// UCLASS(Blueprintable, Category="DTFlux|Websocket") +// class DTFLUXAPI_API UDTFluxWebsocketServer : public UObject +// { +// GENERATED_BODY() +// +// public: +// +// UDTFluxWebsocketServer(); +// +// +// +// void Initialize(const FString& Channel) +// { +// ChannelName = Channel; +// }; +// +// virtual ~UDTFluxWebsocketServer() override; +// +// UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket") +// bool ConnectToChannel(const FString URL, const int Port ); +// +// UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket") +// void LeaveChannel(); +// +// UFUNCTION(BlueprintCallable, Category="DTFlux|Websocket") +// bool SendMessageToAll(const FString Message); +// +// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") +// FOnConnectionConnected OnConnectionConnected; +// +// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") +// FOnConnectionClosed OnConnectionClosed; +// +// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") +// FOnConnectionError OnConnectionError; +// +// UPROPERTY(BlueprintAssignable, Category="DTFlux|Websocket") +// FOnReceivedMessage OnReceivedMessage; +// +// UPROPERTY(BlueprintReadOnly, Category="DTFlux|Websocket") +// FString ChannelName; +// +// void Close(); +// +// +// protected: +// +// +// +// private: +// +// bool bIsConnected = false; +// void OnConnectionConnectedInternal(INetworkingWebSocket* ClientWebSocket); +// void OnConnectionErrorInternal(); +// // void OnConnectionClosedInternal(const FString& Reason); +// void OnReceivedMessageInternal(void* Data, int32 Count); +// +// static TUniquePtr GetServer(); +// +// TArray Clients; +// // TSharedPtr Ws; +// TUniquePtr Ws; +// }; + +#pragma once + +#include "IWebSocketServer.h" +#include "Subsystems/GameInstanceSubsystem.h" +#include "DTFluxWebsocketServer.generated.h" + + + +UCLASS(Blueprintable, Category="DTFlux|Websocket") +class DTFLUXAPI_API UDTFluxServerWebSocket : public UObject +{ + GENERATED_BODY() + +public: + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnConnectionConnected); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionClosed, const FString&, Reason); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnConnectionError, const FString&, Error); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnReceivedMessage, const FString&, Message); + + + UDTFluxServerWebSocket(); + virtual ~UDTFluxServerWebSocket() override; + + void Init(const int& Port, const FString& Url = TEXT("ws://localhost")); + virtual void BeginDestroy() override; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnJsonRecieved, const FString&, Payload); + + UPROPERTY(BlueprintAssignable) + FOnJsonRecieved OnJsonRecieved; + void Close() + { + ServerWebSocket = nullptr; + + }; + +protected: + + void OnWebSocketClientConnected(INetworkingWebSocket* ClientWebSocket); // to the server. + + virtual void ReceivedRawPacket(void* Data, int32 Count); + + +private: + + TUniquePtr ServerWebSocket; + TMap ConnectedClients; + /** Delegate for callbacks to GameThreadTick */ + FTSTicker::FDelegateHandle TickHandle; +};