VLCServerListTVViewController.m 18.1 KB
Newer Older
Felix Paul Kühne's avatar
Felix Paul Kühne committed
1
2
3
4
5
6
7
8
9
10
/*****************************************************************************
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2015 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Tobias Conradi <videolan # tobias-conradi.de>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Tobias's avatar
Tobias committed
11

12
#import "VLCServerListTVViewController.h"
13
#import "VLCSearchableServerBrowsingTVViewController.h"
14
15
16
17
#import "VLCNetworkServerLoginInformation.h"

#import "VLCNetworkServerBrowserPlex.h"
#import "VLCNetworkServerBrowserVLCMedia.h"
18
#import "VLCNetworkServerBrowserFTP.h"
Tobias's avatar
Tobias committed
19

20
21
22
23
24
25
26
27
#import "VLCLocalNetworkServiceBrowserManualConnect.h"
#import "VLCLocalNetworkServiceBrowserPlex.h"
#import "VLCLocalNetworkServiceBrowserFTP.h"
#import "VLCLocalNetworkServiceBrowserUPnP.h"
#ifndef NDEBUG
#import "VLCLocalNetworkServiceBrowserSAP.h"
#endif
#import "VLCLocalNetworkServiceBrowserDSM.h"
28
#import "VLCLocalNetworkServiceBrowserBonjour.h"
29
30
#import "VLCLocalNetworkServiceBrowserHTTP.h"

31
32
#import "VLCNetworkServerLoginInformation+Keychain.h"

33
#import "VLCRemoteBrowsingTVCell.h"
34
#import "GRKArrayDiff+UICollectionView.h"
35
36

@interface VLCServerListTVViewController ()
37
38
39
{
    UILabel *_nothingFoundLabel;
}
40
41
@property (nonatomic, copy) NSMutableArray<id<VLCLocalNetworkService>> *networkServices;

Tobias's avatar
Tobias committed
42
43
@end

44
45
46
47
48
49
@implementation VLCServerListTVViewController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    return [super initWithNibName:@"VLCRemoteBrowsingCollectionViewController" bundle:nil];
}
Tobias's avatar
Tobias committed
50
51
52

- (void)viewDidLoad {
    [super viewDidLoad];
53
54
    self.automaticallyAdjustsScrollViewInsets = NO;
    self.edgesForExtendedLayout = UIRectEdgeAll ^ UIRectEdgeTop;
55
56

    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionViewLayout;
57
58
59
    flowLayout.itemSize = CGSizeMake(250.0, 300.0);
    flowLayout.minimumInteritemSpacing = 48.0;
    flowLayout.minimumLineSpacing = 100.0;
60

61
62
63
64
65
66
    self.nothingFoundLabel.text = NSLocalizedString(@"NO_SERVER_FOUND", nil);
    [self.nothingFoundLabel sizeToFit];
    UIView *nothingFoundView = self.nothingFoundView;
    [nothingFoundView sizeToFit];
    [nothingFoundView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.view addSubview:nothingFoundView];
67

68
    NSLayoutConstraint *yConstraint = [NSLayoutConstraint constraintWithItem:nothingFoundView
69
70
71
72
73
74
75
                                                                   attribute:NSLayoutAttributeCenterY
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:self.view
                                                                   attribute:NSLayoutAttributeCenterY
                                                                  multiplier:1.0
                                                                    constant:0.0];
    [self.view addConstraint:yConstraint];
76
    NSLayoutConstraint *xConstraint = [NSLayoutConstraint constraintWithItem:nothingFoundView
77
78
79
80
81
82
83
                                                                   attribute:NSLayoutAttributeCenterX
                                                                   relatedBy:NSLayoutRelationEqual
                                                                      toItem:self.view
                                                                   attribute:NSLayoutAttributeCenterX
                                                                  multiplier:1.0
                                                                    constant:0.0];
    [self.view addConstraint:xConstraint];
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

    NSArray *classes = @[
                         [VLCLocalNetworkServiceBrowserHTTP class],
                         [VLCLocalNetworkServiceBrowserUPnP class],
                         [VLCLocalNetworkServiceBrowserDSM class],
                         [VLCLocalNetworkServiceBrowserPlex class],
                         [VLCLocalNetworkServiceBrowserFTP class],
#ifndef NDEBUG
                         [VLCLocalNetworkServiceBrowserSAP class],
#endif
                         ];
    self.discoveryController = [[VLCLocalServerDiscoveryController alloc] initWithServiceBrowserClasses:classes];
    self.discoveryController.delegate = self;
}

- (NSString *)title {
    return NSLocalizedString(@"LOCAL_NETWORK", nil);
101
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
102
103
104

- (void)viewDidAppear:(BOOL)animated
{
105
106
107
    [super viewDidAppear:animated];
    [self.discoveryController startDiscovery];
}
Felix Paul Kühne's avatar
Felix Paul Kühne committed
108
109
110

- (void)viewDidDisappear:(BOOL)animated
{
111
112
    [super viewDidDisappear:animated];
    [self.discoveryController stopDiscovery];
Tobias's avatar
Tobias committed
113
114
}

115
#pragma mark - Collection view data source
Tobias's avatar
Tobias committed
116

117
118
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
119
    NSInteger count = self.networkServices.count;
120
121
    self.nothingFoundView.hidden = count > 0;
    return count;
122
123
}

124
125
126
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    VLCRemoteBrowsingTVCell *browsingCell = (VLCRemoteBrowsingTVCell *) [collectionView dequeueReusableCellWithReuseIdentifier:VLCRemoteBrowsingTVCellIdentifier forIndexPath:indexPath];
Tobias's avatar
Tobias committed
127

128
    id<VLCLocalNetworkService> service = self.networkServices[indexPath.row];
129
130
    if (service == nil)
        return browsingCell;
Tobias's avatar
Tobias committed
131

132
133
    browsingCell.isDirectory = YES;
    browsingCell.title = service.title;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
134
    browsingCell.titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption2];
135
    browsingCell.subtitle = service.serviceName;
136
    browsingCell.subtitleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1];
137
138
    UIImage *serviceIcon = service.icon;
    browsingCell.thumbnailImage = serviceIcon ? serviceIcon : [UIImage imageNamed:@"serverIcon"];
139

140
141
    return browsingCell;
}
142

143
144
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
145
    id<VLCLocalNetworkService> service = self.networkServices[indexPath.row];
146
    [self didSelectService:service];
147
148
}

149
150
151
152
#pragma mark - Service specific stuff

- (void)didSelectService:(id<VLCLocalNetworkService>)service
{
153
154
155
    if ([service respondsToSelector:@selector(serverBrowser)]) {
        id <VLCNetworkServerBrowser> browser = [service serverBrowser];
        if (browser) {
156
            VLCServerBrowsingTVViewController *browsingViewController = [[VLCSearchableServerBrowsingTVViewController alloc] initWithServerBrowser:browser];
157
158
159
            [self presentViewController:[[UINavigationController alloc] initWithRootViewController:browsingViewController]
                               animated:YES
                             completion:nil];
160
161
162
163
164
            return;
        }
    }

    if ([service respondsToSelector:@selector(loginInformation)]) {
165
166
167
        VLCNetworkServerLoginInformation *login = service.loginInformation;
        if (!login) return;

168
169
170
171
172
173
174
        NSError *error = nil;
        if ([login loadLoginInformationFromKeychainWithError:&error])
        {
            [self showLoginAlertWithLogin:login];
        } else {
            [self showKeychainLoadError:error forLogin:login];
        }
175
176
        return;
    }
177

178
    if ([service respondsToSelector:@selector(directPlaybackURL)]) {
179
180
181
182

        NSURL *url = service.directPlaybackURL;
        if (!url) return;

183
184
185
186
187
        VLCMediaList *medialist = [[VLCMediaList alloc] init];
        [medialist addMedia:[VLCMedia mediaWithURL:url]];

        [[VLCPlaybackController sharedInstance] playMediaList:medialist firstIndex:0 subtitlesFilePath:nil];

188
189
190
        [self presentViewController:[VLCFullscreenMovieTVViewController fullscreenMovieTVViewController]
                           animated:YES
                         completion:nil];
191
    }
192
193
}

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
- (void)showKeychainLoadError:(NSError *)error forLogin:(VLCNetworkServerLoginInformation *)login
{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
                                                                             message:error.localizedFailureReason preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil)
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction * _Nonnull action) {
                                                          [self showLoginAlertWithLogin:login];
                                                      }]];
    [self presentViewController:alertController animated:YES completion:nil];
}

- (void)showKeychainSaveError:(NSError *)error forLogin:(VLCNetworkServerLoginInformation *)login
{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
                                                                             message:error.localizedFailureReason preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil)
                                                        style:UIAlertActionStyleDefault
                                                      handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
}
- (void)showKeychainDeleteError:(NSError *)error
{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
                                                                             message:error.localizedFailureReason preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_OK", nil)
                                                        style:UIAlertActionStyleDefault
                                                      handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
}
224
225
226
227
228
229
230
231
232
- (void)showLoginAlertWithLogin:(nonnull VLCNetworkServerLoginInformation *)login
{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"CONNECT_TO_SERVER", nil)
                                                                             message:login.address preferredStyle:UIAlertControllerStyleAlert];

    __block UITextField *usernameField = nil;
    __block UITextField *passwordField = nil;
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.placeholder = NSLocalizedString(@"USER_LABEL", nil);
233
        textField.text = login.username;
234
235
236
237
238
        usernameField = textField;
    }];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.secureTextEntry = YES;
        textField.placeholder = NSLocalizedString(@"PASSWORD_LABEL", nil);
239
        textField.text = login.password;
240
241
        passwordField = textField;
    }];
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276

    NSMutableDictionary *additionalFieldsDict = [NSMutableDictionary dictionaryWithCapacity:login.additionalFields.count];
    for (VLCNetworkServerLoginInformationField *fieldInfo in login.additionalFields) {
        [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
            switch (fieldInfo.type) {
                case VLCNetworkServerLoginInformationFieldTypeNumber:
                    textField.keyboardType = UIKeyboardTypeNumberPad;
                    break;
                case VLCNetworkServerLoginInformationFieldTypeText:
                default:
                    textField.keyboardType = UIKeyboardTypeDefault;
                    break;
            }
            textField.placeholder = fieldInfo.localizedLabel;
            textField.text = fieldInfo.textValue;
            additionalFieldsDict[fieldInfo.identifier] = textField;
        }];
    }

    void(^loginBlock)(BOOL) = ^(BOOL save) {
        login.username = usernameField.text;
        login.password = passwordField.text;
        for (VLCNetworkServerLoginInformationField *fieldInfo in login.additionalFields) {
            UITextField *textField = additionalFieldsDict[fieldInfo.identifier];
            fieldInfo.textValue = textField.text;
        }
        if (save) {
            NSError *error = nil;
            if (![login saveLoginInformationToKeychainWithError:&error]) {
                [self showKeychainSaveError:error forLogin:login];
            }
        }
        [self showBrowserWithLogin:login];
    };

277
278
279
    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"LOGIN", nil)
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction * _Nonnull action) {
280
                                                          loginBlock(NO);
281
282
283
284
285
                                                      }]];

    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_SAVE", nil)
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction * _Nonnull action) {
286
                                                          loginBlock(YES);
287
                                                      }]];
288
    if (login.username.length || login.password.length) {
289
290
291
        [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_DELETE", nil)
                                                            style:UIAlertActionStyleDestructive
                                                          handler:^(UIAlertAction * _Nonnull action) {
292
293
294
295
                                                              NSError *error = nil;
                                                              if (![login deleteFromKeychainWithError:&error]){
                                                                  [self showKeychainDeleteError:error];
                                                              }
296
                                                          }]];
297
298
299
300
301
302
303
304
    } else {
        [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_ANONYMOUS_LOGIN", nil)
                                                            style:UIAlertActionStyleDefault
                                                          handler:^(UIAlertAction * _Nonnull action) {
                                                              login.username = nil;
                                                              login.password = nil;
                                                              [self showBrowserWithLogin:login];
                                                          }]];
305
306
307
308
309
    }

    [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"BUTTON_CANCEL", nil)
                                                        style:UIAlertActionStyleCancel
                                                      handler:nil]];
310

311
312
313
314
315
316
317
318
    [self presentViewController:alertController animated:YES completion:nil];
}

- (void)showBrowserWithLogin:(nonnull VLCNetworkServerLoginInformation *)login
{
    id<VLCNetworkServerBrowser> serverBrowser = nil;
    NSString *identifier = login.protocolIdentifier;

319
320
321
    if ([identifier isEqualToString:VLCNetworkServerProtocolIdentifierFTP]) {
        serverBrowser = [[VLCNetworkServerBrowserFTP alloc] initWithLogin:login];
    } else if ([identifier isEqualToString:VLCNetworkServerProtocolIdentifierPlex]) {
322
323
324
325
326
327
        serverBrowser = [[VLCNetworkServerBrowserPlex alloc] initWithLogin:login];
    } else if ([identifier isEqualToString:VLCNetworkServerProtocolIdentifierSMB]) {
        serverBrowser = [VLCNetworkServerBrowserVLCMedia SMBNetworkServerBrowserWithLogin:login];
    }

    if (serverBrowser) {
328
        VLCServerBrowsingTVViewController *targetViewController = [[VLCSearchableServerBrowsingTVViewController alloc] initWithServerBrowser:serverBrowser];
329
330
331
332
        [self presentViewController:[[UINavigationController alloc] initWithRootViewController:targetViewController]
                           animated:YES
                         completion:nil];
    }
Tobias's avatar
Tobias committed
333
334
335
}

#pragma mark - VLCLocalServerDiscoveryController
336
337
- (void)discoveryFoundSomethingNew
{
338
339
340
    NSString * (^mapServiceName)(id<VLCLocalNetworkService>) = ^NSString *(id<VLCLocalNetworkService> service) {
        return [NSString stringWithFormat:@"%@: %@", service.serviceName, service.title];
    };
341
342

    NSMutableArray<id<VLCLocalNetworkService>> *newNetworkServices = [NSMutableArray array];
343
    NSMutableSet<NSString *> *addedNetworkServices = [[NSMutableSet alloc] init];
344
345
346
347
348
349
    VLCLocalServerDiscoveryController *discoveryController = self.discoveryController;
    NSUInteger sectionCount = [discoveryController numberOfSections];
    for (NSUInteger section = 0; section < sectionCount; ++section) {
        NSUInteger itemsCount = [discoveryController numberOfItemsInSection:section];
        for (NSUInteger index = 0; index < itemsCount; ++index) {
            NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];
350
            id<VLCLocalNetworkService> service = [discoveryController networkServiceForIndexPath:indexPath];
351
            if (service != nil) {
352
353
354
355
356
                NSString *mappedName = mapServiceName(service);
                if(![addedNetworkServices containsObject:mappedName]) {
                    [addedNetworkServices addObject:mappedName];
                    [newNetworkServices addObject:service];
                }
357
            }
358
359
360
        }
    }

361
362
363
    NSArray *oldNetworkServices = self.networkServices;
    GRKArrayDiff *diff = [[GRKArrayDiff alloc] initWithPreviousArray:oldNetworkServices
                                                        currentArray:newNetworkServices
364
                                                       identityBlock:mapServiceName
365
366
367
368
369
370
371
                                                       modifiedBlock:nil];

    [diff performBatchUpdatesWithCollectionView:self.collectionView
                                        section:0
                               dataSourceUpdate:^{
                                   self.networkServices = newNetworkServices;
                               } completion:nil];
372
373

    _nothingFoundLabel.hidden = self.discoveryController.foundAnythingAtAll;
Tobias's avatar
Tobias committed
374
375
376
}

@end