VLCServerListTVViewController.m 19 KB

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