Friday, July 4, 2014

Choose the Right Folder for Your Application Data

I'm in the process of updating an older program that will now use a local database. I followed my typical "old school" habits and quietly went about my way thinking "I'll just plop the database in a folder called Data that resides in the same location as the exe file. No big deal."

I figured, I'll just use the reliable ExtractFilePath(Application.ExeName) to get the location of where this Data folder needs to go. Right?

Wrong!

I had forgotten to think about how Delphi's "debug" and "release" paths will play into this scenario. Not to mention 32Bit and 64Bit targets. I'm only focused on creating a Windows product at the moment, so for me this means there are a minimum of five paths:

Source
Source\Win32\Debug
Source\Win32\Release
Source\Win64\Debug
Source\Win64\Release

I know I could use "Post-Build" commands (Project > Options > Build Events) to make sure the files are copied from the $(PROJECTDIR) to the $(OUTPUTDIR). However, I was thinking that this is not a very efficient way to go about it. Did I really want to have five copies of a 140MB database cluttering up my hard drive. The answer is no.

So I posed the question "Looking for best practice for using single-user local database." to the Delphi Community on Google+ (https://plus.google.com/u/0/105522328114529031567/posts/Q8go8eWRAfz)

I received several comments and the consensus was to use an application specific subfolder of CommonAppData. Fair enough. CommonAppData it is. What the heck is CommonAppData?

I tend to be a "Show Me" guy I decided to dive into this head long and found out that CommonAppData actually refers to the folder called C:\ProgramData.

Then I wondered what all the other "AppData" type folders were. So I wrote a simple little program to help figure this out. It turns out there are 17 different AppData folders on my computer. I've included a copy of my program for you to use.

AppData Only: Sorted by Path Column

DFM:
object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Paths'
  ClientHeight = 411
  ClientWidth = 804
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnResize = FormResize
  OnShow = FormShow
  DesignSize = (
    804
    411)
  PixelsPerInch = 96
  TextHeight = 13
  object StringGrid1: TStringGrid
    Left = 16
    Top = 39
    Width = 772
    Height = 354
    Anchors = [akLeft, akTop, akRight, akBottom]
    ColCount = 3
    FixedCols = 0
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Tahoma'
    Font.Style = []
    Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goFixedRowClick]
    ParentFont = False
    TabOrder = 0
    OnFixedCellClick = StringGrid1FixedCellClick
  end
  object Button1: TButton
    Left = 16
    Top = 8
    Width = 75
    Height = 25
    Caption = 'Close'
    TabOrder = 1
    OnClick = Button1Click
  end
  object Button2: TButton
    Left = 97
    Top = 8
    Width = 75
    Height = 25
    Caption = 'Get Folders'
    TabOrder = 2
    OnClick = Button2Click
  end
  object CheckBox1: TCheckBox
    Left = 184
    Top = 12
    Width = 97
    Height = 17
    Caption = 'AppData Only'
    TabOrder = 3
    OnClick = Button2Click
  end
end


Program:



Enjoy - Semper Fi,
Gunny Mike
end.

4 comments:

  1. Very nice!

    Possible further development (feel free to ignore :) ):

    * Option to see the current setting and the default setting?

    * Expand to show all CSIDL values (see: http://msdn.microsoft.com/en-us/library/windows/desktop/bb762494(v=vs.85).aspx) ?

    * Expand to include SHGetKnownFolderPath (http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188(v=vs.85).aspx) and it known folders (http://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx) ? I know that is one is a bit different but I think it would be handy also.

    ReplyDelete
  2. Alternatively you may also create a symbolic link of your Data folder to your Release and Debug folders, so that the Data folder can still be made relative to your executable even while debugging :)

    ReplyDelete
    Replies
    1. @Chee Meng - I've never heard of symbolic references. Whaere can I learn more? Please don't "let me google that for you". I'd rather you sent me to specific references you feel are worthy. :-)

      Delete
    2. Sorry Riley, I did not get notification for reply earlier.

      Symbolic links have been used for a long time in *nix systems, to avoid data duplication… and also for easier version control of executables/shared libraries. For instance you may have a lib_ssl098.so and a lib_ssl099.so, and a symbolic link called lib_ssl.so that currently points to lib_ssl099.so. That way, new compilations will refer to lib_ssl.so that will always point to the latest file, while at the same time you may still directly reference older versions if you want to. And when you get a new file, say, lib_ssl100.so, you then point lib_ssl.so to the latest file and again, future compilations will use the latest file. In *nix and OSX you may achieve that with the “ln -s” command.

      In Windows, you may use the mklink command from the command prompt, or the CreateSymbolicLink[Transacted] API calls. http://msdn.microsoft.com/en-us/library/windows/desktop/aa363866(v=vs.85).aspx

      Wikipedia is still a good source of reference http://en.wikipedia.org/wiki/Symbolic_link and perhaps even this quick premiere http://linux-blog.org/creating-symlinks-how-and-why/

      Delete