root/branches/CoreData/SapphireFrappliance/MetaData/Support/SapphireMetaDataSupport.m @ 839

Revision 839, 24.8 KB (checked in by gbooker, 16 months ago)

Put an autorelease pool around this function since it may be called from a thread, otherwise it cleans up memory earlier.

Line 
1/*
2 * SapphireMetaDataSupport.m
3 * Sapphire
4 *
5 * Created by Graham Booker on Apr. 16, 2008.
6 * Copyright 2008 Sapphire Development Team and/or www.nanopi.net
7 * All rights reserved.
8 *
9 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10 * General Public License as published by the Free Software Foundation; either version 3 of the License,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
14 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
15 * Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with this program; if not,
18 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 */
20
21#import "SapphireMetaDataSupport.h"
22#import "SapphireDirectoryMetaData.h"
23#import "SapphireFileMetaData.h"
24#import "SapphireJoinedFile.h"
25#import "SapphireCollectionDirectory.h"
26#import "SapphireMovie.h"
27#import "SapphireCast.h"
28#import "SapphireMovieTranslation.h"
29#import "SapphireMoviePoster.h"
30#import "SapphireTVTranslation.h"
31#import "SapphireTVShow.h"
32#import "SapphireMetaDataUpgrading.h"
33#import "SapphireDirector.h"
34#import "SapphireGenre.h"
35#import "SapphireDirectorySymLink.h"
36#import "SapphireFileSymLink.h"
37#import "SapphireEpisode.h"
38#import "SapphireXMLData.h"
39#import "SapphireApplianceController.h"
40#import "SapphireSeason.h"
41#import "CoreDataSupportFunctions.h"
42
43@interface NSManagedObject (ChangePersistence)
44- (NSDictionary *)changedValuesWithObjectIDs;
45- (void)updateChanges:(NSDictionary *)changes withTrans:(NSDictionary *)translation;
46@end
47
48@implementation NSManagedObject (ChangePersistence)
49- (NSDictionary *)changedValuesWithObjectIDs
50{
51        NSDictionary *changedValues = [self changedValues];
52        NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithDictionary:changedValues];
53        NSString *key;
54        NSEnumerator *keyEnum = [changedValues keyEnumerator];
55        while((key = [keyEnum nextObject]) != nil)
56        {
57                id value = [ret objectForKey:key];
58                if([value isKindOfClass:[NSManagedObject class]])
59                        [ret setObject:[[(NSManagedObject *)value objectID] URIRepresentation] forKey:key];
60                else if([value isKindOfClass:[NSSet class]] && [[(NSSet *)value anyObject] isKindOfClass:[NSManagedObject class]])
61                        [ret setObject:[value valueForKeyPath:@"objectID.URIRepresentation"] forKey:key];
62        }
63        return [ret autorelease];
64}
65
66- (void)updateChanges:(NSDictionary *)changes withTrans:(NSDictionary *)translation
67{
68        NSEnumerator *keyEnum = [changes keyEnumerator];
69        NSString *key;
70        while((key = [keyEnum nextObject]) != nil)
71        {
72                id newValue = [changes objectForKey:key];
73                id testValue = newValue;
74                BOOL isSet = NO;
75                if([newValue isKindOfClass:[NSSet class]])
76                {
77                        testValue = [newValue anyObject];
78                        isSet = YES;
79                }
80                NSManagedObjectContext *moc = [self managedObjectContext];
81                if([testValue isKindOfClass:[NSURL class]] && [[(NSURL *)testValue scheme] hasPrefix:@"x-core"])
82                {
83                        NSPersistentStoreCoordinator *coord = [moc persistentStoreCoordinator];
84                        NSManagedObject *newObj;
85                        if(isSet)
86                        {
87                                NSSet *objSet = (NSSet *)newValue;
88                                NSEnumerator *objEnum = [objSet objectEnumerator];
89                                NSMutableSet *newSet = [[NSMutableSet alloc] init];
90                                NSURL *url;
91                                while((url = [objEnum nextObject]) != nil)
92                                {
93                                        newObj = [translation objectForKey:url];
94                                        if(newObj == nil)
95                                        {
96                                                NSManagedObjectID *upObjId = [coord managedObjectIDForURIRepresentation:url];
97                                                newObj = [moc objectWithID:upObjId];
98                                        }
99                                        [newSet addObject:newObj];
100                                }
101                                [self setValue:newSet forKey:key];
102                                [newSet release];
103                        }
104                        else
105                        {
106                                newObj = [translation objectForKey:newValue];
107                                if(newObj == nil)
108                                {
109                                        NSManagedObjectID *upObjId = [coord managedObjectIDForURIRepresentation:newValue];
110                                        newObj = [moc objectWithID:upObjId];
111                                }
112                                [self setValue:newObj forKey:key];
113                        }
114                }
115                else if(![newValue isKindOfClass:[NSNull class]])
116                        [self setValue:newValue forKey:key];
117                else
118                        [self setValue:nil forKey:key];
119        }
120}
121
122@end
123
124#define META_VERSION_KEY                        @"Version"
125
126/* Movie Translations */
127#define MOVIE_TRAN_VERSION_KEY                                  @"Version"
128#define MOVIE_TRAN_CURRENT_VERSION                              2
129/* Translation Keys */
130#define MOVIE_TRAN_TRANSLATIONS_KEY                             @"Translations"
131#define MOVIE_TRAN_IMDB_LINK_KEY                                @"IMDB Link"
132#define MOVIE_TRAN_IMP_LINK_KEY                                 @"IMP Link"
133#define MOVIE_TRAN_IMP_POSTERS_KEY                              @"IMP Posters"
134#define MOVIE_TRAN_SELECTED_POSTER_KEY                  @"Selected Poster"
135#define MOVIE_TRAN_AUTO_SELECT_POSTER_KEY               @"Default Poster"
136
137#define CHANGES_INSERTED_KEY                                    @"Inserted"
138#define CHANGES_UPDATED_KEY                                             @"Updated"
139#define CHANGES_DELETED_KEY                                             @"Deleted"
140
141static NSSet *coverArtExtentions = nil;
142
143NSString *searchCoverArtExtForPath(NSString *path)
144{
145        NSFileManager *fm = [NSFileManager defaultManager];
146        NSString *directory = [path stringByDeletingLastPathComponent];
147        NSArray *files = [fm directoryContentsAtPath:directory];
148        NSString *lastComp = [path lastPathComponent];
149        /*Search all files*/
150        NSEnumerator *fileEnum = [files objectEnumerator];
151        NSString *file = nil;
152        while((file = [fileEnum nextObject]) != nil)
153        {
154                NSString *ext = [file pathExtension];
155                if([ext length] &&
156                   [coverArtExtentions containsObject:ext] &&
157                   [lastComp caseInsensitiveCompare:[file stringByDeletingPathExtension]] == NSOrderedSame)
158                        return [directory stringByAppendingPathComponent:file];
159        }
160        /*Didn't find one*/
161        return nil;
162}
163
164@implementation SapphireMetaDataSupport
165
166+ (void)load
167{
168        coverArtExtentions = [[NSSet alloc] initWithObjects:
169                                                  @"jpg",
170                                                  @"jpeg",
171                                                  @"tif",
172                                                  @"tiff",
173                                                  @"png",
174                                                  @"gif",
175                                                  nil];
176}
177
178+ (SapphireMetaDataSupport *)sharedInstance
179{
180        static SapphireMetaDataSupport *shared = nil;
181       
182        if(shared == nil)
183                shared = [[SapphireMetaDataSupport alloc] init];
184       
185        return shared;
186}
187
188- (void) dealloc
189{
190        [mainMoc release];
191        [writeTimer invalidate];
192        writeTimer = nil;
193        [super dealloc];
194}
195
196+ (void)pruneMetaData:(NSManagedObjectContext *)moc
197{
198        NSPredicate *movieFilePred = [NSPredicate predicateWithFormat:@"movie != nil"];
199        NSArray *movieFiles = doFetchRequest(SapphireFileMetaDataName, moc, movieFilePred);
200        NSSet *movieIds = [NSSet setWithArray:[movieFiles valueForKeyPath:@"movie.objectID"]];
201       
202        NSPredicate *movieNoFile = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", movieIds];
203        NSArray *emptyMovies = doFetchRequest(SapphireMovieName, moc, movieNoFile);
204        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Movies %@", [emptyMovies valueForKeyPath:@"title"]);
205        NSEnumerator *objEnum = [emptyMovies objectEnumerator];
206        NSManagedObject *obj;
207        while((obj = [objEnum nextObject]) != nil)
208                [moc deleteObject:obj];
209       
210        NSArray *allMovies = doFetchRequest(SapphireMovieName, moc, nil);
211       
212        NSDictionary *pruneKeys = [NSDictionary dictionaryWithObjectsAndKeys:
213                                                           SapphireCastName, @"cast",
214                                                           SapphireGenreName, @"genres",
215                                                           SapphireDirectorName, @"directors",
216                                                           nil];
217        NSEnumerator *keyEnum = [pruneKeys keyEnumerator];
218        NSString *key;
219        while((key = [keyEnum nextObject]) != nil)
220        {
221                NSString *objName = [pruneKeys objectForKey:key];
222                NSArray *itemSet = [allMovies valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfSets.%@.objectID", key]];
223                NSArray *emptyItems = doFetchRequest(objName, moc, [NSPredicate predicateWithFormat:@"NOT SELF IN %@", itemSet]);
224                SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning %@ %@", key, [emptyItems valueForKeyPath:@"name"]);
225                objEnum = [emptyItems objectEnumerator];
226                while((obj = [objEnum nextObject]) != nil)
227                        [moc deleteObject:obj];
228        }
229       
230        NSPredicate *epFilePred = [NSPredicate predicateWithFormat:@"tvEpisode != nil"];
231        NSArray *epFiles = doFetchRequest(SapphireFileMetaDataName, moc, epFilePred);
232        NSSet *epIds = [NSSet setWithArray:[epFiles valueForKeyPath:@"tvEpisode.objectID"]];
233       
234        NSPredicate *epNoFile = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", epIds];
235        NSArray *emptyEpisodes = doFetchRequest(SapphireEpisodeName, moc, epNoFile);
236        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Episodes %@", [emptyEpisodes valueForKeyPath:@"episodeTitle"]);
237        objEnum = [emptyEpisodes objectEnumerator];
238        while((obj = [objEnum nextObject]) != nil)
239                [moc deleteObject:obj];
240       
241        NSArray *allEps = doFetchRequest(SapphireEpisodeName, moc, nil);
242       
243        NSSet *seasonIds = [allEps valueForKeyPath:@"@distinctUnionOfObjects.season.objectID"];
244        NSPredicate *noEpisodes = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", seasonIds];
245        NSArray *emptySeasons = doFetchRequest(SapphireSeasonName, moc, noEpisodes);
246        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Seasons %@", [emptySeasons valueForKeyPath:@"path"]);
247        objEnum = [emptySeasons objectEnumerator];
248        while((obj = [objEnum nextObject]) != nil)
249                [moc deleteObject:obj];
250       
251        NSSet *showIds = [allEps valueForKeyPath:@"@distinctUnionOfObjects.tvShow.objectID"];
252        noEpisodes = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", showIds];
253        NSArray *emptyShows = doFetchRequest(SapphireTVShowName, moc, noEpisodes);
254        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Shows %@", [emptyShows valueForKeyPath:@"name"]);
255        objEnum = [emptyShows objectEnumerator];
256        while((obj = [objEnum nextObject]) != nil)
257                [moc deleteObject:obj];
258}
259
260- (void)realWriteMetaData:(NSTimer *)timer
261{
262        NSManagedObjectContext *context = nil;
263        if([timer isKindOfClass:[NSManagedObjectContext class]])
264                context = (NSManagedObjectContext *)timer;
265        else
266                context = [timer userInfo];
267       
268        if(writeTimer != nil)
269                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Rescheduled write");
270        [writeTimer invalidate];
271        writeTimer = nil;
272        NSError *error = nil;
273        locked = NO;
274        BOOL success = NO;
275        @try {
276                success = [context save:&error];
277        }
278        @catch (NSException * e) {
279                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Could not save due to exception \"%@\" with reason \"%@\"", [e name], [e reason]);
280        }
281        if(error != nil)
282        {
283                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Save error \"%@\"", error);
284                NSArray *details = [[error userInfo] objectForKey:@"NSDetailedErrors"];
285                if(details != nil)
286                {
287                        NSEnumerator *errorEnum = [details objectEnumerator];
288                        NSError *aError;
289                        while((aError = [errorEnum nextObject]) != nil)
290                                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"One error is %@: %@", aError, [aError userInfo]);
291                }
292                NSException *underlying = [[error userInfo] objectForKey:@"NSUnderlyingException"];
293                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Underlying is %@ %@ %@ %@", underlying, [underlying name], [underlying reason], [underlying userInfo]);
294                if([[underlying reason] isEqualToString:@"database is locked"])
295                {
296                        SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Detected locked");
297                        locked = YES;
298                }
299        }
300        if(success == NO)
301        {
302                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Inserted objects is %@", [context insertedObjects]);
303                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Updated objects is %@", [context updatedObjects]);
304                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Deleted objects is %@", [context deletedObjects]);
305                interval *= 2;
306                [writeTimer invalidate];
307                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Rescheduling write to occurr in %f seconds", interval);
308               
309                @try {
310                        NSSet *objSet = [context updatedObjects];
311                        NSEnumerator *objEnum = [objSet objectEnumerator];
312                        NSManagedObject *obj;
313                        while((obj = [objEnum nextObject]) != nil)
314                                [context refreshObject:obj mergeChanges:YES];
315                        objSet = [context deletedObjects];
316                        objEnum = [objSet objectEnumerator];
317                        while((obj = [objEnum nextObject]) != nil)
318                                [context refreshObject:obj mergeChanges:YES];                   
319                }
320                @catch (NSException * e) {
321                        SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Could not fix save due to exception \"%@\" with reason \"%@\"", [e name], [e reason]);
322                }
323               
324                writeTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(realWriteMetaData:) userInfo:context repeats:NO];
325        }
326        else
327                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Save successful");
328}
329
330- (BOOL)save:(NSManagedObjectContext *)context;
331{
332        if(context != mainMoc)
333        {
334                return YES;
335        }
336        if(writeTimer == nil)
337        {
338                interval = 1;
339                [self performSelectorOnMainThread:@selector(realWriteMetaData:) withObject:context waitUntilDone:YES];
340                return (writeTimer == nil);
341        }
342        else
343                return YES;
344}
345
346+ (BOOL)save:(NSManagedObjectContext *)context
347{
348        if(context == nil)
349                return NO;
350       
351        return [[SapphireMetaDataSupport sharedInstance] save:context];
352}
353
354- (void)applyChanges:(NSDictionary *)changes
355{
356        [SapphireMetaDataSupport applyChanges:changes toContext:mainMoc];
357}
358
359- (void)applyChangesFromContext:(NSManagedObjectContext *)context
360{
361        if(mainMoc != nil)
362        {
363                NSDictionary *changes = [SapphireMetaDataSupport changesDictionaryForContext:context];
364                [self performSelectorOnMainThread:@selector(applyChanges:) withObject:changes waitUntilDone:YES];
365        }
366}
367
368+ (void)applyChangesFromContext:(NSManagedObjectContext *)context
369{
370        [[SapphireMetaDataSupport sharedInstance] applyChangesFromContext:context];
371}
372
373- (void)setMainContext:(NSManagedObjectContext *)moc
374{
375        [mainMoc release];
376        mainMoc = [moc retain];
377}
378
379+ (void)setMainContext:(NSManagedObjectContext *)moc
380{
381        [[SapphireMetaDataSupport sharedInstance] setMainContext:moc];
382}
383
384- (BOOL)wasLocked
385{
386        return locked;
387}
388
389+ (BOOL)wasLocked
390{
391        return [[SapphireMetaDataSupport sharedInstance] wasLocked];
392}
393
394+ (void)importV1Store:(NSManagedObjectContext *)v1Context intoContext:(NSManagedObjectContext *)context withDisplay:(SapphireMetaDataUpgrading *)display
395{
396        [display setCurrentFile:@"Upgrading Cast"];
397        NSDictionary *castLookup = [SapphireCast upgradeV1CastFromContext:v1Context toContext:context];
398        [display setCurrentFile:@"Upgrading Directors"];
399        NSDictionary *directorLookup = [SapphireDirector upgradeV1DirectorsFromContext:v1Context toContext:context];
400        [display setCurrentFile:@"Upgrading Genres"];
401        NSDictionary *genreLookup = [SapphireGenre upgradeV1GenresFromContext:v1Context toContext:context];
402        [display setCurrentFile:@"Upgrading Movies"];
403        NSDictionary *movieLookup = [SapphireMovie upgradeV1MoviesFromContext:v1Context toContext:context withCast:castLookup directors:directorLookup genres:genreLookup];
404        [display setCurrentFile:@"Upgrading Shows"];
405        [SapphireTVShow upgradeV1ShowsFromContext:v1Context toContext:context];
406        [display setCurrentFile:@"Upgrading Directories"];
407        NSDictionary *dirLookup = [SapphireDirectoryMetaData upgradeV1DirectoriesFromContext:v1Context toContext:context];
408        [display setCurrentFile:@"Upgrading Files"];
409        NSDictionary *fileLookup = [SapphireFileMetaData upgradeV1FilesFromContext:v1Context toContext:context withMovies:movieLookup directories:dirLookup];
410        [display setCurrentFile:@"Upgrading SymLinks"];
411        [SapphireDirectorySymLink upgradeV1DirLinksFromContext:v1Context toContext:context directories:dirLookup];
412        [SapphireFileSymLink upgradeV1FileLinksFromContext:v1Context toContext:context directories:dirLookup file:fileLookup];
413        [display setCurrentFile:@"Upgrading Joined Files"];
414        [SapphireJoinedFile upgradeV1JoinedFileFromContext:v1Context toContext:context file:fileLookup];
415        [display setCurrentFile:@"Upgrading Episodes"];
416        [SapphireEpisode upgradeV1EpisodesFromContext:v1Context toContext:context file:fileLookup];
417        [display setCurrentFile:@"Upgrading XML"];
418        [SapphireXMLData upgradeV1XMLFromContext:v1Context toContext:context file:fileLookup];
419}
420
421+ (void)importPlist:(NSString *)configDir intoContext:(NSManagedObjectContext *)context withDisplay:(SapphireMetaDataUpgrading *)display
422{
423        NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[configDir stringByAppendingPathComponent:@"metaData.plist"]];
424        NSMutableDictionary *defer = [NSMutableDictionary dictionaryWithObjectsAndKeys:
425                                                                  [NSMutableDictionary dictionary], @"Join",
426                                                                  [NSMutableDictionary dictionary], @"Cast",
427                                                                  [NSMutableDictionary dictionary], @"Directors",
428                                                                  nil];
429        int version = [[dict objectForKey:META_VERSION_KEY] intValue];
430        SapphireDirectoryMetaData *newDir = nil;
431        if(version > 2)
432        {
433                NSDictionary *slash = [dict objectForKey:@"/"];
434                newDir = [SapphireDirectoryMetaData createDirectoryWithPath:@"/" parent:nil inContext:context];
435                [newDir insertDictionary:slash withDefer:defer andDisplay:display];
436        }
437        else
438        {
439                newDir = [SapphireDirectoryMetaData createDirectoryWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Movies"] parent:nil inContext:context];
440                [newDir insertDictionary:dict withDefer:defer andDisplay:display];
441        }
442        [display setCurrentFile:BRLocalizedString(@"Upgrading Joined Files", @"")];
443        NSDictionary *joinDict = [defer objectForKey:@"Join"];
444        if(joinDict != nil)
445        {
446                NSEnumerator *joinEunm = [joinDict keyEnumerator];
447                NSString *joinedPath;
448                while((joinedPath = [joinEunm nextObject]) != nil)
449                {
450                        SapphireJoinedFile *joinedFile = [SapphireJoinedFile joinedFileForPath:joinedPath inContext:context];
451                        NSArray *joinArray = [joinDict objectForKey:joinedPath];
452                        NSEnumerator *joinedEnum = [joinArray objectEnumerator];
453                        SapphireFileMetaData *joinFile;
454                        while((joinFile = [joinedEnum nextObject]) != nil)
455                                joinFile.joinedToFile = joinedFile;
456                }                       
457        }
458       
459        [display setCurrentFile:BRLocalizedString(@"Upgrading Collection Prefs", @"")];
460        NSDictionary *options = [dict objectForKey:@"Options"];
461        NSMutableSet *collections = [NSMutableSet set];
462        NSArray *custom = [options objectForKey:@"Directories"];
463        if([custom count])
464                [collections unionSet:[NSSet setWithArray:custom]];
465        NSDictionary *hidden = [options objectForKey:@"Hide"];
466        NSArray *keyArray = [hidden allKeys];
467        if([keyArray count])
468                [collections unionSet:[NSSet setWithArray:keyArray]];
469        NSDictionary *skipped = [options objectForKey:@"Skip"];
470        keyArray = [skipped allKeys];
471        if([keyArray count])
472                [collections unionSet:[NSSet setWithArray:keyArray]];
473       
474        NSEnumerator *collectionEnum = [collections objectEnumerator];
475        NSString *collectionPath;
476        while((collectionPath = [collectionEnum nextObject]) != nil)
477        {
478                [SapphireCollectionDirectory collectionAtPath:collectionPath
479                                                                                                mount:NO
480                                                                                                 skip:[[skipped objectForKey:collectionPath] boolValue]
481                                                                                           hidden:[[hidden objectForKey:collectionPath] boolValue]
482                                                                                           manual:[custom containsObject:collectionPath]
483                                                                                        inContext:context];
484        }
485        //Set the mount values for all
486        [SapphireCollectionDirectory availableCollectionDirectoriesInContext:context];
487       
488        [display setCurrentFile:BRLocalizedString(@"Upgrading Movie Translations", @"")];
489        NSDictionary *movieTranslations = [NSDictionary dictionaryWithContentsOfFile:[configDir stringByAppendingPathComponent:@"movieData.plist"]];
490        NSDictionary *translations = [movieTranslations objectForKey:MOVIE_TRAN_TRANSLATIONS_KEY];
491        NSEnumerator *movieEnum = [translations keyEnumerator];
492        NSString *movie = nil;
493        while((movie = [movieEnum nextObject]) != nil)
494        {
495                NSDictionary *movieDict = [translations objectForKey:movie];
496                SapphireMovieTranslation *trans = [SapphireMovieTranslation createMovieTranslationWithName:movie inContext:context];
497                trans.IMPLink = [movieDict objectForKey:MOVIE_TRAN_IMP_LINK_KEY];
498                NSString *IMDBLink = [movieDict objectForKey:MOVIE_TRAN_IMDB_LINK_KEY];
499                trans.IMDBLink = IMDBLink;
500               
501                int imdbNumber = [SapphireMovie imdbNumberFromString:IMDBLink];
502                if(imdbNumber != 0)
503                {
504                        SapphireMovie *thisMovie = [SapphireMovie movieWithIMDB:imdbNumber inContext:context];
505                        trans.movie = thisMovie;
506                }
507               
508                NSArray *posters = [movieDict objectForKey:MOVIE_TRAN_IMP_POSTERS_KEY];
509                NSSet *dupCheck = [NSSet setWithArray:posters];
510                posters = [dupCheck allObjects];
511               
512                NSString *selectedPoster = [movieDict objectForKey:MOVIE_TRAN_SELECTED_POSTER_KEY];
513                int i, count = [posters count];
514                for(i=0; i<count; i++)
515                {
516                        NSString *posterUrl = [posters objectAtIndex:i];
517                        if([posterUrl isEqualToString:selectedPoster])
518                                trans.selectedPosterIndexValue = i;
519                       
520                        [SapphireMoviePoster createPosterWithLink:posterUrl index:i translation:trans inContext:context];
521                }
522        }
523       
524        [display setCurrentFile:BRLocalizedString(@"Upgrading TV Translations", @"")];
525        NSDictionary *tvTranslations = [NSDictionary dictionaryWithContentsOfFile:[configDir stringByAppendingPathComponent:@"tvdata.plist"]];
526        translations = [tvTranslations objectForKey:@"Translations"];
527        NSEnumerator *tvEnum = [translations keyEnumerator];
528        NSString *tvShow = nil;
529        while((tvShow = [tvEnum nextObject]) != nil)
530        {
531                NSString *showPath = [translations objectForKey:tvShow];
532                SapphireTVTranslation *trans = [SapphireTVTranslation createTVTranslationForName:tvShow withPath:showPath inContext:context];
533                SapphireTVShow *show = [SapphireTVShow showWithPath:showPath inContext:context];
534                trans.tvShow = show;
535        }
536       
537        NSError *error = nil;
538        NSManagedObject *obj;
539        NSEnumerator *objEnum = [[context registeredObjects] objectEnumerator];
540        while((obj = [objEnum nextObject]) != nil)
541        {
542                if(![obj validateForUpdate:&error])
543                        SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_INFO, @"%@", error);
544        }
545}
546
547+ (NSString *)collectionArtPath
548{
549        static NSString *path = nil;
550        if(path == nil)
551                path = [[applicationSupportDir() stringByAppendingPathComponent:@"Collection Art"] retain];
552        return path;
553}
554
555+ (NSDictionary *)changesDictionaryForContext:(NSManagedObjectContext *)moc
556{
557        NSSet *insertedObjects = [moc insertedObjects];
558        NSSet *deletedObjects = [moc deletedObjects];
559        NSMutableSet *updatedObjects = [[moc updatedObjects] mutableCopy];
560        [updatedObjects minusSet:insertedObjects];
561        [updatedObjects autorelease];
562       
563        NSManagedObject *obj;
564        NSEnumerator *objEnum = [insertedObjects objectEnumerator];
565        NSMutableDictionary *inserted = [NSMutableDictionary dictionary];
566        while((obj = [objEnum nextObject]) != nil)
567        {
568                [inserted setObject:[obj changedValuesWithObjectIDs] forKey:[[obj objectID] URIRepresentation]];
569        }
570        objEnum = [updatedObjects objectEnumerator];
571        NSMutableDictionary *updated = [NSMutableDictionary dictionary];
572        while((obj = [objEnum nextObject]) != nil)
573        {
574                NSDictionary *changes = [obj changedValuesWithObjectIDs];
575                if([changes count])
576                        [updated setObject:changes forKey:[[obj objectID] URIRepresentation]];
577        }
578        NSArray *deleted = [deletedObjects valueForKeyPath:@"objectID.URIRepresentation"];
579        return [NSDictionary dictionaryWithObjectsAndKeys:
580                        inserted, CHANGES_INSERTED_KEY,
581                        updated, CHANGES_UPDATED_KEY,
582                        deleted, CHANGES_DELETED_KEY,
583                        nil];
584}
585
586+ (void)applyChanges:(NSDictionary *)changes toContext:(NSManagedObjectContext *)moc
587{
588        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
589        NSUndoManager *undo = [[NSUndoManager alloc] init];
590        [moc setUndoManager:undo];
591        [undo beginUndoGrouping];
592        BOOL failed = NO;
593        @try {
594                NSDictionary *inserted = [changes objectForKey:CHANGES_INSERTED_KEY];
595                NSDictionary *updated = [changes objectForKey:CHANGES_UPDATED_KEY];
596                NSArray *deleted = [changes objectForKey:CHANGES_DELETED_KEY];
597                NSMutableDictionary *objIDTranslation = [NSMutableDictionary dictionary];
598                NSURL *key;
599                NSEnumerator *keyEnum = [inserted keyEnumerator];
600                while((key = [keyEnum nextObject]) != nil && !failed)
601                {
602                        NSManagedObjectID *objId = [[moc persistentStoreCoordinator] managedObjectIDForURIRepresentation:key];
603                        NSEntityDescription *desc = [objId entity];
604                        NSManagedObject *newObj = [NSEntityDescription insertNewObjectForEntityForName:[desc name] inManagedObjectContext:moc];
605                        if(newObj == nil)
606                                failed = YES;
607                        else
608                                [objIDTranslation setObject:newObj forKey:key];
609                }
610                keyEnum = [inserted keyEnumerator];
611                while((key = [keyEnum nextObject]) != nil && !failed)
612                {
613                        NSManagedObject *obj = [objIDTranslation objectForKey:key];
614                        if(obj == nil)
615                                failed = YES;
616                        else
617                                [obj updateChanges:[inserted objectForKey:key] withTrans:objIDTranslation];
618                }
619                keyEnum = [updated keyEnumerator];
620                while((key = [keyEnum nextObject]) != nil && !failed)
621                {
622                        NSManagedObjectID *objId = [[moc persistentStoreCoordinator] managedObjectIDForURIRepresentation:key];
623                        if(objId == nil)
624                                failed = YES;
625                        else
626                        {
627                                NSManagedObject *obj = [moc objectWithID:objId];
628                                if(obj == nil)
629                                        failed = YES;
630                                else
631                                        [obj updateChanges:[updated objectForKey:key] withTrans:objIDTranslation];
632                        }
633                }
634                keyEnum = [deleted objectEnumerator];
635                while((key = [keyEnum nextObject]) != nil && !failed)
636                {
637                        NSManagedObjectID *objId = [[moc persistentStoreCoordinator] managedObjectIDForURIRepresentation:key];
638                        if(objId == nil)
639                                failed = YES;
640                        else
641                        {
642                                NSManagedObject *obj = [moc objectWithID:objId];
643                                if(obj == nil)
644                                        failed = YES;
645                                else
646                                        [moc deleteObject:obj];
647                        }
648                }               
649        }
650        @catch (NSException * e) {
651                [SapphireApplianceController logException:e];
652                failed = YES;
653        }
654        [undo endUndoGrouping];
655        if(failed)
656        {
657                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_ERROR, @"Apply failed for %@, undoing", changes);
658                [undo undo];
659        }
660        [moc setUndoManager:nil];
661        [undo release];
662        [pool release];
663}
664
665@end
Note: See TracBrowser for help on using the browser.