source: branches/CoreData/SapphireFrappliance/MetaData/Support/SapphireMetaDataSupport.m @ 857

Revision 857, 26.9 KB checked in by pmerrill, 5 years ago (diff)

Allow for pllists to be upgraded individually

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