VLCHTTPUploaderController.m 12 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 28 29 30
#if TARGET_OS_IOS
#import "VLCMediaFileDiscoverer.h"
#endif

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

@implementation VLCHTTPUploaderController
41

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

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

    return sharedInstance;
}

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

68
    }
69 70

    return self;
71 72
}

73 74 75 76 77
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

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

    if (_backgroundTaskIdentifier && _backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier];
        _backgroundTaskIdentifier = 0;
    }
87 88 89 90
}

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

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

- (BOOL)isServerRunning
{
128
    return _httpServer.isRunning;
129 130 131 132
}

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

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

                /* 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) ) {
151
                        _nameOfUsedNetworkInterface = [NSString stringWithUTF8String:anInterface->ifa_name];
152 153 154 155 156 157 158 159
                        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) ) {
160
                        _nameOfUsedNetworkInterface = [NSString stringWithUTF8String:anInterface->ifa_name];
161 162 163
                        break;
                    }
                }
164 165 166 167 168 169 170 171

                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;
                    }
                }
172 173 174 175
            }
            anInterface = anInterface->ifa_next;
        }
    }
176
    freeifaddrs(listOfInterfaces);
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 204 205 206 207
    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];
208

209
    [_httpServer setInterface:_nameOfUsedNetworkInterface];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
210

211 212 213
    [_httpServer setIPv4Enabled:YES];
    [_httpServer setIPv6Enabled:[[[NSUserDefaults standardUserDefaults] objectForKey:kVLCSettingWiFiSharingIPv6] boolValue]];

214 215
    // Tell the server to broadcast its presence via Bonjour.
    // This allows browsers such as Safari to automatically discover our service.
216
    [_httpServer setType:@"_http._tcp."];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
217

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

221
    APLog(@"Setting document root: %@", docRoot);
222

223 224
    [_httpServer setDocumentRoot:docRoot];
    [_httpServer setPort:80];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
225

226
    [_httpServer setConnectionClass:[VLCHTTPConnection class]];
227

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

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

245
        if (error) {
246
            APLog(@"Error starting HTTP Server: %@", error.localizedDescription);
247
            [_httpServer stop];
248
        }
249
        return false;
250
    }
251
    return true;
252 253
}

254 255 256 257 258 259
- (NSString *)currentIPAddress
{
    NSString *address = @"";
    struct ifaddrs *interfaces = NULL;
    struct ifaddrs *temp_addr = NULL;
    int success = getifaddrs(&interfaces);
260

261
    if (success != 0) {
262 263 264 265 266 267 268
        freeifaddrs(interfaces);
        return address;
    }

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

275 276 277 278
    freeifaddrs(interfaces);
    return address;
}

279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
- (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
}

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

301 302
    /* 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 */
303
#if TARGET_OS_IOS
304 305 306 307 308 309 310 311 312 313 314 315
    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++) {
316
            potentialFilename = [NSString stringWithFormat:@"%@ %lu.%@", rawFileName, (unsigned long)x, fileExtension];
317 318 319 320 321 322 323 324 325
            if (![[NSFileManager defaultManager] fileExistsAtPath:[libraryPath stringByAppendingPathComponent:potentialFilename]])
                break;
        }
        finalFilePath = [libraryPath stringByAppendingPathComponent:potentialFilename];
    }

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

330
    [[VLCMediaFileDiscoverer sharedInstance] performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
331
#endif
332 333 334 335 336 337 338 339 340 341 342 343
}

- (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];
344 345
}

346
@end