When deciding where to store user data, you may be tempted to use SpecialFolder.ApplicationData, which stores the data here on Windows:
\Users\UserName\AppData\Roaming\
The potential problem with this, is the “Roaming” portion of the path. If this is non-essential data, such as certain preferences or app settings that are appropriate to be overwritten when files change, then this may be good. Here is some more information about this location:
https://msdn.microsoft.com/en-us/library/ms995853.aspx
The problem I have with this location is that I’m storing data files where my application may make one or more changes to a single piece of data in the whole file. Consider a flat file format or even something complicated like a SQLite database. Consider the following scenario:
- A user launches the app and the data is read in from this roaming folder.
- The user makes a few changes to the data, and saves the file back out again.
That doesn’t sound too harmless until you add the “roaming” aspect into it. Since the OS may replicate this data to the other user profiles, this means that the file itself is copied to the other users devices. That also doesn’t sound too bad, and almost sounds like a nice file sync solution. While it is true that it is syncing the data across profiles, it is doing it as one large file. That means, if this large file represents many individual records, like in the case of a SQLite database, there could be a problem when writing this data to the other profiles, i.e. roaming. The problem is introduced when the user is using the app on multiple computers/devices.
Now, consider the following:
- A user launches the app on Computer A and modifies some data.
- The OS replicates this to Computer B
- The user launches the app on Computer B and sees the data from Computer A.
- The user doesn’t realize they are offline, and makes some changes on Computer B.
- Later returning to Computer A, they make some more changes.
Here is where things get messy. There are now modified files on both Computer A and Computer B, and the OS is going to replicate the “latest modified” file to both computers. So, if the last changes were done on Computer A (step 5), and both computers connect to the network and try syncing, it is likely that changes from Computer A (step 5) are going to overwrite the changes made on Computer B in step (4).
That’s the trouble.
Now, if the data is a simple setting where the user would expect that their “last change” was the surviving change, and they don’t lose data in the process, then all is well, and this may have been a good case for using the “roaming” location. But, if this is not okay, which is most flat file formats and SQLite databases, then this would be a big problem.
So, what’s the solution? To use the “local” flavor of this AppData folder… the problem is, that isn’t built in to Xojo. So, here’s what I wrote to help solve the problem for me:
First I wrote this Declare:
[code]Private Function GetFolder(csidl As CSIDL) as FolderItem
Declare Function SHGetSpecialFolderPathA Lib “Shell32” (hwnd As Integer, pszPath As Ptr, csidl As Integer, fCreate As Boolean) As Boolean
dim space As new MemoryBlock(1024)
dim spacePtr As Ptr = space
if SHGetSpecialFolderPathA( 0, spacePtr, Integer(csidl), false ) then
Return GetFolderItem( space.CString(0) )
Else
Return nil
end if
End Function
[/code]
Then, I wrote an accessor to use that and pass the special ID for the Local App Data folder:
[code]Protected Property LocalApplicationData as FolderItem
Get
#If TargetWindows
Return GetFolder( CSIDL.CSIDL_LOCAL_APPDATA )
#Endif
Return SpecialFolder.ApplicationData
End Get
Set
End Set
End Property
[/code]
And I defined this constant so that it could map to the directory I wanted:
Private Enum CSIDL
CSIDL_LOCAL_APPDATA = 28
End Enum
I did this on a module so that I could easily reuse it across projects. I thought I would share it with anyone else. When Apple introduced Desktop and Documents sync through iCloud, it really messed up many of my customers who were using more than one machine. This scenario of data being overwritten happened on multiple occasions, and it was not pleasant to tell the customers that their data was “lost”.
So I decided to start putting the data away in the SpecialFolder.ApplicationData location and I’ll simply provide an easy Import/Export feature when customers want to move their data. This is going to be received well by the customers, but because of the “Roaming” problem, I had to write this modified ApplicationData accessor to make sure it stores it only the local directory and doesn’t give a false impression that their data is going to sync across profiles in a reliable way.
I thought I’d share this with anyone else who it may help.