/*
   Filename.....: vistaIni.cc
   ClassName....: vistaIni
   Purpose......: Create, read, and write to a file with
                  ini file type methods. Default the folder used
                  to the xp, vista preferred folder of
                  User\Application Data\Publisher\Application
                  Auto create the folder and file (if necessary).
   Programmer...: Rick Miller.
   Date.........: August 15, 2008.
   Notes........: See Notes below.
   Written for..: dBASE 32 bit.
   Rev. History.: None.
   Dependencies.: shfolder.dll.
   API Calls....: see x_InitializeExterns method.
   Called by....: Any.
   Usage........: set procedure to vistaIni.cc additive
   ----------------------------------------------------------------
   Example:
   ----------------------------------------------------------------
      oIni  =  new vistaIni("Application", "Publisher")
      oIni.set("Grid", "Column1Field", "CUSTOMER")
      oIni.release()
      oIni  := null
   Notes:
   ----------------------------------------------------------------
      1) the 2 parameters at initialization are required.
      2) a subFolder of application can be used by assigning
         the subfolder property.
      3) when working with number values:
         a) the set method will round based upon the
            current set("decimals") value.
         b) the getNumber, and setNumber methods will round
            based upon the current set("decimals") value or
            the decimals parameter when passed.
         c) the getInt and setInt methods always drop all
            decimals with no rounding.
         d) when debugging using the ? command, the display is
            always based upon the current set("decimals") value,
            even when there are more decimals present.
      4) the deleteFolders method may return false when
         there are multiple applications using this object
         with the same publisher property value.
      5) it is not recommended to change the publisher property
         after initialization.
   ----------------------------------------------------------------
   Properties:
   ----------------------------------------------------------------
      application <char>   initialized at creation from parameter.
      filename    <char>   initialized at creation to user name.
      publisher   <char>   initialized at creation from parameter.
      subfolder   <char>   ""
   ----------------------------------------------------------------
   Methods:
   ----------------------------------------------------------------
      close()
         calls to release(), and close this file.
      delete()
         returns  true/false (success/fail).
         delete the file this object uses.
         this method may be used when a temporary file is wished
         or a resetting of the file is desired.
      deleteApplicationFolder()
         returns  true/false (success/fail).
         delete this.publisher + "\" + this.application directory.
      deleteFolders()
         returns  true/false (success/fail).
         delete all folders that may have been created.
      deletePublisherFolder()
         returns  true/false (success/fail).
         delete this.publisher directory.
      deleteSubfolder()
         returns  true/false (success/fail).
         delete this.subfolder directory(ies).
      flush()
         returns  true/false (success/fail).
         flush writes that may be in cache.
      fullFilename()
         returns <char>.
         the fullpath\filename this object is using.
      get(<char> section, <char> name)
         returns <char> from this.filename.
      getInt(<char> section, <char> name)
         returns <int> from this.filename.
      getNumber(<char> section, <char> name[, <int> decimals])
         returns <number> from this.filename.
      release()
         destroy this object.
      rootFolder()
         returns <char>    User\Application Data
      set(<char> section, <char> name, <char> value)
         returns  true/false (success/fail).
         write a <char> to this.filename.
      setInt(<char> section, <char> name, <numeric> value)
         returns  true/false (success/fail).
         write an <int> to this.filename.
      setNumber(<char> section, <char> name,;
                <number> value[, <int> decimals])
         returns  true/false (success/fail).
         write a <number> to this.filename.
      setRoot(<int> CSIDL folder)
         returns  true/false (success/fail).
         assign a proper root folder to use.
   ----------------------------------------------------------------
   */
#define  _VINI_CLASS_         "vistaIni"
#define  _VINI_VERSION_       "1.00"
#define  ALLTRIM(c)           ltrim(trim(c))
#define  ALLTRIMINI(c)        iif(c.indexOf(".") == -1,;
                              ALLTRIM(c) + ".ini",;
                              ALLTRIM(c))
#define  ALLTRIMSUB(c)        iif(empty(c),;
                              "",;
                              ALLTRIM(c) + "\")
#define  MAX_LINELENGTH       1024
#define  MAX_PATH             260
#define  MAX_USERNAME         256
#define  CSIDL_APPDATA        0x001a   // <user>\Application Data
#define  CSIDL_COMMON_APPDATA 0x0023   // All Users\Application Data
#define  CSIDL_LOCAL_APPDATA  0x001c   // <user>\Local Settings\Application Data (non roaming)
#define  ZEROS(n)             replicate(chr(0), n)
//---------------------------------------------------------------//
//          constructor.
//---------------------------------------------------------------//
class vistaIni(Application, Publisher)
   protect  x_DeleteDirectory, x_GetDirectory,;
            x_GetDirectoryApplication, x_GetDirectoryPublisher,;
            x_GetFilename, x_GetUserName,;
            x_InitializeExterns, x_ValidateDirectory
   protect  ~root
   this.~root     =  CSIDL_APPDATA
   this.classname =  _VINI_CLASS_
   this.version   =  _VINI_VERSION_
   if PCount() == 2
      this.x_InitializeExterns()
      this.application  =  "" + Application
      this.publisher    =  "" + Publisher
      this.subfolder    =  ""
      this.filename     =  this.x_GetUsername()
   else
      msgbox( ;
         "Invalid initialization of " + this.classname + chr(13) +;
         "the Application name and" + chr(13) +;
         "the Publisher name" + chr(13) +;
         "are required parameters", this.classname + " " +;
         this.version + " Message", 16)
   endif
   //------------------------------------------------------------//
   //       end of constructor.
   //       public methods below.
   //------------------------------------------------------------//
   function close
      this.release()
      try
         close procedure (program(1))
      catch(exception e)
      endtry
   return
   //------------------------------------------------------------//
   // delete the file used with this object.
   function delete
      Local bRet, cFile, oFile
      cFile  =  this.x_GetFilename()
      oFile =  new file()
      if oFile.exists(cFile)
         oFile.delete(cFile)
      endif
      bRet  =  iif(oFile.exists(cFile), false, true)
      oFile := null
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   // delete the all folders that may have been created.
   function deleteFolders
      Local bRet
      bRet  =  false
      if this.deleteSubfolder()
         if this.deleteApplicationFolder()
            if this.deletePublisherFolder()
               bRet  := true
            endif
         endif
      endif
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   // delete this.publisher + "\" + this.application directory.
   function deleteApplicationFolder
   return   this.x_DeleteDirectory(this.x_GetDirectoryApplication())
   //------------------------------------------------------------//
   // delete this.publisher directory.
   function deletePublisherFolder
   return   this.x_DeleteDirectory(this.x_GetDirectoryPublisher())
   //------------------------------------------------------------//
   // delete this.subfolder directory(ies).
   function deleteSubfolder
      Local bRet
      if empty(this.subfolder)
         bRet  =  true
      else
         Local cFold, i, nCnt, nPos
         nCnt  =  1
         nPos  =  this.subfolder.indexOf("\")
         do while nPos > -1
            nCnt  ++
            nPos  := this.subfolder.indexOf("\", nPos + 1)
         enddo
         nPos  =  0
         cFold =  this.x_GetFilename()
         for i = 1 to nCnt
            cFold := cFold.left(cFold.lastIndexOf("\"))
            if this.x_DeleteDirectory(cFold)
               nPos  ++
            endif
         endfor
         bRet  =  iif(nCnt == nPos, true, false)
      endif
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   // flush file writes that are in the cache.
   function flush
      Local bRet, cFile, oFile
      bRet  =  false
      cFile =  this.x_GetFilename()
      oFile =  new file()
      if oFile.exists(cFile)
         oFile.open(cFile, "W")
         if not oFile.handle == -1
            bRet  :=  RMM_FlushFileBuffers(int(oFile.handle))
         endif
         oFile.close()
      endif
      release object oFile
      oFile := null
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   function fullFilename
   return   this.x_GetFilename()
   //------------------------------------------------------------//
   // return a value with a read.
   function get(cSection, cName)
      Local cFile, cRet, nLen
      cRet  =  ""
      cFile =  this.x_GetFilename()
      if this.x_ValidateDirectory(cFile, false)
         cRet  =  space(MAX_LINELENGTH)
         nLen  =  MAX_LINELENGTH
         // returns number of characters copied to the buffer,
         // not including the terminating null character.
         nLen  := RMM_GetIniString( ;
                  "" + cSection, "" + cName, "", cRet, nLen, cFile)
         cRet  =  left(cRet, nLen)
      endif
   return   cRet
   //------------------------------------------------------------//
   // return an int value.
   function getInt(cSection, cName)
   return   int(val(this.get(cSection, cName)))
   //------------------------------------------------------------//
   // return a numeric value.
   function getNumber(cSection, cName, nDecimals)
      Local nRet, nDeci, nValu
      if ("" + nDecimals) == "false"
         nRet  =  val(this.get(cSection, cName))
      else
         nValu =  int(val("" + nDecimals))
         if nValu > 18
            nValu := 18
         elseif nValu < 0
            nValu := 0
         endif
         nDeci =  set("decimals")
         set decimals to (nValu)
         nRet  =  val("" + this.get(cSection, cName))
         set decimals to (nDeci)
      endif
   return   nRet
   //------------------------------------------------------------//
   function release
      try
         release object this
      catch(exception e)
      endtry
   return
   //------------------------------------------------------------//
   // return the user\application data folder.
   function rootFolder
   return   this.x_GetDirectory(this.~root)
   //------------------------------------------------------------//
   // write a value.
   function set(cSection, cName, value)
      Local bRet, cFile
      bRet  =  false
      cFile =  this.x_GetFilename()
      if this.x_ValidateDirectory(cFile, true)
         bRet  := RMM_WriteIniString( ;
                  "" + cSection, "" + cName, "" + value, cFile)
      endif
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   // write an int.
   function setInt(cSection, cName, value)
   return   this.set(cSection, cName,;
            ltrim(str(int(val("" + value)), 10, 0, "")))
   //------------------------------------------------------------//
   // write a numeric value with the specified decimals.
   function setNumber(cSection, cName, value, nDecimals)
      Local bRet, nDeci, nValu
      nDeci =  set("decimals")
      if ("" + nDecimals) == "false"
         bRet  =  this.set(cSection, cName,;
                  ltrim(str(val("" + value), 20, nDeci, "")))
      else
         nValu =  int(val("" + nDecimals))
         if nValu > 18
            nValu := 18
         elseif nValu < 0
            nValu := 0
         endif
         set decimals to (nValu)
         bRet  =  this.set(cSection, cName,;
                  ltrim(str(val("" + value), 20, nValu, "")))
         set decimals to (nDeci)
      endif
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   // restrict folder to an xp/vista compliant folder.
   // additional valid root folders can be added in here.
   function setRoot(nCsid)
      Local bRet
      bRet  =  true
      if nCsid == CSIDL_APPDATA
         // <user>\Application Data.
         // used for user specific files and tables.
      elseif nCsid == CSIDL_COMMON_APPDATA
         // All Users\Application Data.
         // used for shared data files and tables.
      elseif nCsid == CSIDL_LOCAL_APPDATA
         // <user>\Local Settings\Application Data (non roaming).
         // this is commonly a hidden folder.
      else
         bRet  := false
      endif
      if bRet
         this.~root  := nCsid
      endif
   return   iif(bRet, true, false)
   //------------------------------------------------------------//
   //       end of public methods.
   //       private methods melow.
   //------------------------------------------------------------//
   function x_DeleteDirectory(cFolder)
      if not RMM_RemoveDirectory(cFolder)
         // try 1 more time due to caches.
         RMM_RemoveDirectory(cFolder)
      endif
   return   empty(funique(cFolder + "\????????.txt"))
   //------------------------------------------------------------//
   // return the directory for nCsid using shfolder.dll.
   function x_GetDirectory(nCsid)
      Local cRet, nCsid, nLen
      nLen  =  MAX_PATH
      cRet  =  space(nLen)
      // next line returns 0 for success.
      nLen  := RMM_SHGetFolderPath( ;
               null, nCsid, 0, null, cRet)
      if nLen == 0
         // cRet holds the folder in a long name.
         if cRet.right(1) == "\"
            cRet  := cRet.left(cRet.length - 1)
         endif
      else
         cRet  := ""
      endif
   return   cRet
   //------------------------------------------------------------//
   function x_GetDirectoryApplication
   return   this.x_GetDirectory(this.~root) + "\" +;
            ALLTRIM(this.publisher) + "\" +;
            ALLTRIM(this.application)
   //------------------------------------------------------------//
   function x_GetDirectoryPublisher
   return   this.x_GetDirectory(this.~root) + "\" +;
            ALLTRIM(this.publisher)
   //------------------------------------------------------------//
   function x_GetFilename
   return   this.x_GetDirectoryApplication() + "\" +;
            ALLTRIMSUB(this.subfolder) +;
            ALLTRIMINI(this.filename)
   //------------------------------------------------------------//
   // use the user name logged into windows
   // for the default filename.
   function x_GetUserName
      Local cRet, sBuff, sLen
      sBuff =  ZEROS(MAX_USERNAME + 1)
      cRet  =  "Default"
      sLen  =  ZEROS(4)
      // put the length value into a pointer.
      sLen.setByte(0, bitand(MAX_USERNAME, 0xFF))
      sLen.setByte(1, bitand(bitzrshift(MAX_USERNAME,  8), 0xFF))
      sLen.setByte(2, bitand(bitzrshift(MAX_USERNAME, 16), 0xFF))
      sLen.setByte(3, bitand(bitzrshift(MAX_USERNAME, 24), 0xFF))
      if RMM_GetUserName(sBuff, sLen)
         // the variable pointed to by sLen contains the number
         // of characters copied to the buffer,
         // including the terminating null character.
         // ignore sLen and just find the first chr(0).
         cRet  := sBuff.left(sBuff.indexof(chr(0)))
      endif
   return   cRet
   //------------------------------------------------------------//
   function x_InitializeExterns
      if not type("RMM_CreateDirectory") == "FP"
         extern CLOGICAL   RMM_CreateDirectory( ;
            CSTRING, CPTR) ;
            kernel32 from "CreateDirectoryA"
      endif
      if not type("RMM_RemoveDirectory") == "FP"
         extern CLOGICAL   RMM_RemoveDirectory( ;
            CSTRING) ;
            kernel32 from "RemoveDirectoryA"
      endif
      if not type("RMM_FlushFileBuffers") == "FP"
         extern CLOGICAL   RMM_FlushFileBuffers(CHANDLE) ;
            kernel32 from "FlushFileBuffers"
      endif
      if not type("RMM_GetIniString") == "FP"
         extern CULONG  RMM_GetIniString( ;
            CSTRING, CSTRING, CSTRING, CSTRING, CULONG, CSTRING) ;
            kernel32 from "GetPrivateProfileStringA"
      endif
      if not type("RMM_WriteIniString") == "FP"
         extern CLOGICAL   RMM_WriteIniString( ;
            CSTRING, CSTRING, CSTRING, CSTRING) ;
            kernel32 from "WritePrivateProfileStringA"
      endif
      if not type("RMM_GetUserName") == "FP"
         extern CLOGICAL   RMM_GetUserName(CSTRING, CPTR) ;
            advapi32.dll from "GetUserNameA"
      endif
      if not type("RMM_SHGetFolderPath") == "FP"
         try
            extern CLONG   RMM_SHGetFolderPath( ;
               CHANDLE, CINT, CHANDLE, CULONG, CSTRING) ;
               shfolder from "SHGetFolderPathA"
         catch(exception e)
            msgbox( ;
               "Invalid initialization of " +;
               this.classname + chr(13) +;
               "the required file (shfolder.dll) is missing" +;
               chr(13),;
               this.classname + " " + this.version + " Message",;
               16)
         endtry
      endif
   return
   //------------------------------------------------------------//
   // validate a directory with a file name.
   // optionally create the directory.
   function x_ValidateDirectory(cFile, bMake)
      Local bRet, cTemp, cTest, nPos
      bRet  =  false
      if cFile.indexOf(".") > 0
         // strip off the filename extension.
         cTemp =  cFile.left(cFile.indexOf("."))
      else
         cTemp =  "" + cFile
      endif
      nPos  =  cTemp.lastIndexOf("\")
      if nPos == -1
         cTest =  "" + cTemp
      else
         cTest =  cTemp.left(nPos)
      endif
      // there should always be at least 3 "\" characters.
      nPos  =  cTemp.indexOf("\", 3)
      do while not (nPos == -1)
         cTest := cTemp.left(nPos)
         if empty(funique(cTest + "\????????.txt"))
            if bMake
               RMM_CreateDirectory(cTest, null)
            endif
         endif
         nPos  := cTemp.indexOf("\", nPos + 1)
      enddo
   return   not empty(funique(cTest + "\????????.txt"))
   //------------------------------------------------------------//
   //       end of private methods.
   //------------------------------------------------------------//
endclass
//---------------------------------------------------------------//
//          end of vistaIni class.
//---------------------------------------------------------------//