// ============================================================================= // Duff // ----------------------------------------------------------------------------- // Version 1.0 - Initial Coding // // Version 1.1 - Switched over to using c++'s filesystem, authored // cross-platform Makefile // ============================================================================= #define VERSION "1.1" #include #include #include #include #include #include #include "types.h" #include "fs.h" using std::vector; using std::string; using std::map; // -------------------------------------------------------- // Constants: SEPARATOR - current platform's separator // and OTHER - the "other" platform's // separator character. Assumes '/' and '\' // are the possible separator characters. // -------------------------------------------------------- static const char SEPARATOR = fs::path::preferred_separator; static const char OTHER = (SEPARATOR == '/' ? '\\' : '/'); // ---------------------------------------------------- // fixPath - Ensure directory separator chars in // 'thePath' are canonical for the current // platform, also ensure the returned path // ends with a separator // ---------------------------------------------------- static string fixPath(const string &thePath) { string result; for(char c : thePath) result += (c == OTHER ? SEPARATOR : c); size_t size = result.size(); if(size > 0 && result[size - 1] != SEPARATOR) result += SEPARATOR; return(result); } // ---------------------------------------- // toString - Convert path to string // ---------------------------------------- static string toString(const fs::path &thePath) { return(thePath.u8string()); } // ------------------------------------------------------- // getBase - Extract base name from path // ------------------------------------------------------- static string getBase(const string &root, const fs::path &thePath) { // If thePath is a directory and starts with 'root' // we remove it (similarly for a file but we use the // directory the file resides in). So, assume 'thePath' is: // i:\h\media\xfiles\s9\xfiles-s09-e01.mp4 // and 'root' is: 'i:\h\media', then we'd return // back: 'xfiles\s9'. If 'thePath' is the same as // 'root', we use '.' to mean the current directory. string theKey = toString(thePath); if(is_regular_file(thePath)) theKey = toString(thePath.parent_path()); if(theKey.size() <= root.size()) theKey = "."; else theKey = theKey.substr(root.size()); return(theKey); } // ------------------------------------------------------- // skipThisDiretory - returns true if this is a hidden or // system directory, on platforms that // don't have those attributes this // function will always return false // ------------------------------------------------------- static bool skipThisDirectory(const string &theDirectory) { return(Types::isHidden(theDirectory) || Types::isSystem(theDirectory)); } static void showPath(const fs::path &path, bool doShow = false) { if(doShow) std::cout << "path: " << path << "\n"; } // ---------------------------------------------------- // getMap // ---------------------------------------------------- static bool getMap( const string &dir, map> &theMap, string &errMsg ) { string current; try { for(fs::recursive_directory_iterator next(dir), end; next != end; ++next) { fs::path thePath = next->path(); string base = getBase(dir, thePath); showPath(thePath); if(theMap.count(base) == 0) theMap[base] = map(); if(is_regular_file(thePath)) theMap[base][toString(thePath.filename())] = file_size(thePath); else if(is_directory(thePath) && skipThisDirectory(thePath.u8string())) next.disable_recursion_pending(); current = toString(thePath); } } catch(fs::filesystem_error e) { errMsg = e.code().message() + " (" + (current == "" ? dir : current) + ")"; return(false); } if(theMap.count(".") == 0) theMap["."] = map(); return(true); } // ---------------------------------------------------- // dump // ---------------------------------------------------- static void dump( const string &path, map> &theMap, bool showOutput ) { if(showOutput) { std::cout << path << "\n"; for(auto entry : theMap) { std::cout << " " << entry.first << "\n"; for(auto e2 : theMap[entry.first]) std::cout << " " << e2.first << " " << e2.second << "\n"; } std::cout << "\n"; } } // ---------------------------------------------------- // showDirectories // ---------------------------------------------------- static void showDirectories( const string &nameOne, const string &nameTwo, const vector &list ) { std::cout << "========================================================\n"; std::cout << "Directories found under: [" << nameOne << "]\n"; std::cout << "But not under : [" << nameTwo << "]\n"; std::cout << "========================================================\n\n"; for(auto directory : list) std::cout << " " << directory << "\n"; std::cout << "\n"; } // ---------------------------------------------------- // showFiles // ---------------------------------------------------- static void showFiles( const string &nameOne, const string &nameTwo, const string &dirString, const vector &list ) { std::cout << "========================================================\n"; std::cout << "Files in : [" << nameOne << "]" << dirString << "\n"; std::cout << "but not in: [" << nameTwo << "]" << dirString << "\n"; std::cout << "========================================================\n\n"; for(auto name : list) std::cout << " " << name << "\n"; std::cout << "\n"; } // ---------------------------------------------------- // compare // ---------------------------------------------------- static bool compare(const string &aPath, const string &bPath, string &errMsg) { map> a; map> b; string aFixed = fixPath(aPath); string bFixed = fixPath(bPath); if(!getMap(aFixed, a, errMsg) || !getMap(bFixed, b, errMsg)) return(false); // Change 'false' to 'true' for debug output. dump(aFixed, a, false); dump(bFixed, b, false); vector dOnlyA; vector dOnlyB; vector dCommon; // First, find directories that are in 'a' but not 'b' for(auto entry : a) { if(b.count(entry.first) == 0) dOnlyA.push_back(entry.first); else dCommon.push_back(entry.first); } // Now the opposite for(auto entry : b) if(a.count(entry.first) == 0) dOnlyB.push_back(entry.first); if(dOnlyA.size() > 0) showDirectories(aFixed, bFixed, dOnlyA); if(dOnlyB.size() > 0) showDirectories(bFixed, aFixed, dOnlyB); // Process the common directories for(auto directory : dCommon) { vector fOnlyA; vector fOnlyB; vector fCommon; map aMap = a[directory]; map bMap = b[directory]; for(auto entry : aMap) { if(bMap.count(entry.first) == 0) fOnlyA.push_back(entry.first); else fCommon.push_back(entry.first); } for(auto entry : bMap) if(aMap.count(entry.first) == 0) fOnlyB.push_back(entry.first); string dirString = (directory == "." ? "" : "[" + directory + "]"); if(fOnlyA.size() != 0) showFiles(aFixed, bFixed, dirString, fOnlyA); if(fOnlyB.size() != 0) showFiles(bFixed, aFixed, dirString, fOnlyB); // Now the common files for(auto name : fCommon) { if(aMap[name] != bMap[name]) { // File sizes are different string aFullPath = aFixed + directory + SEPARATOR + name; string bFullPath = bFixed + directory + SEPARATOR + name; std::cout << "========================================================\n"; std::cout << "Size difference encountered\n"; std::cout << " " << aFullPath << ": " << aMap[name] << "\n"; std::cout << " " << bFullPath << ": " << bMap[name] << "\n"; std::cout << "========================================================\n\n"; } } } return(true); } // ---------------------------------------------------- // main // ---------------------------------------------------- int main(int argc, char **argv) { if(argc < 3) { std::cout << "Program Version: " << VERSION << "\n"; std::cout << "Usage: " << argv[0] << " directoryOne directoryTwo\n"; return(1); } string errMsg; if(!compare(argv[1], argv[2], errMsg)) std::cout << errMsg << "\n"; return(0); }