Page 1 of 1

Directory recursivity - Delete/copy/move a whole tree

Posted: Mon Aug 23, 2010 1:32 pm
by mathieumg
Hi,

I'm looking to delete a whole directory (including all it's subdirectories and files), though I might need to do copy/move operations in the same manner.

I'm using 2.8, and I was told that wxRmdir isn't recursive under this version, in other words the directory to remove needs to be empty.

So I'm looking for a way to create a recursive loop to do all that. For example
recursive function (folder)
{
for every item in folder:
if its a file delete/move/copy it
if its a directory, call the function itself on it
}
The easiest would be the delete one I presume, but for the move/copy one, you need to make sure each directory exists in the two places before copying files (as to keep the same directory structure after the whole operation)

I would like to have some clues to help myself to proceed please! I have tried to inspire myself from 2.9's wxRmdir function ( http://svn.wxwidgets.org/svn/wx/wxWidge ... lename.cpp ), without much success.

Also, I'm leaving from here in a week (after which this project will be put on hold) so compiling/upgrading to 2.9 isn't really an option since I have no environment set up at all (I used wxPack).

Thanks a lot in advance!

Posted: Mon Aug 23, 2010 3:17 pm
by DavidHart
Hi,
So I'm looking for a way to create a recursive loop to do all that
Yes, this is what I do:

Code: Select all

bool MyGenericDirCtrl::ReallyDelete( wxFileName* PathName )
{
// Snip lots of linux-specific stuff

// You need to add a line here to check if PathName is a file. If so:
  if ( ! wxRemoveFile( PathName->GetFullPath() ) )        // It's alive, so kill it
          { wxMessageBox(_("Deletion Failed!?!")); return false; }
      }
    return true;
  }

   // If we're here, it's a dir
wxBusyCursor busy;

wxDir dir( PathName->GetPath() );                               // Deal sequentially with each child file & subdir
if ( !dir.IsOpened() )        return false;

wxString filename;
while ( dir.GetFirst( &filename ) )                             // Go thru the dir, committing infanticide 1 child @ a time
  {
    wxFileName child( PathName->GetPath(), filename );          // Make a new wxFileName for child
    if ( ! ReallyDelete( &child ) )                             // Slaughter it by recursion, whether it's a dir or file
        return false;                                           // If this fails, bug out
  }
  
if ( ! PathName->Rmdir() )                                      // Finally, kill the dir itself
  { wxMessageBox(_("Directory deletion Failed!?!")); return false; }

return true; 
}
but for the move/copy one, you need to make sure each directory exists in the two places before copying files (as to keep the same directory structure after the whole operation)
That's easy: for each directory, just use wxFileName::Mkdir with the wxPATH_MKDIR_FULL flag. This doesn't do any harm if the directory already exists.

It would be wise to check each file for pre-existence, though.

Regards,

David

Posted: Tue Aug 24, 2010 3:29 pm
by mathieumg
Thanks for the help David!

Here is what I tried to come up with for the copy version:

Code: Select all

bool copyTree( wxFileName* source, wxFileName* destination )
{
    // Copy file if it isn't a directory.
    if ( ! source->IsDir() )
    {
        if ( ! wxCopyFile(source->GetFullPath(), destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + source->GetFullName()) + wxT("\\\") )
        {
            // Tried to output some stuff to see where problem could lie.
            cout << "copy failed" << endl;
            cout << source->GetFullPath().mb_str(wxConvUTF8) << " to " << destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR).mb_str(wxConvUTF8) << source->GetFullName().mb_str(wxConvUTF8) << "\\\" << endl;
            return false;
        }

        return true;
    }
    else
    {
        wxDir newdir( source->GetPath() );

        wxFileName::Mkdir(destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + newdir.GetName(), 0777, wxPATH_MKDIR_FULL);
    }

    wxDir dir( source->GetPath() );                               // Deal sequentially with each child file & subdir
    if ( !dir.IsOpened() )        return false;

    wxString filename;
    while ( dir.GetFirst( &filename ) )                             // Go thru the dir, cloning 1 child @ a time
      {
        wxFileName child( source->GetPath(), filename );          // Make a new wxFileName for child
        wxFileName newdestination( destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR));
        if ( ! copyTree( &child, &newdestination ) )                             // Clone it by recursion, whether it's a dir or file
            return false;                                           // If this fails, bug out
      }

    return true;
}
And I call it this way:

Code: Select all

    wxFileName test1(wxT("C:\\test\\\"));
    wxFileName test2(wxT("C:\\testdump\\\"));

    if ( copyTree( &test1, &test2 ) )
    {
        cout << "HUGE SUCCESS!!!" << endl;
    }
    else
    {
        cout << "HUGE FAIL!!!" << endl;
    }
For now it's a huge fail :( I didn't put the trailing slashes at first but things were even worse!

I feel like I'm not too far from a working solution, but I would need some more help with it :)

Thanks a lot in advance!

P.S. I know that the function copies source in destination (as opposed to content of source in destination), this is what I want.

Posted: Tue Aug 24, 2010 3:42 pm
by Auria
Star by checking with a debugger or with logging at which point in the function it returns false

Posted: Tue Aug 24, 2010 6:56 pm
by mathieumg
The logging says it couldn't copy "C:\test\1" to "C:\testdump\1" with an access denied error. It sounds like it thinks that 1 is a file (while it actually is a directory). Could it be that source->IsDir() doesn't work well? David said on IRC that it could be related to using forward slashes instead of backslashes, but that doesn't seem to have changed much.

Here is my test tree:

C:\test\
C:\test\1\
C:\test\1\2\
C:\test\1\2\somefile.txt
C:\test\1\2\3\
C:\test\1\2\3\someotherfile.txt
C:\test\A\
C:\test\B\
C:\test\B\othertest.txt
C:\test\test.txt
C:\test\testing.jpg


Thanks for the help!

Posted: Tue Aug 24, 2010 7:58 pm
by DavidHart
It looks as if, despite the terminal wxFILE_SEP_PATH, that wxFileName still gets confused.
Why not try the methods suggested at the top of http://docs.wxwidgets.org/stable/wx_wxfilename.html

Posted: Wed Aug 25, 2010 11:47 am
by mathieumg
David/Auria, as we discussed on IRC yesterday, I still have problems making this work :(

If I put a breakpoint at the beginning of the function and follow it line-by-line, it will always go in the else instruction of the DirExists condition. When I do that, I also see that copyTree gets called infinitely in my callstack.

Here is the latest version of my code:

http://pastebin.com/rMmdbLJa

Posted: Wed Aug 25, 2010 2:30 pm
by mathieumg
I finally got things to work! The code is far from optimal/proper, but it does the job for now based on my needs!

Here is the code if anyone ends up on this page after a search and needs a starting point!

Code: Select all

bool copyTree( wxFileName* source, wxFileName* destination )
{
    // Copy file if it isn't a directory.
    if ( ! wxDir::Exists(source->GetFullPath()) )
    {
        if ( ! wxCopyFile(source->GetFullPath(), destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + source->GetFullName() ))
            return false;

        return true;
    }
    else
    {
        if( ! wxFileName::Mkdir(destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + source->GetDirs()[source->GetDirCount() - 1] + wxT("\\\"), 0777, wxPATH_MKDIR_FULL) )
        {
            return false;
        }
    }

    // Deal sequentially with each child file & subdir.
    wxDir dir( source->GetPath() );
    if ( !dir.IsOpened() )        return false;

    // Go thru the dir, cloning 1 child @ a time.
    wxString filename;
    bool cont = dir.GetFirst( &filename );
    while ( cont )
    {
        wxString childPath = source->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + filename;
        wxString newDestinationPath = destination->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + source->GetDirs()[source->GetDirCount() - 1] + wxT("\\\");
        wxFileName child;

        if ( wxDir::Exists(childPath) )
        {
            child.Assign( childPath + wxT("\\\") );
        }
        else
        {
            child.Assign( source->GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR), filename );
        }

        // Make a new wxFileName for child
        wxFileName newDestination( newDestinationPath );

        // Clone it by recursion, whether it's a dir or file.
        if ( ! copyTree( &child, &newDestination ) )
            // If this fails, bug out.
            return false;

        cont = dir.GetNext(&filename);
    }

    return true;
}
Thank you to everyone who contributed!