MLMediaLibrary.m 32.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/*****************************************************************************
 * MLMediaLibrary.m
 * MobileMediaLibraryKit
 *****************************************************************************
 * Copyright (C) 2010 Pierre d'Herbemont
 * Copyright (C) 2010-2013 VLC authors and VideoLAN
 * $Id$
 *
 * Authors: Pierre d'Herbemont <pdherbemont # videolan.org>
 *          Felix Paul Kühne <fkuehne # videolan.org>
 *
12 13 14
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
15 16 17 18
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
21
 *
22 23 24
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25
 *****************************************************************************/
Pierre's avatar
Pierre committed
26 27

#import "MLMediaLibrary.h"
Pierre's avatar
Pierre committed
28 29
#import "MLTitleDecrapifier.h"
#import "MLMovieInfoGrabber.h"
Pierre's avatar
Pierre committed
30 31
#import "MLTVShowInfoGrabber.h"
#import "MLTVShowEpisodesInfoGrabber.h"
Pierre's avatar
Pierre committed
32 33 34 35
#import "MLFile.h"
#import "MLLabel.h"
#import "MLShowEpisode.h"
#import "MLShow.h"
36 37 38
#import "MLThumbnailerQueue.h"
#import "MLAlbumTrack.h"
#import "MLAlbum.h"
39
#import "MLFileParserQueue.h"
40
#import "MLCrashPreventer.h"
Pierre's avatar
Pierre committed
41 42

#define DEBUG 1
43 44
// To debug
#define DELETE_LIBRARY_ON_EACH_LAUNCH 0
Pierre's avatar
Pierre committed
45

Pierre's avatar
Pierre committed
46 47
// Pref key
static NSString *kLastTVDBUpdateServerTime = @"MLLastTVDBUpdateServerTime";
48
static NSString *kUpdatedToTheGreatSharkHuntDatabaseFormat = @"upgradedToDatabaseFormat 2.3";
Pierre's avatar
Pierre committed
49

Pierre's avatar
Pierre committed
50
#if HAVE_BLOCK
Pierre's avatar
Pierre committed
51
@interface MLMediaLibrary ()
Pierre's avatar
Pierre committed
52 53 54
#else
@interface MLMediaLibrary () <MLMovieInfoGrabberDelegate, MLTVShowEpisodesInfoGrabberDelegate, MLTVShowInfoGrabberDelegate>
#endif
Pierre's avatar
Pierre committed
55
- (NSManagedObjectContext *)managedObjectContext;
Pierre's avatar
Pierre committed
56
- (NSString *)databaseFolderPath;
Pierre's avatar
Pierre committed
57 58
@end

Pierre's avatar
Pierre committed
59
@implementation MLMediaLibrary
60 61
+ (void)initialize
{
62
    [[NSUserDefaults standardUserDefaults] registerDefaults:@{kUpdatedToTheGreatSharkHuntDatabaseFormat : [NSNumber numberWithBool:NO]}];
63 64
}

Pierre's avatar
Pierre committed
65 66 67
+ (id)sharedMediaLibrary
{
    static id sharedMediaLibrary = nil;
Pierre's avatar
Pierre committed
68
    if (!sharedMediaLibrary) {
Pierre's avatar
Pierre committed
69
        sharedMediaLibrary = [[[self class] alloc] init];
70
        APLog(@"Initializing db in %@", [sharedMediaLibrary databaseFolderPath]);
71 72 73 74

        // 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
75
    }
Pierre's avatar
Pierre committed
76 77 78
    return sharedMediaLibrary;
}

79 80 81 82 83 84 85 86
- (void)dealloc
{
    if (_managedObjectContext)
        [_managedObjectContext removeObserver:self forKeyPath:@"hasChanges"];
    [_managedObjectContext release];
    [super dealloc];
}

Pierre's avatar
Pierre committed
87 88 89 90 91
- (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
92
    NSAssert(entityDescription, @"No entity");
Pierre's avatar
Pierre committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    [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
113
- (NSString *)databaseFolderPath
Pierre's avatar
Pierre committed
114
{
Pierre's avatar
Pierre committed
115 116
    int directory = NSLibraryDirectory;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
117
    NSString *directoryPath = paths[0];
118
#if DELETE_LIBRARY_ON_EACH_LAUNCH
Pierre's avatar
Pierre committed
119
    [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:nil];
120
#endif
Pierre's avatar
Pierre committed
121
    return directoryPath;
Pierre's avatar
Pierre committed
122 123
}

Pierre's avatar
Pierre committed
124 125 126 127 128

- (NSString *)thumbnailFolderPath
{
    int directory = NSLibraryDirectory;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES);
129
    NSString *directoryPath = paths[0];
Pierre's avatar
Pierre committed
130 131 132 133 134 135
#if DELETE_LIBRARY_ON_EACH_LAUNCH
    [[NSFileManager defaultManager] removeItemAtPath:directoryPath error:nil];
#endif
    return [directoryPath stringByAppendingPathComponent:@"Thumbnails"];
}

Pierre's avatar
Pierre committed
136 137 138 139 140
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext)
        return _managedObjectContext;

Pierre's avatar
Pierre committed
141
    NSString *databaseFolderPath = [self databaseFolderPath];
Pierre's avatar
Pierre committed
142

Pierre's avatar
Pierre committed
143 144
    NSString *path = [databaseFolderPath stringByAppendingPathComponent: @"MediaLibrary.sqlite"];
    NSURL *url = [NSURL fileURLWithPath:path];
Pierre's avatar
Pierre committed
145 146
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

147
    NSNumber *yes = @YES;
148 149
    NSDictionary *options = @{NSMigratePersistentStoresAutomaticallyOption : yes,
                             NSInferMappingModelAutomaticallyOption : yes};
Pierre's avatar
Pierre committed
150 151

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

    if (!persistentStore) {
Pierre's avatar
Pierre committed
155
#if! TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
156 157 158 159
        // 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
160
        [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
Pierre's avatar
Pierre committed
161
#else
Pierre's avatar
Pierre committed
162
        [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
Pierre's avatar
Pierre committed
163
#endif
Pierre's avatar
Pierre committed
164 165
        persistentStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:url options:options error:&error];
        if (!persistentStore) {
166 167
            if (coordinator)
                [coordinator release];
Pierre's avatar
Pierre committed
168 169 170 171 172
#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];
173
            [alert autorelease];
Pierre's avatar
Pierre committed
174 175 176 177
#endif
            // Probably assert instead.
            return nil;
        }
Pierre's avatar
Pierre committed
178
    }
Pierre's avatar
Pierre committed
179 180 181

    _managedObjectContext = [[NSManagedObjectContext alloc] init];
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];
Pierre's avatar
Pierre committed
182 183 184 185 186 187 188 189 190
    [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
191 192 193
    NSError *error = nil;
    BOOL success = [[self managedObjectContext] save:&error];
    NSAssert1(success, @"Can't save: %@", error);
Pierre's avatar
Pierre committed
194
#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
Pierre's avatar
Pierre committed
195 196 197 198 199 200
    NSProcessInfo *process = [NSProcessInfo processInfo];
    if ([process respondsToSelector:@selector(enableSuddenTermination)])
        [process enableSuddenTermination];
#endif
}

201 202 203 204 205 206 207
- (void)save
{
    NSError *error = nil;
    BOOL success = [[self managedObjectContext] save:&error];
    NSAssert1(success, @"Can't save: %@", error);
}

Pierre's avatar
Pierre committed
208
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
Pierre's avatar
Pierre committed
209 210
{
    if ([keyPath isEqualToString:@"hasChanges"] && object == _managedObjectContext) {
Pierre's avatar
Pierre committed
211
#if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
Pierre's avatar
Pierre committed
212 213 214 215 216
        NSProcessInfo *process = [NSProcessInfo processInfo];
        if ([process respondsToSelector:@selector(disableSuddenTermination)])
            [process disableSuddenTermination];
#endif

Pierre's avatar
Pierre committed
217 218 219 220
        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
221 222 223 224 225
        return;
    }
    [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

Pierre's avatar
Pierre committed
226 227
#pragma mark -
#pragma mark No meta data fallbacks
Pierre's avatar
Pierre committed
228

Pierre's avatar
Pierre committed
229
- (void)computeThumbnailForFile:(MLFile *)file
Pierre's avatar
Pierre committed
230
{
Pierre's avatar
Pierre committed
231
    if (!file.computedThumbnail) {
232
        APLog(@"Computing thumbnail for %@", file.title);
Pierre's avatar
Pierre committed
233 234
        [[MLThumbnailerQueue sharedThumbnailerQueue] addFile:file];
    }
Pierre's avatar
Pierre committed
235
}
Pierre's avatar
Pierre committed
236
- (void)errorWhenFetchingMetaDataForFile:(MLFile *)file
Pierre's avatar
Pierre committed
237
{
238
    APLog(@"Error when fetching for '%@'", file.title);
Pierre's avatar
Pierre committed
239

Pierre's avatar
Pierre committed
240 241
    [self computeThumbnailForFile:file];
}
Pierre's avatar
Pierre committed
242

Pierre's avatar
Pierre committed
243 244 245 246 247 248 249
- (void)errorWhenFetchingMetaDataForShow:(MLShow *)show
{
    for (MLShowEpisode *episode in show.episodes) {
        for (MLFile *file in episode.files)
            [self errorWhenFetchingMetaDataForFile:file];
    }
}
Pierre's avatar
Pierre committed
250

Pierre's avatar
Pierre committed
251 252
- (void)noMetaDataInRemoteDBForFile:(MLFile *)file
{
253
    file.noOnlineMetaData = @YES;
Pierre's avatar
Pierre committed
254
    [self computeThumbnailForFile:file];
Pierre's avatar
Pierre committed
255 256
}

Pierre's avatar
Pierre committed
257
- (void)noMetaDataInRemoteDBForShow:(MLShow *)show
Pierre's avatar
Pierre committed
258
{
Pierre's avatar
Pierre committed
259 260 261
    for (MLShowEpisode *episode in show.episodes) {
        for (MLFile *file in episode.files)
            [self noMetaDataInRemoteDBForFile:file];
Pierre's avatar
Pierre committed
262 263 264
    }
}

Pierre's avatar
Pierre committed
265 266 267 268
#pragma mark -
#pragma mark Getter

- (void)addNewLabelWithName:(NSString *)name
Pierre's avatar
Pierre committed
269
{
Pierre's avatar
Pierre committed
270 271
    MLLabel *label = [self createObjectForEntity:@"Label"];
    label.name = name;
Pierre's avatar
Pierre committed
272 273
}

Pierre's avatar
Pierre committed
274 275 276 277 278 279 280
/**
 * TV MLShow Episodes
 */

#pragma mark -
#pragma mark Online meta data grabbing

Pierre's avatar
Pierre committed
281 282 283
#if !HAVE_BLOCK
- (void)tvShowEpisodesInfoGrabberDidFinishGrabbing:(MLTVShowEpisodesInfoGrabber *)grabber
{
Pierre's avatar
Pierre committed
284
    MLShow *show = grabber.userData;
Pierre's avatar
Pierre committed
285 286

    NSArray *results = grabber.episodesResults;
287
    [show setValue:(grabber.results)[@"serieArtworkURL"] forKey:@"artworkURL"];
Pierre's avatar
Pierre committed
288
    for (id result in results) {
289
        if ([result[@"serie"] boolValue]) {
Pierre's avatar
Pierre committed
290 291
            continue;
        }
292 293 294 295 296
        MLShowEpisode *showEpisode = [MLShowEpisode episodeWithShow:show episodeNumber:result[@"episodeNumber"] seasonNumber:result[@"seasonNumber"] createIfNeeded:YES];
        showEpisode.name = result[@"title"];
        showEpisode.theTVDBID = result[@"id"];
        showEpisode.shortSummary = result[@"shortSummary"];
        showEpisode.artworkURL = result[@"artworkURL"];
Pierre's avatar
Pierre committed
297 298 299 300 301
        if (!showEpisode.artworkURL) {
            for (MLFile *file in showEpisode.files)
                [self computeThumbnailForFile:file];
        }

Pierre's avatar
Pierre committed
302 303 304 305 306
        showEpisode.lastSyncDate = [MLTVShowInfoGrabber serverTime];
    }
    show.lastSyncDate = [MLTVShowInfoGrabber serverTime];
}

Pierre's avatar
Pierre committed
307 308 309 310 311 312
- (void)tvShowEpisodesInfoGrabber:(MLTVShowEpisodesInfoGrabber *)grabber didFailWithError:(NSError *)error
{
    MLShow *show = grabber.userData;
    [self errorWhenFetchingMetaDataForShow:show];
}

Pierre's avatar
Pierre committed
313 314
- (void)tvShowInfoGrabberDidFinishGrabbing:(MLTVShowInfoGrabber *)grabber
{
Pierre's avatar
Pierre committed
315
    MLShow *show = grabber.userData;
Pierre's avatar
Pierre committed
316 317
    NSArray *results = grabber.results;
    if ([results count] > 0) {
318 319
        NSDictionary *result = results[0];
        NSString *showId = result[@"id"];
Pierre's avatar
Pierre committed
320 321

        show.theTVDBID = showId;
322 323 324
        show.name = result[@"title"];
        show.shortSummary = result[@"shortSummary"];
        show.releaseYear = result[@"releaseYear"];
Pierre's avatar
Pierre committed
325 326 327 328 329 330 331 332 333

        // 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
334
        [self noMetaDataInRemoteDBForShow:show];
Pierre's avatar
Pierre committed
335 336 337 338
        show.lastSyncDate = [MLTVShowInfoGrabber serverTime];
    }
}

Pierre's avatar
Pierre committed
339 340 341 342 343 344
- (void)tvShowInfoGrabber:(MLTVShowInfoGrabber *)grabber didFailWithError:(NSError *)error
{
    MLShow *show = grabber.userData;
    [self errorWhenFetchingMetaDataForShow:show];
}

Pierre's avatar
Pierre committed
345 346
- (void)tvShowInfoGrabberDidFetchServerTime:(MLTVShowInfoGrabber *)grabber
{
Pierre's avatar
Pierre committed
347
    MLShow *show = grabber.userData;
Pierre's avatar
Pierre committed
348 349 350

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

Pierre's avatar
Pierre committed
351
    // First fetch the MLShow ID
Pierre's avatar
Pierre committed
352 353 354 355
    MLTVShowInfoGrabber *showInfoGrabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
    showInfoGrabber.delegate = self;
    showInfoGrabber.userData = show;

356
    APLog(@"Fetching show information on %@", show.name);
Pierre's avatar
Pierre committed
357

Pierre's avatar
Pierre committed
358 359 360 361
    [showInfoGrabber lookUpForTitle:show.name];
}
#endif

Pierre's avatar
Pierre committed
362
- (void)fetchMetaDataForShow:(MLShow *)show
Pierre's avatar
Pierre committed
363
{
Pierre's avatar
Pierre committed
364 365
    if (!_allowNetworkAccess)
        return;
366
    APLog(@"Fetching show server time");
Pierre's avatar
Pierre committed
367

Pierre's avatar
Pierre committed
368
    // First fetch the serverTime, so that we can update each entry.
Pierre's avatar
Pierre committed
369 370
#if HAVE_BLOCK
    [MLTVShowInfoGrabber fetchServerTimeAndExecuteBlock:^(NSNumber *serverDate) {
Pierre's avatar
Pierre committed
371 372 373

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

374
        APLog(@"Fetching show information on %@", show.name);
Pierre's avatar
Pierre committed
375 376 377

        // First fetch the MLShow ID
        MLTVShowInfoGrabber *grabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
Pierre's avatar
Pierre committed
378 379 380 381 382 383 384 385 386 387 388
        [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"];

389
                APLog(@"Fetching show episode information on %@", showId);
Pierre's avatar
Pierre committed
390

Pierre's avatar
Pierre committed
391
                // Fetch episode info
Pierre's avatar
Pierre committed
392
                MLTVShowEpisodesInfoGrabber *grabber = [[[MLTVShowEpisodesInfoGrabber alloc] init] autorelease];
Pierre's avatar
Pierre committed
393 394 395 396 397 398 399
                [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;
                        }
400
                        MLShowEpisode *showEpisode = [MLShowEpisode episodeWithShow:show episodeNumber:[result objectForKey:@"episodeNumber"] seasonNumber:[result objectForKey:@"seasonNumber"] createIfNeeded:YES];
Pierre's avatar
Pierre committed
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
                        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
417
#else
Pierre's avatar
Pierre committed
418 419 420 421
    MLTVShowInfoGrabber *grabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
    grabber.delegate = self;
    grabber.userData = show;
    [grabber fetchServerTime];
Pierre's avatar
Pierre committed
422
#endif
Pierre's avatar
Pierre committed
423 424
}

Pierre's avatar
Pierre committed
425
- (void)addTVShowEpisodeWithInfo:(NSDictionary *)tvShowEpisodeInfo andFile:(MLFile *)file
Pierre's avatar
Pierre committed
426
{
Pierre's avatar
Pierre committed
427
    file.type = kMLFileTypeTVShowEpisode;
Pierre's avatar
Pierre committed
428

429 430 431
    NSNumber *seasonNumber = tvShowEpisodeInfo[@"season"];
    NSNumber *episodeNumber = tvShowEpisodeInfo[@"episode"];
    NSString *tvShowName = tvShowEpisodeInfo[@"tvShowName"];
432
    NSString *tvEpisodeName = tvShowEpisodeInfo[@"tvEpisodeName"];
Pierre's avatar
Pierre committed
433 434
    BOOL hasNoTvShow = NO;
    if (!tvShowName) {
435
        tvShowName = @"";
Pierre's avatar
Pierre committed
436 437 438
        hasNoTvShow = YES;
    }
    BOOL wasInserted = NO;
Pierre's avatar
Pierre committed
439 440 441 442
    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
443 444 445 446
    if (wasInserted && !hasNoTvShow) {
        show.name = tvShowName;
        [self fetchMetaDataForShow:show];
    }
447
    episode.name = tvEpisodeName;
Pierre's avatar
Pierre committed
448

Felix Paul Kühne's avatar
Felix Paul Kühne committed
449
    if (episode.name.length < 1)
Pierre's avatar
Pierre committed
450 451 452
        episode.name = file.title;
    file.seasonNumber = seasonNumber;
    file.episodeNumber = episodeNumber;
453
    episode.shouldBeDisplayed = @YES;
Pierre's avatar
Pierre committed
454 455

    [episode addFilesObject:file];
Pierre's avatar
Pierre committed
456
    file.showEpisode = episode;
Pierre's avatar
Pierre committed
457

Pierre's avatar
Pierre committed
458
    // The rest of the meta data will be fetched using the MLShow
459
    file.hasFetchedInfo = @YES;
Pierre's avatar
Pierre committed
460 461
}

462 463 464 465
- (void)addAudioContentWithInfo:(NSDictionary *)audioContentInfo andFile:(MLFile *)file
{
    file.type = kMLFileTypeAudio;

466 467 468
    file.title = audioContentInfo[VLCMetaInformationTitle];

    /* all further meta data is set by the FileParserQueue */
469 470 471 472

    file.hasFetchedInfo = @YES;
}

Pierre's avatar
Pierre committed
473
/**
Pierre's avatar
Pierre committed
474
 * MLFile auto detection
Pierre's avatar
Pierre committed
475 476
 */

Pierre's avatar
Pierre committed
477
#if !HAVE_BLOCK
Pierre's avatar
Pierre committed
478 479 480 481 482 483
- (void)movieInfoGrabber:(MLMovieInfoGrabber *)grabber didFailWithError:(NSError *)error
{
    MLFile *file = grabber.userData;
    [self errorWhenFetchingMetaDataForFile:file];
}

Pierre's avatar
Pierre committed
484 485
- (void)movieInfoGrabberDidFinishGrabbing:(MLMovieInfoGrabber *)grabber
{
486
    NSNumber *yes = @YES;
Pierre's avatar
Pierre committed
487 488

    NSArray *results = grabber.results;
Pierre's avatar
Pierre committed
489
    MLFile *file = grabber.userData;
Pierre's avatar
Pierre committed
490
    if ([results count] > 0) {
491 492 493 494 495
        NSDictionary *result = results[0];
        file.artworkURL = result[@"artworkURL"];
        file.title = result[@"title"];
        file.shortSummary = result[@"shortSummary"];
        file.releaseYear = result[@"releaseYear"];
Pierre's avatar
Pierre committed
496
    }
Pierre's avatar
Pierre committed
497 498 499 500
    else {
        [self noMetaDataInRemoteDBForFile:file];
    }

Pierre's avatar
Pierre committed
501 502 503 504
    file.hasFetchedInfo = yes;
}
#endif

Pierre's avatar
Pierre committed
505
- (void)fetchMetaDataForFile:(MLFile *)file
Pierre's avatar
Pierre committed
506
{
507
    APLog(@"Fetching meta data for %@", file.title);
Pierre's avatar
Pierre committed
508

509
    [[MLFileParserQueue sharedFileParserQueue] addFile:file];
510

Pierre's avatar
Pierre committed
511 512 513 514 515
    if (!_allowNetworkAccess) {
        // Automatically compute the thumbnail
        [self computeThumbnailForFile:file];
    }

Pierre's avatar
Pierre committed
516
    NSDictionary *tvShowEpisodeInfo = [MLTitleDecrapifier tvShowEpisodeInfoFromString:file.title];
Pierre's avatar
Pierre committed
517 518 519 520 521
    if (tvShowEpisodeInfo) {
        [self addTVShowEpisodeWithInfo:tvShowEpisodeInfo andFile:file];
        return;
    }

522 523 524 525 526 527 528 529
    if ([file isSupportedAudioFile]) {
        NSDictionary *audioContentInfo = [MLTitleDecrapifier audioContentInfoFromFile:file];
        if (audioContentInfo && ![file videoTrack]) {
            [self addAudioContentWithInfo:audioContentInfo andFile:file];
            return;
        }
    }

Pierre's avatar
Pierre committed
530 531 532
    if (!_allowNetworkAccess)
        return;

Pierre's avatar
Pierre committed
533
    // Go online and fetch info.
Pierre's avatar
Pierre committed
534 535 536

    // 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
537
    MLMovieInfoGrabber *grabber = [[[MLMovieInfoGrabber alloc] init] autorelease];
Pierre's avatar
Pierre committed
538

539
    APLog(@"Looking up for Movie '%@'", file.title);
Pierre's avatar
Pierre committed
540

Pierre's avatar
Pierre committed
541
#if HAVE_BLOCK
Pierre's avatar
Pierre committed
542
    [grabber lookUpForTitle:file.title andExecuteBlock:^(NSError *err){
Pierre's avatar
Pierre committed
543 544
        if (err) {
            [self errorWhenFetchingMetaDataForFile:file];
Pierre's avatar
Pierre committed
545
            return;
Pierre's avatar
Pierre committed
546
        }
Pierre's avatar
Pierre committed
547 548 549 550 551

        NSArray *results = grabber.results;
        if ([results count] > 0) {
            NSDictionary *result = [results objectAtIndex:0];
            file.artworkURL = [result objectForKey:@"artworkURL"];
Pierre's avatar
Pierre committed
552 553
            if (!file.artworkURL)
                [self computeThumbnailForFile:file];
Pierre's avatar
Pierre committed
554 555 556
            file.title = [result objectForKey:@"title"];
            file.shortSummary = [result objectForKey:@"shortSummary"];
            file.releaseYear = [result objectForKey:@"releaseYear"];
Pierre's avatar
Pierre committed
557 558 559
        } else
            [self noMetaDataInRemoteDBForFile:file];
        file.hasFetchedInfo = [NSNumber numberWithBool:YES];
Pierre's avatar
Pierre committed
560
    }];
Pierre's avatar
Pierre committed
561
#else
Pierre's avatar
Pierre committed
562 563 564
    grabber.userData = file;
    grabber.delegate = self;
    [grabber lookUpForTitle:file.title];
Pierre's avatar
Pierre committed
565
#endif
Pierre's avatar
Pierre committed
566 567
}

Pierre's avatar
Pierre committed
568 569 570
#pragma mark -
#pragma mark Adding file to the DB

Pierre's avatar
Pierre committed
571
- (void)addFilePath:(NSString *)filePath
Pierre's avatar
Pierre committed
572
{
573
    APLog(@"Adding Path %@", filePath);
Pierre's avatar
Pierre committed
574

Pierre's avatar
Pierre committed
575
    NSURL *url = [NSURL fileURLWithPath:filePath];
Pierre's avatar
Pierre committed
576 577
    NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
    NSString *title = [filePath lastPathComponent];
Pierre's avatar
Pierre committed
578
#if !TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
579 580
    NSDate *openedDate = nil; // FIXME kMDItemLastUsedDate
    NSDate *modifiedDate = nil; // FIXME [result valueForAttribute:@"kMDItemFSContentChangeDate"];
Pierre's avatar
Pierre committed
581
#endif
582
    NSNumber *size = attributes[NSFileSize]; // FIXME [result valueForAttribute:@"kMDItemFSSize"];
Pierre's avatar
Pierre committed
583

Pierre's avatar
Pierre committed
584
    MLFile *file = [self createObjectForEntity:@"File"];
Pierre's avatar
Pierre committed
585
    file.url = [url absoluteString];
Pierre's avatar
Pierre committed
586 587 588 589

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

590 591
    NSNumber *no = @NO;
    NSNumber *yes = @YES;
Pierre's avatar
Pierre committed
592 593

    file.currentlyWatching = no;
594 595
    file.lastPosition = @0.0;
    file.remainingTime = @0.0;
Pierre's avatar
Pierre committed
596 597
    file.unread = yes;

Pierre's avatar
Pierre committed
598
#if !TARGET_OS_IPHONE
Pierre's avatar
Pierre committed
599 600 601 602
    if ([openedDate isGreaterThan:modifiedDate]) {
        file.playCount = [NSNumber numberWithDouble:1];
        file.unread = no;
    }
Pierre's avatar
Pierre committed
603 604
#endif

Pierre's avatar
Pierre committed
605
    file.title = [MLTitleDecrapifier decrapify:[title stringByDeletingPathExtension]];
Pierre's avatar
Pierre committed
606 607

    if ([size longLongValue] < 150000000) /* 150 MB */
Pierre's avatar
Pierre committed
608
        file.type = kMLFileTypeClip;
Pierre's avatar
Pierre committed
609
    else
Pierre's avatar
Pierre committed
610
        file.type = kMLFileTypeMovie;
Pierre's avatar
Pierre committed
611 612 613 614

    [self fetchMetaDataForFile:file];
}

Pierre's avatar
Pierre committed
615
- (void)addFilePaths:(NSArray *)filepaths
Pierre's avatar
Pierre committed
616
{
Pierre's avatar
Pierre committed
617
    NSUInteger count = [filepaths count];
Pierre's avatar
Pierre committed
618 619 620 621
    NSMutableArray *fetchPredicates = [NSMutableArray arrayWithCapacity:count];
    NSMutableDictionary *urlToObject = [NSMutableDictionary dictionaryWithCapacity:count];

    // Prepare a fetch request for all items
Pierre's avatar
Pierre committed
622
    for (NSString *path in filepaths) {
Pierre's avatar
Pierre committed
623
        NSURL *url = [NSURL fileURLWithPath:path];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
624
        NSString *urlString = [url absoluteString];
Pierre's avatar
Pierre committed
625
        [fetchPredicates addObject:[NSPredicate predicateWithFormat:@"url == %@", urlString]];
626
        urlToObject[urlString] = path;
Pierre's avatar
Pierre committed
627 628 629 630 631 632
    }

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

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

633
    APLog(@"Fetching");
Pierre's avatar
Pierre committed
634
    NSArray *dbResults = [[self managedObjectContext] executeFetchRequest:request error:nil];
635
    APLog(@"Done");
Pierre's avatar
Pierre committed
636

Pierre's avatar
Pierre committed
637
    NSMutableArray *filePathsToAdd = [NSMutableArray arrayWithArray:filepaths];
Pierre's avatar
Pierre committed
638 639

    // Remove objects that are already in db.
Pierre's avatar
Pierre committed
640 641
    for (MLFile *dbResult in dbResults) {
        NSString *urlString = dbResult.url;
642
        [filePathsToAdd removeObject:urlToObject[urlString]];
Pierre's avatar
Pierre committed
643 644 645
    }

    // Add only the newly added items
Pierre's avatar
Pierre committed
646 647
    for (NSString* path in filePathsToAdd)
        [self addFilePath:path];
Pierre's avatar
Pierre committed
648 649
}

Pierre's avatar
Pierre committed
650 651 652 653

#pragma mark -
#pragma mark DB Updates

Pierre's avatar
Pierre committed
654 655 656 657 658 659
#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
660
    for (MLShow *show in results)
Pierre's avatar
Pierre committed
661 662 663 664
        [self fetchMetaDataForShow:show];
}
#endif

665 666
- (BOOL)libraryNeedsUpgrade
{
667
    if (![[[NSUserDefaults standardUserDefaults] objectForKey:kUpdatedToTheGreatSharkHuntDatabaseFormat] boolValue])
668 669 670 671 672 673
        return YES;
    return NO;
}

- (void)upgradeLibrary
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
674
    [self libraryDidDisappear];
675 676 677
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSFileManager *fileManager = [NSFileManager defaultManager];

678
    /* remove potential empty albums left over by previous releases */
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705
    NSArray *collection = [MLAlbum allAlbums];
    NSUInteger count = collection.count;
    MLAlbum *album;
    MLAlbumTrack *track;
    NSArray *secondaryCollection;
    NSURL *fileURL;
    NSUInteger secondaryCount = 0;
    NSArray *tertiaryCollection;
    NSUInteger tertiaryCount = 0;
    NSUInteger emptyAlbumCounter = 0;
    for (NSUInteger x = 0; x < count; x++) {
        album = collection[x];
        if (album.tracks.count < 1)
            [[self managedObjectContext] deleteObject:album];
        else {
            secondaryCollection = album.tracks.allObjects;
            secondaryCount = secondaryCollection.count;
            emptyAlbumCounter = 0;
            for (NSUInteger y = 0; y < secondaryCount; y++) {
                track = secondaryCollection[y];
                tertiaryCollection = track.files.allObjects;
                tertiaryCount = tertiaryCollection.count;
                for (NSUInteger z = 0; z < tertiaryCount; z++) {
                    fileURL = [NSURL URLWithString:[(MLFile *)tertiaryCollection[z] url]];
                    BOOL exists = [fileManager fileExistsAtPath:[fileURL path]];
                    if (exists)
                        emptyAlbumCounter++;
706 707
                    else
                        [album removeTrack:track];
708 709 710 711 712 713 714
                }
            }
            if (emptyAlbumCounter == 0)
                [[self managedObjectContext] deleteObject:album];
        }
    }
    album = nil;
715 716

    /* remove potential empty shows left over by previous releases */
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
    collection = [MLShow allShows];
    MLShow *show;
    MLShowEpisode *showEpisode;
    count = collection.count;
    for (NSUInteger x = 0; x < count; x++) {
        show = collection[x];
        if (show.episodes.count < 1)
            [[self managedObjectContext] deleteObject:show];
        else {
            secondaryCollection = show.episodes.allObjects;
            secondaryCount = secondaryCollection.count;
            emptyAlbumCounter = 0;
            for (NSUInteger y = 0; y < secondaryCount; y++) {
                showEpisode = secondaryCollection[y];
                tertiaryCollection = showEpisode.files.allObjects;
                tertiaryCount = tertiaryCollection.count;
                for (NSUInteger z = 0; z < tertiaryCount; z++) {
                    fileURL = [NSURL URLWithString:[(MLFile *)tertiaryCollection[z] url]];
                    BOOL exists = [fileManager fileExistsAtPath:[fileURL path]];
                    if (exists)
                        emptyAlbumCounter++;
738 739
                    else
                        [show removeEpisode:showEpisode];
740 741 742 743 744 745 746
                }
            }
            if (emptyAlbumCounter == 0)
                [[self managedObjectContext] deleteObject:show];
        }
    }

747 748 749 750 751
    /* remove duplicates */
    NSArray *allFiles = [MLFile allFiles];
    NSUInteger allFilesCount = allFiles.count;
    NSMutableArray *seenFiles = [[NSMutableArray alloc] initWithCapacity:allFilesCount];
    MLFile *currentFile;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
752 753
    NSString *currentFilePath;
    for (NSUInteger x = 0; x < allFilesCount; x++) {
754
        currentFile = allFiles[x];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
755 756
        currentFilePath = [currentFile.url stringByReplacingOccurrencesOfString:@"/localhost/" withString:@"//"];
        if ([seenFiles containsObject:currentFilePath])
757 758
            [[self managedObjectContext] deleteObject:currentFile];
        else
Felix Paul Kühne's avatar
Felix Paul Kühne committed
759
            [seenFiles addObject:currentFilePath];
760
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
761
    [seenFiles release];
762

763
    [defaults setBool:YES forKey:kUpdatedToTheGreatSharkHuntDatabaseFormat];
Felix Paul Kühne's avatar
Felix Paul Kühne committed
764 765 766
    [defaults synchronize];

    [self libraryDidAppear];
767 768 769 770
    if ([self.delegate respondsToSelector:@selector(libraryUpgradeComplete)])
        [self.delegate libraryUpgradeComplete];
}

771
- (void)updateMediaDatabase
Pierre's avatar
Pierre committed
772
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
773
    [self libraryDidDisappear];
Pierre's avatar
Pierre committed
774
    // Remove no more present files
Pierre's avatar
Pierre committed
775 776
    NSFetchRequest *request = [self fetchRequestForEntity:@"File"];
    NSArray *results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
777
    NSFileManager *fileManager = [NSFileManager defaultManager];
778

779 780 781
    unsigned int count = results.count;
    for (unsigned int x = 0; x < count; x++) {
        MLFile *file = results[x];
Pierre's avatar
Pierre committed
782 783
        NSString *urlString = [file url];
        NSURL *fileURL = [NSURL URLWithString:urlString];
784
        BOOL exists = [fileManager fileExistsAtPath:[fileURL path]];
785
        if (!exists) {
786
            APLog(@"Marking - %@", [fileURL absoluteString]);
787
            file.isSafe = YES; // It doesn't exists, it's safe.
788 789
            if (file.isAlbumTrack) {
                MLAlbum *album = file.albumTrack.album;
790 791 792 793 794 795 796
                if (album.tracks.count <= 1) {
                    @try {
                        [[self managedObjectContext] deleteObject:album];
                    }
                    @catch (NSException *exception) {
                        APLog(@"failed to nuke object because it disappeared in front of us");
                    }
797
                } else
798
                    [album removeTrack:file.albumTrack];
799 800 801
            }
            if (file.isShowEpisode) {
                MLShow *show = file.showEpisode.show;
802 803 804 805 806 807 808
                if (show.episodes.count <= 1) {
                    @try {
                        [[self managedObjectContext] deleteObject:show];
                    }
                    @catch (NSException *exception) {
                        APLog(@"failed to nuke object because it disappeared in front of us");
                    }
809 810
                } else
                    [show removeEpisode:file.showEpisode];
811
            }
812
#if TARGET_OS_IPHONE
813
            NSString *thumbPath = [[[self thumbnailFolderPath] stringByAppendingPathComponent:[[file.objectID URIRepresentation] path]] stringByAppendingString:@".png"];
814 815
            bool thumbExists = [fileManager fileExistsAtPath:thumbPath];
            if (thumbExists)
816
                [fileManager removeItemAtPath:thumbPath error:nil];
817 818
            [[self managedObjectContext] deleteObject:file];
#endif
819
        }
820 821 822
#if !TARGET_OS_IPHONE
    file.isOnDisk = @(exists);
#endif
Pierre's avatar
Pierre committed
823
    }
Felix Paul Kühne's avatar
Felix Paul Kühne committed
824
    [self libraryDidAppear];
Pierre's avatar
Pierre committed
825

826 827 828 829 830 831 832
    // 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
833 834 835
    if (!_allowNetworkAccess) {
        // Always attempt to fetch
        request = [self fetchRequestForEntity:@"File"];
Pierre's avatar
Pierre committed
836
        [request setPredicate:[NSPredicate predicateWithFormat:@"isOnDisk == YES"]];
Pierre's avatar
Pierre committed
837
        results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
838 839 840 841
        for (MLFile *file in results) {
            if (!file.computedThumbnail)
                [self computeThumbnailForFile:file];
        }
Pierre's avatar
Pierre committed
842 843 844
        return;
    }

Pierre's avatar
Pierre committed
845 846
    // Get the thumbnails to compute
    request = [self fetchRequestForEntity:@"File"];
Pierre's avatar
Pierre committed
847
    [request setPredicate:[NSPredicate predicateWithFormat:@"isOnDisk == YES && hasFetchedInfo == 1 && artworkURL == nil"]];
Pierre's avatar
Pierre committed
848 849
    results = [[self managedObjectContext] executeFetchRequest:request error:nil];
    for (MLFile *file in results)
850
        if (!file.computedThumbnail && ![file isAlbumTrack])
Pierre's avatar
Pierre committed
851
            [self computeThumbnailForFile:file];
Pierre's avatar
Pierre committed
852 853 854 855 856 857

    // 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
858 859
        [self fetchMetaDataForFile:file];

Pierre's avatar
Pierre committed
860
    // Get to fetch show info
Pierre's avatar
Pierre committed
861 862 863
    request = [self fetchRequestForEntity:@"Show"];
    [request setPredicate:[NSPredicate predicateWithFormat:@"lastSyncDate == 0"]];
    results = [[self managedObjectContext] executeFetchRequest:request error:nil];
Pierre's avatar
Pierre committed
864
    for (MLShow *show in results)
Pierre's avatar
Pierre committed
865 866
        [self fetchMetaDataForShow:show];

Pierre's avatar
Pierre committed
867
    // Get updated TV Shows
868
    NSNumber *lastServerTime = @([[NSUserDefaults standardUserDefaults] integerForKey:kLastTVDBUpdateServerTime]);
Pierre's avatar
Pierre committed
869
#if HAVE_BLOCK
Pierre's avatar
Pierre committed
870
    [MLTVShowInfoGrabber fetchUpdatesSinceServerTime:lastServerTime andExecuteBlock:^(NSArray *updates){
Pierre's avatar
Pierre committed
871 872 873
        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
874
        for (MLShow *show in results)
Pierre's avatar
Pierre committed
875 876
            [self fetchMetaDataForShow:show];
    }];
Pierre's avatar
Pierre committed
877 878 879 880
#else
    MLTVShowInfoGrabber *grabber = [[[MLTVShowInfoGrabber alloc] init] autorelease];
    grabber.delegate = self;
    [grabber fetchUpdatesSinceServerTime:lastServerTime];
Pierre's avatar
Pierre committed
881
#endif
Pierre's avatar
Pierre committed
882
    /* Update every hour - FIXME: Preferences key */
Felix Paul Kühne's avatar
Felix Paul Kühne committed
883
    [self performSelector:@selector(updateMediaDatabase) withObject:nil afterDelay:60 * 60];
Pierre's avatar
Pierre committed
884
}
Pierre's avatar
Pierre committed
885

886 887 888 889 890
- (void)applicationWillExit
{
    [[MLCrashPreventer sharedPreventer] cancelAllFileParse];
}

Pierre's avatar
Pierre committed
891 892 893 894 895
- (void)applicationWillStart
{
    [[MLCrashPreventer sharedPreventer] markCrasherFiles];
}

Pierre's avatar
Pierre committed
896 897 898 899 900 901 902 903 904 905 906
- (void)libraryDidDisappear
{
    // Stop expansive work
    [[MLThumbnailerQueue sharedThumbnailerQueue] stop];
}

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