source: trunk/SapphireFrappliance/MetaData/SapphireMObjects/SapphireFileMetaData.m @ 949

Revision 949, 36.0 KB checked in by gbooker, 4 years ago (diff)

Make the directory based import run if any import is needed, even if the file data is already present.

Line 
1#import "SapphireFileMetaData.h"
2#import "SapphireXMLData.h"
3#import "SapphireMovie.h"
4#import "SapphireEpisode.h"
5#import "SapphireDirectoryMetaData.h"
6#import "SapphireVideoTsParser.h"
7#import "SapphireMetaDataSupport.h"
8#import "SapphireMediaPreview.h"
9#import "SapphireFileSymLink.h"
10#import "SapphireSettings.h"
11#import "CoreDataSupportFunctions.h"
12
13#import "SapphireTVShow.h"
14#import "SapphireSeason.h"
15#import "SapphireTVTranslation.h"
16#import "SapphireMovieTranslation.h"
17
18#import "NSArray-Extensions.h"
19#import "NSString-Extensions.h"
20
21#import <QTKit/QTKit.h>
22
23@implementation SapphireFileMetaData
24
25//ATV Extra Info
26NSString *META_SHOW_BROADCASTER_KEY =           @"Broadcast Company";
27NSString *META_SHOW_PUBLISHED_DATE_KEY =        @"Published Date";
28NSString *META_SHOW_AQUIRED_DATE =                      @"Date Aquired";
29NSString *META_SHOW_RATING_KEY =                        @"Rating";
30NSString *META_SHOW_FAVORITE_RATING_KEY =       @"User Rating";
31NSString *META_COPYRIGHT_KEY =                          @"Copyright";
32
33//General Keys
34NSString *META_TITLE_KEY =                                      @"Title";
35NSString *META_DESCRIPTION_KEY =                        @"Show Description";
36NSString *META_SUMMARY_KEY =                            @"Summary";
37NSString *META_RATING_KEY =                                     @"Rating";
38NSString *FILE_CLASS_KEY =                                      @"File Class";
39
40//IMDB Type Info
41NSString *META_MOVIE_TITLE_KEY =                                @"Title";
42NSString *META_MOVIE_CAST_KEY =                                 @"Cast";
43NSString *META_MOVIE_RELEASE_DATE_KEY =                 @"Release Date";
44NSString *META_MOVIE_DIRECTOR_KEY =                             @"Director";
45NSString *META_MOVIE_WIRTERS_KEY =                              @"Writers";
46NSString *META_MOVIE_GENRES_KEY =                               @"Genres";
47NSString *META_MOVIE_PLOT_KEY =                                 @"Plot";
48NSString *META_MOVIE_IMDB_RATING_KEY =                  @"IMDB Rating";
49NSString *META_MOVIE_IMDB_250_KEY =                             @"IMDB Top 250";
50NSString *META_MOVIE_MPAA_RATING_KEY =                  @"MPAA Rating";
51NSString *META_MOVIE_OSCAR_KEY =                                @"Oscars";
52NSString *META_MOVIE_IDENTIFIER_KEY =                   @"Movie ID";
53NSString *META_SEARCH_IMDB_NUMBER_KEY =                 @"Search IMDB Number";
54NSString *META_MOVIE_SORT_TITLE_KEY =                   @"Movie Sort Title";
55
56//TV Show Specific Keys
57NSString *META_SEASON_NUMBER_KEY =                      @"Season";
58NSString *META_EPISODE_NUMBER_KEY =                     @"Episode";
59NSString *META_SHOW_NAME_KEY =                          @"Show Name";
60NSString *META_SHOW_AIR_DATE =                          @"Air Date";
61NSString *META_ABSOLUTE_EP_NUMBER_KEY =         @"Episode Number";
62NSString *META_SHOW_IDENTIFIER_KEY =            @"Show ID";
63NSString *META_EPISODE_2_NUMBER_KEY =           @"Episode 2";
64NSString *META_ABSOLUTE_EP_2_NUMBER_KEY =       @"Episode Number 2";
65NSString *META_SEARCH_SEASON_NUMBER_KEY =       @"Search Season";
66NSString *META_SEARCH_EPISODE_NUMBER_KEY =      @"Search Episode";
67NSString *META_SEARCH_EPISODE_2_NUMBER_KEY =    @"Search Episode 2";
68
69//File Specific Keys
70NSString *META_FILE_MODIFIED_KEY =                              @"Modified";
71NSString *META_FILE_WATCHED_KEY =                               @"Watched";
72NSString *META_FILE_FAVORITE_KEY =                              @"Favorite";
73NSString *META_FILE_RESUME_KEY =                                @"Resume Time";
74NSString *META_FILE_SIZE_KEY =                                  @"Size";
75NSString *META_FILE_DURATION_KEY =                              @"Duration";
76NSString *META_FILE_AUDIO_DESC_KEY =                    @"Audio Description";
77NSString *META_FILE_SAMPLE_RATE_KEY =                   @"Sample Rate";
78NSString *META_FILE_VIDEO_DESC_KEY =                    @"Video Description";
79NSString *META_FILE_AUDIO_FORMAT_KEY =                  @"Audio Format";
80NSString *META_FILE_SUBTITLES_KEY =                     @"Subtitles";
81NSString *META_FILE_JOINED_FILE_KEY =                   @"Joined File";
82
83static NSSet *displayedMetaData;
84static NSArray *displayedMetaDataOrder;
85static NSSet *secondaryFiles;
86
87+ (void)load
88{
89        displayedMetaDataOrder = [[NSArray alloc] initWithObjects:
90                                                          META_MOVIE_IMDB_250_KEY,
91                                                          META_MOVIE_IMDB_RATING_KEY,                                     
92                                                          META_MOVIE_DIRECTOR_KEY,
93                                                          META_MOVIE_CAST_KEY,
94                                                          META_MOVIE_GENRES_KEY,
95                                                          META_EPISODE_AND_SEASON_KEY,
96                                                          META_SEASON_NUMBER_KEY,
97                                                          META_EPISODE_NUMBER_KEY,
98                                                          META_MOVIE_IMDB_STATS_KEY,
99                                                          META_FILE_SIZE_KEY,
100                                                          META_FILE_DURATION_KEY,
101                                                          VIDEO_DESC_LABEL_KEY,
102                                                          VIDEO2_DESC_LABEL_KEY,
103                                                          AUDIO_DESC_LABEL_KEY,
104                                                          AUDIO2_DESC_LABEL_KEY,
105                                                          META_FILE_SUBTITLES_KEY,
106                                                          nil];
107        displayedMetaData = [[NSSet alloc] initWithArray:displayedMetaDataOrder];
108        secondaryFiles = [[NSSet alloc] initWithObjects:
109                                          @"xml",
110                                          @"srt",
111                                          @"sub",
112                                          @"idx",
113                                          @"ass",
114                                          @"ssa",
115                                          nil];
116}
117
118+ (SapphireFileMetaData *)fileWithPath:(NSString *)path inContext:(NSManagedObjectContext *)moc
119{
120        SapphireMetaData *meta = [SapphireMetaData metaDataWithPath:path inContext:moc];
121        if([meta isKindOfClass:[SapphireFileMetaData class]])
122                return (SapphireFileMetaData *)meta;
123        return nil;
124}
125
126+ (SapphireFileMetaData *)internalCreateFileWithPath:(NSString *)path parent:(SapphireDirectoryMetaData *)parent inContext:(NSManagedObjectContext *)moc
127{
128        SapphireFileMetaData *ret = [NSEntityDescription insertNewObjectForEntityForName:SapphireFileMetaDataName inManagedObjectContext:moc];
129        ret.parent = parent;
130        ret.path = path;
131       
132        return ret;
133}
134
135+ (SapphireFileMetaData *)createFileWithPath:(NSString *)path inContext:(NSManagedObjectContext *)moc
136{
137        SapphireFileMetaData *ret = [SapphireFileMetaData fileWithPath:path inContext:moc];
138        if(ret != nil)
139                return ret;
140       
141        SapphireDirectoryMetaData *parent = [SapphireDirectoryMetaData createDirectoryWithPath:[path stringByDeletingLastPathComponent] inContext:moc];
142        ret = [SapphireFileMetaData internalCreateFileWithPath:path parent:parent inContext:moc];
143       
144        return ret;
145}
146
147+ (SapphireFileMetaData *)createFileWithPath:(NSString *)path parent:(SapphireDirectoryMetaData *)parent inContext:(NSManagedObjectContext *)moc
148{
149        SapphireFileMetaData *ret = [SapphireFileMetaData fileWithPath:path inContext:moc];
150        if(ret != nil)
151                return ret;
152       
153        return [SapphireFileMetaData internalCreateFileWithPath:path parent:parent inContext:moc];
154}
155
156+ (NSDictionary *)upgradeV1FilesFromContext:(NSManagedObjectContext *)oldMoc toContext:(NSManagedObjectContext *)newMoc withMovies:(NSDictionary *)movieLookup directories:(NSDictionary *)dirLookup
157{
158        NSMutableDictionary *lookup = [NSMutableDictionary dictionary];
159        NSArray *files = doFetchRequest(SapphireFileMetaDataName, oldMoc, nil);
160        NSEnumerator *fileEnum = [files objectEnumerator];
161        NSManagedObject *oldFile;
162        while((oldFile = [fileEnum nextObject]) != nil)
163        {
164                SapphireFileMetaData *newFile = [NSEntityDescription insertNewObjectForEntityForName:SapphireFileMetaDataName inManagedObjectContext:newMoc];
165                NSString *path = [oldFile valueForKey:@"path"];
166                newFile.path = path;
167                newFile.parent = [dirLookup objectForKey:[oldFile valueForKeyPath:@"parent.path"]];
168                newFile.audioDescription = [oldFile valueForKey:@"audioDescription"];
169                newFile.audioFormatID = [oldFile valueForKey:@"audioFormatID"];
170                newFile.duration = [oldFile valueForKey:@"duration"];
171                newFile.favorite = [oldFile valueForKey:@"favorite"];
172                newFile.fileClass = [oldFile valueForKey:@"fileClass"];
173                newFile.fileContainerType = [oldFile valueForKey:@"fileContainerType"];
174                newFile.hasVideo = [oldFile valueForKey:@"hasVideo"];
175                newFile.importTypeValue = [[oldFile valueForKey:@"importType"] intValue] & ~IMPORT_TYPE_XML_MASK;
176                newFile.modified = [oldFile valueForKey:@"modified"];
177                newFile.resumeTime = [oldFile valueForKey:@"resumeTime"];
178                newFile.sampleRate = [oldFile valueForKey:@"sampleRate"];
179                newFile.size = [oldFile valueForKey:@"size"];
180                newFile.subtitlesDescription = [oldFile valueForKey:@"subtitlesDescription"];
181                newFile.videoDescription = [oldFile valueForKey:@"videoDescription"];
182                newFile.watched = [oldFile valueForKey:@"watched"];
183                NSNumber *oldMovieNumber = [oldFile valueForKeyPath:@"movie.imdbNumber"];
184                if(oldMovieNumber != nil)
185                        newFile.movie = [movieLookup objectForKey:oldMovieNumber];
186               
187                [lookup setObject:newFile forKey:path];
188        }
189        return lookup;
190}
191
192- (void)insertDictionary:(NSDictionary *)dict withDefer:(NSMutableDictionary *)defer
193{
194        self.audioDescription = [dict objectForKey:META_FILE_AUDIO_DESC_KEY];
195        self.audioFormatID = [dict objectForKey:META_FILE_AUDIO_FORMAT_KEY];
196        self.duration = [dict objectForKey:META_FILE_DURATION_KEY];
197        self.favoriteValue = [[dict objectForKey:META_FILE_FAVORITE_KEY] boolValue];
198        self.fileClass = [dict objectForKey:@"File Class"];
199        self.fileContainerType = [dict objectForKey:@"File Container Type"];
200        id value = [dict objectForKey:META_FILE_MODIFIED_KEY];
201        if(value != nil)
202                self.modified = [NSDate dateWithTimeIntervalSince1970:[value intValue]];
203        self.resumeTimeValue = [[dict objectForKey:META_FILE_RESUME_KEY] unsignedIntValue];
204        self.sampleRate = [dict objectForKey:META_FILE_SAMPLE_RATE_KEY];
205        self.size = [dict objectForKey:META_FILE_SIZE_KEY];
206        self.subtitlesDescription = [dict objectForKey:META_FILE_SUBTITLES_KEY];
207        self.videoDescription = [dict objectForKey:META_FILE_VIDEO_DESC_KEY];
208        self.watchedValue = [[dict objectForKey:META_FILE_WATCHED_KEY] boolValue];
209        self.hasVideoValue = self.videoDescription != nil;
210        value = [dict objectForKey:@"XML Source"];
211        if(value != nil)
212        {
213                NSDictionary *xmlDict = (NSDictionary *)value;
214                SapphireXMLData *xml = self.xmlData;
215                if(xml == nil)
216                {
217                        xml = [NSEntityDescription insertNewObjectForEntityForName:SapphireXMLDataName inManagedObjectContext:[self managedObjectContext]];
218                        self.xmlData = xml;
219                }
220                [xml insertDictionary:xmlDict];
221                xml.modified = [NSDate dateWithTimeIntervalSince1970:[[xmlDict objectForKey:META_FILE_MODIFIED_KEY] intValue]];
222                self.importTypeValue |= IMPORT_TYPE_XML_MASK;
223        }
224        value = [dict objectForKey:@"TVRage Source"];
225        if(value != nil)
226        {
227                SapphireEpisode *ep = [SapphireEpisode episodeWithDictionaries:[NSArray arrayWithObject:value] inContext:[self managedObjectContext]];
228                self.tvEpisode = ep;
229                if(ep != nil)
230                {
231                        self.fileClassValue = FILE_CLASS_TV_SHOW;
232                        NSString *epCoverPath = [[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:[ep path]];
233                        NSString *oldBasePath = [[epCoverPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[self.path lastPathComponent]];
234                        NSString *oldCoverPath = searchCoverArtExtForPath([oldBasePath stringByDeletingPathExtension]);
235                        if(oldCoverPath != nil)
236                        {
237                                NSString *newPath = [epCoverPath stringByAppendingPathExtension:[oldCoverPath pathExtension]];
238                                NSFileManager *fm = [NSFileManager defaultManager];
239                                [fm movePath:oldCoverPath toPath:newPath handler:nil];
240                        }
241                }
242                self.importTypeValue |= IMPORT_TYPE_TVSHOW_MASK;
243        }
244        value = [dict objectForKey:@"IMDB Source"];
245        if(value != nil)
246        {
247                SapphireMovie *movie = [SapphireMovie movieWithDictionary:(NSDictionary *)value inContext:[self managedObjectContext] lookup:defer];
248                self.movie = movie;
249                if(movie != nil)
250                {
251                        self.fileClassValue = FILE_CLASS_MOVIE;
252                        NSString *movieCoverPath = [movie coverArtPath];
253                        NSString *oldBasePath = [[[SapphireMetaDataSupport collectionArtPath] stringByAppendingPathComponent:@"@MOVIES"] stringByAppendingPathComponent:[self.path lastPathComponent]];
254                        NSString *oldCoverPath = searchCoverArtExtForPath([oldBasePath stringByDeletingPathExtension]);
255                        if(oldCoverPath != nil)
256                        {
257                                NSString *newPath = [movieCoverPath stringByAppendingPathExtension:[oldCoverPath pathExtension]];
258                                NSFileManager *fm = [NSFileManager defaultManager];
259                                [fm movePath:oldCoverPath toPath:newPath handler:nil];
260                        }
261                }
262                self.importTypeValue |= IMPORT_TYPE_MOVIE_MASK;
263        }
264        NSString *joinPath = [dict objectForKey:META_FILE_JOINED_FILE_KEY];
265        if(joinPath != nil)
266        {
267                NSMutableDictionary *joinDict = [defer objectForKey:@"Join"];
268                NSMutableArray *joinList = [joinDict objectForKey:joinPath];
269                if(joinList == nil)
270                {
271                        joinList = [NSMutableArray array];
272                        [joinDict setObject:joinList forKey:joinPath];
273                }
274                [joinList addObject:self];
275        }
276}
277
278/*Custom TV Episode handler*/
279- (NSComparisonResult) episodeCompare:(SapphireFileMetaData *)other
280{
281        /*Sort by episode*/
282        SapphireEpisode *myEp = self.tvEpisode;
283        SapphireEpisode *theirEp = nil;
284        if([other isKindOfClass:[SapphireFileSymLink class]])
285                theirEp = ((SapphireFileSymLink *)other).file.tvEpisode;
286        else
287                theirEp = other.tvEpisode;
288       
289        if(myEp != nil)
290        {
291                if(theirEp != nil)
292                        return [myEp compare:theirEp];
293                else
294                        return NSOrderedAscending;
295        }
296        else if (theirEp != nil)
297                return NSOrderedDescending;
298
299        return NSOrderedSame;
300}
301
302- (NSComparisonResult) movieCompare:(SapphireFileMetaData *)other
303{
304        SapphireMovie *myMovie = self.movie;
305        SapphireMovie *theirMovie = other.movie;
306       
307        if(myMovie != nil)
308                if(theirMovie != nil)
309                {
310                        NSComparisonResult ret = [myMovie titleCompare:theirMovie];
311                        if(ret == NSOrderedSame)
312                                ret = [myMovie releaseDateCompare:theirMovie];
313                        return ret;
314                }
315                else
316                        return NSOrderedAscending;
317        else if(theirMovie != nil)
318                return NSOrderedDescending;
319       
320        return NSOrderedSame;
321}
322
323- (BOOL) needsUpdating
324{
325        /*Check modified date*/
326        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:self.path traverseLink:YES];
327        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
328       
329        if(props == nil)
330        /*No file*/
331                return FALSE;
332       
333        /*Has it been modified since last import?*/
334        if(modTime != [self.modified timeIntervalSince1970])
335                return YES;
336       
337        if(self.hasVideo == nil)
338                return YES;
339       
340        return NO;
341}
342
343- (BOOL)needsImporting
344{
345        NSFileManager *fm = [NSFileManager defaultManager];
346       
347        if(self.joinedToFile != nil)
348                return NO;
349
350        if([self needsUpdating])
351                return YES;
352       
353        //Check XML
354        BOOL xmlPathIsDir = NO;
355        NSString *xmlFilePath=[[self.path stringByDeletingPathExtension] stringByAppendingPathExtension:@"xml"];
356        SapphireXMLData *xml = self.xmlData;
357        NSDictionary *xmlProps = [fm fileAttributesAtPath:xmlFilePath traverseLink:YES];
358       
359        if(xmlProps == nil && xml != nil)
360                //XML file is gone, but we still reference it
361                return YES;
362
363        int modTime = [[xmlProps objectForKey:NSFileModificationDate] timeIntervalSince1970];
364        if(modTime != [self importedTimeFromSource:IMPORT_TYPE_XML_MASK])
365                //XML modification time does not match our last import
366                return YES;
367       
368        //Match improrts, but exclude xml and file b/c they are tracked through other means
369        int match = IMPORT_TYPE_ALL_MASK & ~IMPORT_TYPE_FILE_MASK & ~IMPORT_TYPE_XML_MASK;
370        switch (self.fileClassValue) {
371                case FILE_CLASS_TV_SHOW:
372                        match &= ~IMPORT_TYPE_MOVIE_MASK;
373                        break;
374                case FILE_CLASS_MOVIE:
375                        match &= ~IMPORT_TYPE_TVSHOW_MASK;
376                        break;
377                default:
378                        break;
379        }
380       
381        int completed = self.importTypeValue & match;
382        BOOL ret = (match != completed);
383        if(ret)
384                NSLog(@"Going to import %@ because a file of class %d %d != %d", self.path, self.fileClassValue, match, completed);
385       
386        return ret;
387}
388
389- (oneway void)addFileData:(bycopy NSDictionary *)fileMeta
390{
391        self.audioDescription = [fileMeta objectForKey:META_FILE_AUDIO_DESC_KEY];
392        self.audioFormatID = [fileMeta objectForKey:META_FILE_AUDIO_FORMAT_KEY];
393        self.duration = [fileMeta objectForKey:META_FILE_DURATION_KEY];
394        id value = [fileMeta objectForKey:META_FILE_MODIFIED_KEY];
395        if(value != nil)
396                self.modified = [NSDate dateWithTimeIntervalSince1970:[value intValue]];
397        self.sampleRate = [fileMeta objectForKey:META_FILE_SAMPLE_RATE_KEY];
398        self.size = [fileMeta objectForKey:META_FILE_SIZE_KEY];
399        self.subtitlesDescription = [fileMeta objectForKey:META_FILE_SUBTITLES_KEY];
400        NSString *videoDesc = [fileMeta objectForKey:META_FILE_VIDEO_DESC_KEY];
401        self.videoDescription = videoDesc;
402        if(videoDesc != nil)
403                self.hasVideoValue = YES;
404}
405
406
407BOOL updateMetaData(SapphireFileMetaData *file)
408{
409        BOOL updated =FALSE;
410        if([file needsUpdating])
411        {
412                /*We did an update*/
413                updated=TRUE ;
414                NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
415                NSString *path = [file path];
416               
417                NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
418                int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
419                /*Set modified, size, and version*/
420                [fileMeta setObject:[NSNumber numberWithInt:modTime] forKey:META_FILE_MODIFIED_KEY];
421                [fileMeta setObject:[props objectForKey:NSFileSize] forKey:META_FILE_SIZE_KEY];
422               
423                if([file fileContainerTypeValue] == FILE_CONTAINER_TYPE_QT_MOVIE)
424                {
425                        /*Open the movie*/
426                        NSError *error = nil;
427                        QTMovie *movie = [QTMovie movieWithFile:path error:&error];
428                        QTTime duration = [movie duration];
429                        [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:META_FILE_DURATION_KEY];
430                        NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
431                        NSNumber *audioSampleRate = nil;
432                        int trackCount = [audioTracks count];
433                        int i;
434                        BOOL foundAC3 = NO;
435                        for(i=0; i<trackCount; i++)
436                        {
437                                /*Get the audio track*/
438                                QTTrack *track = [audioTracks objectAtIndex:i];
439                                QTMedia *media = [track media];
440                                if(media != nil)
441                                {
442                                        /*Get the audio format*/
443                                        Media qtMedia = [media quickTimeMedia];
444                                        Handle sampleDesc = NewHandle(1);
445                                        GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
446                                        AudioStreamBasicDescription asbd;
447                                        ByteCount       propSize = 0;
448                                        QTSoundDescriptionGetProperty((SoundDescriptionHandle)sampleDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_AudioStreamBasicDescription, sizeof(asbd), &asbd, &propSize);
449                                       
450                                        if(propSize != 0 && !foundAC3)
451                                        {
452                                                /*Set the format and sample rate*/
453                                                NSNumber *format = [NSNumber numberWithUnsignedInt:asbd.mFormatID];
454                                                [fileMeta setObject:format forKey:META_FILE_AUDIO_FORMAT_KEY];
455                                                audioSampleRate = [NSNumber numberWithDouble:asbd.mSampleRate];
456                                        }
457                                       
458                                        CFStringRef userText = nil;
459                                        propSize = 0;
460                                        QTSoundDescriptionGetProperty((SoundDescriptionHandle)sampleDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_UserReadableText, sizeof(userText), &userText, &propSize);
461                                        if(userText != nil)
462                                        {
463                                                if([(NSString *)userText hasPrefix:@"AC3"])
464                                                        foundAC3 = YES;
465                                                /*Set the description*/
466                                                NSString *prevDesc = [fileMeta objectForKey:META_FILE_AUDIO_DESC_KEY];
467                                                NSString *newDesc;
468                                                if(prevDesc != nil)
469                                                        newDesc = [prevDesc stringByAppendingFormat:@"\n%@", userText];
470                                                else
471                                                        newDesc = (NSString *)userText;
472                                                [fileMeta setObject:newDesc forKey:META_FILE_AUDIO_DESC_KEY];
473                                                CFRelease(userText);
474                                        }
475                                        DisposeHandle(sampleDesc);
476                                }
477                        }
478                        /*Set the sample rate*/
479                        if(audioSampleRate != nil)
480                                [fileMeta setObject:audioSampleRate forKey:META_FILE_SAMPLE_RATE_KEY];
481                        NSArray *videoTracks = [movie tracksOfMediaType:@"vide"];
482                        trackCount = [videoTracks count];
483                        for(i=0; i<trackCount; i++)
484                        {
485                                /*Get the video track*/
486                                QTTrack *track = [videoTracks objectAtIndex:i];
487                                QTMedia *media = [track media];
488                                if(media != nil)
489                                {
490                                        /*Get the video description*/
491                                        Media qtMedia = [media quickTimeMedia];
492                                        Handle sampleDesc = NewHandle(1);
493                                        GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
494                                        CFStringRef userText = nil;
495                                        ByteCount propSize = 0;
496                                        ICMImageDescriptionGetProperty((ImageDescriptionHandle)sampleDesc, kQTPropertyClass_ImageDescription, kICMImageDescriptionPropertyID_SummaryString, sizeof(userText), &userText, &propSize);
497                                        DisposeHandle(sampleDesc);
498                                       
499                                        if(userText != nil)
500                                        {
501                                                /*Set the description*/
502                                                NSString *prevDesc = [fileMeta objectForKey:META_FILE_VIDEO_DESC_KEY];
503                                                NSString *newDesc;
504                                                if(prevDesc != nil)
505                                                        newDesc = [prevDesc stringByAppendingFormat:@"\n%@", userText];
506                                                else
507                                                        newDesc = (NSString *)userText;
508                                                [fileMeta setObject:newDesc forKey:META_FILE_VIDEO_DESC_KEY];
509                                                CFRelease(userText);
510                                        }
511                                }
512                        }
513                } //QTMovie
514                else if([file fileContainerTypeValue] == FILE_CONTAINER_TYPE_VIDEO_TS)
515                {
516                        SapphireVideoTsParser *dvd = [[SapphireVideoTsParser alloc] initWithPath:path];
517                       
518                        [fileMeta setObject:[dvd videoFormatsString ] forKey:META_FILE_VIDEO_DESC_KEY];
519                        [fileMeta setObject:[dvd audioFormatsString ] forKey:META_FILE_AUDIO_DESC_KEY];
520                        [fileMeta setObject:[dvd subtitlesString    ] forKey:META_FILE_SUBTITLES_KEY ];
521                        [fileMeta setObject:[dvd mainFeatureDuration] forKey:META_FILE_DURATION_KEY  ];
522                        [fileMeta setObject:[dvd totalSize          ] forKey:META_FILE_SIZE_KEY      ];
523                       
524                        [dvd release];
525                } // VIDEO_TS
526                [file addFileData:fileMeta];
527        }
528        return updated;
529}
530
531- (BOOL)updateMetaData
532{
533        return updateMetaData(self);
534}
535
536- (NSString *)sizeString
537{
538        /*Get size*/
539        float size = [self sizeValue];
540        if(size == 0)
541                return @"-";
542       
543        /*The letter for magnitude*/
544        char letter = ' ';
545        if(size >= 1024000)
546        {
547                if(size >= 1024*1024000)
548                {
549                        /*GB*/
550                        size /= 1024 * 1024 * 1024;
551                        letter = 'G';
552                }
553                else
554                {
555                        /*MB*/
556                        size /= 1024 * 1024;
557                        letter = 'M';
558                }
559        }
560        else if (size >= 1000)
561        {
562                /*KB*/
563                size /= 1024;
564                letter = 'K';
565        }
566        return [NSString stringWithFormat:@"%.1f%cB", size, letter];   
567}
568
569- (void)setToReimportFromMask:(NSNumber *)mask
570{
571        [self setToReimportFromMaskValue:[mask intValue]];
572}
573
574- (void)setToReimportFromMaskValue:(int)mask
575{
576        int currentMask = self.importTypeValue;
577        self.importTypeValue = currentMask & ~mask;
578        if(mask & IMPORT_TYPE_MOVIE_MASK)
579        {
580                SapphireMovie *movie = self.movie;
581                self.movie = nil;
582                if(movie != nil && [movie.filesSet count] == 0)
583                        [[self managedObjectContext] deleteObject:movie];
584        }
585        if(mask & IMPORT_TYPE_TVSHOW_MASK)
586        {
587                SapphireEpisode *ep = self.tvEpisode;
588                self.tvEpisode = nil;
589                if(ep != nil && [ep.filesSet count] == 0)
590                        [[self managedObjectContext] deleteObject:ep];
591        }
592}
593
594- (void)setToResetImportDecisions
595{
596        NSManagedObjectContext *moc = [self managedObjectContext];
597        NSString *lowerFileName = [[self.path lastPathComponent] lowercaseString];
598
599        SapphireEpisode *ep = self.tvEpisode;
600        if(ep != nil)
601        {
602                NSSet *translations = ep.tvShow.translationsSet;
603                SapphireTVTranslation *tran;
604                NSEnumerator *tranEnum = [translations objectEnumerator];
605                while((tran = [tranEnum nextObject]) != nil)
606                {
607                        if([lowerFileName hasPrefix:tran.name])
608                        {
609                                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Deleting TV import translation for %@", tran.name);
610                                [moc deleteObject:tran];
611                        }
612                }
613        }
614        NSString *lookupName;
615        if([[SapphireSettings sharedSettings] dirLookup])
616                lookupName = [[[self.path stringByDeletingLastPathComponent] lastPathComponent] lowercaseString];
617        else
618                lookupName = lowerFileName;
619        SapphireMovieTranslation *movieTran = [SapphireMovieTranslation movieTranslationWithName:[lookupName stringByDeletingPathExtension] inContext:moc];
620        if(movieTran != nil)
621        {
622                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Deleting Movie import translation for %@", movieTran.name);
623                [moc deleteObject:movieTran];
624        }
625       
626        [self setToReimportFromMaskValue:IMPORT_TYPE_ALL_MASK];
627}
628
629- (void)clearMetaData
630{
631        self.audioDescription = nil;
632        self.audioFormatID = nil;
633        self.duration = nil;
634        self.favoriteValue = 0;
635        self.fileClassValue = nil;
636        self.fileContainerType = nil;
637        self.hasVideo = nil;
638        self.importTypeValue = 0;
639        self.modified = nil;
640        self.resumeTime = nil;
641        self.sampleRate = nil;
642        self.size = nil;
643        self.subtitlesDescription = nil;
644        self.videoDescription = nil;
645        self.watchedValue = 0;
646        self.movie = nil;
647        self.tvEpisode = nil;
648        if(self.xmlData != nil)
649        {
650                [[self managedObjectContext] deleteObject:self.xmlData];
651        }
652}
653
654- (NSString *)coverArtPath
655{
656        /*Find cover art for the current file in the "Cover Art" dir */
657        NSString *subPath = [self path];
658        if([self fileContainerTypeValue] != FILE_CONTAINER_TYPE_VIDEO_TS)
659                subPath = [subPath stringByDeletingPathExtension];
660       
661        NSString *fileName = [subPath lastPathComponent];
662        NSString * myArtPath=nil;
663       
664        if([self fileClassValue]==FILE_CLASS_TV_SHOW)
665                myArtPath=[[self tvEpisode] coverArtPath];
666        if([self fileClassValue]==FILE_CLASS_MOVIE)
667                myArtPath=[[self movie] coverArtPath];
668       
669        /* Check the Collection Art location */
670        NSString *ret=searchCoverArtExtForPath(myArtPath);
671       
672        if(ret != nil)
673                return ret;
674       
675        /* Try Legacy Folders with the file */
676        ret=searchCoverArtExtForPath([[[subPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Cover Art"] stringByAppendingPathComponent:fileName]);
677       
678        if(ret != nil)
679                return ret;
680       
681        /*Find cover art for the current file in the current dir*/
682        ret = searchCoverArtExtForPath(subPath);
683       
684        if(ret != nil)
685                return ret;
686       
687       
688        return nil;
689}
690
691- (NSString *)durationString
692{
693        /*Create duration string*/
694        return [NSString colonSeparatedTimeStringForSeconds:[self durationValue]];
695}
696
697static BOOL moving = NO;
698static BOOL moveSuccess = NO;
699static NSString *movingFromPath = @"From";
700static NSString *movingToPath = @"To";
701
702- (void)threadedMove:(NSDictionary *)pathInfo
703{
704        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
705        moveSuccess = [[NSFileManager defaultManager] movePath:[pathInfo objectForKey:movingFromPath] toPath:[pathInfo objectForKey:movingToPath] handler:nil];
706        moving = NO;
707        [pool drain];
708}
709
710- (NSString *)moveToPath:(NSString *)newPath pathForMoveError:(NSString *)errorPath inDir:(SapphireDirectoryMetaData *)newParent
711{
712        NSString *oldPath = [[[self path] retain] autorelease];
713        NSFileManager *fm = [NSFileManager defaultManager];
714        if([fm fileExistsAtPath:newPath])
715                return [NSString stringWithFormat:BRLocalizedString(@"The name %@ is already taken", @"Name taken on a file/directory rename; parameter is name"), [newPath lastPathComponent]];
716        if(newParent != nil)
717        {
718                moving = YES;
719                [NSThread detachNewThreadSelector:@selector(threadedMove:) toTarget:self withObject:[NSDictionary dictionaryWithObjectsAndKeys:
720                                                                                                                                                                                         oldPath, movingFromPath,
721                                                                                                                                                                                         newPath, movingToPath,
722                                                                                                                                                                                         nil]];
723                while(moving)
724                        [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] addTimeInterval:1]];           
725        }
726        else
727                moveSuccess = [fm movePath:oldPath toPath:newPath handler:nil];
728        NSLog(@"Move %d", moveSuccess);
729        if(!moveSuccess)
730                return [NSString stringWithFormat:BRLocalizedString(@"Could not move %@.  Is the filesystem read-only?", @"Unknown error renaming file/directory; parameter is name"), errorPath];
731        [self setPath:newPath];
732        NSLog(@"path set to %@", newPath);
733        if(newParent != nil)
734        {
735                SapphireDirectoryMetaData *oldParent = self.parent;
736                self.parent = newParent;
737                [oldParent clearPredicateCache];
738                [newParent clearPredicateCache];
739        }
740        NSLog(@"new parent set");
741        [SapphireMetaDataSupport save:[self managedObjectContext]];
742        NSLog(@"Save done");
743        NSString *extLessPath = [oldPath stringByDeletingPathExtension];
744        NSEnumerator *secondaryExtEnum = [secondaryFiles objectEnumerator];
745        NSString *extension;
746       
747        while((extension = [secondaryExtEnum nextObject]) != nil)
748        {
749                NSString *secondaryPath = [extLessPath stringByAppendingPathExtension:extension];
750                if([fm fileExistsAtPath:secondaryPath])
751                {
752                        NSString *newSecondaryPath = [[newPath stringByDeletingPathExtension] stringByAppendingPathExtension:extension];
753                        if(newParent != nil)
754                        {
755                                moving = YES;
756                                [NSThread detachNewThreadSelector:@selector(threadedMove:) toTarget:self withObject:[NSDictionary dictionaryWithObjectsAndKeys:
757                                                                                                                                                                                                         secondaryPath, movingFromPath,
758                                                                                                                                                                                                         newSecondaryPath, movingToPath,
759                                                                                                                                                                                                         nil]];
760                                while(moving)
761                                        [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] addTimeInterval:1]];           
762                        }
763                        else
764                                moveSuccess = [fm movePath:secondaryPath toPath:newSecondaryPath handler:nil];                 
765                        if(!moveSuccess)
766                                return [NSString stringWithFormat:BRLocalizedString(@"Could not move %@ file for %@.  Is the filesystem read-only?", @"Unknown error renaming file/directory; parameter is extension, name"), extension, errorPath];
767                }
768        }
769        NSLog(@"Secondary files done");
770        NSString *coverArtPath = searchCoverArtExtForPath(extLessPath);
771        if(coverArtPath != nil)
772        {
773                NSString *newCoverArtPath = [[newPath stringByDeletingPathExtension] stringByAppendingPathExtension:[coverArtPath pathExtension]];
774                if(newParent != nil)
775                {
776                        moving = YES;
777                        [NSThread detachNewThreadSelector:@selector(threadedMove:) toTarget:self withObject:[NSDictionary dictionaryWithObjectsAndKeys:
778                                                                                                                                                                                                 coverArtPath, movingFromPath,
779                                                                                                                                                                                                 newCoverArtPath, movingToPath,
780                                                                                                                                                                                                 nil]];
781                        while(moving)
782                                [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] addTimeInterval:1]];           
783                }
784                else
785                        moveSuccess = [fm movePath:coverArtPath toPath:newCoverArtPath handler:nil];
786                if(!moveSuccess)
787                        return [NSString stringWithFormat:BRLocalizedString(@"Could not move cover art for %@.  Is the filesystem read-only?", @"Unknown error renaming file/directory; parameter is name"), errorPath];
788        }
789        NSLog(@"Covert art done");
790        return nil;
791}
792
793- (NSString *)moveToDir:(SapphireDirectoryMetaData *)dir
794{
795        NSString *destination = [dir path];
796        NSString *newPath = [destination stringByAppendingPathComponent:[[self path] lastPathComponent]];
797        return [self moveToPath:newPath pathForMoveError:[newPath lastPathComponent] inDir:dir];
798}
799
800- (NSString *)rename:(NSString *)newFilename
801{
802        int componentCount = [[newFilename pathComponents] count];
803        if(componentCount != 1)
804                return BRLocalizedString(@"A File name should not contain any '/' characters", @"");
805        NSString *oldPath = [self path];
806        newFilename = [newFilename stringByAppendingPathExtension:[oldPath pathExtension]];
807        NSString *newPath = [[oldPath stringByDeletingLastPathComponent] stringByAppendingPathComponent:newFilename];
808        if([oldPath isEqualToString:newPath])
809                return nil;
810        NSLog(@"Going to move path %@ to %@ with error %@", [self path], newPath, newFilename);
811        return [self moveToPath:newPath pathForMoveError:newFilename inDir:nil];
812}
813
814- (NSString *)prettyName
815{
816        if(self.tvEpisode != nil)
817        {
818                //TV Episode
819                SapphireEpisode *ep = self.tvEpisode;
820                NSString *tvShowName = ep.tvShow.name;
821                NSString *epName = [ep episodeTitle];
822                int season = ep.season.seasonNumberValue;
823                int firstEp = [ep episodeNumberValue];
824                int lastEp = [ep lastEpisodeNumberValue];
825               
826                NSString *SEString;
827                if(firstEp == 0)
828                        //Single Special Episode
829                        SEString = [NSString stringWithFormat:@"S%02dES1", season];
830                else if(lastEp == firstEp)
831                        //Single normal episode
832                        SEString = [NSString stringWithFormat:@"S%02dE%02d", season, firstEp];
833                else
834                        //Double episode
835                        SEString = [NSString stringWithFormat:@"S%02dE%02d-E%02d", season, firstEp, lastEp];
836               
837                return [NSString stringWithFormat:@"%@ %@ %@", tvShowName, SEString, epName];
838        }
839        else if(self.movie != nil)
840        {
841                //Movie
842                NSDate *releaseDate = [self.movie releaseDate];
843                NSCalendarDate *releaseCalDate = [NSCalendarDate dateWithTimeIntervalSinceReferenceDate:[releaseDate timeIntervalSinceReferenceDate]];
844                return [NSString stringWithFormat:@"%@ (%d)", [self.movie title], [releaseCalDate yearOfCommonEra]];
845        }
846        return nil;
847}
848
849- (NSString *)renameToPrettyName
850{
851        NSString *prettyName = [self prettyName];
852        if(prettyName == nil)
853        {
854                return BRLocalizedString(@"No pretty name to construct", @"");
855        }
856       
857        NSMutableString *mutStr = [prettyName mutableCopy];
858        [mutStr replaceOccurrencesOfString:@"/" withString:@"-" options:0 range:NSMakeRange(0, [mutStr length])];
859        NSLog(@"Going to rename %@ to %@", [self path], mutStr);
860       
861        return [self rename:[mutStr autorelease]];
862}
863
864- (NSMutableDictionary *)getDisplayedMetaDataInOrder:(NSArray * *)order;
865{
866        NSString *name = [[self path] lastPathComponent];
867        NSString *durationStr = [self durationString];
868        /*Set the order*/
869        if(order != nil)
870                *order = displayedMetaDataOrder;
871       
872        NSMutableDictionary *ret = [[NSMutableDictionary alloc] init];
873       
874        SapphireMovie *movie = [self movie];
875        SapphireEpisode *ep = [self tvEpisode];
876        if(movie != nil)
877        {
878                [movie insertDisplayMetaData:ret];
879        }
880        else if (ep != nil)
881        {
882                [ep insertDisplayMetaData:ret];
883        }
884       
885        id value = [self videoDescription];
886        if(value != nil)
887        {
888                NSString *valueString = (NSString *)value;
889                NSMutableArray *valueArray = [[valueString componentsSeparatedByString:@"\n"] mutableCopy];
890                [valueArray uniqueObjects];
891                int count = [valueArray count];
892                if(count > 1)
893                {
894                        NSString *first = nil;
895                        int i;
896                        for(i=0; i<count; i++)
897                        {
898                                NSString *trackName = [valueArray objectAtIndex:i];
899                                if(![trackName hasPrefix:@"VobSub"])
900                                {
901                                        first = [[trackName retain] autorelease];
902                                        [valueArray removeObjectAtIndex:i];
903                                        break;
904                                }
905                        }
906                        if(first == nil)
907                        {
908                                first = [[[valueArray objectAtIndex:0] retain] autorelease];
909                                [valueArray removeObjectAtIndex:0];
910                        }
911                        [ret setObject:first forKey:VIDEO_DESC_LABEL_KEY];
912                        [ret setObject:[valueArray componentsJoinedByString:@"\n"] forKey:VIDEO2_DESC_LABEL_KEY];
913                }
914                else
915                        [ret setObject:value forKey:VIDEO_DESC_LABEL_KEY];
916                [valueArray release];
917        }
918        value = [self audioDescription];
919        if(value != nil)
920        {
921                NSString *valueString = (NSString *)value;
922                NSMutableArray *valueArray = [[valueString componentsSeparatedByString:@"\n"] mutableCopy];
923                [valueArray uniqueObjects];
924                if([valueArray count] > 1)
925                {
926                        [ret setObject:[valueArray objectAtIndex:0] forKey:AUDIO_DESC_LABEL_KEY];
927                        [valueArray removeObjectAtIndex:0];
928                        [ret setObject:[valueArray componentsJoinedByString:@"\n"] forKey:AUDIO2_DESC_LABEL_KEY];
929                }
930                else
931                        [ret setObject:value forKey:AUDIO_DESC_LABEL_KEY];
932                [valueArray release];
933        }
934        value = [self subtitlesDescription];
935        if(value != nil)
936                [ret setObject:value forKey:META_FILE_SUBTITLES_KEY];
937        if([self durationValue])
938        {
939                if([self sizeValue])
940                {
941                        int resumeTime = [self resumeTimeValue];
942                        NSString *fullDurationString = [NSString stringWithFormat:@"%@ (%@)", durationStr, [self sizeString]];
943                        if(resumeTime != 0)
944                                fullDurationString = [fullDurationString stringByAppendingFormat:BRLocalizedString(@" %@ Remaining", @"Time left to display in preview pane next to file runtime"), [NSString colonSeparatedTimeStringForSeconds:[self durationValue] - resumeTime]];
945                        [ret setObject:fullDurationString forKey:META_FILE_DURATION_KEY];
946                }
947                else
948                        [ret setObject:durationStr forKey:META_FILE_DURATION_KEY];
949        }
950        else
951                [ret setObject:[self sizeString] forKey:META_FILE_SIZE_KEY];
952       
953        /*Set the title*/
954        if([ret objectForKey:META_TITLE_KEY] == nil)
955                [ret setObject:name forKey:META_TITLE_KEY];
956        return [ret autorelease];
957}
958
959- (NSString *)searchShowName
960{
961        return self.xmlData.searchShowName;
962}
963
964- (int)searchSeasonNumber
965{
966        NSNumber *value = self.xmlData.searchSeasonNumber;
967        if(value != nil)
968                return [value intValue];
969        return -1;
970}
971
972- (int)searchEpisodeNumber
973{
974        NSNumber *value = self.xmlData.searchEpisode;
975        if(value != nil)
976                return [value intValue];
977        return -1;
978}
979
980- (int)searchLastEpisodeNumber
981{
982        NSNumber *value = self.xmlData.searchLastEpisodeNumber;
983        if(value != nil)
984                return [value intValue];
985        return -1;
986}
987
988- (int)searchIMDBNumber
989{
990        NSNumber *value = self.xmlData.searchIMDBNumber;
991        if(value != nil)
992                return [value intValue];
993        return -1;
994}
995
996- (FileContainerType)fileContainerTypeValue
997{
998        return super.fileContainerTypeValue;
999}
1000
1001- (ImportTypeMask)importTypeValue
1002{
1003        return super.importTypeValue;
1004}
1005
1006- (long)importedTimeFromSource:(int)source
1007{
1008        if(source == IMPORT_TYPE_FILE_MASK)
1009                return [self.modified timeIntervalSince1970];
1010        else if(source == IMPORT_TYPE_XML_MASK)
1011                return [self.xmlData.modified timeIntervalSince1970];
1012        return 0;
1013}
1014
1015- (void)didImportType:(ImportTypeMask)type
1016{
1017        self.importTypeValue |= type;
1018}
1019
1020- (void)setMovie:(SapphireMovie *)movie
1021{
1022        SapphireMovie *oldMovie = self.movie;
1023        super.movie = movie;
1024        if([self isDeleted])
1025                return;
1026        if(movie != nil)
1027        {
1028                [self setFileClassValue:FILE_CLASS_MOVIE];
1029                self.importTypeValue |= IMPORT_TYPE_MOVIE_MASK;
1030        }
1031        if(movie != oldMovie)
1032                self.xmlData.movie = movie;
1033}
1034
1035- (void)setTvEpisode:(SapphireEpisode *)ep
1036{
1037        SapphireEpisode *oldEp = self.tvEpisode;
1038        super.tvEpisode = ep;
1039        if([self isDeleted])
1040                return;
1041        if(ep != nil)
1042        {
1043                [self setFileClassValue:FILE_CLASS_TV_SHOW];
1044                self.importTypeValue |= IMPORT_TYPE_TVSHOW_MASK;
1045        }
1046        if(ep != oldEp)
1047                self.xmlData.episode = ep;
1048}
1049
1050- (void)setXmlData:(SapphireXMLData *)data
1051{
1052        super.xmlData = data;
1053        if([self isDeleted])
1054                return;
1055        if(data != nil)
1056        {
1057                data.episode = self.tvEpisode;
1058                data.movie = self.movie;
1059        }
1060}
1061
1062- (FileClass)fileClassValue
1063{
1064        FileClass xmlClass = self.xmlData.fileClassValue;
1065        if(xmlClass != FILE_CLASS_UNKNOWN)
1066                return xmlClass;
1067        return super.fileClassValue;
1068}
1069
1070- (void)setFileClassValue:(FileClass)fileClass
1071{
1072        FileClass xmlClass = self.xmlData.fileClassValue;
1073        if(xmlClass != FILE_CLASS_UNKNOWN)
1074                self.xmlData.fileClassValue = FILE_CLASS_UNKNOWN;
1075        super.fileClassValue = fileClass;
1076}
1077
1078- (void)setWatched:(NSNumber*)value_ {
1079        NSNumber *oldValue = [self.watched retain];
1080        super.watched = value_;
1081        if(![oldValue isEqualToNumber:value_])
1082        {
1083                self.resumeTime = nil;
1084                [self.parent clearPredicateCache];
1085                [self.tvEpisode clearPredicateCache];
1086                [self.movie clearPredicateCache];
1087        }
1088}
1089
1090- (void)setFavorite:(NSNumber*)value_ {
1091        NSNumber *oldValue = [self.favorite retain];
1092        super.favorite = value_;
1093        if(![oldValue isEqualToNumber:value_])
1094        {
1095                [self.parent clearPredicateCache];
1096                [self.tvEpisode clearPredicateCache];
1097                [self.movie clearPredicateCache];
1098        }
1099}
1100
1101@end
Note: See TracBrowser for help on using the repository browser.