2014-08-31 13:13:46 -04:00
// MainFrameViewController . m
2015-05-12 21:48:30 -04:00
// Moonlight
2014-08-31 13:13:46 -04:00
//
// Created by Diego Waxemberg on 1 / 17 / 14.
2015-05-12 21:48:30 -04:00
// Copyright ( c ) 2014 Moonlight Stream . All rights reserved .
2014-08-31 13:13:46 -04:00
//
2015-11-10 21:23:00 -08:00
@ import ImageIO ;
2014-08-31 13:13:46 -04:00
# import "MainFrameViewController.h"
2014-10-16 21:20:27 -04:00
# import "CryptoManager.h"
2014-10-18 21:13:48 -04:00
# import "HttpManager.h"
2014-10-20 11:31:40 -04:00
# import "Connection.h"
2014-10-20 13:35:42 -04:00
# import "StreamManager.h"
2014-10-20 20:54:03 -04:00
# import "Utils.h"
2014-10-24 16:02:10 -04:00
# import "UIComputerView.h"
# import "UIAppView.h"
# import "App.h"
2014-10-29 00:47:51 -04:00
# import "SettingsViewController.h"
# import "DataManager.h"
# import "Settings.h"
2015-01-02 19:15:47 -05:00
# import "WakeOnLanManager.h"
2015-02-01 04:26:47 -05:00
# import "AppListResponse.h"
# import "ServerInfoResponse.h"
2015-02-01 19:46:28 -05:00
# import "StreamFrameViewController.h"
2015-02-24 22:13:33 -05:00
# import "LoadingFrameViewController.h"
2015-09-30 19:13:37 -07:00
# import "ComputerScrollView.h"
2015-09-30 20:34:52 -07:00
# import "TemporaryApp.h"
2015-10-31 23:03:21 -07:00
# import "IdManager.h"
2014-08-31 13:13:46 -04:00
2014-10-20 11:31:40 -04:00
@ implementation MainFrameViewController {
NSOperationQueue * _opQueue ;
2015-01-01 22:30:03 -05:00
Host * _selectedHost ;
2014-10-26 02:15:53 -04:00
NSString * _uniqueId ;
NSData * _cert ;
2015-01-01 22:30:03 -05:00
DiscoveryManager * _discMan ;
2015-01-31 20:10:14 -05:00
AppAssetManager * _appManager ;
2015-02-01 19:46:28 -05:00
StreamConfiguration * _streamConfig ;
2015-06-23 22:23:12 -07:00
UIAlertController * _pairAlert ;
2014-10-26 02:15:53 -04:00
UIScrollView * hostScrollView ;
2015-01-01 16:50:10 -05:00
int currentPosition ;
2015-07-11 01:28:52 -07:00
NSArray * _sortedAppList ;
2015-11-10 21:23:00 -08:00
NSCache * _boxArtCache ;
2014-10-20 11:31:40 -04:00
}
2014-10-26 02:15:53 -04:00
static NSMutableSet * hostList ;
2014-08-31 13:13:46 -04:00
2014-10-20 13:35:42 -04:00
- ( void ) showPIN : ( NSString * ) PIN {
2015-07-09 21:02:19 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-06-23 22:23:12 -07:00
_pairAlert = [ UIAlertController alertControllerWithTitle : @ "Pairing"
message : [ NSString stringWithFormat : @ "Enter the following PIN on the host machine: %@" , PIN ]
preferredStyle : UIAlertControllerStyleAlert ] ;
[ self presentViewController : _pairAlert animated : YES completion : nil ] ;
2014-10-20 13:35:42 -04:00
} ) ;
}
- ( void ) pairFailed : ( NSString * ) message {
2015-07-09 21:02:19 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-06-23 22:23:12 -07:00
[ _pairAlert dismissViewControllerAnimated : YES completion : nil ] ;
_pairAlert = [ UIAlertController alertControllerWithTitle : @ "Pairing Failed"
message : message
preferredStyle : UIAlertControllerStyleAlert ] ;
[ _pairAlert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDestructive handler : nil ] ] ;
[ self presentViewController : _pairAlert animated : YES completion : nil ] ;
2015-01-02 23:57:11 -05:00
[ _discMan startDiscovery ] ;
2015-02-24 22:33:14 -05:00
[ self hideLoadingFrame ] ;
2014-10-20 13:35:42 -04:00
} ) ;
}
- ( void ) pairSuccessful {
2015-07-09 21:02:19 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-06-23 22:23:12 -07:00
[ _pairAlert dismissViewControllerAnimated : YES completion : nil ] ;
2015-01-02 23:57:11 -05:00
[ _discMan startDiscovery ] ;
2015-07-09 20:39:15 -07:00
[ self alreadyPaired ] ;
2014-10-20 13:35:42 -04:00
} ) ;
}
2014-10-26 02:15:53 -04:00
- ( void ) alreadyPaired {
2015-07-10 21:22:57 -07:00
BOOL usingCachedAppList = false ;
2015-07-11 23:59:09 -07:00
// Capture the host here because it can change once we
// leave the main thread
Host * host = _selectedHost ;
if ( [ host . appList count ] > 0 ) {
2015-07-10 21:22:57 -07:00
usingCachedAppList = true ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-07-11 23:59:09 -07:00
if ( host ! = _selectedHost ) {
return ;
}
_computerNameButton . title = host . name ;
2015-07-10 21:22:57 -07:00
[ self . navigationController . navigationBar setNeedsLayout ] ;
2015-07-11 23:59:09 -07:00
[ self updateAppsForHost : host ] ;
2015-07-10 21:22:57 -07:00
[ self hideLoadingFrame ] ;
} ) ;
}
Log ( LOG_I , @ "Using cached app list: %d" , usingCachedAppList ) ;
2014-10-26 02:15:53 -04:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
2015-07-11 23:59:09 -07:00
HttpManager * hMan = [ [ HttpManager alloc ] initWithHost : host . activeAddress uniqueId : _uniqueId deviceName : deviceName cert : _cert ] ;
2015-02-01 04:26:47 -05:00
2015-07-12 00:23:48 -07:00
// Exempt this host from discovery while handling the applist query
[ _discMan removeHostFromDiscovery : host ] ;
2015-11-11 18:14:59 -08:00
// Try up to 5 times to get the app list
AppListResponse * appListResp ;
for ( int i = 0 ; i < 5 ; i + + ) {
appListResp = [ [ AppListResponse alloc ] init ] ;
[ hMan executeRequestSynchronously : [ HttpRequest requestForResponse : appListResp withUrlRequest : [ hMan newAppListRequest ] ] ] ;
if ( appListResp = = nil || ! [ appListResp isStatusOk ] || [ appListResp getAppList ] = = nil ) {
Log ( LOG_W , @ "Failed to get applist on try %d: %@" , i , appListResp . statusMessage ) ;
// Wait for one second then retry
[ NSThread sleepForTimeInterval : 1 ] ;
}
else {
Log ( LOG_I , @ "App list successfully retreived - took %d tries" , i ) ;
break ;
}
}
2015-07-12 00:23:48 -07:00
[ _discMan addHostToDiscovery : host ] ;
2015-07-10 21:22:57 -07:00
if ( appListResp = = nil || ! [ appListResp isStatusOk ] || [ appListResp getAppList ] = = nil ) {
2015-02-10 21:26:55 -05:00
Log ( LOG_W , @ "Failed to get applist: %@" , appListResp . statusMessage ) ;
2015-07-09 21:15:52 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-07-11 23:59:09 -07:00
[ self hideLoadingFrame ] ;
2015-07-12 00:30:44 -07:00
if ( host ! = _selectedHost ) {
return ;
}
2015-07-09 21:15:52 -07:00
UIAlertController * applistAlert = [ UIAlertController alertControllerWithTitle : @ "Fetching App List Failed"
message : @ "The connection to the PC was interrupted."
preferredStyle : UIAlertControllerStyleAlert ] ;
[ applistAlert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDestructive handler : nil ] ] ;
[ self presentViewController : applistAlert animated : YES completion : nil ] ;
2015-07-11 23:59:09 -07:00
host . online = NO ;
2015-07-10 11:52:27 -07:00
[ self showHostSelectionView ] ;
2015-07-09 21:15:52 -07:00
} ) ;
2015-01-31 01:57:23 -05:00
} else {
2015-07-09 21:15:52 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-07-11 23:59:09 -07:00
[ self mergeAppLists : [ appListResp getAppList ] forHost : host ] ;
if ( host ! = _selectedHost ) {
return ;
}
_computerNameButton . title = host . name ;
2015-07-09 21:15:52 -07:00
[ self . navigationController . navigationBar setNeedsLayout ] ;
2015-01-31 01:57:23 -05:00
2015-07-11 23:59:09 -07:00
[ self updateAppsForHost : host ] ;
[ _appManager stopRetrieving ] ;
[ _appManager retrieveAssetsFromHost : host ] ;
[ self hideLoadingFrame ] ;
2015-07-09 21:15:52 -07:00
} ) ;
2015-01-31 01:57:23 -05:00
}
2014-10-26 02:15:53 -04:00
} ) ;
}
2015-07-11 23:59:09 -07:00
- ( void ) mergeAppLists : ( NSArray * ) newList forHost : ( Host * ) host {
2015-07-11 18:47:05 -07:00
DataManager * database = [ [ DataManager alloc ] init ] ;
2015-09-30 20:34:52 -07:00
for ( TemporaryApp * app in newList ) {
2015-07-11 01:28:52 -07:00
BOOL appAlreadyInList = NO ;
2015-07-11 23:59:09 -07:00
for ( App * savedApp in host . appList ) {
2015-07-10 22:19:25 -07:00
if ( [ app . id isEqualToString : savedApp . id ] ) {
2015-09-30 20:44:14 -07:00
savedApp . name = app . name ;
2015-07-12 00:12:53 -07:00
savedApp . isRunning = app . isRunning ;
2015-07-10 21:22:57 -07:00
appAlreadyInList = YES ;
2015-07-11 01:28:52 -07:00
break ;
2015-07-10 21:22:57 -07:00
}
}
if ( ! appAlreadyInList ) {
2015-07-11 23:59:09 -07:00
app . host = host ;
2015-09-30 20:34:52 -07:00
[ database addAppFromTemporaryApp : app ] ;
2015-07-10 22:19:25 -07:00
}
}
2015-09-30 20:44:14 -07:00
BOOL appWasRemoved ;
do {
appWasRemoved = NO ;
for ( App * app in host . appList ) {
appWasRemoved = YES ;
for ( App * mergedApp in newList ) {
if ( [ mergedApp . id isEqualToString : app . id ] ) {
appWasRemoved = NO ;
break ;
}
}
if ( appWasRemoved ) {
// Removing the app mutates the list we ' re iterating ( which isn ' t legal ) .
// We need to jump out of this loop and restart enumeration .
[ database removeAppFromHost : app ] ;
2015-07-11 01:28:52 -07:00
break ;
2015-07-10 22:19:25 -07:00
}
}
2015-09-30 20:44:14 -07:00
// Keep looping until the list is no longer being mutated
} while ( appWasRemoved ) ;
2015-07-11 18:47:05 -07:00
[ database saveData ] ;
2015-07-10 21:22:57 -07:00
}
2014-12-04 19:54:21 +09:00
- ( void ) showHostSelectionView {
2015-02-10 18:40:22 -05:00
[ _appManager stopRetrieving ] ;
2015-07-11 18:47:05 -07:00
[ [ [ DataManager alloc ] init ] saveData ] ;
2015-07-11 01:28:52 -07:00
_selectedHost = nil ;
2015-01-01 23:30:18 -05:00
_computerNameButton . title = @ "No Host Selected" ;
2014-12-04 19:54:21 +09:00
[ self . collectionView reloadData ] ;
[ self . view addSubview : hostScrollView ] ;
}
2014-10-26 02:15:53 -04:00
- ( void ) receivedAssetForApp : ( App * ) app {
2015-11-10 21:23:00 -08:00
// Update the box art cache now so we don ' t have to do it
// on the main thread
[ self updateBoxArtCacheForApp : app ] ;
2015-07-11 23:59:09 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self . collectionView reloadData ] ;
} ) ;
2014-10-26 02:15:53 -04:00
}
2014-10-21 14:31:03 -04:00
- ( void ) displayDnsFailedDialog {
UIAlertController * alert = [ UIAlertController alertControllerWithTitle : @ "Network Error"
message : @ "Failed to resolve host."
preferredStyle : UIAlertControllerStyleAlert ] ;
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDestructive handler : nil ] ] ;
[ self presentViewController : alert animated : YES completion : nil ] ;
}
2015-07-09 21:15:52 -07:00
- ( void ) hostClicked : ( Host * ) host view : ( UIView * ) view {
// Treat clicks on offline hosts to be long clicks
// This shows the context menu with wake , delete , etc . rather
// than just hanging for a while and failing as we would in this
// code path .
if ( ! host . online && view ! = nil ) {
[ self hostLongClicked : host view : view ] ;
return ;
}
2015-02-10 21:26:55 -05:00
Log ( LOG_D , @ "Clicked host: %@" , host . name ) ;
2015-01-01 22:30:03 -05:00
_selectedHost = host ;
2015-07-11 23:59:09 -07:00
[ self disableNavigation ] ;
2015-07-11 21:59:41 -07:00
// If we are online , paired , and have a cached app list , skip straight
// to the app grid without a loading frame . This is the fast path that users
// should hit most .
if ( host . online && host . pairState = = PairStatePaired && host . appList . count > 0 ) {
2015-07-11 23:59:09 -07:00
[ self alreadyPaired ] ;
2015-07-11 21:59:41 -07:00
return ;
}
[ self showLoadingFrame ] ;
2014-10-26 02:15:53 -04:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
2015-07-10 00:28:51 -07:00
HttpManager * hMan = [ [ HttpManager alloc ] initWithHost : host . activeAddress uniqueId : _uniqueId deviceName : deviceName cert : _cert ] ;
2015-02-01 04:26:47 -05:00
ServerInfoResponse * serverInfoResp = [ [ ServerInfoResponse alloc ] init ] ;
2015-07-12 00:23:48 -07:00
// Exempt this host from discovery while handling the serverinfo request
[ _discMan removeHostFromDiscovery : host ] ;
2015-07-08 23:03:23 -07:00
[ hMan executeRequestSynchronously : [ HttpRequest requestForResponse : serverInfoResp withUrlRequest : [ hMan newServerInfoRequest ]
fallbackError : 401 fallbackRequest : [ hMan newHttpServerInfoRequest ] ] ] ;
2015-07-12 00:23:48 -07:00
[ _discMan addHostToDiscovery : host ] ;
2015-01-31 01:57:23 -05:00
if ( serverInfoResp = = nil || ! [ serverInfoResp isStatusOk ] ) {
2015-02-10 21:26:55 -05:00
Log ( LOG_W , @ "Failed to get server info: %@" , serverInfoResp . statusMessage ) ;
2015-07-09 21:15:52 -07:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-07-11 23:59:09 -07:00
[ self hideLoadingFrame ] ;
2015-07-12 00:30:44 -07:00
if ( host ! = _selectedHost ) {
return ;
}
2015-07-09 21:15:52 -07:00
UIAlertController * applistAlert = [ UIAlertController alertControllerWithTitle : @ "Fetching Server Info Failed"
message : @ "The connection to the PC was interrupted."
preferredStyle : UIAlertControllerStyleAlert ] ;
[ applistAlert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDestructive handler : nil ] ] ;
[ self presentViewController : applistAlert animated : YES completion : nil ] ;
2015-07-10 11:52:27 -07:00
host . online = NO ;
[ self showHostSelectionView ] ;
2015-07-09 21:15:52 -07:00
} ) ;
2014-10-26 02:15:53 -04:00
} else {
2015-02-10 21:26:55 -05:00
Log ( LOG_D , @ "server info pair status: %@" , [ serverInfoResp getStringTag : @ "PairStatus" ] ) ;
2015-02-01 04:26:47 -05:00
if ( [ [ serverInfoResp getStringTag : @ "PairStatus" ] isEqualToString : @ "1" ] ) {
2015-02-10 21:26:55 -05:00
Log ( LOG_I , @ "Already Paired" ) ;
2015-01-31 01:57:23 -05:00
[ self alreadyPaired ] ;
} else {
2015-02-10 21:26:55 -05:00
Log ( LOG_I , @ "Trying to pair" ) ;
2015-01-31 01:57:23 -05:00
// Polling the server while pairing causes the server to screw up
[ _discMan stopDiscoveryBlocking ] ;
PairManager * pMan = [ [ PairManager alloc ] initWithManager : hMan andCert : _cert callback : self ] ;
[ _opQueue addOperation : pMan ] ;
}
2014-10-26 02:15:53 -04:00
}
} ) ;
}
2015-01-08 19:37:51 -05:00
- ( void ) hostLongClicked : ( Host * ) host view : ( UIView * ) view {
2015-02-10 21:26:55 -05:00
Log ( LOG_D , @ "Long clicked host: %@" , host . name ) ;
2015-01-01 23:25:09 -05:00
UIAlertController * longClickAlert = [ UIAlertController alertControllerWithTitle : host . name message : @ "" preferredStyle : UIAlertControllerStyleActionSheet ] ;
2015-07-11 01:29:28 -07:00
if ( ! host . online ) {
2015-01-02 19:15:47 -05:00
[ longClickAlert addAction : [ UIAlertAction actionWithTitle : @ "Wake" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
UIAlertController * wolAlert = [ UIAlertController alertControllerWithTitle : @ "Wake On Lan" message : @ "" preferredStyle : UIAlertControllerStyleAlert ] ;
[ wolAlert addAction : [ UIAlertAction actionWithTitle : @ "OK" style : UIAlertActionStyleDefault handler : nil ] ] ;
if ( host . pairState ! = PairStatePaired ) {
wolAlert . message = @ "Cannot wake host because you are not paired" ;
} else if ( host . mac = = nil || [ host . mac isEqualToString : @ "00:00:00:00:00:00" ] ) {
wolAlert . message = @ "Host MAC unknown, unable to send WOL Packet" ;
} else {
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
[ WakeOnLanManager wakeHost : host ] ;
} ) ;
wolAlert . message = @ "Sent WOL Packet" ;
}
[ self presentViewController : wolAlert animated : YES completion : nil ] ;
} ] ] ;
}
2015-01-01 23:25:09 -05:00
[ longClickAlert addAction : [ UIAlertAction actionWithTitle : @ "Remove Host" style : UIAlertActionStyleDestructive handler : ^ ( UIAlertAction * action ) {
[ _discMan removeHostFromDiscovery : host ] ;
2015-01-01 23:40:52 -05:00
DataManager * dataMan = [ [ DataManager alloc ] init ] ;
[ dataMan removeHost : host ] ;
2015-01-08 19:37:51 -05:00
@ synchronized ( hostList ) {
[ hostList removeObject : host ] ;
}
[ self updateAllHosts : [ hostList allObjects ] ] ;
2015-01-01 23:25:09 -05:00
} ] ] ;
[ longClickAlert addAction : [ UIAlertAction actionWithTitle : @ "Cancel" style : UIAlertActionStyleCancel handler : nil ] ] ;
2015-01-08 19:37:51 -05:00
// these two lines are required for iPad support of UIAlertSheet
longClickAlert . popoverPresentationController . sourceView = view ;
2015-01-31 01:57:23 -05:00
2015-01-08 19:37:51 -05:00
longClickAlert . popoverPresentationController . sourceRect = CGRectMake ( view . bounds . size . width / 2.0 , view . bounds . size . height / 2.0 , 1.0 , 1.0 ) ; // center of the view
2015-01-01 23:25:09 -05:00
[ self presentViewController : longClickAlert animated : YES completion : ^ {
[ self updateHosts ] ;
} ] ;
}
2014-10-26 02:15:53 -04:00
- ( void ) addHostClicked {
2015-02-10 21:26:55 -05:00
Log ( LOG_D , @ "Clicked add host" ) ;
2015-02-24 22:33:14 -05:00
[ self showLoadingFrame ] ;
2014-10-26 02:15:53 -04:00
UIAlertController * alertController = [ UIAlertController alertControllerWithTitle : @ "Host Address" message : @ "Please enter a hostname or IP address" preferredStyle : UIAlertControllerStyleAlert ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "Cancel" style : UIAlertActionStyleCancel handler : nil ] ] ;
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "OK" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
2015-01-01 22:30:03 -05:00
NSString * hostAddress = ( ( UITextField * ) [ [ alertController textFields ] objectAtIndex : 0 ] ) . text ;
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _HIGH , 0 ) , ^ {
2015-02-10 17:40:49 -05:00
[ _discMan discoverHost : hostAddress withCallback : ^ ( Host * host , NSString * error ) {
2015-01-01 22:30:03 -05:00
if ( host ! = nil ) {
DataManager * dataMan = [ [ DataManager alloc ] init ] ;
2015-07-11 18:47:05 -07:00
[ dataMan saveData ] ;
2015-01-01 22:30:03 -05:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-01-01 23:54:54 -05:00
@ synchronized ( hostList ) {
[ hostList addObject : host ] ;
}
2015-01-01 22:30:03 -05:00
[ self updateHosts ] ;
} ) ;
} else {
2015-02-10 17:40:49 -05:00
UIAlertController * hostNotFoundAlert = [ UIAlertController alertControllerWithTitle : @ "Add Host" message : error preferredStyle : UIAlertControllerStyleAlert ] ;
2015-01-01 22:30:03 -05:00
[ hostNotFoundAlert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDestructive handler : nil ] ] ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self presentViewController : hostNotFoundAlert animated : YES completion : nil ] ;
} ) ;
}
} ] ; } ) ;
2014-10-26 02:15:53 -04:00
} ] ] ;
[ alertController addTextFieldWithConfigurationHandler : nil ] ;
2015-02-24 22:33:14 -05:00
[ self hideLoadingFrame ] ;
2014-10-26 02:15:53 -04:00
[ self presentViewController : alertController animated : YES completion : nil ] ;
}
- ( void ) appClicked : ( App * ) app {
2015-07-10 21:22:57 -07:00
Log ( LOG_D , @ "Clicked app: %@" , app . name ) ;
2015-02-01 19:46:28 -05:00
_streamConfig = [ [ StreamConfiguration alloc ] init ] ;
2015-07-11 23:59:09 -07:00
_streamConfig . host = app . host . activeAddress ;
2015-07-10 21:22:57 -07:00
_streamConfig . appID = app . id ;
2014-10-20 20:54:03 -04:00
2014-10-29 00:47:51 -04:00
DataManager * dataMan = [ [ DataManager alloc ] init ] ;
Settings * streamSettings = [ dataMan retrieveSettings ] ;
2015-02-01 19:46:28 -05:00
_streamConfig . frameRate = [ streamSettings . framerate intValue ] ;
_streamConfig . bitRate = [ streamSettings . bitrate intValue ] ;
_streamConfig . height = [ streamSettings . height intValue ] ;
_streamConfig . width = [ streamSettings . width intValue ] ;
2014-10-29 00:47:51 -04:00
2015-05-28 23:47:30 -07:00
[ _appManager stopRetrieving ] ;
2014-11-27 11:36:28 -05:00
if ( currentPosition ! = FrontViewPositionLeft ) {
[ [ self revealViewController ] revealToggle : self ] ;
}
2015-01-01 16:50:10 -05:00
2015-07-11 23:59:09 -07:00
App * currentApp = [ self findRunningApp : app . host ] ;
2015-01-01 16:50:10 -05:00
if ( currentApp ! = nil ) {
2015-01-09 17:25:18 -05:00
UIAlertController * alertController = [ UIAlertController
2015-07-10 21:22:57 -07:00
alertControllerWithTitle : app . name
message : [ app . id isEqualToString : currentApp . id ] ? @ "" : [ NSString stringWithFormat : @ "%@ is currently running" , currentApp . name ] preferredStyle : UIAlertControllerStyleAlert ] ;
2015-01-09 17:25:18 -05:00
[ alertController addAction : [ UIAlertAction
2015-07-10 21:22:57 -07:00
actionWithTitle : [ app . id isEqualToString : currentApp . id ] ? @ "Resume App" : @ "Resume Running App" style : UIAlertActionStyleDefault handler : ^ ( UIAlertAction * action ) {
Log ( LOG_I , @ "Resuming application: %@" , currentApp . name ) ;
2015-01-31 01:57:23 -05:00
[ self performSegueWithIdentifier : @ "createStreamFrame" sender : nil ] ;
} ] ] ;
2015-01-09 17:25:18 -05:00
[ alertController addAction : [ UIAlertAction actionWithTitle :
2015-07-10 21:22:57 -07:00
[ app . id isEqualToString : currentApp . id ] ? @ "Quit App" : @ "Quit Running App and Start" style : UIAlertActionStyleDestructive handler : ^ ( UIAlertAction * action ) {
Log ( LOG_I , @ "Quitting application: %@" , currentApp . name ) ;
2015-01-31 01:57:23 -05:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
2015-07-11 23:59:09 -07:00
HttpManager * hMan = [ [ HttpManager alloc ] initWithHost : app . host . activeAddress uniqueId : _uniqueId deviceName : deviceName cert : _cert ] ;
2015-07-09 20:33:23 -07:00
HttpResponse * quitResponse = [ [ HttpResponse alloc ] init ] ;
HttpRequest * quitRequest = [ HttpRequest requestForResponse : quitResponse withUrlRequest : [ hMan newQuitAppRequest ] ] ;
2015-07-12 00:23:48 -07:00
// Exempt this host from discovery while handling the quit operation
[ _discMan removeHostFromDiscovery : app . host ] ;
2015-07-09 20:33:23 -07:00
[ hMan executeRequestSynchronously : quitRequest ] ;
2015-07-12 00:23:48 -07:00
[ _discMan addHostToDiscovery : app . host ] ;
2015-01-31 01:57:23 -05:00
2015-07-09 20:33:23 -07:00
UIAlertController * alert ;
// If it fails , display an error and stop the current operation
if ( quitResponse . statusCode ! = 200 ) {
alert = [ UIAlertController alertControllerWithTitle : @ "Quitting App Failed"
message : @ "Failed to quit app. If this app was started by "
"another device, you'll need to quit from that device."
preferredStyle : UIAlertControllerStyleAlert ] ;
}
// If it succeeds and we ' re to start streaming , segue to the stream and return
2015-07-10 21:22:57 -07:00
else if ( ! [ app . id isEqualToString : currentApp . id ] ) {
2015-07-09 20:33:23 -07:00
currentApp . isRunning = NO ;
2015-01-31 01:57:23 -05:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-07-11 23:59:09 -07:00
[ self updateAppsForHost : app . host ] ;
2015-01-31 01:57:23 -05:00
[ self performSegueWithIdentifier : @ "createStreamFrame" sender : nil ] ;
} ) ;
2015-07-09 20:33:23 -07:00
return ;
2015-01-31 01:57:23 -05:00
}
2015-07-09 20:33:23 -07:00
// Otherwise , display a dialog to notify the user that the app was quit
else {
currentApp . isRunning = NO ;
alert = [ UIAlertController alertControllerWithTitle : @ "Quitting App"
message : @ "The app was quit successfully."
preferredStyle : UIAlertControllerStyleAlert ] ;
}
[ alert addAction : [ UIAlertAction actionWithTitle : @ "Ok" style : UIAlertActionStyleDestructive handler : nil ] ] ;
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-07-11 23:59:09 -07:00
[ self updateAppsForHost : app . host ] ;
2015-07-09 20:33:23 -07:00
[ self presentViewController : alert animated : YES completion : nil ] ;
} ) ;
2015-01-31 01:57:23 -05:00
} ) ;
} ] ] ;
2015-01-01 16:50:10 -05:00
[ alertController addAction : [ UIAlertAction actionWithTitle : @ "Cancel" style : UIAlertActionStyleCancel handler : nil ] ] ;
[ self presentViewController : alertController animated : YES completion : nil ] ;
} else {
[ self performSegueWithIdentifier : @ "createStreamFrame" sender : nil ] ;
}
}
2015-07-11 23:59:09 -07:00
- ( App * ) findRunningApp : ( Host * ) host {
for ( App * app in host . appList ) {
2015-01-01 16:50:10 -05:00
if ( app . isRunning ) {
return app ;
}
}
return nil ;
}
2014-10-29 00:47:51 -04:00
- ( void ) revealController : ( SWRevealViewController * ) revealController didMoveToPosition : ( FrontViewPosition ) position {
// If we moved back to the center position , we should save the settings
if ( position = = FrontViewPositionLeft ) {
[ ( SettingsViewController * ) [ revealController rearViewController ] saveSettings ] ;
2014-10-20 20:54:03 -04:00
}
2014-11-27 11:36:28 -05:00
currentPosition = position ;
2014-08-31 13:13:46 -04:00
}
2015-02-01 19:46:28 -05:00
- ( void ) prepareForSegue : ( UIStoryboardSegue * ) segue sender : ( id ) sender {
if ( [ segue . destinationViewController isKindOfClass : [ StreamFrameViewController class ] ] ) {
StreamFrameViewController * streamFrame = segue . destinationViewController ;
streamFrame . streamConfig = _streamConfig ;
}
}
2015-02-24 22:13:33 -05:00
- ( void ) showLoadingFrame {
LoadingFrameViewController * loadingFrame = [ self . storyboard instantiateViewControllerWithIdentifier : @ "loadingFrame" ] ;
[ self . navigationController presentViewController : loadingFrame animated : YES completion : nil ] ;
}
- ( void ) hideLoadingFrame {
[ self dismissViewControllerAnimated : YES completion : nil ] ;
2015-07-11 23:59:09 -07:00
[ self enableNavigation ] ;
2015-02-24 22:13:33 -05:00
}
2014-08-31 13:13:46 -04:00
- ( void ) viewDidLoad
{
[ super viewDidLoad ] ;
2014-10-29 00:47:51 -04:00
2015-02-01 19:46:28 -05:00
// Set the side bar button action . When it ' s tapped , it ' ll show the sidebar .
2014-12-04 18:41:01 +09:00
[ _limelightLogoButton addTarget : self . revealViewController action : @ selector ( revealToggle : ) forControlEvents : UIControlEventTouchDown ] ;
2014-10-29 00:47:51 -04:00
2015-02-01 19:46:28 -05:00
// Set the host name button action . When it ' s tapped , it ' ll show the host selection view .
2014-12-04 19:54:21 +09:00
[ _computerNameButton setTarget : self ] ;
[ _computerNameButton setAction : @ selector ( showHostSelectionView ) ] ;
2014-10-29 00:47:51 -04:00
// Set the gesture
[ self . view addGestureRecognizer : self . revealViewController . panGestureRecognizer ] ;
2014-11-27 13:56:39 -05:00
// Get callbacks associated with the viewController
2014-10-29 00:47:51 -04:00
[ self . revealViewController setDelegate : self ] ;
2014-11-27 11:36:28 -05:00
// Set the current position to the center
currentPosition = FrontViewPositionLeft ;
2014-10-19 04:19:54 -04:00
2014-11-27 13:56:39 -05:00
// Set up crypto
2014-10-26 02:15:53 -04:00
[ CryptoManager generateKeyPairUsingSSl ] ;
2015-10-31 23:03:21 -07:00
_uniqueId = [ IdManager getUniqueId ] ;
2014-10-26 02:15:53 -04:00
_cert = [ CryptoManager readCertFromFile ] ;
2015-01-31 20:10:14 -05:00
_appManager = [ [ AppAssetManager alloc ] initWithCallback : self ] ;
2015-02-01 19:46:28 -05:00
_opQueue = [ [ NSOperationQueue alloc ] init ] ;
2015-01-08 22:18:26 -05:00
2014-10-29 00:47:51 -04:00
// Only initialize the host picker list once
2014-10-26 02:15:53 -04:00
if ( hostList = = nil ) {
hostList = [ [ NSMutableSet alloc ] init ] ;
}
2014-10-24 16:02:10 -04:00
2015-11-10 21:23:00 -08:00
_boxArtCache = [ [ NSCache alloc ] init ] ;
2014-10-29 00:47:51 -04:00
[ self setAutomaticallyAdjustsScrollViewInsets : NO ] ;
2015-09-30 19:13:37 -07:00
hostScrollView = [ [ ComputerScrollView alloc ] init ] ;
2014-10-29 00:47:51 -04:00
hostScrollView . frame = CGRectMake ( 0 , self . navigationController . navigationBar . frame . origin . y + self . navigationController . navigationBar . frame . size . height , self . view . frame . size . width , self . view . frame . size . height / 2 ) ;
2014-10-24 16:02:10 -04:00
[ hostScrollView setShowsHorizontalScrollIndicator : NO ] ;
2015-09-30 19:13:37 -07:00
hostScrollView . delaysContentTouches = NO ;
2015-10-17 16:16:13 -07:00
UIButton * pullArrow = [ [ UIButton alloc ] init ] ;
[ pullArrow setImage : [ UIImage imageNamed : @ "PullArrow" ] forState : UIControlStateNormal ] ;
[ pullArrow sizeToFit ] ;
pullArrow . frame = CGRectMake ( 0 ,
self . collectionView . frame . size . height / 2 - pullArrow . frame . size . height / 2 - self . navigationController . navigationBar . frame . size . height ,
pullArrow . frame . size . width ,
pullArrow . frame . size . height ) ;
[ self . view addSubview : pullArrow ] ;
2015-09-30 19:13:37 -07:00
self . collectionView . delaysContentTouches = NO ;
2015-10-18 16:52:14 -07:00
self . collectionView . allowsMultipleSelection = NO ;
self . collectionView . multipleTouchEnabled = NO ;
2014-10-29 00:47:51 -04:00
2014-11-26 17:57:16 -05:00
[ self retrieveSavedHosts ] ;
2015-01-01 22:30:03 -05:00
_discMan = [ [ DiscoveryManager alloc ] initWithHosts : [ hostList allObjects ] andCallback : self ] ;
2015-11-10 21:23:00 -08:00
[ [ NSNotificationCenter defaultCenter ] addObserver : self
selector : @ selector ( handleReturnToForeground )
name : UIApplicationWillEnterForegroundNotification
object : nil ] ;
2015-01-01 22:30:03 -05:00
[ self updateHosts ] ;
2014-10-24 16:02:10 -04:00
[ self . view addSubview : hostScrollView ] ;
2014-10-21 05:11:51 -04:00
}
2015-11-10 21:23:00 -08:00
- ( void ) dealloc
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : self ] ;
}
- ( void ) handleReturnToForeground
{
// This will refresh the applist
if ( _selectedHost ! = nil ) {
[ self hostClicked : _selectedHost view : nil ] ;
}
}
2014-10-21 05:11:51 -04:00
- ( void ) viewDidAppear : ( BOOL ) animated
{
2014-11-26 17:57:16 -05:00
[ super viewDidAppear : animated ] ;
2014-10-29 00:47:51 -04:00
[ self . navigationController setNavigationBarHidden : NO animated : YES ] ;
2014-12-04 18:41:01 +09:00
// Hide 1 px border line
UIImage * fakeImage = [ [ UIImage alloc ] init ] ;
[ self . navigationController . navigationBar setShadowImage : fakeImage ] ;
[ self . navigationController . navigationBar setBackgroundImage : fakeImage forBarPosition : UIBarPositionAny barMetrics : UIBarMetricsDefault ] ;
2015-01-01 22:30:03 -05:00
[ _discMan startDiscovery ] ;
2015-01-01 16:50:10 -05:00
2015-11-10 21:23:00 -08:00
[ self handleReturnToForeground ] ;
2014-10-21 05:11:51 -04:00
}
- ( void ) viewDidDisappear : ( BOOL ) animated
{
2014-11-26 17:57:16 -05:00
[ super viewDidDisappear : animated ] ;
2015-01-01 22:30:03 -05:00
// when discovery stops , we must create a new instance because you cannot restart an NSOperation when it is finished
[ _discMan stopDiscovery ] ;
2015-01-31 01:57:23 -05:00
2015-11-10 21:23:00 -08:00
// Purge the box art cache
[ _boxArtCache removeAllObjects ] ;
2015-01-01 22:30:03 -05:00
// In case the host objects were updated in the background
2015-07-11 18:47:05 -07:00
[ [ [ DataManager alloc ] init ] saveData ] ;
2014-10-14 19:21:48 -04:00
}
2014-11-26 17:57:16 -05:00
- ( void ) retrieveSavedHosts {
DataManager * dataMan = [ [ DataManager alloc ] init ] ;
NSArray * hosts = [ dataMan retrieveHosts ] ;
2015-01-01 23:54:54 -05:00
@ synchronized ( hostList ) {
[ hostList addObjectsFromArray : hosts ] ;
2015-07-10 00:28:51 -07:00
// Initialize the non - persistent host state
for ( Host * host in hostList ) {
2015-07-10 12:18:09 -07:00
if ( host . activeAddress = = nil ) {
host . activeAddress = host . localAddress ;
}
if ( host . activeAddress = = nil ) {
host . activeAddress = host . externalAddress ;
}
if ( host . activeAddress = = nil ) {
host . activeAddress = host . address ;
}
2015-07-10 00:28:51 -07:00
}
2015-01-01 23:54:54 -05:00
}
2014-11-26 17:57:16 -05:00
}
2015-01-01 22:30:03 -05:00
- ( void ) updateAllHosts : ( NSArray * ) hosts {
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
2015-02-10 21:26:55 -05:00
Log ( LOG_D , @ "New host list:" ) ;
2015-01-01 22:30:03 -05:00
for ( Host * host in hosts ) {
2015-07-10 00:28:51 -07:00
Log ( LOG_D , @ "Host: \n{\n\t name:%@ \n\t address:%@ \n\t localAddress:%@ \n\t externalAddress:%@ \n\t uuid:%@ \n\t mac:%@ \n\t pairState:%d \n\t online:%d \n\t activeAddress:%@ \n}" , host . name , host . address , host . localAddress , host . externalAddress , host . uuid , host . mac , host . pairState , host . online , host . activeAddress ) ;
2015-01-01 22:30:03 -05:00
}
2015-01-01 23:54:54 -05:00
@ synchronized ( hostList ) {
[ hostList removeAllObjects ] ;
[ hostList addObjectsFromArray : hosts ] ;
}
2015-01-01 22:30:03 -05:00
[ self updateHosts ] ;
} ) ;
}
- ( void ) updateHosts {
2015-02-10 21:26:55 -05:00
Log ( LOG_I , @ "Updating hosts..." ) ;
2014-10-26 02:15:53 -04:00
[ [ hostScrollView subviews ] makeObjectsPerformSelector : @ selector ( removeFromSuperview ) ] ;
UIComputerView * addComp = [ [ UIComputerView alloc ] initForAddWithCallback : self ] ;
UIComputerView * compView ;
float prevEdge = -1 ;
2015-01-01 23:54:54 -05:00
@ synchronized ( hostList ) {
for ( Host * comp in hostList ) {
compView = [ [ UIComputerView alloc ] initWithComputer : comp andCallback : self ] ;
compView . center = CGPointMake ( [ self getCompViewX : compView addComp : addComp prevEdge : prevEdge ] , hostScrollView . frame . size . height / 2 ) ;
prevEdge = compView . frame . origin . x + compView . frame . size . width ;
[ hostScrollView addSubview : compView ] ;
}
2014-10-26 02:15:53 -04:00
}
2015-11-10 21:23:00 -08:00
2014-10-26 02:15:53 -04:00
prevEdge = [ self getCompViewX : addComp addComp : addComp prevEdge : prevEdge ] ;
addComp . center = CGPointMake ( prevEdge , hostScrollView . frame . size . height / 2 ) ;
2014-10-21 13:39:14 -04:00
2014-10-26 02:15:53 -04:00
[ hostScrollView addSubview : addComp ] ;
[ hostScrollView setContentSize : CGSizeMake ( prevEdge + addComp . frame . size . width , hostScrollView . frame . size . height ) ] ;
}
- ( float ) getCompViewX : ( UIComputerView * ) comp addComp : ( UIComputerView * ) addComp prevEdge : ( float ) prevEdge {
if ( prevEdge = = -1 ) {
return hostScrollView . frame . origin . x + comp . frame . size . width / 2 + addComp . frame . size . width / 2 ;
} else {
return prevEdge + addComp . frame . size . width / 2 + comp . frame . size . width / 2 ;
}
}
2015-11-10 21:23:00 -08:00
// This function forces immediate decoding of the UIImage , rather
// than the default lazy decoding that results in janky scrolling .
+ ( UIImage * ) loadBoxArtForCaching : ( App * ) app {
CGImageSourceRef source = CGImageSourceCreateWithData ( ( __bridge CFDataRef ) app . image , NULL ) ;
CGImageRef cgImage = CGImageSourceCreateImageAtIndex ( source , 0 , ( __bridge CFDictionaryRef ) @ { ( id ) kCGImageSourceShouldCacheImmediately : ( id ) kCFBooleanTrue } ) ;
UIImage * boxArt = [ UIImage imageWithCGImage : cgImage ] ;
CGImageRelease ( cgImage ) ;
CFRelease ( source ) ;
return boxArt ;
}
- ( void ) updateBoxArtCacheForApp : ( App * ) app {
if ( app . image = = nil ) {
[ _boxArtCache removeObjectForKey : app ] ;
}
else if ( [ _boxArtCache objectForKey : app ] = = nil ) {
[ _boxArtCache setObject : [ MainFrameViewController loadBoxArtForCaching : app ] forKey : app ] ;
}
}
2015-07-11 23:59:09 -07:00
- ( void ) updateAppsForHost : ( Host * ) host {
if ( host ! = _selectedHost ) {
Log ( LOG_W , @ "Mismatched host during app update" ) ;
return ;
}
_sortedAppList = [ host . appList allObjects ] ;
2015-07-11 01:28:52 -07:00
_sortedAppList = [ _sortedAppList sortedArrayUsingSelector : @ selector ( compareName : ) ] ;
2015-11-13 19:52:35 -08:00
// Split the sorted array in half to allow 2 jobs to process app assets at once
NSArray * firstHalf ;
NSArray * secondHalf ;
NSRange range ;
range . location = 0 ;
range . length = [ _sortedAppList count ] / 2 ;
firstHalf = [ _sortedAppList subarrayWithRange : range ] ;
range . location = range . length ;
range . length = [ _sortedAppList count ] - range . length ;
secondHalf = [ _sortedAppList subarrayWithRange : range ] ;
// Start 2 jobs
2015-11-13 19:07:15 -08:00
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
2015-11-13 19:52:35 -08:00
for ( App * app in firstHalf ) {
[ self updateBoxArtCacheForApp : app ] ;
}
} ) ;
dispatch_async ( dispatch_get _global _queue ( DISPATCH_QUEUE _PRIORITY _DEFAULT , 0 ) , ^ {
for ( App * app in secondHalf ) {
2015-11-10 21:23:00 -08:00
[ self updateBoxArtCacheForApp : app ] ;
2015-11-13 19:07:15 -08:00
}
} ) ;
2015-11-10 21:23:00 -08:00
2014-12-04 18:41:01 +09:00
[ hostScrollView removeFromSuperview ] ;
[ self . collectionView reloadData ] ;
2014-10-26 02:15:53 -04:00
}
2014-12-04 18:41:01 +09:00
- ( UICollectionViewCell * ) collectionView : ( UICollectionView * ) collectionView cellForItemAtIndexPath : ( NSIndexPath * ) indexPath {
UICollectionViewCell * cell = [ collectionView dequeueReusableCellWithReuseIdentifier : @ "AppCell" forIndexPath : indexPath ] ;
2015-07-11 01:28:52 -07:00
App * app = _sortedAppList [ indexPath . row ] ;
2015-11-10 21:23:00 -08:00
UIAppView * appView = [ [ UIAppView alloc ] initWithApp : app cache : _boxArtCache andCallback : self ] ;
2014-12-04 18:41:01 +09:00
[ appView updateAppImage ] ;
2015-01-01 16:50:10 -05:00
2014-12-04 18:41:01 +09:00
if ( appView . bounds . size . width > 10.0 ) {
CGFloat scale = cell . bounds . size . width / appView . bounds . size . width ;
[ appView setCenter : CGPointMake ( appView . bounds . size . width / 2 * scale , appView . bounds . size . height / 2 * scale ) ] ;
appView . transform = CGAffineTransformMakeScale ( scale , scale ) ;
2014-10-21 13:39:14 -04:00
}
2015-01-01 16:50:10 -05:00
2014-12-04 18:41:01 +09:00
[ cell . subviews . firstObject removeFromSuperview ] ; // Remove a view that was previously added
[ cell addSubview : appView ] ;
2015-09-28 21:18:35 -07:00
UIBezierPath * shadowPath = [ UIBezierPath bezierPathWithRect : cell . bounds ] ;
cell . layer . masksToBounds = NO ;
cell . layer . shadowColor = [ UIColor blackColor ] . CGColor ;
cell . layer . shadowOffset = CGSizeMake ( 1.0 f , 5.0 f ) ;
cell . layer . shadowOpacity = 0.5 f ;
cell . layer . shadowPath = shadowPath . CGPath ;
cell . layer . borderColor = [ [ UIColor colorWithRed : 0 green : 0 blue : 0 alpha : 0.3 f ] CGColor ] ;
cell . layer . borderWidth = 1 ;
2015-10-18 16:52:14 -07:00
cell . exclusiveTouch = YES ;
2014-12-04 18:41:01 +09:00
return cell ;
}
- ( NSInteger ) numberOfSectionsInCollectionView : ( UICollectionView * ) collectionView {
return 1 ; // App collection only
}
- ( NSInteger ) collectionView : ( UICollectionView * ) collectionView numberOfItemsInSection : ( NSInteger ) section {
2015-07-11 01:28:52 -07:00
if ( _selectedHost ! = nil ) {
return _selectedHost . appList . count ;
}
else {
return 0 ;
}
2014-08-31 13:13:46 -04:00
}
- ( void ) didReceiveMemoryWarning
{
[ super didReceiveMemoryWarning ] ;
// Dispose of any resources that can be recreated .
}
2014-10-20 21:39:40 -04:00
2014-10-20 21:45:37 -04:00
- ( void ) touchesBegan : ( NSSet * ) touches withEvent : ( UIEvent * ) event {
[ self . view endEditing : YES ] ;
}
- ( BOOL ) textFieldShouldReturn : ( UITextField * ) textField {
[ textField resignFirstResponder ] ;
return YES ;
}
2014-10-20 21:39:40 -04:00
- ( BOOL ) shouldAutorotate {
return YES ;
}
2015-02-01 19:46:28 -05:00
2015-07-11 23:59:09 -07:00
- ( void ) disableNavigation {
self . navigationController . navigationBar . topItem . rightBarButtonItem . enabled = NO ;
}
- ( void ) enableNavigation {
self . navigationController . navigationBar . topItem . rightBarButtonItem . enabled = YES ;
}
2014-08-31 13:13:46 -04:00
@ end