// Windows/FileFind.cpp

#include "StdAfx.h"

// #include <stdio.h>

#ifndef _WIN32
#include <fcntl.h>           /* Definition of AT_* constants */
#include "TimeUtils.h"
// for major
// #include <sys/sysmacros.h>
#endif

#include "FileFind.h"
#include "FileIO.h"
#include "FileName.h"

#ifndef _UNICODE
extern bool g_IsNT;
#endif

using namespace NWindows;
using namespace NFile;
using namespace NName;

#if defined(_WIN32) && !defined(UNDER_CE)

EXTERN_C_BEGIN

typedef enum
{
  My_FindStreamInfoStandard,
  My_FindStreamInfoMaxInfoLevel
} MY_STREAM_INFO_LEVELS;

typedef struct
{
  LARGE_INTEGER StreamSize;
  WCHAR cStreamName[MAX_PATH + 36];
} MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA;

typedef HANDLE (WINAPI *Func_FindFirstStreamW)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
    LPVOID findStreamData, DWORD flags);

typedef BOOL (APIENTRY *Func_FindNextStreamW)(HANDLE findStream, LPVOID findStreamData);

EXTERN_C_END

#endif // defined(_WIN32) && !defined(UNDER_CE)


namespace NWindows {
namespace NFile {


#ifdef _WIN32
#ifdef Z7_DEVICE_FILE
namespace NSystem
{
bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize);
}
#endif
#endif

namespace NFind {

/*
#ifdef _WIN32
#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;
#else
#define MY_CLEAR_FILETIME(ft) ft.tv_sec = 0;  ft.tv_nsec = 0;
#endif
*/

void CFileInfoBase::ClearBase() throw()
{
  Size = 0;
  FiTime_Clear(CTime);
  FiTime_Clear(ATime);
  FiTime_Clear(MTime);
 
 #ifdef _WIN32
  Attrib = 0;
  // ReparseTag = 0;
  IsAltStream = false;
  IsDevice = false;
 #else
  dev = 0;
  ino = 0;
  mode = 0;
  nlink = 0;
  uid = 0;
  gid = 0;
  rdev = 0;
 #endif
}

bool CFileInfo::IsDots() const throw()
{
  if (!IsDir() || Name.IsEmpty())
    return false;
  if (Name[0] != '.')
    return false;
  return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.');
}


#ifdef _WIN32


#define WIN_FD_TO_MY_FI(fi, fd) \
  fi.Attrib = fd.dwFileAttributes; \
  fi.CTime = fd.ftCreationTime; \
  fi.ATime = fd.ftLastAccessTime; \
  fi.MTime = fd.ftLastWriteTime; \
  fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \
  /* fi.ReparseTag = fd.dwReserved0; */ \
  fi.IsAltStream = false; \
  fi.IsDevice = false;

  /*
  #ifdef UNDER_CE
  fi.ObjectID = fd.dwOID;
  #else
  fi.ReparseTag = fd.dwReserved0;
  #endif
  */

static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi)
{
  WIN_FD_TO_MY_FI(fi, fd)
  fi.Name = us2fs(fd.cFileName);
  #if defined(_WIN32) && !defined(UNDER_CE)
  // fi.ShortName = us2fs(fd.cAlternateFileName);
  #endif
}

#ifndef _UNICODE
static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi)
{
  WIN_FD_TO_MY_FI(fi, fd)
  fi.Name = fas2fs(fd.cFileName);
  #if defined(_WIN32) && !defined(UNDER_CE)
  // fi.ShortName = fas2fs(fd.cAlternateFileName);
  #endif
}
#endif
  
////////////////////////////////
// CFindFile

bool CFindFileBase::Close() throw()
{
  if (_handle == INVALID_HANDLE_VALUE)
    return true;
  if (!::FindClose(_handle))
    return false;
  _handle = INVALID_HANDLE_VALUE;
  return true;
}

/*
WinXP-64 FindFirstFile():
  ""      -  ERROR_PATH_NOT_FOUND
  folder\ -  ERROR_FILE_NOT_FOUND
  \       -  ERROR_FILE_NOT_FOUND
  c:\     -  ERROR_FILE_NOT_FOUND
  c:      -  ERROR_FILE_NOT_FOUND, if current dir is ROOT     ( c:\ )
  c:      -  OK,                   if current dir is NOT ROOT ( c:\folder )
  folder  -  OK

  \\               - ERROR_INVALID_NAME
  \\Server         - ERROR_INVALID_NAME
  \\Server\        - ERROR_INVALID_NAME
      
  \\Server\Share            - ERROR_BAD_NETPATH
  \\Server\Share            - ERROR_BAD_NET_NAME (Win7).
             !!! There is problem : Win7 makes some requests for "\\Server\Shar" (look in Procmon),
                 when we call it for "\\Server\Share"
                      
  \\Server\Share\           - ERROR_FILE_NOT_FOUND
  
  \\?\UNC\Server\Share      - ERROR_INVALID_NAME
  \\?\UNC\Server\Share      - ERROR_BAD_PATHNAME (Win7)
  \\?\UNC\Server\Share\     - ERROR_FILE_NOT_FOUND
  
  \\Server\Share_RootDrive  - ERROR_INVALID_NAME
  \\Server\Share_RootDrive\ - ERROR_INVALID_NAME
  
  e:\* - ERROR_FILE_NOT_FOUND, if there are no items in that root folder
  w:\* - ERROR_PATH_NOT_FOUND, if there is no such drive w:
*/

bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi)
{
  if (!Close())
    return false;
  #ifndef _UNICODE
  if (!g_IsNT)
  {
    WIN32_FIND_DATAA fd;
    _handle = ::FindFirstFileA(fs2fas(path), &fd);
    if (_handle == INVALID_HANDLE_VALUE)
      return false;
    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
  }
  else
  #endif
  {
    WIN32_FIND_DATAW fd;

    IF_USE_MAIN_PATH
      _handle = ::FindFirstFileW(fs2us(path), &fd);
    #ifdef Z7_LONG_PATH
    if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
    {
      UString superPath;
      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
        _handle = ::FindFirstFileW(superPath, &fd);
    }
    #endif
    if (_handle == INVALID_HANDLE_VALUE)
      return false;
    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
  }
  return true;
}

bool CFindFile::FindNext(CFileInfo &fi)
{
  #ifndef _UNICODE
  if (!g_IsNT)
  {
    WIN32_FIND_DATAA fd;
    if (!::FindNextFileA(_handle, &fd))
      return false;
    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
  }
  else
  #endif
  {
    WIN32_FIND_DATAW fd;
    if (!::FindNextFileW(_handle, &fd))
      return false;
    Convert_WIN32_FIND_DATA_to_FileInfo(fd, fi);
  }
  return true;
}

#if defined(_WIN32) && !defined(UNDER_CE)

////////////////////////////////
// AltStreams

static Func_FindFirstStreamW g_FindFirstStreamW;
static Func_FindNextStreamW g_FindNextStreamW;

static struct CFindStreamLoader
{
  CFindStreamLoader()
  {
    const HMODULE hm = ::GetModuleHandleA("kernel32.dll");
       g_FindFirstStreamW = Z7_GET_PROC_ADDRESS(
    Func_FindFirstStreamW, hm,
        "FindFirstStreamW");
       g_FindNextStreamW = Z7_GET_PROC_ADDRESS(
    Func_FindNextStreamW, hm,
        "FindNextStreamW");
  }
} g_FindStreamLoader;

bool CStreamInfo::IsMainStream() const throw()
{
  return StringsAreEqualNoCase_Ascii(Name, "::$DATA");
}

UString CStreamInfo::GetReducedName() const
{
  // remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA"
  UString s (Name);
  if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA"))
    s.DeleteFrom(s.Len() - 6);
  return s;
}

/*
UString CStreamInfo::GetReducedName2() const
{
  UString s = GetReducedName();
  if (!s.IsEmpty() && s[0] == ':')
    s.Delete(0);
  return s;
}
*/

static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si)
{
  si.Size = (UInt64)sd.StreamSize.QuadPart;
  si.Name = sd.cStreamName;
}

/*
  WinXP-64 FindFirstStream():
  ""      -  ERROR_PATH_NOT_FOUND
  folder\ -  OK
  folder  -  OK
  \       -  OK
  c:\     -  OK
  c:      -  OK, if current dir is ROOT     ( c:\ )
  c:      -  OK, if current dir is NOT ROOT ( c:\folder )
  \\Server\Share   - OK
  \\Server\Share\  - OK

  \\               - ERROR_INVALID_NAME
  \\Server         - ERROR_INVALID_NAME
  \\Server\        - ERROR_INVALID_NAME
*/

bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si)
{
  if (!Close())
    return false;
  if (!g_FindFirstStreamW)
  {
    ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return false;
  }
  {
    MY_WIN32_FIND_STREAM_DATA sd;
    SetLastError(0);
    IF_USE_MAIN_PATH
      _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0);
    if (_handle == INVALID_HANDLE_VALUE)
    {
      if (::GetLastError() == ERROR_HANDLE_EOF)
        return false;
      // long name can be tricky for path like ".\dirName".
      #ifdef Z7_LONG_PATH
      if (USE_SUPER_PATH)
      {
        UString superPath;
        if (GetSuperPath(path, superPath, USE_MAIN_PATH))
          _handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0);
      }
      #endif
    }
    if (_handle == INVALID_HANDLE_VALUE)
      return false;
    Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
  }
  return true;
}

bool CFindStream::FindNext(CStreamInfo &si)
{
  if (!g_FindNextStreamW)
  {
    ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
    return false;
  }
  {
    MY_WIN32_FIND_STREAM_DATA sd;
    if (!g_FindNextStreamW(_handle, &sd))
      return false;
    Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si);
  }
  return true;
}

bool CStreamEnumerator::Next(CStreamInfo &si, bool &found)
{
  bool res;
  if (_find.IsHandleAllocated())
    res = _find.FindNext(si);
  else
    res = _find.FindFirst(_filePath, si);
  if (res)
  {
    found = true;
    return true;
  }
  found = false;
  return (::GetLastError() == ERROR_HANDLE_EOF);
}

#endif


/*
WinXP-64 GetFileAttributes():
  If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code

  \    - OK
  C:\  - OK, if there is such drive,
  D:\  - ERROR_PATH_NOT_FOUND, if there is no such drive,

  C:\folder     - OK
  C:\folder\    - OK
  C:\folderBad  - ERROR_FILE_NOT_FOUND

  \\Server\BadShare  - ERROR_BAD_NETPATH
  \\Server\Share     - WORKS OK, but MSDN says:
                          GetFileAttributes for a network share, the function fails, and GetLastError
                          returns ERROR_BAD_NETPATH. You must specify a path to a subfolder on that share.
*/

DWORD GetFileAttrib(CFSTR path)
{
  #ifndef _UNICODE
  if (!g_IsNT)
    return ::GetFileAttributes(fs2fas(path));
  else
  #endif
  {
    IF_USE_MAIN_PATH
    {
      DWORD dw = ::GetFileAttributesW(fs2us(path));
      if (dw != INVALID_FILE_ATTRIBUTES)
        return dw;
    }
    #ifdef Z7_LONG_PATH
    if (USE_SUPER_PATH)
    {
      UString superPath;
      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
        return ::GetFileAttributesW(superPath);
    }
    #endif
    return INVALID_FILE_ATTRIBUTES;
  }
}

/* if path is "c:" or "c::" then CFileInfo::Find() returns name of current folder for that disk
   so instead of absolute path we have relative path in Name. That is not good in some calls */

/* In CFileInfo::Find() we want to support same names for alt streams as in CreateFile(). */

/* CFileInfo::Find()
We alow the following paths (as FindFirstFile):
  C:\folder
  c:                      - if current dir is NOT ROOT ( c:\folder )

also we support paths that are not supported by FindFirstFile:
  \
  \\.\c:
  c:\                     - Name will be without tail slash ( c: )
  \\?\c:\                 - Name will be without tail slash ( c: )
  \\Server\Share
  \\?\UNC\Server\Share

  c:\folder:stream  - Name = folder:stream
  c:\:stream        - Name = :stream
  c::stream         - Name = c::stream
*/

bool CFileInfo::Find(CFSTR path, bool followLink)
{
  #ifdef Z7_DEVICE_FILE
  
  if (IS_PATH_SEPAR(path[0]) &&
      IS_PATH_SEPAR(path[1]) &&
      path[2] == '.' &&
      path[3] == 0)
  {
    // 22.00 : it's virtual directory for devices
    // IsDevice = true;
    ClearBase();
    Name = path + 2;
    Attrib = FILE_ATTRIBUTE_DIRECTORY;
    return true;
  }
  
  if (IsDevicePath(path))
  {
    ClearBase();
    Name = path + 4;
    IsDevice = true;
    
    if (NName::IsDrivePath2(path + 4) && path[6] == 0)
    {
      FChar drive[4] = { path[4], ':', '\\', 0 };
      UInt64 clusterSize, totalSize, freeSize;
      if (NSystem::MyGetDiskFreeSpace(drive, clusterSize, totalSize, freeSize))
      {
        Size = totalSize;
        return true;
      }
    }

    NIO::CInFile inFile;
    // ::OutputDebugStringW(path);
    if (!inFile.Open(path))
      return false;
    // ::OutputDebugStringW(L"---");
    if (inFile.SizeDefined)
      Size = inFile.Size;
    return true;
  }
  #endif

  #if defined(_WIN32) && !defined(UNDER_CE)

  const int colonPos = FindAltStreamColon(path);
  if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0)
  {
    UString streamName = fs2us(path + (unsigned)colonPos);
    FString filePath (path);
    filePath.DeleteFrom((unsigned)colonPos);
    /* we allow both cases:
      name:stream
      name:stream:$DATA
    */
    const unsigned kPostfixSize = 6;
    if (streamName.Len() <= kPostfixSize
        || !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA"))
      streamName += ":$DATA";

    bool isOk = true;
    
    if (IsDrivePath2(filePath) &&
        (colonPos == 2 || (colonPos == 3 && filePath[2] == '\\')))
    {
      // FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT)
      ClearBase();
      Name.Empty();
      if (colonPos == 2)
        Name = filePath;
    }
    else
      isOk = Find(filePath, followLink); // check it (followLink)

    if (isOk)
    {
      Attrib &= ~(DWORD)(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT);
      Size = 0;
      CStreamEnumerator enumerator(filePath);
      for (;;)
      {
        CStreamInfo si;
        bool found;
        if (!enumerator.Next(si, found))
          return false;
        if (!found)
        {
          ::SetLastError(ERROR_FILE_NOT_FOUND);
          return false;
        }
        if (si.Name.IsEqualTo_NoCase(streamName))
        {
          // we delete postfix, if alt stream name is not "::$DATA"
          if (si.Name.Len() > kPostfixSize + 1)
            si.Name.DeleteFrom(si.Name.Len() - kPostfixSize);
          Name += us2fs(si.Name);
          Size = si.Size;
          IsAltStream = true;
          return true;
        }
      }
    }
  }
  
  #endif

  CFindFile finder;

  #if defined(_WIN32) && !defined(UNDER_CE)
  {
    /*
    DWORD lastError = GetLastError();
    if (lastError == ERROR_FILE_NOT_FOUND
        || lastError == ERROR_BAD_NETPATH  // XP64: "\\Server\Share"
        || lastError == ERROR_BAD_NET_NAME // Win7: "\\Server\Share"
        || lastError == ERROR_INVALID_NAME // XP64: "\\?\UNC\Server\Share"
        || lastError == ERROR_BAD_PATHNAME // Win7: "\\?\UNC\Server\Share"
        )
    */
    
    unsigned rootSize = 0;
    if (IsSuperPath(path))
      rootSize = kSuperPathPrefixSize;
    
    if (NName::IsDrivePath(path + rootSize) && path[rootSize + 3] == 0)
    {
      DWORD attrib = GetFileAttrib(path);
      if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
      {
        ClearBase();
        Attrib = attrib;
        Name = path + rootSize;
        Name.DeleteFrom(2);
        if (!Fill_From_ByHandleFileInfo(path))
        {
        }
        return true;
      }
    }
    else if (IS_PATH_SEPAR(path[0]))
    {
      if (path[1] == 0)
      {
        DWORD attrib = GetFileAttrib(path);
        if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)
        {
          ClearBase();
          Name.Empty();
          Attrib = attrib;
          return true;
        }
      }
      else
      {
        const unsigned prefixSize = GetNetworkServerPrefixSize(path);
        if (prefixSize > 0 && path[prefixSize] != 0)
        {
          if (NName::FindSepar(path + prefixSize) < 0)
          {
            if (Fill_From_ByHandleFileInfo(path))
            {
              Name = path + prefixSize;
              return true;
            }

            FString s (path);
            s.Add_PathSepar();
            s += '*'; // CHAR_ANY_MASK
            bool isOK = false;
            if (finder.FindFirst(s, *this))
            {
              if (Name == FTEXT("."))
              {
                Name = path + prefixSize;
                return true;
              }
              isOK = true;
              /* if "\\server\share" maps to root folder "d:\", there is no "." item.
                 But it's possible that there are another items */
            }
            {
              DWORD attrib = GetFileAttrib(path);
              if (isOK || (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0))
              {
                ClearBase();
                if (attrib != INVALID_FILE_ATTRIBUTES)
                  Attrib = attrib;
                else
                  SetAsDir();
                Name = path + prefixSize;
                return true;
              }
            }
            // ::SetLastError(lastError);
          }
        }
      }
    }
  }
  #endif

  bool res = finder.FindFirst(path, *this);
  if (!followLink
      || !res
      || !HasReparsePoint())
    return res;

  // return FollowReparse(path, IsDir());
  return Fill_From_ByHandleFileInfo(path);
}

bool CFileInfoBase::Fill_From_ByHandleFileInfo(CFSTR path)
{
  BY_HANDLE_FILE_INFORMATION info;
  if (!NIO::CFileBase::GetFileInformation(path, &info))
    return false;
  {
    Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
    CTime = info.ftCreationTime;
    ATime = info.ftLastAccessTime;
    MTime = info.ftLastWriteTime;
    Attrib = info.dwFileAttributes;
    return true;
  }
}

/*
bool CFileInfo::FollowReparse(CFSTR path, bool isDir)
{
  if (isDir)
  {
    FString prefix = path;
    prefix.Add_PathSepar();

    // "folder/." refers to folder itself. So we can't use that path
    // we must use enumerator and search "." item
    CEnumerator enumerator;
    enumerator.SetDirPrefix(prefix);
    for (;;)
    {
      CFileInfo fi;
      if (!enumerator.NextAny(fi))
        break;
      if (fi.Name.IsEqualTo_Ascii_NoCase("."))
      {
        // we can copy preperies;
        CTime = fi.CTime;
        ATime = fi.ATime;
        MTime = fi.MTime;
        Attrib = fi.Attrib;
        Size = fi.Size;
        return true;
      }
      break;
    }
    // LastError(lastError);
    return false;
  }

  {
    NIO::CInFile inFile;
    if (inFile.Open(path))
    {
      BY_HANDLE_FILE_INFORMATION info;
      if (inFile.GetFileInformation(&info))
      {
        ClearBase();
        Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow;
        CTime = info.ftCreationTime;
        ATime = info.ftLastAccessTime;
        MTime = info.ftLastWriteTime;
        Attrib = info.dwFileAttributes;
        return true;
      }
    }
    return false;
  }
}
*/

bool DoesFileExist_Raw(CFSTR name)
{
  CFileInfo fi;
  return fi.Find(name) && !fi.IsDir();
}

bool DoesFileExist_FollowLink(CFSTR name)
{
  CFileInfo fi;
  return fi.Find_FollowLink(name) && !fi.IsDir();
}

bool DoesDirExist(CFSTR name, bool followLink)
{
  CFileInfo fi;
  return fi.Find(name, followLink) && fi.IsDir();
}

bool DoesFileOrDirExist(CFSTR name)
{
  CFileInfo fi;
  return fi.Find(name);
}


void CEnumerator::SetDirPrefix(const FString &dirPrefix)
{
  _wildcard = dirPrefix;
  _wildcard += '*';
}

bool CEnumerator::NextAny(CFileInfo &fi)
{
  if (_findFile.IsHandleAllocated())
    return _findFile.FindNext(fi);
  else
    return _findFile.FindFirst(_wildcard, fi);
}

bool CEnumerator::Next(CFileInfo &fi)
{
  for (;;)
  {
    if (!NextAny(fi))
      return false;
    if (!fi.IsDots())
      return true;
  }
}

bool CEnumerator::Next(CFileInfo &fi, bool &found)
{
  /*
  for (;;)
  {
    if (!NextAny(fi))
      break;
    if (!fi.IsDots())
    {
      found = true;
      return true;
    }
  }
  */

  if (Next(fi))
  {
    found = true;
    return true;
  }

  found = false;
  DWORD lastError = ::GetLastError();
  if (_findFile.IsHandleAllocated())
    return (lastError == ERROR_NO_MORE_FILES);
  // we support the case for empty root folder: FindFirstFile("c:\\*") returns ERROR_FILE_NOT_FOUND
  if (lastError == ERROR_FILE_NOT_FOUND)
    return true;
  if (lastError == ERROR_ACCESS_DENIED)
  {
    // here we show inaccessible root system folder as empty folder to eliminate redundant user warnings
    const char *s = "System Volume Information" STRING_PATH_SEPARATOR "*";
    const int len = (int)strlen(s);
    const int delta = (int)_wildcard.Len() - len;
    if (delta == 0 || (delta > 0 && IS_PATH_SEPAR(_wildcard[(unsigned)delta - 1])))
      if (StringsAreEqual_Ascii(_wildcard.Ptr((unsigned)delta), s))
        return true;
  }
  return false;
}


////////////////////////////////
// CFindChangeNotification
// FindFirstChangeNotification can return 0. MSDN doesn't tell about it.

bool CFindChangeNotification::Close() throw()
{
  if (!IsHandleAllocated())
    return true;
  if (!::FindCloseChangeNotification(_handle))
    return false;
  _handle = INVALID_HANDLE_VALUE;
  return true;
}
           
HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD notifyFilter)
{
  #ifndef _UNICODE
  if (!g_IsNT)
    _handle = ::FindFirstChangeNotification(fs2fas(path), BoolToBOOL(watchSubtree), notifyFilter);
  else
  #endif
  {
    IF_USE_MAIN_PATH
    _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter);
    #ifdef Z7_LONG_PATH
    if (!IsHandleAllocated())
    {
      UString superPath;
      if (GetSuperPath(path, superPath, USE_MAIN_PATH))
        _handle = ::FindFirstChangeNotificationW(superPath, BoolToBOOL(watchSubtree), notifyFilter);
    }
    #endif
  }
  return _handle;
}

#ifndef UNDER_CE

bool MyGetLogicalDriveStrings(CObjectVector<FString> &driveStrings)
{
  driveStrings.Clear();
  #ifndef _UNICODE
  if (!g_IsNT)
  {
    driveStrings.Clear();
    UINT32 size = GetLogicalDriveStrings(0, NULL);
    if (size == 0)
      return false;
    CObjArray<char> buf(size);
    UINT32 newSize = GetLogicalDriveStrings(size, buf);
    if (newSize == 0 || newSize > size)
      return false;
    AString s;
    UINT32 prev = 0;
    for (UINT32 i = 0; i < newSize; i++)
    {
      if (buf[i] == 0)
      {
        s = buf + prev;
        prev = i + 1;
        driveStrings.Add(fas2fs(s));
      }
    }
    return prev == newSize;
  }
  else
  #endif
  {
    UINT32 size = GetLogicalDriveStringsW(0, NULL);
    if (size == 0)
      return false;
    CObjArray<wchar_t> buf(size);
    UINT32 newSize = GetLogicalDriveStringsW(size, buf);
    if (newSize == 0 || newSize > size)
      return false;
    UString s;
    UINT32 prev = 0;
    for (UINT32 i = 0; i < newSize; i++)
    {
      if (buf[i] == 0)
      {
        s = buf + prev;
        prev = i + 1;
        driveStrings.Add(us2fs(s));
      }
    }
    return prev == newSize;
  }
}

#endif // UNDER_CE



#else // _WIN32

// ---------- POSIX ----------

static int MY__lstat(CFSTR path, struct stat *st, bool followLink)
{
  memset(st, 0, sizeof(*st));
  int res;
  // #ifdef ENV_HAVE_LSTAT
  if (/* global_use_lstat && */ !followLink)
  {
    // printf("\nlstat\n");
    res = lstat(path, st);
  }
  else
  // #endif
  {
    // printf("\nstat\n");
    res = stat(path, st);
  }
  /*
  printf("\nres = %d\n", res);
  printf("\n st_dev = %lld \n", (long long)(st->st_dev));
  printf("\n st_ino = %lld \n", (long long)(st->st_ino));
  printf("\n st_mode = %lld \n", (long long)(st->st_mode));
  printf("\n st_nlink = %lld \n", (long long)(st->st_nlink));
  printf("\n st_uid = %lld \n", (long long)(st->st_uid));
  printf("\n st_gid = %lld \n", (long long)(st->st_gid));
  printf("\n st_size = %lld \n", (long long)(st->st_size));
  printf("\n st_blksize = %lld \n", (long long)(st->st_blksize));
  printf("\n st_blocks = %lld \n", (long long)(st->st_blocks));
  */

  return res;
}


static const char *Get_Name_from_Path(CFSTR path) throw()
{
  size_t len = strlen(path);
  if (len == 0)
    return path;
  const char *p = path + len - 1;
  {
    if (p == path)
      return path;
    p--;
  }
  for (;;)
  {
    char c = *p;
    if (IS_PATH_SEPAR(c))
      return p + 1;
    if (p == path)
      return path;
    p--;
  }
}


UInt32 Get_WinAttribPosix_From_PosixMode(UInt32 mode)
{
  UInt32 attrib = S_ISDIR(mode) ?
      FILE_ATTRIBUTE_DIRECTORY :
      FILE_ATTRIBUTE_ARCHIVE;
  if ((mode & 0222) == 0) // S_IWUSR in p7zip
    attrib |= FILE_ATTRIBUTE_READONLY;
  return attrib | FILE_ATTRIBUTE_UNIX_EXTENSION | ((mode & 0xFFFF) << 16);
}

/*
UInt32 Get_WinAttrib_From_stat(const struct stat &st)
{
  UInt32 attrib = S_ISDIR(st.st_mode) ?
    FILE_ATTRIBUTE_DIRECTORY :
    FILE_ATTRIBUTE_ARCHIVE;

  if ((st.st_mode & 0222) == 0) // check it !!!
    attrib |= FILE_ATTRIBUTE_READONLY;

  attrib |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((st.st_mode & 0xFFFF) << 16);
  return attrib;
}
*/

void CFileInfo::SetFrom_stat(const struct stat &st)
{
  // IsDevice = false;

  if (S_ISDIR(st.st_mode))
  {
    Size = 0;
  }
  else
  {
    Size = (UInt64)st.st_size; // for a symbolic link, size = size of filename
  }

  // Attrib = Get_WinAttribPosix_From_PosixMode(st.st_mode);

  // NTime::UnixTimeToFileTime(st.st_ctime, CTime);
  // NTime::UnixTimeToFileTime(st.st_mtime, MTime);
  // NTime::UnixTimeToFileTime(st.st_atime, ATime);
  #ifdef __APPLE__
  // #ifdef _DARWIN_FEATURE_64_BIT_INODE
  /*
    here we can use birthtime instead of st_ctimespec.
    but we use st_ctimespec for compatibility with previous versions and p7zip.
    st_birthtimespec in OSX
    st_birthtim : at FreeBSD, NetBSD
  */
  // timespec_To_FILETIME(st.st_birthtimespec, CTime);
  // #else
  // timespec_To_FILETIME(st.st_ctimespec, CTime);
  // #endif
  // timespec_To_FILETIME(st.st_mtimespec, MTime);
  // timespec_To_FILETIME(st.st_atimespec, ATime);
  CTime = st.st_ctimespec;
  MTime = st.st_mtimespec;
  ATime = st.st_atimespec;

  #else
  // timespec_To_FILETIME(st.st_ctim, CTime, &CTime_ns100);
  // timespec_To_FILETIME(st.st_mtim, MTime, &MTime_ns100);
  // timespec_To_FILETIME(st.st_atim, ATime, &ATime_ns100);
  CTime = st.st_ctim;
  MTime = st.st_mtim;
  ATime = st.st_atim;

  #endif

  dev = st.st_dev;
  ino = st.st_ino;
  mode = st.st_mode;
  nlink = st.st_nlink;
  uid = st.st_uid;
  gid = st.st_gid;
  rdev = st.st_rdev;

  /*
  printf("\n sizeof timespec = %d", (int)sizeof(timespec));
  printf("\n sizeof st_rdev = %d", (int)sizeof(rdev));
  printf("\n sizeof st_ino = %d", (int)sizeof(ino));
  printf("\n sizeof mode_t = %d", (int)sizeof(mode_t));
  printf("\n sizeof nlink_t = %d", (int)sizeof(nlink_t));
  printf("\n sizeof uid_t = %d", (int)sizeof(uid_t));
  printf("\n");
  */
  /*
  printf("\n st_rdev = %llx", (long long)rdev);
  printf("\n st_dev  = %llx", (long long)dev);
  printf("\n dev  : major = %5x minor = %5x", (unsigned)major(dev), (unsigned)minor(dev));
  printf("\n st_ino = %lld", (long long)(ino));
  printf("\n rdev : major = %5x minor = %5x", (unsigned)major(rdev), (unsigned)minor(rdev));
  printf("\n size = %lld \n", (long long)(Size));
  printf("\n");
  */
}

/*
int Uid_To_Uname(uid_t uid, AString &name)
{
  name.Empty();
  struct passwd *passwd;

  if (uid != 0 && uid == cached_no_such_uid)
    {
      *uname = xstrdup ("");
      return;
    }

  if (!cached_uname || uid != cached_uid)
    {
      passwd = getpwuid (uid);
      if (passwd)
  {
    cached_uid = uid;
    assign_string (&cached_uname, passwd->pw_name);
  }
      else
  {
    cached_no_such_uid = uid;
    *uname = xstrdup ("");
    return;
  }
    }
  *uname = xstrdup (cached_uname);
}
*/

bool CFileInfo::Find_DontFill_Name(CFSTR path, bool followLink)
{
  struct stat st;
  if (MY__lstat(path, &st, followLink) != 0)
    return false;
  // printf("\nFind_DontFill_Name : name=%s\n", path);
  SetFrom_stat(st);
  return true;
}


bool CFileInfo::Find(CFSTR path, bool followLink)
{
  // printf("\nCEnumerator::Find() name = %s\n", path);
  if (!Find_DontFill_Name(path, followLink))
    return false;

  // printf("\nOK\n");

  Name = Get_Name_from_Path(path);
  if (!Name.IsEmpty())
  {
    char c = Name.Back();
    if (IS_PATH_SEPAR(c))
      Name.DeleteBack();
  }
  return true;
}


bool DoesFileExist_Raw(CFSTR name)
{
  // FIXME for symbolic links.
  struct stat st;
  if (MY__lstat(name, &st, false) != 0)
    return false;
  return !S_ISDIR(st.st_mode);
}

bool DoesFileExist_FollowLink(CFSTR name)
{
  // FIXME for symbolic links.
  struct stat st;
  if (MY__lstat(name, &st, true) != 0)
    return false;
  return !S_ISDIR(st.st_mode);
}

bool DoesDirExist(CFSTR name, bool followLink)
{
  struct stat st;
  if (MY__lstat(name, &st, followLink) != 0)
    return false;
  return S_ISDIR(st.st_mode);
}

bool DoesFileOrDirExist(CFSTR name)
{
  struct stat st;
  if (MY__lstat(name, &st, false) != 0)
    return false;
  return true;
}


CEnumerator::~CEnumerator()
{
  if (_dir)
    closedir(_dir);
}

void CEnumerator::SetDirPrefix(const FString &dirPrefix)
{
  _wildcard = dirPrefix;
}

bool CDirEntry::IsDots() const throw()
{
  /* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN)
     we can call fstatat() for that case, but we use only (Name) check here */

  #if !defined(_AIX)
  if (Type != DT_DIR && Type != DT_UNKNOWN)
    return false;
  #endif

  return Name.Len() != 0
      && Name.Len() <= 2
      && Name[0] == '.'
      && (Name.Len() == 1 || Name[1] == '.');
}


bool CEnumerator::NextAny(CDirEntry &fi, bool &found)
{
  found = false;

  if (!_dir)
  {
    const char *w = "./";
    if (!_wildcard.IsEmpty())
      w = _wildcard.Ptr();
    _dir = ::opendir((const char *)w);
    if (_dir == NULL)
      return false;
  }

  // To distinguish end of stream from an error, we must set errno to zero before readdir()
  errno = 0;

  struct dirent *de = readdir(_dir);
  if (!de)
  {
    if (errno == 0)
      return true; // it's end of stream, and we report it with (found = false)
    // it's real error
    return false;
  }

  fi.iNode = de->d_ino;
  
  #if !defined(_AIX)
  fi.Type = de->d_type;
  /* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN)
     we can set (Type) from fstatat() in that case.
     But (Type) is not too important. So we don't set it here with slow fstatat() */
  /*
  // fi.Type = DT_UNKNOWN; // for debug
  if (fi.Type == DT_UNKNOWN)
  {
    struct stat st;
    if (fstatat(dirfd(_dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0)
      if (S_ISDIR(st.st_mode))
        fi.Type = DT_DIR;
  }
  */
  #endif
  
  /*
  if (de->d_type == DT_DIR)
    fi.Attrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(S_IFDIR) << 16);
  else if (de->d_type < 16)
    fi.Attrib = FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(de->d_type) << (16 + 12));
  */
  fi.Name = de->d_name;

  /*
  printf("\nCEnumerator::NextAny; len = %d %s \n", (int)fi.Name.Len(), fi.Name.Ptr());
  for (unsigned i = 0; i < fi.Name.Len(); i++)
    printf (" %02x", (unsigned)(Byte)de->d_name[i]);
  printf("\n");
  */

  found = true;
  return true;
}


bool CEnumerator::Next(CDirEntry &fi, bool &found)
{
  // printf("\nCEnumerator::Next()\n");
  // PrintName("Next", "");
  for (;;)
  {
    if (!NextAny(fi, found))
      return false;
    if (!found)
      return true;
    if (!fi.IsDots())
    {
      /*
      if (!NeedFullStat)
        return true;
      // we silently skip error file here - it can be wrong link item
      if (fi.Find_DontFill_Name(path))
        return true;
      */
      return true;
    }
  }
}

/*
bool CEnumerator::Next(CDirEntry &fileInfo, bool &found)
{
  bool found;
  if (!Next(fi, found))
    return false;
  return found;
}
*/

bool CEnumerator::Fill_FileInfo(const CDirEntry &de, CFileInfo &fileInfo, bool followLink) const
{
  // printf("\nCEnumerator::Fill_FileInfo()\n");
  struct stat st;
  // probably it's OK to use fstatat() even if it changes file position dirfd(_dir)
  int res = fstatat(dirfd(_dir), de.Name, &st, followLink ? 0 : AT_SYMLINK_NOFOLLOW);
  // if fstatat() is not supported, we can use stat() / lstat()
  
  /*
  const FString path = _wildcard + s;
  int res = MY__lstat(path, &st, followLink);
  */
  
  if (res != 0)
    return false;
  // printf("\nname=%s\n", de.Name.Ptr());
  fileInfo.SetFrom_stat(st);
  fileInfo.Name = de.Name;
  return true;
}

#endif // _WIN32

}}}
