Lovely Core Data Issue; is it a bug or a feature?

I’ve had a few users report that they lost all their data when upgrading to the latest ReceiptWallet or DocumentWallet. This really concerns me as I don’t like data loss (who does?). The latest version of both programs switched to using an SQLite database when users were running under Leopard as it is much faster. At the same time, I changed my object model as I was including lots of models that I didn’t use (damn Apple sample code included all models in all frameworks). So the changes weren’t minor, bug I did my testing and things were working fine. It was in beta for awhile and I had no reports of issues.

Today I decided to revisit the issue and try to solve the problem once and for all. In order to determine when object model I’m using, I add a model version to the metadata which gets written out to the database. I had code like this when I was setting up the data, at the end of the conversion.

[self generateMetadata]

if (managedObjectContext != nil && [managedObjectContext hasChanges])
{	
	[managedObjectContext commitEditing];
	[managedObjectContext save:&error];
}

That should be fine as I don’t want to save if there aren’t any changes and there has been a report that saving when there aren’t changes can cause data loss under Leopard. My code should be nice and fine. Well, here’s the rub. When I do the upgrade, I modify the metadata (in generateMetadata) and if there are no changes (like when the app initially starts), the data file doesn’t get saved. So the next time the app is launched, it thinks it is using the old data model, attempts to do an upgrade, hoses all the data, and continues. If the user modified something prior to quitting the app, all is fine.

So, it looks like people launched the app, let it do the update and then immediately quit it. Ouch.

My modified code is:

	BOOL metadataChanged = [self generateMetadata];
	if (managedObjectContext != nil && ([managedObjectContext hasChanges] || metadataChanged))
	{	
		if ([managedObjectContext hasChanges])
		{
			[managedObjectContext commitEditing];
		}
		[managedObjectContext save:&error];
	}

where generateMetadata returns true if the metadata is different than what already exists.

This brings up another question. Why did I restrict this to Leopard. Well, I was having problems in Tiger where the data got hosed. In my testing this evening, the problem was the above issue. I guess I changed something in the app during Leopard testing which caused the metadata to be written out, but didn’t change anything under Tiger. OK, so now Tiger users get the benefit of the SQLite database format.

Of course, the users that had this problem said that they installed the update and their data was gone. What they didn’t say is that they quit the program right after the update without changing anything. Well, I don’t expect people to take this much notice to what they are doing, but it would have helped track this down faster.

The good news about this mess is that during testing I saw how this could be an issue and made a backup of the old database called ReceiptWallet.receiptwallet.old or DocumentWallet.documentwallet.old. This is in addition to the daily backups I automatically make of the database (up to 5 copies).

Bluetooth in VMWare Fusion

Yesterday I wanted to update the firmware in my Parrot EasyDrive Bluetooth carkit. Seems simple enough to do it using VMWare Fusion. I started up Fusion, chose the option to connect the “Bluetooth USB device” and found that Windows didn’t have the driver. Crap, I remember having to do something with BootCamp to get the drivers, but I couldn’t recall. Now that BootCamp wasn’t a download (it is part of Leopard), I started poking around at the BootCamp Assistant and got no where. I searched online and couldn’t find an answer. Finally, I found a reference to inserting the Leopard DVD while in Windows. I tried this and when I did, the BootCamp driver installer came up and installed my drivers. Perfect; why couldn’t VMWare say something about this on their website?

Oh that brings me to something else, on the Mac, AutoPlaying of a data CD/DVD has been disabled for years, but it seems that Windows still has this on by default (I run Windows XP). Has Microsoft not learned from all the viruses and malware available for Windows? Are they taking that ease of use over security? While I understand there are tradeoffs, I think this is a poor one to make.

Indie Software Developers

For years, I’ve seen references to “indie” (independent) software developers, but never considered myself one. I’ve never had to rely on sales of my own software as I’ve been an independent contractor without my own products (that brought in any significant amount of income) for almost five years. However, this past year, ReceiptWallet has started to change that; I’m not about to give up my day job, but in an exchange with another developer today, he referred to both of us as “indie” developers. I guess I am an independent developer.

Broken Idle Time in 10.4/10.5

After fighting with trying to get the amount of time since a user has done something with the system, I’ve determined that CGEventSourceSecondsSinceLastEventType is broken. The documentation indicates that calling it like:

CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType)

will tell me how many seconds since the user last moved the mouse, touched the keyboard, etc. The docs say:

The various system and app defined events do not contribute to this event type’s time.

Unfortunately this just isn’t true. In one app I’m working on, I have a timer that fires every 5 seconds and prints the idle time. It starts going up, but then a notification comes in and it resets back to zero without me touching the keyboard. So, this call is almost useless as I need to know when things are idle in order to perform some tasks. While some of you are saying that I can use a Carbon Event Idle Timer, it turns out that they don’t work in background only apps. My only solution is to make the above call using something like:

+ (double) idleTimeInSeconds
{
	double idleTime = 0;
	double tempIdleTime = 0;
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventLeftMouseDown);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventLeftMouseUp);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}

	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventRightMouseDown);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}

	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventRightMouseUp);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventMouseMoved);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventLeftMouseDragged);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventRightMouseDragged);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventKeyDown);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventKeyUp);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventFlagsChanged);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventScrollWheel);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventTabletPointer);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventTabletProximity);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventOtherMouseDown);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventOtherMouseUp);
	if (idleTime == 0 || tempIdleTime < idleTime)
	{
		idleTime = tempIdleTime;
	}
	
	tempIdleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGEventOtherMouseDragged);
	
	return idleTime;
}

Wow, that is freaking ugly, but at least I know exactly when a user event occurred. Feel free to use my code in any way you see fit. I've searched the web and found answers to a lot of my problems, so here's a small contribution back to the community.

Thanks for the update, Fujitsu!

When Leopard came out, I blogged about a bug in Fujitsu’s ScanSnap Manager software. I was unable to get anywhere with Fujitsu and almost forgot about it. However, today in trolling through their website, I saw that they had an article specifically about the software running under Leopard along with a download to a new version of the ScanSnap Manager. Yeah, I downloaded the software, gave it a try and found that it fixed my problem. However, they added a stupid feature called “Quick Menu” which brings up a half baked feature allowing you to send stuff to email, a folder, iPhoto, etc.

Too bad Fujitsu has never heard of VersionTracker or MacUpdate to post updates so that customers can actually find out about updates without having to dig through their site (which actually doesn’t have a direct link to the Mac software under their downloads area).

Quality HP Software

For years I’ve complained to my father about the poor quality of HP’s Mac scanner software. He always brushed it off as my standard are pretty high and it would appear (to outsiders) that I don’t like many programs. (There are some programs I really like and there are some that just suck; yes, my standards are high, but I’m on a Mac and I deserve the best!). Recently, he started using his HP All-In-One with his new Mac (he had been using it from his PC) and after hours (maybe days) of frustration, he contacted their support and got no where (support was on a PC reading from a script). He complained up the change and accused them of fraud/deception as they advertised certain features working on the Mac, when they clearly don’t.

It was quite fun basically saying “I told you so”; I have an HP All-In-One that I bought several years ago that I only use as a fax machine and a copier; for those functions, it works great. If HP is serious about the Mac, they need to step up to the plate and re-write their software. Windows users might like the cutesy scanning app, but Mac users just want it to work like every other Mac application and integrate with Address Book (for the fax app), etc.

If their devices weren’t so cheap, Mac users probably wouldn’t be lured into buying them.

Installing SlimServer on an AppleTV

One of the services I needed to move off my server before shutting it down was SlimServer which runs my Squeezeboxes. I had looked at a few small PCs that were ultra quiet and as I was looking at the Shuttle website, it compared one of their boxes to a Mac Mini. Hmmm, I have an extra one of those lying around that I could use and then remembered I have an AppleTV that I’m not using. So, I decided to see if I could install SlimServer on the AppleTV as it is fanless and super quiet. Here’s what I did:

  1. Download Patchstick
  2. Follow the procedure and install Patchstick from a USB thumb drive
  3. Download the Mac OS X version of SlimServer
  4. Download the XMRadio plugin
  5. Edit XROAPI.pm in the plugin by commenting out
    	if ( defined( $self->{activeClient} ) ) {		return [			$client->string('PLUGIN_XMRADIO_ERROR_HEADER'),			$client->string('PLUGIN_XMRADIO_IN_USE') . ' ' . $client->name()		];	}

    So that I can have more than 1 Squeezebox talking to XM at the same time.

  6. From the AppleTV, enable AFP in the awakwardTV menu
  7. Mount the SlimServer dmg file that was downloaded above
  8. From the image, copy Install Files/SlimServer.prefPane/Resources/server to your desktop
  9. Place the modified XMRadio plugin in ~/Destkop/server/Plugins
  10. Mount the AppleTV volume via AFP
  11. Modify ~/Desktop/server/Slim Launcher.app/Contents/Resources/Start Slim Server.sh to add
    HOME=/Users/frontrow; export HOME

    before the ./slimserver.pl line

  12. Copy ~/Desktop/server to the AppleTV’s AFP volume
  13. Create a folder on the Desktop called SlimServer
  14. In that folder create 2 files. The first is called StartupParameters.plist and it contains:
    {  Description     = "SlimServer";  Provides        = ("SlimServer");  Requires        = ("Disks");    Uses		= ("mDNSResponder", "Resolver", "DirectoryServices", "NFS", "Network Time");    OrderPreference	= "Last";    Messages =    {	start = "Starting SlimServer";	stop = "Stopping SlimServer";    };}

    The second is called SlimServer and it contains:

    #!/bin/sh. /etc/rc.commonSERVER_RUNNING=`ps -axww | grep "slimp3.pl|slimp3d|slimserver.pl|slimserver" | grep -v grep | cat`StartService() {ConsoleMessage "Starting SlimServer"if [ z"$SERVER_RUNNING" = z ] ; then	pushd "/Users/frontrow/server"    sudo -u frontrow "Slim Launcher.app/Contents/Resources/Start Slim Server.sh"    popdfiif [ z"$#" != z"0" ] ; then    ConsoleMessage -Sfi}StopService() {if [ z"$SERVER_RUNNING" != z ] ; then    kill `echo $SERVER_RUNNING | sed -n 's/^[ ]*([0-9]*)[ ]*.*$/1/p'`fi}RunService "$1"
  15. Copy the SlimServer folder to the AppleTV’s AFP volume
  16. Login via ssh using
    ssh -1 frontrow@appletv.local

    password is frontrow

  17. Change the root file system to read/write using
    sudo mount -uw /
  18. Move the SlimServer folder using
    sudo mv /Users/frontrow/SlimServer /Library/StartupItems/
  19. Make the SlimServer file executable
    sudo chmod +x /Library/StartupItems/SlimServer/SlimServer
  20. Change the owner
    sudo chown -R root:wheel /Library/StartupItems/SlimServer
  21. Disable auto updating
    sudo bash -c 'echo "127.0.0.1       mesu.apple.com" >> /etc/hosts'
  22. Restart the AppleTV
    sudo reboot
  23. From Safari goto: http://appletv.local:9000/
  24. Change the music directory in the SlimServer prefs to /mnt/Media/Media Files

The only problem so far is that it creates multiple Albums for each album due to how the AppleTV stores the music.

Lets’ spy on the competition

Today I saw that NEAT Receipts has provided more information on their Mac version, and here. Just about a month ago, the founder of NEAT Receipts (or one of his employees) purchased a copy of ReceiptWallet and DocumentWallet. At first I thought maybe they wanted to talk to me about the products and I contacted them about it, but now it seems that the goal was simply to either look at the competition or learn from it. So it looks like NEAT Receipts has 3 developers working on the program; I wrote ReceiptWallet initially in about 2 weeks part time and have had over a year to perfect it.I just hope that they don’t copy my ideas and interface as it has taken me a long time to get things quite usable. They’re exhibiting at Macworld, so I’ll have to take a look at what they have and maybe change my marketing message, “The original receipt management program for Mac OS X”.

One year of selling ReceiptWallet

Yesterday marked the 1st anniversary of ReceiptWallet! When I started ReceiptWallet, it was simply out of my own desire for a program to keep track of my receipts. A lot has happened in one year and I’m quite pleased with how mature the product has come as well as the number of users using it. I hope that the next year brings as much success as the first year and then some!

Software Piracy on the Mac

One of the recurring themes in the shareware industry is how to best protect our work from people that don’t want to pay. I know I’ve written on this topic before, but the recent release of Leopard has brought this back to the forefront of my mind. Another blogger wrote about how many people had pre-ordered the Leopard family pack via his Amazon link and was amazed. The family pack is simply the same as the single user pack (same CD), but the license agreement differs slightly in that it says it can be installed on up to 5 machines in the same household. Why would people do this? It seems to me that Mac users are generally honest people willing to pay for good software. I’ve seen this with my own software where people purchase multiple licenses and I only send 1 serial number (I’ll send more than one if I’m asked, but my automated system only sends 1) and people have asked about installing on more than one machine.

Software isn’t cheap, but it’s good to know that many Mac users do the right thing and purchase software even though it is quite easy to pirate many pieces of Mac software, including Leopard.