c ++ - FindFirstFile/FindNextFile returnerer ikke alle filer i mappen

Indlæg af Hanne Mølgaard Plasc

Problem



Bemærk: Det foreslåede spørgsmål som et duplikat diskuterer CreateFile, ERROR\_FILE\_NOT\_FOUND, har eksisterende håndtag på en fil og markerer en fil, der skal slettes senere. Selvom det er et lignende emne, vedrører ingen af ​​disse problemer mit tilfælde.


Jeg har følgende metode til at slette en mappe. Jeg har brugt det i et stykke tid uden problemer.


Men for nylig har jeg virkelig sat det igennem sine skridt med en stor opgave at slette mapper og filer over en netværkssti (bare et USB-drev, der er forbundet med min router).


Alt ser ud til at fungere godt bortset fra et område, hvor det ikke sletter alle filerne i en mappe, og så RemoveDirectory() fejler. (Jeg har tilføjet hver fil til en liste og bekræftet, at listen ikke indeholder de filer, der ikke blev slettet.)


Alle filer er navngivet meget ens, og der er ikke noget usædvanligt om navnet på de filer, der ikke returneres eller slettes. Hvis jeg kører programmet igen, vil det slette de resterende filer og derefter have den samme fejl lidt senere på en anden mappe.


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


Et par noter på koden: ConvertToExtendedLengthPath() tilføjer et præfiks, så stier længere end MAX\_PATH vil blive tilladt; Men selvom disse navne er ret lange, overstiger ingen af ​​dem MAX\_PATH. (I dette tilfælde returnerer metoden bare indtastningsværdien.)


Jeg fjerner også den skrivebeskyttede attribut på filer og mapper, der har denne egenskab. Men igen, jeg har bekræftet, at dette ikke kommer til spil på nogen af ​​de filer, jeg arbejder med.


Endelig er dette ikke tilfældet, hvor de filer, jeg arbejder med, ændrer sig. Jeg er den eneste med adgang til dette eksterne drev.


Har nogen set noget tilfælde hvor FindFirstFile/FindNextFile vil savne nogle få filer? Eller hvor adgang til filer via en netværksandel kan forstyrre adfærden af ​​disse funktioner?

Bedste reference


Selvom både Windows-filsystemet og Windows-implementeringen af ​​SMB sikrer, at filer ikke bliver udeladt af en telefonregistrering, selvom indholdet af mappen ændrer sig, ser det ikke ud til at være et krav i SMB-protokollen selv. Jeg er næsten ikke en ekspert, så jeg har muligvis overset noget.) Uanset om din servers adfærd er teknisk korrekt eller ej, vil du formodentlig være nødt til at håndtere det som det er.]] [16]


Jeg vil gætte, at shell API'en allerede beskæftiger sig med denne situation, men i dit tilfælde vil jeg anbefale imod at bruge det, fordi jeg ikke tror det understøtter lange stinavn.


Hvis du ved, at der aldrig vil være et for stort antal poster i en given mappe, og at ingen anden proces vil slette filer i mappen samtidig med at du opregner den, kan du helst læse listen over filer først og derefter begynde at slette dem. Jeg indsamler dig 'har allerede bygget et proof of concept i denne retning.


En lidt mindre tidseffektiv, men mere hukommelseseffektiv, og jeg tror, ​​at en mere robust tilgang ville være at opregne og slette filerne samtidig (som allerede vist i den opdaterede kode), men derefter løkke rundt og genkaldere, indtil du finder det biblioteket er tomt fra . og .. indgange. Den eneste indlysende ulempe ved dette er den ekstra rundrejse til serveren. YMMV. :-)

Andre referencer 1


Jeg har ikke personligt observeret sådan ulige adfærd, mens du bruger FindFirstFile/FindNextFile combo.


Men hvis du vil slette en mappe og dens indhold, er der en løsning:


// 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;
}