// Windows/FileFind.cpp

#include "StdAfx.h"

#include "FileFind.h"
#include "FileIO.h"
#include "FileName.h"
#ifndef _UNICODE
#include "../Common/StringConvert.h"
#endif

#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 WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel,
    LPVOID findStreamData, DWORD flags);

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

EXTERN_C_END

#endif

namespace NWindows {
namespace NFile {

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

namespace NFind {

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

#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.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;
}

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 WIN_LONG_PATH
    if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH)
    {
      UString longPath;
      if (GetSuperPath(path, longPath, USE_MAIN_PATH))
        _handle = ::FindFirstFileW(longPath, &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 FindFirstStreamW_Ptr g_FindFirstStreamW;
static FindNextStreamW_Ptr g_FindNextStreamW;

struct CFindStreamLoader
{
  CFindStreamLoader()
  {
    g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW");
    g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW");
  }
} g_FindStreamLoader;

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

UString CStreamInfo::GetReducedName() const
{
  UString s = Name;
  if (s.Len() >= 6)
    if (wcscmp(s.RightPtr(6), L":$DATA") == 0)
      s.DeleteFrom(s.Len() - 6);
  return s;
}

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

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;
    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 WIN_LONG_PATH
      if (USE_SUPER_PATH)
      {
        UString longPath;
        if (GetSuperPath(path, longPath, USE_MAIN_PATH))
          _handle = g_FindFirstStreamW(longPath, 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


#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0;

void CFileInfoBase::Clear() throw()
{
  Size = 0;
  MY_CLEAR_FILETIME(CTime);
  MY_CLEAR_FILETIME(ATime);
  MY_CLEAR_FILETIME(MTime);
  Attrib = 0;
  IsAltStream = false;
  IsDevice = false;
}

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

static int FindAltStreamColon(CFSTR path)
{
  for (int i = 0;; i++)
  {
    FChar c = path[i];
    if (c == 0)
      return -1;
    if (c == ':')
    {
      if (path[i + 1] == '\\')
        if (i == 1 || (i > 1 && path[i - 2] == '\\'))
        {
          wchar_t c0 = path[i - 1];
          if (c0 >= 'a' && c0 <= 'z' ||
              c0 >= 'A' && c0 <= 'Z')
            continue;
        }
      return i;
    }
  }
}

#endif

bool CFileInfo::Find(CFSTR path)
{
  #ifdef SUPPORT_DEVICE_FILE
  if (IsDevicePath(path))
  {
    Clear();
    Name = path + 4;

    IsDevice = true;
    if (/* path[0] == '\\' && path[1] == '\\' && path[2] == '.' && path[3] == '\\' && */
        path[5] == ':' && 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)

  int colonPos = FindAltStreamColon(path);
  if (colonPos >= 0)
  {
    UString streamName = fs2us(path + (unsigned)colonPos);
    FString filePath = path;
    filePath.DeleteFrom(colonPos);
    streamName += L":$DATA"; // change it!!!!
    if (Find(filePath))
    {
      // if (IsDir())
        Attrib &= ~FILE_ATTRIBUTE_DIRECTORY;
      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.IsEqualToNoCase(streamName))
        {
          Name += us2fs(si.Name);
          Name.DeleteFrom(Name.Len() - 6);
          Size = si.Size;
          IsAltStream = true;
          return true;
        }
      }
    }
  }
  
  #endif

  CFindFile finder;
  if (finder.FindFirst(path, *this))
    return true;
  #ifdef _WIN32
  {
    DWORD lastError = GetLastError();
    if (lastError == ERROR_BAD_NETPATH ||
        lastError == ERROR_FILE_NOT_FOUND ||
        lastError == ERROR_INVALID_NAME // for "\\SERVER\shared" paths that are translated to "\\?\UNC\SERVER\shared"
        )
    {
      unsigned len = MyStringLen(path);
      if (len > 2 && path[0] == '\\' && path[1] == '\\')
      {
        int startPos = 2;
        if (len > kSuperUncPathPrefixSize && IsSuperUncPath(path))
          startPos = kSuperUncPathPrefixSize;
        int pos = FindCharPosInString(path + startPos, FTEXT('\\'));
        if (pos >= 0)
        {
          pos += startPos + 1;
          len -= pos;
          int pos2 = FindCharPosInString(path + pos, FTEXT('\\'));
          if (pos2 < 0 || pos2 == (int)len - 1)
          {
            FString s = path;
            if (pos2 < 0)
            {
              pos2 = len;
              s += FTEXT('\\');
            }
            s += FCHAR_ANY_MASK;
            if (finder.FindFirst(s, *this))
              if (Name == FTEXT("."))
              {
                Name.SetFrom(s.Ptr(pos), pos2);
                return true;
              }
            ::SetLastError(lastError);
          }
        }
      }
    }
  }
  #endif
  return false;
}

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

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

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)
{
  if (Next(fi))
  {
    found = true;
    return true;
  }
  found = false;
  return (::GetLastError() == ERROR_NO_MORE_FILES);
}

////////////////////////////////
// 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 WIN_LONG_PATH
    if (!IsHandleAllocated())
    {
      UString longPath;
      if (GetSuperPath(path, longPath, USE_MAIN_PATH))
        _handle = ::FindFirstChangeNotificationW(longPath, 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;
    AString buf;
    UINT32 newSize = GetLogicalDriveStrings(size, buf.GetBuffer(size));
    if (newSize == 0 || newSize > size)
      return false;
    AString s;
    for (UINT32 i = 0; i < newSize; i++)
    {
      char c = buf[i];
      if (c == '\0')
      {
        driveStrings.Add(fas2fs(s));
        s.Empty();
      }
      else
        s += c;
    }
    return s.IsEmpty();
  }
  else
  #endif
  {
    UINT32 size = GetLogicalDriveStringsW(0, NULL);
    if (size == 0)
      return false;
    UString buf;
    UINT32 newSize = GetLogicalDriveStringsW(size, buf.GetBuffer(size));
    if (newSize == 0 || newSize > size)
      return false;
    UString s;
    for (UINT32 i = 0; i < newSize; i++)
    {
      WCHAR c = buf[i];
      if (c == L'\0')
      {
        driveStrings.Add(us2fs(s));
        s.Empty();
      }
      else
        s += c;
    }
    return s.IsEmpty();
  }
}

#endif

}}}