macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Inside StYNCies
Pages: 1, 2

Hide the Application Menu

Although our project isn't complete, now is a good time for a gut check, so tell Xcode to Build and Go. You should not have any compiler errors, but definitely will have some warnings because there's a lot of method bodies not defined yet. You should be able to run your application, see the symbol up on your status bar and successfully quit the application using the symbol's drop-down menu.



You'll get a runtime message that the syncAndEject: action couldn't be connected, but we already know this. The point is to get our bearings and take care of any typos that have occurred up 'til now. But wait a minute--there's still a full menu when we run the app. One other thing we can knock out right now is backgrounding the app to remove that menu bar. Double-click on Info.plist in the Resources folder of Xcode's left pane to open up the plist editor. Expand Root and click on any of the items in the dictionary. Click on New Sibling up top and add the key "LSUIElement" (Launch Services User Interface Element) with a value of 1. If you don't have Xcode set up to edit plists with the Property List Editor, you can change this to your default in Xcode's File Types preferences, open the Property List Editor in your /Developer/Applications/Utilities folder, or just add in this plain text to the dictionary by viewing it as plain text.

<key>LSUIElement</key>
<string>1</string>

Save the changes, and choose Build and Go in Xcode. Your menu problems have just disappeared--literally.

Finishing off AppController

Let's finish off AppController. Once that's done, all that remains for this installment is to define the IPodUtils class. Check out the method bodies below and copy them into your AppController.m file.

- (void)mountNotification:(NSNotification*)notification 
{
	if ([podUtil deviceIsiPodAtPath:
		[[notification userInfo] 
			objectForKey:@"NSDevicePath"]])
	{
		[self synciPodsAndEject:NO];
	}
}

- (void)synciPodsAndEject:(BOOL)eject
{
	NSArray* iPods = [podUtil connectediPods];
	NSEnumerator *enumerator = [iPods objectEnumerator];
	NSString *podPath;
	
	while (podPath = [enumerator nextObject])
	{
		NSString *stickiesPodPath;
		NSString *stickiesPrefix;
		BOOL toNotes;
		if ([podUtil deviceHasNotesFolderAtPath:podPath])
		{
			stickiesPodPath =
			[podPath stringByAppendingPathComponent:
				@"/Notes/StickiesSync"];

			stickiesPrefix = 
			[NSString stringWithString:@""];

			toNotes = YES;
		}
		else
		{
			stickiesPodPath = 
			[podPath stringByAppendingPathComponent:
				@"/Contacts/StickiesSync"];

			stickiesPrefix =
			[NSString stringWithString:@"!"];

			toNotes = NO;
		}

		//remove any existing "StickiesSync" folder on iPod
		[fileManager removeFileAtPath:stickiesPodPath
			handler:nil];

		//create new "StickiesSync" folder on iPod
		[fileManager createDirectoryAtPath:stickiesPodPath
			attributes:nil];

		//copy in the notes to "StickiesSync"
		[self copyStickiesToiPodAtPath:stickiesPodPath
			withPrefix:stickiesPrefix
			toNotesFolder:(BOOL)toNotes];

		if (eject)
		{
			[[NSWorkspace sharedWorkspace] 
				unmountAndEjectDeviceAtPath:podPath];
		}
	}
}

- (IBAction)syncAndEject:(id)sender
{
	[self synciPodsAndEject:YES];
}

- (void)copyStickiesToiPodAtPath:(NSString*)path 
			withPrefix:(NSString*)prefix
			toNotesFolder:(BOOL)toNotes
{
	/* TODO next time */
	NSLog(@"Syncing StickiesDatabase to iPod next time...");

}

You've noticed that syncAndEject: is simply a wrapper that calls synciPodsAndEject: with an argument of YES. Our finished application will sync the iPod each time it's connected to the computer, as well as any time the user chooses to sync it from the menu bar. Passing the buck to synciPodsAndEject: allows the reuse of identical code that's also needed in the method mountNotification, as will be seen. The method mountNotification retrieves the path of the recently mounted device and passes it to our iPod utility class (still undefined at the moment) to determine if the device is an iPod, and conditionally calls synciPodsAndEject:.

In synciPodsAndEject: we find most of the action. In short, the iPod utility class returns to our controller an array of paths that correspond to mounted iPods, and is able to determine if the device is older than a 3G iPod. If it is older than 3G, the notes are copied into its Contacts folder using the vCard format the iPod expects to read via a call to copyStickiesToiPodAtPath:.

A common file prefix is added to each note copied in order to group the notes together. The Contacts folder orders items alphabetically, so if notes don't share a common prefix, they get scattered throughout your contacts, which is not very convenient. If the iPod is 3G or newer, the notes can be copied into the Notes folder as plain text and ordered alphabetically. Each sync is accomplished by removing any existing notes on the iPod and then copying in the most recent ones from the Stickies application. The implementation details of reverse engineering the database Stickies uses is left until next time.

Creating iPod Utilities

Before this installment finishes, let's fill in the IPodUtils class. There's no rocket science involved here; it's just some grunt work that's convenient to encapsulate into a easier-to-use class. The interface for class IPodUtils is like so:

@interface IPodUtils : NSObject {}
	- (id)init;
	- (void)dealloc;

	- (NSArray*)connectediPods;
	- (BOOL)deviceIsiPodAtPath:(NSString*)path;
	- (BOOL)deviceHasNotesFolderAtPath:(NSString*)path;
@end

You should be able to see how these methods fit into everything based on their method names. The only "clever" thing we do here is take advantage of some things we know about all iPods: 1) they all have an iPod_Control directory and 2) 3G and new iPods have a Notes folder, while older models do not. These two details allow us to determine if a device in question is indeed an iPod and how we should go about storing text notes into it. Here are the implementation details:

#import "IPodUtils.h"


@implementation IPodUtils
- (id)init
{
	return [super init];
}

- (void)dealloc
{
	[super dealloc];
}

- (NSArray*)connectediPods 
{
	NSArray* allVolumes = 
		[[NSWorkspace sharedWorkspace] mountedRemovableMedia];
NSMutableArray* iPodVolumes = [[NSMutableArray alloc] init];

	int i;	
	for (i=0;i<[allVolumes count];i++)
	{
		NSString* iPodControlPath = 
		[[allVolumes objectAtIndex:i] 
			stringByAppendingPathComponent:@"iPod_Control"];

		if ([[NSFileManager defaultManager] 
			fileExistsAtPath:iPodControlPath])
		{
			[iPodVolumes addObject:
				[allVolumes objectAtIndex:i]];
		}
	}
	
	[iPodVolumes autorelease];
	return iPodVolumes;
}

- (BOOL)deviceIsiPodAtPath:(NSString*) path 
{
	return [[NSFileManager defaultManager] fileExistsAtPath:
	   [path stringByAppendingPathComponent:@"iPod_Control"]];
}

- (BOOL)deviceHasNotesFolderAtPath:(NSString*)path
{
	
	return [[NSFileManager defaultManager] fileExistsAtPath:
		[path stringByAppendingPathComponent:@"Notes"]];
}
@end

If you remember to add a #import "IPodUtils.h" directive to the top of your AppController.h file, you should be able to compile with no errors. Your user interface is now complete. You should run your app and verify that it is working. If you have an iPod connected and choose Sync Stickies and Eject, you should see the output in the Run Log console, and your iPod should eject. Once that's done, redock it to see that your app detects it and issues the statement again to the console.

At this point, all that's left is to pull data out of the StickiesDatabase file located in ~/Library and send it over to the iPod. But wait, Apple hasn't published any API for Stickies. Reverse engineering, anyone?

You'll see that a little intuition and clever guessing allows us to develop our own API without significant pains. If you want to get ahead, take a peek at the documentation on NSUnarchiver before next time. If you like where this is going, you can also download StYNCies, a more sophisticated version of the app we're building.

Matthew Russell is a computer scientist from middle Tennessee; and serves Digital Reasoning Systems as the Director of Advanced Technology. Hacking and writing are two activities essential to his renaissance man regimen.


Return to MacDevCenter.com.