/* Copyright© 2021 John Sennesael UsenetSearch is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. UsenetSearch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with UsenetSearch. If not, see . */ #include #include #include #include #include "usenetsearch/StringUtils.h" #include "usenetsearch/UsenetClient.h" #include "usenetsearch/Database.h" namespace usenetsearch { Database::~Database() { if (m_newsGroupFileInput.is_open()) { m_newsGroupFileInput.close(); } if (m_newsGroupFileOutput.is_open()) { m_newsGroupFileOutput.close(); } } std::filesystem::path Database::GetArticleFilePath( const std::wstring& newsgroup) { const auto groupFile = StringHash(m_conv.to_bytes(newsgroup)) + ".db"; return m_databasePath / groupFile; } std::unique_ptr> Database::LoadArticleList( const std::wstring& newsgroup) { const auto articleFile = GetArticleFilePath(newsgroup); if (!std::filesystem::exists(articleFile)) { throw DatabaseException(ENOTFOUND, "No article list found for newsgroup " + m_conv.to_bytes(newsgroup) ); } std::ifstream io; io.open(articleFile, std::ios::binary); std::uint64_t articleCount; io.read( reinterpret_cast(&articleCount), sizeof(articleCount) ); auto result = std::make_unique>(); for (std::uint64_t i = 0; i != articleCount; ++i) { std::wstring articleId; io >> articleId; result->emplace_back(articleId); } io.close(); return result; } std::unique_ptr> Database::LoadNewsgroupList() { OpenNewsGroupFile(); std::uint64_t dbVersion{0}; m_newsGroupFileInput.read( reinterpret_cast(&dbVersion), sizeof(dbVersion) ); if (dbVersion != m_databaseVersion) { throw DatabaseException(EINVAL, "The loaded database version (" + std::to_string(dbVersion) + ") does not match the current database version (" + std::to_string(m_databaseVersion) + ")"); } size_t newsGroupCount{0}; m_newsGroupFileInput.read( reinterpret_cast(&newsGroupCount), sizeof(newsGroupCount) ); auto result = std::make_unique>(); for (size_t numLoaded = 0; numLoaded != newsGroupCount; ++numLoaded) { NntpListEntry entry; m_newsGroupFileInput >> entry; result->emplace_back(entry); } return result; } void Database::Open(std::filesystem::path dbPath) { m_databasePath = dbPath; if (!std::filesystem::exists(dbPath)) { std::filesystem::create_directory(dbPath); } OpenNewsGroupFile(); } void Database::OpenNewsGroupFile() { if (m_newsGroupFileInput.is_open() && m_newsGroupFileOutput.is_open()) { return; } const std::filesystem::path newsGroupFilePath = m_databasePath / "newsgroups.db"; if (!m_newsGroupFileInput.is_open()) { m_newsGroupFileInput.open(newsGroupFilePath, std::ios::binary); } if (!m_newsGroupFileOutput.is_open()) { m_newsGroupFileOutput.open(newsGroupFilePath, std::ios::binary); } } void Database::UpdateArticleList( const std::wstring& newsgroup, const std::vector& articleIds) { const auto articleFile = GetArticleFilePath(newsgroup); std::ofstream io; io.open(articleFile, std::ios::binary); const std::uint64_t articleCount = articleIds.size(); io.write( reinterpret_cast(&articleCount), sizeof(articleCount) ); for (const auto& id: articleIds) { io << id; } io.close(); } void Database::UpdateNewsgroupList(const std::vector& list) { OpenNewsGroupFile(); m_newsGroupFileOutput.write( reinterpret_cast(&m_databaseVersion), sizeof(m_databaseVersion) ); const std::uint64_t newsGroupCount = list.size(); m_newsGroupFileOutput.write( reinterpret_cast(&newsGroupCount), sizeof(newsGroupCount) ); for (const auto& entry: list) { m_newsGroupFileOutput << entry; } m_newsGroupFileOutput.flush(); } } // namespace usenetsearch