void AddDirFileInfo(
    const UString &prefix,        // prefix for logical path
    const UString &fullPathName,  // path on disk: can be relative to some basePrefix
    const NFind::CFileInfoW &fileInfo, 
    CObjectVector<CDirItem> &dirItems)
{
  CDirItem item;
  item.Attributes = fileInfo.getAttributes();
  item.Size = fileInfo.getSize();
  item.CreationTime = fileInfo.getCreationTime();
  item.LastAccessTime = fileInfo.getLastAccessTime();
  item.LastWriteTime = fileInfo.getLastWriteTime();
  item.Name = prefix + fileInfo.getFileName();
  item.FullPath = fullPathName;
  dirItems.Add(item);
}
static HRESULT EnumerateDirItems(
    const NWildcard::CCensorNode &curNode, 
    const UString &diskPrefix,        // full disk path prefix 
    const UString &archivePrefix,     // prefix from root
    const UStringVector &addArchivePrefix,  // prefix from curNode
    CObjectVector<CDirItem> &dirItems, 
    bool enterToSubFolders,
    IEnumDirItemCallback *callback,
    UStringVector &errorPaths,
    CRecordVector<DWORD> &errorCodes)
{
  if (!enterToSubFolders)
    if (curNode.NeedCheckSubDirs())
      enterToSubFolders = true;
  if (callback)
    RINOK(callback->CheckBreak());

  // try direct_names case at first
  if (addArchivePrefix.IsEmpty() && !enterToSubFolders)
  {
    // check that all names are direct
    int i;
    for (i = 0; i < curNode.IncludeItems.Size(); i++)
    {
      const NWildcard::CItem &item = curNode.IncludeItems[i];
      if (item.Recursive || item.PathParts.Size() != 1)
        break;
      const UString &name = item.PathParts.Front();
      if (name.IsEmpty() || DoesNameContainWildCard(name))
        break;
    }
    if (i == curNode.IncludeItems.Size())
    {
      // all names are direct (no wildcards)
      // so we don't need file_system's dir enumerator
      CRecordVector<bool> needEnterVector;
      for (i = 0; i < curNode.IncludeItems.Size(); i++)
      {
        const NWildcard::CItem &item = curNode.IncludeItems[i];
        const UString &name = item.PathParts.Front();
        const UString fullPath = diskPrefix + name;
        NFind::CFileInfoW fileInfo;
        if (!NFind::FindFile(fullPath, fileInfo))
        {
          errorCodes.Add(::GetLastError());
          errorPaths.Add(fullPath);
          continue;
        }
        bool isDir = fileInfo.IsDirectory();
        if (isDir && !item.ForDir || !isDir && !item.ForFile)
        {
          errorCodes.Add((DWORD)E_FAIL);
          errorPaths.Add(fullPath);
          continue;
        }
        const UString realName = fileInfo.getFileName();
        const UString realDiskPath = diskPrefix + realName;
        {
          UStringVector pathParts;
          pathParts.Add(fileInfo.getFileName());
          if (curNode.CheckPathToRoot(false, pathParts, !isDir))
            continue;
        }
        AddDirFileInfo(archivePrefix, realDiskPath, fileInfo, dirItems);
        if (!isDir)
          continue;
        
        UStringVector addArchivePrefixNew;
        const NWildcard::CCensorNode *nextNode = 0;
        int index = curNode.FindSubNode(name);
        if (index >= 0)
        {
          for (int t = needEnterVector.Size(); t <= index; t++)
            needEnterVector.Add(true);
          needEnterVector[index] = false;
          nextNode = &curNode.SubNodes[index];
        }
        else
        {
          nextNode = &curNode;
          addArchivePrefixNew.Add(name); // don't change it to realName. It's for shortnames support
        }
        RINOK(EnumerateDirItems(*nextNode,   
            realDiskPath + wchar_t(kDirDelimiter), 
            archivePrefix + realName + wchar_t(kDirDelimiter), 
            addArchivePrefixNew, dirItems, true, callback, errorPaths, errorCodes));
      }
      for (i = 0; i < curNode.SubNodes.Size(); i++)
      {
        if (i < needEnterVector.Size())
          if (!needEnterVector[i])
            continue;
        const NWildcard::CCensorNode &nextNode = curNode.SubNodes[i];
        const UString fullPath = diskPrefix + nextNode.Name;
        NFind::CFileInfoW fileInfo;
        if (!NFind::FindFile(fullPath, fileInfo))
        {
          if (!nextNode.AreThereIncludeItems())
            continue;
          errorCodes.Add(::GetLastError());
          errorPaths.Add(fullPath);
          continue;
        }
        if (!fileInfo.IsDirectory())
        {
          errorCodes.Add((DWORD)E_FAIL);
          errorPaths.Add(fullPath);
          continue;
        }
        RINOK(EnumerateDirItems(nextNode, 
            diskPrefix + fileInfo.getFileName() + wchar_t(kDirDelimiter), 
            archivePrefix + fileInfo.getFileName() + wchar_t(kDirDelimiter), 
            UStringVector(), dirItems, false, callback, errorPaths, errorCodes));
      }
      return S_OK;
    }
  }


  NFind::CEnumeratorW enumerator(diskPrefix + wchar_t(kAnyStringWildcard));
  for (;;)
  {
    NFind::CFileInfoW fileInfo;
    bool found;
    if (!enumerator.Next(fileInfo, found))
    {
      errorCodes.Add(::GetLastError());
      errorPaths.Add(diskPrefix);
      break;
    }
    if (!found)
      break;

    if (callback)
      RINOK(callback->CheckBreak());
    const UString &name = fileInfo.getFileName();
    bool enterToSubFolders2 = enterToSubFolders;
    UStringVector addArchivePrefixNew = addArchivePrefix;
    addArchivePrefixNew.Add(name);
    {
      UStringVector addArchivePrefixNewTemp(addArchivePrefixNew);
      if (curNode.CheckPathToRoot(false, addArchivePrefixNewTemp, !fileInfo.IsDirectory()))
        continue;
    }
    if (curNode.CheckPathToRoot(true, addArchivePrefixNew, !fileInfo.IsDirectory()))
    {
      AddDirFileInfo(archivePrefix, diskPrefix + name, fileInfo, dirItems);
      if (fileInfo.IsDirectory())
        enterToSubFolders2 = true;
    }
    if (!fileInfo.IsDirectory())
      continue;

    const NWildcard::CCensorNode *nextNode = 0;
    if (addArchivePrefix.IsEmpty())
    {
      int index = curNode.FindSubNode(name);
      if (index >= 0)
        nextNode = &curNode.SubNodes[index];
    }
    if (!enterToSubFolders2 && nextNode == 0)
      continue;

    addArchivePrefixNew = addArchivePrefix;
    if (nextNode == 0)
    {
      nextNode = &curNode;
      addArchivePrefixNew.Add(name);
    }
    RINOK(EnumerateDirItems(*nextNode,   
        diskPrefix + name + wchar_t(kDirDelimiter), 
        archivePrefix + name + wchar_t(kDirDelimiter), 
        addArchivePrefixNew, dirItems, enterToSubFolders2, callback, errorPaths, errorCodes));
  }
  return S_OK;
}