Last active
December 10, 2015 09:38
-
-
Save kosugi/4415114 to your computer and use it in GitHub Desktop.
checks missing files (bad “Location” field) in “iTunes Music Library.xml” file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // -*- mode: objc; coding: utf-8 -*- | |
| #import <Foundation/Foundation.h> | |
| static NSString *nvl(NSString *s1, NSString *s2) | |
| { | |
| return s1? s1: s2; | |
| } | |
| @interface Parser: NSObject <NSXMLParserDelegate> | |
| - (void)parse:(NSURL *)url; | |
| @end | |
| @implementation Parser | |
| { | |
| int _depth; // <dict> のネスト度合い | |
| int _targetDepth; | |
| BOOL _shouldStore; | |
| NSString *_keyName; | |
| NSString *_prevElementName; | |
| NSMutableString *_buf; | |
| NSMutableDictionary *_track; | |
| int _numTracks; | |
| NSMutableArray *_missedTracks; | |
| } | |
| static NSCharacterSet *digitCharSet; | |
| + (void)initialize | |
| { | |
| digitCharSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"]; | |
| } | |
| + (BOOL)isDigit:(NSString *)text | |
| { | |
| NSScanner *scanner = [NSScanner localizedScannerWithString:text]; | |
| [scanner setCharactersToBeSkipped:nil]; | |
| return [scanner scanCharactersFromSet:digitCharSet intoString:NULL] && [scanner isAtEnd]; | |
| } | |
| - (void)parse:(NSURL *)url | |
| { | |
| NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; | |
| parser.delegate = self; | |
| [parser parse]; | |
| } | |
| - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError | |
| { | |
| NSLog(@"Error: %ld, Column: %ld, Line: %ld, Description: %@", | |
| [parseError code], | |
| [parser columnNumber], | |
| [parser lineNumber], | |
| [parseError description]); | |
| } | |
| - (void)parserDidStartDocument:(NSXMLParser *)parser | |
| { | |
| _keyName = nil; | |
| _prevElementName = nil; | |
| _shouldStore = NO; | |
| _depth = 0; | |
| _targetDepth = 0; | |
| _buf = [NSMutableString new]; | |
| _track = [NSMutableDictionary new]; | |
| _missedTracks = [NSMutableArray new]; | |
| } | |
| - (void)parserDidEndDocument:(NSXMLParser *)parser | |
| { | |
| printf("%7d tracks\n", _numTracks); | |
| printf("%7ld missed tracks\n", [_missedTracks count]); | |
| for (NSArray *info in _missedTracks) { | |
| printf( | |
| " %s: %s\n", | |
| [[info objectAtIndex:0] cStringUsingEncoding:NSUTF8StringEncoding], | |
| [[info objectAtIndex:1] cStringUsingEncoding:NSUTF8StringEncoding]); | |
| } | |
| } | |
| - (void)store | |
| { | |
| ++_numTracks; | |
| NSString *trackType = [_track objectForKey:@"Track Type"]; | |
| if ([trackType isEqualToString:@"Remote"]) { | |
| return; // Remote Track は Location を持たない | |
| } | |
| if ([trackType isEqualToString:@"URL"]) { | |
| return; // URL Track は Location を持つが存在確認を行なわないことにする | |
| } | |
| NSString *trackId = [_track objectForKey:@"Track ID"]; | |
| NSString *name = [_track objectForKey:@"Name"]; | |
| NSString *location = [_track objectForKey:@"Location"]; | |
| NSURL *url = [NSURL URLWithString:location]; | |
| NSString *path = url.path; | |
| if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) { | |
| [_missedTracks addObject:@[nvl(trackId, @"<>"), nvl(path, nvl(name, @"[]"))]]; | |
| } | |
| } | |
| - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict | |
| { | |
| if ([@"dict" isEqualToString:elementName]) { | |
| ++_depth; | |
| if ([@"key" isEqualToString:_prevElementName] && [Parser isDigit:_keyName]) { | |
| _targetDepth = _depth; | |
| //NSLog(@"Track ID: %@ parsing...", _keyName); | |
| } | |
| } | |
| _shouldStore = 0 < _depth && _targetDepth == _depth; | |
| [_buf setString:@""]; | |
| } | |
| - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName | |
| { | |
| if ([@"key" isEqualToString:elementName]) | |
| { | |
| _keyName = [_buf copy]; | |
| } | |
| else if ([@"dict" isEqualToString:elementName]) | |
| { | |
| --_depth; | |
| if (_shouldStore) { | |
| _shouldStore = NO; | |
| _targetDepth = 0; | |
| [self store]; | |
| [_track removeAllObjects]; | |
| } | |
| } | |
| else if (_shouldStore && [@"key" isEqualToString:_prevElementName]) | |
| { | |
| NSString *value = [NSString stringWithString:_buf]; | |
| [_track setObject:value forKey:_keyName]; | |
| } | |
| _prevElementName = [elementName copy]; | |
| } | |
| - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string | |
| { | |
| [_buf appendString:string]; | |
| } | |
| @end | |
| void doit(NSString *path) | |
| { | |
| NSDate *startTime = [NSDate date]; | |
| [[Parser new] parse:[NSURL fileURLWithPath:path]]; | |
| NSLog(@"XML parse time is %lf (sec)", [[NSDate date] timeIntervalSinceDate:startTime]); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| if (argc != 2) | |
| { | |
| printf("usage: %s: itunes_library.xml\n", argv[0]); | |
| return 1; | |
| } | |
| @autoreleasepool | |
| { | |
| doit([NSString stringWithUTF8String:argv[1]]); | |
| } | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| TARGET = main | |
| OBJS = main.o | |
| CC = clang | |
| CFLAGS = -fobjc-arc -O2 -Wall | |
| LDFLAGS = -framework Foundation | |
| .m.o: | |
| $(CC) $(CFLAGS) -c $< -o $*.o | |
| .PHONY: all | |
| all: $(OBJS) | |
| $(CC) $(LDFLAGS) $(CFLAGS) -o $(TARGET) $(OBJS) | |
| .PHONY: clean | |
| clean: | |
| $(RM) $(TARGET) $(OBJS) |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
ううむ, 微妙だ…….