source: trunk/SapphireMetaData.m @ 6

Revision 6, 11.0 KB checked in by gbooker, 7 years ago (diff)

A new main-menu and the browser is now a different class

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        else if([[mainMetaDictionary objectForKey:META_VERSION_KEY] intValue] < META_VERSION)
120        {
121                [mainMetaDictionary removeAllObjects];
122                [mainMetaDictionary setObject:[NSNumber numberWithInt:META_VERSION] forKey:META_VERSION_KEY];
123        }       
124        self = [self initWithDictionary:mainMetaDictionary parent:nil path:myPath];
125        if(!self)
126                return nil;
127       
128        dictionaryPath = [dictionary retain];
129        mainDirectory = [[SapphireDirectoryMetaData alloc] initWithDictionary:mainMetaDictionary parent:self path:myPath];
130       
131        return self;
132}
133
134- (void)dealloc
135{
136        [mainDirectory release];
137        [dictionaryPath release];
138        [super dealloc];
139}
140
141- (SapphireDirectoryMetaData *)rootDirectory
142{
143        return mainDirectory;
144}
145
146static void makeParentDir(NSFileManager *manager, NSString *dir)
147{
148        NSString *parent = [dir stringByDeletingLastPathComponent];
149       
150        BOOL isDir;
151        if(![manager fileExistsAtPath:parent isDirectory:&isDir])
152                makeParentDir(manager, parent);
153        else if(!isDir)
154                //Can't work with this
155                return;
156       
157        [manager createDirectoryAtPath:dir attributes:nil];
158}
159
160- (void)writeMetaData
161{
162        makeParentDir([NSFileManager defaultManager], [dictionaryPath stringByDeletingLastPathComponent]);
163        [metaData writeToFile:dictionaryPath atomically:YES];
164}
165
166@end
167
168@interface SapphireDirectoryMetaData (private)
169- (void)reloadDirectoryContents;
170@end
171
172@implementation SapphireDirectoryMetaData
173
174- (id)initWithDictionary:(NSDictionary *)dict parent:(SapphireMetaData *)myParent path:(NSString *)myPath
175{
176        self = [super initWithDictionary:dict parent:myParent path:myPath];
177        if(!self)
178                return nil;
179       
180        metaFiles = [metaData objectForKey:FILES_KEY];
181        if(metaFiles == nil)
182                metaFiles = [NSMutableDictionary dictionary];
183        else
184                metaFiles = [[metaFiles mutableCopy] autorelease];
185        [metaData setObject:metaFiles forKey:FILES_KEY];
186
187        metaDirs = [metaData objectForKey:DIRS_KEY];
188        if(metaDirs == nil)
189                metaDirs = [NSMutableDictionary dictionary];
190        else
191                metaDirs = [[metaDirs mutableCopy] autorelease];
192        [metaData setObject:metaDirs forKey:DIRS_KEY];
193        cachedMetaDirs = [NSMutableDictionary new];
194        cachedMetaFiles = [NSMutableDictionary new];
195       
196        importTimer = nil;
197        [self reloadDirectoryContents];
198        if([self pruneMetaData] || [self updateMetaData])
199                [self writeMetaData];
200        [directories sortUsingSelector:@selector(episodeCompare:)];
201        [files sortUsingSelector:@selector(episodeCompare:)];
202       
203        return self;
204}
205
206- (void)dealloc
207{
208        [importTimer invalidate];
209        [importArray release];
210        [cachedMetaDirs release];
211        [cachedMetaFiles release];
212        [files release];
213        [directories release];
214        [super dealloc];
215}
216
217- (void)reloadDirectoryContents
218{
219        [files release];
220        [directories release];
221        files = [NSMutableArray new];
222        directories = [NSMutableArray new];
223       
224        NSArray *names = [[[NSFileManager defaultManager] directoryContentsAtPath:path] retain];
225       
226        NSEnumerator *nameEnum = [names objectEnumerator];
227        NSString *name = nil;
228        // Display Menu Items
229        while((name = [nameEnum nextObject]) != nil)
230        {
231                if([name hasPrefix:@"."])
232                        continue;
233                //Only accept if it is a directory or right extension
234                NSString *extension = [name pathExtension];
235                if([extensions containsObject:extension])
236                        [files addObject:name];
237                else if([self isDirectory:[path stringByAppendingPathComponent:name]])
238                        [directories addObject:name];
239        }
240}
241
242- (NSArray *)files
243{
244        return files;
245}
246
247- (NSArray *)directories
248{
249        return directories;
250}
251
252- (SapphireFileMetaData *)metaDataForFile:(NSString *)file
253{
254        SapphireFileMetaData *ret = [cachedMetaFiles objectForKey:file];
255        if(ret == nil)
256        {
257                ret = [[SapphireFileMetaData alloc] initWithDictionary:[metaFiles objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
258                [metaFiles setObject:[ret dict] forKey:file];
259                [cachedMetaFiles setObject:ret forKey:file];
260                [ret autorelease];
261        }
262        return ret;
263}
264
265- (SapphireDirectoryMetaData *)metaDataForDirectory:(NSString *)file
266{
267        SapphireDirectoryMetaData *ret = [cachedMetaDirs objectForKey:file];
268        if(ret == nil)
269        {
270                ret = [[SapphireDirectoryMetaData alloc] initWithDictionary:[metaDirs objectForKey:file] parent:self path:[path stringByAppendingPathComponent:file]];
271                [metaDirs setObject:[ret dict] forKey:file];
272                [cachedMetaDirs setObject:ret forKey:file];
273                [ret autorelease];             
274        }
275        return ret;
276}
277
278- (BOOL)pruneMetaData
279{
280        BOOL ret = NO;
281        NSSet *existingSet = [NSSet setWithArray:files];
282        NSArray *metaArray = [metaFiles allKeys];
283        NSMutableSet *pruneSet = [NSMutableSet setWithArray:metaArray];
284       
285        [pruneSet minusSet:existingSet];
286        if([pruneSet anyObject] != nil)
287        {
288                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
289                NSString *pruneKey = nil;
290                while((pruneKey = [pruneEnum nextObject]) != nil)
291                        [metaFiles removeObjectForKey:pruneKey];
292                ret = YES;             
293        }
294       
295        existingSet = [NSSet setWithArray:directories];
296        metaArray = [metaDirs allKeys];
297        pruneSet = [NSMutableSet setWithArray:metaArray];
298       
299        [pruneSet minusSet:existingSet];
300        if([pruneSet anyObject] != nil)
301        {
302                NSEnumerator *pruneEnum = [pruneSet objectEnumerator];
303                NSString *pruneKey = nil;
304                while((pruneKey = [pruneEnum nextObject]) != nil)
305                        [metaDirs removeObjectForKey:pruneKey];
306                ret = YES;
307        }
308       
309        return ret;
310}
311
312- (BOOL)updateMetaData
313{
314        BOOL ret = NO;
315        NSArray *metaArray = [metaDirs allKeys];
316        NSSet *metaSet = [NSSet setWithArray:metaArray];
317        NSMutableSet *newSet = [NSMutableSet setWithArray:directories];
318       
319        [newSet minusSet:metaSet];
320        if([newSet anyObject] != nil)
321        {
322                NSEnumerator *newEnum = [newSet objectEnumerator];
323                NSString *newKey = nil;
324                while((newKey = [newEnum nextObject]) != nil)
325                        [metaDirs setObject:[NSMutableDictionary dictionary] forKey:newKey];
326                ret = YES;
327        }
328       
329        NSEnumerator *fileEnum = [files objectEnumerator];
330        NSString *fileName = nil;
331        importArray = [[NSMutableArray alloc] init];
332        while((fileName = [fileEnum nextObject]) != nil)
333        {
334                NSDictionary *fileMeta = [metaFiles objectForKey:fileName];
335                if(fileMeta == nil)
336                        [importArray addObject:fileName];
337                else
338                {
339                        NSString *filePath = [path stringByAppendingPathComponent:fileName];
340                        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:filePath traverseLink:YES];
341                        NSDate *modDate = [props objectForKey:NSFileModificationDate];
342                        if([[fileMeta objectForKey:MODIFIED_KEY] intValue] != [modDate timeIntervalSince1970])
343                                [importArray addObject:fileName];
344                }
345        }
346        [self resumeImport];
347       
348        return ret;
349}
350
351- (void)processFiles:(NSTimer *)timer
352{
353        NSString *file = [importArray objectAtIndex:0];
354       
355        [[self metaDataForFile:file] updateMetaData];
356       
357        [self writeMetaData];
358        [delegate updateComplete];
359       
360        [importArray removeObjectAtIndex:0];
361        [self resumeImport];
362}
363
364- (void)cancelImport
365{
366        [importTimer invalidate];
367        importTimer = nil;
368}
369
370- (void)resumeImport
371{
372        if([importArray count])
373                importTimer = [NSTimer scheduledTimerWithTimeInterval:1.1 target:self selector:@selector(processFiles:) userInfo:nil repeats:NO];
374        else
375        {
376                importTimer = nil;
377                [importArray release];
378                importArray = nil;
379        }
380}
381
382- (SapphireMetaData *)metaDataForSubPath:(NSString *)subPath
383{
384        NSArray *components = [subPath pathComponents];
385        NSString *file = [components objectAtIndex:0];
386       
387        if([self isDirectory:file])
388        {
389                NSMutableArray *newComp = [components mutableCopy];
390                [newComp removeObjectAtIndex:0];
391                [newComp autorelease];
392                SapphireDirectoryMetaData *nextLevel = [self metaDataForDirectory:file];
393                return [nextLevel metaDataForSubPath:[NSString pathWithComponents:newComp]];
394        }
395        else if([components count] > 1)
396                return nil;
397        return [self metaDataForFile:file];
398}
399
400- (void)scanDirectory
401{
402       
403}
404
405@end
406
407@implementation SapphireFileMetaData : SapphireMetaData
408
409- (void) updateMetaData
410{
411        NSDictionary *props = [[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES];
412       
413        if(props != nil)
414        {
415                NSMutableDictionary *fileMeta = [NSMutableDictionary dictionary];
416               
417                [fileMeta setObject:[NSNumber numberWithInt:[[props objectForKey:NSFileModificationDate] timeIntervalSince1970]] forKey:MODIFIED_KEY];
418                [fileMeta setObject:[props objectForKey:NSFileSize] forKey:SIZE_KEY];
419               
420                NSError *error = nil;
421                QTMovie *movie = [QTMovie movieWithFile:path error:&error];
422                QTTime duration = [movie duration];
423                [fileMeta setObject:[NSNumber numberWithFloat:(float)duration.timeValue/(float)duration.timeScale] forKey:DURATION_KEY];
424                NSArray *audioTracks = [movie tracksOfMediaType:@"soun"];
425                NSNumber *audioSampleRate = nil;
426                if([audioTracks count])
427                        [[[audioTracks objectAtIndex:0] media] attributeForKey:QTMediaTimeScaleAttribute];
428                if(audioSampleRate != nil)
429                        [fileMeta setObject:audioSampleRate forKey:SAMPLE_RATE_KEY];
430                [metaData addEntriesFromDictionary:fileMeta];
431        }
432}
433
434- (int)modified
435{
436        return [[metaData objectForKey:MODIFIED_KEY] intValue];
437}
438
439- (BOOL)watched
440{
441        return [[metaData objectForKey:WATCHED_KEY] boolValue];
442}
443
444- (void)setWatched
445{
446        [metaData setObject:[NSNumber numberWithBool:YES] forKey:WATCHED_KEY];
447}
448
449- (long long)size
450{
451        return [[metaData objectForKey:SIZE_KEY] longLongValue];
452}
453
454- (float)duration
455{
456        return [[metaData objectForKey:DURATION_KEY] floatValue];
457}
458
459- (int)sampleRate
460{
461        return [[metaData objectForKey:SAMPLE_RATE_KEY] intValue];
462}
463
464@end
Note: See TracBrowser for help on using the repository browser.