VLCGoogleDriveController.m 10.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*****************************************************************************
 * VLCGoogleDriveController.m
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2013 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Carola Nitz <nitz.carola # googlemail.com>
 *          Felix Paul Kühne <fkuehne # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/
Carola Nitz's avatar
Carola Nitz committed
13 14 15 16 17 18 19 20 21 22

#import "VLCGoogleDriveController.h"
#import "NSString+SupportedMedia.h"
#import "VLCAppDelegate.h"
#import "HTTPMessage.h"

@interface VLCGoogleDriveController ()
{
    GTLDriveFileList *_fileList;
    GTLServiceTicket *_fileListTicket;
23

Carola Nitz's avatar
Carola Nitz committed
24 25 26 27 28
    NSArray *_currentFileList;

    NSMutableArray *_listOfGoogleDriveFilesToDownload;
    BOOL _downloadInProgress;

29
    NSString *_nextPageToken;
30 31 32 33

    CGFloat _averageSpeed;
    NSTimeInterval _startDL;
    NSTimeInterval _lastStatsUpdate;
Carola Nitz's avatar
Carola Nitz committed
34 35 36 37 38 39 40 41
}

@end

@implementation VLCGoogleDriveController

#pragma mark - session handling

42 43
+ (VLCGoogleDriveController *)sharedInstance
{
44 45
    static VLCGoogleDriveController *sharedInstance = nil;
    static dispatch_once_t pred;
46

47 48 49
    dispatch_once(&pred, ^{
        sharedInstance = [[self alloc] init];
    });
50

51
    return sharedInstance;
52 53
}

Carola Nitz's avatar
Carola Nitz committed
54 55 56 57 58 59
- (void)startSession
{
    self.driveService = [[GTLServiceDrive alloc] init];
    self.driveService.authorizer = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName clientID:kVLCGoogleDriveClientID clientSecret:kVLCGoogleDriveClientSecret];
}

60 61 62 63 64 65 66
- (void)stopSession
{
    [_fileListTicket cancelTicket];
    _nextPageToken = nil;
    _currentFileList = nil;
}

Carola Nitz's avatar
Carola Nitz committed
67 68
- (void)logout
{
69 70
    [GTMOAuth2ViewControllerTouch removeAuthFromKeychainForName:kKeychainItemName];
    self.driveService.authorizer = nil;
71
    _currentFileList = nil;
72 73
    if ([self.delegate respondsToSelector:@selector(mediaListUpdated)])
    [self.delegate mediaListUpdated];
Carola Nitz's avatar
Carola Nitz committed
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
}

- (BOOL)isAuthorized
{
    return [((GTMOAuth2Authentication *)self.driveService.authorizer) canAuthorize];;
}

- (void)showAlert:(NSString *)title message:(NSString *)message
{
    UIAlertView *alert;
    alert = [[UIAlertView alloc] initWithTitle: title
                                       message: message
                                      delegate: nil
                             cancelButtonTitle: @"OK"
                             otherButtonTitles: nil];
    [alert show];
}

#pragma mark - file management
93
- (void)requestFileListing
Carola Nitz's avatar
Carola Nitz committed
94 95 96 97 98
{
    if (self.isAuthorized)
        [self listFiles];
}

99 100 101 102 103
- (BOOL)hasMoreFiles
{
    return _nextPageToken != nil;
}

104
- (void)downloadFileToDocumentFolder:(GTLDriveFile *)file
Carola Nitz's avatar
Carola Nitz committed
105
{
106 107
    if (!_listOfGoogleDriveFilesToDownload)
        _listOfGoogleDriveFilesToDownload = [[NSMutableArray alloc] init];
Carola Nitz's avatar
Carola Nitz committed
108

109
    [_listOfGoogleDriveFilesToDownload addObject:file];
Carola Nitz's avatar
Carola Nitz committed
110

111 112 113 114
    if ([self.delegate respondsToSelector:@selector(numberOfFilesWaitingToBeDownloadedChanged)])
        [self.delegate numberOfFilesWaitingToBeDownloadedChanged];

    [self _triggerNextDownload];
Carola Nitz's avatar
Carola Nitz committed
115 116 117 118 119 120
}

- (void)listFiles
{
    _fileList = nil;

121
    GTLQueryDrive *query;
Carola Nitz's avatar
Carola Nitz committed
122

123 124 125 126
    query = [GTLQueryDrive queryForFilesList];
    query.fields = @"items(originalFilename,title,mimeType,fileExtension,fileSize,iconLink,downloadUrl,webContentLink),nextPageToken";
    query.pageToken = _nextPageToken;
    query.maxResults = 100;
Carola Nitz's avatar
Carola Nitz committed
127

128
    APLog(@"fetching files with following queryfields:%@", query.fields);
129
    _fileListTicket = [self.driveService executeQuery:query
Carola Nitz's avatar
Carola Nitz committed
130 131 132
                          completionHandler:^(GTLServiceTicket *ticket,
                                              GTLDriveFileList *fileList,
                                              NSError *error) {
133 134
                              if (error == nil) {
                                  _fileList = fileList;
135
                                  _nextPageToken = fileList.nextPageToken;
136
                                  _fileListTicket = nil;
137
                                  [self _listOfGoodFiles];
138
                              } else {
139
                                  [self showAlert:NSLocalizedString(@"GDRIVE_ERROR_FETCHING_FILES",nil) message:error.localizedDescription];
140
                              }
Carola Nitz's avatar
Carola Nitz committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154
                          }];
}

- (void)_triggerNextDownload
{
    if (_listOfGoogleDriveFilesToDownload.count > 0 && !_downloadInProgress) {
        [self _reallyDownloadFileToDocumentFolder:_listOfGoogleDriveFilesToDownload[0]];
        [_listOfGoogleDriveFilesToDownload removeObjectAtIndex:0];

        if ([self.delegate respondsToSelector:@selector(numberOfFilesWaitingToBeDownloadedChanged)])
            [self.delegate numberOfFilesWaitingToBeDownloadedChanged];
    }
}

155
- (void)_reallyDownloadFileToDocumentFolder:(GTLDriveFile *)file
Carola Nitz's avatar
Carola Nitz committed
156 157
{
    NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
158
    NSString *filePath = [searchPaths[0] stringByAppendingFormat:@"/%@", file.originalFilename];
Carola Nitz's avatar
Carola Nitz committed
159

160
    [self loadFile:file intoPath:filePath];
Carola Nitz's avatar
Carola Nitz committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175

    if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStarted)])
        [self.delegate operationWithProgressInformationStarted];

    _downloadInProgress = YES;
}

- (BOOL)_supportedFileExtension:(NSString *)filename
{
    if ([filename isSupportedMediaFormat] || [filename isSupportedAudioMediaFormat] || [filename isSupportedSubtitleFormat])
        return YES;

    return NO;
}

176
- (void)_listOfGoodFiles
Carola Nitz's avatar
Carola Nitz committed
177 178
{
    NSMutableArray *listOfGoodFilesAndFolders = [[NSMutableArray alloc] init];
179

Carola Nitz's avatar
Carola Nitz committed
180 181 182
    for (GTLDriveFile *driveFile in _fileList.items)
    {
        BOOL isDirectory = [driveFile.mimeType isEqualToString:@"application/vnd.google-apps.folder"];
183
        if (!isDirectory && [self _supportedFileExtension:[NSString stringWithFormat:@".%@",driveFile.fileExtension ]]) {
184
            [listOfGoodFilesAndFolders addObject:driveFile];
Carola Nitz's avatar
Carola Nitz committed
185 186
        }
    }
187
    _currentFileList = [_currentFileList count] ? [_currentFileList arrayByAddingObjectsFromArray:listOfGoodFilesAndFolders] : [NSArray arrayWithArray:listOfGoodFilesAndFolders];
Carola Nitz's avatar
Carola Nitz committed
188

189
    if ([_currentFileList count] <= 10 && [self hasMoreFiles]) {
190
        [self requestFileListing];
191 192
        return;
    }
Carola Nitz's avatar
Carola Nitz committed
193 194 195 196 197 198

    APLog(@"found filtered metadata for %i files", _currentFileList.count);
    if ([self.delegate respondsToSelector:@selector(mediaListUpdated)])
        [self.delegate mediaListUpdated];
}

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
- (void)loadFile:(GTLDriveFile*)file intoPath:(NSString*)destinationPath
{
    NSString *exportURLStr = file.downloadUrl;

    if ([exportURLStr length] > 0) {
        NSString *suggestedName = file.originalFilename;
        if ([suggestedName length] == 0) {
            suggestedName = file.title;
        }

        NSURL *url = [NSURL URLWithString:exportURLStr];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];

        fetcher.authorizer = self.driveService.authorizer;
        fetcher.downloadPath = destinationPath;

        // Fetcher logging can include comments.
        [fetcher setCommentWithFormat:@"Downloading \"%@\"", file.title];
        __weak GTMHTTPFetcher *weakFetcher = fetcher;
219
        _startDL = [NSDate timeIntervalSinceReferenceDate];
220
        fetcher.receivedDataBlock = ^(NSData *receivedData) {
221 222 223 224
            if ((_lastStatsUpdate > 0 && ([NSDate timeIntervalSinceReferenceDate] - _lastStatsUpdate > .5)) || _lastStatsUpdate <= 0) {
                [self calculateRemainingTime:weakFetcher.downloadedLength expectedDownloadSize:[file.fileSize floatValue]];
                _lastStatsUpdate = [NSDate timeIntervalSinceReferenceDate];
            }
225

226
            CGFloat progress = (CGFloat)weakFetcher.downloadedLength / (CGFloat)[file.fileSize unsignedLongValue];
227 228 229 230 231 232
            if ([self.delegate respondsToSelector:@selector(currentProgressInformation:)])
                [self.delegate currentProgressInformation:progress];
        };

        [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
            if (error == nil) {
233
                [self showAlert:NSLocalizedString(@"GDRIVE_DOWNLOAD_SUCCESSFUL_TITLE",nil) message:NSLocalizedString(@"GDRIVE_DOWNLOAD_SUCCESSFUL",nil)];
234 235
                [self downloadSucessfull];
            } else {
236
                [self showAlert:NSLocalizedString(@"GDRIVE_ERROR_DOWNLOADING_FILE_TITLE",nil) message:NSLocalizedString(@"GDRIVE_ERROR_DOWNLOADING_FILE",nil)];
237 238 239
                [self downloadFailedWithError:error];
            }
        }];
Carola Nitz's avatar
Carola Nitz committed
240 241 242
    }
}

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
- (void)calculateRemainingTime:(CGFloat)receivedDataSize expectedDownloadSize:(CGFloat)expectedDownloadSize
{
    CGFloat lastSpeed = receivedDataSize / ([NSDate timeIntervalSinceReferenceDate] - _startDL);
    CGFloat smoothingFactor = 0.005;
    _averageSpeed = isnan(_averageSpeed) ? lastSpeed : smoothingFactor * lastSpeed + (1 - smoothingFactor) * _averageSpeed;

    CGFloat RemainingInSeconds = (expectedDownloadSize - receivedDataSize) / _averageSpeed;

    NSDate *date = [NSDate dateWithTimeIntervalSince1970:RemainingInSeconds];
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"HH:mm:ss"];
    [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];

    NSString  *remaingTime = [formatter stringFromDate:date];
    if ([self.delegate respondsToSelector:@selector(updateRemainingTime:)])
        [self.delegate updateRemainingTime:remaingTime];
}

261 262 263
- (void)downloadSucessfull
{
    /* update library now that we got a file */
264
    APLog(@"DriveFile download was successful");
265
    VLCAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
266
    [appDelegate performSelectorOnMainThread:@selector(updateMediaList) withObject:nil waitUntilDone:NO];
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284

    if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStopped)])
        [self.delegate operationWithProgressInformationStopped];
    _downloadInProgress = NO;

    [self _triggerNextDownload];
}

- (void)downloadFailedWithError:(NSError*)error
{
    APLog(@"DriveFile download failed with error %i", error.code);
    if ([self.delegate respondsToSelector:@selector(operationWithProgressInformationStopped)])
        [self.delegate operationWithProgressInformationStopped];
    _downloadInProgress = NO;

    [self _triggerNextDownload];
}

Carola Nitz's avatar
Carola Nitz committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
#pragma mark - VLC internal communication and delegate

- (NSArray *)currentListFiles
{
    return _currentFileList;
}

- (NSInteger)numberOfFilesWaitingToBeDownloaded
{
    if (_listOfGoogleDriveFilesToDownload)
        return _listOfGoogleDriveFilesToDownload.count;

    return 0;
}

@end