source: trunk/SapphireMetaData.m @ 20

Last change on this file since 20 was 20, checked in by pmerrill, 10 years ago

Added Controller for Populating Show Data
Added icon update to settings menu options
Code Cleanup

File size: 14.1 KB
Line 
1//
2//  SapphireMetaData.m
3//  Sapphire
4//
5//  Created by Graham Booker on 6/22/07.
6//  Copyright 2007 __MyCompanyName__. All rights reserved.
7//
8
9#import "SapphireMetaData.h"
10#import <QTKit/QTKit.h>
11
12//Structure Specific Keys
13#define FILES_KEY                                       @"Files"
14#define DIRS_KEY                                        @"Dirs"
15#define META_VERSION_KEY                        @"Version"
16#define META_VERSION                            1
17
18//File Specific Keys
19#define MODIFIED_KEY                            @"Modified"
20#define WATCHED_KEY                             @"Watched"
21#define FAVORITE_KEY                            @"Favorite"
22#define RESUME_KEY                              @"Resume Time"
23#define SIZE_KEY                                        @"Size"
24#define DURATION_KEY                            @"Duration"
25#define SAMPLE_RATE_KEY                 @"Sample Rate"
26
27//TV Show Specific Keys
28#define EPISODE_NUMBER_KEY              @"Episode Number"
29#define EPISODE_TITLE_KEY                       @"Episode Title"
30#define SEASON_NUMBER_KEY               @"Season"
31#define SHOW_NAME_KEY                   @"Show Name"
32#define SHOW_DESCRIPTION_KEY    @"Show Description"
33#define SHOW_AIR_DATE                   @"Air Date"
34
35
36@implementation NSString (episodeSorting)
37
38// Custom TV Episode handler
39- (NSComparisonResult) episodeCompare:(NSString *)other
40{
41        return [self compare:other options:NSCaseInsensitiveSearch | NSNumericSearch];
42}
43
44@end
45
46@interface SapphireFileMetaData (private)
47- (void)updateMetaData;
48@end
49
50@implementation SapphireMetaData
51
52// Static set of file extensions to filter
53static NSArray *extensions = nil;
54
55+(void)load
56{
57        extensions = [[NSArray alloc] initWithObjects:@"avi", @"mov", @"mpg", @"wmv",@"mkv", nil];
58}
59
60- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
61{
62        self = [super init];
63        if(!self)
64                return nil;
65       
66        else if(dict == nil)
67                metaData = [NSMutableDictionary new];
68        else
69                metaData = [dict mutableCopy];
70        path = [myPath retain];
71        parent = myParent;
72       
73        return self;
74}
75
76- (void)dealloc
77{
78        [metaData release];
79        [path release];
80        [super dealloc];
81}
82
83- (NSMutableDictionary *)dict
84{
85        return metaData;
86}
87
88- (NSString *)path
89{
90        return path;
91}
92
93- (void)setDelegate:(id <SapphireMetaDataDelegate>)newDelegate
94{
95        delegate = newDelegate;
96}
97
98- (void)writeMetaData
99{
100        [parent writeMetaData];
101}
102
103- (void)cancelImport
104{
105}
106
107- (void)resumeImport
108{
109}
110
111- (BOOL)isDirectory:(NSString *)fullPath
112{
113        BOOL isDir = NO;
114        return [[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir;
115}
116
117@end
118
119@implementation SapphireMetaDataCollection
120
121- (id)initWithFile:(NSString *)dictionary path:(NSString *)myPath
122{
123        self = [super init];
124        if(!self)
125                return nil;
126       
127        NSMutableDictionary *mainMetaDictionary = [[NSDictionary dictionaryWithContentsOfFile:dictionary] mutableCopy];
128        if(mainMetaDictionary == nil)
129        {
130                mainMetaDictionary = [[NSMutableDictionary alloc] init];
131        }
132        [mainMetaDictionary setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
133        self = [self initWithDictionary:mainMetaDictionary parent:nil path:myPath];
134        if(!self)
135                return nil;
136       
137        dictionaryPath = [dictionary retain];
138        mainDirectory = [[SapphireDirectoryMetaData alloc] initWithDictionary:mainMetaDictionary parent:self path:myPath];
139       
140        return self;
141}
142
143- (void)dealloc
144{
145        [mainDirectory release];
146        [dictionaryPath release];
147        [super dealloc];
148}
149
150- (SapphireDirectoryMetaData *)rootDirectory
151{
152        return mainDirectory;
153}
154
155static void makeParentDir(NSFileManager *manager, NSString *dir)
156{
157        NSString *parent = [dir stringByDeletingLastPathComponent];
158       
159        BOOL isDir;
160        if(![manager fileExistsAtPath:parent isDirectory:&isDir])
161                makeParentDir(manager, parent);
162        else if(!isDir)
163                //Can't work with this
164                return;
165       
166        [manager createDirectoryAtPath:dir attributes:nil];
167}
168
169- (void)writeMetaData
170{
171        makeParentDir([NSFileManager defaultManager], [dictionaryPath stringByDeletingLastPathComponent]);
172        [[mainDirectory dict] writeToFile:dictionaryPath atomically:YES];
173}
174
175@end
176
177@interface SapphireDirectoryMetaData (private)
178- (void)reloadDirectoryContents;
179@end
180
181@implementation SapphireDirectoryMetaData
182
183- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
184{
185        self = [super initWithDictionary:dict parent:myParent path:myPath];
186        if(!self)
187                return nil;
188       
189        metaFiles = [metaData objectForKey:FILES_KEY];
190        if(metaFiles == nil)
191                metaFiles = [NSMutableDictionary dictionary];
192        else
193                metaFiles = [[metaFiles mutableCopy] autorelease];
194        [metaData setObject:metaFiles forKey:FILES_KEY];
195
196        metaDirs = [metaData objectForKey:DIRS_KEY];
197        if(metaDirs == nil)
198                metaDirs = [NSMutableDictionary dictionary];
199        else
200                metaDirs = [[metaDirs mutableCopy] autorelease];
201        [metaData setObject:metaDirs forKey:DIRS_KEY];
202        cachedMetaDirs = [NSMutableDictionary new];
203        cachedMetaFiles = [NSMutableDictionary new];
204       
205        importTimer = nil;
206        [self reloadDirectoryContents];
207        if([self pruneMetaData] || [self updateMetaData])
208                [self writeMetaData];
209       
210        return self;
211}
212
213- (void)dealloc
214{
215        [importTimer invalidate];
216        [importArray release];
217        [cachedMetaDirs release];
218        [cachedMetaFiles release];
219        [files release];
220        [directories release];
221        [super dealloc];
222}
223
224- (void)reloadDirectoryContents
225{
226        [files release];
227        [directories release];
228        files = [NSMutableArray new];
229        directories = [NSMutableArray new];
230       
231        NSArray *names = [[[NSFileManager defaultManager] directoryContentsAtPath:path] retain];
232       
233        NSEnumerator *nameEnum = [names objectEnumerator];
234        NSString *name = nil;
235        // Display Menu Items
236        while((name = [nameEnum nextObject]) != nil)
237        {
238                if([name hasPrefix:@"."])
239                        continue;
240                //Only accept if it is a directory or right extension
241                NSString *extension = [name pathExtension];
242                if([extensions containsObject:extension])
243                        [files addObject:name];
244                else if([self isDirectory:[path stringByAppendingPathComponent:name]])
245                        [directories addObject:name];
246        }
247        [directories sortUsingSelector:@selector(episodeCompare:)];
248        [files sortUsingSelector:@selector(episodeCompare:)];
249}
250
251- (NSArray *)files
252{
253        return files;
254}
255
256- (NSArray *)directories
257{
258        return directories;
259}
260
261- (BOOL)hasPredicatedFiles:(SapphirePredicate *)predicate
262{
263        NSEnumerator *fileEnum = [files objectEnumerator];
264        NSString *file = nil;
265        while((file = [fileEnum nextObject]) != nil)
266        {
267                BOOL include = NO;
268                if([metaFiles objectForKey:file] != nil)
269                {
270                        SapphireFileMetaData *meta = [self metaDataForFile:file];
271                        include = [predicate accept:[meta path] meta:meta];
272                }
273                else
274                        include = [predicate accept:[path stringByAppendingPathComponent:file] meta:nil];
275                if(include)
276                        return YES;
277        }
278        return NO;
279}
280
281- (BOOL)hasPredicatedDirectories:(SapphirePredicate *)predicate
282{
283        NSEnumerator *directoryEnum = [directories objectEnumerator];
284        NSString *directory = nil;
285        while((directory = [directoryEnum nextObject]) != nil)
286        {
287                SapphireDirectoryMetaData *meta = [self metaDataForDirectory:directory];
288                [meta cancelImport];
289               
290                if([meta hasPredicatedFiles:predicate] || [meta hasPredicatedDirectories:predicate])
291                        return YES;
292        }
293        return NO;
294}
295
296- (NSArray *)predicatedFiles:(SapphirePredicate *)predicate
297{
298        NSMutableArray *ret = [NSMutableArray array];
299        NSEnumerator *fileEnum = [files objectEnumerator];
300        NSString *file = nil;
301        while((file = [fileEnum nextObject]) != nil)
302        {
303                BOOL include = NO;
304                if([metaFiles objectForKey:file] != nil)
305                {
306                        SapphireFileMetaData *meta = [self metaDataForFile:file];
307                        include = [predicate accept:[meta path] meta:meta];
308                }
309                else
310                        include = [predicate accept:[path stringByAppendingPathComponent:file] meta:nil];
311                if(include)
312                        [ret addObject:file];
313        }
314        return ret;
315}
316- (NSArray *)predicatedDirectories:(SapphirePredicate *)predicate
317{
318        NSMutableArray *ret = [NSMutableArray array];
319        NSEnumerator *directoryEnum = [directories objectEnumerator];
320        NSString *directory = nil;
321        while((directory = [directoryEnum nextObject]) != nil)
322        {
323                SapphireDirectoryMetaData *meta = [self metaDataForDirectory:directory];
324                [meta cancelImport];
325
326                if([meta hasPredicatedFiles:predicate] || [meta hasPredicatedDirectories:predicate])
327                        [ret addObject:directory];
328        }
329        return ret;
330}
331
332- (SapphireFileMetaData *)metaDataForFile:(NSString *)file
333{
334        SapphireFileMetaData *ret = [cachedMetaFiles objectForKey:file];
335        if(ret == nil)
336        {
337                ret = [[SapphireFileMetaData alloc] initWithDictionary:[metaFiles objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
338                [metaFiles setObject:[ret dict] forKey:file];
339                [cachedMetaFiles setObject:ret forKey:file];
340                [ret autorelease];
341        }
342        return ret;
343}
344
345- (SapphireDirectoryMetaData *)metaDataForDirectory:(NSString *)file
346{
347        SapphireDirectoryMetaData *ret = [cachedMetaDirs objectForKey:file];
348        if(ret == nil)
349        {
350                ret = [[SapphireDirectoryMetaData alloc] initWithDictionary:[metaDirs objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
351                [metaDirs setObject:[ret dict] forKey:file];
352                [cachedMetaDirs setObject:ret forKey:file];
353                [ret autorelease];             
354        }
355        return ret;
356}
357
358- (BOOL)pruneMetaData
359{
360        BOOL ret = NO;
361        NSSet *existingSet = [NSSet setWithArray:files];
362        NSArray *metaArray = [metaFiles allKeys];
363        NSMutableSet *pruneSet = [NSMutableSet setWithArray:metaArray];
364       
365        [pruneSet minusSet:existingSet];
366        if([pruneSet anyObject] != nil)
367        {
368                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
369                NSString *pruneKey = nil;
370                while((pruneKey = [pruneEnum nextObject]) != nil)
371                        [metaFiles removeObjectForKey:pruneKey];
372                ret = YES;             
373        }
374       
375        existingSet = [NSSet setWithArray:directories];
376        metaArray = [metaDirs allKeys];
377        pruneSet = [NSMutableSet setWithArray:metaArray];
378       
379        [pruneSet minusSet:existingSet];
380        if([pruneSet anyObject] != nil)
381        {
382                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
383                NSString *pruneKey = nil;
384                while((pruneKey = [pruneEnum nextObject]) != nil)
385                        [metaDirs removeObjectForKey:pruneKey];
386                ret = YES;
387        }
388       
389        return ret;
390}
391
392- (BOOL)updateMetaData
393{
394        BOOL ret = NO;
395        NSArray *metaArray = [metaDirs allKeys];
396        NSSet *metaSet = [NSSet setWithArray:metaArray];
397        NSMutableSet *newSet = [NSMutableSet setWithArray:directories];
398       
399        [newSet minusSet:metaSet];
400        if([newSet anyObject] != nil)
401        {
402                NSEnumerator *newEnum = [newSet objectEnumerator];
403                NSString *newKey = nil;
404                while((newKey = [newEnum nextObject]) != nil)
405                        [metaDirs setObject:[NSMutableDictionary dictionary] forKey:newKey];
406                ret = YES;
407        }
408       
409        NSEnumerator *fileEnum = [files objectEnumerator];
410        NSString *fileName = nil;
411        importArray = [[NSMutableArray alloc] init];
412        while((fileName = [fileEnum nextObject]) != nil)
413        {
414                NSDictionary *fileMeta = [metaFiles objectForKey:fileName];
415                if(fileMeta == nil)
416                        [importArray addObject:fileName];
417                else
418                {
419                        NSString *filePath = [path stringByAppendingPathComponent:fileName];
420                        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:filePath traverseLink:YES];
421                        NSDate *modDate = [props objectForKey:NSFileModificationDate];
422                        if([[fileMeta objectForKey:MODIFIED_KEY] intValue] != [modDate timeIntervalSince1970] || [[fileMeta objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
423                                [importArray addObject:fileName];
424                }
425        }
426        [self resumeImport];
427       
428        return ret;
429}
430
431- (void)processFiles:(NSTimer *)timer
432{
433        NSString *file = [importArray objectAtIndex:0];
434       
435        [[self metaDataForFile:file] updateMetaData];
436       
437        [self writeMetaData];
438        [delegate updateComplete];
439       
440        [importArray removeObjectAtIndex:0];
441        [self resumeImport];
442}
443
444- (void)cancelImport
445{
446        [importTimer invalidate];
447        importTimer = nil;
448}
449
450- (void)resumeImport
451{
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- (SapphireMetaData *)metaDataForSubPath:(NSString *)subPath
463{
464        NSArray *components = [subPath pathComponents];
465        if(![components count])
466                return self;
467        NSString *file = [components objectAtIndex:0];
468       
469        if([self isDirectory:[directories file]])
470        {
471                NSMutableArray *newComp = [components mutableCopy];
472                [newComp removeObjectAtIndex:0];
473                [newComp autorelease];
474                SapphireDirectoryMetaData *nextLevel = [self metaDataForDirectory:file];
475                return [nextLevel metaDataForSubPath:[NSString pathWithComponents:newComp]];
476        }
477        else if([components count] > 1)
478                return nil;
479        return [self metaDataForFile:file];
480}
481
482- (void)processAllFiles
483{
484        NSEnumerator *fileEnum = [files objectEnumerator];
485        NSString *file = nil;
486        while((file = [fileEnum nextObject]) != nil)
487                [[self metaDataForFile:file] updateMetaData];
488}
489
490- (void)scanDirectory
491{
492        NSEnumerator *dirEnum = [directories objectEnumerator];
493        NSString *directory = nil;
494        while((directory = [dirEnum nextObject]) != nil)
495                [[self metaDataForDirectory:directory] scanDirectory];
496}
497
498- (void)preloadMetaData
499{
500        [self scanDirectory];
501        [self processAllFiles];
502}
503
504@end
505
506@implementation SapphireFileMetaData : SapphireMetaData
507
508- (void) updateMetaData
509{
510        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
511        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
512       
513        if(props == nil)
514                //No file
515                return;
516       
517        if(modTime != [self modified] || [[metaData objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
518        {
519                NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
520               
521                [fileMeta setObject:[NSNumber numberWithInt:modTime] forKey:MODIFIED_KEY];
522                [fileMeta setObject:[props objectForKey:NSFileSize] forKey:SIZE_KEY];
523                [fileMeta setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
524               
525                NSError *error = nil;
526                QTMovie *movie = [QTMovie movieWithFile:path error:&error];
527                QTTime duration = [movie duration];
528                [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:DURATION_KEY];
529                NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
530                NSNumber *audioSampleRate = nil;
531                if([audioTracks count])
532                        [[[audioTracks objectAtIndex:0] media] attributeForKey:QTMediaTimeScaleAttribute];
533                if(audioSampleRate != nil)
534                        [fileMeta setObject:audioSampleRate forKey:SAMPLE_RATE_KEY];
535                [metaData addEntriesFromDictionary:fileMeta];
536        }
537}
538
539- (int)modified
540{
541        return [[metaData objectForKey:MODIFIED_KEY] intValue];
542}
543
544- (BOOL)watched
545{
546        return [[metaData objectForKey:WATCHED_KEY] boolValue];
547}
548
549- (void)setWatched
550{
551        [metaData setObject:[NSNumber numberWithBool:YES] forKey:WATCHED_KEY];
552}
553
554- (long long)size
555{
556        return [[metaData objectForKey:SIZE_KEY] longLongValue];
557}
558
559- (float)duration
560{
561        return [[metaData objectForKey:DURATION_KEY] floatValue];
562}
563
564- (int)sampleRate
565{
566        return [[metaData objectForKey:SAMPLE_RATE_KEY] intValue];
567}
568
569@end
Note: See TracBrowser for help on using the repository browser.