Comprehensive C++ Program for Directory File Logging and Comparison
Efficiently track and compare directory contents with detailed logging
Key Takeaways
- Automated Logging: Streamlines the process of recording file details across directories and subdirectories.
- Accurate Comparison: Effectively identifies added, removed, and modified files by comparing log files.
- User-Friendly Output: Generates clear and organized difference reports for easy analysis.
Introduction
Managing and monitoring the contents of directories is a common requirement in various applications. Whether it's for backup solutions, synchronization tasks, or simply tracking changes over time, having a reliable method to log and compare directory contents is invaluable. This comprehensive guide presents a C++ program designed to record detailed information about files within a directory and its subdirectories, and subsequently compare these records to identify any changes.
Program Objectives
The C++ program outlined below accomplishes the following objectives:
- Records the filename, size (in KB), modification date, and creation date of each file in a specified directory.
- Recursively processes all subdirectories to ensure comprehensive coverage.
- Generates a timestamped log file containing the recorded information.
- Finds the most recent previous log file and compares it with the current log to identify added, deleted, or modified files.
- Outputs the differences to a corresponding ".dif" file for easy review.
Detailed C++ Implementation
1. Setting Up the Environment
To effectively utilize the program, ensure that your development environment supports C++17 or later, as the program leverages the std::filesystem library introduced in C++17 for handling file and directory operations.
2. Program Structure
The program is structured into several key components:
- File Information Struct: Defines the structure to store file details.
- Time Formatting Functions: Converts file timestamps to the desired string format.
- Directory Traversal: Recursively scans directories and collects file information.
- Logging Mechanism: Writes the collected information to a log file.
- Log Comparison: Identifies differences between the latest and previous logs.
- Difference Reporting: Outputs the changes to a ".dif" file.
3. The Complete C++ Code
Code Overview
Below is the complete C++ program that fulfills the outlined objectives. The code is thoroughly commented to explain each section's functionality.
#include <iostream>
#include <fstream>
#include <filesystem>
#include <vector>
#include <map>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <ctime>
// Namespace alias for filesystem
namespace fs = std::filesystem;
// Struct to hold file information
struct FileInfo {
std::string filename;
double size_kb;
std::string modify_time;
std::string create_time;
};
// Function to get current time as string in "YYYY-MM-DD-HH-MM-SS" format
std::string getCurrentTimeString() {
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::tm *tm_ptr = std::localtime(&now_c);
std::ostringstream oss;
oss << std::put_time(tm_ptr, "%Y-%m-%d-%H-%M-%S");
return oss.str();
}
// Function to convert filesystem time to string
std::string timePointToString(const fs::file_time_type& ftime) {
auto sctp = std::chrono::system_clock::now() +
(ftime - fs::file_time_type::clock::now());
std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
std::tm *tm_ptr = std::localtime(&tt);
std::ostringstream oss;
oss << std::put_time(tm_ptr, "%Y-%m-%d-%H-%M-%S");
return oss.str();
}
// Function to record files in a directory recursively
std::vector<FileInfo> recordFiles(const fs::path& directory) {
std::vector<FileInfo> files;
try {
for (const auto& entry : fs::recursive_directory_iterator(directory)) {
if (fs::is_regular_file(entry.status())) {
FileInfo fi;
fi.filename = entry.path().filename().string();
fi.size_kb = static_cast<double>(fs::file_size(entry.path())) / 1024.0;
fi.modify_time = timePointToString(fs::last_write_time(entry.path()));
// Creation time handling
#ifdef _WIN32
fi.create_time = timePointToString(fs::creation_time(entry.path()));
#else
fi.create_time = fi.modify_time; // POSIX may not support creation time
#endif
files.push_back(fi);
}
}
}
catch(const fs::filesystem_error& e) {
std::cerr << "Filesystem error: " << e.what() << '\n';
}
return files;
}
// Function to write file information to a log file
bool writeLogFile(const std::vector<FileInfo>& files, const std::string& log_filename) {
std::ofstream log_file(log_filename);
if (!log_file.is_open()) {
std::cerr << "Failed to open log file: " << log_filename << '\n';
return false;
}
for (const auto& file : files) {
log_file << file.filename << ","
<< std::fixed << std::setprecision(2) << file.size_kb << "KB,"
<< file.modify_time << ","
<< file.create_time << "\n";
}
log_file.close();
return true;
}
// Function to read log file and parse into a map
std::map<std::string, FileInfo> readLogFile(const std::string& log_filename) {
std::map<std::string, FileInfo> file_map;
std::ifstream log_file(log_filename);
if (!log_file.is_open()) {
std::cerr << "Failed to open log file for reading: " << log_filename << '\n';
return file_map;
}
std::string line;
while (std::getline(log_file, line)) {
std::istringstream iss(line);
std::string token;
FileInfo fi;
if (std::getline(iss, token, ',')) {
fi.filename = token;
}
if (std::getline(iss, token, ',')) {
size_t pos = token.find("KB");
fi.size_kb = std::stod(token.substr(0, pos));
}
if (std::getline(iss, token, ',')) {
fi.modify_time = token;
}
if (std::getline(iss, token, ',')) {
fi.create_time = token;
}
file_map[fi.filename] = fi;
}
log_file.close();
return file_map;
}
// Function to find the closest previous log file
std::string findClosestLog(const fs::path& directory, const std::string& current_log_time) {
std::string closest_log;
long long min_diff = LLONG_MAX;
for (const auto& entry : fs::directory_iterator(directory)) {
if (fs::is_regular_file(entry.status()) && entry.path().extension() == ".log") {
std::string filename = entry.path().filename().string();
// Extract time part
size_t pos = filename.find(".log");
if (pos == std::string::npos) continue;
std::string log_time = filename.substr(0, pos);
// Convert to time_t for comparison
std::tm tm = {};
std::istringstream ss(log_time);
ss >> std::get_time(&tm, "%Y-%m-%d-%H-%M-%S");
if (ss.fail()) continue;
std::time_t log_tt = std::mktime(&tm);
// Current log time
std::tm current_tm = {};
std::istringstream css(current_log_time);
css >> std::get_time(¤t_tm, "%Y-%m-%d-%H-%M-%S");
std::time_t current_tt = std::mktime(¤t_tm);
if (current_tt <= log_tt) continue; // Only consider earlier logs
long long diff = current_tt - log_tt;
if (diff < min_diff) {
min_diff = diff;
closest_log = filename;
}
}
}
return closest_log;
}
// Function to compare two log files and output differences
bool compareLogs(const std::map<std::string, FileInfo>& old_log,
const std::map<std::string, FileInfo>& new_log,
const std::string& diff_filename) {
std::ofstream diff_file(diff_filename);
if (!diff_file.is_open()) {
std::cerr << "Failed to open difference file: " << diff_filename << '\n';
return false;
}
// Identify added and modified files
std::vector<std::string> added_files;
std::vector<std::string> modified_files;
for (const auto& [filename, new_info] : new_log) {
auto it = old_log.find(filename);
if (it == old_log.end()) {
added_files.push_back(filename);
}
else {
if (it->second.size_kb != new_info.size_kb ||
it->second.modify_time != new_info.modify_time ||
it->second.create_time != new_info.create_time) {
modified_files.push_back(filename);
}
}
}
// Identify deleted files
std::vector<std::string> deleted_files;
for (const auto& [filename, old_info] : old_log) {
if (new_log.find(filename) == new_log.end()) {
deleted_files.push_back(filename);
}
}
// Write differences to the .dif file
if (!added_files.empty()) {
diff_file << "新增檔案:\n";
for (const auto& file : added_files) {
diff_file << file << "\n";
}
diff_file << "\n";
}
if (!deleted_files.empty()) {
diff_file << "刪除檔案:\n";
for (const auto& file : deleted_files) {
diff_file << file << "\n";
}
diff_file << "\n";
}
if (!modified_files.empty()) {
diff_file << "修改檔案:\n";
for (const auto& file : modified_files) {
diff_file << file << "\n";
}
diff_file << "\n";
}
diff_file.close();
return true;
}
int main(int argc, char* argv[]) {
// Check for directory path argument
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <directory_path>\n";
return 1;
}
fs::path directory = argv[1];
if (!fs::exists(directory) || !fs::is_directory(directory)) {
std::cerr << "Invalid directory path provided.\n";
return 1;
}
// Step 1: Record current directory files
std::vector<FileInfo> current_files = recordFiles(directory);
std::string current_time = getCurrentTimeString();
std::string log_filename = current_time + ".log";
if (!writeLogFile(current_files, (directory / log_filename).string())) {
return 1;
}
std::cout << "Log file created: " << log_filename << "\n";
// Step 2: Find the closest previous log file
std::string closest_log = findClosestLog(directory, current_time);
if (closest_log.empty()) {
std::cout << "No previous log file found to compare.\n";
return 0;
}
std::cout << "Comparing with log file: " << closest_log << "\n";
// Step 3: Read both log files
std::map<std::string, FileInfo> old_log = readLogFile((directory / closest_log).string());
std::map<std::string, FileInfo> new_log;
for (const auto& file : current_files) {
new_log[file.filename] = file;
}
// Step 4: Compare logs and write differences
std::string dif_filename = current_time + ".dif";
if (compareLogs(old_log, new_log, (directory / dif_filename).string())) {
std::cout << "Difference file created: " << dif_filename << "\n";
} else {
return 1;
}
return 0;
}
Code Explanation
a. Including Necessary Headers
The program begins by including essential headers for file operations, input/output handling, and string manipulation. The std::filesystem library is pivotal for directory traversal and file attribute retrieval.
b. Defining the FileInfo Struct
The FileInfo struct is a simple data structure designed to hold pertinent details about each file:
- filename: The name of the file.
- size_kb: The size of the file in kilobytes.
- modify_time: The last modification timestamp.
- create_time: The creation timestamp.
c. Time Formatting Functions
Accurate time representation is crucial for both logging and comparison. The getCurrentTimeString function captures the current system time and formats it into a string following the "YYYY-MM-DD-HH-MM-SS" pattern. The timePointToString function converts std::filesystem::file_time_type to a formatted string, ensuring consistency across different timestamps.
d. Recording Files with recordFiles
The recordFiles function traverses the specified directory recursively, capturing information about each regular file encountered. It utilizes std::filesystem::recursive_directory_iterator to traverse all subdirectories. For each file, it records the filename, computes the size in kilobytes, and retrieves both the modification and creation times.
e. Logging Mechanism with writeLogFile
After collecting file information, the writeLogFile function writes this data to a log file. Each line in the log file follows a comma-separated format:
filename,sizeKB,modify_time,create_time
This standardized format facilitates easy parsing and comparison in subsequent steps.
f. Reading Logs with readLogFile
The readLogFile function reads an existing log file and populates a std::map with the data. This map uses the filename as the key and the corresponding FileInfo as the value, enabling efficient lookups during the comparison phase.
g. Finding the Closest Previous Log with findClosestLog
To compare the current directory state with the previous one, the program needs to identify the most recent log file prior to the current one. The findClosestLog function scans the directory for log files, parses their timestamps, and determines which log file has the closest timestamp before the current log's timestamp.
h. Comparing Logs with compareLogs
The core functionality lies in the compareLogs function. It compares the old and new log maps to identify:
-
Added Files: Files present in the new log but absent in the old one.
-
Deleted Files: Files present in the old log but absent in the new one.
-
Modified Files: Files whose size or timestamps have changed between logs.
The differences are then written to a ".dif" file, categorized under added, deleted, and modified sections for clarity.
i. Main Function Workflow
The main function orchestrates the program's workflow:
- Validates the provided directory path.
- Records the current state of the directory by invoking
recordFilesand logs it usingwriteLogFile. - Identifies the closest previous log file using
findClosestLog. - Reads both the current and previous log files.
- Compares the logs and outputs the differences to a ".dif" file using
compareLogs.
Enhancements and Best Practices
Error Handling
Robust error handling ensures that the program can gracefully handle unexpected scenarios, such as inaccessible directories or malformed log files. The program includes checks for file accessibility and validates the existence of directories before proceeding.
Cross-Platform Compatibility
While the program is designed to be cross-platform, certain functionalities like retrieving the creation time are platform-dependent. On Windows systems, the creation time can be accurately fetched using std::filesystem::creation_time, whereas on POSIX systems, this might not be available, and the modification time is used as a fallback.
Performance Optimization
For directories with a vast number of files, performance can be a concern. Utilizing efficient data structures like std::unordered_map instead of std::map for storing log data can enhance lookup speeds. Additionally, multithreading can be incorporated to parallelize directory traversal, further improving performance.
Extensibility
The program can be extended to include more file attributes, such as file permissions, owner information, or checksums for enhanced verification. Integrating logging libraries can also provide more detailed logging capabilities, facilitating easier debugging and maintenance.
Sample Output
Log File (.log)
| Filename | Size (KB) | Modification Date | Creation Date |
|---|---|---|---|
| example.txt | 15.25KB | 2025-02-15-10-30-00 | 2025-02-10-09-20-30 |
| report.pdf | 250.50KB | 2025-02-16-11-45-10 | 2025-02-11-08-15-25 |
Difference File (.dif)
| Category | Files |
|---|---|
| 新增檔案 (Added Files) | new_document.docx |
| 刪除檔案 (Deleted Files) | old_image.png |
| 修改檔案 (Modified Files) | report.pdf |
Conclusion
This C++ program provides a robust solution for logging and comparing directory contents. By automating the recording of file attributes and facilitating straightforward comparisons between different directory states, it serves as a valuable tool for developers and system administrators alike. The structured approach ensures scalability and ease of maintenance, making it adaptable to various use cases and environments.
References
Last updated February 17, 2025