VLCServerListTVViewController.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*****************************************************************************
  2. * VLC for iOS
  3. *****************************************************************************
  4. * Copyright (c) 2015 VideoLAN. All rights reserved.
  5. * $Id$
  6. *
  7. * Authors: Tobias Conradi <videolan # tobias-conradi.de>
  8. *
  9. * Refer to the COPYING file of the official project for license.
  10. *****************************************************************************/
  11. #import "VLCServerListTVViewController.h"
  12. #import "VLCSearchableServerBrowsingTVViewController.h"
  13. #import "VLCNetworkServerLoginInformation.h"
  14. #import "VLCNetworkServerBrowserPlex.h"
  15. #import "VLCNetworkServerBrowserVLCMedia.h"
  16. #import "VLCNetworkServerBrowserFTP.h"
  17. #import "VLCLocalNetworkServiceBrowserManualConnect.h"
  18. #import "VLCLocalNetworkServiceBrowserPlex.h"
  19. #import "VLCLocalNetworkServiceBrowserFTP.h"
  20. #import "VLCLocalNetworkServiceBrowserUPnP.h"
  21. #ifndef NDEBUG
  22. #import "VLCLocalNetworkServiceBrowserSAP.h"
  23. #endif
  24. #import "VLCLocalNetworkServiceBrowserDSM.h"
  25. #import "VLCLocalNetworkServiceBrowserBonjour.h"
  26. #import "VLCLocalNetworkServiceBrowserHTTP.h"
  27. #import "VLCNetworkServerLoginInformation+Keychain.h"
  28. #import "VLCRemoteBrowsingTVCell.h"
  29. #import "GRKArrayDiff+UICollectionView.h"
  30. @interface VLCServerListTVViewController ()
  31. {
  32. UILabel *_nothingFoundLabel;
  33. }
  34. @property (nonatomic, copy) NSMutableArray<id<VLCLocalNetworkService>> *networkServices;
  35. @end
  36. @implementation VLCServerListTVViewController
  37. - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
  38. {
  39. return [super initWithNibName:@"VLCRemoteBrowsingCollectionViewController" bundle:nil];
  40. }
  41. - (void)viewDidLoad {
  42. [super viewDidLoad];
  43. self.automaticallyAdjustsScrollViewInsets = NO;
  44. self.edgesForExtendedLayout = UIRectEdgeAll ^ UIRectEdgeTop;
  45. UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
  46. flowLayout.itemSize = CGSizeMake(250.0, 300.0);
  47. flowLayout.minimumInteritemSpacing = 48.0;
  48. flowLayout.minimumLineSpacing = 100.0;
  49. self.nothingFoundLabel.text = NSLocalizedString(@"NO_SERVER_FOUND", nil);
  50. [self.nothingFoundLabel sizeToFit];
  51. UIView *nothingFoundView = self.nothingFoundView;
  52. [nothingFoundView sizeToFit];
  53. [nothingFoundView setTranslatesAutoresizingMaskIntoConstraints:NO];
  54. [self.view addSubview:nothingFoundView];
  55. NSLayoutConstraint *yConstraint = [NSLayoutConstraint constraintWithItem:nothingFoundView
  56. attribute:NSLayoutAttributeCenterY
  57. relatedBy:NSLayoutRelationEqual
  58. toItem:self.view
  59. attribute:NSLayoutAttributeCenterY
  60. multiplier:1.0
  61. constant:0.0];
  62. [self.view addConstraint:yConstraint];
  63. NSLayoutConstraint *xConstraint = [NSLayoutConstraint constraintWithItem:nothingFoundView
  64. attribute:NSLayoutAttributeCenterX
  65. relatedBy:NSLayoutRelationEqual
  66. toItem:self.view
  67. attribute:NSLayoutAttributeCenterX
  68. multiplier:1.0
  69. constant:0.0];
  70. [self.view addConstraint:xConstraint];
  71. NSArray *classes = @[
  72. [VLCLocalNetworkServiceBrowserHTTP class],
  73. [VLCLocalNetworkServiceBrowserUPnP class],
  74. [VLCLocalNetworkServiceBrowserDSM class],
  75. [VLCLocalNetworkServiceBrowserPlex class],
  76. [VLCLocalNetworkServiceBrowserFTP class],
  77. #ifndef NDEBUG
  78. [VLCLocalNetworkServiceBrowserSAP class],
  79. #endif
  80. ];
  81. self.discoveryController = [[VLCLocalServerDiscoveryController alloc] initWithServiceBrowserClasses:classes];
  82. self.discoveryController.delegate = self;
  83. }
  84. - (NSString *)title {
  85. return NSLocalizedString(@"LOCAL_NETWORK", nil);
  86. }
  87. - (void)viewDidAppear:(BOOL)animated
  88. {
  89. [super viewDidAppear:animated];
  90. [self.discoveryController startDiscovery];
  91. }
  92. - (void)viewDidDisappear:(BOOL)animated
  93. {
  94. [super viewDidDisappear:animated];
  95. [self.discoveryController stopDiscovery];
  96. }
  97. #pragma mark - Collection view data source
  98. - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
  99. {
  100. NSInteger count = self.networkServices.count;
  101. self.nothingFoundView.hidden = count > 0;
  102. return count;
  103. }
  104. - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
  105. {
  106. VLCRemoteBrowsingTVCell *browsingCell = (VLCRemoteBrowsingTVCell *) [collectionView dequeueReusableCellWithReuseIdentifier:VLCRemoteBrowsingTVCellIdentifier forIndexPath:indexPath];
  107. id<VLCLocalNetworkService> service = self.networkServices[indexPath.row];
  108. if (service == nil)
  109. return browsingCell;
  110. browsingCell.isDirectory = YES;
  111. browsingCell.title = service.title;
  112. browsingCell.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
  113. browsingCell.subtitle = service.serviceName;
  114. browsingCell.subtitleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
  115. UIImage *serviceIcon = service.icon;
  116. browsingCell.thumbnailImage = serviceIcon ? serviceIcon : [UIImage imageNamed:@"serverIcon"];
  117. return browsingCell;
  118. }
  119. - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
  120. {
  121. id<VLCLocalNetworkService> service = self.networkServices[indexPath.row];
  122. [self didSelectService:service];
  123. }
  124. #pragma mark - Service specific stuff
  125. - (void)didSelectService:(id<VLCLocalNetworkService>)service
  126. {
  127. if ([service respondsToSelector:@selector(serverBrowser)]) {
  128. id <VLCNetworkServerBrowser> browser = [service serverBrowser];
  129. if (browser) {
  130. VLCServerBrowsingTVViewController *browsingViewController = [[VLCSearchableServerBrowsingTVViewController alloc] initWithServerBrowser:browser];
  131. [self presentViewController:[[UINavigationController alloc] initWithRootViewController:browsingViewController]
  132. animated:YES
  133. completion:nil];
  134. return;
  135. }
  136. }
  137. if ([service respondsToSelector:@selector(loginInformation)]) {
  138. VLCNetworkServerLoginInformation *login = service.loginInformation;
  139. if (!login) return;
  140. NSError *error = nil;
  141. if ([login loadLoginInformationFromKeychainWithError:&error])
  142. {
  143. [self showLoginAlertWithLogin:login];
  144. } else {
  145. [self showKeychainLoadError:error forLogin:login];
  146. }
  147. return;
  148. }
  149. if ([service respondsToSelector:@selector(directPlaybackURL)]) {
  150. NSURL *url = service.directPlaybackURL;
  151. if (!url) return;
  152. VLCMediaList *medialist = [[VLCMediaList alloc] init];
  153. [medialist addMedia:[VLCMedia mediaWithURL:url]];
  154. [[VLCPlaybackController sharedInstance] playMediaList:medialist firstIndex:0 subtitlesFilePath:nil];
  155. [self presentViewController:[VLCFullscreenMovieTVViewController fullscreenMovieTVViewController]
  156. animated:YES
  157. completion:nil];
  158. }
  159. }
  160. - (void)showKeychainLoadError:(NSError *)error forLogin:(VLCNetworkServerLoginInformation *)login
  161. {
  162. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
  163. message:error.localizedFailureReason preferredStyle:UIAlertControllerStyleAlert];
  164. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil)
  165. style:UIAlertActionStyleDefault
  166. handler:^(UIAlertAction * _Nonnull action) {
  167. [self showLoginAlertWithLogin:login];
  168. }]];
  169. [self presentViewController:alertController animated:YES completion:nil];
  170. }
  171. - (void)showKeychainSaveError:(NSError *)error forLogin:(VLCNetworkServerLoginInformation *)login
  172. {
  173. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
  174. message:error.localizedFailureReason preferredStyle:UIAlertControllerStyleAlert];
  175. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil)
  176. style:UIAlertActionStyleDefault
  177. handler:nil]];
  178. [self presentViewController:alertController animated:YES completion:nil];
  179. }
  180. - (void)showKeychainDeleteError:(NSError *)error
  181. {
  182. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
  183. message:error.localizedFailureReason preferredStyle:UIAlertControllerStyleAlert];
  184. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil)
  185. style:UIAlertActionStyleDefault
  186. handler:nil]];
  187. [self presentViewController:alertController animated:YES completion:nil];
  188. }
  189. - (void)showLoginAlertWithLogin:(nonnull VLCNetworkServerLoginInformation *)login
  190. {
  191. UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"CONNECT_TO_SERVER", nil)
  192. message:login.address preferredStyle:UIAlertControllerStyleAlert];
  193. __block UITextField *usernameField = nil;
  194. __block UITextField *passwordField = nil;
  195. [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
  196. textField.placeholder = NSLocalizedString(@"USER_LABEL", nil);
  197. textField.text = login.username;
  198. usernameField = textField;
  199. }];
  200. [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
  201. textField.secureTextEntry = YES;
  202. textField.placeholder = NSLocalizedString(@"PASSWORD_LABEL", nil);
  203. textField.text = login.password;
  204. passwordField = textField;
  205. }];
  206. NSMutableDictionary *additionalFieldsDict = [NSMutableDictionary dictionaryWithCapacity:login.additionalFields.count];
  207. for (VLCNetworkServerLoginInformationField *fieldInfo in login.additionalFields) {
  208. [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
  209. switch (fieldInfo.type) {
  210. case VLCNetworkServerLoginInformationFieldTypeNumber:
  211. textField.keyboardType = UIKeyboardTypeNumberPad;
  212. break;
  213. case VLCNetworkServerLoginInformationFieldTypeText:
  214. default:
  215. textField.keyboardType = UIKeyboardTypeDefault;
  216. break;
  217. }
  218. textField.placeholder = fieldInfo.localizedLabel;
  219. textField.text = fieldInfo.textValue;
  220. additionalFieldsDict[fieldInfo.identifier] = textField;
  221. }];
  222. }
  223. void(^loginBlock)(BOOL) = ^(BOOL save) {
  224. login.username = usernameField.text;
  225. login.password = passwordField.text;
  226. for (VLCNetworkServerLoginInformationField *fieldInfo in login.additionalFields) {
  227. UITextField *textField = additionalFieldsDict[fieldInfo.identifier];
  228. fieldInfo.textValue = textField.text;
  229. }
  230. if (save) {
  231. NSError *error = nil;
  232. if (![login saveLoginInformationToKeychainWithError:&error]) {
  233. [self showKeychainSaveError:error forLogin:login];
  234. }
  235. }
  236. [self showBrowserWithLogin:login];
  237. };
  238. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"LOGIN", nil)
  239. style:UIAlertActionStyleDefault
  240. handler:^(UIAlertAction * _Nonnull action) {
  241. loginBlock(NO);
  242. }]];
  243. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_SAVE", nil)
  244. style:UIAlertActionStyleDefault
  245. handler:^(UIAlertAction * _Nonnull action) {
  246. loginBlock(YES);
  247. }]];
  248. if (login.username.length || login.password.length) {
  249. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_DELETE", nil)
  250. style:UIAlertActionStyleDestructive
  251. handler:^(UIAlertAction * _Nonnull action) {
  252. NSError *error = nil;
  253. if (![login deleteFromKeychainWithError:&error]){
  254. [self showKeychainDeleteError:error];
  255. }
  256. }]];
  257. } else {
  258. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_ANONYMOUS_LOGIN", nil)
  259. style:UIAlertActionStyleDefault
  260. handler:^(UIAlertAction * _Nonnull action) {
  261. login.username = nil;
  262. login.password = nil;
  263. [self showBrowserWithLogin:login];
  264. }]];
  265. }
  266. [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
  267. style:UIAlertActionStyleCancel
  268. handler:nil]];
  269. [self presentViewController:alertController animated:YES completion:nil];
  270. }
  271. - (void)showBrowserWithLogin:(nonnull VLCNetworkServerLoginInformation *)login
  272. {
  273. id<VLCNetworkServerBrowser> serverBrowser = nil;
  274. NSString *identifier = login.protocolIdentifier;
  275. if ([identifier isEqualToString:VLCNetworkServerProtocolIdentifierFTP]) {
  276. serverBrowser = [[VLCNetworkServerBrowserFTP alloc] initWithLogin:login];
  277. } else if ([identifier isEqualToString:VLCNetworkServerProtocolIdentifierPlex]) {
  278. serverBrowser = [[VLCNetworkServerBrowserPlex alloc] initWithLogin:login];
  279. } else if ([identifier isEqualToString:VLCNetworkServerProtocolIdentifierSMB]) {
  280. serverBrowser = [VLCNetworkServerBrowserVLCMedia SMBNetworkServerBrowserWithLogin:login];
  281. }
  282. if (serverBrowser) {
  283. VLCServerBrowsingTVViewController *targetViewController = [[VLCSearchableServerBrowsingTVViewController alloc] initWithServerBrowser:serverBrowser];
  284. [self presentViewController:[[UINavigationController alloc] initWithRootViewController:targetViewController]
  285. animated:YES
  286. completion:nil];
  287. }
  288. }
  289. #pragma mark - VLCLocalServerDiscoveryController
  290. - (void)discoveryFoundSomethingNew
  291. {
  292. NSString * (^mapServiceName)(id<VLCLocalNetworkService>) = ^NSString *(id<VLCLocalNetworkService> service) {
  293. return [NSString stringWithFormat:@"%@: %@", service.serviceName, service.title];
  294. };
  295. NSMutableArray<id<VLCLocalNetworkService>> *newNetworkServices = [NSMutableArray array];
  296. NSMutableSet<NSString *> *addedNetworkServices = [[NSMutableSet alloc] init];
  297. VLCLocalServerDiscoveryController *discoveryController = self.discoveryController;
  298. NSUInteger sectionCount = [discoveryController numberOfSections];
  299. for (NSUInteger section = 0; section < sectionCount; ++section) {
  300. NSUInteger itemsCount = [discoveryController numberOfItemsInSection:section];
  301. for (NSUInteger index = 0; index < itemsCount; ++index) {
  302. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];
  303. id<VLCLocalNetworkService> service = [discoveryController networkServiceForIndexPath:indexPath];
  304. if (service != nil) {
  305. NSString *mappedName = mapServiceName(service);
  306. if(![addedNetworkServices containsObject:mappedName]) {
  307. [addedNetworkServices addObject:mappedName];
  308. [newNetworkServices addObject:service];
  309. }
  310. }
  311. }
  312. }
  313. NSArray *oldNetworkServices = self.networkServices;
  314. GRKArrayDiff *diff = [[GRKArrayDiff alloc] initWithPreviousArray:oldNetworkServices
  315. currentArray:newNetworkServices
  316. identityBlock:mapServiceName
  317. modifiedBlock:nil];
  318. [diff performBatchUpdatesWithCollectionView:self.collectionView
  319. section:0
  320. dataSourceUpdate:^{
  321. self.networkServices = newNetworkServices;
  322. } completion:nil];
  323. _nothingFoundLabel.hidden = self.discoveryController.foundAnythingAtAll;
  324. }
  325. @end