UsenetSearch/src/Database.cpp

180 lines
4.8 KiB
C++

/*
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 <https://www.gnu.org/licenses/>.
*/
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
#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<std::vector<std::wstring>> 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<char*>(&articleCount),
sizeof(articleCount)
);
auto result = std::make_unique<std::vector<std::wstring>>();
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<std::vector<NntpListEntry>> Database::LoadNewsgroupList()
{
OpenNewsGroupFile();
std::uint64_t dbVersion{0};
m_newsGroupFileInput.read(
reinterpret_cast<char*>(&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<char*>(&newsGroupCount),
sizeof(newsGroupCount)
);
auto result = std::make_unique<std::vector<NntpListEntry>>();
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<std::wstring>& 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<const char*>(&articleCount),
sizeof(articleCount)
);
for (const auto& id: articleIds)
{
io << id;
}
io.close();
}
void Database::UpdateNewsgroupList(const std::vector<NntpListEntry>& list)
{
OpenNewsGroupFile();
m_newsGroupFileOutput.write(
reinterpret_cast<const char*>(&m_databaseVersion),
sizeof(m_databaseVersion)
);
const std::uint64_t newsGroupCount = list.size();
m_newsGroupFileOutput.write(
reinterpret_cast<const char*>(&newsGroupCount),
sizeof(newsGroupCount)
);
for (const auto& entry: list)
{
m_newsGroupFileOutput << entry;
}
m_newsGroupFileOutput.flush();
}
} // namespace usenetsearch