VLCHTTPUploaderController.m 12.3 KB
Newer Older
1
/*****************************************************************************
2
 * VLCHTTPUploaderController.m
3 4
 * VLC for iOS
 *****************************************************************************
5
 * Copyright (c) 2013-2015 VideoLAN. All rights reserved.
6 7 8 9 10 11
 * $Id$
 *
 * Authors: Jean-Baptiste Kempf <jb # videolan.org>
 *          Gleb Pinigin <gpinigin # gmail.com>
 *          Felix Paul Kühne <fkuehne # videolan.org>
 *          Jean-Romain Prévost <jr # 3on.fr>
12 13
 *          Carola Nitz <caro # videolan.org>
 *          Ron Soffer <rsoffer1 # gmail.com>
14 15 16
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
17

18 19
#import "VLCHTTPUploaderController.h"
#import "VLCHTTPConnection.h"
20
#import "VLCActivityManager.h"
21
#import "HTTPServer.h"
22
#import "Reachability.h"
23

24 25 26
#import <ifaddrs.h>
#import <arpa/inet.h>

27
#if TARGET_OS_IOS
28
#import "VLC-Swift.h"
29 30 31
#import "VLCMediaFileDiscoverer.h"
#endif

32
@interface VLCHTTPUploaderController()
33
{
34 35
    NSString *_nameOfUsedNetworkInterface;
    HTTPServer *_httpServer;
36
    UIBackgroundTaskIdentifier _backgroundTaskIdentifier;
37
    Reachability *_reachability;
38
}
39 40 41
@end

@implementation VLCHTTPUploaderController
42

Felix Paul Kühne's avatar
Felix Paul Kühne committed
43 44 45 46 47 48
+ (instancetype)sharedInstance
{
    static VLCHTTPUploaderController *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
49
        sharedInstance = [VLCHTTPUploaderController new];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
50 51 52 53 54
    });

    return sharedInstance;
}

55 56
- (id)init
{
57
    if (self = [super init]) {
58 59
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(applicationDidBecomeActive:)
60
            name:UIApplicationDidBecomeActiveNotification object:nil];
61
        [center addObserver:self selector:@selector(applicationDidEnterBackground:)
62
            name:UIApplicationDidEnterBackgroundNotification object:nil];
63
        [center addObserver:self selector:@selector(netReachabilityChanged) name:kReachabilityChangedNotification object:nil];
64 65
        
        BOOL isHTTPServerOn = [[NSUserDefaults standardUserDefaults] boolForKey:kVLCSettingSaveHTTPUploadServerStatus];
66
        [self netReachabilityChanged];
67 68
        [self changeHTTPServerState:isHTTPServerOn];

69
    }
70 71

    return self;
72 73 74 75
}

- (void)applicationDidBecomeActive: (NSNotification *)notification
{
76
    if (!_httpServer.isRunning)
77 78 79 80 81 82
        [self changeHTTPServerState:[[NSUserDefaults standardUserDefaults] boolForKey:kVLCSettingSaveHTTPUploadServerStatus]];

    if (_backgroundTaskIdentifier && _backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier];
        _backgroundTaskIdentifier = 0;
    }
83 84 85 86
}

- (void)applicationDidEnterBackground: (NSNotification *)notification
{
87
    if (_httpServer.isRunning) {
88
        if (!_backgroundTaskIdentifier || _backgroundTaskIdentifier == UIBackgroundTaskInvalid) {
89
            dispatch_block_t expirationHandler = ^{
90
                [self changeHTTPServerState:NO];
91 92
                [[UIApplication sharedApplication] endBackgroundTask:self->_backgroundTaskIdentifier];
                self->_backgroundTaskIdentifier = 0;
93 94 95 96 97 98
            };
            if ([[UIApplication sharedApplication] respondsToSelector:@selector(beginBackgroundTaskWithName:expirationHandler:)]) {
                _backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:@"VLCUploader" expirationHandler:expirationHandler];
            } else {
                _backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:expirationHandler];
            }
99 100
        }
    }
101 102
}

103 104
- (NSString *)httpStatus
{
105
    if (_httpServer.isRunning) {
106 107 108 109 110 111 112 113 114 115 116
        if (_httpServer.listeningPort != 80) {
            return [NSString stringWithFormat:@"http://%@:%i\nhttp://%@:%i",
                    [self currentIPAddress],
                    _httpServer.listeningPort,
                    [self hostname],
                    _httpServer.listeningPort];
        } else {
            return [NSString stringWithFormat:@"http://%@\nhttp://%@",
                    [self currentIPAddress],
                    [self hostname]];
        }
117 118 119 120 121 122 123
    } else {
        return NSLocalizedString(@"HTTP_UPLOAD_SERVER_OFF", nil);
    }
}

- (BOOL)isServerRunning
{
124
    return _httpServer.isRunning;
125 126 127 128
}

- (void)netReachabilityChanged
{
129 130 131
    // find an interface to listen on
    struct ifaddrs *listOfInterfaces = NULL;
    struct ifaddrs *anInterface = NULL;
132 133
    BOOL serverWasRunning = self.isServerRunning;
    [self changeHTTPServerState:NO];
134
    _nameOfUsedNetworkInterface = nil;
135 136 137 138 139 140
    int ret = getifaddrs(&listOfInterfaces);
    if (ret == 0) {
        anInterface = listOfInterfaces;

        while (anInterface != NULL) {
            if (anInterface->ifa_addr->sa_family == AF_INET) {
141
                APLog(@"Found interface %s, address %@", anInterface->ifa_name, @(inet_ntoa(((struct sockaddr_in *)anInterface->ifa_addr)->sin_addr)));
142 143 144 145 146

                /* check for primary interface first */
                if (strncmp (anInterface->ifa_name,"en0",strlen("en0")) == 0) {
                    unsigned int flags = anInterface->ifa_flags;
                    if( (flags & 0x1) && (flags & 0x40) && !(flags & 0x8) ) {
147
                        _nameOfUsedNetworkInterface = [NSString stringWithUTF8String:anInterface->ifa_name];
148 149 150 151 152 153 154 155
                        break;
                    }
                }

                /* oh well, let's move on to the secondary interface */
                if (strncmp (anInterface->ifa_name,"en1",strlen("en1")) == 0) {
                    unsigned int flags = anInterface->ifa_flags;
                    if( (flags & 0x1) && (flags & 0x40) && !(flags & 0x8) ) {
156
                        _nameOfUsedNetworkInterface = [NSString stringWithUTF8String:anInterface->ifa_name];
157 158 159
                        break;
                    }
                }
160 161 162 163 164 165 166 167

                if (strncmp (anInterface->ifa_name,"bridge100",strlen("bridge100")) == 0) {
                    unsigned int flags = anInterface->ifa_flags;
                    if( (flags & 0x1) && (flags & 0x40) && !(flags & 0x8) ) {
                        _nameOfUsedNetworkInterface = [NSString stringWithUTF8String:anInterface->ifa_name];
                        break;
                    }
                }
168 169 170 171
            }
            anInterface = anInterface->ifa_next;
        }
    }
172
    freeifaddrs(listOfInterfaces);
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
    if (_nameOfUsedNetworkInterface == nil) {
        _isReachable = NO;
        [self changeHTTPServerState:NO];
        return;
    }
    _isReachable = YES;
    if (serverWasRunning) {
        [self changeHTTPServerState:YES];
    }
}

- (BOOL)changeHTTPServerState:(BOOL)state
{
    if (!state) {
        [_httpServer stop];
        return true;
    }

    if (_nameOfUsedNetworkInterface == nil) {
        APLog(@"No interface to listen on, server not started");
        _isReachable = NO;
        return NO;
    }

#if TARGET_OS_IOS
    // clean cache before accepting new stuff
    [self cleanCache];
#endif

    // Initialize our http server
    _httpServer = [[HTTPServer alloc] init];
204

205
    [_httpServer setInterface:_nameOfUsedNetworkInterface];
206

207 208 209
    [_httpServer setIPv4Enabled:YES];
    [_httpServer setIPv6Enabled:[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingWiFiSharingIPv6] boolValue]];

210 211
    // Tell the server to broadcast its presence via Bonjour.
    // This allows browsers such as Safari to automatically discover our service.
212
    [_httpServer setType:@"_http._tcp."];
213

214 215
    // Serve files from the standard Sites folder
    NSString *docRoot = [[[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"] stringByDeletingLastPathComponent];
216

217
    APLog(@"Setting document root: %@", docRoot);
218

219
    [_httpServer setDocumentRoot:docRoot];
220
    [_httpServer setPort:80];
221

222
    [_httpServer setConnectionClass:[VLCHTTPConnection class]];
223

224
    NSError *error = nil;
225
    if (![_httpServer start:&error]) {
226
        if (error.code == EACCES) {
227
            APLog(@"Port forbidden by OS, trying another one");
228 229
            [_httpServer setPort:8888];
            if(![_httpServer start:&error])
230 231
                return true;
        }
232

233
        /* Address already in Use, take a random one */
234
        if (error.code == EADDRINUSE) {
235
            APLog(@"Port already in use, trying another one");
236 237
            [_httpServer setPort:0];
            if(![_httpServer start:&error])
238
                return true;
239
        }
240

241
        if (error) {
242
            APLog(@"Error starting HTTP Server: %@", error.localizedDescription);
243
            [_httpServer stop];
244
        }
245
        return false;
246
    }
247
    return true;
248 249
}

250 251 252 253 254 255
- (NSString *)currentIPAddress
{
    NSString *address = @"";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = getifaddrs(&interfaces);
256

257
    if (success != 0) {
258 259 260 261 262 263 264
        freeifaddrs(interfaces);
        return address;
    }

    temp_addr = interfaces;
    while (temp_addr != NULL) {
        if (temp_addr->ifa_addr->sa_family == AF_INET) {
265
            if([@(temp_addr->ifa_name) isEqualToString:_nameOfUsedNetworkInterface])
266
                address = @(inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr));
267
        }
268
        temp_addr = temp_addr->ifa_next;
269
    }
270

271 272 273 274
    freeifaddrs(interfaces);
    return address;
}

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
- (NSString *)hostname
{
    char baseHostName[256];
    int success = gethostname(baseHostName, 255);
    if (success != 0)
        return nil;
    baseHostName[255] = '\0';

#if !TARGET_IPHONE_SIMULATOR
    return [NSString stringWithFormat:@"%s.local", baseHostName];
#else
    return [NSString stringWithFormat:@"%s", baseHostName];
#endif
}

290 291 292 293 294
- (NSString *)hostnamePort
{
    return [NSString stringWithFormat:@"%i", _httpServer.listeningPort];
}

295 296
- (void)moveFileFrom:(NSString *)filepath
{
297 298 299 300 301
    /* update media library when file upload was completed */
    VLCActivityManager *activityManager = [VLCActivityManager defaultManager];
    [activityManager networkActivityStopped];
    [activityManager activateIdleTimer];

302 303
    /* on tvOS, the media remains in the cache folder and will disappear from there
     * while on iOS we have persistent storage, so move it there */
304
#if TARGET_OS_IOS
305 306 307 308 309 310 311 312 313 314 315 316
    NSString *fileName = [filepath lastPathComponent];
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *libraryPath = searchPaths[0];
    NSString *finalFilePath = [libraryPath stringByAppendingPathComponent:fileName];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if ([fileManager fileExistsAtPath:finalFilePath]) {
        /* we don't want to over-write existing files, so add an integer to the file name */
        NSString *potentialFilename;
        NSString *fileExtension = [fileName pathExtension];
        NSString *rawFileName = [fileName stringByDeletingPathExtension];
        for (NSUInteger x = 1; x < 100; x++) {
317
            potentialFilename = [NSString stringWithFormat:@"%@ %lu.%@", rawFileName, (unsigned long)x, fileExtension];
318 319 320 321 322 323 324 325 326
            if (![[NSFileManager defaultManager] fileExistsAtPath:[libraryPath stringByAppendingPathComponent:potentialFilename]])
                break;
        }
        finalFilePath = [libraryPath stringByAppendingPathComponent:potentialFilename];
    }

    NSError *error;
    [fileManager moveItemAtPath:filepath toPath:finalFilePath error:&error];
    if (error) {
327
        APLog(@"Moving received media %@ to library folder failed (%li), deleting", fileName, (long)error.code);
328 329 330
        [fileManager removeItemAtPath:filepath error:nil];
    }

331
    [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
332 333 334
    // FIXME: Replace notifications by cleaner observers
    [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.VLCNewFileAddedNotification
                                                        object:self];
335
#endif
336 337 338 339 340 341 342 343 344 345 346 347
}

- (void)cleanCache
{
    if ([[VLCActivityManager defaultManager] haveNetworkActivity])
        return;

    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString* uploadDirPath = [searchPaths[0] stringByAppendingPathComponent:@"Upload"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:uploadDirPath])
        [fileManager removeItemAtPath:uploadDirPath error:nil];
348 349
}

350
@end