// // Created by Stepan Usatiuk on 23.05.2023. // #include "CommandDiff.h" #include "BytesFormatter.h" #include "ChangeDetectorFactory.h" #include "ChunkerFactory.h" #include "Diff.h" #include "Exception.h" #include "Progress.h" #include "RunningDiffAverage.h" #include "Serialize.h" #include "Signals.h" #include "ThreadPool.h" #include "objects/Archive.h" #include "objects/Chunk.h" using namespace CommandsCommon; CommandDiff::CommandDiff() : Command() {} void CommandDiff::run(Context ctx) { std::string diffMode = ctx.repo->getConfig().getStr("diff-mode"); Object::idType archive1; if (!ctx.repo->getConfig().exists("aid")) { auto archives = ctx.repo->getObjects(Object::ObjectType::Archive); archive1 = std::max_element(archives.begin(), archives.end(), [](const auto &a1, const auto &a2) { return a1.second < a2.second; })->second; } else { archive1 = ctx.repo->getConfig().getInt("aid"); } ThreadPool threadPool([&](const std::string &error) { ctx.logger->write("Error: " + error, 0); }, ctx.repo->getConfig().exists("threads") ? ctx.repo->getConfig().getInt("threads") : std::thread::hardware_concurrency()); auto archiveO1 = Serialize::deserialize(ctx.repo->getObjectRaw(archive1)); std::mutex filesLock; std::map files;///< Files in the first archive for (auto id: archiveO1.files) { auto file = Serialize::deserialize(ctx.repo->getObjectRaw(id)); auto path = std::filesystem::path(file.name); if (isSubpath(ctx.repo->getConfig().getStr("prefix"), path)) files.emplace(file.getKey(), std::move(file)); } /// Container of ChangeDetectors built using the config of the repository ChangeDetectorContainer changeDetector = ChangeDetectorFactory::getChangeDetectors(ctx.repo->getConfig()); /// Task to to compare the given file with the first archive auto processFile = [&, this](ComparableFile p) { auto relPath = p.path; std::unique_lock lock(filesLock); if (files.count(relPath) == 0) { ctx.logger->write(relPath + " is new\n", 0); lock.unlock(); } else { File repoFile = files.at(relPath); lock.unlock(); if (changeDetector.check({repoFile, ctx.repo}, p)) { ctx.logger->write(relPath + " is different " + Diff::diff({repoFile, ctx.repo}, p) + "\n", 1); } else { if (diffMode == "file") ctx.logger->write(relPath + " are same ", 0); } } lock.lock(); files.erase(relPath); }; std::optional archiveO2; if (diffMode == "normal") { /// If a second archive is given, run the task for each of its files, otherwise use the "from" config option if (ctx.repo->getConfig().exists("aid2")) { archiveO2.emplace( Serialize::deserialize(ctx.repo->getObjectRaw(ctx.repo->getConfig().getInt("aid2")))); threadPool.push([&]() { for (auto id: archiveO2.value().files) { /// Exit when asked to if (Signals::shouldQuit) throw Exception("Quitting"); auto file = Serialize::deserialize(ctx.repo->getObjectRaw(id)); if (isSubpath(ctx.repo->getConfig().getStr("prefix"), std::filesystem::path(file.name))) threadPool.push([&, file]() { processFile(ComparableFile{file, ctx.repo}); }); if (Signals::shouldQuit) break; } return true; }); } else { std::filesystem::path from = ctx.repo->getConfig().getStr("from"); /// Start the diff with the root directory and empty ignore list threadPool.push([&, from]() { processDirWithIgnore( from, {}, [&](std::function f) { threadPool.push(std::move(f)); }, [processFile, from, prefix = ctx.repo->getConfig().getStr("prefix")]( const std::filesystem::directory_entry &dirEntry) { if (isSubpath(prefix, dirEntry.path().lexically_relative(from))) processFile(ComparableFile{dirEntry, from}); }); }); } } else if (diffMode == "file") { if (files.count(ctx.repo->getConfig().getStr("prefix")) == 0) { ctx.logger->write("Doesn't exist in the first archive", 0); return; } if (ctx.repo->getConfig().exists("aid2")) { archiveO2.emplace( Serialize::deserialize(ctx.repo->getObjectRaw(ctx.repo->getConfig().getInt("aid2")))); std::map files2;///< Files in the first archive for (auto id: archiveO2->files) { auto file = Serialize::deserialize(ctx.repo->getObjectRaw(id)); auto path = std::filesystem::path(file.name); if (isSubpath(ctx.repo->getConfig().getStr("prefix"), path)) files2.emplace(file.getKey(), std::move(file)); } if (files2.count(ctx.repo->getConfig().getStr("prefix")) == 0) { ctx.logger->write("Doesn't exist in the second archive", 0); return; } else { processFile(ComparableFile{files2.at(ctx.repo->getConfig().getStr("prefix")), ctx.repo}); } } else { std::filesystem::path from = ctx.repo->getConfig().getStr("from"); if (!std::filesystem::exists(from / ctx.repo->getConfig().getStr("prefix"))) { ctx.logger->write("Doesn't exist in the filesystem archive", 0); return; } /// Start the diff with the root directory and empty ignore list processFile(ComparableFile{from / ctx.repo->getConfig().getStr("prefix"), from}); } } else { throw Exception("Unknown diff-mode: " + diffMode); } /// Wait for diff to end std::unique_lock finishedLock(threadPool.finishedLock); threadPool.finished.wait(finishedLock, [&threadPool] { return threadPool.empty(); }); if (diffMode == "normal") for (auto const &s: files) { ctx.logger->write(s.first.string() + " is removed\n", 0); } }