////////////////////////////////////////////////////////////////////////////////
// Filename:    CoreDrives.CC                                                 //
// Written for: dBASE (32-bit versions)                                       //
// Written by:  Jim Sare - dBASE Inc. R&D Group                               //
// Copyright:   Copyright dataBased Intelligence, Inc. 2002-2006              //
// Rights:      You have the right to freely include this code in any         //
//              compiled dBASE application. You do not have the right to      //
//              reproduce or publish this source code without the explicit    //
//              written permission of dataBased Intelligence, Inc.,           //
//              Vestal, NY                                                    //
// Run From:    :CoreShared:CoreDrives.CC                                     //
// Purpose:     To obtain information on installed local drives and mapped    //
//              network drives on a system.                                   //
// Overview:    This is a simple class subclassed from the Array class.       //
//              When instantiated, the CoreDrives class will query the        //
//              system for installed logical drives currently configured.     //
//              This includes local drives and mapped network drives. The     //
//              array elements are filled with drive identifiers, and other   //
//              information specific to each drive.                           //
//              Each row in the array represents a local drive or mapped      //
//              network drive.                                                //
//              The columns are defined as:                                   //
//              1 - Drive reference as A:\, C:\, etc                          //
//              2 - Drive type as numeric 0 - 6                               //
//                  See 'Drive type constants' below                          //
//              3 - Volume label (local drives) or parsed UNC (network drives)//
//              4 - Volume serial (local drives) or "" (network drives)       //
//              5 - Maximum filename component length supported by filesystem //
//              6 - File system used on drive (FAT/FAT32/NTFS/CDFS/etc)       //
//              7 - File system flags (compression supported/type/etc)        //
//                  See 'File Flags' and 'File System Flags' below.           //
// Usage:       set procedure to :CoreShared:CoreDrives.CC additive           //
//              a = new CoreDrives()                                          //
//              A simple test program:                                        //
//              // GetDrives.PRG                                              //
//              LOCAL a, i                                                    //
//              set procedure to :CoreShared:CoreDrives.CC additive           //
//              a = new CoreDrives()  // Instantiate array and populate.      //
//              // Display column headings.                                   //
//              ? "Drv",;                                                     //
//                "Typ" at 5,;                                                //
//                "Label" at 10,;                                             //
//                "Serial" at 37,;                                            //
//                "Len" at 47,;                                               //
//                "File Sys" at 52,;                                          //
//                "Flags" at 62                                               //
//              // Display drive information.                                 //
//              for i = 1 to a.size / 7                                       //
//                 ? a[i, 1],;                                                //
//                   lTrim(Str(a[i, 2])) at 5,;                               //
//                   a[i, 3] at 10,;                                          //
//                   a[i, 4] at 37,;                                          //
//                   lTrim(str(a[i, 5])) at 47,;                              //
//                   a[i, 6] at 52,;                                          //
//                   iToH(a[i, 7]) at 62                                      //
//              endFor                                                        //
// Properties:  See the comments in the code.                                 //
// Methods:     See the comments in the code.                                 //
////////////////////////////////////////////////////////////////////////////////
// Versioning and History comments:
// #define COREDRIVES_VERSION  "1000"  // 08/15/2002 - Original version.
// #define COREDRIVES_VERSION  "1001"  // 08/17/2002 - Parse UNC name for network
//                                                       drives and cleanup.
// #define COREDRIVES_VERSION  "1002"  // 07/25/2003 - Corrected major bug in the
//                                                     implementation of
//                                                     WNetGetUniversalName
//                                                     which caused garbage
//                                                     chars in column 3 data.
//                                                     Also cleaned up comments.
#define COREDRIVES_VERSION  "1003"  // 07/25/2003 - Corrected for variation in
//                                                     operation of NT-based
//                                                     WNetGetUniversalNameA as
//                                                     it returns larger byte
//                                                     count than actually
//                                                     required for the string.

CLASS CoreDrives of Array

   if type("dB_GetDriveType") # "FP"
      extern CUINT dB_GetDriveType(CSTRING);
        kernel32 from "GetDriveTypeA"
   endIf
      // Drive type constants
      #define DRIVE_UNKNOWN              0
      #define DRIVE_NO_ROOT_DIR          1
      #define DRIVE_REMOVABLE            2
      #define DRIVE_FIXED                3
      #define DRIVE_REMOTE               4
      #define DRIVE_CDROM                5
      #define DRIVE_RAMDISK              6
   if type("dB_GetLogicalDriveStrings") # "FP"
      extern CULONG dB_GetLogicalDriveStrings(CULONG, CPTR);
        kernel32 from "GetLogicalDriveStringsA"
   endIf
   if type("dB_GetVolumeInformation") # "FP"
      extern CLOGICAL dB_GetVolumeInformation(CSTRING, CSTRING, CULONG, CPTR CULONG,;
                                              CPTR CULONG, CPTR CULONG, CSTRING, CULONG);
        kernel32 from "GetVolumeInformationA"
   endIf
      // File Flags
      #define FILE_CASE_SENSITIVE_SEARCH      0x00000001
      #define FILE_CASE_PRESERVED_NAMES       0x00000002
      #define FILE_UNICODE_ON_DISK            0x00000004
      #define FILE_PERSISTENT_ACLS            0x00000008
      #define FILE_FILE_COMPRESSION           0x00000010
      #define FILE_VOLUME_QUOTAS              0x00000020
      #define FILE_SUPPORTS_SPARSE_FILES      0x00000040
      #define FILE_SUPPORTS_REPARSE_POINTS    0x00000080
      #define FILE_VOLUME_IS_COMPRESSED       0x00008000
      #define FILE_SUPPORTS_OBJECT_IDS        0x00010000  
      #define FILE_SUPPORTS_ENCRYPTION        0x00020000  

      // File System Flags
      #define FS_CASE_IS_PRESERVED            FILE_CASE_PRESERVED_NAMES
      #define FS_CASE_SENSITIVE               FILE_CASE_SENSITIVE_SEARCH
      #define FS_UNICODE_STORED_ON_DISK       FILE_UNICODE_ON_DISK
      #define FS_PERSISTENT_ACLS              FILE_PERSISTENT_ACLS
      #define FS_VOL_IS_COMPRESSED            FILE_VOLUME_IS_COMPRESSED
      #define FS_FILE_COMPRESSION             FILE_FILE_COMPRESSION
   if type("dB_MultiByteToWideCharN3") # "FP"
      extern CINT dB_MultiByteToWideCharN3(CUINT, CULONG, CULONG, CINT, CPTR, CINT);
        kernel32 from "MultiByteToWideChar"
   endIf
      #define CP_ACP                          0       // default to ANSI code page
   if type("dB_WNetGetUniversalName") # "FP"
      extern CULONG dB_WNetGetUniversalName(CSTRING, CULONG, CPTR, CPTR CULONG);
        mpr from "WNetGetUniversalNameA"
   endIf
      #define UNIVERSAL_NAME_INFO_LEVEL       0x00000001
      #define REMOTE_NAME_INFO_LEVEL          0x00000002
      #define NO_ERROR                        0
      #define ERROR_MORE_DATA                 234

   // Published custom properties.
   this.version =                 COREDRIVES_VERSION

   this.getDrives()

   PROTECT GetDrives  // Can only populate the array once during instantiation,
                      //   so block additional calls to GetDrives.

   PROCEDURE GetDrives
      // Populate 7-column array with local and mapped network drive parameters.
      // Each row in the array represents a local drive or mapped network drive.
      // The columns are defined as:
      // 1 - Drive reference as A:\, C:\, etc
      // 2 - Drive type as numeric 0 - 6
      //     See 'Drive type constants' above
      // 3 - Volume label (local drives) or parsed UNC (network drives)
      // 4 - Volume serial (local drives) or "" (network drives)
      // 5 - Maximum filename component length supported by filesystem
      // 6 - File system used on drive (FAT/FAT32/NTFS/CDFS/etc)
      // 7 - File system flags (compression supported/type/etc)
      //     See 'File Flags' and 'File System Flags' above.
      LOCAL xBuff, i, cTemp, cVolName, nVolSer, nCompLen, nFlags,;
            cFileSys, xTEMP, nBuffSize, nRet, nPtr, cUniName

      xBuff = replicate(chr(0), ((26 * 4) + 2) / 2)
      dB_GetLogicalDriveStrings((26 * 4) + 2, xBuff)
      i = 0
      do
         if xBuff.getByte(i) == chr(0)
            exit
         endIf
         cTemp = ""
         do while xBuff.getByte(i) # chr(0)
            cTemp += upper(chr(xBuff.getByte(i++)))
         endDo
         this.add(cTemp)
      until ++ i > (26 * 4) + 1
      this.resize(this.size, 7, 1)
      for i = 1 to this.size / 7
         this[i, 2] := int(dB_GetDriveType(this[i, 1]))
         cVolName = space(50)
         nVolSer = 0
         nCompLen = 0
         nFlags = 0
         cFileSys = space(10)
         if dB_GetVolumeInformation(this[i, 1], cVolName, 30, nVolSer, nCompLen, nFlags, cFileSys, 20)
            if this[i, 2] = DRIVE_REMOTE
               xTEMP = ""
               nBuffSize = 0
               nRet = dB_WNetGetUniversalName(this[i, 1],;
                                              UNIVERSAL_NAME_INFO_LEVEL,;
                                              xTEMP,;
                                              nBuffSize)
               if nRet = ERROR_MORE_DATA
                  xTEMP := replicate(chr(0), ceiling(nBuffSize / 2))
                  nRet := dB_WNetGetUniversalName(this[i, 1],;
                                                  UNIVERSAL_NAME_INFO_LEVEL,;
                                                  xTEMP,;
                                                  nBuffSize)
               endIf
               if nRet = NO_ERROR
                  nPtr = Int(xTEMP.GetByte(0) +;
                             BitLShift(xTEMP.GetByte(1),  8) +;
                             BitLShift(xTEMP.GetByte(2), 16) +;
                             BitLShift(xTEMP.GetByte(3), 24))
                  cUniName = replicate(chr(0), nBuffSize - 5)
                  dB_MultiByteToWideCharN3(CP_ACP, 0, nPtr, -1, cUniName, nBuffSize - 5)
                  if at(chr(0), cUniName) # 0  // Required for NT-based O/S's
                     cUniName := left(cUniName, at(chr(0), cUniName) - 1)
                  endIf
                  if right(cUniName, 1) == "\"
                     cUniName := left(cUniName, len(cUniName) - 1)
                  endIf
                  this[i, 3] := subStr(cUniName, rAt("\", cUniName) + 1) + " on "
                  cUniName := left(cUniName, at("\", cUniName, 3) - 1)
                  this[i, 3] += subStr(cUniName, at("\\", cUniName) + 2)
               else
                  this[i, 3] := ""
               endIf
               this[i, 4] := ""
            else
               this[i, 3] := upper(iIf(at(chr(0), cVolName) # 0,;
                                       left(cVolName, at(chr(0), cVolName) - 1),;
                                       cVolName))
               this[i, 4] := iToH(nVolSer)
            endIf
            this[i, 5] := nCompLen
            this[i, 6] := upper(iIf(at(chr(0), cFileSys) # 0,;
                                               left(cFileSys, at(chr(0), cFileSys) - 1),;
                                               cFileSys))
            this[i, 7] := nFlags
         else
            this[i, 3] := ""
            this[i, 4] := ""
            this[i, 5] := 0
            this[i, 6] := ""
            this[i, 7] := 0
         endIf
      endFor

ENDCLASS  // CoreDrives of Array

