diff --git a/source/log/local_log_repository.cpp b/source/log/local_log_repository.cpp index 3c77cb7..1568463 100644 --- a/source/log/local_log_repository.cpp +++ b/source/log/local_log_repository.cpp @@ -47,11 +47,14 @@ std::optional LocalLogRepository::read(const std::chrono::year_month_da } void LocalLogRepository::write(const LogFile &log) { + const auto path = m_pathProvider.path(log.getDate()); + std::filesystem::create_directories(path.parent_path()); + if (not m_password.empty()) { std::istringstream iss{log.getContent()}; - std::ofstream{m_pathProvider.path(log.getDate())} << utils::encrypt(m_password, iss); + std::ofstream{path} << utils::encrypt(m_password, iss); } else { - std::ofstream{m_pathProvider.path(log.getDate())} << log.getContent(); + std::ofstream{path} << log.getContent(); } } diff --git a/source/log/local_log_repository.hpp b/source/log/local_log_repository.hpp index c511aeb..a13281a 100644 --- a/source/log/local_log_repository.hpp +++ b/source/log/local_log_repository.hpp @@ -4,6 +4,7 @@ #include "utils/date.hpp" #include +#include #include namespace caps_log::log { @@ -17,7 +18,8 @@ class LocalFSLogFilePathProvider { : m_logDirectory{logDir}, m_logFilenameFormat{std::move(logFilenameFormat)} {} inline std::filesystem::path path(const std::chrono::year_month_day &date) const { - return {m_logDirectory / utils::date::formatToString(date, m_logFilenameFormat)}; + return {m_logDirectory / fmt::format("y{}", (int)date.year()) / + utils::date::formatToString(date, m_logFilenameFormat)}; } inline std::filesystem::path getLogDirPath() const { return m_logDirectory; } diff --git a/source/log/log_repository_crypto_applier.cpp b/source/log/log_repository_crypto_applier.cpp index 33f2b12..1b4c500 100644 --- a/source/log/log_repository_crypto_applier.cpp +++ b/source/log/log_repository_crypto_applier.cpp @@ -1,7 +1,9 @@ #include "log_repository_crypto_applier.hpp" #include "utils/crypto.hpp" +#include #include +#include #include #include #include @@ -45,7 +47,7 @@ std::optional openOutputFileStream(const std::filesystem::path &p return std::move(ofs); } -bool fileMatchesLogFilenamePatter(const std::filesystem::directory_entry &entry, +bool fileMatchesLogFilenameFormat(const std::filesystem::directory_entry &entry, const std::string &logFilenameFormat) { std::tm time{}; std::istringstream iss(entry.path().filename().string()); @@ -105,11 +107,25 @@ void LogRepositoryCryptoApplier::apply(const std::string &password, } }; + const auto isYearDir = [&](const std::filesystem::directory_entry &entry) { + const auto filename = entry.path().filename().string(); + return entry.is_directory() && filename.size() == 5 /* yYYYY */ && filename[0] == 'y' && + std::all_of(filename.begin() + 1, filename.end(), + [](char c) { return std::isdigit(c); }); + }; + // Iterate over all files in the directory for (const auto &entry : std::filesystem::directory_iterator{logDirPath}) { - if (entry.is_regular_file() && fileMatchesLogFilenamePatter(entry, logFilenameFormat)) { + if (!isYearDir(entry)) { + continue; + } + for (const auto &subEntry : std::filesystem::directory_iterator{entry.path()}) { + if (!(subEntry.is_regular_file() && + fileMatchesLogFilenameFormat(subEntry, logFilenameFormat))) { + continue; + } try { - processLogFile(entry); + processLogFile(subEntry); } catch (const std::exception &e) { // Do not throw here, accumulate errors instead errors.push_back(e); diff --git a/source/main.cpp b/source/main.cpp index 10fe39b..9204d5c 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -21,6 +22,66 @@ auto md(unsigned char month, unsigned int day) { return std::chrono::month_day{std::chrono::month{month}, std::chrono::day{day}}; } +/** + * If the curret log dir has a structure like + * / + * - 2021-01-01.md + * - 2021-01-02.md + * - .. + * - 2024-12-31.md + * then migrate to + * / + * - 2021/ + * - 2021-01-01.md + * - 2021-01-02.md + * - 2022/ + * - 2022-01-01.md + */ +void migrateToNewRepoStructureIfNeeded( + const caps_log::log::LocalFSLogFilePathProvider &pathProvider) { + + const auto logDirPath = pathProvider.getLogDirPath(); + const auto logFilenameFormat = pathProvider.getLogFilenameFormat(); + + const auto parseDate = + [logFilenameFormat]( + const std::filesystem::path &path) -> std::optional { + const auto filenameStr = path.filename().string(); + std::tm dateTime = {}; + std::istringstream ss{filenameStr}; + if (!(ss >> std::get_time(&dateTime, logFilenameFormat.c_str()))) { + return std::nullopt; + } + + std::string remaining; + std::getline(ss, remaining); + if (!remaining.empty()) { + return std::nullopt; + } + return std::chrono::year_month_day{ + std::chrono::year{dateTime.tm_year + 1900}, + std::chrono::month{static_cast(dateTime.tm_mon + 1)}, + std::chrono::day{static_cast(dateTime.tm_mday)}}; + }; + + for (const auto &entry : std::filesystem::directory_iterator{logDirPath}) { + const auto date = parseDate(entry.path()); + if (!date.has_value()) { + continue; + } + const auto newPath = pathProvider.path(date.value()); + if (entry.path() == newPath) { + throw std::runtime_error{"Log file already in correct location!"}; + } + if (std::filesystem::exists(newPath)) { + throw std::runtime_error{ + fmt::format("Log file already exists at: {}", newPath.string())}; + } + std::filesystem::create_directories(newPath.parent_path()); + std::filesystem::rename(entry.path(), newPath); + } +} + std::string promptPassword(const caps_log::Config &config) { using namespace ftxui; auto screen = ScreenInteractive::Fullscreen(); @@ -82,6 +143,8 @@ auto makeCapsLog(const caps_log::Config &conf) { return std::nullopt; }(); + migrateToNewRepoStructureIfNeeded(pathProvider); + AppConfig appConf; appConf.skipFirstLine = conf.ignoreFirstLineWhenParsingSections; appConf.events = conf.calendarEvents; diff --git a/test/local_log_repository_test.cpp b/test/local_log_repository_test.cpp index a0e2d9c..3ae2a3a 100644 --- a/test/local_log_repository_test.cpp +++ b/test/local_log_repository_test.cpp @@ -37,7 +37,9 @@ class LocalLogRepositoryTest : public ::testing::Test { void SetUp() override { std::filesystem::create_directory(kTestLogDirectory); } void TearDown() override { std::filesystem::remove_all(kTestLogDirectory); } void writeDummyLog(const std::chrono::year_month_day &date, const std::string &content) const { - std::ofstream ofs(TMPDirPathProvider.path(date)); + const auto path = TMPDirPathProvider.path(date); + std::filesystem::create_directories(path.parent_path()); + std::ofstream ofs{path}; ofs << content; } static void writeDummyFile(const std::string &path, const std::string &content) {