2017-02-23 28 views
3

Примечание: Вопрос, предложенный в качестве дубликата, обсуждает CreateFile, ERROR_FILE_NOT_FOUND, имея существующие ручки в файле и помещая файл для последующего удаления. Хотя это аналогичная тема, ни одна из этих проблем не относится к моему делу.FindFirstFile/FindNextFile не возвращает все файлы в папке

У меня есть следующий способ удаления каталога. Я использовал его некоторое время без проблем.

Но в последнее время я действительно выполнял свои задачи с большой задачей, удаляя каталоги и файлы по сетевому пути (только USB-накопитель, подключенный к моему маршрутизатору).

Все работает отлично, за исключением одной области, где не удаляются все файлы в каталоге, и поэтому RemoveDirectory() не работает. (Я добавил каждый файл в список и проверил, что в список не были файлы, которые не были удалены.)

Все файлы называются очень похожими, и нет ничего необычного в отношении имени файлов, которые не были возвращены или удален. Если я снова запустил программу, она удалит оставшиеся файлы и затем будет иметь ту же ошибку немного позже в другом каталоге.

bool CBackupWorker::DeleteDirectory(LPCTSTR lpszName) 
{ 
    if (!DirectoryExists(lpszName)) 
    { 
     ASSERT(false); // Unexpected 
     return true; 
    } 

    CFileFind find; 
    BOOL bContinue = find.FindFile(AppendPath(lpszName, _T("*"))); 
    while (bContinue) 
    { 
     bContinue = find.FindNextFile(); 
     if (find.IsDirectory()) 
     { 
      if (!find.IsDots()) 
      { 
       if (!DeleteDirectory(find.GetFilePath())) 
        return false; 
      } 
     } 
     else 
     { 
      if (find.IsReadOnly()) 
       ClearReadOnlyAttribute(find); 
      if (!::DeleteFile(ConvertToExtendedLengthPath(find.GetFilePath()))) 
      { 
       LogErrorV(::GetLastError(), _T("ERROR DELETING FILE : '%s'"), (LPCTSTR)find.GetFilePath()); 
       return false; 
      } 
     } 
    } 

    CString sPath = ConvertToExtendedLengthPath(lpszName); 
    DWORD dwAttributes = ::GetFileAttributes(sPath); 
    if (dwAttributes != INVALID_FILE_ATTRIBUTES && (dwAttributes & FILE_ATTRIBUTE_READONLY)) 
     ::SetFileAttributes(sPath, dwAttributes & (DWORD)~FILE_ATTRIBUTE_READONLY); 
    if (!::RemoveDirectory(sPath)) 
    { 
     LogErrorV(::GetLastError(), _T("ERROR DELETING DIRECTORY : '%s'"), lpszName); 
     return false; 
    } 
    return true; 
} 

Несколько замечаний по коду: ConvertToExtendedLengthPath() добавляет префикс так, что пути длиннее, чем MAX_PATH будет разрешено; однако, хотя эти имена довольно длинные, ни один из них не превышает MAX_PATH. (В этом случае метод просто возвращает значение ввода.)

Кроме того, я удаляю атрибут «только для чтения» для файлов и каталогов, имеющих этот атрибут. Но опять же, я подтвердил, что это не вступает в игру ни с одним из файлов, с которыми я сейчас работаю.

И, наконец, это не тот случай, когда файлы, с которыми я работаю, меняются. Я единственный, у кого есть доступ к этому внешнему диску.

Кто-нибудь видел случай, когда FindFirstFile/FindNextFile пропустит несколько файлов? Или когда доступ к файлам через сетевой ресурс может помешать работе этих функций?

+0

В MSDN есть небольшое примечание, связанное с атрибутами файла, возвращаемыми FindFirstFile/FindNextFile [здесь] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa364418). Проверьте, соответствует ли это вам: Примечание. В редких случаях или в сильно загруженной системе информация атрибута файла в файловых системах NTFS может не быть актуальной во время вызова этой функции. Чтобы быть уверенным в получении текущих файловых атрибутов файловой системы NTFS, вызовите функцию GetFileInformationByHandle. – cha

+1

Возможно, вам стоит использовать операцию 'SHFilOperation()', такую ​​как ['RemoveDirectory()' documentation] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365488.aspx). : "* Чтобы рекурсивно удалить файлы в каталоге, используйте функцию' SHFileOperation'. * –

+1

Я подозреваю, что проблема заключается в том, что, удаляя файлы по мере поступления, вы меняете содержимое каталога во время сканирования. , вы могли бы сначала сохранить все имена файлов и каталогов и только затем удалить их? –

ответ

2

Хотя и файловой системы Windows, и реализация Окна SMB обеспечивает files aren't left out of a directory enumeration, even if the contents of the directory are changing, это не представляется быть требование SMB protocol itself. (Я вряд ли эксперт, поэтому, возможно, я что-то упустил.) Во всяком случае, независимо от того, является ли поведение вашего сервера технически правильным или нет, вам, вероятно, придется иметь дело с ним как есть.

Я бы предположил, что API-интерфейс оболочки уже имеет дело с этой ситуацией, но в вашем случае я бы рекомендовал не использовать его, потому что я не верю, что он поддерживает длинные имена путей.

Если вы знаете, что в любом заданном каталоге никогда не будет чрезмерно большого количества записей, и что ни один другой процесс не будет удалять файлы в каталоге в то же самое время, когда вы перечисляете его, вы можете предпочесть читать список файлов, а затем начать их удаление.Полагаю, вы уже построили доказательство концепции в этом направлении.

Немного менее экономичный, но более эффективный с точки зрения памяти, и я думаю, что более надежный подход состоял бы в том, чтобы перечислять и удалять файлы в одно и то же время (как уже показано в опубликованном коде), но затем перебирать и повторно перечислять пока вы не обнаружите, что каталог пуст, кроме записей . и ... Единственным очевидным недостатком этого является дополнительное путешествие туда-обратно к серверу. YMMV. :-)

2

Я лично не наблюдал такое нечетное поведение при использовании FindFirstFile/FindNextFile комбо.

Однако, если вы хотите удалить каталог и его содержимое, вот решение:

// VadaPoché_SO.cpp : Defines the entry point for the console application. 
#include "stdafx.h" 
#include <iostream> 
#include <strsafe.h> 
#include <Shobjidl.h> 

HRESULT CreateAndInitializeFileOperation(REFIID riid, void **ppv) //this function is copied verbatim from https://github.com/Microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/shell/appplatform/fileoperations/FileOperationSample.cpp 
{ 
    *ppv = NULL; 
    // Create the IFileOperation object 
    IFileOperation *pfo; 
    HRESULT hr = CoCreateInstance(__uuidof(FileOperation), NULL, CLSCTX_ALL, IID_PPV_ARGS(&pfo)); 
    if (SUCCEEDED(hr)) 
    { 
     // Set the operation flags. Turn off all UI 
     // from being shown to the user during the 
     // operation. This includes error, confirmation 
     // and progress dialogs. 
     hr = pfo->SetOperationFlags(FOF_NO_UI); 
     if (SUCCEEDED(hr)) 
     { 
      hr = pfo->QueryInterface(riid, ppv); 
     } 
     pfo->Release(); 
    } 
    return hr; 
} 

int main() 
{ 
    using namespace std; 

    const wchar_t *dirFullPath = L"C:\\test1\\test"; //this directory, and its contents, if any, will be deleted. 

    IShellItem* itemDirToDelete = NULL; 
    IFileOperation *fileOp = NULL; 
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); 

    if (FAILED(hr)) 
    { 
     cout << "CoInitializeEx failed. Error code returned: 0x" << hex << hr << endl; 
     return -1; 
    } 

    if (FAILED(SHCreateItemFromParsingName(dirFullPath, NULL, IID_PPV_ARGS(&itemDirToDelete)))) 
    { 
     cout << "SHCreateItemFromParsingName failed. Error code returned: 0x" << hex << hr << endl; 
     return -1; 
    } 

    if (SUCCEEDED(CreateAndInitializeFileOperation(IID_PPV_ARGS(&fileOp)))) 
    { 
     //Note: contrary to its name DeleteItem, this does NOT do the actual deletion. 
     //It only declares an intention to perform deletion. 
     if (SUCCEEDED(fileOp->DeleteItem(itemDirToDelete, NULL))) 
     { 
      hr = fileOp->PerformOperations(); //This is the statement that acts on the intention declared above. i.e. it deletes the folder. 
     } 
     fileOp->Release(); 
    } 

    return 0; 
} 
Смежные вопросы