Well, it has been almost a year since I posted about my adoption of Pixen. While I haven’t made the progress I had hoped in that time, there is some to report. My current GitHub repository builds on 10.6 only and only with the 32-bit Intel architecture. I’ve gotten the build down to 3 warnings with all of the options I’ve enabled but the clang static analyzer still finds over a hundred issues with the code. There are also several bugs still present that I’ve been trying to narrow down. And, unfortunately, several major parts of Pixen must be rewritten nearly from scratch to build against the 64-bit APIs. Overall, this has turned out to be a much more complicated project that I’d hoped. Pixen consists of over 25,000 lines of custom code, and that doesn’t count 3rd party libraries, of which there are several. There are an assortment of custom classes used throughout the program, including custom UI elements and even the basic image type is custom. Combine this with very little in the way of documentation or comments and my new job, and I just haven’t had the time (or expertise) to delve as far into the code as necessary. So I apologize to anyone hoping for some Pixen progress.
There is still hope however. Pixen: we can rebuild it, we have the technology. Over the coming weeks, time allowing, I’ll be adding a list of several major issues that need to be addressed for Pixen to move into 2010. These will be posted on my GitHub page, so feel free to comment with any suggestions. I always welcome additional insight into the Pixen code. Even if you don’t implement features or fix bugs, pointing me in the right direction so I can try to do so myself would be great. I’ll be focusing on any bugs that pop up (I already have at least one) and the transition to the 64-bit APIs. One of the biggest changes I’d like to see, even if I don’t do much with the foundation of the program, is a refreshed UI. I am not a designer by any stretch of the imagination, so if anyone is interested in submitting UI ideas or artwork, especially the UI icons, I would greatly appreciate it.
Thanks for reading.
Posted in Cocoa, Pixen, Programming.
By Jon
– August 21, 2010
Just over a week ago, Andy Matuschak, of Sparkle fame, asked for volunteers to adopt his Pixen project. It’s a pixel-based image editor for Mac OS X, and Andy just doesn’t have the time to maintain it or add features for the upcoming 4.0 release. So I offered my services. I figured I may be more successful working on an already established project rather than creating my own from scratch. You can follow my progress at GitHub and some of my code has already been integrated into the main Pixen repository there.
So far my efforts have been limited to project cleanup, modernization, and house keeping. I expect this to be a great learning experience for me going forward and I’ll be posting code updates as I understand more of the Pixen code. Right now my priorities are fixing all of the warnings I created with new compiler options, updating the Obj-C syntax and Cocoa API’s used, and taking a look at the performance. Plus we’ll all be discussing exactly what new features to add for version 4.
Posted in Cocoa, Pixen, Programming.
By Jon
– September 15, 2009
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.
Posted in BitMaster, Cocoa, Computing, Programming.
By Jon
– January 27, 2009
I’ve switched from my own server to using DreamHost, which is faster and has better features. More to come later.
Posted in Jon's Life.
By Jon
– January 23, 2009
I’m finally free. Once AT&T installs my Uverse service (mmm, fiber) Jan. 6, I’ll be able to start posting. And hopefully customizing this site. I have a lot to catch up on.
Posted in Jon's Life.
By Jon
– December 31, 2008
Yes, I’m still alive and kicking, just off the grid for a while. I was able to implement my previous Objective-C code using the new Obj-C2 @property syntax and a better algorithm, so it is much shorter and works better. However, I still seem to have trouble converting all of the strings I need to from NSData to NSString objects. I don’t have access to my Quad right now, so I haven’t been able to work on it in a while, but that’s the last thing I need to fix before torrent file parsing should work.
In other news, I’ve been reading a lot about computer architecture and design, as well as Unix programming on Mac OS X. Once I get my Quad back and upgraded to Leopard, I should be able to start experimenting.
I’ve also updated my wish list, so family members seeking gift ideas should check it out.
Posted in Computing, Jon's Life, Programming.
By Jon
– November 12, 2007
In an effort to teach myself Objective-C and the Cocoa framework, I’ve decided to create a Cocoa BitTorrent client. So far I’m able to parse torrent files (most of the time), hash the file to be downloaded (if it’s already downloaded) and the info dictionary string (big hack), and both scrape and get torrent specific data from trackers. Once this program can actually download a file from a torrent I’ll release the source code under an open source license (likely the Apache license). In the meantime, I’m going to be using whatever resources I can find. That includes IRC channels frequented by various Mac projects (#webkit, #transmission, etc) on irc.freenode.net (which may be how you got here). From time to time I’m going to post some of my code on this blog so I can get feedback from various parties on fixing bugs or implementing features. That’s what this post in about.
Currently, I can parse .torrent files pretty successfully. By turning them into NSDictionary’s I can access their contents very easily. However, I’m not 100% successful yet. Strangely, some torrents, like this one, don’t parse successfully, breaking my later code. It seems as if the contents of the comment and name keys in the file aren’t decoding correctly as UTF8 strings, so the data put into those keys is incorrect. I can’t figure out why that is. I suspect it has something to do with the findBlockRangeAtIndex: method. Here’s the code:
//
// JSbcoder.m
//
// Created by Jon Shier on 3/4/05.
//</code>
<code>#import "JSbcoder.h"
@implementation JSbcoder
- (NSDictionary *) decodeTorrentFileData: (NSData *) torrentData {
//Need error checking. Check that the data starts with d.
const char *torrentFileBytes = [torrentData bytes];
[self setDataLength: [torrentData length]];
return [NSDictionary dictionaryWithDictionary: [self decodeDictionaryFromBytes: torrentFileBytes withRange: NSMakeRange(1, dataLength - 2)]];
}
- (NSDictionary *) convertDictionaryDataToStrings: (NSDictionary *) torrentDictionary {
NSMutableArray *keyArray = [NSMutableArray arrayWithArray: [torrentDictionary allKeys]];
NSMutableArray *valueArray = [NSMutableArray arrayWithArray: [torrentDictionary allValues]];
for (int i = 0; i < [keyArray count]; i++) {
[keyArray replaceObjectAtIndex: i withObject: [NSString stringWithUTF8String: [[keyArray objectAtIndex: i] bytes]]];
}
for (int i = 0; i < [valueArray count]; i++) {
if([[valueArray objectAtIndex: i] isKindOfClass: [NSData class]] && ![[keyArray objectAtIndex: i] isEqualToString: @"pieces"]) {
if(IsEmpty([NSString stringWithUTF8String: [[valueArray objectAtIndex: i] bytes]])) NSLog(@"A value is nil, value array is: %@ nil value at index %i which is %@ string of which is %@\n", valueArray, i, [valueArray objectAtIndex: i], [NSString stringWithUTF8String: [[valueArray objectAtIndex: i] bytes]]);
[valueArray replaceObjectAtIndex: i withObject: [NSString stringWithUTF8String: [[valueArray objectAtIndex: i] bytes]]];
} else if([[valueArray objectAtIndex: i] isKindOfClass: [NSDictionary class]]) {
[valueArray replaceObjectAtIndex: i withObject: [self convertDictionaryDataToStrings: [valueArray objectAtIndex: i]]];
}
}
return [NSDictionary dictionaryWithObjects: valueArray forKeys: keyArray];
}
- (NSNumber *) decodeIntegerFromBytes: (const char*) torrentData withRange: (NSRange) range {
return [NSNumber numberWithInt: [[[[NSString alloc] initWithBytes:torrentData + range.location length:range.length encoding:NSUTF8StringEncoding] autorelease] intValue]];
}
- (NSData *) decodeStringAndReturnDataFromBytes: (const char*) torrentData withRange: (NSRange) range {
return [[NSData dataWithBytes: torrentData length: dataLength] subdataWithRange: range];
}
- (unsigned int) findBlockRangeAtIndex: (unsigned int) index inBytes: (const char *) torrentData {
int j = 0;
int blockLength = 1;
//Need error checking
while (index + blockLength < dataLength) {
blockLength++;
if (torrentData[index + blockLength] == 'i') {
for (j = 1; j < dataLength; j++) {
if (torrentData[index + j + blockLength] == 'e') break;
}
blockLength += j;
} else if (isdigit(torrentData[index + blockLength])) {
NSRange range;
for (j = 0; j < dataLength; j++) {
if (isdigit(torrentData[j + index + blockLength]) && torrentData[j + index + 1 + blockLength] == ':') {
range.location = index + blockLength;
range.length = j + 1;
break;
}
}
blockLength += j + 1;
j = [[[[NSString alloc] initWithBytes:torrentData + range.location length:range.length encoding:NSUTF8StringEncoding] autorelease] intValue];
blockLength += j;
} else if (torrentData[index + blockLength] == 'l') {
blockLength += [self findBlockRangeAtIndex: (index + blockLength) inBytes: torrentData] - 1;
} else if (torrentData[index + blockLength] == 'd') {
blockLength += [self findBlockRangeAtIndex: (index + blockLength) inBytes: torrentData] - 1;
} else if (torrentData[index + blockLength] == 'e') {
break;
}
}
return blockLength + 1;
}
- (NSArray *) decodeListFromBytes: (const char *) torrentData withRange: (NSRange) range {
//Insert error checking
return [NSArray arrayWithArray:[self createArrayFromData: torrentData withRange: range]];
}
- (NSDictionary *) decodeDictionaryFromBytes: (const char *) torrentData withRange: (NSRange) range {
//Need error checking
NSArray *methodArray = [NSArray arrayWithArray:[self createArrayFromData: torrentData withRange: range]];
NSMutableArray *keyArray = [NSMutableArray array];
NSMutableArray *valueArray = [NSMutableArray array];
for(int i = 0; i < [methodArray count]; i += 2) {
if([[methodArray objectAtIndex: i] isKindOfClass: [NSData class]]) {
if(IsEmpty([NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]])) {
[keyArray addObject: [methodArray objectAtIndex: i]];
} else [keyArray addObject: [NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]]];
} else {
[keyArray addObject: [methodArray objectAtIndex: i]];
}
}
for(int i = 1; i < [methodArray count]; i += 2) {
if([[methodArray objectAtIndex: i] isKindOfClass: [NSData class]] ) {
if(IsEmpty([NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]])) {
[valueArray addObject: [methodArray objectAtIndex: i]];
} else [valueArray addObject: [NSString stringWithUTF8String:[[methodArray objectAtIndex: i] bytes]]];
} else {
[valueArray addObject: [methodArray objectAtIndex: i]];
}
}
return [NSDictionary dictionaryWithObjects: valueArray forKeys: keyArray];
}
- (void) setDataLength: (unsigned int) torrentFileDataLength {
dataLength = torrentFileDataLength;
}
- (NSArray *) createArrayFromData: (const char *) torrentData withRange: (NSRange) range {
NSMutableArray *methodArray = [NSMutableArray array];
unsigned int j = 0;
unsigned int k = range.length + range.location;
for (int i = range.location; i <= k; i++) {
if (torrentData[i] == 'i') {
for (j = 1; j < dataLength; j++) {
if (torrentData[i + j] == 'e') break;
}
range.location = i + 1;
range.length = j - 1;
[methodArray addObject: [self decodeIntegerFromBytes: torrentData withRange: range]];
i += j;
} else if (isdigit(torrentData[i])) {
for (j = 0; j < dataLength; j++) {
if (isdigit(torrentData[j + i]) && torrentData[j + i + 1] == ':') {
range.location = i;
range.length = j + 1;
break;
}
}
i += range.length;
j = [[[[NSString alloc] initWithBytes:torrentData + range.location length:range.length encoding:NSUTF8StringEncoding] autorelease] intValue];
range.location += range.length + 1;
range.length = j;
[methodArray addObject: [self decodeStringAndReturnDataFromBytes: torrentData withRange: range]];
i += range.length;
} else if (torrentData[i] == 'l') {
range.location = i + 1;
range.length = [self findBlockRangeAtIndex: i inBytes: torrentData] - 2;
[methodArray addObject: [self decodeListFromBytes: torrentData withRange: range]];
i += range.length + 1;
} else if (torrentData[i] == 'd') {
range.location = i + 1;
range.length = [self findBlockRangeAtIndex: i inBytes: torrentData] - 2;
[methodArray addObject: [self decodeDictionaryFromBytes: torrentData withRange: range]];
i += range.length + 1;
}
}
return methodArray;
}
@end
I know it’s rough and fragile, but anything you can suggest will help. If you want more info on the BitTorrent specification this is the best resource I could find.
Posted in Computing, Programming.
By Jon
– April 5, 2007
After months of downtime due to dual hard drive failure in the server, I’m back online. Much new content will be forthcoming, including Leopard rumors and news.
Posted in Jon's Life.
By Jon
– August 1, 2006