Over the last few years I’ve been teaching myself how to program. Little programs like ultrawar were my first complete programs, but I’ve read and learned a lot since then. I’ve been working on a BitTorrent client for the last few years, mostly as a learning exercise but also with the goal of a real client at the end. I know Mac OS X doesn’t really need another torrent client, since Transmission rules and even the mighty uTorrent has a Mac version now. But I wondered what a client that focused solely on Mac technologies would look like, whether it could be any better than those that strive to live across platforms. So I’ve created BitMaster. Eventually it will be open source from this site, but it’s still in a very prototype stage (I can only read and parse torrent files, hash downloads, and create torrent files), so I’ll be posting code excerpts for now. It’s my hope that having public eyes on the project will help me learn to code and to actually release something, rather than playing with it whenever I’m in the mood. So here’s my first bit of code for public scrutiny: my torrent file parsing code.
Obviously, when I first sat down to develop BitMaster I had to determine what functionality I needed first. I chose torrent file parsing, since it seemed like a simple and ubiquitous part of torrent client development. You can see my first attempt a this code in my previous entry “Learning through doing…” which is quite rough. After feedback from various people online, it now looks like this:
// // JSbdecoder.m // BitMaster // // Created by Jon Shier on 4/9/07. // Copyright 2007 __MyCompanyName__. All rights reserved. // #import "JSbdecoder.h" @implementation JSbdecoder - (NSDictionary *) decodeTorrentFile: (NSString *) pathToTorrentFile { torrentData = [NSData dataWithContentsOfFile: pathToTorrentFile options: NSUncachedRead error: nil]; rawTorrentData = [torrentData bytes]; position = 0; return [self convertObjectDataToStrings:[[self bdecodeData] objectAtIndex: 0]]; } - (NSArray *) bdecodeData { NSMutableArray *methodArray = [NSMutableArray array]; do { if(rawTorrentData[position] == 'i') { position++; [methodArray addObject:[self decodeInt]]; } else if(rawTorrentData[position] == 'l') { position++; [methodArray addObject:[self decodeList]]; } else if(rawTorrentData[position] == 'd') { position++; [methodArray addObject:[self decodeDictionary]]; } else if(isdigit(rawTorrentData[position])) { [methodArray addObject:[self decodeBytes]]; } else if(rawTorrentData[position] == 'e') { position++; //Should only hit this when at the end of lists, dictionaries, or integers. break; } else { NSLog(@"Error decoding torrent. Unexpected character '%c' at position %d.", rawTorrentData[position], position); return methodArray; } } while (position < [torrentData length]); return methodArray; } - (NSNumber *) decodeInt { NSUInteger methodPosition = position; while(rawTorrentData[position] != 'e') { position++; } position++; return [NSNumber numberWithInteger: [[NSString stringWithUTF8String: [[torrentData subdataWithRange: NSMakeRange(methodPosition, (position - methodPosition))] bytes]] integerValue]]; } - (NSData *) decodeBytes { NSUInteger methodPosition = position; while(rawTorrentData[position] != ':') { position++; } NSUInteger bytesLength = [[NSString stringWithUTF8String: [[torrentData subdataWithRange: NSMakeRange(methodPosition, (position - methodPosition))] bytes]] integerValue]; position++; methodPosition = position; position += bytesLength; return [torrentData subdataWithRange: NSMakeRange(methodPosition, bytesLength)]; } - (NSArray *) decodeList { return [self bdecodeData]; } - (NSDictionary *) decodeDictionary { NSMutableDictionary *methodDictionary = [NSMutableDictionary dictionary]; NSArray *methodArray = [NSArray arrayWithArray:[self bdecodeData]]; for(NSUInteger i = 0; i < [methodArray count]; i += 2) { [methodDictionary setObject: [methodArray objectAtIndex: i + 1] forKey: [methodArray objectAtIndex: i]]; } return methodDictionary; } - (id) convertObjectDataToStrings: (id) object { if ([object conformsToProtocol: @protocol(NSFastEnumeration)]) { NSMutableArray *valueArray = [NSMutableArray array]; if ([object isKindOfClass: [NSArray class]]) { for (id enumeratedObject in object) { [valueArray addObject: [self convertObjectDataToStrings: enumeratedObject]]; } return valueArray; } else if ([object isKindOfClass: [NSDictionary class]]) { valueArray = [[object allValues] mutableCopy]; NSMutableArray *keyArray = [NSMutableArray arrayWithArray: [object allKeys]]; for (NSUInteger i = 0; i < [keyArray count]; i++) { [keyArray replaceObjectAtIndex: i withObject: [self convertObjectDataToStrings: [keyArray objectAtIndex: i]]]; } for (NSUInteger i = 0; i < [valueArray count]; i++) { if (![[keyArray objectAtIndex: i] isEqualToString: @"pieces"]) { [valueArray replaceObjectAtIndex: i withObject: [self convertObjectDataToStrings: [valueArray objectAtIndex: i]]]; } } return [NSDictionary dictionaryWithObjects: valueArray forKeys: keyArray]; } } else if ([object isKindOfClass: [NSNumber class]]) { return object; } else if ([object isKindOfClass: [NSData class]]) { return [[NSString alloc] initWithBytes: [object bytes] length: [object length] encoding: NSUTF8StringEncoding]; } else { NSLog(@"Unexpected object %@ of class %@", object, [object class]); } return object; } @end
This code is much faster, cleaner, and accurate than the previous attempt. But it’s still inelegant in a lot of ways. For example, I store the torrent file data within both an NSData object (for the subdata* methods) and a char array (so I can access individual characters to iterate over). Surely there must be a better way, I just haven’t found it. For a few of my methods, there must be a better way of converting to an NSNumber object than getting a subdata, making it a string, and making the string a number. Seems quite obtuse compared to the conversions I’ve seen in other languages.
Well, that’s all for now. I’ll try to get this post out and around so I can get feed back, so feel free to leave comments.
There is also one another Torrent client for Mac. It’s Folx- free Internet download manager and Torrent client for Mac with nice interface and lots of features.