Few useful applications run without at some point having to save information and later load it. This may just be user preferences which could easily be discarded and recreated without much of an impact on the user or it may be something much more important and crucial to its operation.
In either case you to provide the user with the best possible experience you want to ensure that this information is saved safely and completely so that the app can be confident of being able to load it when the user next needs it.
Sources Of File Corruption
Much more common though are app and operating system failures, disk space issues or other recoverable problems where the app has failed to write the full contents of the file and may have been prevented from recovering from the failure (for example on an unexpected operating system shutdown or a lock-up). Conscientious apps that continuously save files to save ongoing work dramatically increase their chances of hitting this case just by increasing the frequency of saves. However, there are relatively simple ways to avoid this common problematic case of creating a 'broken' file which it will inevitably fail to read on the next run.
Avoiding Special Formats
- Be robust
- Keep things as simple to use as possible
- Don't slow down I/O significantly
- Avoid special file formats
The first three here are pretty simple but the last is less obvious. If you are writing a file to disk and you want to be sure that it has written in its entirity, an initially attractive method might be to use markers within the file such that on loading, it would be easy to detect if the file was partially written or not. This plus some saving of previous versions might be enough to do the job.
The issue is that this only works where the file format is custom. If you are saving JPEGs and you want to ensure that the saved JPEG is always valid then you can't adapt the file format to suit your case.
Borrowing From Filesystem Design - Simple Journaling
In journaling, the intent of the changes is first stored somewhere and such that once the changes are made, if they are only able to be partially made for any reason, they can be finished off when the file is next accessed. This ensures that the file is kept in a consistent state when accessing it.
A new simple API in JWrapper - JWAtomicFileOutputStream - does exactly this. It is used a simple alternative to FileOutputStream but rather than saving directly into the target file it first saves any data into a temporary file in the same folder as your target file. Once you have finished writing data and call the close() method it will flush data into your new file, remove your existing file and rename the new version into its place. This means that at any stage of this process you can always:
1) be sure there is a valid (fully written) copy of the file
2) know which is the valid and most up to date copy
When loading you need only call JWAtomicFileOutputStream.prepareForReading and JW will check if the file was partially saved. If it is it will restore it back to a valid state so that you can load the file as usual.
This doesn't slow down your I/O or modify your file format in any way - you can use it to save JPEGs or any other type of file and it allows you to use it as a drop-in replacement for any existing FileOutputStreams you want to protect. It also allows you to retain backwards compatibility with any other apps or versions that load your saved file, since it will be ultimately saved in exactly the same format.
A trivial change which allows you to avoid issues for your users and support queries for yourself.