?
Solved

Compress Directory Tree or Multiple Files/Folders

Posted on 2005-03-08
26
Medium Priority
?
561 Views
Last Modified: 2012-06-21
I am working on a class to compress multiple files or directories.  With the help of some clever programmers from here I have developed a small class that will copy a file from one location to another and compress that file in it's new location.  I now am in need of having that same functionalit but with multiple files or directory trees.  These are the functions i am using in my class:

BOOL CZip::Compress(CString strDest)
{
      HANDLE hFile;
      USHORT Format = COMPRESSION_FORMAT_DEFAULT;
      DWORD dummy;

      //get handle to destination file
      hFile = CreateFile(strDest,
            GENERIC_ALL,
            FILE_SHARE_READ,
            NULL,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);

      //set compression attribute of destination file
      BOOL bRC = DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, &Format, sizeof(USHORT),
            NULL, 0, &dummy, NULL);

      CloseHandle(hFile);

      return bRC;
}

BOOL CZip::Copy(CString strSource)
{
      CString strDest;
      CString strCompressFolder = "c:\\FolderToCompress\\";
      
      //trim source name down to filename.ext
      int bsPos;
      if ((bsPos=strSource.ReverseFind('\\')) == -1)
            bsPos=0;
      else
            bsPos++;
      strDest=strSource.Right(strSource.GetLength()-bsPos);

      //add the directory for compression to the begining of strDest
      strDest = strCompressFolder + strDest;

      //copy specific file to new directory for compression
      if(CopyFile(strSource,strDest,TRUE))
      {
            //compress destination file
            if(Compress(strDest))
                  return TRUE;
            else
                  return FALSE;
      }
      else
            return FALSE;
}

Thanks for any info
0
Comment
Question by:ptrennum
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 11
  • 8
  • 6
  • +1
26 Comments
 
LVL 30

Expert Comment

by:Axter
ID: 13486844
Hi ptrennum,
Exactly what is your question?

David Maisonave :-)
Cheers!
0
 

Author Comment

by:ptrennum
ID: 13486921
Well, I am wondering how to modify these functions so that they will traverse a directory tree.  Example:

If I pass a path like:  c:\data, which has several files as well as several other directories containing files.

How can I modify these functions to copy that path and then compress the files in them.

Hope that makes a little more sense.

0
 
LVL 86

Expert Comment

by:jkr
ID: 13487028
Well, I gave you the code in your prevoius Q. The idea is to go through all the files in a base directory and recurse into the subdirs:

void CompressTree ( char* pszPath,  char*   pszBase)
{
WIN32_FIND_DATA w32fd;
HANDLE hFind;
DWORD dwAtt;
char acPath [ MAX_PATH];
char acBase [ MAX_PATH];

printf  (   "CheckFiles():\tcalled with '%s' '%s'\n",   pszPath,    pszBase);

if ( '.' == * (pszPath + lstrlen ( pszPath) - 1))
                return;

if  (   pszBase)
   sprintf (   acPath, "%s\\%s",   pszBase,    pszPath);
else
   lstrcpy ( acPath, pszPath);

printf ( "path is %s\n",    acPath);
lstrcpy ( acBase, acPath);

dwAtt = GetFileAttributes ( acPath);

if ( 0xffffffff == dwAtt)
{
 // error ...
}

if ( FILE_ATTRIBUTE_DIRECTORY & dwAtt)
{
    if  (   '\\'    ==  acPath  [   lstrlen (   acPath) -   1])
            lstrcat (   acPath, "*.*");
     else
            lstrcat (   acPath, "\\*.*");

    printf ( "path is now %s\n",    acPath);
}


hFind = FindFirstFile ( acPath, &w32fd);

if ( INVALID_HANDLE_VALUE == hFind)
{
 // error

printf ( "ERROR %d\n",  GetLastError    ());

return;
}

// recurse if directory...
if ( FILE_ATTRIBUTE_DIRECTORY == w32fd.dwFileAttributes)
{

    CompressTree ( w32fd.cFileName,    acBase);
}
else
 HandleFile ( &w32fd);

while ( FindNextFile ( hFind, &w32fd))
{
    // recurse if directory...
    if ( FILE_ATTRIBUTE_DIRECTORY == w32fd.dwFileAttributes)
    {

     CompressTree ( w32fd.cFileName,    acBase);
   }
    else
      HandleFile ( &w32fd);
}

if ( ERROR_NO_MORE_FILES != GetLastError())
{
 // error
}
FindClose ( hFind);
}

void HandleFile ( WIN32_FIND_DATA* pw32fd)
{
HANDLE hFile;
USHORT Format = COMPRESSION_FORMAT_DEFAULT;
DWORD dummy;

hFile = CreateFile(pszDst,
    GENERIC_ALL,
    SHARE_ALL,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL);

DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, &Format, sizeof(USHORT),
NULL, 0, &dummy, NULL);

CloseHandle(hFile);
}

To copy the whole tree before doing so, use e.g.

SHFILEOPSTRUCT sfo;
ZeroMemory ( &sfo, sizeof (sfo));

sfo.wFunc = FO_COPY;
sfo.pFrom = "C:\\OriginalDir";
sfo.pTo =  "c:\\FolderToCompress";
sfo.fFlags = FOF_SILENT | FO_NOCONFIRMATION | FOF_NOERRORUI;

SHFileOperation ( &sfo);
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 13487053
#include <string>
#include <fstream>
#include <stdio.h>
using namespace std;

BOOL CZip::CopyDir(CString strDir)
{
     CString strCmd = "dir /s /b " + strDir + " >xxx.txt";
     system(strCmd);
     ifstream ifs("xxx.txt");
     string file;
     while (getline(ifs, file))
          Copy(CString(file.c_str());
     ifs.close();  
     remove("xxx.txt");
}

You should pass the full absolute or relative path in strDir.

Regards, Alex
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 13487075
It needs one more ')'

   Copy(CString(file.c_str()));
0
 

Author Comment

by:ptrennum
ID: 13487079
What exactly is the xxx.txt supposed to represent?
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 13487141
>>> What exactly is the xxx.txt supposed to represent?

xxx.txt is a temporary text file that contains the output of  

     dir /s /b <your_directory>

same as given that command in a command line window.

If you comment the remove("xxx.txt") or set a breakpoint before opening the text file, you may check the contents of the file in the editor.

Regards, Alex

BTW, you could add a filter like *.dat to the command.


0
 
LVL 86

Expert Comment

by:jkr
ID: 13487196
Um, isn't a temp. file a little cumbersome when you can do that programmatically using 'SHFileOperation()' and/or API functions (the above can be found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/base/listing_the_files_in_a_directory.asp - "Listing the Files in a Directory")?
0
 

Author Comment

by:ptrennum
ID: 13487258
For some reason I get a fatal error including <fstream.h>  m compiler (VS.NET 2003)  : No File or directory found
0
 
LVL 86

Expert Comment

by:jkr
ID: 13487303
>> For some reason I get a fatal error including <fstream.h>

The reason is a *good* one - there are no STL headers ending with .h. All files that do so are not part of the standard, nd therefor should be strictly avoided.
0
 

Author Comment

by:ptrennum
ID: 13487400
Thanks JKR,

>> while (getline(ifs, file))

get line takes several more arguments by the look of it
0
 

Author Comment

by:ptrennum
ID: 13487416
I need to know what delimiter to use?
\n?
0
 
LVL 86

Expert Comment

by:jkr
ID: 13487419
BTW, as I wrote above: Isn't a temp. file a *little* cumbersome when you can do that programmatically? I would not recommend that either.
0
 
LVL 86

Expert Comment

by:jkr
ID: 13487453
Yes, '\n' is the default delimiter. There's no need to specify that explicitly. And I have to admit that such a file approach has advantages over having the user do a 'dir' manually and type the results *SCNR* :o)
0
 

Author Comment

by:ptrennum
ID: 13487648
The CopDir function works however it does not maintian the director structure when it copies over,  I plan on studing JKR's response more carefully when I have more time, I would just like to have a quick implementation of this functionality.
0
 
LVL 86

Expert Comment

by:jkr
ID: 13487693
>>I would just like to have a quick implementation of this functionality.

Isn't

SHFILEOPSTRUCT sfo;
ZeroMemory ( &sfo, sizeof (sfo));

sfo.wFunc = FO_COPY;
sfo.pFrom = "C:\\OriginalDir";
sfo.pTo =  "c:\\FolderToCompress";
sfo.fFlags = FOF_SILENT | FO_NOCONFIRMATION | FOF_NOERRORUI;

SHFileOperation ( &sfo);

quick enough?
0
 

Author Comment

by:ptrennum
ID: 13487783
yep it is, however it doesn't do anything.  My directory c:\FolderToCompress is empty??
0
 

Author Comment

by:ptrennum
ID: 13487797
for pFrom I passed c:\lexmark which is a directory tree that exists
0
 
LVL 86

Expert Comment

by:jkr
ID: 13488222
Sorry, my fault - that should be

SHFILEOPSTRUCT sfo;
ZeroMemory ( &sfo, sizeof (sfo));

sfo.wFunc = FO_COPY;
sfo.pFrom = "C:\\OriginalDir\\*.*";
sfo.pTo =  "c:\\FolderToCompress";
sfo.fFlags = FOF_SILENT | FO_NOCONFIRMATION | FOF_NOERRORUI;

SHFileOperation ( &sfo);
0
 

Author Comment

by:ptrennum
ID: 13488896
That does not copy over the contents of pFrom
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 13489132
>>I would just like to have a quick implementation of this functionality.

int XXX::CopyDir(const CString& strFrom, const CString& strTo)
{
   CString  strCommand = "xcopy /s /e " + strFrom + " " + strTo + "\\";
   return system(strCommand);
}

strFrom should be the full path of the source directory, e. g.
 
   strFrom = "C:\\Program Files\\MyApp"

or with file filter

   strFrom = "C:\\Program Files\\MyApp\\*.doc"


strTo must be a target directory on the same level as the source directory. If it doesn't exist it got created.

   CTime t = CTime::GetCurrentTime();  
   strTo = "C:\\backup\\ + t.Format("%Y%m%d");

Regards, Alex


   

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 13489280
You could make a command tool of your compress function and put the executable to a directory that is in the system path.

int main(int nArgs, char* pszArgs[])
{
     if (nArgs != 3)
     {
           cout << "Wrong number of arguments" << endl;
           return 1;
     }

     CZip zip(pszArgs[1]);   // first argument is the filename to zip
     BOOL ok = zip.Copy(pszArgs[2]);

     return ok? 0 : 2;
}

With that you should be able to call your executable, say Myzip, from the commandline like that

    myzip c:\temp\xxx.dat c:\backup

You also could compress directory trees like that

    for /f  "delims=" %i in ('dir /s /b c:\temp') do myzip "%i" c:\backup

You also could use the same command programmatically by using the system command:

    CString strCommand = "for /f  \"delims=\" %i in ('dir /s /b c:\\temp') do myzip \"%i\" c:\\backup";
    system(strCommand);

Note, the *escape* \ characters before  " and \.

Regards, Alex
0
 

Author Comment

by:ptrennum
ID: 13489577
In JKL's :
void HandleFile ( WIN32_FIND_DATA* pw32fd)
the param there is not used in the function - is this the wrong param or is the function incorrect in some way?
0
 

Author Comment

by:ptrennum
ID: 13489799
Also this line in JKL's function:
if  (   pszBase)
   sprintf (   acPath, "%s\\%s",   pszBase,    pszPath);
else
   lstrcpy ( acPath, pszPath);

Not sure what the first if statement is trying to check?
0
 
LVL 86

Assisted Solution

by:jkr
jkr earned 1000 total points
ID: 13491306
There's a problem in the above sampe of 'HandleFile()', the file name is not take from the struct:

void HandleFile ( WIN32_FIND_DATA* pw32fd)
{
HANDLE hFile;
USHORT Format = COMPRESSION_FORMAT_DEFAULT;
DWORD dummy;

hFile = CreateFile(pw32fs->cFileName,
   GENERIC_ALL,
   SHARE_ALL,
   NULL,
   OPEN_EXISTING,
   FILE_ATTRIBUTE_NORMAL,
   NULL);

DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, &Format, sizeof(USHORT),
NULL, 0, &dummy, NULL);

CloseHandle(hFile);
}

The other part we had already covered in your last Q, it should be replaced by

if ( !strcmp(pszPath,".") || !strcmp(pszPath,"..")) return;

in order not to recurse back into parent directories.
0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 1000 total points
ID: 13493837
>>>> if  (   pszBase)
>>>>   sprintf (   acPath, "%s\\%s",   pszBase,    pszPath);
>>>> else
>>>>   lstrcpy ( acPath, pszPath);

Replace that code by

    sprintf (   acPath, "%s\\%s",   pszBase,    pszPath);

or

    CString strPath = pszBase + CString("\\") + pszPath; // Then take strPath instead of acPath

In any case pszBase must not be NULL as the printf statement 5 lines before would have crashed.

>>>> CreateFile(pw32fs->cFileName,

It should be   pw32fd->cFileName.

It makes not much sense to pass a WIN32_FIND_DATA pointer to a function if the function only needs a filename.


Maybe better use this:

#include <string>
#include <iostream>
using namespace std;

#include <windows.h>

class FileQueryObject
{
    int    m_level;
    string m_name;
    bool   m_isFolder;
public:
    FileQueryObject(int level, const string& name, bool folder)
        : m_level(level), m_name(name), m_isFolder(folder) {}
    int    getLevel() { return m_level; }
    string getName() { return m_name; }
    bool   isFolder() { return m_isFolder; }
};

class FileQuery
{
   FileQueryObject* m_pCurObject;
   FileQuery*       m_pSubQuery;
   HANDLE           m_hFind;        
   int              m_level;
   string           m_startDir;

public:
   FileQuery(int level, const string& startDir)
       : m_pCurObject(NULL), m_pSubQuery(NULL), m_hFind(NULL),
       m_level(level), m_startDir(startDir)  {}
   virtual ~FileQuery() { delete m_pCurObject; delete m_pSubQuery; }

   int    getLevel() { return m_level; }
   string getStartDir() { return m_startDir; }
   FileQueryObject* find( );

};

// query function to search for directories and files
FileQueryObject* FileQuery::find( )
{
    FileQueryObject*    pFound  = NULL;

    // handle a running sub query first
    if ( m_pSubQuery != NULL )
    {
        pFound = m_pSubQuery->find( );
        if ( pFound != NULL )
        {
            return pFound;
        }
        delete m_pSubQuery;
        m_pSubQuery = NULL;
        delete m_pCurObject;  
        m_pCurObject = NULL;
    }
    // next case is to open a new sub query of the current directory
    else if ( m_pCurObject != NULL )
    {
        if ( m_pCurObject->isFolder() )
        {
            m_pSubQuery  = new FileQuery( m_level + 1, m_startDir + "/" + m_pCurObject->getName());
            pFound = m_pSubQuery->find( );
            if ( pFound != NULL )
            {
                return pFound;
            }
            delete m_pSubQuery;
            m_pSubQuery = NULL;
        }
        delete m_pCurObject;  
        m_pCurObject = NULL;
    }

    // if come here we start looking for directories and files
    WIN32_FIND_DATA     findData;
    string              name;

    pFound = NULL;

    // we use a loop to easily skip dummy directories and entries that do not fit
    do
    {
        // check if search starts
        if ( m_hFind == NULL )
        {
            // build full path and search all files and sub folders
            string startDir = m_startDir + "/*.*";
            // get first subdirectory or file and a valid handle
            m_hFind = FindFirstFile( startDir.c_str(), &findData );
            if ( m_hFind == INVALID_HANDLE_VALUE )
            {
                break;
            }
            // now we have at least one result
        }
        // next call
        else
        {
            if ( !FindNextFile( m_hFind, &findData ) )
            {  
                break;
            }
        }

        pFound = new FileQueryObject (m_level+1, findData.cFileName,
                                      ( findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 );
        name   = pFound->getName();
        if ( pFound->isFolder()   && ( name == "."  || name == ".."  ) )        
        {
            delete pFound;
            pFound = NULL;
        }
    }
    while ( pFound == NULL );

    return m_pCurObject = pFound;
}

int main()
{
    FileQuery query(1, "d:/test");

    FileQueryObject*  pFound;

    while ((pFound = query.find()) != NULL)
    {
        cout << pFound->getLevel();
        for (int n = 0; n < pFound->getLevel(); ++n)
            cout << "\t";
        cout << pFound->getName() << endl;
    }
    return 0;
}

Regards, Alex
0

Featured Post

On Demand Webinar: Networking for the Cloud Era

Did you know SD-WANs can improve network connectivity? Check out this webinar to learn how an SD-WAN simplified, one-click tool can help you migrate and manage data in the cloud.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
C++ Properties One feature missing from standard C++ that you will find in many other Object Oriented Programming languages is something called a Property (http://www.experts-exchange.com/Programming/Languages/CPP/A_3912-Object-Properties-in-C.ht…
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.

777 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question