source: trunk/SapphireMetaData.m @ 14

Revision 14, 13.7 KB checked in by gbooker, 7 years ago (diff)

Metadata versioning on file basis.

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