From fd85e2242839f38a06963483c9e15b604e67fb55 Mon Sep 17 00:00:00 2001 From: Jamie Greunbaum Date: Fri, 31 Mar 2023 14:47:16 -0400 Subject: [PATCH] - ServerREST renamed to ServerAPI because it makes a lot more sense that way. - A bunch of files converted from UTF-16 to UTF-8. --- .../Private/BugMarkerSubsystem.cpp | Bin 8324 -> 4077 bytes Source/Unrealzilla/Private/ServerAPI.cpp | 609 ++++++++++++++++++ Source/Unrealzilla/Private/ServerREST.cpp | Bin 41154 -> 0 bytes .../Private/UnrealzillaGlobalSettings.cpp | 2 +- .../{JSON => API}/BugzillaJSONStructs.h | 2 +- .../Public/{JSON => API}/JiraJSONStructs.h | 2 +- .../Public/API/UnrealzillaBugDataStructs.h | 113 ++++ Source/Unrealzilla/Public/BugMarkerActor.h | 2 +- .../Unrealzilla/Public/BugMarkerSubsystem.h | Bin 3820 -> 1945 bytes Source/Unrealzilla/Public/ServerAPI.h | 68 ++ Source/Unrealzilla/Public/ServerREST.h | Bin 10294 -> 0 bytes 11 files changed, 794 insertions(+), 4 deletions(-) create mode 100644 Source/Unrealzilla/Private/ServerAPI.cpp delete mode 100644 Source/Unrealzilla/Private/ServerREST.cpp rename Source/Unrealzilla/Public/{JSON => API}/BugzillaJSONStructs.h (98%) rename Source/Unrealzilla/Public/{JSON => API}/JiraJSONStructs.h (55%) create mode 100644 Source/Unrealzilla/Public/API/UnrealzillaBugDataStructs.h create mode 100644 Source/Unrealzilla/Public/ServerAPI.h delete mode 100644 Source/Unrealzilla/Public/ServerREST.h diff --git a/Source/Unrealzilla/Private/BugMarkerSubsystem.cpp b/Source/Unrealzilla/Private/BugMarkerSubsystem.cpp index b0a2a52cf4c869e694b603334a20e8fffa71e087..ab816be7c9123f77d98718d3576edc29fdf16266 100644 GIT binary patch literal 4077 zcmd5<&2Hm15I);a!MbQ5EgWr-qc&?mcI+gr9UGS2CMk-5k?2?rEgBS^Y_`Y)^%?e1 zpfA){=#Ug;{n?u=dg#d%XNEKL&9DFY^4I(K=y6iEE37T*gZUPap zIBBEdu;(C;QZ$Y3mm)z^k`Vq#LZ{hmejmjF-Gl_Sx|{nE<`0DXo4e#W5oF~oTeS>c zAQ+d#t?;=J>k+7=qxOA6#7aZ-?Wv+nzV;pN*&2R@M9=ySG1pi;pY{(Y#7`-HSL95vI{+Vhu8{ zdyG=B7oh9ngrI}%x83}s*@za%`py!|C^_t8#-oqul00cgPv+TmNAcOV-E4qlW3EiD zx3rD=oHO1=v;Ni0YW3KLhA3tN1ptH~NWX~yC=^hfBu>i)=Nd1~m%SePOZJp`o>-X& z+WsxYxk&n}wRpCSttAH!JHUI21%}*NLsAi^8^z&Vd#t={qk~$EOL9F-=^YLpr16c4 z5{1E=RTQN5E0_|o;qfp^#CQRg0U`#(-C1%Tg=CAA#!y>+M#L|S)6lXHJ8+&wE5b)O z#`lCZqNX%o( zaHw@8%3+yQssb8!kj)QL+l$QIw3^CZ>`2D_XhVa@W37#Vrk4Y|t?=5FsKqdAUKQ4S zh))R>*e3ytL&%+1B+rDCHRc4m%PL9|XetB7SF(`ud6YGb!;T@pxd^17RAvP8b)W&x zX(?39t4p~cwfFthYiW~AE{OE*@>0+U#R{+{0u6HMHDQU6XrTjhQZ20F5G3q3b*IVL zpZzZo^GO_<*jt&e3c5YmmE1Nlu{4k~k8}KdlE*U@=%51$R}M=cs8WTdA}LU!8sj09 zvgunol)EEY4;;oGhS~qV4G;8e|{VV9KOk7LPDg!zb4r;RiC{Y;|K9FKkEgFxX4BdD=z;9Vgcv z$3!2M;*!~wg(E+S{WX4y6$}+vEafMxw8jDTxLH16V5c=HH)7R}WZhJj0WSb{-#aNqXuWSm~6Mj(*9z4?yJ|AeDnRUmKQmY>UN5B zzT^jsVU=;L3>QN*lh)~fBEd!wD~?psFI*7($C}v|b=-)F0G8+4lS9 zcs#SSS7Rs5LuIX5&+eSdckaVKe?OI{QkTEvxje)59QWJOlNg_O=6)>qaxEhn;McK4 zGL{RulRlnb<9;L)S;Lb9e0F3VcTKs$r!H+gzmhk2Z(@E8@OOay_xQfRo1e>Pv+GO4 zp^trcct%c7AjKW_zk)20{BCxhVC7d(d50^;$`DlE;7v=Z8kMGzeGIB@ch=5*@QCFW z5~#*E;8?uld&osZjv(c4c$*mQm=)?A8JWwto|rfF{kH7MiF8W1cEK^it{%|)12_`> zp1i>K7;aP*dIEGAh`3~11C_f~uya~;+EVt!RTm>%5 zww~Go17Ot#)DXc2Cy>g!eE*YhKAn+-?V)T-w~>V^^5uaDx~n?ZDKiE?Lv-= zSSH`z%a4$JUW;g8QBj*)tZ3I|c$C%@`cORcodzOU^9XZi3ppaI${yn#_9E8Pm`%d9 zPd%p{BTCt2{O@tL%G=t{%3*!YTDXy6-_Qpi+w;s`vH|GylLjSq7RxOZh-Ar1yDcbAUeT$xkxsjda1}^QI%A{al zl`n_Q+_YE~9Z#oIW>WLB%+>QLexzF@WvJU)u5QYGB}MIQR)Mm3ouLCwqLHA!)A{8Lxgsq+^J)!8Ep{Wf}sd@FWL_2zlnrODbu(9Bxn2}CR73GLj*POCAy zS0bgA<1KXAFu!@0)&j3Fs@*v}JG96uvX-O6`oU^Q4Z~c@wN|3ikjD}D4ic-r&c!yY zBhGy5rt9Sk(2nINgAmU*D4X_coCqJl+j&H}$22@|pf+i|ec64yZRb=02W*oyD( zx8B0NX;0>vOA{JVC!dq0V51{V#r0RT9M460W;*~i*6e2qpUXSzQh9tzhls(U!M847 z0UJer7j!RyG*6de#AFwrtjVb8{E??(YBhQkW75}m0&M8D>@-~$=AW6>kQ*Y%(Zd;w z>u2;-!JbqpT29+y)T(rUK7~9l)b$R!r6J^tj9w$id1Zc4Yt}h>uzSayM$s?Aj@iAp z4?3Jk*wNBgtvtM+=M2kY9&++&Qj%gF=?C>b znw*RBCijr4jaOo6@AD_3aCa!vI zDC@cOGKZv|q*O;zC#B{3+CCze|9{HL&h%_%6+2)0S`^2Mi(%%uFdwk1U@nUm%n23= z&3xt4Z!+grvs}}wuI05l3q^mvWhd*SnDu^nMf3HbM=>)^K$jhkPV%lsIFC(c-E7Vu zqXM#e-u#DE&_|CQ$fr{YePo)z=*EkWW!1u>c>fsnkg@uhGqCEX?8yp{R}CFKo5@)R U-D-a3bMKUAZuhRuRayoA1ql>lkpKVy diff --git a/Source/Unrealzilla/Private/ServerAPI.cpp b/Source/Unrealzilla/Private/ServerAPI.cpp new file mode 100644 index 0000000..585326e --- /dev/null +++ b/Source/Unrealzilla/Private/ServerAPI.cpp @@ -0,0 +1,609 @@ +// ©2022 Batty Bovine Productions, LLC. All Rights Reserved. + + +#include "ServerAPI.h" + +#include "HttpModule.h" +#include "JsonObjectConverter.h" +#include "UnrealzillaGlobalSettings.h" + +#include "Kismet/GameplayStatics.h" + + +const FString GetGameVersion() +{ + FString GameVersion; + GConfig->GetString(TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectVersion"), GameVersion, GGameIni); + if (GameVersion.IsEmpty()) + { + GameVersion = "1.0.0.0"; + } + return GameVersion; +} + + +void UServerAPI::ReturnListOfBugs() +{ + const FString FullURL = GetDefault()->SubmissionServer + "/rest.cgi"; + + TArray StatusQueries; + if (GetDefault()->bShowUnresolvedBugs) + { + for (const FString Unresolved : GetDefault()->UnresolvedStatuses) + { + StatusQueries.Add("status=" + Unresolved); + } + } + if (GetDefault()->bShowInProgressBugs) + { + for (const FString InProgress : GetDefault()->InProgressStatuses) + { + StatusQueries.Add("status=" + InProgress); + } + } + if (GetDefault()->bShowResolvedBugs) + { + for (const FString Resolved : GetDefault()->ResolvedStatuses) + { + StatusQueries.Add("status=" + Resolved); + } + } + StatusQueries.Add("cf_mapname=" + this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len())); + StatusQueries.Add("api_key=" + GetDefault()->APIKey); + const FString QueryString = FString::Join(StatusQueries, TEXT("&")); + + FHttpModule &HttpModule = FHttpModule::Get(); + TSharedRef SeverityRequest = HttpModule.CreateRequest(); + SeverityRequest->SetVerb(TEXT("GET")); + SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + SeverityRequest->SetURL(FullURL + "/bug" + "?" + QueryString); + SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ListOfBugsResponse); + SeverityRequest->ProcessRequest(); +} + +void UServerAPI::ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONBugResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (!ResponseData.error) + { + TArray BugData; + for (const FBugzillaJSONBugData &BugzillaData : ResponseData.bugs) + { + FUnrealzillaBugData Bug; + Bug.ID = BugzillaData.id; + Bug.Summary = BugzillaData.summary; + Bug.Component = BugzillaData.component; + Bug.MapName = BugzillaData.cf_mapname; + Bug.MapLocation = BugzillaData.cf_location; + Bug.Platform = BugzillaData.platform; + Bug.OperatingSystem = BugzillaData.op_sys; + Bug.Severity = BugzillaData.severity; + Bug.Status = BugzillaData.status; + Bug.Resolution = BugzillaData.resolution; + Bug.DuplicateOf = BugzillaData.dupe_of; + Bug.bIsOpen = BugzillaData.is_open; + BugData.Add(Bug); + } + this->BugDataResponse.Execute(BugData); + } + else + { + this->CreateError(EErrorVerb::GET, ResponseData); + } + } +} + +void UServerAPI::PrepareForm() +{ + const FString FullURL = GetDefault()->SubmissionServer + "/rest.cgi"; + + // Assemble query data into key:value pairs + TMap QueryData; + QueryData.Add("api_key", GetDefault()->APIKey); + const FString QueryString = UServerAPI::FormatQueryString(QueryData); + + // Query the server for information about the current product + FHttpModule &HttpModule = FHttpModule::Get(); + TSharedRef ProductRequest = HttpModule.CreateRequest(); + ProductRequest->SetVerb(TEXT("GET")); + ProductRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + ProductRequest->SetURL(FullURL + "/product/" + GetDefault()->ProductName + "?" + QueryString); + ProductRequest->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ServerProductInfoResponse); + ProductRequest->ProcessRequest(); + + // Send a second query to retrieve severity options + TSharedRef SeverityRequest = HttpModule.CreateRequest(); + SeverityRequest->SetVerb(TEXT("GET")); + SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + SeverityRequest->SetURL(FullURL + "/field/bug/bug_severity" + "?" + QueryString); + SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ServerSeverityInfoResponse); + SeverityRequest->ProcessRequest(); + + // Send a third query to retrieve platform options + TSharedRef PlatformsRequest = HttpModule.CreateRequest(); + PlatformsRequest->SetVerb(TEXT("GET")); + PlatformsRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + PlatformsRequest->SetURL(FullURL + "/field/bug/rep_platform" + "?" + QueryString); + PlatformsRequest->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ServerPlatformInfoResponse); + PlatformsRequest->ProcessRequest(); + + // Send a final query to retrieve OS options + TSharedRef OSRequest = HttpModule.CreateRequest(); + OSRequest->SetVerb(TEXT("GET")); + OSRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + OSRequest->SetURL(FullURL + "/field/bug/op_sys" + "?" + QueryString); + OSRequest->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ServerOSInfoResponse); + OSRequest->ProcessRequest(); +} + +void UServerAPI::SendFormData(const FUnrealzillaPostData &PostData) +{ + const FString FullURL = GetDefault()->SubmissionServer + "/rest.cgi"; + const FString RequestURL = "/bug"; + + // Assemble query data into key:value pairs + TMap QueryData; + QueryData.Add("api_key", GetDefault()->APIKey); + + const FString DefaultStatus = GetDefault()->DefaultStatus; + + FBugzillaJSONPostBug PostDataJSON; + PostDataJSON.product = GetDefault()->ProductName; + PostDataJSON.version = PostData.Version; + PostDataJSON.platform = PostData.Platform; + PostDataJSON.op_sys = PostData.OS; + PostDataJSON.component = PostData.Component; + PostDataJSON.severity = PostData.Severity; + PostDataJSON.cf_mapname = PostData.MapName; + PostDataJSON.cf_location = PostData.MapLocation; + PostDataJSON.summary = PostData.Summary; + PostDataJSON.description = PostData.Comment; + if (!DefaultStatus.IsEmpty()) + { + PostDataJSON.status = DefaultStatus; + } + + if (PostDataJSON.version.IsEmpty()) + { + this->CreateError("You must select a version number."); + return; + } + if (PostDataJSON.platform.IsEmpty() || PostDataJSON.op_sys.IsEmpty()) + { + PostDataJSON.platform = "All"; + PostDataJSON.op_sys = "All"; + } + if (PostDataJSON.component.IsEmpty()) + { + this->CreateError("You must select a component."); + return; + } + if (PostDataJSON.severity.IsEmpty()) + { + this->CreateError("You must select a severity level."); + return; + } + if (PostDataJSON.summary.IsEmpty()) + { + this->CreateError("You must provide a summary."); + return; + } + + FString PostJsonString; + FJsonObjectConverter::UStructToJsonObjectString(PostDataJSON, PostJsonString); + + FHttpModule &HttpModule = FHttpModule::Get(); + TSharedRef Request = HttpModule.CreateRequest(); + Request->SetVerb(TEXT("POST")); + Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + Request->SetURL(FullURL + RequestURL + "?" + UServerAPI::FormatQueryString(QueryData)); + Request->SetContentAsString(PostJsonString); + Request->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ServerPOSTResponse); + Request->ProcessRequest(); +} + + +void UServerAPI::ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONPostResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (ResponseData.error) + { + this->CreateError(EErrorVerb::POST, ResponseData); + } + else + { + // Use the response's bug ID to get the info from the newly filed bug report and update its marker + + const FString FullURL = GetDefault()->SubmissionServer + "/rest.cgi"; + + TArray StatusQueries; + StatusQueries.Add("id=" + FString::FromInt(ResponseData.id)); + if (GetDefault()->bShowUnresolvedBugs) + { + for (const FString Unresolved : GetDefault()->UnresolvedStatuses) + { + StatusQueries.Add("status=" + Unresolved); + } + } + if (GetDefault()->bShowInProgressBugs) + { + for (const FString InProgress : GetDefault()->InProgressStatuses) + { + StatusQueries.Add("status=" + InProgress); + } + } + if (GetDefault()->bShowResolvedBugs) + { + for (const FString Resolved : GetDefault()->ResolvedStatuses) + { + StatusQueries.Add("status=" + Resolved); + } + } + StatusQueries.Add("cf_mapname=" + this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len())); + StatusQueries.Add("api_key=" + GetDefault()->APIKey); + const FString QueryString = FString::Join(StatusQueries, TEXT("&")); + + FHttpModule &HttpModule = FHttpModule::Get(); + TSharedRef SeverityRequest = HttpModule.CreateRequest(); + SeverityRequest->SetVerb(TEXT("GET")); + SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); + SeverityRequest->SetURL(FullURL + "/bug" + "?" + QueryString); + SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UServerAPI::ServerPOSTUpdateMarkerResponse); + SeverityRequest->ProcessRequest(); + } + } + else + { + this->ServerConnectionError(Request->GetStatus()); + } +} + +void UServerAPI::ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONBugResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (ResponseData.error) + { + this->CreateError(EErrorVerb::GET, ResponseData); + } + else + { + if (!ResponseData.bugs.IsEmpty()) + { + TArray BugData; + FUnrealzillaBugData Bug; + Bug.ID = ResponseData.bugs[0].id; + Bug.Summary = ResponseData.bugs[0].summary; + Bug.Component = ResponseData.bugs[0].component; + Bug.MapName = ResponseData.bugs[0].cf_mapname; + Bug.MapLocation = ResponseData.bugs[0].cf_location; + Bug.Platform = ResponseData.bugs[0].platform; + Bug.OperatingSystem = ResponseData.bugs[0].op_sys; + Bug.Severity = ResponseData.bugs[0].severity; + Bug.Status = ResponseData.bugs[0].status; + Bug.Resolution = ResponseData.bugs[0].resolution; + Bug.DuplicateOf = ResponseData.bugs[0].dupe_of; + Bug.bIsOpen = ResponseData.bugs[0].is_open; + BugData.Add(Bug); + this->BugDataResponse.Execute(BugData); + } + } + } + else + { + this->ServerConnectionError(Request->GetStatus()); + } +} + +void UServerAPI::ServerProductInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONProductResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (ResponseData.error) + { + //this->ShowProcessingOverlayMessage(ResponseData.message); + this->CreateError(EErrorVerb::GET, ResponseData); + } + else + { + if (!ResponseData.products.IsEmpty()) + { + const FBugzillaJSONProductData &ProductData = ResponseData.products[0]; + if (ProductData.name == GetDefault()->ProductName) + { + for (const FBugzillaJSONComponentData &ComponentData : ProductData.components) + { + this->ComponentsList.Add(ComponentData.name); + } + for (const FBugzillaJSONVersionData &VersionData : ProductData.versions) + { + this->VersionsList.Add(VersionData.name); + } + } + + this->CheckIfAllFormResponsesAreIn(); + } + else + { + FStringFormatOrderedArguments Args; + Args.Add(FStringFormatArg(GetDefault()->ProductName)); + //this->ShowProcessingOverlayMessage(FString::Format(TEXT("Could not find data for a product called {0}"), Args), true); + this->CreateError(FString::Format(TEXT("Could not find data for a product called {0}"), Args)); + } + } + } + else + { + this->ServerConnectionError(Request->GetStatus()); + } +} + +void UServerAPI::ServerSeverityInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONFieldResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (ResponseData.error) + { + //this->ShowProcessingOverlayMessage(ResponseData.message); + this->CreateError(EErrorVerb::GET, ResponseData); + } + else + { + if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "bug_severity") + { + for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values) + { + this->SeverityList.Add(FieldValue.name); + } + } + + this->CheckIfAllFormResponsesAreIn(); + } + } + else + { + this->ServerConnectionError(Request->GetStatus()); + } +} + +void UServerAPI::ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONFieldResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (ResponseData.error) + { + //this->ShowProcessingOverlayMessage(ResponseData.message); + this->CreateError(EErrorVerb::GET, ResponseData); + } + else + { + if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "rep_platform") + { + for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values) + { + this->PlatformsList.Add(FieldValue.name); + } + } + + this->CheckIfAllFormResponsesAreIn(); + } + } + else + { + this->ServerConnectionError(Request->GetStatus()); + } +} + +void UServerAPI::ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +{ + if (Success) + { + FBugzillaJSONFieldResponse ResponseData; + FString JSONResponse = Response->GetContentAsString(); + FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + + if (ResponseData.error) + { + //this->ShowProcessingOverlayMessage(ResponseData.message); + this->CreateError(EErrorVerb::GET, ResponseData); + } + else + { + if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "op_sys") + { + for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values) + { + this->OSList.Add(FieldValue.name); + } + } + + this->CheckIfAllFormResponsesAreIn(); + } + } + else + { + this->ServerConnectionError(Request->GetStatus()); + } +} + +void UServerAPI::CheckIfAllFormResponsesAreIn() +{ + if (!this->ComponentsList.IsEmpty() && !this->VersionsList.IsEmpty() && !this->SeverityList.IsEmpty() && + !this->PlatformsList.IsEmpty() && !this->OSList.IsEmpty()) + { + FUnrealzillaFormPrepData Data; + + Data.ComponentsList = this->ComponentsList; + Data.SeverityList = this->SeverityList; + Data.VersionsList = this->VersionsList; + Data.PlatformsList = this->PlatformsList; + Data.OSList = this->OSList; + + // Find a default version number to use if possible + if (this->VersionsList.Contains(GetGameVersion())) + { + Data.DetectedVersion = GetGameVersion(); + } + else if (this->VersionsList.Contains("unspecified")) + { + Data.DetectedVersion = "unspecified"; + } + else if (this->VersionsList.Contains("Latest")) + { + Data.DetectedVersion = "Latest"; + } + else if (!this->VersionsList.IsEmpty()) + { + Data.DetectedVersion = this->VersionsList[0]; + } + + // Set these as defaults in case nothing below changes this setting + Data.DetectedHardware = "All"; + Data.DetectedOS = "All"; + + if (this->PlatformsList.Contains("PC")) + { + // Try our best to auto-detect PC hardware + if (UGameplayStatics::GetPlatformName() == "Windows" && this->OSList.Contains("Windows")) + { + Data.DetectedHardware = "PC"; + Data.DetectedOS = "Windows"; + } + else if (UGameplayStatics::GetPlatformName() == "Linux" && this->OSList.Contains("Linux")) + { + Data.DetectedHardware = "PC"; + Data.DetectedOS = "Linux"; + } + else if (UGameplayStatics::GetPlatformName() == "Mac" && this->OSList.Contains("Mac OS")) + { + Data.DetectedHardware = "All"; + Data.DetectedOS = "Mac OS"; + } + } + if (UGameplayStatics::GetPlatformName() == "Mac") + { + // Try our best to auto-detect Macintosh hardware + if (this->PlatformsList.Contains("Macintosh")) + { + if (this->OSList.Contains("Mac OS")) + { + Data.DetectedHardware = "Macintosh"; + Data.DetectedOS = "Mac OS"; + } + } + } + + this->FormDataResponse.Execute(Data); + + this->ComponentsList.Empty(); + this->VersionsList.Empty(); + this->SeverityList.Empty(); + this->PlatformsList.Empty(); + this->OSList.Empty(); + } +} + +void UServerAPI::ServerConnectionError(const EHttpRequestStatus::Type Status) +{ + switch (Status) { + case EHttpRequestStatus::Failed_ConnectionError: + this->CreateError("Unable to connect to the server"); + //this->ShowProcessingOverlayMessage("Unable to connect to the server", true); + break; + case EHttpRequestStatus::NotStarted: + this->CreateError("Connection could not start"); + //this->ShowProcessingOverlayMessage("Connection could not start", true); + break; + case EHttpRequestStatus::Failed: + this->CreateError("Connection failed"); + //this->ShowProcessingOverlayMessage("Connection failed", true); + break; + default: + this->CreateError("Request failed for unknown reasons"); + //this->ShowProcessingOverlayMessage("Request failed for unknown reasons", true); + } +} + +void UServerAPI::CreateError(const EErrorVerb &Verb, const FBugzillaJSONPostResponse &Data) +{ + FUnrealzillaErrorData Error; + Error.ErrorVerb = Verb; + Error.ErrorCode = Data.code; + Error.ErrorMessage = Data.message; + Error.DocumentationLink = Data.documentation; + this->ErrorResponse.Execute(Error); +} +void UServerAPI::CreateError(const EErrorVerb &Verb, const FBugzillaJSONProductResponse &Data) +{ + FUnrealzillaErrorData Error; + Error.ErrorVerb = Verb; + Error.ErrorCode = Data.code; + Error.ErrorMessage = Data.message; + Error.DocumentationLink = Data.documentation; + this->ErrorResponse.Execute(Error); +} +void UServerAPI::CreateError(const EErrorVerb &Verb, const FBugzillaJSONFieldResponse &Data) +{ + FUnrealzillaErrorData Error; + Error.ErrorVerb = Verb; + Error.ErrorCode = Data.code; + Error.ErrorMessage = Data.message; + Error.DocumentationLink = Data.documentation; + this->ErrorResponse.Execute(Error); +} +void UServerAPI::CreateError(const EErrorVerb &Verb, const FBugzillaJSONBugResponse &Data) +{ + FUnrealzillaErrorData Error; + Error.ErrorVerb = Verb; + Error.ErrorCode = Data.code; + Error.ErrorMessage = Data.message; + Error.DocumentationLink = Data.documentation; + this->ErrorResponse.Execute(Error); +} +void UServerAPI::CreateError(const FString &ErrorMessage) +{ + FUnrealzillaErrorData Error; + Error.ErrorVerb = EErrorVerb::None; + Error.ErrorMessage = ErrorMessage; + this->ErrorResponse.Execute(Error); +} + + +const FString UServerAPI::FormatQueryString(const TMap &QueryData) +{ + TArray AssembledKeyValuePairs; + TArray QueryKeys; + QueryData.GenerateKeyArray(QueryKeys); + for (const FString &QueryKey : QueryKeys) + { + AssembledKeyValuePairs.Add(QueryKey + "=" + QueryData[QueryKey]); + } + + return FString::Join(AssembledKeyValuePairs, TEXT("&")); +} diff --git a/Source/Unrealzilla/Private/ServerREST.cpp b/Source/Unrealzilla/Private/ServerREST.cpp deleted file mode 100644 index 1fd52941f5503cc7f503490248fd96e7122b8415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41154 zcmeHQZF3v95x%eNO#cI;&Zx??q0@YH6L%cja_l&fQ_D#@nT$s-l&o5o6;gJhs`)|v zlWm^|%jIHmz}-Eow z@cB*kt~#iWsv-V*T8*kV)o%5++QIV!d_Jni)t~X?3I5%y2KaQhI>f(z^>;kquU?|v zIQ%ul?;-lX!_Qr`{C)Mi>Hj~35q8k;EuL|tmFn5F^$4&Y;O|ZJdWk36GuP{jfcbIt z)dbH&K(SuA9*=<+=dn?3SN}w7&g@?JhM11<7k@Uxc#i%fwA;cA{)M@a0$bHX{Coqb z{FbvJO-adpjA-yUn;c$WqV2eP9s1n@tgZ0*I6yuHyc2vwcsn&{gt!xCFh+}hwH{iJ zCS&&ji?}GnZ}Ha%Foc_H)rWz;XW?fr(^v5Dz5Y6B{cu9LJwV&9KC13a#v-;l{u=oB zp!yy^*TBnLz-=cyJ4TyZctZM!ywzz&%qfYZKvR)MD(*y?N^@x@=Em=NX*C6 z|0~mxf1OaC(su&w838k5TIQ1UP#t;)IXQsN^{eMWMnykO<8D{Cp?wDX30hOCHZbGQ zF&Aj;k;nTOWe<4s`APLDTFkChcLFYyyJvv24T=+6mF?J~ z^s7H%WXc~Y#~BSEhm<^}Eh*H4^xQ=Y%G)V$@|tu95GdKy!?%F(4SrHK4+6xxY>-Dt|cJYsvhCEhIeTKh90&=68UiT{1Fa2$rzDRD;zKE^b2cJ0e@l@NC24*?BaWe;V zaoo&}ws>ylM!kA&(tgG|bWy!b;Vq7f=}3#`U^-$G_hM|k?Tz(*2dl~ptOQ$FeUI>y z9+m0;3G64=2=i6G3%2nvSd9;(@4pHVzMeqfnm34i+(8JIJ?#ke=a7r%<@p7^)h;6?0RKg3Kn9mTmeH(_iLzd*Y! z=p6kg4^?6@;(dgXKf(y7km_L=C*?Qgoe>hFB8iR|+1)~1Cox_u4c&1QUv&*q;C)y1 zAh+@T{?wybss1+ooR@Hum3HcE0DtiWb}(o90iJuL4&w^OxulWUrO$(=?7}OIqgt~~ zq}wWW8y7(UP zjN?A>_w2O|y3q5cjxsx<-15=1dXKp_u}`}`@%Aj2@ECA6s;?*Cr@WAP#5mm#^1n5; zO7%!yA=Y8h;-%l(`snrXE@VPhGg-%sn>|EN>#*9Qd9fS{gm*#qB)cN^z1}j0yb1bJ zV#znjKdk|}P$k7yp>5?BGLN?wOMPALBN%{uMy;4`82xZY+O%ck(P%lve9@vM3ite6wI z4=gBaB|Hb<%x)yX99h~9!1uSXd-Uo?SP$CZj9u$IK59v6-(;<6gSrEGpzXCDdC1;# zY=>I+SDWBI!T9lN*9|^1lX0*72v1?rxQ>b}zHX(nWGAhC9_j{YCOJIKhdu)j=?x7* z&o=zup!W;3W{*7lbUkW=IL5eQjl`=eLACrrJ49=nhbG#`72_@DExFe=s3YJhaisOh zL%$EGu^%)H`MNgv%&a^*~6kQLe9MgO!~w9Nt`7s#O}l+>IdKW1hel?;^N!Tey-EZ-u9sZTWG-@5n1Oq z5t%WrNVC7>q>oa$o}0(7TvZLNKQWnK2_smfz7phQ5fqwS?$pxa{1oI{^qu;waZxYfy?I#v-B&;^2 zH^DZq6P!`gY%H#gZ4}_F;p#N34|< zx&m>RZ<$dPYLMnJ&Ail^^|=ab(G|Bgd%@g4{m@JCc67 zuBWtY*YC7PT{14J;o|>~g2s2z?=&91eEm)%_%wek5!}Um0iPpCM=*_?#UJPj^D4Bw zjixr^^XNHO31&D_**TA%cfs_1SjRoL+cmw*ye_Kiw87jjW=_A0u5TbGdHMRyQN5kJ zxJK7unnZWk;U*|op|wr;oJUXNJmh(FGmp>JYa{V$*T6jX-E?lYY!+8_`q-LL&|E=U zyO!UPcnXi2F|YPw^qzj__vmg$UaW_#XF0#;Erv+H*bP=hipS~eAeR}o=6Y_cJk4d; zBywruh_4G}o{SkRR+S207Lh3^k107z!MeN`t$HL`b)F9Jx~99cngK06AHdg%vu}%= zvDhcg0BCNc9Y2XTwT@WuOJ_5fRK;kkcBrX`SAv^*JMEC;wW$P^7Q7u2W!CR;mf+p1 zwQPqzPGZ>->@>1#hn%bWD?#`Bvt41QJE+aj(_Py3QT(2D3A*10Z->eXt1;FFR*+ey zsRUhk%8E(WUMX~|mnd71Zf71+9a4)i2y3>tfaS3iFY_y`rlUN9*XeHfcG>@1sr~^? zcniDzI@oXSI#AA;2j$r!$rbw1`=BrD8Me`eXN_2=rnBnd8|Dq=Y=?d;bL}$E)XL;$ z+OPhNe`O0kz>I9yJqJN+Mb4)Cre(^f7>V?)RCht6L(E<0$c$}%Me+EQ`gf;M-O6>F zAPe$WSM!Qq_c-P_H@&Qtsro{ywm)m?zNGO4>RtG5;Jk(x$o z9bNoX^<2zT&_a`;g zQf;u0mT|=scQaTW#d;}bTUcq!S<`P}#q-bb#8^qqeGkSetYJ{N9Q7D|cpCz}9I1I= zwdiihQxOJhXNil{Wb?$?D8vHFqvnj{b*ngJCF;e`h7$@sCXIBz0Ut>>IYA!R2hzi| zQ;WXtkkzH@6P_}2e}Y=ivL+Ccorxm`raz0wGR}5Jo!D3m$-ktc?>e7Vug|F~*T<1; z825FjLu$bpLy@DlPH;SpebdjHyICx!DTU%Ul+Ul%snU(pIoOLk0i7Ey)>!&cn)~34 zQt7=Ir*osfUz`>@Q@(SePLXb0PT?+&!|6zu$K7<~uAGfAEUy^347k~l*xwOba6Qhw zQ=Yob|LV`a6IbT-N~bK8cb9wqomefpvYlgG=X3BPWxmdm+A4KGW30-SE%-KgY+cU6 z&jR%-vN*Hg*Ob|bYY1kqJMV_Y`;$v~9z{XiNgRb!MZ7_)lw;{O!)|#O!ct#PfBO?` z=1s>oKhC~LzKb!S-Rk!M^UxD*b;*|%Beuh=kGJ(pFp-}Z$p>|%?aj**Nlis6L(43Y z*C}RPR%@E3Ss#^0%|K}ElTuaNFE&dg=bq-9Bhnc)-CQ6!9NsutzJ-uh#OwtBN^=_; zbDsY`19}}@py+)7KAshQ;2iYUfHD_##y{Z3#2+#5*=`Z&4%hEA=JBO%&DGCzAB7&I zhmar1SFy+*)hwgTc8lv%J=5Bp|0mVmo3_cy~goUHUYwWFJ@JgZlMM=bj&X z02z>5O1WMy>gE+cwpPM7?_`uR_a)laeP5*$>D%;__mg=HwI^@sPW|C@2PTbW>hfQQ zptlLyo@*ID?%5cEf1<@FcZ1o|O;8fcN2hZP#y&YUMGcE#D)++6`av ze$gB8y*`=7r!qXhmCDaksuYBKq^G|XhT6v4Ratd%gwL!qkrTN5W;aA-ykkRc5I-M- z-gakCACkiCgx0Q{A^QI{)J!txqFj)7LdYGG@g1fjDPH=>X<|BzZuFwx$h@cCR{Xxq z%5%{qUO(w?FaIX{@yy@v+;AE<|8_k5^-vc?9Z9WU*&{JqES$b z&!wVew%OS|g{dzWQ=e;DYR|pUh+9#siazw$7=q}O|?#w%sZ6@a?W!aWN5x|Tc9PZ9;{T=ZMsw@}{8lT2n;E?S&v z&`Z>=LoT(#*Xd)>}EnxRUS5U}Xb$a=359T{L^s zbAM9ppu3d*E+n2&=9y*QgeRwr`K~TT#H@5UKz5J&Jvy4|Zp`x%4MldC$>#e$7+>qY zTmJ3YQsJ&L?bk4q81C6|$B32m+$Z#-3-!b|FOOnW)SQ>qQt+I$yshlKb6f!}Ia^kz z?F4Ad!|qOQ7g0){O=;SjYU3d6NVd(c41dw{dNSFqV+ABq!BienSnC9aw26d$@eJE-RXNlK}I!d{a4F@ofhD zp0R`6LU0g1vF{1~NiXGIS-ZXf9PY~h9DdV2uwEv2lUNxB?N`2^j-S)GvIWiAl@(O8 z+-s0Yq4y@{!W$U46V7*2l4g`;wSimsp4RZAU_n(6St)Z5ZR8CRMhnH>PWL~@4xUsN z9fKFFE#pr1R-pF|Mv}Kwkpt=Mv|4~RkhBsie;4?)8@M{g?9}IpK@S)zOxxCU(wE zSLxjk_jCCb$NL9OGFOt^`fZ5aW!K}Cr>@Dc+P8VjtdiwR{iZi1B6Cd)q`$$|r|+pqqO8!Gq}==g3_`!w!tOyi;wCm{W=%M#ty$IQbBg`OwGWzz^ zn#D3x$^x&1(~o=?AtP4x>4ZW$f__VQY(w1jjyzf$(=uAmcvQY2jNY|WdFmV4#E))1 zPjGR(SYMf|sN4qbte|`~#d~&se2rP1O!&*&#~7*3O81W+}yiH@BvQfaQmYI2Zh5yu4#>=7~^4@z! zP>a&=`Cyl)<$2bSl8vNJ+InGo2(k`ugJOCPVmrJcH*Vklo(fVSzV(#Q%#UEqsq0JW zq`G0G{rXXDxY`SAMK41uM{N#_p7pQhK9q3ErQ|1cOgRI?@0)x3lVd3ie_AXD={`p1 zdvf)RgJcTGA&K2y)$n32Am2mb$7`0+Cgn`s-?O%=BO-TQJeJFrw)j*+I`GpQ|~Q3ZL}JB zLf$X&k^SO5uhK2vj5HaC=d6V3jmKSls?t-1G;g1Wo+|%)@0U3cJ(buGC#8n?`yFP) zyXYPuhTaVM70tELo6Fxo>FcmIL+10i`ZPKD$Y{WG`3}BWjbN(3>N%X=EEVHT2z=iH z^B#N~lAPuI5`Ue-W9I2j*0??mvDFBzl)`N=ZJq+ldfF4kb`Z|FQ4-`XpkXC_c!HGQ zFkfiB6rOsey<#<6JWo$2fg$ICa*$;=>};BCnuS7k>Nth0H#6Fuu+~Cp@k4E ComponentsList; + UPROPERTY(BlueprintReadOnly) + TArray SeverityList; + UPROPERTY(BlueprintReadOnly) + TArray VersionsList; + UPROPERTY(BlueprintReadOnly) + TArray PlatformsList; + UPROPERTY(BlueprintReadOnly) + TArray OSList; + + UPROPERTY(BlueprintReadOnly) + FString DetectedVersion; + UPROPERTY(BlueprintReadOnly) + FString DetectedHardware; + UPROPERTY(BlueprintReadOnly) + FString DetectedOS; +}; + + +USTRUCT(Blueprintable) +struct FUnrealzillaPostData +{ + GENERATED_BODY() +public: + UPROPERTY(BlueprintReadOnly) + FString Version; + UPROPERTY(BlueprintReadOnly) + FString Platform; + UPROPERTY(BlueprintReadOnly) + FString OS; + UPROPERTY(BlueprintReadOnly) + FString Component; + UPROPERTY(BlueprintReadOnly) + FString Severity; + UPROPERTY(BlueprintReadOnly) + FString MapName; + UPROPERTY(BlueprintReadOnly) + FString MapLocation; + UPROPERTY(BlueprintReadOnly) + FString Summary; + UPROPERTY(BlueprintReadOnly) + FString Comment; +}; diff --git a/Source/Unrealzilla/Public/BugMarkerActor.h b/Source/Unrealzilla/Public/BugMarkerActor.h index f3ee144..5950496 100644 --- a/Source/Unrealzilla/Public/BugMarkerActor.h +++ b/Source/Unrealzilla/Public/BugMarkerActor.h @@ -4,7 +4,7 @@ #include "CoreMinimal.h" -#include "ServerREST.h" +#include "ServerAPI.h" #include "BugMarkerActor.generated.h" diff --git a/Source/Unrealzilla/Public/BugMarkerSubsystem.h b/Source/Unrealzilla/Public/BugMarkerSubsystem.h index 294cc862c57db5b1cf4b19ad0c8789f89a236bdd..c728b09a8dcb2170b390d5e6a3a0104b85cdb621 100644 GIT binary patch literal 1945 zcmb7_QE$^Q5XYaJPjP@WwbN+P9yc^8ZPTtOG*Q|h;H7dC(;6JR*iNT{_&|IH9+3D# zdZ&v(E3-<|XK*Y92r{`@?Db$$+gV$2Tua?1pTP{}k;43k2if$tBx z;BpQTTdj?Th-#{~H0?Uhi;W_yjDQpg#V;ljo~IO^52T`RnP3^=-SzXv#w=gzoi;Sn zJzpk-hn(!Fs#mRX!`cley?(y>#yBT$X2Eo36jJHCE+@iJ^@$`@_a@iIY$EzKr`kMb zq&JuxJqU=w-V*htNwlJZDq=8Q%jWb>PaQagni$NQ7)WH6<$@(>r{RW~H7xTLD7ul# zK%th>Ktu`|5oConDbpL9dRiD}MNHjA&pjs|`0i}h?l=i2T0=aIJlFp?@qKrJdO&=i z5iVepFF8vfen?d3yVDwbQ!jGo-f+9%+~UzM%3-jWx0NhMhENhD4q`HlVIBJ^PFxJ z6DG_R?KrL0Qc4akHql<0cTBS-r|k|ZEwq8}sIAGBfM;gS^vkPT$K8qcvW?kcVE9on zg;)uqC?=FKt+A^|Qe{J8h{db8?WVCC?R^YgG#iX!p(x=+4~9}3dkjvhx8nRfSbj!* zLZe=nHJjN1QCO}kI2lt@e_{eP`kuVr+T#vAtbXJzhMw<@v0jTn(2yvSwMTW1bzLm1 zP3^2yD4j-YyRE`2Q-Rz+nexD5GDJ9hrnqvPb zPhkPYv&s&Ybg*N$IPrFdZ0DC*RTcxn`I01G%66K&N|D{`2WnKF!&b2n^}zU&q*Ho> z_j0ms^yy)Z4#GU(zdEr`v5ZkNhrw)q9Jty4_ee`Gca(V?s3CaqS`1MfMnULBb9*_p zT1Q9fc4n|YOI=+zF;bmg*(#KmhHaGpP#q5x8r#{Jvy7^1yr?;aO8dszp-C(_772fi qcWi!O;A(38V7wUM9FK$O!@_$%_aeOPD#Ch@eYwKltbL#_RO26)&7$7` literal 3820 zcmcha+fEcg5Qgh^O?-!mBnF7OhD#tOoWXF|>_Vd6kO4+mIV{dD7&JVPFR6Zi_qKD| z6A;a0c4oS(s;mC`tGf61ue#MOvY$3*Z@K4qU$)qKTzBrhW4G3}D@*ulSZWl#et}@YfGi|t*U~PRsURA?lVd7Zldx<)aG8NfNSL5aR8D`Xv zl*`nunAn2zNuJhnezf312Ub)u?Yii^R$gPXZ*R0lc^!2`@l4D}`cYnDNjdj1t>Z@u z&sLG?af=(nd{$Pa-2*YNHSIXBF6&vG)oFyx0cdMoWg)BT2D*|DD+SkZ@l@_uAr zu%r&q;7)Ck_dI)B%=+$~Iv%%R%{!N=4Q6`8p3QqsE7AzQ`C1fPQvCJxEQDU8U)5Sb z;y#ztRk-OZOVx8oRBP~k4gPxG9^$*YOvB|M20!twn#F2|3K)kRY9CXFc^KMdD|yrd z%C$#&q#S!51RtA3n(zF1CjC340(;7dWChL{r^g_UMuO?(~?i`_XvlwsYyNcHE$sM3{mGE2=E{#`91ka& zbt2b4q6B|7sQ{gGb;^n=RWVp@yd9l7F8Y<`|Lu*kbf1g8yhCl1y$$yCmHuA7!FQ89 f9+J@y#G$&); + FBugListResponseDelegate BugDataResponse; + + DECLARE_DELEGATE_OneParam(FFormResponseDelegate, const FUnrealzillaFormPrepData&); + FFormResponseDelegate FormDataResponse; + + DECLARE_DELEGATE_OneParam(FServerErrorResponseDelegate, const FUnrealzillaErrorData &) + FServerErrorResponseDelegate ErrorResponse; + + TArray GetComponentsList() const { return this->ComponentsList; } + TArray GetSeverityList() const { return this->SeverityList; } + TArray GetVersionsList() const { return this->VersionsList; } + TArray GetPlatformsList() const { return this->PlatformsList; } + TArray GetOSList() const { return this->OSList; } + +private: + void ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + + void ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerProductInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerSeverityInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void CheckIfAllFormResponsesAreIn(); + void ServerConnectionError(const EHttpRequestStatus::Type Status); + + void CreateError(const EErrorVerb &Verb, const FBugzillaJSONPostResponse &Data); + void CreateError(const EErrorVerb &Verb, const FBugzillaJSONProductResponse &Data); + void CreateError(const EErrorVerb &Verb, const FBugzillaJSONFieldResponse &Data); + void CreateError(const EErrorVerb &Verb, const FBugzillaJSONBugResponse &Data); + void CreateError(const FString &ErrorMessage); + + static const FString FormatQueryString(const TMap &QueryData); + + TArray ComponentsList; + TArray SeverityList; + TArray VersionsList; + TArray PlatformsList; + TArray OSList; +}; diff --git a/Source/Unrealzilla/Public/ServerREST.h b/Source/Unrealzilla/Public/ServerREST.h deleted file mode 100644 index a8b14fc693d99c435f10bf71abaa2dff91c292f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10294 zcmds7+fE}#5Up1t74VC@zDCyr(lYy5q3Z-QnQ`-tKNI9+2;DmxC@x!ZXOnrFE8*uE`L%CN63C*Vac z9Z0_=oA@>Zj(vRdH~8@TETH$&@hM^$g;obNRY&O%YQIQL_GA}m-h=?5~0>e?EtHH@qb4afQEWcosDRR z4Ev7oT`J!=O_N#|`cIsN8qgc9y@soei&py0aXZ4dOZe+LO=j|w+kNl;&w(4Yl**jr zx(1{x;9bF5YLHsE!YH5mA^R#`hwGfaX`YbCI`*~jIfZ^M1HbVWsNfFmMqZ|I zO23Q0ec-L*wIYYG^&VdT00On`4Xj05pTo9Gxt0$Q1NO15>h{*%>Run=Ich-OJs`V; z?UWvE)88{ZR_`tE2Kdnuu$cW-hqTwzP^oAC=%Sy;!YHE=hge?A54p{r&Tk!lMjMjv zsgu)A%MLWhGsJk9md39F$(rum^8C^_EShgSOcy!l^1W1}-7I-%=nGpo4+$bpxz?{a zOECMjaaJbf)3J}yo3HYGq*gDB(+(uLd5G8La@lsC?ESYaZdI3sGSm&sX;-CLX%Cu9 z!9gWrbg02681d5yay^0+*N`YdeBT0f<=V=9U9(Gh(TA0Z)+OhVt2~cqjHMpCkgzPj zV5aLhU3S4qE8$(546JS+1OOcyZBl9S$#h+0Z zg*kQ^y6TO99ds?MF|&#J$1n`B?@yb*)>oa}Q>QO~97m^*fHi(m>#U0>U(;1^K6p;q zDYMTiYJO5NqUhxb&SR15CQd^e+&{S2@?v_-ms(aC!hA>KTZA1-41HW5_MhDqry|Rc#E{R8 zjezrf`mSj+Myz&tibl*{G38;p7-UJKXTD>|i(0T%!?@C8)a#5+yyNK_2+ydWAI5yX zR3DzANkHGWnJTLcA0hnOVca>Tx2a>iHq=P$v2jfgS$>q(TkQOl$FHv7#aQuPQT|fs$#t#Q?#?r)xV;yVNSnur^|j5Q9P-2{1%DNl;fv5 zT35v#$$UUFJ_Cw$Sm)3?fAkfx#h+m`;!J6E<*54ySNEgI5a+k?B`DU=