PhoneGap Files, Filepaths, Backups, and a Nasty Bug

I recently updated my main phone to iOS7. To my surprise, images within my The Fisher app no longer appeared. All the stored data was there but instead of the images I only got a white screen.

My first hope was that this was due to my app not being the version downloaded from the App Store and the restoring from backup not working for non-App Store. But I had to test it with my backup phone which I can easily erase and restore from backups. After testing, I was in a bit of a panic mode since the images really were not restored from the backups. To put it mildly, in an app with user-generated content, losing that content is not good.

Inspecting the contents of the app bundle with iExplorer (which I highly recommend), I found out that the files were there but they were not shown in the UI. Comparing the catches from the backup with newly added catches, I noticed that the file paths were different.

PhoneGap Files and Filepaths

On iOS (and when working with PhoneGap), files have a path like this:

file://localhost/var/mobile/Applications/C5875140-A4D2-4310-A6A7-55CC2F504F2A/Documents/1377869303030.jpg

In my app, I was saving these absolute paths to the database. And apparently you should never do that! As the Apple File System Programming Guide says, file paths consist of application home directory and the location of the file within the apps sandbox. The thing is, the application home directory can change at least when restoring from backups.

Fixing is Easy

Fixing the problem is easy. Instead of storing the absolute path of the file, you should only store the part of the file that you specify. In my app, the file names are based on the time they were created, so they are all of the form 1377869303030.jpg (or, [0-9]+\.jpg). Thus, whenever wanting to access the file, I can simply replace the beginning of the stored file path with the correct path to the application persistent file directory. To get this directory path, I have the following code executed on deviceready event.

function setFsRoot(callback) {
  window.requestFileSystem(LocalFileSystem.PERSISTENT,
                           0,
                           gotFS,
                           fail);
  function fail() {
    // handle the error
  }

  function gotFS(fileSystem) {
    // save the file system for later access
    window.FS_ROOT = fileSystem.root.fullPath;
    callback();
  }
}

A file path is then window.FS_ROOT + fileName. Changing all file access to work like this I was able to fix the issue with relatively little changes. If you are storing absolute paths, you should make this fix now, before everyone upgrades to iOS7.

Conclusion

Accessing files is critical in my app, so I really should have done a better job in testing backup restores. However, I do think that the Apple documentation as well as PhoneGap API docs should mention this potential pitfall.

Now my problem is fixed and an updated version 1.1.1 is already available in the App Store. Hopefully, no major harm was done to my users. I was also lucky that file removals used the same file path, so the user was not able to remove the catches before I updated the app.