4
\$\begingroup\$

I found this Powershell command useful in comparing folders and find common and different files. Since I really like C and C++, I've decided to create a program to do that.

It will get all files in 2 folders given as arguments, will store them in an std::map. After, it will compare the 2 maps and give the common files. Is using std::map the best container for this?

Some notes:

The findFiles method should benefit from RAII treatment, but since I have ZERO work or internship experience, I am unable to implement that.

Some functions like finding a file size and iterating over a folder are present in C++ 17, but I use Digital Mars, an old compiler not up to date.

I use this compiler because it is small, provided as a compressed folder aka portable in the mainstream lexicon (even though portable means something else) and it is straightforward to use.

I used an online code beautifier for indentation.

The sanitizePath method is used to eliminate trailing "/" or "\" from the given path.

Please give all your valuable comments on this work.

#include <iostream>
#include <iterator>
#include <map>
#include <string>
#include <sys/stat.h>
#include <windows.h>

#ifndef INVALID_FILE_ATTRIBUTES
constexpr DWORD INVALID_FILE_ATTRIBUTES = ((DWORD)-1);
#endif

bool IsDir(const std::string &path)
{
    DWORD Attr;

    Attr = GetFileAttributes(path.c_str());
    if (Attr == INVALID_FILE_ATTRIBUTES)
    return false;

    return (bool)(Attr & FILE_ATTRIBUTE_DIRECTORY);
}

std::string sanitizePath(std::string const &input)
{
auto pos = input.find_last_not_of("/\\");
return input.substr(0, pos + 1);
}

std::map<std::string, unsigned long > findFiles(std::string &spath)
{
size_t i = 1;
WIN32_FIND_DATA FindFileData;

std::map<std::string, unsigned long > list;

std::string sourcepath = spath + std::string("\\*.*");

HANDLE hFind = FindFirstFile(sourcepath.c_str(), &FindFileData);

if (hFind != INVALID_HANDLE_VALUE)
    do {
        std::string fullpath = std::string(spath) + std::string("\\") + std::string(FindFileData.cFileName);

        if (*(fullpath.rbegin()) == '.')
            continue;
        else
        if (FindFileData.dwFileAttributes &FILE_ATTRIBUTE_DIRECTORY)
            findFiles(fullpath);
        else
        {
            list[FindFileData.cFileName] = FindFileData.nFileSizeHigh *(MAXWORD + 1) + FindFileData.nFileSizeLow;
        }
    } while (FindNextFile(hFind, &FindFileData));

FindClose(hFind);

return list;
}

void displayMap(std::map<std::string, unsigned long > &map)
{
std::map<std::string, unsigned long>::const_iterator itr;

for (itr = map.begin(); itr != map.end(); itr++)
    std::cout << "File Name: " << itr->first << " Size: " << itr->second << " bytes" << std::endl;
}

std::map<std::string, unsigned long > map_intersect(std::map<std::string, unsigned long > const 
 &source, std::map<std::string, unsigned long > const &dest)
{
    std::map<std::string, unsigned long > inter;
    std::map<std::string, unsigned long>::const_iterator iter = dest.begin();
    std::map<std::string, unsigned long>::const_iterator end = dest.end();
    for (; iter != end; iter++)
    {
        if (source.find(iter->first) != source.end())
        {
        inter[iter->first] = iter->second;
        }
    }

    return inter;
   }

std::map<std::string, unsigned long > map_difference(std::map<std::string, unsigned long > const 
    &source, std::map<std::string, unsigned long > const &dest)
    {
        std::map<std::string, unsigned long > diff = source;
        std::map<std::string, unsigned long>::const_iterator iter = dest.begin();
        std::map<std::string, unsigned long>::const_iterator end = dest.end();
        for (; iter != end; iter++)
        {
            if (source.find(iter->first) != source.end())
            {
            diff.erase(iter->first);
            }
        }

        return diff;
    }

int main(int argc, char **argv)
{
if (argc <= 2)
{
    std::cerr << "No path or filename provided" << std::endl;
    return EXIT_FAILURE;
}

const char *source = argv[1];
const char *destination = argv[2];

if (!IsDir(source))
{
    std::cerr << "Source path doesn't exist" << std::endl;
    return EXIT_FAILURE;
}

if (!IsDir(destination))
{
    std::cerr << "Destination path doesn't exist" << std::endl;
    return EXIT_FAILURE;
}

std::string spath = sanitizePath(source);
std::string dpath = sanitizePath(destination);

std::cout << "Comparing " << spath << " and " << dpath << std::endl;

std::map<std::string, unsigned long > slist, dlist, ilist, diflist;

slist = findFiles(spath);
dlist = findFiles(dpath);

ilist = map_intersect(slist, dlist);
diflist = map_difference(slist, dlist);

if (ilist.empty())
    std::cout << "There is no common files" << std::endl;
else
{
    std::cout << "The common files are" << std::endl;
    displayMap(ilist);
}

if (diflist.empty())
    std::cout << "The 2 folder are the same" << std::endl;

return EXIT_SUCCESS;
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Simplify For Loops by Using Ranged For Loops
Ranged for loops have existed in C++ since C++11. A ranged for loop is basically the equivalent of a foreach loop in PHP or C#. The functions displayMap(), map_intersect() and map_difference(). Using ranged for loops in these functions removes the need for the header file iterator.

Compare the following functions to the ones in the question:

void displayMap(std::map<std::string, unsigned long >& map)
{
    for (auto file: map)
    {
        std::cout << "File Name: " << file.first << " Size: " << file.second << " bytes" << "\n";
    }

    std::cout << std::endl;
}

std::map<std::string, unsigned long > map_intersect(std::map<std::string, unsigned long > const
    & source, std::map<std::string, unsigned long > const& dest)
{
    std::map<std::string, unsigned long > intersections;
    for (auto file : dest)
    {
        if (source.find(file.first) != source.end())
        {
            intersections.insert(std::pair<std::string, unsigned long>(file.first, file.second));
        }
    }

    return intersections;
}

std::map<std::string, unsigned long > map_difference(std::map<std::string, unsigned long > const
    & source, std::map<std::string, unsigned long > const& dest)
{
    std::map<std::string, unsigned long > differences = source;
    for (auto file: dest)
    {
        if (source.find(file.first) != source.end())
        {
            differences.erase(file.first);
        }
    }

    return differences;
}

The use and declaration of iterators is not required and simplifies the code.

Variable Names
While the variable names are not a, b, c they are abbreviations and make the code less readable. The functions above have replaced some of the variable names as examples.

In main() I would also lengthen the variable names of at least the lists:

    std::map<std::string, unsigned long > sourceFiles, destinationFiles, intersections, differences;

Put Variable Declarations on Separate Lines
To make code easier to maintain, each map/list should be declared on its own line.

    std::map<std::string, unsigned long > sourceFiles;
    std::map<std::string, unsigned long > destinationFiles;
    std::map<std::string, unsigned long > intersections;
    std::map<std::string, unsigned long > differences;

This makes adding or deleting variables easier as well as making the code more readable.

Use std::string Rather than C Style Strings
Rather than use old style C strings initialize std::strings with argv[1] and argv[2].

    std::string source(argv[1]);
    std::string destination(argv[2]);

This ensures that the types match in the function calls.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.