source: trunk/SapphireMetaData.m @ 22

Revision 22, 14.1 KB checked in by gbooker, 7 years ago (diff)

Refactored settings a bit to ease reading/writing
Redraw support enabled now
Potential crasher fix
Removed some dead code

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 invalidate];
457                importTimer = nil;
458                [importArray release];
459                importArray = nil;
460        }
461}
462
463- (SapphireMetaData *)metaDataForSubPath:(NSString *)subPath
464{
465        NSArray *components = [subPath pathComponents];
466        if(![components count])
467                return self;
468        NSString *file = [components objectAtIndex:0];
469       
470        if([self isDirectory:[path stringByAppendingPathComponent:file]])
471        {
472                NSMutableArray *newComp = [components mutableCopy];
473                [newComp removeObjectAtIndex:0];
474                [newComp autorelease];
475                SapphireDirectoryMetaData *nextLevel = [self metaDataForDirectory:file];
476                return [nextLevel metaDataForSubPath:[NSString pathWithComponents:newComp]];
477        }
478        else if([components count] > 1)
479                return nil;
480        return [self metaDataForFile:file];
481}
482
483- (void)processAllFiles
484{
485        NSEnumerator *fileEnum = [files objectEnumerator];
486        NSString *file = nil;
487        while((file = [fileEnum nextObject]) != nil)
488                [[self metaDataForFile:file] updateMetaData];
489}
490
491- (void)scanDirectory
492{
493        NSEnumerator *dirEnum = [directories objectEnumerator];
494        NSString *directory = nil;
495        while((directory = [dirEnum nextObject]) != nil)
496                [[self metaDataForDirectory:directory] scanDirectory];
497}
498
499- (void)preloadMetaData
500{
501        [self scanDirectory];
502        [self processAllFiles];
503}
504
505@end
506
507@implementation SapphireFileMetaData : SapphireMetaData
508
509- (void) updateMetaData
510{
511        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
512        int modTime = [[props objectForKey:NSFileModificationDate] timeIntervalSince1970];
513       
514        if(props == nil)
515                //No file
516                return;
517       
518        if(modTime != [self modified] || [[metaData objectForKey:META_VERSION_KEY] intValue] != META_VERSION)
519        {
520                NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
521               
522                [fileMeta setObject:[NSNumber numberWithInt:modTime] forKey:MODIFIED_KEY];
523                [fileMeta setObject:[props objectForKey:NSFileSize] forKey:SIZE_KEY];
524                [fileMeta setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
525               
526                NSError *error = nil;
527                QTMovie *movie = [QTMovie movieWithFile:path error:&error];
528                QTTime duration = [movie duration];
529                [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:DURATION_KEY];
530                NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
531                NSNumber *audioSampleRate = nil;
532                if([audioTracks count])
533                        [[[audioTracks objectAtIndex:0] media] attributeForKey:QTMediaTimeScaleAttribute];
534                if(audioSampleRate != nil)
535                        [fileMeta setObject:audioSampleRate forKey:SAMPLE_RATE_KEY];
536                [metaData addEntriesFromDictionary:fileMeta];
537        }
538}
539
540- (int)modified
541{
542        return [[metaData objectForKey:MODIFIED_KEY] intValue];
543}
544
545- (BOOL)watched
546{
547        return [[metaData objectForKey:WATCHED_KEY] boolValue];
548}
549
550- (void)setWatched
551{
552        [metaData setObject:[NSNumber numberWithBool:YES] forKey:WATCHED_KEY];
553}
554
555- (long long)size
556{
557        return [[metaData objectForKey:SIZE_KEY] longLongValue];
558}
559
560- (float)duration
561{
562        return [[metaData objectForKey:DURATION_KEY] floatValue];
563}
564
565- (int)sampleRate
566{
567        return [[metaData objectForKey:SAMPLE_RATE_KEY] intValue];
568}
569
570@end
Note: See TracBrowser for help on using the repository browser.