source: trunk/SapphireMetaData.m @ 125

Revision 125, 26.7 KB checked in by gbooker, 7 years ago (diff)
  • Redesigned a bit of the metadata display
  • Pull air dates from tvrage
  • Corrected values imported from XML to by right type
  • A few bug fixes
Line 
1//
2//  SapphireMetaData.m
3//  Sapphire
4//
5//  Created by Graham Booker on 6/22/07.
6//  Copyright 2007 __www.nanopi.net__. All rights reserved.
7//
8
9#import "SapphireMetaData.h"
10#import <QTKit/QTKit.h>
11#include <sys/types.h>
12#include <sys/stat.h>
13#import "SapphireSettings.h"
14
15//Structure Specific Keys
16#define FILES_KEY                                       @"Files"
17#define DIRS_KEY                                        @"Dirs"
18#define META_VERSION_KEY                        @"Version"
19#define META_VERSION                            1
20
21//File Specific Keys
22#define MODIFIED_KEY                            @"Modified"
23#define WATCHED_KEY                                     @"Watched"
24#define FAVORITE_KEY                            @"Favorite"
25#define RESUME_KEY                                      @"Resume Time"
26#define SIZE_KEY                                        @"Size"
27#define DURATION_KEY                            @"Duration"
28#define AUDIO_DESC_KEY                          @"Audio Description"
29#define SAMPLE_RATE_KEY                         @"Sample Rate"
30#define VIDEO_DESC_KEY                          @"Video Description"
31
32@implementation NSString (episodeSorting)
33
34- (NSComparisonResult) directoryNameCompare:(NSString *)other
35{
36        return [self compare:other options:NSCaseInsensitiveSearch | NSNumericSearch];
37}
38
39@end
40
41@implementation SapphireMetaData
42
43// Static set of file extensions to filter
44static NSSet *extensions = nil;
45
46+(void)load
47{
48        extensions = [[NSSet alloc] initWithObjects:@"avi", @"mov", @"mpg", @"mpeg", @"wmv",@"mkv", @"flv", @"divx", @"mp4", nil];
49}
50
51- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
52{
53        self = [super init];
54        if(!self)
55                return nil;
56       
57        if(dict == nil)
58                metaData = [NSMutableDictionary new];
59        else
60                metaData = [dict mutableCopy];
61        path = [myPath retain];
62        parent = myParent;
63       
64        return self;
65}
66
67- (void)dealloc
68{
69        [metaData release];
70        [path release];
71        [super dealloc];
72}
73
74- (NSMutableDictionary *)dict
75{
76        return metaData;
77}
78
79- (NSString *)path
80{
81        return path;
82}
83
84- (void)setDelegate:(id <SapphireMetaDataDelegate>)newDelegate
85{
86        delegate = newDelegate;
87}
88
89- (void)writeMetaData
90{
91        [parent writeMetaData];
92}
93
94- (BOOL)isDirectory:(NSString *)fullPath
95{
96        BOOL isDir = NO;
97        return [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir;
98}
99
100- (NSMutableDictionary *)getDisplayedMetaDataInOrder:(NSArray * *)order
101{
102        return nil;
103}
104
105@end
106
107@implementation SapphireMetaDataCollection
108
109- (id)initWithFile:(NSString *)dictionary path:(NSString *)myPath
110{
111        self = [super init];
112        if(!self)
113                return nil;
114       
115        dictionaryPath = [dictionary retain];
116        NSDictionary *metaDict = [NSDictionary dictionaryWithContentsOfFile:dictionary];
117        mainDirectory = [[SapphireDirectoryMetaData alloc] initWithDictionary:metaDict parent:self path:myPath];
118        metaData = [[mainDirectory dict] retain];
119        [metaData setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
120       
121        return self;
122}
123
124- (void)dealloc
125{
126        [mainDirectory release];
127        [dictionaryPath release];
128        [super dealloc];
129}
130
131- (SapphireDirectoryMetaData *)rootDirectory
132{
133        return mainDirectory;
134}
135
136static void makeParentDir(NSFileManager *manager, NSString *dir)
137{
138        NSString *parent = [dir stringByDeletingLastPathComponent];
139       
140        BOOL isDir;
141        if(![manager fileExistsAtPath:parent isDirectory:&isDir])
142                makeParentDir(manager, parent);
143        else if(!isDir)
144                //Can't work with this
145                return;
146       
147        [manager createDirectoryAtPath:dir attributes:nil];
148}
149
150- (void)writeMetaData
151{
152        makeParentDir([NSFileManager defaultManager], [dictionaryPath stringByDeletingLastPathComponent]);
153        [[mainDirectory dict] writeToFile:dictionaryPath atomically:YES];
154}
155
156@end
157
158@interface SapphireDirectoryMetaData (private)
159- (void)reloadDirectoryContents;
160@end
161
162@implementation SapphireDirectoryMetaData
163
164- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
165{
166        self = [super initWithDictionary:dict parent:myParent path:myPath];
167        if(!self)
168                return nil;
169       
170        metaFiles = [metaData objectForKey:FILES_KEY];
171        if(metaFiles == nil)
172                metaFiles = [NSMutableDictionary new];
173        else
174                metaFiles = [metaFiles mutableCopy];
175        [metaData setObject:metaFiles forKey:FILES_KEY];
176        [metaFiles release];
177
178        metaDirs = [metaData objectForKey:DIRS_KEY];
179        if(metaDirs == nil)
180                metaDirs = [NSMutableDictionary new];
181        else
182                metaDirs = [metaDirs mutableCopy];
183        [metaData setObject:metaDirs forKey:DIRS_KEY];
184        [metaDirs release];
185       
186        cachedMetaDirs = [NSMutableDictionary new];
187        cachedMetaFiles = [NSMutableDictionary new];
188       
189        return self;
190}
191
192- (void)dealloc
193{
194        [importTimer invalidate];
195        [importArray release];
196        [cachedMetaDirs release];
197        [cachedMetaFiles release];
198        [files release];
199        [directories release];
200        [super dealloc];
201}
202
203- (void)reloadDirectoryContents
204{
205        [files release];
206        [directories release];
207        files = [NSMutableArray new];
208        directories = [NSMutableArray new];
209        NSMutableArray *fileMetas = [NSMutableArray array];
210       
211        NSArray *names = [[NSFileManager defaultManager] directoryContentsAtPath:path];
212       
213        NSEnumerator *nameEnum = [names objectEnumerator];
214        NSString *name = nil;
215        // Display Menu Items
216        while((name = [nameEnum nextObject]) != nil)
217        {
218                if([name hasPrefix:@"."])
219                        continue;
220                if([name isEqualToString:@"Cover Art"])
221                        continue;
222                //Only accept if it is a directory or right extension
223                NSString *extension = [name pathExtension];
224                if([self isDirectory:[path stringByAppendingPathComponent:name]])
225                        [directories addObject:name];
226                else if([extensions containsObject:extension])
227                        [fileMetas addObject:[self metaDataForFile:name]];
228        }
229        [directories sortUsingSelector:@selector(directoryNameCompare:)];
230        [fileMetas sortUsingSelector:@selector(episodeCompare:)];
231        nameEnum = [fileMetas objectEnumerator];
232        SapphireFileMetaData *fileMeta = nil;
233        while((fileMeta = [nameEnum nextObject]) != nil)
234                [files addObject:[[fileMeta path] lastPathComponent]];
235        [self updateMetaData];
236        if([importArray count])
237                [self writeMetaData];
238        scannedDirectory = YES;
239}
240
241- (NSArray *)files
242{
243        return files;
244}
245
246- (NSArray *)directories
247{
248        return directories;
249}
250
251- (BOOL)hasPredicatedFiles:(SapphirePredicate *)predicate
252{
253        NSArray *filesToScan = files;
254        if(!scannedDirectory)
255                filesToScan = [metaFiles allKeys];
256        NSEnumerator *fileEnum = [filesToScan objectEnumerator];
257        NSString *file = nil;
258        while((file = [fileEnum nextObject]) != nil)
259        {
260                BOOL include = NO;
261                if([metaFiles objectForKey:file] != nil)
262                {
263                        SapphireFileMetaData *meta = [self metaDataForFile:file];
264                        include = [predicate accept:[meta path] meta:meta];
265                }
266                else
267                        include = [predicate accept:[path stringByAppendingPathComponent:file] meta:nil];
268                if(include)
269                        return YES;
270        }
271        return NO;
272}
273
274- (BOOL)hasXMLMetaData:(NSString *)file
275{
276//      NSString *extension = [file pathExtension] ;
277
278        return FALSE ;
279}
280
281- (BOOL)hasPredicatedDirectories:(SapphirePredicate *)predicate
282{
283        NSArray *directoriesToScan = directories;
284        if(!scannedDirectory)
285                directoriesToScan = [metaDirs allKeys];
286        NSEnumerator *directoryEnum = [directoriesToScan objectEnumerator];
287        NSString *directory = nil;
288        while((directory = [directoryEnum nextObject]) != nil)
289        {
290                SapphireDirectoryMetaData *meta = [self metaDataForDirectory:directory];
291                if(![[SapphireSettings sharedSettings] fastSwitching])
292                        [meta reloadDirectoryContents];
293               
294                if([meta hasPredicatedFiles:predicate] || [meta hasPredicatedDirectories:predicate])
295                        return YES;
296        }
297        return NO;
298}
299
300- (NSArray *)predicatedFiles:(SapphirePredicate *)predicate
301{
302        NSMutableArray *ret = [NSMutableArray array];
303        NSArray *filesToScan = files;
304        if(!scannedDirectory)
305                filesToScan = [metaFiles allKeys];
306        NSEnumerator *fileEnum = [filesToScan objectEnumerator];
307        NSString *file = nil;
308        while((file = [fileEnum nextObject]) != nil)
309        {
310                BOOL include = NO;
311                if([metaFiles objectForKey:file] != nil)
312                {
313                        SapphireFileMetaData *meta = [self metaDataForFile:file];
314                        include = [predicate accept:[meta path] meta:meta];
315                }
316                else
317                        include = [predicate accept:[path stringByAppendingPathComponent:file] meta:nil];
318                if(include)
319                        [ret addObject:file];
320        }
321        return ret;
322}
323- (NSArray *)predicatedDirectories:(SapphirePredicate *)predicate
324{
325        NSMutableArray *ret = [NSMutableArray array];
326        NSArray *directoriesToScan = directories;
327        if(!scannedDirectory)
328                directoriesToScan = [metaDirs allKeys];
329        NSEnumerator *directoryEnum = [directoriesToScan objectEnumerator];
330        NSString *directory = nil;
331        while((directory = [directoryEnum nextObject]) != nil)
332        {
333                SapphireDirectoryMetaData *meta = [self metaDataForDirectory:directory];
334                if(![[SapphireSettings sharedSettings] fastSwitching])
335                        [meta reloadDirectoryContents];
336
337                if([meta hasPredicatedFiles:predicate] || [meta hasPredicatedDirectories:predicate])
338                        [ret addObject:directory];
339        }
340        return ret;
341}
342
343- (SapphireFileMetaData *)metaDataForFile:(NSString *)file
344{
345        SapphireFileMetaData *ret = [cachedMetaFiles objectForKey:file];
346        if(ret == nil)
347        {
348                ret = [[SapphireFileMetaData alloc] initWithDictionary:[metaFiles objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
349                [metaFiles setObject:[ret dict] forKey:file];
350                [cachedMetaFiles setObject:ret forKey:file];
351                [ret autorelease];
352        }
353        return ret;
354}
355
356- (SapphireDirectoryMetaData *)metaDataForDirectory:(NSString *)file
357{
358        SapphireDirectoryMetaData *ret = [cachedMetaDirs objectForKey:file];
359        if(ret == nil)
360        {
361                ret = [[SapphireDirectoryMetaData alloc] initWithDictionary:[metaDirs objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
362                [metaDirs setObject:[ret dict] forKey:file];
363                [cachedMetaDirs setObject:ret forKey:file];
364                [ret autorelease];             
365        }
366        return ret;
367}
368
369- (BOOL)pruneMetaData
370{
371        BOOL ret = NO;
372        NSSet *existingSet = [NSSet setWithArray:files];
373        NSArray *metaArray = [metaFiles allKeys];
374        NSMutableSet *pruneSet = [NSMutableSet setWithArray:metaArray];
375       
376        [pruneSet minusSet:existingSet];
377        if([pruneSet anyObject] != nil)
378        {
379                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
380                NSString *pruneKey = nil;
381                while((pruneKey = [pruneEnum nextObject]) != nil)
382                        [metaFiles removeObjectForKey:pruneKey];
383                ret = YES;             
384        }
385       
386        existingSet = [NSSet setWithArray:directories];
387        metaArray = [metaDirs allKeys];
388        pruneSet = [NSMutableSet setWithArray:metaArray];
389       
390        [pruneSet minusSet:existingSet];
391        if([pruneSet anyObject] != nil)
392        {
393                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
394                NSString *pruneKey = nil;
395                while((pruneKey = [pruneEnum nextObject]) != nil)
396                        [metaDirs removeObjectForKey:pruneKey];
397                ret = YES;
398        }
399       
400        return ret;
401}
402
403- (BOOL)updateMetaData
404{
405        NSEnumerator *fileEnum = [files objectEnumerator];
406        NSString *fileName = nil;
407        importArray = [[NSMutableArray alloc] init];
408        while((fileName = [fileEnum nextObject]) != nil)
409        {
410                NSDictionary *fileMeta = [metaFiles objectForKey:fileName];
411                if(fileMeta == nil)
412                {
413                        [self metaDataForFile:fileName];
414                        [importArray addObject:fileName];
415                }
416                else
417                {
418                        NSString *filePath = [path stringByAppendingPathComponent:fileName];
419                        struct stat sb;
420                        memset(&sb, 0, sizeof(struct stat));
421                        stat([filePath fileSystemRepresentation], &sb);
422                        long modTime = sb.st_mtimespec.tv_sec;
423                        if([[fileMeta objectForKey:MODIFIED_KEY] intValue] != modTime || [[fileMeta objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
424                                [importArray addObject:fileName];
425                }
426        }
427        return NO;
428}
429
430- (void)processFiles:(NSTimer *)timer
431{
432        NSString *file = [importArray objectAtIndex:0];
433       
434        [[self metaDataForFile:file] updateMetaData];
435       
436        [self writeMetaData];
437        [delegate updateCompleteForFile:file];
438       
439        [importArray removeObjectAtIndex:0];
440        [self resumeImport];
441}
442
443- (void)cancelImport
444{
445        [importTimer invalidate];
446        importTimer = nil;
447}
448
449- (void)resumeImport
450{
451        [importTimer invalidate];
452        if([importArray count])
453                importTimer = [NSTimer scheduledTimerWithTimeInterval:1.1 target:self selector:@selector(processFiles:) userInfo:nil repeats:NO];
454        else
455        {
456                importTimer = nil;
457                [importArray release];
458                importArray = nil;
459        }
460}
461
462- (void)resumeDelayedImport
463{
464        [importTimer invalidate];
465        if([importArray count])
466                importTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(resumeImport) userInfo:nil repeats:NO];
467        else
468                importTimer = nil;
469}
470
471- (SapphireMetaData *)metaDataForSubPath:(NSString *)subPath
472{
473        NSArray *components = [subPath pathComponents];
474        if(![components count])
475                return self;
476        NSString *file = [components objectAtIndex:0];
477       
478        if([self isDirectory:[path stringByAppendingPathComponent:file]])
479        {
480                NSMutableArray *newComp = [components mutableCopy];
481                [newComp removeObjectAtIndex:0];
482                [newComp autorelease];
483                SapphireDirectoryMetaData *nextLevel = [self metaDataForDirectory:file];
484                return [nextLevel metaDataForSubPath:[NSString pathWithComponents:newComp]];
485        }
486        else if([components count] > 1)
487                return nil;
488        return [self metaDataForFile:file];
489}
490
491- (NSArray *)subFileMetas
492{
493        NSMutableArray *ret = [[NSMutableArray alloc] init];
494        [self reloadDirectoryContents];
495        NSEnumerator *dirEnum = [directories objectEnumerator];
496        NSString *dir = nil;
497        while((dir = [dirEnum nextObject]) != nil)
498        {
499                SapphireDirectoryMetaData *dirMeta = [self metaDataForDirectory:dir];
500                if(dirMeta != nil)
501                        [ret addObjectsFromArray:[dirMeta subFileMetas]];
502        }
503        NSEnumerator *fileEnum = [files objectEnumerator];
504        NSString *file = nil;
505        while((file = [fileEnum nextObject]) != nil)
506        {
507                SapphireFileMetaData *fileMeta = [self metaDataForFile:file];
508                if(fileMeta != nil)
509                        [ret addObject:fileMeta];
510        }
511        return [ret autorelease];
512}
513
514- (void)scanForNewFiles
515{
516        [self reloadDirectoryContents];
517        NSEnumerator *dirEnum = [directories objectEnumerator];
518        NSString *dir = nil;
519        while((dir = [dirEnum nextObject]) != nil)
520        {
521                SapphireDirectoryMetaData *dirMeta = [self metaDataForDirectory:dir];
522                if(dirMeta != nil)
523                        [dirMeta scanForNewFiles];
524        }
525        NSEnumerator *fileEnum = [files objectEnumerator];
526        NSString *file = nil;
527        while((file = [fileEnum nextObject]) != nil)
528                [self metaDataForFile:file];
529}
530
531- (void)setupFiles:(NSArray * *)filesToScan andDirectories:(NSArray * *)directoriesToScan arraysForPredicate:(SapphirePredicate *)predicate
532{
533        if(predicate)
534        {
535                *filesToScan = [self predicatedFiles:predicate];
536                *directoriesToScan = [self predicatedDirectories:predicate];
537        }
538        else if(!scannedDirectory)
539        {
540                //Likely haven't scanned the directory yet, so use cached
541                *filesToScan = [metaFiles allKeys];
542                *directoriesToScan = [metaDirs allKeys];
543        }
544}
545
546- (BOOL)checkResult:(BOOL)result recursivelyOnFiles:(NSInvocation *)fileInv forPredicate:(SapphirePredicate *)predicate
547{
548        NSArray *filesToScan = files;
549        NSArray *directoriesToScan = directories;
550        [self setupFiles:&filesToScan andDirectories:&directoriesToScan arraysForPredicate:predicate];
551        NSEnumerator *fileEnum = [filesToScan objectEnumerator];
552        NSString *file = nil;
553        while((file = [fileEnum nextObject]) != nil)
554        {
555                [fileInv invokeWithTarget:[self metaDataForFile:file]];
556                BOOL result = NO;
557                [fileInv getReturnValue:&result];
558                if(result == result)
559                        return result;
560        }
561
562        NSEnumerator *dirEnum = [directoriesToScan objectEnumerator];
563        NSString *dir = nil;
564        while((dir = [dirEnum nextObject]) != nil)
565                if([[self metaDataForDirectory:dir] checkResult:result recursivelyOnFiles:fileInv forPredicate:predicate] == result)
566                        return result;
567       
568        return !result;
569}
570
571- (void)invokeRecursivelyOnFiles:(NSInvocation *)fileInv withPredicate:(SapphirePredicate *)predicate
572{
573        [self reloadDirectoryContents];
574        NSEnumerator *dirEnum = [directories objectEnumerator];
575        NSString *dir = nil;
576        while((dir = [dirEnum nextObject]) != nil)
577                [[self metaDataForDirectory:dir] invokeRecursivelyOnFiles:fileInv withPredicate:predicate];
578       
579        NSEnumerator *fileEnum = [files objectEnumerator];
580        NSString *file = nil;
581        while((file = [fileEnum nextObject]) != nil)
582        {
583                SapphireFileMetaData *fileMeta = [self metaDataForFile:file];
584                if(!predicate || [predicate accept:[fileMeta path] meta:fileMeta])
585                        [fileInv invokeWithTarget:fileMeta];
586        }
587}
588
589- (BOOL)watchedForPredicate:(SapphirePredicate *)predicate
590{
591        SEL select = @selector(watched);
592        NSInvocation *fileInv = [NSInvocation invocationWithMethodSignature:[[SapphireFileMetaData class] instanceMethodSignatureForSelector:select]];
593        [fileInv setSelector:select];
594        return [self checkResult:NO recursivelyOnFiles:fileInv forPredicate:predicate];
595}
596
597- (void)setWatched:(BOOL)watched predicate:(SapphirePredicate *)predicate
598{
599        SEL select = @selector(setWatched:);
600        NSInvocation *fileInv = [NSInvocation invocationWithMethodSignature:[[SapphireFileMetaData class] instanceMethodSignatureForSelector:select]];
601        [fileInv setSelector:select];
602        [fileInv setArgument:&watched atIndex:2];
603        [self invokeRecursivelyOnFiles:fileInv withPredicate:predicate];
604}
605
606- (BOOL)favoriteForPredicate:(SapphirePredicate *)predicate
607{
608        SEL select = @selector(favorite);
609        NSInvocation *fileInv = [NSInvocation invocationWithMethodSignature:[[SapphireFileMetaData class] instanceMethodSignatureForSelector:select]];
610        [fileInv setSelector:select];
611        return [self checkResult:YES recursivelyOnFiles:fileInv forPredicate:predicate];       
612}
613
614- (void)setFavorite:(BOOL)favorite predicate:(SapphirePredicate *)predicate
615{
616        SEL select = @selector(setFavorite:);
617        NSInvocation *fileInv = [NSInvocation invocationWithMethodSignature:[[SapphireFileMetaData class] instanceMethodSignatureForSelector:select]];
618        [fileInv setSelector:select];
619        [fileInv setArgument:&favorite atIndex:2];
620        [self invokeRecursivelyOnFiles:fileInv withPredicate:predicate];
621}
622
623- (void)setToImportFromTVForPredicate:(SapphirePredicate *)predicate
624{
625        SEL select = @selector(setToImportFromTV);
626        NSInvocation *fileInv = [NSInvocation invocationWithMethodSignature:[[SapphireFileMetaData class] instanceMethodSignatureForSelector:select]];
627        [fileInv setSelector:select];
628        [self invokeRecursivelyOnFiles:fileInv withPredicate:predicate];
629}
630
631- (NSMutableDictionary *)getDisplayedMetaDataInOrder:(NSArray * *)order;
632{
633        if(order != nil)
634                *order = [NSArray arrayWithObject:META_TITLE_KEY];
635        return [NSMutableDictionary dictionaryWithObjectsAndKeys:
636                [path lastPathComponent], META_TITLE_KEY,
637                nil];
638}
639
640@end
641
642@interface SapphireFileMetaData (private)
643- (void)constructCombinedData;
644- (void)combinedDataChanged;
645@end
646
647@implementation SapphireFileMetaData
648
649static NSDictionary *metaDataSubstitutions = nil;
650static NSSet *displayedMetaData = nil;
651static NSArray *displayedMetaDataOrder = nil;
652
653+ (void) initialize
654{
655        metaDataSubstitutions = [[NSDictionary alloc] initWithObjectsAndKeys:
656                //These substitute keys in the meta data to nicer display keys
657                @"Video", VIDEO_DESC_KEY,
658                @"Audio", AUDIO_DESC_KEY,
659                nil];
660        displayedMetaDataOrder = [NSArray arrayWithObjects:
661                //These are not shown in the list
662                META_RATING_KEY,
663                META_DESCRIPTION_KEY,
664                META_COPYRIGHT_KEY,
665                META_TITLE_KEY,
666                META_SHOW_AIR_DATE,
667                //These are displayed as line items
668                META_EPISODE_AND_SEASON_KEY,
669                META_SEASON_NUMBER_KEY,
670                META_EPISODE_NUMBER_KEY,
671                SIZE_KEY,
672                DURATION_KEY,
673                VIDEO_DESC_KEY,
674                AUDIO_DESC_KEY,
675                nil];
676        displayedMetaData = [[NSSet alloc] initWithArray:displayedMetaDataOrder];
677       
678        int excludedKeys = 5;
679        NSMutableArray *modified = [[displayedMetaDataOrder subarrayWithRange:NSMakeRange(excludedKeys, [displayedMetaDataOrder count] - excludedKeys)] mutableCopy];
680       
681        int i;
682        for(i=0; i<[modified count]; i++)
683        {
684                NSString *newKey = [metaDataSubstitutions objectForKey:[modified objectAtIndex:i]];
685                if(newKey != nil)
686                        [modified replaceObjectAtIndex:i withObject:newKey];
687        }
688        displayedMetaDataOrder = [[NSArray alloc] initWithArray:modified];
689        [modified release];
690}
691
692- (void)dealloc
693{
694        [combinedInfo release];
695        [super dealloc];
696}
697
698- (BOOL) updateMetaData
699{
700        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
701        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
702        BOOL updated =FALSE;
703       
704        if(props == nil)
705                //No file
706                return FALSE;
707       
708        if(modTime != [self modified] || [[metaData objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
709        {
710                updated=TRUE ;
711                NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
712               
713                [fileMeta setObject:[NSNumber numberWithInt:modTime] forKey:MODIFIED_KEY];
714                [fileMeta setObject:[props objectForKey:NSFileSize] forKey:SIZE_KEY];
715                [fileMeta setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
716               
717                NSError *error = nil;
718                QTMovie *movie = [QTMovie movieWithFile:path error:&error];
719                QTTime duration = [movie duration];
720                [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:DURATION_KEY];
721                NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
722                NSNumber *audioSampleRate = nil;
723                if([audioTracks count])
724                {
725                        QTTrack *track = [audioTracks objectAtIndex:0];
726                        QTMedia *media = [track media];
727                        audioSampleRate = [media attributeForKey:QTMediaTimeScaleAttribute];
728                        if(media != nil)
729                        {
730                                Media qtMedia = [media quickTimeMedia];
731                                Handle sampleDesc = NewHandle(1);
732                                GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
733                                CFStringRef userText = nil;
734                                ByteCount       propSize = 0;
735                                QTSoundDescriptionGetProperty((SoundDescriptionHandle)sampleDesc, kQTPropertyClass_SoundDescription, kQTSoundDescriptionPropertyID_UserReadableText, sizeof(userText), &userText, &propSize);
736                                DisposeHandle(sampleDesc);
737                               
738                                if(userText != nil)
739                                {
740                                        [fileMeta setObject:(NSString *)userText forKey:AUDIO_DESC_KEY];
741                                        CFRelease(userText);
742                                }
743                        }
744                }
745                if(audioSampleRate != nil)
746                        [fileMeta setObject:audioSampleRate forKey:SAMPLE_RATE_KEY];
747                NSArray *videoTracks = [movie tracksOfMediaType:@"vide"];
748                if([videoTracks count])
749                {
750                        QTTrack *track = [videoTracks objectAtIndex:0];
751                        QTMedia *media = [track media];
752                        if(media != nil)
753                        {
754                                Media qtMedia = [media quickTimeMedia];
755                                Handle sampleDesc = NewHandle(1);
756                                GetMediaSampleDescription(qtMedia, 1, (SampleDescriptionHandle)sampleDesc);
757                                CFStringRef userText = nil;
758                                ByteCount       propSize = 0;
759                                ICMImageDescriptionGetProperty((ImageDescriptionHandle)sampleDesc, kQTPropertyClass_ImageDescription, kICMImageDescriptionPropertyID_SummaryString, sizeof(userText), &userText, &propSize);
760                                DisposeHandle(sampleDesc);
761                               
762                                if(userText != nil)
763                                {
764                                        [fileMeta setObject:(NSString *)userText forKey:VIDEO_DESC_KEY];
765                                        CFRelease(userText);
766                                }
767                        }
768                }
769                [metaData addEntriesFromDictionary:fileMeta];
770        }
771        return updated ;
772}
773
774- (int)modified
775{
776        return [[metaData objectForKey:MODIFIED_KEY] intValue];
777}
778
779- (BOOL)watched
780{
781        return [[metaData objectForKey:WATCHED_KEY] boolValue];
782}
783
784- (void)setWatched:(BOOL)watched
785{
786        [metaData setObject:[NSNumber numberWithBool:watched] forKey:WATCHED_KEY];
787}
788
789- (BOOL)favorite
790{
791        return [[metaData objectForKey:FAVORITE_KEY] boolValue];
792}
793
794- (void)setFavorite:(BOOL)favorite
795{
796        [metaData setObject:[NSNumber numberWithBool:favorite] forKey:FAVORITE_KEY];
797}
798
799- (unsigned int)resumeTime
800{
801        return [[metaData objectForKey:RESUME_KEY] unsignedIntValue];
802}
803
804- (void)setResumeTime:(unsigned int)resumeTime
805{
806        [metaData setObject:[NSNumber numberWithUnsignedInt:resumeTime] forKey:RESUME_KEY];
807}
808
809- (long long)size
810{
811        return [[metaData objectForKey:SIZE_KEY] longLongValue];
812}
813
814- (float)duration
815{
816        return [[metaData objectForKey:DURATION_KEY] floatValue];
817}
818
819- (int)sampleRate
820{
821        return [[metaData objectForKey:SAMPLE_RATE_KEY] intValue];
822}
823
824- (void)constructCombinedData
825{
826        if(combinedInfo != nil)
827                return;
828        NSMutableDictionary *ret = [metaData mutableCopy];
829        [ret addEntriesFromDictionary:[ret objectForKey:META_TVRAGE_IMPORT_KEY]];
830        [ret addEntriesFromDictionary:[ret objectForKey:META_XML_IMPORT_KEY]];
831        combinedInfo = ret;
832}
833
834- (void)combinedDataChanged
835{
836        [combinedInfo release];
837        combinedInfo = nil;
838}
839
840- (long)importedTimeFromSource:(NSString *)source
841{
842        return [[[metaData objectForKey:source] objectForKey:MODIFIED_KEY] longValue];
843}
844
845- (void)setToImportFromSource:(NSString *)source
846{
847        [metaData removeObjectForKey:source];
848        [self combinedDataChanged];
849}
850
851- (void)importInfo:(NSMutableDictionary *)newMeta fromSource:(NSString *)source withTime:(long)modTime
852{
853        [newMeta setObject:[NSNumber numberWithInt:modTime] forKey:MODIFIED_KEY];
854        [metaData setObject:newMeta forKey:source];
855        [self combinedDataChanged];
856}
857
858- (int)episodeNumber
859{
860        [self constructCombinedData];
861        return [[combinedInfo objectForKey:META_EPISODE_NUMBER_KEY] intValue] ;
862}
863
864- (int)seasonNumber
865{
866        [self constructCombinedData];
867        return [[combinedInfo objectForKey:META_SEASON_NUMBER_KEY] intValue];
868}
869
870- (NSString *)episodeTitle
871{
872        [self constructCombinedData];
873        return [combinedInfo objectForKey:META_TITLE_KEY] ;
874}
875
876- (NSString *)sizeString
877{
878        float size = [self size];
879        if(size == 0)
880                return @"-";
881        char letter = ' ';
882        if(size >= 1024000)
883        {
884                if(size >= 1024*1024000)
885                {
886                        size /= 1024 * 1024 * 1024;
887                        letter = 'G';
888                }
889                else
890                {
891                        size /= 1024 * 1024;
892                        letter = 'M';
893                }
894        }
895        else if (size >= 1000)
896        {
897                size /= 1024;
898                letter = 'K';
899        }
900        return [NSString stringWithFormat:@"%.1f%cB", size, letter];   
901}
902
903- (NSMutableDictionary *)getDisplayedMetaDataInOrder:(NSArray * *)order;
904{
905        NSString *name = [path lastPathComponent];
906        int duration = [self duration];
907        int secs = duration % 60;
908        int mins = (duration /60) % 60;
909        int hours = duration / 3600;
910        NSString *durationStr = nil;
911        if(hours != 0)
912                durationStr = [NSString stringWithFormat:@"%d:%02d:%02d", hours, mins, secs];
913        else if (mins != 0)
914                durationStr = [NSString stringWithFormat:@"%d:%02d", mins, secs];
915        else
916                durationStr = [NSString stringWithFormat:@"%ds", secs];
917        if(order != nil)
918                *order = displayedMetaDataOrder;
919        [self constructCombinedData];
920        NSMutableDictionary *ret = [combinedInfo mutableCopy];
921        //Pretty this up now
922        NSMutableSet *currentKeys = [NSMutableSet setWithArray:[ret allKeys]];
923        [currentKeys minusSet:displayedMetaData];
924        [ret removeObjectsForKeys:[currentKeys allObjects]];
925       
926        NSEnumerator *subEnum = [metaDataSubstitutions keyEnumerator];
927        NSString *key = nil;
928        while((key = [subEnum nextObject]) != nil)
929        {
930                NSString *value = [ret objectForKey:key];
931                if(value != nil)
932                {
933                        [ret setObject:value forKey:[metaDataSubstitutions objectForKey:key]];
934                        [ret removeObjectForKey:key];
935                }
936        }
937        if([self duration])
938        {
939                if([self size])
940                {
941                        [ret setObject:[NSString stringWithFormat:@"%@ (%@)", durationStr, [self sizeString]] forKey:DURATION_KEY];
942                        [ret removeObjectForKey:SIZE_KEY];
943                }
944                else
945                        [ret setObject:durationStr forKey:DURATION_KEY];
946        }
947        else if([self size])
948                [ret setObject:[self sizeString] forKey:SIZE_KEY];
949        else
950                [ret removeObjectForKey:SIZE_KEY];
951               
952        if([ret objectForKey:META_TITLE_KEY] == nil)
953                [ret setObject:name forKey:META_TITLE_KEY];\
954        int season = [self seasonNumber];
955        int ep = [self episodeNumber];
956        if(season != 0 && ep != 0)
957                [ret setObject:[NSString stringWithFormat:@"%d / %d", season, ep] forKey:META_EPISODE_AND_SEASON_KEY];
958        return ret;
959}
960
961// Custom TV Episode handler
962- (NSComparisonResult) episodeCompare:(SapphireFileMetaData *)other
963{
964        int myNum = [self seasonNumber];
965        int theirNum = [other seasonNumber];
966        if(myNum == 0)
967                myNum = INT_MAX;
968        if(theirNum == 0)
969                theirNum = INT_MAX;
970        if(myNum > theirNum)
971                return NSOrderedDescending;
972        if(theirNum > myNum)
973                return NSOrderedAscending;
974       
975        myNum = [self episodeNumber];
976        theirNum = [other episodeNumber];
977        if(myNum == 0)
978                myNum = INT_MAX;
979        if(theirNum == 0)
980                theirNum = INT_MAX;
981        if(myNum > theirNum)
982                return NSOrderedDescending;
983        if(theirNum > myNum)
984                return NSOrderedAscending;
985        return [[path lastPathComponent] compare:[[other path] lastPathComponent] options:NSCaseInsensitiveSearch | NSNumericSearch];
986}
987
988@end
Note: See TracBrowser for help on using the repository browser.