This repository has been archived on 2022-03-12. You can view files and clone it, but cannot push or open issues or pull requests.
reValuate/node_modules/expo-updates/ios/EXUpdates/EXUpdatesAppController.m

287 lines
11 KiB
Mathematica
Raw Normal View History

2021-04-02 02:24:13 +03:00
// Copyright © 2019 650 Industries. All rights reserved.
#import <EXUpdates/EXUpdatesAppController.h>
#import <EXUpdates/EXUpdatesAppLauncher.h>
#import <EXUpdates/EXUpdatesAppLauncherNoDatabase.h>
#import <EXUpdates/EXUpdatesAppLauncherWithDatabase.h>
#import <EXUpdates/EXUpdatesReaper.h>
#import <EXUpdates/EXUpdatesSelectionPolicyNewest.h>
#import <EXUpdates/EXUpdatesUtils.h>
NS_ASSUME_NONNULL_BEGIN
static NSString * const EXUpdatesAppControllerErrorDomain = @"EXUpdatesAppController";
static NSString * const EXUpdatesConfigPlistName = @"Expo";
static NSString * const EXUpdatesUpdateAvailableEventName = @"updateAvailable";
static NSString * const EXUpdatesNoUpdateAvailableEventName = @"noUpdateAvailable";
static NSString * const EXUpdatesErrorEventName = @"error";
@interface EXUpdatesAppController ()
@property (nonatomic, readwrite, strong) EXUpdatesConfig *config;
@property (nonatomic, readwrite, strong) id<EXUpdatesAppLauncher> launcher;
@property (nonatomic, readwrite, strong) EXUpdatesDatabase *database;
@property (nonatomic, readwrite, strong) id<EXUpdatesSelectionPolicy> selectionPolicy;
@property (nonatomic, readwrite, strong) dispatch_queue_t assetFilesQueue;
@property (nonatomic, readwrite, strong) NSURL *updatesDirectory;
@property (nonatomic, strong) id<EXUpdatesAppLauncher> candidateLauncher;
@property (nonatomic, assign) BOOL hasLaunched;
@property (nonatomic, strong) dispatch_queue_t controllerQueue;
@property (nonatomic, assign) BOOL isStarted;
@property (nonatomic, assign) BOOL isEmergencyLaunch;
@end
@implementation EXUpdatesAppController
+ (instancetype)sharedInstance
{
static EXUpdatesAppController *theController;
static dispatch_once_t once;
dispatch_once(&once, ^{
if (!theController) {
theController = [[EXUpdatesAppController alloc] init];
}
});
return theController;
}
- (instancetype)init
{
if (self = [super init]) {
_config = [self _loadConfigFromExpoPlist];
_database = [[EXUpdatesDatabase alloc] init];
_selectionPolicy = [[EXUpdatesSelectionPolicyNewest alloc] initWithRuntimeVersion:[EXUpdatesUtils getRuntimeVersionWithConfig:_config]];
_assetFilesQueue = dispatch_queue_create("expo.controller.AssetFilesQueue", DISPATCH_QUEUE_SERIAL);
_controllerQueue = dispatch_queue_create("expo.controller.ControllerQueue", DISPATCH_QUEUE_SERIAL);
_isStarted = NO;
}
return self;
}
- (void)setConfiguration:(NSDictionary *)configuration
{
if (_updatesDirectory) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"EXUpdatesAppController:setConfiguration should not be called after start"
userInfo:@{}];
}
[_config loadConfigFromDictionary:configuration];
_selectionPolicy = [[EXUpdatesSelectionPolicyNewest alloc] initWithRuntimeVersion:[EXUpdatesUtils getRuntimeVersionWithConfig:_config]];
}
- (void)start
{
NSAssert(!_updatesDirectory, @"EXUpdatesAppController:start should only be called once per instance");
if (!_config.isEnabled) {
EXUpdatesAppLauncherNoDatabase *launcher = [[EXUpdatesAppLauncherNoDatabase alloc] init];
_launcher = launcher;
[launcher launchUpdateWithConfig:_config];
if (_delegate) {
[_delegate appController:self didStartWithSuccess:self.launchAssetUrl != nil];
}
return;
}
if (!_config.updateUrl) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"expo-updates is enabled, but no valid URL is configured under EXUpdatesURL. If you are making a release build for the first time, make sure you have run `expo publish` at least once."
userInfo:@{}];
}
_isStarted = YES;
NSError *fsError;
_updatesDirectory = [EXUpdatesUtils initializeUpdatesDirectoryWithError:&fsError];
if (fsError) {
[self _emergencyLaunchWithFatalError:fsError];
return;
}
__block BOOL dbSuccess;
__block NSError *dbError;
dispatch_semaphore_t dbSemaphore = dispatch_semaphore_create(0);
dispatch_async(_database.databaseQueue, ^{
dbSuccess = [self->_database openDatabaseInDirectory:self->_updatesDirectory withError:&dbError];
dispatch_semaphore_signal(dbSemaphore);
});
dispatch_semaphore_wait(dbSemaphore, DISPATCH_TIME_FOREVER);
if (!dbSuccess) {
[self _emergencyLaunchWithFatalError:dbError];
return;
}
EXUpdatesAppLoaderTask *loaderTask = [[EXUpdatesAppLoaderTask alloc] initWithConfig:_config
database:_database
directory:_updatesDirectory
selectionPolicy:_selectionPolicy
delegateQueue:_controllerQueue];
loaderTask.delegate = self;
[loaderTask start];
}
- (void)startAndShowLaunchScreen:(UIWindow *)window
{
NSBundle *mainBundle = [NSBundle mainBundle];
UIViewController *rootViewController = [UIViewController new];
NSString *launchScreen = (NSString *)[mainBundle objectForInfoDictionaryKey:@"UILaunchStoryboardName"] ?: @"LaunchScreen";
if ([mainBundle pathForResource:launchScreen ofType:@"nib"] != nil) {
NSArray *views = [mainBundle loadNibNamed:launchScreen owner:self options:nil];
rootViewController.view = views.firstObject;
rootViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
} else if ([mainBundle pathForResource:launchScreen ofType:@"storyboard"] != nil ||
[mainBundle pathForResource:launchScreen ofType:@"storyboardc"] != nil) {
UIStoryboard *launchScreenStoryboard = [UIStoryboard storyboardWithName:launchScreen bundle:nil];
rootViewController = [launchScreenStoryboard instantiateInitialViewController];
} else {
NSLog(@"Launch screen could not be loaded from a .xib or .storyboard. Unexpected loading behavior may occur.");
UIView *view = [UIView new];
view.backgroundColor = [UIColor whiteColor];
rootViewController.view = view;
}
window.rootViewController = rootViewController;
[window makeKeyAndVisible];
[self start];
}
- (void)requestRelaunchWithCompletion:(EXUpdatesAppRelaunchCompletionBlock)completion
{
if (_bridge) {
EXUpdatesAppLauncherWithDatabase *launcher = [[EXUpdatesAppLauncherWithDatabase alloc] initWithConfig:_config database:_database directory:_updatesDirectory completionQueue:_controllerQueue];
_candidateLauncher = launcher;
[launcher launchUpdateWithSelectionPolicy:self->_selectionPolicy completion:^(NSError * _Nullable error, BOOL success) {
if (success) {
self->_launcher = self->_candidateLauncher;
completion(YES);
[self->_bridge reload];
[self _runReaper];
} else {
NSLog(@"Failed to relaunch: %@", error.localizedDescription);
completion(NO);
}
}];
} else {
NSLog(@"EXUpdatesAppController: Failed to reload because bridge was nil. Did you set the bridge property on the controller singleton?");
completion(NO);
}
}
- (nullable EXUpdatesUpdate *)launchedUpdate
{
return _launcher.launchedUpdate ?: nil;
}
- (nullable NSURL *)launchAssetUrl
{
return _launcher.launchAssetUrl ?: nil;
}
- (nullable NSDictionary *)assetFilesMap
{
return _launcher.assetFilesMap ?: nil;
}
- (BOOL)isUsingEmbeddedAssets
{
if (!_launcher) {
return YES;
}
return _launcher.isUsingEmbeddedAssets;
}
# pragma mark - EXUpdatesAppLoaderTaskDelegate
- (BOOL)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didLoadCachedUpdate:(nonnull EXUpdatesUpdate *)update
{
return YES;
}
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didStartLoadingUpdate:(EXUpdatesUpdate *)update
{
// do nothing here for now
}
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishWithLauncher:(id<EXUpdatesAppLauncher>)launcher isUpToDate:(BOOL)isUpToDate
{
_launcher = launcher;
if (self->_delegate) {
[EXUpdatesUtils runBlockOnMainThread:^{
[self->_delegate appController:self didStartWithSuccess:YES];
}];
}
}
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishWithError:(NSError *)error
{
[self _emergencyLaunchWithFatalError:error];
}
- (void)appLoaderTask:(EXUpdatesAppLoaderTask *)appLoaderTask didFinishBackgroundUpdateWithStatus:(EXUpdatesBackgroundUpdateStatus)status update:(nullable EXUpdatesUpdate *)update error:(nullable NSError *)error
{
if (status == EXUpdatesBackgroundUpdateStatusError) {
NSAssert(error != nil, @"Background update with error status must have a nonnull error object");
[EXUpdatesUtils sendEventToBridge:_bridge withType:EXUpdatesErrorEventName body:@{@"message": error.localizedDescription}];
} else if (status == EXUpdatesBackgroundUpdateStatusUpdateAvailable) {
NSAssert(update != nil, @"Background update with error status must have a nonnull update object");
[EXUpdatesUtils sendEventToBridge:_bridge withType:EXUpdatesUpdateAvailableEventName body:@{@"manifest": update.rawManifest}];
} else if (status == EXUpdatesBackgroundUpdateStatusNoUpdateAvailable) {
[EXUpdatesUtils sendEventToBridge:_bridge withType:EXUpdatesNoUpdateAvailableEventName body:@{}];
}
}
# pragma mark - internal
- (EXUpdatesConfig *)_loadConfigFromExpoPlist
{
NSString *configPath = [[NSBundle mainBundle] pathForResource:EXUpdatesConfigPlistName ofType:@"plist"];
if (!configPath) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Cannot load configuration from Expo.plist. Please ensure you've followed the setup and installation instructions for expo-updates to create Expo.plist and add it to your Xcode project."
userInfo:@{}];
}
return [EXUpdatesConfig configWithDictionary:[NSDictionary dictionaryWithContentsOfFile:configPath]];
}
- (void)_runReaper
{
if (_launcher.launchedUpdate) {
[EXUpdatesReaper reapUnusedUpdatesWithConfig:_config
database:_database
directory:_updatesDirectory
selectionPolicy:_selectionPolicy
launchedUpdate:_launcher.launchedUpdate];
}
}
- (void)_emergencyLaunchWithFatalError:(NSError *)error
{
_isEmergencyLaunch = YES;
EXUpdatesAppLauncherNoDatabase *launcher = [[EXUpdatesAppLauncherNoDatabase alloc] init];
_launcher = launcher;
[launcher launchUpdateWithConfig:_config fatalError:error];
if (_delegate) {
[EXUpdatesUtils runBlockOnMainThread:^{
[self->_delegate appController:self didStartWithSuccess:self.launchAssetUrl != nil];
}];
}
}
@end
NS_ASSUME_NONNULL_END