MLMediaLibrary.m 25.5 KB
Newer Older
Pierre's avatar
Pierre committed
1 2 3 4 5 6 7 8 9
//
//  MLMediaLibrary.m
//  MobileMediaLibraryKit
//
//  Created by Pierre d'Herbemont on 7/14/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "MLMediaLibrary.h"
Pierre's avatar
Pierre committed
10 11
#import "MLTitleDecrapifier.h"
#import "MLMovieInfoGrabber.h"
Pierre's avatar
Pierre committed
12 13
#import "MLTVShowInfoGrabber.h"
#import "MLTVShowEpisodesInfoGrabber.h"
Pierre's avatar
Pierre committed
14 15 16 17 18
#import "MLFile.h"
#import "MLLabel.h"
#import "MLShowEpisode.h"
#import "MLThumbnailerQueue.h"
#import "MLShow.h"
19
#import "MLFileParserQueue.h"
20
#import "MLCrashPreventer.h"
Pierre's avatar
Pierre committed
21 22

#define DEBUG 1
23 24
// To debug
#define DELETE_LIBRARY_ON_EACH_LAUNCH 0
Pierre's avatar
Pierre committed
25 26 27 28 29

#ifdef TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif

30 31
#define MLLog(...) NSLog(__VA_ARGS__)

Pierre's avatar
Pierre committed
32 33
// Pref key
static NSString *kLastTVDBUpdateServerTime = @"MLLastTVDBUpdateServerTime";
Pierre's avatar
Pierre committed
34

Pierre's avatar
Pierre committed
35
#if HAVE_BLOCK
Pierre's avatar
Pierre committed
36
@interface MLMediaLibrary ()
Pierre's avatar
Pierre committed
37 38 39
#else
@interface MLMediaLibrary () <MLMovieInfoGrabberDelegate, MLTVShowEpisodesInfoGrabberDelegate, MLTVShowInfoGrabberDelegate>
#endif
Pierre's avatar
Pierre committed
40
- (NSManagedObjectContext *)managedObjectContext;
41
- (void)startUpdateDB;
Pierre's avatar
Pierre committed
42
- (NSString *)databaseFolderPath;
Pierre's avatar
Pierre committed
43 44
@end

Pierre's avatar
Pierre committed
45 46 47 48
@implementation MLMediaLibrary
+ (id)sharedMediaLibrary
{
    static id sharedMediaLibrary = nil;
Pierre's avatar
Pierre committed
49
    if (!sharedMediaLibrary) {
Pierre's avatar
Pierre committed
50
        sharedMediaLibrary = [[[self class] alloc] init];
51
        NSLog(@"Initializing db in %@", [sharedMediaLibrary databaseFolderPath]);
52 53 54 55

        // Also force to init the crash preventer
        // Because it will correctly set up the parser and thumbnail queue
        [MLCrashPreventer sharedPreventer];
Pierre's avatar
Pierre committed
56
    }
Pierre's avatar
Pierre committed
57 58 59 60 61 62 63 64
    return sharedMediaLibrary;
}

- (NSFetchRequest *)fetchRequestForEntity:(NSString *)entity
{
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSManagedObjectContext *moc = [self managedObjectContext];
    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:entity inManagedObjectContext:moc];
Pierre's avatar
Pierre committed
65
    NSAssert(entityDescription, @"No entity");
Pierre's avatar
Pierre committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    [request setEntity:entityDescription];
    return [request autorelease];
}

- (id)createObjectForEntity:(NSString *)entity
{
    NSManagedObjectContext *moc = [self managedObjectContext];
    return [NSEntityDescription insertNewObjectForEntityForName:entity inManagedObjectContext:moc];
}

#pragma mark -
#pragma mark Media Library
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel)
        return _managedObjectModel;
    _managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
    return _managedObjectModel;
}

Pierre's avatar
Pierre committed
86
- (NSString *)databaseFolderPath
Pierre's avatar
Pierre committed
87
{
Pierre's avatar
Pierre committed
88 89 90
    int directory = NSLibraryDirectory;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
    NSString *directoryPath = [paths objectAtIndex:0];
91
#if DELETE_LIBRARY_ON_EACH_LAUNCH
Pierre's avatar
Pierre committed
92
    [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:nil];
93
#endif
Pierre's avatar
Pierre committed
94
    return directoryPath;
Pierre's avatar
Pierre committed
95 96
}

Pierre's avatar
Pierre committed
97 98 99 100 101 102 103 104 105 106 107 108

- (NSString *)thumbnailFolderPath
{
    int directory = NSLibraryDirectory;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
    NSString *directoryPath = [paths objectAtIndex:0];
#if DELETE_LIBRARY_ON_EACH_LAUNCH
    [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:nil];
#endif
    return [directoryPath stringByAppendingPathComponent:@"Thumbnails"];
}

Pierre's avatar
Pierre committed
109 110 111 112 113
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext)
        return _managedObjectContext;

Pierre's avatar
Pierre committed
114
    NSString *databaseFolderPath = [self databaseFolderPath];
Pierre's avatar
Pierre committed
115

Pierre's avatar
Pierre committed
116 117
    NSString *path = [databaseFolderPath stringByAppendingPathComponent: @"MediaLibrary.sqlite"];
    NSURL *url = [NSURL fileURLWithPath:path];
Pierre's avatar
Pierre committed
118 119
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

Pierre's avatar
Pierre committed
120
    NSNumber *yes = [NSNumber numberWithBool:YES];
Pierre's avatar
Pierre committed
121
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
Pierre's avatar
Pierre committed
122 123
                             yes, NSMigratePersistentStoresAutomaticallyOption,
                             yes, NSInferMappingModelAutomaticallyOption, nil];
Pierre's avatar
Pierre committed
124 125

    NSError *error;
Pierre's avatar
Pierre committed
126 127 128
    NSPersistentStore *persistentStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:options error:&error];

    if (!persistentStore) {
Pierre's avatar
Pierre committed
129
#if! TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
130 131 132 133
        // FIXME: Deal with versioning
        NSInteger ret = NSRunAlertPanel(@"Error", @"The Media Library you have on your disk is not compatible with the one Lunettes can read. Do you want to create a new one?", @"No", @"Yes", nil);
        if (ret == NSOKButton)
            [NSApp terminate:nil];
Pierre's avatar
Pierre committed
134
        [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
Pierre's avatar
Pierre committed
135
#else
Pierre's avatar
Pierre committed
136
        [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
Pierre's avatar
Pierre committed
137
#endif
Pierre's avatar
Pierre committed
138 139
        persistentStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:options error:&error];
        if (!persistentStore) {
140 141
            if (coordinator)
                [coordinator release];
Pierre's avatar
Pierre committed
142 143 144 145 146
#if! TARGET_OS_IPHONE
            NSRunInformationalAlertPanel(@"Corrupted Media Library", @"There is nothing we can apparently do about it...", @"OK", nil, nil);
#else
            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Corrupted Media Library" message:@"There is nothing we can apparently do about it..." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [alert show];
147
            [alert autorelease];
Pierre's avatar
Pierre committed
148 149 150 151
#endif
            // Probably assert instead.
            return nil;
        }
Pierre's avatar
Pierre committed
152
    }
Pierre's avatar
Pierre committed
153 154 155

    _managedObjectContext = [[NSManagedObjectContext alloc] init];
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];
Pierre's avatar
Pierre committed
156 157 158 159 160 161 162 163 164
    [coordinator release];
    [_managedObjectContext setUndoManager:nil];
    [_managedObjectContext addObserver:self forKeyPath:@"hasChanges" options:NSKeyValueObservingOptionInitial context:nil];
    return _managedObjectContext;
}

- (void)savePendingChanges
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(savePendingChanges) object:nil];
Pierre's avatar
Pierre committed
165 166 167
    NSError *error = nil;
    BOOL success = [[self managedObjectContext] save:&error];
    NSAssert1(success, @"Can't save: %@", error);
Pierre's avatar
Pierre committed
168
#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
Pierre's avatar
Pierre committed
169 170 171 172 173 174
    NSProcessInfo *process = [NSProcessInfo processInfo];
    if ([process respondsToSelector:@selector(enableSuddenTermination)])
        [process enableSuddenTermination];
#endif
}

Pierre's avatar
Pierre committed
175
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
Pierre's avatar
Pierre committed
176 177
{
    if ([keyPath isEqualToString:@"hasChanges"] && object == _managedObjectContext) {
Pierre's avatar
Pierre committed
178
#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
Pierre's avatar
Pierre committed
179 180 181 182 183
        NSProcessInfo *process = [NSProcessInfo processInfo];
        if ([process respondsToSelector:@selector(disableSuddenTermination)])
            [process disableSuddenTermination];
#endif

Pierre's avatar
Pierre committed
184 185 186 187
        if ([[self managedObjectContext] hasChanges]) {
            [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(savePendingChanges) object:nil];
            [self performSelector:@selector(savePendingChanges) withObject:nil afterDelay:1.];
        }
Pierre's avatar
Pierre committed
188 189 190 191 192
        return;
    }
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

Pierre's avatar
Pierre committed
193 194
#pragma mark -
#pragma mark No meta data fallbacks
Pierre's avatar
Pierre committed
195

Pierre's avatar
Pierre committed
196
- (void)computeThumbnailForFile:(MLFile *)file
Pierre's avatar
Pierre committed
197
{
Pierre's avatar
Pierre committed
198 199 200 201
    if (!file.computedThumbnail) {
        MLLog(@"Computing thumbnail for %@", file.title);
        [[MLThumbnailerQueue sharedThumbnailerQueue] addFile:file];
    }
Pierre's avatar
Pierre committed
202
}
Pierre's avatar
Pierre committed
203
- (void)errorWhenFetchingMetaDataForFile:(MLFile *)file
Pierre's avatar
Pierre committed
204
{
Pierre's avatar
Pierre committed
205
    MLLog(@"Error when fetching for '%@'", file.title);
Pierre's avatar
Pierre committed
206

Pierre's avatar
Pierre committed
207 208
    [self computeThumbnailForFile:file];
}
Pierre's avatar
Pierre committed
209

Pierre's avatar
Pierre committed
210 211 212 213 214 215 216
- (void)errorWhenFetchingMetaDataForShow:(MLShow *)show
{
    for (MLShowEpisode *episode in show.episodes) {
        for (MLFile *file in episode.files)
            [self errorWhenFetchingMetaDataForFile:file];
    }
}
Pierre's avatar
Pierre committed
217

Pierre's avatar
Pierre committed
218 219 220 221
- (void)noMetaDataInRemoteDBForFile:(MLFile *)file
{
    file.noOnlineMetaData = [NSNumber numberWithBool:YES];
    [self computeThumbnailForFile:file];
Pierre's avatar
Pierre committed
222 223
}

Pierre's avatar
Pierre committed
224
- (void)noMetaDataInRemoteDBForShow:(MLShow *)show
Pierre's avatar
Pierre committed
225
{
Pierre's avatar
Pierre committed
226 227 228
    for (MLShowEpisode *episode in show.episodes) {
        for (MLFile *file in episode.files)
            [self noMetaDataInRemoteDBForFile:file];
Pierre's avatar
Pierre committed
229 230 231
    }
}

Pierre's avatar
Pierre committed
232 233 234 235
#pragma mark -
#pragma mark Getter

- (void)addNewLabelWithName:(NSString *)name
Pierre's avatar
Pierre committed
236
{
Pierre's avatar
Pierre committed
237 238
    MLLabel *label = [self createObjectForEntity:@"Label"];
    label.name = name;
Pierre's avatar
Pierre committed
239 240
}

Pierre's avatar
Pierre committed
241 242 243 244 245 246 247
/**
 * TV MLShow Episodes
 */

#pragma mark -
#pragma mark Online meta data grabbing

Pierre's avatar
Pierre committed
248 249 250
#if !HAVE_BLOCK
- (void)tvShowEpisodesInfoGrabberDidFinishGrabbing:(MLTVShowEpisodesInfoGrabber *)grabber
{
Pierre's avatar
Pierre committed
251
    MLShow *show = grabber.userData;
Pierre's avatar
Pierre committed
252 253 254 255 256 257 258

    NSArray *results = grabber.episodesResults;
    [show setValue:[grabber.results objectForKey:@"serieArtworkURL"] forKey:@"artworkURL"];
    for (id result in results) {
        if ([[result objectForKey:@"serie"] boolValue]) {
            continue;
        }
Pierre's avatar
Pierre committed
259
        MLShowEpisode *showEpisode = [MLShowEpisode episodeWithShow:show episodeNumber:[result objectForKey:@"episodeNumber"] seasonNumber:[result objectForKey:@"seasonNumber"] createIfNeeded:YES];
Pierre's avatar
Pierre committed
260 261 262 263
        showEpisode.name = [result objectForKey:@"title"];
        showEpisode.theTVDBID = [result objectForKey:@"id"];
        showEpisode.shortSummary = [result objectForKey:@"shortSummary"];
        showEpisode.artworkURL = [result objectForKey:@"artworkURL"];
Pierre's avatar
Pierre committed
264 265 266 267 268
        if (!showEpisode.artworkURL) {
            for (MLFile *file in showEpisode.files)
                [self computeThumbnailForFile:file];
        }

Pierre's avatar
Pierre committed
269 270 271 272 273
        showEpisode.lastSyncDate = [MLTVShowInfoGrabber serverTime];
    }
    show.lastSyncDate = [MLTVShowInfoGrabber serverTime];
}

Pierre's avatar
Pierre committed
274 275 276 277 278 279 280 281 282 283 284
- (void)save
{
    [[self managedObjectContext] save:nil];
}

- (void)tvShowEpisodesInfoGrabber:(MLTVShowEpisodesInfoGrabber *)grabber didFailWithError:(NSError *)error
{
    MLShow *show = grabber.userData;
    [self errorWhenFetchingMetaDataForShow:show];
}

Pierre's avatar
Pierre committed
285 286
- (void)tvShowInfoGrabberDidFinishGrabbing:(MLTVShowInfoGrabber *)grabber
{
Pierre's avatar
Pierre committed
287
    MLShow *show = grabber.userData;
Pierre's avatar
Pierre committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    NSArray *results = grabber.results;
    if ([results count] > 0) {
        NSDictionary *result = [results objectAtIndex:0];
        NSString *showId = [result objectForKey:@"id"];

        show.theTVDBID = showId;
        show.name = [result objectForKey:@"title"];
        show.shortSummary = [result objectForKey:@"shortSummary"];
        show.releaseYear = [result objectForKey:@"releaseYear"];

        // Fetch episodes info
        MLTVShowEpisodesInfoGrabber *grabber = [[[MLTVShowEpisodesInfoGrabber alloc] init] autorelease];
        grabber.delegate = self;
        grabber.userData = show;
        [grabber lookUpForShowID:showId];
    }
    else {
        // Not found.
Pierre's avatar
Pierre committed
306
        [self noMetaDataInRemoteDBForShow:show];
Pierre's avatar
Pierre committed
307 308 309 310
        show.lastSyncDate = [MLTVShowInfoGrabber serverTime];
    }
}

Pierre's avatar
Pierre committed
311 312 313 314 315 316
- (void)tvShowInfoGrabber:(MLTVShowInfoGrabber *)grabber didFailWithError:(NSError *)error
{
    MLShow *show = grabber.userData;
    [self errorWhenFetchingMetaDataForShow:show];
}

Pierre's avatar
Pierre committed
317 318
- (void)tvShowInfoGrabberDidFetchServerTime:(MLTVShowInfoGrabber *)grabber
{
Pierre's avatar
Pierre committed
319
    MLShow *show = grabber.userData;
Pierre's avatar
Pierre committed
320 321 322

    [[NSUserDefaults standardUserDefaults] setInteger:[[MLTVShowInfoGrabber serverTime] integerValue] forKey:kLastTVDBUpdateServerTime];

Pierre's avatar
Pierre committed
323
    // First fetch the MLShow ID
Pierre's avatar
Pierre committed
324 325 326 327
    MLTVShowInfoGrabber *showInfoGrabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
    showInfoGrabber.delegate = self;
    showInfoGrabber.userData = show;

Pierre's avatar
Pierre committed
328 329
    MLLog(@"Fetching show information on %@", show.name);

Pierre's avatar
Pierre committed
330 331 332 333
    [showInfoGrabber lookUpForTitle:show.name];
}
#endif

Pierre's avatar
Pierre committed
334
- (void)fetchMetaDataForShow:(MLShow *)show
Pierre's avatar
Pierre committed
335
{
Pierre's avatar
Pierre committed
336 337
    if (!_allowNetworkAccess)
        return;
Pierre's avatar
Pierre committed
338 339
    MLLog(@"Fetching show server time");

Pierre's avatar
Pierre committed
340
    // First fetch the serverTime, so that we can update each entry.
Pierre's avatar
Pierre committed
341 342
#if HAVE_BLOCK
    [MLTVShowInfoGrabber fetchServerTimeAndExecuteBlock:^(NSNumber *serverDate) {
Pierre's avatar
Pierre committed
343 344 345

        [[NSUserDefaults standardUserDefaults] setInteger:[serverDate integerValue] forKey:kLastTVDBUpdateServerTime];

Pierre's avatar
Pierre committed
346 347 348 349
        MLLog(@"Fetching show information on %@", show.name);

        // First fetch the MLShow ID
        MLTVShowInfoGrabber *grabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
Pierre's avatar
Pierre committed
350 351 352 353 354 355 356 357 358 359 360
        [grabber lookUpForTitle:show.name andExecuteBlock:^{
            NSArray *results = grabber.results;
            if ([results count] > 0) {
                NSDictionary *result = [results objectAtIndex:0];
                NSString *showId = [result objectForKey:@"id"];

                show.theTVDBID = showId;
                show.name = [result objectForKey:@"title"];
                show.shortSummary = [result objectForKey:@"shortSummary"];
                show.releaseYear = [result objectForKey:@"releaseYear"];

Pierre's avatar
Pierre committed
361 362
                MLLog(@"Fetching show episode information on %@", showId);

Pierre's avatar
Pierre committed
363
                // Fetch episode info
Pierre's avatar
Pierre committed
364
                MLTVShowEpisodesInfoGrabber *grabber = [[[MLTVShowEpisodesInfoGrabber alloc] init] autorelease];
Pierre's avatar
Pierre committed
365 366 367 368 369 370 371
                [grabber lookUpForShowID:showId andExecuteBlock:^{
                    NSArray *results = grabber.episodesResults;
                    [show setValue:[grabber.results objectForKey:@"serieArtworkURL"] forKey:@"artworkURL"];
                    for (id result in results) {
                        if ([[result objectForKey:@"serie"] boolValue]) {
                            continue;
                        }
Pierre's avatar
Pierre committed
372
                        MLShowEpisode *showEpisode = [self showEpisodeWithShow:show episodeNumber:[result objectForKey:@"episodeNumber"] seasonNumber:[result objectForKey:@"seasonNumber"]];
Pierre's avatar
Pierre committed
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
                        showEpisode.name = [result objectForKey:@"title"];
                        showEpisode.theTVDBID = [result objectForKey:@"id"];
                        showEpisode.shortSummary = [result objectForKey:@"shortSummary"];
                        showEpisode.artworkURL = [result objectForKey:@"artworkURL"];
                        showEpisode.lastSyncDate = serverDate;
                    }
                    show.lastSyncDate = serverDate;
                }];
            }
            else {
                // Not found.
                show.lastSyncDate = serverDate;
            }

        }];
    }];
Pierre's avatar
Pierre committed
389
#else
Pierre's avatar
Pierre committed
390 391 392 393
    MLTVShowInfoGrabber *grabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
    grabber.delegate = self;
    grabber.userData = show;
    [grabber fetchServerTime];
Pierre's avatar
Pierre committed
394
#endif
Pierre's avatar
Pierre committed
395 396
}

Pierre's avatar
Pierre committed
397
- (void)addTVShowEpisodeWithInfo:(NSDictionary *)tvShowEpisodeInfo andFile:(MLFile *)file
Pierre's avatar
Pierre committed
398
{
Pierre's avatar
Pierre committed
399
    file.type = kMLFileTypeTVShowEpisode;
Pierre's avatar
Pierre committed
400 401 402 403 404 405

    NSNumber *seasonNumber = [tvShowEpisodeInfo objectForKey:@"season"];
    NSNumber *episodeNumber = [tvShowEpisodeInfo objectForKey:@"episode"];
    NSString *tvShowName = [tvShowEpisodeInfo objectForKey:@"tvShowName"];
    BOOL hasNoTvShow = NO;
    if (!tvShowName) {
Pierre's avatar
Pierre committed
406
        tvShowName = @"Untitled TV MLShow";
Pierre's avatar
Pierre committed
407 408 409
        hasNoTvShow = YES;
    }
    BOOL wasInserted = NO;
Pierre's avatar
Pierre committed
410 411 412 413
    MLShow *show = nil;
    MLShowEpisode *episode = [MLShowEpisode episodeWithShowName:tvShowName episodeNumber:episodeNumber seasonNumber:seasonNumber createIfNeeded:YES wasCreated:&wasInserted];
    if (episode)
        show = episode.show;
Pierre's avatar
Pierre committed
414 415 416 417 418 419 420 421 422 423 424 425
    if (wasInserted && !hasNoTvShow) {
        show.name = tvShowName;
        [self fetchMetaDataForShow:show];
    }

    if (hasNoTvShow)
        episode.name = file.title;
    file.seasonNumber = seasonNumber;
    file.episodeNumber = episodeNumber;
    episode.shouldBeDisplayed = [NSNumber numberWithBool:YES];

    [episode addFilesObject:file];
Pierre's avatar
Pierre committed
426
    file.showEpisode = episode;
Pierre's avatar
Pierre committed
427

Pierre's avatar
Pierre committed
428
    // The rest of the meta data will be fetched using the MLShow
Pierre's avatar
Pierre committed
429
    file.hasFetchedInfo = [NSNumber numberWithBool:YES];
Pierre's avatar
Pierre committed
430 431 432 433
}


/**
Pierre's avatar
Pierre committed
434
 * MLFile auto detection
Pierre's avatar
Pierre committed
435 436
 */

Pierre's avatar
Pierre committed
437

Pierre's avatar
Pierre committed
438
#if !HAVE_BLOCK
Pierre's avatar
Pierre committed
439 440 441 442 443 444
- (void)movieInfoGrabber:(MLMovieInfoGrabber *)grabber didFailWithError:(NSError *)error
{
    MLFile *file = grabber.userData;
    [self errorWhenFetchingMetaDataForFile:file];
}

Pierre's avatar
Pierre committed
445 446 447 448 449
- (void)movieInfoGrabberDidFinishGrabbing:(MLMovieInfoGrabber *)grabber
{
    NSNumber *yes = [NSNumber numberWithBool:YES];

    NSArray *results = grabber.results;
Pierre's avatar
Pierre committed
450
    MLFile *file = grabber.userData;
Pierre's avatar
Pierre committed
451 452 453 454 455 456 457
    if ([results count] > 0) {
        NSDictionary *result = [results objectAtIndex:0];
        file.artworkURL = [result objectForKey:@"artworkURL"];
        file.title = [result objectForKey:@"title"];
        file.shortSummary = [result objectForKey:@"shortSummary"];
        file.releaseYear = [result objectForKey:@"releaseYear"];
    }
Pierre's avatar
Pierre committed
458 459 460 461
    else {
        [self noMetaDataInRemoteDBForFile:file];
    }

Pierre's avatar
Pierre committed
462 463 464 465
    file.hasFetchedInfo = yes;
}
#endif

Pierre's avatar
Pierre committed
466
- (void)fetchMetaDataForFile:(MLFile *)file
Pierre's avatar
Pierre committed
467
{
Pierre's avatar
Pierre committed
468 469
    MLLog(@"Fetching meta data for %@", file.title);

470
    [[MLFileParserQueue sharedFileParserQueue] addFile:file];
471

Pierre's avatar
Pierre committed
472 473 474 475 476
    if (!_allowNetworkAccess) {
        // Automatically compute the thumbnail
        [self computeThumbnailForFile:file];
    }

Pierre's avatar
Pierre committed
477
    NSDictionary *tvShowEpisodeInfo = [MLTitleDecrapifier tvShowEpisodeInfoFromString:file.title];
Pierre's avatar
Pierre committed
478 479 480 481 482
    if (tvShowEpisodeInfo) {
        [self addTVShowEpisodeWithInfo:tvShowEpisodeInfo andFile:file];
        return;
    }

Pierre's avatar
Pierre committed
483 484 485
    if (!_allowNetworkAccess)
        return;

Pierre's avatar
Pierre committed
486
    // Go online and fetch info.
Pierre's avatar
Pierre committed
487 488 489

    // We don't care about keeping a reference to track the item during its life span
    // because we are a singleton
Pierre's avatar
Pierre committed
490
    MLMovieInfoGrabber *grabber = [[[MLMovieInfoGrabber alloc] init] autorelease];
Pierre's avatar
Pierre committed
491

Pierre's avatar
Pierre committed
492 493
    MLLog(@"Looking up for Movie '%@'", file.title);

Pierre's avatar
Pierre committed
494
#if HAVE_BLOCK
Pierre's avatar
Pierre committed
495
    [grabber lookUpForTitle:file.title andExecuteBlock:^(NSError *err){
Pierre's avatar
Pierre committed
496 497
        if (err) {
            [self errorWhenFetchingMetaDataForFile:file];
Pierre's avatar
Pierre committed
498
            return;
Pierre's avatar
Pierre committed
499
        }
Pierre's avatar
Pierre committed
500 501 502 503 504

        NSArray *results = grabber.results;
        if ([results count] > 0) {
            NSDictionary *result = [results objectAtIndex:0];
            file.artworkURL = [result objectForKey:@"artworkURL"];
Pierre's avatar
Pierre committed
505 506
            if (!file.artworkURL)
                [self computeThumbnailForFile:file];
Pierre's avatar
Pierre committed
507 508 509
            file.title = [result objectForKey:@"title"];
            file.shortSummary = [result objectForKey:@"shortSummary"];
            file.releaseYear = [result objectForKey:@"releaseYear"];
Pierre's avatar
Pierre committed
510 511 512
        } else
            [self noMetaDataInRemoteDBForFile:file];
        file.hasFetchedInfo = [NSNumber numberWithBool:YES];
Pierre's avatar
Pierre committed
513
    }];
Pierre's avatar
Pierre committed
514
#else
Pierre's avatar
Pierre committed
515 516 517
    grabber.userData = file;
    grabber.delegate = self;
    [grabber lookUpForTitle:file.title];
Pierre's avatar
Pierre committed
518
#endif
Pierre's avatar
Pierre committed
519 520
}

Pierre's avatar
Pierre committed
521 522 523
#pragma mark -
#pragma mark Adding file to the DB

Pierre's avatar
Pierre committed
524
- (void)addFilePath:(NSString *)filePath
Pierre's avatar
Pierre committed
525
{
Pierre's avatar
Pierre committed
526 527
    MLLog(@"Adding Path %@", filePath);

Pierre's avatar
Pierre committed
528
    NSURL *url = [NSURL fileURLWithPath:filePath];
Pierre's avatar
Pierre committed
529 530
    NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
    NSString *title = [filePath lastPathComponent];
Pierre's avatar
Pierre committed
531
#if !TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
532 533
    NSDate *openedDate = nil; // FIXME kMDItemLastUsedDate
    NSDate *modifiedDate = nil; // FIXME [result valueForAttribute:@"kMDItemFSContentChangeDate"];
Pierre's avatar
Pierre committed
534
#endif
Pierre's avatar
Pierre committed
535
    NSNumber *size = [attributes objectForKey:NSFileSize]; // FIXME [result valueForAttribute:@"kMDItemFSSize"];
Pierre's avatar
Pierre committed
536

Pierre's avatar
Pierre committed
537
    MLFile *file = [self createObjectForEntity:@"File"];
Pierre's avatar
Pierre committed
538
    file.url = [url absoluteString];
Pierre's avatar
Pierre committed
539 540 541 542 543 544 545 546 547 548 549 550

    // Yes, this is a negative number. VLCTime nicely display negative time
    // with "XX minutes remaining". And we are using this facility.

    NSNumber *no = [NSNumber numberWithBool:NO];
    NSNumber *yes = [NSNumber numberWithBool:YES];

    file.currentlyWatching = no;
    file.lastPosition = [NSNumber numberWithDouble:0];
    file.remainingTime = [NSNumber numberWithDouble:0];
    file.unread = yes;

Pierre's avatar
Pierre committed
551
#if !TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
552 553 554 555
    if ([openedDate isGreaterThan:modifiedDate]) {
        file.playCount = [NSNumber numberWithDouble:1];
        file.unread = no;
    }
Pierre's avatar
Pierre committed
556 557
#endif

Pierre's avatar
Pierre committed
558
    file.title = [MLTitleDecrapifier decrapify:[title stringByDeletingPathExtension]];
Pierre's avatar
Pierre committed
559 560

    if ([size longLongValue] < 150000000) /* 150 MB */
Pierre's avatar
Pierre committed
561
        file.type = kMLFileTypeClip;
Pierre's avatar
Pierre committed
562
    else
Pierre's avatar
Pierre committed
563
        file.type = kMLFileTypeMovie;
Pierre's avatar
Pierre committed
564 565 566 567

    [self fetchMetaDataForFile:file];
}

Pierre's avatar
Pierre committed
568
- (void)addFilePaths:(NSArray *)filepaths
Pierre's avatar
Pierre committed
569
{
Pierre's avatar
Pierre committed
570
    NSUInteger count = [filepaths count];
Pierre's avatar
Pierre committed
571 572 573 574
    NSMutableArray *fetchPredicates = [NSMutableArray arrayWithCapacity:count];
    NSMutableDictionary *urlToObject = [NSMutableDictionary dictionaryWithCapacity:count];

    // Prepare a fetch request for all items
Pierre's avatar
Pierre committed
575
    for (NSString *path in filepaths) {
Pierre's avatar
Pierre committed
576
        NSURL *url = [NSURL fileURLWithPath:path];
Pierre's avatar
Pierre committed
577 578 579 580
        NSString *urlString = [url absoluteString];
        [fetchPredicates addObject:[NSPredicate predicateWithFormat:@"url == %@", urlString]];
        [urlToObject setObject:path forKey:urlString];

Pierre's avatar
Pierre committed
581 582 583 584 585 586
    }

    NSFetchRequest *request = [self fetchRequestForEntity:@"File"];

    [request setPredicate:[NSCompoundPredicate orPredicateWithSubpredicates:fetchPredicates]];

Pierre's avatar
Pierre committed
587
    MLLog(@"Fetching");
Pierre's avatar
Pierre committed
588
    NSArray *dbResults = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
589
    MLLog(@"Done");
Pierre's avatar
Pierre committed
590

Pierre's avatar
Pierre committed
591
    NSMutableArray *filePathsToAdd = [NSMutableArray arrayWithArray:filepaths];
Pierre's avatar
Pierre committed
592

Pierre's avatar
Pierre committed
593

Pierre's avatar
Pierre committed
594
    // Remove objects that are already in db.
Pierre's avatar
Pierre committed
595 596
    for (MLFile *dbResult in dbResults) {
        NSString *urlString = dbResult.url;
Pierre's avatar
Pierre committed
597
        [filePathsToAdd removeObject:[urlToObject objectForKey:urlString]];
Pierre's avatar
Pierre committed
598 599 600
    }

    // Add only the newly added items
Pierre's avatar
Pierre committed
601 602
    for (NSString* path in filePathsToAdd)
        [self addFilePath:path];
Pierre's avatar
Pierre committed
603 604
}

Pierre's avatar
Pierre committed
605 606 607 608

#pragma mark -
#pragma mark DB Updates

Pierre's avatar
Pierre committed
609 610 611 612 613 614
#if !HAVE_BLOCK
- (void)tvShowInfoGrabber:(MLTVShowInfoGrabber *)grabber didFetchUpdates:(NSArray *)updates
{
    NSFetchRequest *request = [self fetchRequestForEntity:@"Show"];
    [request setPredicate:[NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"theTVDBID"] rightExpression:[NSExpression expressionForConstantValue:updates] modifier:NSDirectPredicateModifier type:NSInPredicateOperatorType options:0]];
    NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
615
    for (MLShow *show in results)
Pierre's avatar
Pierre committed
616 617 618 619
        [self fetchMetaDataForShow:show];
}
#endif

620
- (void)startUpdateDB
Pierre's avatar
Pierre committed
621
{
Pierre's avatar
Pierre committed
622
    // Remove no more present files
Pierre's avatar
Pierre committed
623 624
    NSFetchRequest *request = [self fetchRequestForEntity:@"File"];
    NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
625
    NSFileManager *fileManager = [NSFileManager defaultManager];
626

Pierre's avatar
Pierre committed
627 628 629
    for (MLFile *file in results) {
        NSString *urlString = [file url];
        NSURL *fileURL = [NSURL URLWithString:urlString];
630
        BOOL exists = [fileManager fileExistsAtPath:[fileURL path]];
631
        if (!exists) {
632
            NSLog(@"Marking - %@", [fileURL absoluteString]);
633 634
            file.isSafe = YES; // It doesn't exists, it's safe.
        }
Pierre's avatar
Pierre committed
635 636
        file.isOnDisk = [NSNumber numberWithBool:exists];
    }
Pierre's avatar
Pierre committed
637

638 639 640 641 642 643 644
    // Get the file to parse
    request = [self fetchRequestForEntity:@"File"];
    [request setPredicate:[NSPredicate predicateWithFormat:@"isOnDisk == YES && tracks.@count == 0"]];
    results = [[self managedObjectContext] executeFetchRequest:request error:nil];
    for (MLFile *file in results)
        [[MLFileParserQueue sharedFileParserQueue] addFile:file];

Pierre's avatar
Pierre committed
645 646 647
    if (!_allowNetworkAccess) {
        // Always attempt to fetch
        request = [self fetchRequestForEntity:@"File"];
Pierre's avatar
Pierre committed
648
        [request setPredicate:[NSPredicate predicateWithFormat:@"isOnDisk == YES"]];
Pierre's avatar
Pierre committed
649
        results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
650 651 652 653
        for (MLFile *file in results) {
            if (!file.computedThumbnail)
                [self computeThumbnailForFile:file];
        }
Pierre's avatar
Pierre committed
654 655 656
        return;
    }

Pierre's avatar
Pierre committed
657 658
    // Get the thumbnails to compute
    request = [self fetchRequestForEntity:@"File"];
Pierre's avatar
Pierre committed
659
    [request setPredicate:[NSPredicate predicateWithFormat:@"isOnDisk == YES && hasFetchedInfo == 1 && artworkURL == nil"]];
Pierre's avatar
Pierre committed
660 661
    results = [[self managedObjectContext] executeFetchRequest:request error:nil];
    for (MLFile *file in results)
Pierre's avatar
Pierre committed
662 663
        if (!file.computedThumbnail)
            [self computeThumbnailForFile:file];
Pierre's avatar
Pierre committed
664

Pierre's avatar
Pierre committed
665

Pierre's avatar
Pierre committed
666 667 668 669 670
    // Get to fetch meta data
    request = [self fetchRequestForEntity:@"File"];
    [request setPredicate:[NSPredicate predicateWithFormat:@"isOnDisk == YES && hasFetchedInfo == 0"]];
    results = [[self managedObjectContext] executeFetchRequest:request error:nil];
    for (MLFile *file in results)
Pierre's avatar
Pierre committed
671 672
        [self fetchMetaDataForFile:file];

Pierre's avatar
Pierre committed
673
    // Get to fetch show info
Pierre's avatar
Pierre committed
674 675 676
    request = [self fetchRequestForEntity:@"Show"];
    [request setPredicate:[NSPredicate predicateWithFormat:@"lastSyncDate == 0"]];
    results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
677
    for (MLShow *show in results)
Pierre's avatar
Pierre committed
678 679
        [self fetchMetaDataForShow:show];

Pierre's avatar
Pierre committed
680
    // Get updated TV Shows
Pierre's avatar
Pierre committed
681
    NSNumber *lastServerTime = [NSNumber numberWithInteger:[[NSUserDefaults standardUserDefaults] integerForKey:kLastTVDBUpdateServerTime]];
Pierre's avatar
Pierre committed
682
#if HAVE_BLOCK
Pierre's avatar
Pierre committed
683
    [MLTVShowInfoGrabber fetchUpdatesSinceServerTime:lastServerTime andExecuteBlock:^(NSArray *updates){
Pierre's avatar
Pierre committed
684 685 686
        NSFetchRequest *request = [self fetchRequestForEntity:@"Show"];
        [request setPredicate:[NSComparisonPredicate predicateWithLeftExpression:[NSExpression expressionForKeyPath:@"theTVDBID"] rightExpression:[NSExpression expressionForConstantValue:updates] modifier:NSDirectPredicateModifier type:NSInPredicateOperatorType options:0]];
        NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
687
        for (MLShow *show in results)
Pierre's avatar
Pierre committed
688 689
            [self fetchMetaDataForShow:show];
    }];
Pierre's avatar
Pierre committed
690 691 692 693
#else
    MLTVShowInfoGrabber *grabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
    grabber.delegate = self;
    [grabber fetchUpdatesSinceServerTime:lastServerTime];
Pierre's avatar
Pierre committed
694
#endif
Pierre's avatar
Pierre committed
695
    /* Update every hour - FIXME: Preferences key */
696
    [self performSelector:@selector(startUpdateDB) withObject:nil afterDelay:60 * 60];
Pierre's avatar
Pierre committed
697
}
Pierre's avatar
Pierre committed
698

699 700 701 702 703
- (void)applicationWillExit
{
    [[MLCrashPreventer sharedPreventer] cancelAllFileParse];
}

Pierre's avatar
Pierre committed
704 705 706 707 708
- (void)applicationWillStart
{
    [[MLCrashPreventer sharedPreventer] markCrasherFiles];
}

Pierre's avatar
Pierre committed
709 710 711 712 713 714 715 716 717 718 719
- (void)libraryDidDisappear
{
    // Stop expansive work
    [[MLThumbnailerQueue sharedThumbnailerQueue] stop];
}

- (void)libraryDidAppear
{
    // Resume our work
    [[MLThumbnailerQueue sharedThumbnailerQueue] resume];
}
Pierre's avatar
Pierre committed
720
@end