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).