2019-12-26 14:45:42 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-02-07 09:15:10 -05:00
2019-02-05 18:09:08 -05:00
# include "IOS/ApplePlatformBackgroundHttpManager.h"
2019-11-13 14:31:30 -05:00
# include "IOS/ApplePlatformBackgroundHttp.h"
2019-02-05 18:09:08 -05:00
# include "IOS/ApplePlatformBackgroundHttpRequest.h"
# include "IOS/ApplePlatformBackgroundHttpResponse.h"
2020-05-06 17:58:18 -04:00
# include "HAL/PlatformFileManager.h"
2019-02-05 18:09:08 -05:00
# include "HAL/PlatformAtomics.h"
2019-02-07 01:38:51 -05:00
# include "Misc/ConfigCacheIni.h"
# include "Misc/CoreDelegates.h"
# include "Misc/ScopeRWLock.h"
2020-09-01 14:07:48 -04:00
# include "Stats/Stats.h"
2019-02-05 18:09:08 -05:00
# include "IOS/IOSBackgroundURLSessionHandler.h"
2019-09-04 15:40:51 -04:00
# include "PlatformBackgroundHttp.h"
2019-02-05 18:09:08 -05:00
FApplePlatformBackgroundHttpManager : : ~ FApplePlatformBackgroundHttpManager ( )
{
2019-11-26 11:53:43 -05:00
bWasAppleBGHTTPInitialized = false ;
2019-12-03 15:56:40 -05:00
{
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_Write ) ;
[ UnAssociatedTasks release ] ;
UnAssociatedTasks = nullptr ;
}
2019-02-05 18:09:08 -05:00
CleanUpNSURLSessionResponseDelegates ( ) ;
}
FString FApplePlatformBackgroundHttpManager : : BackgroundSessionIdentifier = FString ( " " ) ;
float FApplePlatformBackgroundHttpManager : : ActiveTimeOutSetting = 30.0f ;
int FApplePlatformBackgroundHttpManager : : RetryResumeDataLimitSetting = - 1.f ;
2019-11-26 11:53:43 -05:00
volatile bool FApplePlatformBackgroundHttpManager : : bWasAppleBGHTTPInitialized = false ;
2019-02-05 18:09:08 -05:00
FApplePlatformBackgroundHttpManager : : FApplePlatformBackgroundHttpManager ( )
: bHasFinishedPopulatingUnassociatedTasks ( false )
, bIsInBackground ( false )
, bIsIteratingThroughSessionTasks ( false )
, RequestsPendingRemove ( )
2019-02-15 21:06:27 -05:00
, NumCurrentlyActiveTasks ( 0 )
2019-08-21 09:37:24 -04:00
, MaxNumActualTasks ( MaxActiveDownloads )
2019-02-05 18:09:08 -05:00
{
}
void FApplePlatformBackgroundHttpManager : : Initialize ( )
{
2019-12-03 15:56:40 -05:00
//Initialize UnAssociatedTasks
{
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_Write ) ;
UnAssociatedTasks = [ [ NSMutableDictionary alloc ] init ] ;
}
//This has its own lock when needed, so not included above
2019-02-05 18:09:08 -05:00
PopulateUnAssociatedTasks ( ) ;
2019-12-03 15:56:40 -05:00
2020-09-01 14:07:48 -04:00
GConfig - > GetFloat ( TEXT ( " BackgroundHttp.iOSSettings " ) , TEXT ( " ActiveReceiveTimeout " ) , ActiveTimeOutSetting , GEngineIni ) ;
GConfig - > GetInt ( TEXT ( " BackgroundHttp.iOSSettings " ) , TEXT ( " RetryResumeDataLimit " ) , RetryResumeDataLimitSetting , GEngineIni ) ;
2019-02-05 18:09:08 -05:00
SetupNSURLSessionResponseDelegates ( ) ;
2019-03-25 19:44:02 -04:00
2019-03-25 19:45:08 -04:00
FBackgroundHttpManagerImpl : : Initialize ( ) ;
2019-11-26 11:53:43 -05:00
bWasAppleBGHTTPInitialized = true ;
2019-02-05 18:09:08 -05:00
}
void FApplePlatformBackgroundHttpManager : : PopulateUnAssociatedTasks ( )
{
if ( ensureAlwaysMsgf ( ( nullptr ! = UnAssociatedTasks ) , TEXT ( " Call to PopulateUnAssociatedTasks without initializing UnAssociatedTasks Dictionary! " ) ) )
{
NSURLSession * BackgroundDownloadSession = FBackgroundURLSessionHandler : : GetBackgroundSession ( ) ;
if ( ensureAlwaysMsgf ( ( nullptr ! = BackgroundDownloadSession ) , TEXT ( " Invalid Background Download NSURLSession during AppleBackgroundHttp Init! Should have already Initialized the NSURLSession by this point! " ) ) )
{
[ BackgroundDownloadSession getAllTasksWithCompletionHandler : ^ ( NSArray < __kindof NSURLSessionTask * > * tasks )
{
2019-12-03 15:56:40 -05:00
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_Write ) ;
2019-02-05 18:09:08 -05:00
//Store all existing tasks by their URL
for ( id task in tasks )
{
2019-04-17 20:14:46 -04:00
//Make sure we have a valid absolute string version of the URL to use for our task's key. Otherwise, we just disregard this task.
2019-04-17 20:27:28 -04:00
if ( ( task ! = nullptr )
2019-04-17 20:14:46 -04:00
& & ( [ task currentRequest ] ! = nullptr )
& & ( [ [ task currentRequest ] URL ] ! = nullptr )
& & ( [ [ [ task currentRequest ] URL ] absoluteString ] ! = nullptr )
& & ( [ [ [ [ task currentRequest ] URL ] absoluteString ] length ] ! = 0 ) )
{
[ UnAssociatedTasks setObject : task forKey : [[[task currentRequest] URL] absoluteString]] ;
}
2019-02-05 18:09:08 -05:00
}
bHasFinishedPopulatingUnassociatedTasks = true ;
} ] ;
}
}
}
void FApplePlatformBackgroundHttpManager : : PauseAllUnassociatedTasks ( )
{
2019-12-03 15:56:40 -05:00
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_ReadOnly ) ;
2019-02-05 18:09:08 -05:00
for ( id Key in UnAssociatedTasks )
{
NSURLSessionDownloadTask * Task = ( NSURLSessionDownloadTask * ) ( [ UnAssociatedTasks objectForKey : Key ] ) ;
if ( nullptr ! = Task )
{
if ( [ Task state ] = = NSURLSessionTaskStateRunning )
{
[ Task suspend ] ;
}
}
}
}
void FApplePlatformBackgroundHttpManager : : UnpauseAllUnassociatedTasks ( )
{
2019-12-03 15:56:40 -05:00
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_ReadOnly ) ;
2019-02-05 18:09:08 -05:00
for ( id Key in UnAssociatedTasks )
{
NSURLSessionDownloadTask * Task = ( NSURLSessionDownloadTask * ) ( [ UnAssociatedTasks objectForKey : Key ] ) ;
if ( nullptr ! = Task )
{
if ( [ Task state ] = = NSURLSessionTaskStateSuspended )
{
[ Task resume ] ;
}
}
}
}
void FApplePlatformBackgroundHttpManager : : Shutdown ( )
{
2019-12-03 15:56:40 -05:00
{
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_Write ) ;
[ UnAssociatedTasks release ] ;
UnAssociatedTasks = nullptr ;
}
2019-02-05 18:09:08 -05:00
CleanUpNSURLSessionResponseDelegates ( ) ;
FBackgroundURLSessionHandler : : ShutdownBackgroundSession ( ) ;
}
void FApplePlatformBackgroundHttpManager : : AddRequest ( const FBackgroundHttpRequestPtr Request )
{
UE_LOG ( LogBackgroundHttpManager , Verbose , TEXT ( " AddRequest Called - RequestID:%s " ) , * Request - > GetRequestID ( ) ) ;
//See if our request is an AppleBackgroundHttpRequest so we can do more detailed checks on it.
FAppleBackgroundHttpRequestPtr AppleRequest = StaticCastSharedPtr < FApplePlatformBackgroundHttpRequest > ( Request ) ;
2019-04-17 20:14:46 -04:00
if ( ensureAlwaysMsgf ( AppleRequest . IsValid ( ) , TEXT ( " Adding a non-Apple background request to our Apple Background Http Manager! This is not supported or expected! " ) ) )
2019-02-05 18:09:08 -05:00
{
2019-04-17 20:14:46 -04:00
//If we fail to generate URLMapEntries or AssociateWithAnyExistingRequest, then we will have already sent a completion handler immediately, so only start work and monitor
//these requests if those didn't already complete this Request
if ( GenerateURLMapEntriesForRequest ( AppleRequest ) & & ! AssociateWithAnyExistingRequest ( Request ) )
{
if ( ! AssociateWithAnyExistingUnAssociatedTasks ( Request ) )
{
StartRequest ( AppleRequest ) ;
}
2019-03-14 19:06:08 -04:00
2019-04-17 20:14:46 -04:00
FRWScopeLock ScopeLock ( ActiveRequestLock , SLT_Write ) ;
ActiveRequests . Add ( Request ) ;
//Increment our underlying FBackgroundHttpManagerImpl tracker for active requests as we
//don't implement the method it uses to increase this number.
// NOTE: We don't make use of this number in Apple Platform functions as all requests are "Active" but their
// underlying Task might not be, see NumCurrentlyActiveTasks instead to track how many current Tasks are downloading data.
+ + NumCurrentlyActiveRequests ;
}
2019-02-05 18:09:08 -05:00
}
}
2019-04-17 20:14:46 -04:00
bool FApplePlatformBackgroundHttpManager : : GenerateURLMapEntriesForRequest ( FAppleBackgroundHttpRequestPtr Request )
2019-02-05 18:09:08 -05:00
{
2019-04-17 20:14:46 -04:00
bool bWasGenerateSuccess = true ;
//Attempt to add entries for all URLs
{
FRWScopeLock ScopeLock ( URLToRequestMapLock , SLT_Write ) ;
for ( const FString & URL : Request - > GetURLList ( ) )
{
FBackgroundHttpURLMappedRequestPtr & FoundRequest = URLToRequestMap . FindOrAdd ( URL ) ;
const bool bRequestAlreadyExistsForURL = ( ( FoundRequest . IsValid ( ) ) & & ( Request ! = FoundRequest ) ) ;
if ( ensureAlwaysMsgf ( ! bRequestAlreadyExistsForURL , TEXT ( " URL is represented by 2 different Requests! Immediately completing new request with error. " ) ) )
{
FoundRequest = Request ;
}
else
{
bWasGenerateSuccess = false ;
FBackgroundHttpResponsePtr NewResponse = FPlatformBackgroundHttp : : ConstructBackgroundResponse ( EHttpResponseCodes : : Unknown , FString ( ) ) ;
Request - > CompleteWithExistingResponseData ( NewResponse ) ;
}
}
}
//if we didn't succeed, make sure we don't have any stale partial URL Map entries for this request
if ( ! bWasGenerateSuccess )
{
RemoveURLMapEntriesForRequest ( Request ) ;
}
return bWasGenerateSuccess ;
2019-02-05 18:09:08 -05:00
}
2019-02-07 01:38:51 -05:00
void FApplePlatformBackgroundHttpManager : : RemoveURLMapEntriesForRequest ( FAppleBackgroundHttpRequestPtr Request )
{
FRWScopeLock ScopeLock ( URLToRequestMapLock , SLT_Write ) ;
for ( const FString & URL : Request - > GetURLList ( ) )
{
FBackgroundHttpURLMappedRequestPtr & FoundRequest = URLToRequestMap . FindOrAdd ( URL ) ;
if ( FoundRequest = = Request )
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Verbose , TEXT ( " Removing URL Entry -- RequestDebugID:%s | URL:%s " ) , * Request - > GetRequestDebugID ( ) , * URL ) ;
2019-02-07 01:38:51 -05:00
URLToRequestMap . Remove ( URL ) ;
}
}
}
2019-02-05 18:09:08 -05:00
void FApplePlatformBackgroundHttpManager : : StartRequest ( FAppleBackgroundHttpRequestPtr Request )
{
//Just count it as a retry that won't increment the retry counter before giving us the URL as our RetryCount 0 should start this up.
2019-11-13 14:31:30 -05:00
RetryRequest ( Request , false , nullptr ) ;
2019-02-05 18:09:08 -05:00
}
void FApplePlatformBackgroundHttpManager : : RemoveRequest ( const FBackgroundHttpRequestPtr Request )
{
FAppleBackgroundHttpRequestPtr AppleRequest = StaticCastSharedPtr < FApplePlatformBackgroundHttpRequest > ( Request ) ;
if ( AppleRequest . IsValid ( ) )
{
RemoveSessionTasksForRequest ( AppleRequest ) ;
}
RequestsPendingRemove . Add ( Request ) ;
}
void FApplePlatformBackgroundHttpManager : : DeletePendingRemoveRequests ( )
{
2019-02-15 21:06:27 -05:00
//Don't want to do this when background tasks might be using our request
ensureAlwaysMsgf ( IsInGameThread ( ) , TEXT ( " Called from un-expected thread! Potential error in an implementation of background downloads! " ) ) ;
2019-02-05 18:09:08 -05:00
for ( const FBackgroundHttpRequestPtr & Request : RequestsPendingRemove )
{
FBackgroundHttpManagerImpl : : RemoveRequest ( Request ) ;
}
RequestsPendingRemove . Empty ( ) ;
}
void FApplePlatformBackgroundHttpManager : : RemoveSessionTasksForRequest ( FAppleBackgroundHttpRequestPtr Request )
{
2019-02-07 01:38:51 -05:00
//First remove map entries. That way we won't send a completion handler when we cancel
RemoveURLMapEntriesForRequest ( Request ) ;
//Now cancel our active task
Request - > CancelActiveTask ( ) ;
2019-02-05 18:09:08 -05:00
}
2019-08-21 09:37:24 -04:00
void FApplePlatformBackgroundHttpManager : : SetMaxActiveDownloads ( int InMaxActiveDownloads )
{
FBackgroundHttpManagerImpl : : SetMaxActiveDownloads ( InMaxActiveDownloads ) ;
// It's possible we have more than the new maximum active right now, so we gracefully reduce MaxNumActualTasks down to MaxActiveDownloads as the extra tasks finish.
MaxNumActualTasks = FMath : : Max ( MaxActiveDownloads . Load ( ) , FPlatformAtomics : : AtomicRead ( & NumCurrentlyActiveTasks ) ) ;
}
2019-02-15 21:06:27 -05:00
bool FApplePlatformBackgroundHttpManager : : AssociateWithAnyExistingUnAssociatedTasks ( const FBackgroundHttpRequestPtr Request )
2019-02-05 18:09:08 -05:00
{
2019-02-15 21:06:27 -05:00
bool bDidAssociateWithUnAssociatedTask = false ;
if ( ! bHasFinishedPopulatingUnassociatedTasks )
{
//@TODO: TRoss, might want to look at trying to associate these after bHasFinishedPopulatingUnassociatedTasks. At this point I don't think its really worth it as it SHOULD be done before we get here, however
//the PopulateUnAssociatedTasks() function has an asynch component so it could technically be unfinished with some tight timing.
UE_LOG ( LogBackgroundHttpManager , Warning , TEXT ( " Call to AssociateWithAnyExistingRequest before we have finished populating unassociated tasks! Might have an unassociated task for this request that we won't associate with. " ) ) ;
}
//See if our request is an AppleBackgroundHttpRequest so we can do more detailed checks on it.
FAppleBackgroundHttpRequestPtr AppleRequest = StaticCastSharedPtr < FApplePlatformBackgroundHttpRequest > ( Request ) ;
if ( AppleRequest . IsValid ( ) )
{
bDidAssociateWithUnAssociatedTask = CheckForExistingUnAssociatedTask ( AppleRequest ) ;
}
return bDidAssociateWithUnAssociatedTask ;
2019-02-05 18:09:08 -05:00
}
bool FApplePlatformBackgroundHttpManager : : CheckForExistingUnAssociatedTask ( const FAppleBackgroundHttpRequestPtr Request )
{
bool bDidFindExistingTask = false ;
2019-12-03 15:56:40 -05:00
TArray < FString > URLsToRemove ;
//Go through and read the UnAssociatedTasksLock and save off which URLs we need to remove as we associate with those tasks.
2019-02-05 18:09:08 -05:00
{
2019-12-03 15:56:40 -05:00
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_ReadOnly ) ;
if ( ensureAlwaysMsgf ( Request . IsValid ( ) , TEXT ( " CheckForExistingUnAssociatedTask called with invalid Request! " ) ) )
2019-02-05 18:09:08 -05:00
{
2019-12-03 15:56:40 -05:00
const TArray < FString > & URLList = Request - > GetURLList ( ) ;
for ( const FString & URL : URLList )
2019-02-05 18:09:08 -05:00
{
2019-12-03 15:56:40 -05:00
NSURLSessionTask * FoundTask = [ UnAssociatedTasks valueForKey : URL . GetNSString ( ) ] ;
if ( nullptr ! = FoundTask )
2019-03-14 19:06:08 -04:00
{
2019-12-03 15:56:40 -05:00
if ( [ FoundTask state ] ! = NSURLSessionTaskStateCompleted & & [ FoundTask state ] ! = NSURLSessionTaskStateCanceling )
2019-09-13 13:57:36 -04:00
{
2019-12-03 15:56:40 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Existing UnAssociateTask found for Request! Attempting to Associate! RequestDebugID:%s " ) , * ( Request - > GetRequestDebugID ( ) ) ) ;
//Associate with task so that our Request takes over ownership of this task so we can remove it from our UnAssociated Tasks list without it getting GC'd
if ( Request - > AssociateWithTask ( FoundTask ) )
{
//Always set our bWasTaskStartedInBG flag on our Request as true in the UnAssociated case as we don't know when it was really started
FPlatformAtomics : : InterlockedExchange ( & ( Request - > bWasTaskStartedInBG ) , true ) ;
2019-03-14 19:06:08 -04:00
2019-12-03 15:56:40 -05:00
//Suspend task in case it was running so that we can adhere to our desired platform max tasks
[ FoundTask suspend ] ;
2019-03-14 19:06:08 -04:00
2019-12-03 15:56:40 -05:00
bDidFindExistingTask = true ;
break ;
}
else
{
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " UnAssociatedTask for request found, but failed to Associate with Task! -- RequestDebugID:%s | URL:%s " ) , * ( Request - > GetRequestDebugID ( ) ) , * URL ) ;
}
2019-09-13 13:57:36 -04:00
}
else
{
2019-12-03 15:56:40 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " UnAssociatedTask for request found, BUT NOT USING as it was cancelling or completed already! -- RequestDebugID:%s | URL:%s " ) , * ( Request - > GetRequestDebugID ( ) ) , * URL ) ;
2019-09-13 13:57:36 -04:00
}
2019-12-03 15:56:40 -05:00
//Still want to remove UnAssociatedTask even though we didn't use it as something else can now be downloading this data and we do not want duplicates
URLsToRemove . Add ( URL ) ;
2019-03-14 19:06:08 -04:00
}
2019-02-05 18:09:08 -05:00
}
}
}
2019-12-03 15:56:40 -05:00
//Remove all URLs from UnAssociatedTasks
{
FRWScopeLock ScopeLock ( UnAssociatedTasksLock , SLT_Write ) ;
for ( const FString & URL : URLsToRemove )
{
[ UnAssociatedTasks removeObjectForKey : URL . GetNSString ( ) ] ;
}
}
2019-02-05 18:09:08 -05:00
return bDidFindExistingTask ;
}
void FApplePlatformBackgroundHttpManager : : SetupNSURLSessionResponseDelegates ( )
{
OnApp_EnteringBackgroundHandle = FCoreDelegates : : ApplicationWillEnterBackgroundDelegate . AddRaw ( this , & FApplePlatformBackgroundHttpManager : : OnApp_EnteringBackground ) ;
OnApp_EnteringForegroundHandle = FCoreDelegates : : ApplicationHasEnteredForegroundDelegate . AddRaw ( this , & FApplePlatformBackgroundHttpManager : : OnApp_EnteringForeground ) ;
OnTask_DidFinishDownloadingToURLHandle = FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_DidFinishDownloadingToURL . AddRaw ( this , & FApplePlatformBackgroundHttpManager : : OnTask_DidFinishDownloadingToURL ) ;
OnTask_DidWriteDataHandle = FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_DidWriteData . AddRaw ( this , & FApplePlatformBackgroundHttpManager : : OnTask_DidWriteData ) ;
OnTask_DidCompleteWithErrorHandle = FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_DidCompleteWithError . AddRaw ( this , & FApplePlatformBackgroundHttpManager : : OnTask_DidCompleteWithError ) ;
OnSession_SessionDidFinishAllEventsHandle = FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_SessionDidFinishAllEvents . AddRaw ( this , & FApplePlatformBackgroundHttpManager : : OnSession_SessionDidFinishAllEvents ) ;
}
void FApplePlatformBackgroundHttpManager : : CleanUpNSURLSessionResponseDelegates ( )
{
FCoreDelegates : : ApplicationWillEnterBackgroundDelegate . Remove ( OnApp_EnteringBackgroundHandle ) ;
FCoreDelegates : : ApplicationHasEnteredForegroundDelegate . Remove ( OnApp_EnteringForegroundHandle ) ;
FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_DidFinishDownloadingToURL . Remove ( OnTask_DidFinishDownloadingToURLHandle ) ;
FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_DidWriteData . Remove ( OnTask_DidWriteDataHandle ) ;
FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_DidCompleteWithError . Remove ( OnTask_DidCompleteWithErrorHandle ) ;
FIOSBackgroundDownloadCoreDelegates : : OnIOSBackgroundDownload_SessionDidFinishAllEvents . Remove ( OnSession_SessionDidFinishAllEventsHandle ) ;
}
void FApplePlatformBackgroundHttpManager : : OnApp_EnteringForeground ( )
{
2019-02-07 01:38:51 -05:00
PauseAllActiveTasks ( ) ;
2019-02-15 21:06:27 -05:00
FPlatformAtomics : : InterlockedExchange ( & bIsInBackground , false ) ;
2019-02-05 18:09:08 -05:00
}
void FApplePlatformBackgroundHttpManager : : OnApp_EnteringBackground ( )
{
FPlatformAtomics : : InterlockedExchange ( & bIsInBackground , true ) ;
2019-11-13 14:31:30 -05:00
ResumeTasksForBackgrounding ( ) ;
2019-02-05 18:09:08 -05:00
}
2019-02-07 01:38:51 -05:00
void FApplePlatformBackgroundHttpManager : : PauseAllActiveTasks ( )
2019-02-05 18:09:08 -05:00
{
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Attempting to Pause All Active Tasks " ) ) ;
NSURLSession * BackgroundDownloadSession = FBackgroundURLSessionHandler : : GetBackgroundSession ( ) ;
if ( nullptr ! = BackgroundDownloadSession )
{
[ BackgroundDownloadSession getTasksWithCompletionHandler : ^ ( NSArray < __kindof NSURLSessionDataTask * > * DataTasks , NSArray < __kindof NSURLSessionUploadTask * > * UploadTasks , NSArray < __kindof NSURLSessionDownloadTask * > * DownloadTasks )
{
for ( NSURLSessionDownloadTask * DownloadTask : DownloadTasks )
{
if ( [ DownloadTask state ] = = NSURLSessionTaskStateRunning )
{
FString TaskURL = [ [ [ DownloadTask currentRequest ] URL ] absoluteString ] ;
2019-03-14 19:06:08 -04:00
int TaskIdentifier = ( int ) [ DownloadTask taskIdentifier ] ;
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Pausing Task for URL:%s | TaskIdentifier:%d " ) , * TaskURL , TaskIdentifier ) ;
2019-02-05 18:09:08 -05:00
[ DownloadTask suspend ] ;
}
}
} ] ;
}
}
2019-11-13 14:31:30 -05:00
void FApplePlatformBackgroundHttpManager : : ResumeTasksForBackgrounding ( FIOSBackgroundHttpPostSessionWorkCallback Callback )
2019-02-05 18:09:08 -05:00
{
2019-11-13 14:31:30 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Attempting to resume all active tasks that don't have a corresponding request that has paused them in priority order " ) ) ;
2019-02-05 18:09:08 -05:00
NSURLSession * BackgroundDownloadSession = FBackgroundURLSessionHandler : : GetBackgroundSession ( ) ;
if ( nullptr ! = BackgroundDownloadSession )
{
[ BackgroundDownloadSession getTasksWithCompletionHandler : ^ ( NSArray < __kindof NSURLSessionDataTask * > * DataTasks , NSArray < __kindof NSURLSessionUploadTask * > * UploadTasks , NSArray < __kindof NSURLSessionDownloadTask * > * DownloadTasks )
{
2019-11-26 11:53:43 -05:00
FRWScopeLock ScopeLock ( URLToRequestMapLock , SLT_ReadOnly ) ;
2019-11-13 14:31:30 -05:00
//We only want to automatically re-queue the highest priority things, so go through all tasks by priority order and stop once we have requeued something
bool bDidResumeATask = false ;
for ( uint8 PriorityAsInt = ( uint8 ) EBackgroundHTTPPriority : : High ; ( ! bDidResumeATask & & ( PriorityAsInt < ( uint8 ) EBackgroundHTTPPriority : : Num ) ) ; + + PriorityAsInt )
2019-02-05 18:09:08 -05:00
{
2019-11-13 14:31:30 -05:00
for ( NSURLSessionDownloadTask * DownloadTask : DownloadTasks )
2019-02-05 18:09:08 -05:00
{
2019-11-26 11:53:43 -05:00
FString TaskURL = [ [ [ DownloadTask currentRequest ] URL ] absoluteString ] ;
int TaskIdentifier = ( int ) [ DownloadTask taskIdentifier ] ;
FBackgroundHttpURLMappedRequestPtr * WeakRequestInMap = URLToRequestMap . Find ( TaskURL ) ;
FAppleBackgroundHttpRequestPtr FoundRequest = ( ( nullptr ! = WeakRequestInMap ) & & ( WeakRequestInMap - > IsValid ( ) ) ) ? WeakRequestInMap - > Pin ( ) : nullptr ;
if ( FoundRequest . IsValid ( ) )
{
2019-12-02 19:43:10 -05:00
bDidResumeATask = ResumeDownloadTaskForBackgroundingIfAppropriate ( DownloadTask , FoundRequest , EBackgroundHTTPPriority ( PriorityAsInt ) ) | | bDidResumeATask ;
2019-11-26 11:53:43 -05:00
}
else
{
UE_LOG ( LogBackgroundHttpManager , Verbose , TEXT ( " Skipped Resuming Task as there is no corresponding request! URL:%s | TaskIdentifier:%d " ) , * TaskURL , TaskIdentifier ) ;
}
2019-11-13 14:31:30 -05:00
}
if ( bDidResumeATask )
{
2019-12-02 20:37:13 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Tasks of Priority: %s Found. Not Resuming Any Lower Priority Tasks. " ) , LexToString ( ( EBackgroundHTTPPriority ) PriorityAsInt ) ) ;
2019-02-05 18:09:08 -05:00
}
2019-12-02 19:43:10 -05:00
else
{
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " No tasks found of priority %s. " ) , LexToString ( ( EBackgroundHTTPPriority ) PriorityAsInt ) ) ;
}
2019-02-05 18:09:08 -05:00
}
2019-11-13 14:31:30 -05:00
Callback . ExecuteIfBound ( ) ;
2019-02-05 18:09:08 -05:00
} ] ;
}
}
2019-11-26 11:53:43 -05:00
bool FApplePlatformBackgroundHttpManager : : ResumeDownloadTaskForBackgroundingIfAppropriate ( NSURLSessionDownloadTask * DownloadTask , const FAppleBackgroundHttpRequestPtr Request , EBackgroundHTTPPriority LowestPriorityToQueue )
2019-11-13 14:31:30 -05:00
{
2019-11-26 11:53:43 -05:00
if ( ( nullptr = = DownloadTask ) | | ( nullptr = = [ DownloadTask currentRequest ] ) | | ( ! Request . IsValid ( ) ) )
2019-11-13 14:31:30 -05:00
{
return false ;
}
bool bDidResumeTask = false ;
2019-12-02 20:37:13 -05:00
bool bDidFindActiveTaskOfPriority = false ;
2019-11-13 14:31:30 -05:00
2019-12-02 20:37:13 -05:00
FString TaskURL = [ [ [ DownloadTask currentRequest ] URL ] absoluteString ] ;
int TaskIdentifier = ( int ) [ DownloadTask taskIdentifier ] ;
2019-11-13 14:31:30 -05:00
2019-12-02 20:37:13 -05:00
EBackgroundHTTPPriority FoundRequestPriority = Request - > GetRequestPriority ( ) ;
if ( FoundRequestPriority < = LowestPriorityToQueue )
{
2019-11-26 11:53:43 -05:00
const bool bIsRequestPaused = Request - > bIsTaskPaused ;
2019-11-13 14:31:30 -05:00
if ( ! bIsRequestPaused )
{
2019-12-02 20:37:13 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Resuming Found Task for URL:%s | TaskIdentifier:%d | TaskPriority:%s " ) , * TaskURL , TaskIdentifier , LexToString ( FoundRequestPriority ) ) ;
const bool bIsTaskActive = Request - > IsUnderlyingTaskActive ( ) ;
//We only want to resume Suspended tasks
if ( [ DownloadTask state ] = = NSURLSessionTaskStateSuspended )
2019-11-13 14:31:30 -05:00
{
[ DownloadTask resume ] ;
bDidResumeTask = true ;
}
2019-12-02 20:37:13 -05:00
else if ( bIsTaskActive )
2019-11-13 14:31:30 -05:00
{
2019-12-02 20:37:13 -05:00
bDidFindActiveTaskOfPriority = true ;
2019-11-13 14:31:30 -05:00
}
}
else
{
UE_LOG ( LogBackgroundHttpManager , Verbose , TEXT ( " NOT Resuming Task for URL as associated request was paused! URL:%s | TaskIdentifier:%d " ) , * TaskURL , TaskIdentifier ) ;
}
}
2019-12-02 20:37:13 -05:00
else
{
UE_LOG ( LogBackgroundHttpManager , Verbose , TEXT ( " NOT RESUMING Task for URL because its a lower priority:%s | TaskIdentifier:%d | TaskPriority:%s | LowestPriorityToQueue:%s " ) , * TaskURL , TaskIdentifier , LexToString ( FoundRequestPriority ) , LexToString ( LowestPriorityToQueue ) ) ;
}
2019-11-13 14:31:30 -05:00
2019-12-02 20:37:13 -05:00
return ( bDidResumeTask | | bDidFindActiveTaskOfPriority ) ;
2019-11-13 14:31:30 -05:00
}
2019-02-05 18:09:08 -05:00
void FApplePlatformBackgroundHttpManager : : OnTask_DidFinishDownloadingToURL ( NSURLSessionDownloadTask * Task , NSError * Error , const FString & TempFilePath )
{
FString TaskURL = [ [ [ Task currentRequest ] URL ] absoluteString ] ;
2019-03-14 19:06:08 -04:00
int TaskIdentifier = ( int ) [ Task taskIdentifier ] ;
2019-02-05 18:09:08 -05:00
const int ErrorCode = [ Error code ] ;
const FString ErrorDescription = [ Error localizedDescription ] ;
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
const bool bFileExists = PlatformFile . FileExists ( * TempFilePath ) ;
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Recieved Background Session Callback for URL:%s | TaskIdentifier:%d | bFileExists:%d | ErrorCode:%d | ErrorDescription:%s | Location:%s " ) , * TaskURL , TaskIdentifier , ( int ) ( bFileExists ) , ErrorCode , * ErrorDescription , * TempFilePath ) ;
2019-02-05 18:09:08 -05:00
2019-02-15 21:06:27 -05:00
if ( bFileExists )
{
//Find request for this task and mark it complete
{
FRWScopeLock ScopeLock ( URLToRequestMapLock , SLT_ReadOnly ) ;
FBackgroundHttpURLMappedRequestPtr * WeakRequestInMap = URLToRequestMap . Find ( TaskURL ) ;
2019-11-26 11:53:43 -05:00
FAppleBackgroundHttpRequestPtr FoundRequest = ( ( nullptr ! = WeakRequestInMap ) & & ( WeakRequestInMap - > IsValid ( ) ) ) ? WeakRequestInMap - > Pin ( ) : nullptr ;
2019-02-15 21:06:27 -05:00
if ( FoundRequest . IsValid ( ) )
{
FoundRequest - > SetRequestAsSuccess ( TempFilePath ) ;
}
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Attempt To Mark Task Complete -- URL:%s | TaskIdentifier:%d |bDidFindTask:%d " ) , * TaskURL , TaskIdentifier , ( int ) ( FoundRequest . IsValid ( ) ) ) ;
2019-02-15 21:06:27 -05:00
}
}
else
{
//Forward to the OnCompleteWithError as we don't have our finished file!
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " File Not Found For DidFinishDownloadingToURL. Transitioning to DidCompleteWithError -- TaskURL:%s | TaskIdentifier:%d| ErrorCode:%d | ErrorDescription:%s | Location:%s " ) , * TaskURL , TaskIdentifier , ErrorCode , * ErrorDescription , * TempFilePath ) ;
2019-02-15 21:06:27 -05:00
OnTask_DidCompleteWithError ( Task , Error ) ;
}
2019-02-05 18:09:08 -05:00
}
void FApplePlatformBackgroundHttpManager : : FinishRequest ( FAppleBackgroundHttpRequestPtr Request )
{
2019-02-15 21:06:27 -05:00
//We should only come into here from the GameThread so that if we send out a complete event our delegate subscribers don't have to worry about being thread-safe unnessecarily
ensureAlwaysMsgf ( IsInGameThread ( ) , TEXT ( " Called from un-expected thread! Potential error in an implementation of background downloads! " ) ) ;
2019-02-05 18:09:08 -05:00
//Make sure we another thread hasn't already finished this request
bool bHasAlreadyFinishedRequest = FPlatformAtomics : : InterlockedExchange ( & ( Request - > bHasAlreadyFinishedRequest ) , true ) ;
if ( ! bHasAlreadyFinishedRequest )
{
//by default we will be finishing this request in this function, but some errors might prompt a retry out of this function
bool bIsRequestActuallyFinished = true ;
if ( ensureAlwaysMsgf ( Request . IsValid ( ) , TEXT ( " Call to FinishRequest with invalid request! " ) ) )
{
const FString & TempFilePath = Request - > CompletedTempDownloadLocation ;
IPlatformFile & PlatformFile = FPlatformFileManager : : Get ( ) . GetPlatformFile ( ) ;
const bool bFileExists = PlatformFile . FileExists ( * TempFilePath ) ;
int ResponseCode = bFileExists ? EHttpResponseCodes : : Created : EHttpResponseCodes : : Unknown ;
if ( bFileExists )
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Task Completed Successfully. RequestDebugID:%s TempFileLocation:%s " ) , * ( Request - > GetRequestDebugID ( ) ) , * TempFilePath ) ;
2019-02-05 18:09:08 -05:00
FBackgroundHttpResponsePtr NewResponse = FPlatformBackgroundHttp : : ConstructBackgroundResponse ( ResponseCode , * TempFilePath ) ;
Request - > CompleteWithExistingResponseData ( NewResponse ) ;
}
else
{
volatile bool bDidFail = FPlatformAtomics : : AtomicRead ( & ( Request - > bIsFailed ) ) ;
//Unexpected case where we didn't find a valid download, but we thought this completed
2019-02-15 21:06:27 -05:00
//successfully. Handle this unexpected failure by trying to retry the task.
2019-02-05 18:09:08 -05:00
if ( ! bDidFail )
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Error , TEXT ( " Task finished downloading, but finished temp file was not found! -- RequestDebugID:%s | TempFileLocation:%s " ) , * ( Request - > GetRequestDebugID ( ) ) , * TempFilePath ) ;
2019-02-05 18:09:08 -05:00
//Mark our download as not completed as we hit an error so that we don't just keep trying to call FinishRequest
FPlatformAtomics : : InterlockedExchange ( & ( Request - > bIsCompleted ) , false ) ;
FPlatformAtomics : : InterlockedExchange ( & ( Request - > bHasAlreadyFinishedRequest ) , false ) ;
//Just cancel the task. This will lead to it getting a callback to OnTask_DidCompleteWithError where we will re-create it
Request - > CancelActiveTask ( ) ;
bIsRequestActuallyFinished = false ;
}
//Expected case where we failed, but expected to fail
else
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Task failed completely -- RequestDebugID:%s " ) , * ( Request - > GetRequestDebugID ( ) ) ) ;
2019-02-05 18:09:08 -05:00
FBackgroundHttpResponsePtr NewResponse = FPlatformBackgroundHttp : : ConstructBackgroundResponse ( ResponseCode , TEXT ( " " ) ) ;
Request - > CompleteWithExistingResponseData ( NewResponse ) ;
}
}
2019-02-15 21:06:27 -05:00
//If we are actually finishing this request, lets decrement our NumCurrentlyActiveTasks counter
2019-02-05 18:09:08 -05:00
if ( bIsRequestActuallyFinished )
{
2019-11-26 11:53:43 -05:00
//only decrement NumCurrentlyActiveTasks if this was an actual active task,
//can still be completed in the BG or from existing completed data without being active
const bool bIsTaskActive = Request - > IsUnderlyingTaskActive ( ) ;
if ( bIsTaskActive )
2019-02-05 18:09:08 -05:00
{
2019-02-15 21:06:27 -05:00
int NumActualTasks = FPlatformAtomics : : InterlockedDecrement ( & NumCurrentlyActiveTasks ) ;
2019-02-05 18:09:08 -05:00
2019-08-21 09:37:24 -04:00
// Handle the case that SetMaxActiveDownloads reduced the MaxActiveDownloads while we had more than the new maximum in progress.
MaxNumActualTasks = FMath : : Max ( MaxNumActualTasks - 1 , MaxActiveDownloads . Load ( ) ) ;
2019-02-05 18:09:08 -05:00
//Sanity check that our data is valid. Shouldn't ever trip if everything is working as intended.
2019-08-21 09:37:24 -04:00
const bool bNumActualTasksIsValid = ( ( NumActualTasks > = 0 ) & & ( NumActualTasks < = MaxNumActualTasks ) ) ;
2019-02-15 21:06:27 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Finishing Request lowering Task Count: %d " ) , NumActualTasks ) ;
ensureMsgf ( bNumActualTasksIsValid , TEXT ( " Number of Requests we think are active is invalid! -- NumCurrentlyActiveTasks:%d " ) , NumActualTasks ) ;
2019-02-05 18:09:08 -05:00
}
}
}
}
else
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Not finishing Request as its already sending a finish notification -- RequestDebugID:%s " ) , * ( Request - > GetRequestDebugID ( ) ) ) ;
2019-02-05 18:09:08 -05:00
}
}
2019-11-13 14:31:30 -05:00
void FApplePlatformBackgroundHttpManager : : RetryRequest ( FAppleBackgroundHttpRequestPtr Request , bool bShouldIncreaseRetryCount , NSData * RetryData )
2019-02-05 18:09:08 -05:00
{
NSURLSessionDownloadTask * NewTask = nullptr ;
if ( ensureAlwaysMsgf ( Request . IsValid ( ) , TEXT ( " Call to RetryRequest with an invalid request! " ) ) )
{
NSURLSession * BackgroundDownloadSession = FBackgroundURLSessionHandler : : GetBackgroundSession ( ) ;
if ( ensureAlwaysMsgf ( ( nullptr ! = BackgroundDownloadSession ) , TEXT ( " Invalid Background Download NSURLSession during RetryRequest! Should have already Initialized the NSURLSession by this point! " ) ) )
{
//First, lets see if we should base this task of existing RetryData
const bool bShouldUseRetryData = ShouldUseRequestRetryData ( Request , RetryData ) ;
if ( bShouldUseRetryData )
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Resuming Task With Resume Data -- RequestDebugID:%s | RetryData Length:%d " ) , * ( Request - > GetRequestDebugID ( ) ) , [ RetryData length ] ) ;
2019-02-05 18:09:08 -05:00
NewTask = [ BackgroundDownloadSession downloadTaskWithResumeData : RetryData ] ;
}
//If not retry data, lets try and just retry on the next CDN
2019-03-14 19:06:08 -04:00
if ( nullptr = = NewTask )
2019-02-05 18:09:08 -05:00
{
//Since we created a new task instead of using retry data, reset resume data's retry count on the request
Request - > ResumeDataRetryCount . Reset ( ) ;
const FString & NewRetryURL = Request - > GetURLForRetry ( bShouldIncreaseRetryCount ) ;
const bool bShouldStartNewRequest = ! NewRetryURL . IsEmpty ( ) ;
if ( bShouldStartNewRequest )
{
NSURL * URL = [ NSURL URLWithString : NewRetryURL . GetNSString ( ) ] ;
NewTask = [ BackgroundDownloadSession downloadTaskWithURL : URL ] ;
}
}
if ( nullptr ! = NewTask )
{
Request - > AssociateWithTask ( NewTask ) ;
2019-11-13 14:31:30 -05:00
//If we are in BG activate right now without waiting for the FG tick
2019-02-05 18:09:08 -05:00
volatile bool bCopyOfBGState = FPlatformAtomics : : AtomicRead ( & bIsInBackground ) ;
2019-11-13 14:31:30 -05:00
//We also want to re-activate any already activated tasks
const bool bIsTaskActive = Request - > IsUnderlyingTaskActive ( ) ;
if ( ! Request - > bIsTaskPaused & & ( bCopyOfBGState | | bIsTaskActive ) )
2019-02-05 18:09:08 -05:00
{
Request - > ActivateUnderlyingTask ( ) ;
}
2019-11-13 14:31:30 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Created Task for Request -- RequestDebugID:%s | bIsAppInBG:%d | bIsPaused:%d " ) , * ( Request - > GetRequestDebugID ( ) ) , ( int ) bCopyOfBGState , ( int ) Request - > bIsTaskPaused ) ;
2019-02-05 18:09:08 -05:00
//Always set our bWasTaskStartedInBG flag on our Request so we will know if we need to restart this task next FG Tick.
FPlatformAtomics : : InterlockedExchange ( & ( Request - > bWasTaskStartedInBG ) , bCopyOfBGState ) ;
}
else
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Marking Request Failed. Out of Retries -- RequestDebugID:%s | bShouldUseRetryData:%d " ) , * ( Request - > GetRequestDebugID ( ) ) , ( int ) bShouldUseRetryData ) ;
2019-02-05 18:09:08 -05:00
Request - > SetRequestAsFailed ( ) ;
}
}
}
}
bool FApplePlatformBackgroundHttpManager : : ShouldUseRequestRetryData ( FAppleBackgroundHttpRequestPtr Request , NSData * RetryData ) const
{
bool bShouldUseData = false ;
if ( ensureAlwaysMsgf ( Request . IsValid ( ) , TEXT ( " Call to ShouldUseRequestRetryData with an invalid request! " ) ) )
{
if ( IsRetryDataValid ( RetryData ) )
{
const int CurrentResumeDataRetryCount = Request - > ResumeDataRetryCount . Increment ( ) ;
if ( ( RetryResumeDataLimitSetting < 0 ) | | ( CurrentResumeDataRetryCount < = RetryResumeDataLimitSetting ) )
{
bShouldUseData = true ;
}
}
}
return bShouldUseData ;
}
bool FApplePlatformBackgroundHttpManager : : IsRetryDataValid ( NSData * RetryData ) const
{
2019-06-07 13:18:42 -04:00
return ( ( nullptr ! = RetryData ) & & ( [ RetryData length ] > 0 ) ) ;
2019-02-05 18:09:08 -05:00
}
void FApplePlatformBackgroundHttpManager : : OnTask_DidWriteData ( NSURLSessionDownloadTask * Task , int64_t BytesWrittenSinceLastCall , int64_t TotalBytesWritten , int64_t TotalBytesExpectedToWrite )
{
if ( ensureAlwaysMsgf ( ( nullptr ! = Task ) , TEXT ( " Call to DidWriteData with invalid Task! " ) ) )
{
FString TaskURL = [ [ [ Task currentRequest ] URL ] absoluteString ] ;
2019-03-14 19:06:08 -04:00
int TaskIdentifier = ( int ) [ Task taskIdentifier ] ;
2019-02-05 18:09:08 -05:00
//Find task and update it's download progress
{
FRWScopeLock ScopeLock ( URLToRequestMapLock , SLT_ReadOnly ) ;
FBackgroundHttpURLMappedRequestPtr * WeakRequestInMap = URLToRequestMap . Find ( TaskURL ) ;
2019-11-26 11:53:43 -05:00
FAppleBackgroundHttpRequestPtr FoundRequest = ( ( nullptr ! = WeakRequestInMap ) & & ( WeakRequestInMap - > IsValid ( ) ) ) ? WeakRequestInMap - > Pin ( ) : nullptr ;
2019-02-05 18:09:08 -05:00
if ( FoundRequest . IsValid ( ) )
{
if ( FoundRequest - > DownloadProgress < TotalBytesWritten )
{
int64 DownloadProgress = FPlatformAtomics : : AtomicRead ( & ( FoundRequest - > DownloadProgress ) ) ;
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Verbose , TEXT ( " Updating Task Progress! -- RequestDebugID:%s | TaskIdentifier:%d | Current Progress:%lld | New Progress:%lld " ) , * ( FoundRequest - > GetRequestDebugID ( ) ) , TaskIdentifier , DownloadProgress , TotalBytesWritten ) ;
2019-02-05 18:09:08 -05:00
}
else
{
2019-03-14 19:06:08 -04:00
ensureAlwaysMsgf ( false , TEXT ( " Download Progress tried to go down not up unexpectidly! This could mean a task was unknowingly duplicated! -- RequestDebugID:%s | TaskIdentifier:%d | Current Progress:%lld | New Progress:%lld " ) , * ( FoundRequest - > GetRequestDebugID ( ) ) , TaskIdentifier , FoundRequest - > DownloadProgress , TotalBytesWritten ) ;
2019-02-05 18:09:08 -05:00
}
FoundRequest - > UpdateDownloadProgress ( TotalBytesWritten , BytesWrittenSinceLastCall ) ;
}
}
}
}
void FApplePlatformBackgroundHttpManager : : OnTask_DidCompleteWithError ( NSURLSessionTask * Task , NSError * Error )
{
if ( ensureAlwaysMsgf ( ( nullptr ! = Task ) , TEXT ( " Call to OnTask_DidCompleteWithError delegate with an invalid task! " ) ) )
{
FString TaskURL = [ [ [ Task currentRequest ] URL ] absoluteString ] ;
2019-03-14 19:06:08 -04:00
int TaskIdentifier = ( int ) [ Task taskIdentifier ] ;
2019-02-05 18:09:08 -05:00
const bool bDidCompleteWithError = ( nullptr ! = Error ) ;
const int ErrorCode = [ Error code ] ;
const FString ErrorDescription = [ Error localizedDescription ] ;
NSData * ResumeData = Error ? [ Error . userInfo objectForKey : NSURLSessionDownloadTaskResumeData ] : nullptr ;
const bool bHasResumeData = ( ResumeData & & ( [ ResumeData length ] > 0 ) ) ;
NSNumber * CancelledReasonKey = [ Error . userInfo objectForKey : NSURLErrorBackgroundTaskCancelledReasonKey ] ;
int CancelledReasonInt = ( nullptr ! = CancelledReasonKey ) ? [ CancelledReasonKey intValue ] : - 1 ;
FString DebugRetryOverrideReason ;
2019-03-14 19:06:08 -04:00
//We still come into the function when tasks complete successfully. Only handle actual errors
2019-02-05 18:09:08 -05:00
if ( bDidCompleteWithError )
{
FRWScopeLock ScopeLock ( URLToRequestMapLock , SLT_ReadOnly ) ;
FBackgroundHttpURLMappedRequestPtr * WeakRequestInMap = URLToRequestMap . Find ( TaskURL ) ;
2019-11-26 11:53:43 -05:00
FAppleBackgroundHttpRequestPtr FoundRequest = ( ( nullptr ! = WeakRequestInMap ) & & ( WeakRequestInMap - > IsValid ( ) ) ) ? WeakRequestInMap - > Pin ( ) : nullptr ;
2019-02-05 18:09:08 -05:00
const bool bDidFindValidRequest = FoundRequest . IsValid ( ) ;
//by default increase error count. Special cases below will overwrite this
bool bShouldRetryIncreaseRetryCount = true ;
//If we don't have internet, we don't want to move through our CDNs, but rather chain recreate download tasks until we regain internet
if ( [ Error code ] = = NSURLErrorNotConnectedToInternet )
{
bShouldRetryIncreaseRetryCount = false ;
DebugRetryOverrideReason = TEXT ( " Not Connected To Internet " ) ;
}
2019-06-07 13:18:42 -04:00
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " DidCompleteWithError for Task. -- URL:%s | TaskIdentifier:%d | bDidFindVaildRequest:%d | bDidCompleteWithError:%d | ErrorCode:%d | bHasResumeData:%d | CancelledReasonKey:%d | RetryOverrideReason:%s | bShouldRetryIncreaseRetryCount:%d | ErrorDescription:%s " ) , * TaskURL , TaskIdentifier , ( int ) bDidFindValidRequest , ( int ) bDidCompleteWithError , ErrorCode , ( int ) bHasResumeData , CancelledReasonInt , * DebugRetryOverrideReason , ( int ) bShouldRetryIncreaseRetryCount , * ErrorDescription ) ;
2019-02-05 18:09:08 -05:00
if ( bDidFindValidRequest )
{
2019-11-13 14:31:30 -05:00
RetryRequest ( FoundRequest , bShouldRetryIncreaseRetryCount , ResumeData ) ;
2019-02-05 18:09:08 -05:00
}
else
{
2019-03-14 19:06:08 -04:00
//This can be a valid case because of UnAssociatedTasks, so don't error here
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " No request for completing task! -- TaskURL:%s | TaskIdentifier:%d " ) , * TaskURL , TaskIdentifier ) ;
2019-02-05 18:09:08 -05:00
}
}
}
}
2019-11-13 14:31:30 -05:00
void FApplePlatformBackgroundHttpManager : : OnSession_SessionDidFinishAllEvents ( NSURLSession * Session , FIOSBackgroundDownloadCoreDelegates : : FIOSBackgroundDownload_DelayedBackgroundURLSessionCompleteHandler Callback )
2019-02-05 18:09:08 -05:00
{
2019-11-13 14:31:30 -05:00
//Let BackgroundURLSessionHandler know that it should wait until we call the callback
FBackgroundURLSessionHandler : : AddDelayedBackgroundURLSessionComplete ( ) ;
const bool bCopyIsBG = FPlatformAtomics : : AtomicRead ( & bIsInBackground ) ;
2019-12-02 20:37:13 -05:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " NSURLSession done sending background events for all already queued tasks. bWasAppleBGHTTPInitialized:%d | bIsBG:%d " ) , bWasAppleBGHTTPInitialized , bCopyIsBG ) ;
2019-11-26 11:53:43 -05:00
//If we are in the BG, or if we have not yet been initialized, lets go ahead and just immediately send the callback
if ( bWasAppleBGHTTPInitialized & & bCopyIsBG )
2019-11-13 14:31:30 -05:00
{
//Now that we have finished all queued background tasks, lets resume tasks for any lower priorities that weren't started
ResumeTasksForBackgrounding ( FIOSBackgroundDownloadCoreDelegates : : FIOSBackgroundDownload_DelayedBackgroundURLSessionCompleteHandler : : CreateLambda (
[ = ] ( )
{
Callback . ExecuteIfBound ( ) ;
} ) ) ;
}
else
{
Callback . ExecuteIfBound ( ) ;
}
2019-02-05 18:09:08 -05:00
}
bool FApplePlatformBackgroundHttpManager : : Tick ( float DeltaTime )
{
2020-09-01 14:07:48 -04:00
QUICK_SCOPE_CYCLE_COUNTER ( STAT_FApplePlatformBackgroundHttpManager_Tick ) ;
2019-02-15 21:06:27 -05:00
ensureAlwaysMsgf ( IsInGameThread ( ) , TEXT ( " Called from un-expected thread! Potential error in an implementation of background downloads! " ) ) ;
2019-02-05 18:09:08 -05:00
TickRequests ( DeltaTime ) ;
TickUnassociatedTasks ( DeltaTime ) ;
2020-09-01 14:07:48 -04:00
GetFileHashHelper ( ) - > SaveData ( ) ;
2019-02-05 18:09:08 -05:00
//Always keep ticking
return true ;
}
void FApplePlatformBackgroundHttpManager : : TickRequests ( float DeltaTime )
{
2019-02-15 21:06:27 -05:00
ensureAlwaysMsgf ( IsInGameThread ( ) , TEXT ( " Called from un-expected thread! Potential error in an implementation of background downloads! " ) ) ;
2019-02-05 18:09:08 -05:00
//First lets go through all our Requests to see if we need to complete or recreate any requests
{
2019-11-13 14:31:30 -05:00
//Check to make sure we have room for more tasks to be active first
int CurrentCount = FPlatformAtomics : : AtomicRead ( & NumCurrentlyActiveTasks ) ;
bool bNeedsMoreTasks = ( CurrentCount < MaxActiveDownloads ) ;
FAppleBackgroundHttpRequestPtr FoundRequestToStart = nullptr ;
FRWScopeLock ScopeLock ( ActiveRequestLock , SLT_ReadOnly ) ;
2019-02-05 18:09:08 -05:00
for ( FBackgroundHttpRequestPtr & Request : ActiveRequests )
{
FAppleBackgroundHttpRequestPtr AppleRequest = StaticCastSharedPtr < FApplePlatformBackgroundHttpRequest > ( Request ) ;
if ( ensureAlwaysMsgf ( AppleRequest . IsValid ( ) , TEXT ( " Invalid Request Pointer in ActiveRequests list! " ) ) )
{
const bool bIsTaskActive = AppleRequest - > IsUnderlyingTaskActive ( ) ;
2019-02-07 01:38:51 -05:00
const bool bIsTaskPaused = AppleRequest - > IsUnderlyingTaskPaused ( ) ;
2019-02-05 18:09:08 -05:00
const bool bIsTaskComplete = AppleRequest - > IsTaskComplete ( ) ;
const bool bWasStartedInBG = FPlatformAtomics : : AtomicRead ( & ( AppleRequest - > bWasTaskStartedInBG ) ) ;
2019-02-15 21:06:27 -05:00
const bool bIsPendingCancel = FPlatformAtomics : : AtomicRead ( & ( AppleRequest - > bIsPendingCancel ) ) ;
2019-02-05 18:09:08 -05:00
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , VeryVerbose , TEXT ( " Checking Status of Request on Tick -- RequestDebugID::%s | bIsTaskComplete:%d | bWasStartedInBG:%d " ) , * ( AppleRequest - > GetRequestDebugID ( ) ) , ( int ) bIsTaskComplete , ( int ) bWasStartedInBG ) ;
2019-02-05 18:09:08 -05:00
if ( bIsTaskComplete )
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Calling FinishRequest On -- RequestDebugID::%s | bIsTaskComplete:%d | bWasStartedInBG:%d " ) , * ( AppleRequest - > GetRequestDebugID ( ) ) , ( int ) bIsTaskComplete , ( int ) bWasStartedInBG ) ;
2019-02-05 18:09:08 -05:00
FinishRequest ( AppleRequest ) ;
}
2019-02-15 21:06:27 -05:00
else if ( bWasStartedInBG & & ! bIsPendingCancel )
2019-02-05 18:09:08 -05:00
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Cancelling Request Created In BG To Re-Create In FG -- RequestDebugID:%s " ) , * ( AppleRequest - > GetRequestDebugID ( ) ) ) ;
2019-02-05 18:09:08 -05:00
2019-02-15 21:06:27 -05:00
//reset to false so we don't run this twice while waiting on recreation
FPlatformAtomics : : InterlockedExchange ( & ( AppleRequest - > bWasTaskStartedInBG ) , false ) ;
2019-02-05 18:09:08 -05:00
//Just cancel the task. This will lead to it getting a callback to OnTask_DidCompleteWithError where we will re-create it
//We want to recreate any task spun up in the background as it will not respect our session settings if created in BG.
AppleRequest - > CancelActiveTask ( ) ;
}
2019-02-15 21:06:27 -05:00
else if ( bIsTaskActive & & ! bIsTaskPaused & & ! bIsPendingCancel )
2019-02-05 18:09:08 -05:00
{
const bool bShouldTimeOut = AppleRequest - > TickTimeOutTimer ( DeltaTime ) ;
if ( bShouldTimeOut )
{
2019-03-14 19:06:08 -04:00
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Timing out Request Due To Lack of Server Response -- RequestDebugID:%s " ) , * ( AppleRequest - > GetRequestDebugID ( ) ) ) ;
2019-02-05 18:09:08 -05:00
//Just cancel the task and let the OnTask_DidCompleteWithError callback handle retrying it if appropriate.
AppleRequest - > CancelActiveTask ( ) ;
}
2019-02-15 21:06:27 -05:00
}
2019-11-13 14:31:30 -05:00
else if ( bNeedsMoreTasks & & ! bIsTaskActive & & ! bIsTaskPaused & & ! bIsPendingCancel )
{
//This task is a possible to start, so lets see if its the highest priority task we have found yet.
if ( ! FoundRequestToStart . IsValid ( ) )
{
FoundRequestToStart = AppleRequest ;
}
else
{
if ( AppleRequest - > GetRequestPriority ( ) < FoundRequestToStart - > GetRequestPriority ( ) )
{
FoundRequestToStart = AppleRequest ;
}
}
}
2019-12-03 15:56:40 -05:00
AppleRequest - > SendDownloadProgressUpdate ( ) ;
2019-02-05 18:09:08 -05:00
}
}
2019-11-13 14:31:30 -05:00
if ( FoundRequestToStart . IsValid ( ) )
{
const int32 OldTotal = FPlatformAtomics : : InterlockedIncrement ( & NumCurrentlyActiveTasks ) ;
if ( OldTotal < = MaxActiveDownloads )
{
UE_LOG ( LogBackgroundHttpManager , Display , TEXT ( " Starting Task for Request -- RequestDebugID:%s | CurrentlyActiveRequests:%d " ) , * ( FoundRequestToStart - > GetRequestDebugID ( ) ) , OldTotal ) ;
FoundRequestToStart - > ActivateUnderlyingTask ( ) ;
}
else
{
UE_LOG ( LogBackgroundHttpManager , Log , TEXT ( " Request failed to activate as we passed the platform max from another requeset before we could resume. RequestDebugID:%s | CurrentlyActiveRequests:%d " ) , * ( FoundRequestToStart - > GetRequestDebugID ( ) ) , OldTotal ) ;
//Don't activate and remove our increment from above because something put us over the limit before we resumed
FPlatformAtomics : : InterlockedDecrement ( & NumCurrentlyActiveTasks ) ;
}
}
2019-02-05 18:09:08 -05:00
}
//Now that we have gone through and finished all the requests, go ahead and delete any pending removes
DeletePendingRemoveRequests ( ) ;
}
void FApplePlatformBackgroundHttpManager : : TickUnassociatedTasks ( float DeltaTime )
{
2019-02-15 21:06:27 -05:00
ensureAlwaysMsgf ( IsInGameThread ( ) , TEXT ( " Called from un-expected thread! Potential error in an implementation of background downloads! " ) ) ;
2019-02-05 18:09:08 -05:00
//If we don't have anything queued, lets resume any un-associated tasks
2019-02-15 21:06:27 -05:00
int CurrentCount = FPlatformAtomics : : AtomicRead ( & NumCurrentlyActiveTasks ) ;
2019-02-05 18:09:08 -05:00
if ( CurrentCount = = 0 )
{
UnpauseAllUnassociatedTasks ( ) ;
}
else
{
//we have something queued, lets pause unassociated tasks
PauseAllUnassociatedTasks ( ) ;
}
}
2020-09-01 14:07:48 -04:00
//Make sure we are using the FBackgroundURLSessionHandler's version so that both our results are synced
BackgroundHttpFileHashHelperRef FApplePlatformBackgroundHttpManager : : GetFileHashHelper ( )
{
return FBackgroundURLSessionHandler : : GetFileHashHelper ( ) ;
}
const BackgroundHttpFileHashHelperRef FApplePlatformBackgroundHttpManager : : GetFileHashHelper ( ) const
{
return FBackgroundURLSessionHandler : : GetFileHashHelper ( ) ;
}