Avoid loading databases when modification date is <= last update

* Do HTTP head request first when loading database from mirror to avoid
  downloading the full database all the time
* Use the last modification date of the local database file because with
  the persistent storage even local database reloads became a bit expensive
This commit is contained in:
Martchus 2022-01-25 00:13:10 +01:00
parent fe09463b0a
commit 218dfecf56
19 changed files with 255 additions and 111 deletions

View File

@ -111,7 +111,7 @@ struct LIBPKG_EXPORT Config : public Lockable, public ReflectiveRapidJSON::Binar
// load config and packages // load config and packages
void loadPacmanConfig(const char *pacmanConfigPath); void loadPacmanConfig(const char *pacmanConfigPath);
void loadAllPackages(bool withFiles); void loadAllPackages(bool withFiles, bool force);
// storage and caching // storage and caching
void initStorage(const char *path = "libpkg.db", std::uint32_t maxDbs = 0); void initStorage(const char *path = "libpkg.db", std::uint32_t maxDbs = 0);

View File

@ -125,7 +125,7 @@ struct LIBPKG_EXPORT Database : public ReflectiveRapidJSON::JsonSerializable<Dat
void deducePathsFromLocalDirs(); void deducePathsFromLocalDirs();
void resetConfiguration(); void resetConfiguration();
void clearPackages(); void clearPackages();
void loadPackages(bool withFiles = false); void loadPackagesFromConfiguredPaths(bool withFiles = false, bool force = false);
void loadPackages(const std::string &databaseData, CppUtilities::DateTime lastModified); void loadPackages(const std::string &databaseData, CppUtilities::DateTime lastModified);
void loadPackages(FileMap &&databaseFiles, CppUtilities::DateTime lastModified); void loadPackages(FileMap &&databaseFiles, CppUtilities::DateTime lastModified);
static bool isFileRelevant(const char *filePath, const char *fileName, mode_t); static bool isFileRelevant(const char *filePath, const char *fileName, mode_t);

View File

@ -143,11 +143,11 @@ void Config::loadPacmanConfig(const char *pacmanConfigPath)
} }
} }
void Config::loadAllPackages(bool withFiles) void Config::loadAllPackages(bool withFiles, bool force)
{ {
for (auto &db : databases) { for (auto &db : databases) {
try { try {
db.loadPackages(withFiles); db.loadPackagesFromConfiguredPaths(withFiles, force);
} catch (const runtime_error &e) { } catch (const runtime_error &e) {
cerr << Phrases::ErrorMessage << "Unable to load database \"" << db.name << "\": " << e.what() << Phrases::EndFlush; cerr << Phrases::ErrorMessage << "Unable to load database \"" << db.name << "\": " << e.what() << Phrases::EndFlush;
} }

View File

@ -21,13 +21,16 @@ bool Database::isFileRelevant(const char *filePath, const char *fileName, mode_t
return !std::strcmp(fileName, "desc") || !std::strcmp(fileName, "depends") || !std::strcmp(fileName, "files"); return !std::strcmp(fileName, "desc") || !std::strcmp(fileName, "depends") || !std::strcmp(fileName, "files");
} }
void Database::loadPackages(bool withFiles) void Database::loadPackagesFromConfiguredPaths(bool withFiles, bool force)
{ {
const auto &dbPath = withFiles && !filesPath.empty() ? filesPath : path; const auto &dbPath = withFiles && !filesPath.empty() ? filesPath : path;
if (dbPath.empty()) { if (dbPath.empty()) {
throw runtime_error("local path not configured"); throw runtime_error("local path not configured");
} }
loadPackages(extractFiles(dbPath, &isFileRelevant), lastModified(dbPath)); const auto lastFileUpdate = lastModified(dbPath);
if (force || lastFileUpdate > lastUpdate) {
loadPackages(extractFiles(dbPath, &isFileRelevant), lastFileUpdate);
}
} }
void LibPkg::Database::loadPackages(const string &databaseData, DateTime lastModified) void LibPkg::Database::loadPackages(const string &databaseData, DateTime lastModified)

View File

@ -305,7 +305,7 @@ void ParserTests::testParsingDatabase()
db->filesPath = testFilePath("core.files"); db->filesPath = testFilePath("core.files");
// load packages // load packages
config.loadAllPackages(true); config.loadAllPackages(true, false);
CPPUNIT_ASSERT_EQUAL_MESSAGE("all 215 packages present"s, 215_st, db->packageCount()); CPPUNIT_ASSERT_EQUAL_MESSAGE("all 215 packages present"s, 215_st, db->packageCount());
const auto autoreconf = db->findPackage("autoconf"); const auto autoreconf = db->findPackage("autoconf");
CPPUNIT_ASSERT_MESSAGE("autoreconf exists", autoreconf != nullptr); CPPUNIT_ASSERT_MESSAGE("autoreconf exists", autoreconf != nullptr);

View File

@ -78,7 +78,14 @@ BuildActionMetaInfo::BuildActionMetaInfo()
.category = "Repo management", .category = "Repo management",
.name = "Reload databases", .name = "Reload databases",
.type = "reload-database", .type = "reload-database",
.flags = {}, .flags = {
BuildActionFlagMetaInfo{
.id = static_cast<BuildActionFlagType>(ReloadDatabaseFlags::ForceReload),
.name = "Force reload",
.desc = "Load the database even if its last modification data isn't newer than the last time the database was loaded",
.param = "force-reload",
},
},
.settings = {}, .settings = {},
.directory = false, .directory = false,
.sourceDb = false, .sourceDb = false,

View File

@ -55,6 +55,10 @@ enum class CheckForUpdatesFlags : BuildActionFlagType {
None, None,
ConsiderRegularPackage = (1 << 0), // be consistent with LibPkg::UpdateCheckOptions here ConsiderRegularPackage = (1 << 0), // be consistent with LibPkg::UpdateCheckOptions here
}; };
enum class ReloadDatabaseFlags : BuildActionFlagType {
None,
ForceReload = (1 << 0),
};
enum class ReloadLibraryDependenciesFlags : BuildActionFlagType { enum class ReloadLibraryDependenciesFlags : BuildActionFlagType {
None, None,
ForceReload = (1 << 0), ForceReload = (1 << 0),
@ -179,6 +183,7 @@ inline const BuildActionTypeMetaMapping &BuildActionMetaInfo::mappingForId(Build
} // namespace LibRepoMgr } // namespace LibRepoMgr
CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::ReloadDatabaseFlags)
CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::ReloadLibraryDependenciesFlags) CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::ReloadLibraryDependenciesFlags)
CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::PrepareBuildFlags) CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::PrepareBuildFlags)
CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::ConductBuildFlags) CPP_UTILITIES_MARK_FLAG_ENUM_CLASS(LibRepoMgr, LibRepoMgr::ConductBuildFlags)

View File

@ -28,6 +28,8 @@ ReloadDatabase::ReloadDatabase(ServiceSetup &setup, const std::shared_ptr<BuildA
void ReloadDatabase::run() void ReloadDatabase::run()
{ {
const auto flags = static_cast<ReloadDatabaseFlags>(m_buildAction->flags);
const auto force = flags & ReloadDatabaseFlags::ForceReload;
const auto withFiles = m_setup.building.loadFilesDbs; const auto withFiles = m_setup.building.loadFilesDbs;
vector<LibPkg::Database *> dbsToLoadFromMirror; vector<LibPkg::Database *> dbsToLoadFromMirror;
@ -70,15 +72,26 @@ void ReloadDatabase::run()
continue; continue;
} }
boost::asio::post( boost::asio::post(
m_setup.building.ioContext.get_executor(), [this, session, dbName = db->name, dbArch = db->arch, dbPath = move(dbPath)]() mutable { m_setup.building.ioContext.get_executor(), [this, force, session, dbName = db->name, dbArch = db->arch, dbPath = move(dbPath)]() mutable {
m_buildAction->appendOutput(
Phrases::InfoMessage, "Loading database \"", dbName, '@', dbArch, "\" from local file \"", dbPath, "\"\n");
try { try {
auto dbFileLock = m_setup.locks.acquireToRead(m_buildAction->log(), ServiceSetup::Locks::forDatabase(dbName, dbArch)); auto dbFileLock = m_setup.locks.acquireToRead(m_buildAction->log(), ServiceSetup::Locks::forDatabase(dbName, dbArch));
const auto lastModified = LibPkg::lastModified(dbPath); const auto lastModified = LibPkg::lastModified(dbPath);
if (!force) {
auto configReadLock2 = m_setup.config.lockToRead();
auto *const destinationDb = m_setup.config.findDatabase(dbName, dbArch);
if (const auto lastUpdate = destinationDb->lastUpdate; lastModified <= lastUpdate) {
configReadLock2.unlock();
m_buildAction->appendOutput(Phrases::InfoMessage, "Skip loading database \"", dbName, '@', dbArch,
"\" from local file \"", dbPath, "\"; last modification time <= last update (", lastModified.toString(), '<', '=',
lastUpdate.toString(), ')', '\n');
return;
}
}
auto dbFile = LibPkg::extractFiles(dbPath, &LibPkg::Database::isFileRelevant); auto dbFile = LibPkg::extractFiles(dbPath, &LibPkg::Database::isFileRelevant);
auto packages = LibPkg::Package::fromDatabaseFile(move(dbFile)); auto packages = LibPkg::Package::fromDatabaseFile(move(dbFile));
dbFileLock.lock().unlock(); dbFileLock.lock().unlock();
m_buildAction->appendOutput(
Phrases::InfoMessage, "Loading database \"", dbName, '@', dbArch, "\" from local file \"", dbPath, "\"\n");
const auto configLock = m_setup.config.lockToWrite(); const auto configLock = m_setup.config.lockToWrite();
auto *const destinationDb = m_setup.config.findDatabase(dbName, dbArch); auto *const destinationDb = m_setup.config.findDatabase(dbName, dbArch);
if (!destinationDb) { if (!destinationDb) {
@ -99,7 +112,7 @@ void ReloadDatabase::run()
// query databases // query databases
auto query = WebClient::prepareDatabaseQuery(m_buildAction->log(), dbsToLoadFromMirror, withFiles); auto query = WebClient::prepareDatabaseQuery(m_buildAction->log(), dbsToLoadFromMirror, withFiles);
std::get<std::shared_lock<std::shared_mutex>>(configReadLock).unlock(); std::get<std::shared_lock<std::shared_mutex>>(configReadLock).unlock();
WebClient::queryDatabases(m_buildAction->log(), m_setup, std::move(query.queryParamsForDbs), session); WebClient::queryDatabases(m_buildAction->log(), m_setup, std::move(query.queryParamsForDbs), session, force);
m_preparationFailures = std::move(query.failedDbs); m_preparationFailures = std::move(query.failedDbs);
// clear AUR cache // clear AUR cache

View File

@ -653,7 +653,7 @@ void CleanRepository::run()
std::make_unique<LibPkg::Database>(argsToString("clean-repository-", dbFilePaths.front().stem()), dbFilePaths.front())); std::make_unique<LibPkg::Database>(argsToString("clean-repository-", dbFilePaths.front().stem()), dbFilePaths.front()));
db->arch = dirInfo.canonicalPath.stem(); db->arch = dirInfo.canonicalPath.stem();
db->initStorage(*m_setup.config.storage()); db->initStorage(*m_setup.config.storage());
db->loadPackages(); db->loadPackagesFromConfiguredPaths();
dirInfo.relevantDbs.emplace(db.get()); dirInfo.relevantDbs.emplace(db.get());
// acquire lock for db directory // acquire lock for db directory
dirInfo.lock.emplace<SharedLoggingLock>(m_setup.locks.acquireToRead(m_buildAction->log(), ServiceSetup::Locks::forDatabase(*db))); dirInfo.lock.emplace<SharedLoggingLock>(m_setup.locks.acquireToRead(m_buildAction->log(), ServiceSetup::Locks::forDatabase(*db)));

View File

@ -636,7 +636,7 @@ void ServiceSetup::run()
#endif #endif
loadConfigFiles(true); loadConfigFiles(true);
config.discardDatabases(); config.discardDatabases();
config.loadAllPackages(building.loadFilesDbs); config.loadAllPackages(building.loadFilesDbs, building.forceLoadingDbs);
#ifndef CPP_UTILITIES_DEBUG_BUILD #ifndef CPP_UTILITIES_DEBUG_BUILD
} catch (const std::exception &e) { } catch (const std::exception &e) {
cerr << Phrases::SubError << e.what() << endl; cerr << Phrases::SubError << e.what() << endl;

View File

@ -107,6 +107,7 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable {
std::string testFilesDir; std::string testFilesDir;
BuildPresets presets; BuildPresets presets;
bool loadFilesDbs = false; bool loadFilesDbs = false;
bool forceLoadingDbs = false;
// never changed after startup // never changed after startup
unsigned short threadCount = 4; unsigned short threadCount = 4;
@ -151,7 +152,7 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable {
void restoreLibraryDependenciesFromJson(const std::string &json, ReflectiveRapidJSON::JsonDeserializationErrors *errors); void restoreLibraryDependenciesFromJson(const std::string &json, ReflectiveRapidJSON::JsonDeserializationErrors *errors);
std::size_t restoreState(); std::size_t restoreState();
std::size_t saveState(); std::size_t saveState();
void initStorage(); void initStorage();
void run(); void run();
ServiceStatus computeStatus() const; ServiceStatus computeStatus() const;
}; };

View File

@ -550,7 +550,7 @@ void BuildActionsTests::testConductingBuild()
readFile("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig")); readFile("repos/boost/os/x86_64/boost-libs-1.73.0-1-x86_64.pkg.tar.zst.sig"));
// add packages needing a rebuild to trigger auto-staging // add packages needing a rebuild to trigger auto-staging
m_setup.config.loadAllPackages(false); m_setup.config.loadAllPackages(false, true);
auto *const boostDb = m_setup.config.findDatabase("boost"sv, "x86_64"sv); auto *const boostDb = m_setup.config.findDatabase("boost"sv, "x86_64"sv);
auto *const miscDb = m_setup.config.findDatabase("misc"sv, "x86_64"sv); auto *const miscDb = m_setup.config.findDatabase("misc"sv, "x86_64"sv);
CPPUNIT_ASSERT_MESSAGE("boost database present", boostDb); CPPUNIT_ASSERT_MESSAGE("boost database present", boostDb);
@ -670,7 +670,7 @@ void BuildActionsTests::testCleanup()
auto *const miscDb = m_setup.config.findOrCreateDatabase("misc"sv, std::string_view()); auto *const miscDb = m_setup.config.findOrCreateDatabase("misc"sv, std::string_view());
miscDb->path = repoDir64 / "misc.db"; miscDb->path = repoDir64 / "misc.db";
miscDb->localDbDir = miscDb->localPkgDir = repoDir64; miscDb->localDbDir = miscDb->localPkgDir = repoDir64;
miscDb->loadPackages(); miscDb->loadPackagesFromConfiguredPaths();
// create and run build action // create and run build action
m_buildAction = std::make_shared<BuildAction>(0, &m_setup); m_buildAction = std::make_shared<BuildAction>(0, &m_setup);

View File

@ -345,8 +345,10 @@ void getPackages(const Params &params, ResponseHandler &&handler)
void postLoadPackages(const Params &params, ResponseHandler &&handler) void postLoadPackages(const Params &params, ResponseHandler &&handler)
{ {
const auto withFiles = params.target.hasFlag("with-files");
const auto force = params.target.hasFlag("force");
auto lock = params.setup.config.lockToWrite(); auto lock = params.setup.config.lockToWrite();
params.setup.config.loadAllPackages(params.target.hasFlag("with-files")); params.setup.config.loadAllPackages(withFiles, force);
lock.unlock(); lock.unlock();
handler(makeText(params.request(), "packages loaded")); handler(makeText(params.request(), "packages loaded"));
} }

View File

@ -56,103 +56,138 @@ static int matchToInt(const std::sub_match<const char *> &match)
return stringToNumber<int>(sv); return stringToNumber<int>(sv);
} }
void queryDatabases( static CppUtilities::DateTime parseLastModified(LogContext &log, const auto &message, std::string_view dbName, std::string_view dbArch)
LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&dbQueries, std::shared_ptr<DatabaseQuerySession> &dbQuerySession) {
auto lastModified = DateTime();
const auto lastModifiedHeader = message.find(boost::beast::http::field::last_modified);
if (lastModifiedHeader != message.cend()) {
// parse "Last-Modified" header which should be something like "<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT"
const auto lastModifiedStr = lastModifiedHeader->value();
static const auto lastModifiedPattern = std::regex("..., (\\d\\d) (...) (\\d\\d\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d) GMT");
static const auto months = unordered_map<string, int>{ { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }, { "Apr", 4 }, { "May", 5 }, { "Jun", 6 },
{ "Jul", 7 }, { "Aug", 8 }, { "Sep", 9 }, { "Oct", 10 }, { "Nov", 11 }, { "Dec", 12 } };
try {
auto match = std::cmatch();
if (!std::regex_search(lastModifiedStr.cbegin(), lastModifiedStr.cend(), match, lastModifiedPattern)) {
throw ConversionException("date/time denotation not in expected format");
}
const auto month = months.find(match[2]);
if (month == months.cend()) {
throw ConversionException("month is invalid");
}
lastModified = DateTime::fromDateAndTime(
matchToInt(match[3]), month->second, matchToInt(match[1]), matchToInt(match[4]), matchToInt(match[5]), matchToInt(match[6]));
} catch (const ConversionException &e) {
log(Phrases::WarningMessage, "Unable to parse \"Last-Modified\" header for database ", dbName, '@', dbArch, ": ", e.what(),
" (last modification time was \"", string_view(lastModifiedStr.data(), lastModifiedStr.size()), "\")\n");
}
}
return lastModified;
}
void queryDatabases(LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&dbQueries,
std::shared_ptr<DatabaseQuerySession> &dbQuerySession, bool force)
{ {
for (auto &query : dbQueries) { for (auto &query : dbQueries) {
log(Phrases::InfoMessage, "Retrieving \"", query.databaseName, "\" from mirror: ", query.url, '\n'); log(Phrases::InfoMessage, "Retrieving \"", query.databaseName, "\" from mirror: ", query.url, '\n');
auto session = runSessionFromUrl( auto headHandler
setup.building.ioContext, setup.webServer.sslContext, query.url, = force ? Session::HeadHandler() : [&log, &setup, dbName = query.databaseName, dbArch = query.databaseArch](Session &session3) mutable {
[&log, &setup, dbName = std::move(query.databaseName), dbArch = std::move(query.databaseArch), dbQuerySession]( auto lastModified = parseLastModified(log, session3.headResponse.get(), dbName, dbArch);
Session &session2, const WebClient::HttpClientError &error) mutable { auto configReadLock = setup.config.lockToRead();
if (error.errorCode != boost::beast::errc::success && error.errorCode != boost::asio::ssl::error::stream_truncated) { auto *const destinationDb = setup.config.findDatabase(dbName, dbArch);
log(Phrases::ErrorMessage, "Error retrieving database file \"", session2.destinationFilePath, "\" for ", dbName, ": ", if (!destinationDb) {
error.what(), '\n'); log(Phrases::InfoMessage, "Skip requesting database \"", dbName, '@', dbArch, "\" as it no longer exists\n");
dbQuerySession->addResponse(std::move(dbName)); session3.skip = true;
return; return;
} }
const auto lastUpdate = destinationDb->lastUpdate;
configReadLock.unlock();
if (lastModified > lastUpdate) {
return;
}
log(Phrases::InfoMessage, "Skip requesting database \"", dbName, '@', dbArch,
"\" from mirror; last modification time <= last update (", lastModified.toString(), " <= ", lastUpdate.toString(), ')', '\n');
session3.skip = true;
};
auto handler = [&log, &setup, dbName = std::move(query.databaseName), dbArch = std::move(query.databaseArch), dbQuerySession, force](
Session &session2, const WebClient::HttpClientError &error) mutable {
if (error.errorCode != boost::beast::errc::success && error.errorCode != boost::asio::ssl::error::stream_truncated) {
log(Phrases::ErrorMessage, "Error retrieving database file \"", session2.destinationFilePath, "\" for ", dbName, ": ", error.what(),
'\n');
dbQuerySession->addResponse(std::move(dbName));
return;
}
if (session2.skip) {
return;
}
const auto &response = get<FileResponse>(session2.response); const auto &response = std::get<FileResponse>(session2.response);
const auto &message = response.get(); const auto &message = response.get();
if (message.result() != boost::beast::http::status::ok) { if (message.result() != boost::beast::http::status::ok) {
log(Phrases::ErrorMessage, "Error retrieving database file \"", session2.destinationFilePath, "\" for ", dbName, log(Phrases::ErrorMessage, "Error retrieving database file \"", session2.destinationFilePath, "\" for ", dbName, ": mirror returned ",
": mirror returned ", message.result_int(), " response\n"); message.result_int(), " response\n");
dbQuerySession->addResponse(std::move(dbName)); dbQuerySession->addResponse(std::move(dbName));
return; return;
} }
// find last modification time // log/skip
auto lastModified = DateTime(); auto lastModified = parseLastModified(log, message, dbName, dbArch);
const auto lastModifiedHeader = message.find(boost::beast::http::field::last_modified); if (lastModified.isNull()) {
if (lastModifiedHeader != message.cend()) { log(Phrases::InfoMessage, "Loading database \"", dbName, '@', dbArch, "\" from mirror response\n");
// parse "Last-Modified" header which should be something like "<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT" lastModified = DateTime::gmtNow();
const auto lastModifiedStr = lastModifiedHeader->value(); } else if (!force) {
static const auto lastModifiedPattern = regex("..., (\\d\\d) (...) (\\d\\d\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d) GMT"); auto configReadLock = setup.config.lockToRead();
static const auto months = unordered_map<string, int>{ { "Jan", 1 }, { "Feb", 2 }, { "Mar", 3 }, { "Apr", 4 }, { "May", 5 }, if (auto *const destinationDb = setup.config.findDatabase(dbName, dbArch)) {
{ "Jun", 6 }, { "Jul", 7 }, { "Aug", 8 }, { "Sep", 9 }, { "Oct", 10 }, { "Nov", 11 }, { "Dec", 12 } }; if (const auto lastUpdate = destinationDb->lastUpdate; lastModified <= lastUpdate) {
try { configReadLock.unlock();
cmatch match; log(Phrases::InfoMessage, "Skip loading database \"", dbName, '@', dbArch,
if (!regex_search(lastModifiedStr.cbegin(), lastModifiedStr.cend(), match, lastModifiedPattern)) { "\" from mirror response; last modification time <= last update (", lastModified.toString(),
throw ConversionException("date/time denotation not in expected format"); " <= ", lastUpdate.toString(), ')', '\n');
}
const auto month = months.find(match[2]);
if (month == months.cend()) {
throw ConversionException("month is invalid");
}
lastModified = DateTime::fromDateAndTime(matchToInt(match[3]), month->second, matchToInt(match[1]), matchToInt(match[4]),
matchToInt(match[5]), matchToInt(match[6]));
} catch (const ConversionException &e) {
log(Phrases::WarningMessage, "Unable to parse \"Last-Modified\" header for database ", dbName, ": ", e.what(),
" (last modification time was \"", string_view(lastModifiedStr.data(), lastModifiedStr.size()), "\")\n");
}
}
// log
if (lastModified.isNull()) {
log(Phrases::InfoMessage, "Loading database \"", dbName, "\" from mirror response\n");
lastModified = DateTime::gmtNow();
} else {
log(Phrases::InfoMessage, "Loading database \"", dbName,
"\" from mirror response; last modification time: ", lastModified.toString(), '\n');
}
try {
// load packages
auto files = extractFiles(session2.destinationFilePath, &Database::isFileRelevant);
auto packages = Package::fromDatabaseFile(move(files));
// insert packages
auto lock = setup.config.lockToWrite();
auto db = setup.config.findDatabase(dbName, dbArch);
if (!db) {
log(Phrases::ErrorMessage, "Retrieved database file for \"", dbName, '@', dbArch, "\" but it no longer exists; discarding\n");
return; return;
} }
db->replacePackages(packages, lastModified);
} catch (const std::runtime_error &e) {
log(Phrases::ErrorMessage, "Unable to parse retrieved database file for \"", dbName, '@', dbArch, "\": ", e.what(), '\n');
dbQuerySession->addResponse(std::move(dbName));
} }
}, } else {
move(query.destinationFilePath)); log(Phrases::InfoMessage, "Loading database \"", dbName, '@', dbArch,
"\" from mirror response; last modification time: ", lastModified.toString(), '\n');
}
try {
// load packages
auto files = extractFiles(session2.destinationFilePath, &Database::isFileRelevant);
auto packages = Package::fromDatabaseFile(std::move(files));
// insert packages
auto lock = setup.config.lockToWrite();
auto db = setup.config.findDatabase(dbName, dbArch);
if (!db) {
log(Phrases::ErrorMessage, "Retrieved database file for \"", dbName, '@', dbArch, "\" but it no longer exists; discarding\n");
return;
}
db->replacePackages(packages, lastModified);
} catch (const std::runtime_error &e) {
log(Phrases::ErrorMessage, "Unable to parse retrieved database file for \"", dbName, '@', dbArch, "\": ", e.what(), '\n');
dbQuerySession->addResponse(std::move(dbName));
}
};
auto session = runSessionFromUrl(setup.building.ioContext, setup.webServer.sslContext, query.url, std::move(handler), std::move(headHandler),
std::move(query.destinationFilePath));
} }
} }
std::shared_ptr<DatabaseQuerySession> queryDatabases( std::shared_ptr<DatabaseQuerySession> queryDatabases(
LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&urls, DatabaseQuerySession::HandlerType &&handler) LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&urls, bool force, DatabaseQuerySession::HandlerType &&handler)
{ {
auto dbQuerySession = DatabaseQuerySession::create(setup.building.ioContext, move(handler)); auto dbQuerySession = DatabaseQuerySession::create(setup.building.ioContext, move(handler));
queryDatabases(log, setup, move(urls), dbQuerySession); queryDatabases(log, setup, move(urls), dbQuerySession, force);
return dbQuerySession; return dbQuerySession;
} }
std::shared_ptr<DatabaseQuerySession> queryDatabases(LogContext &log, ServiceSetup &setup, std::shared_lock<shared_mutex> *configReadLock, std::shared_ptr<DatabaseQuerySession> queryDatabases(LogContext &log, ServiceSetup &setup, std::shared_lock<shared_mutex> *configReadLock,
const std::vector<Database *> &dbs, DatabaseQuerySession::HandlerType &&handler) const std::vector<Database *> &dbs, bool force, DatabaseQuerySession::HandlerType &&handler)
{ {
shared_lock<shared_mutex> ownConfigReadLock;
auto query = prepareDatabaseQuery(log, dbs, setup.building.loadFilesDbs); auto query = prepareDatabaseQuery(log, dbs, setup.building.loadFilesDbs);
configReadLock->unlock(); configReadLock->unlock();
return queryDatabases(log, setup, std::move(query.queryParamsForDbs), move(handler)); return queryDatabases(log, setup, std::move(query.queryParamsForDbs), force, move(handler));
} }
PackageCachingSession::PackageCachingSession(PackageCachingDataForSession &data, boost::asio::io_context &ioContext, PackageCachingSession::PackageCachingSession(PackageCachingDataForSession &data, boost::asio::io_context &ioContext,

View File

@ -30,11 +30,11 @@ struct DatabaseQuery {
[[nodiscard]] DatabaseQuery prepareDatabaseQuery(LogContext &log, const std::vector<LibPkg::Database *> &dbs, bool withFiles); [[nodiscard]] DatabaseQuery prepareDatabaseQuery(LogContext &log, const std::vector<LibPkg::Database *> &dbs, bool withFiles);
std::shared_ptr<DatabaseQuerySession> queryDatabases( std::shared_ptr<DatabaseQuerySession> queryDatabases(
LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&urls, DatabaseQuerySession::HandlerType &&handler); LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&urls, bool force, DatabaseQuerySession::HandlerType &&handler);
std::shared_ptr<DatabaseQuerySession> queryDatabases(LogContext &log, ServiceSetup &setup, std::shared_lock<std::shared_mutex> *configReadLock, std::shared_ptr<DatabaseQuerySession> queryDatabases(LogContext &log, ServiceSetup &setup, std::shared_lock<std::shared_mutex> *configReadLock,
const std::vector<LibPkg::Database *> &dbs, DatabaseQuerySession::HandlerType &&handler); const std::vector<LibPkg::Database *> &dbs, bool force, DatabaseQuerySession::HandlerType &&handler);
void queryDatabases( void queryDatabases(LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&urls,
LogContext &log, ServiceSetup &setup, std::vector<DatabaseQueryParams> &&urls, std::shared_ptr<DatabaseQuerySession> &dbQuerySession); std::shared_ptr<DatabaseQuerySession> &dbQuerySession, bool force = false);
struct PackageCachingDataForPackage { struct PackageCachingDataForPackage {
std::string_view url; std::string_view url;

View File

@ -57,10 +57,10 @@ void Session::run(
// set up an HTTP request message // set up an HTTP request message
request.version(version); request.version(version);
request.method(verb);
request.target(target); request.target(target);
request.set(http::field::host, host); request.set(http::field::host, host);
request.set(http::field::user_agent, APP_NAME " " APP_VERSION); request.set(http::field::user_agent, APP_NAME " " APP_VERSION);
method = verb;
// setup a file response // setup a file response
if (!destinationFilePath.empty()) { if (!destinationFilePath.empty()) {
@ -131,6 +131,17 @@ void Session::handshakeDone(boost::beast::error_code ec)
void Session::sendRequest() void Session::sendRequest()
{ {
// send the HTTP request to the remote host // send the HTTP request to the remote host
if (m_headHandler) {
request.method(boost::beast::http::verb::head);
std::visit(
[this](auto &&stream) {
boost::beast::http::async_write(
stream, request, std::bind(&Session::headRequested, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
},
m_stream);
return;
}
request.method(method);
std::visit( std::visit(
[this](auto &&stream) { [this](auto &&stream) {
boost::beast::http::async_write( boost::beast::http::async_write(
@ -139,6 +150,24 @@ void Session::sendRequest()
m_stream); m_stream);
} }
void Session::headRequested(boost::beast::error_code ec, std::size_t bytesTransferred)
{
boost::ignore_unused(bytesTransferred);
if (ec) {
m_handler(*this, HttpClientError("sending HEAD request", ec));
return;
}
// receive the HTTP response
headResponse.skip(true);
std::visit(
[this](auto &stream) {
http::async_read(
stream, m_buffer, headResponse, std::bind(&Session::headReceived, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
},
m_stream);
}
void Session::requested(boost::beast::error_code ec, std::size_t bytesTransferred) void Session::requested(boost::beast::error_code ec, std::size_t bytesTransferred)
{ {
boost::ignore_unused(bytesTransferred); boost::ignore_unused(bytesTransferred);
@ -222,6 +251,22 @@ bool Session::continueReadingChunks()
return true; return true;
} }
void Session::headReceived(boost::beast::error_code ec, std::size_t bytesTransferred)
{
boost::ignore_unused(bytesTransferred);
if (ec) {
m_handler(*this, HttpClientError("receiving HEAD response", ec));
return;
}
m_headHandler(*this);
m_headHandler = HeadHandler();
if (skip) {
sendRequest();
} else {
closeGracefully();
}
}
void Session::received(boost::beast::error_code ec, std::size_t bytesTransferred) void Session::received(boost::beast::error_code ec, std::size_t bytesTransferred)
{ {
boost::ignore_unused(bytesTransferred); boost::ignore_unused(bytesTransferred);
@ -229,8 +274,12 @@ void Session::received(boost::beast::error_code ec, std::size_t bytesTransferred
m_handler(*this, HttpClientError("receiving response", ec)); m_handler(*this, HttpClientError("receiving response", ec));
return; return;
} }
closeGracefully();
}
// close the stream gracefully void Session::closeGracefully()
{
auto ec = boost::beast::error_code();
if (auto *const sslStream = std::get_if<SslStream>(&m_stream)) { if (auto *const sslStream = std::get_if<SslStream>(&m_stream)) {
// perform the SSL handshake // perform the SSL handshake
sslStream->async_shutdown(std::bind(&Session::closed, shared_from_this(), std::placeholders::_1)); sslStream->async_shutdown(std::bind(&Session::closed, shared_from_this(), std::placeholders::_1));
@ -249,6 +298,14 @@ void Session::closed(boost::beast::error_code ec)
std::variant<std::string, std::shared_ptr<Session>> runSessionFromUrl(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, std::variant<std::string, std::shared_ptr<Session>> runSessionFromUrl(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext,
std::string_view url, Session::Handler &&handler, std::string &&destinationPath, std::string_view userName, std::string_view password, std::string_view url, Session::Handler &&handler, std::string &&destinationPath, std::string_view userName, std::string_view password,
boost::beast::http::verb verb, std::optional<std::uint64_t> bodyLimit, Session::ChunkHandler &&chunkHandler) boost::beast::http::verb verb, std::optional<std::uint64_t> bodyLimit, Session::ChunkHandler &&chunkHandler)
{
return runSessionFromUrl(ioContext, sslContext, url, std::move(handler), Session::HeadHandler(), std::move(destinationPath), userName, password,
verb, bodyLimit, std::move(chunkHandler));
}
std::variant<std::string, std::shared_ptr<Session>> runSessionFromUrl(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext,
std::string_view url, Session::Handler &&handler, Session::HeadHandler &&headHandler, std::string &&destinationPath, std::string_view userName,
std::string_view password, boost::beast::http::verb verb, std::optional<std::uint64_t> bodyLimit, Session::ChunkHandler &&chunkHandler)
{ {
std::string host, port, target; std::string host, port, target;
auto ssl = false; auto ssl = false;
@ -284,8 +341,8 @@ std::variant<std::string, std::shared_ptr<Session>> runSessionFromUrl(boost::asi
port = ssl ? "443" : "80"; port = ssl ? "443" : "80";
} }
auto session auto session = ssl ? std::make_shared<Session>(ioContext, sslContext, std::move(handler), std::move(headHandler))
= ssl ? std::make_shared<Session>(ioContext, sslContext, std::move(handler)) : std::make_shared<Session>(ioContext, std::move(handler)); : std::make_shared<Session>(ioContext, std::move(handler), std::move(headHandler));
if (!userName.empty()) { if (!userName.empty()) {
const auto authInfo = userName % ":" + password; const auto authInfo = userName % ":" + password;
session->request.set(boost::beast::http::field::authorization, session->request.set(boost::beast::http::field::authorization,

View File

@ -45,6 +45,7 @@ inline LibRepoMgr::WebClient::HttpClientError::operator bool() const
using Response = WebAPI::Response; using Response = WebAPI::Response;
using FileResponse = boost::beast::http::response_parser<boost::beast::http::file_body>; using FileResponse = boost::beast::http::response_parser<boost::beast::http::file_body>;
using StringResponse = boost::beast::http::response_parser<boost::beast::http::string_body>; using StringResponse = boost::beast::http::response_parser<boost::beast::http::string_body>;
using EmptyResponse = boost::beast::http::response_parser<boost::beast::http::empty_body>;
using MultiResponse = std::variant<Response, FileResponse, StringResponse>; using MultiResponse = std::variant<Response, FileResponse, StringResponse>;
using Request = boost::beast::http::request<boost::beast::http::empty_body>; using Request = boost::beast::http::request<boost::beast::http::empty_body>;
struct ChunkProcessing; struct ChunkProcessing;
@ -52,14 +53,17 @@ struct ChunkProcessing;
class LIBREPOMGR_EXPORT Session : public std::enable_shared_from_this<Session> { class LIBREPOMGR_EXPORT Session : public std::enable_shared_from_this<Session> {
public: public:
using Handler = std::function<void(Session &, const HttpClientError &error)>; using Handler = std::function<void(Session &, const HttpClientError &error)>;
using HeadHandler = std::function<void(Session &)>;
using ChunkHandler = std::function<void(const boost::beast::http::chunk_extensions &chunkExtensions, std::string_view chunkData)>; using ChunkHandler = std::function<void(const boost::beast::http::chunk_extensions &chunkExtensions, std::string_view chunkData)>;
template <typename ResponseType = Response> explicit Session(boost::asio::io_context &ioContext, const Handler &handler = Handler()); template <typename ResponseType = Response> explicit Session(boost::asio::io_context &ioContext, const Handler &handler = Handler());
template <typename ResponseType = Response> explicit Session(boost::asio::io_context &ioContext, Handler &&handler = Handler()); template <typename ResponseType = Response>
explicit Session(boost::asio::io_context &ioContext, Handler &&handler = Handler(), HeadHandler &&headHandler = HeadHandler());
template <typename ResponseType = Response> template <typename ResponseType = Response>
explicit Session(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, const Handler &handler = Handler()); explicit Session(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, const Handler &handler = Handler());
template <typename ResponseType = Response> template <typename ResponseType = Response>
explicit Session(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, Handler &&handler); explicit Session(
boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, Handler &&handler, HeadHandler &&headHandler = HeadHandler());
void setChunkHandler(ChunkHandler &&handler); void setChunkHandler(ChunkHandler &&handler);
void run(const char *host, const char *port, boost::beast::http::verb verb, const char *target, void run(const char *host, const char *port, boost::beast::http::verb verb, const char *target,
@ -83,25 +87,33 @@ private:
void connected(boost::beast::error_code ec); void connected(boost::beast::error_code ec);
void handshakeDone(boost::beast::error_code ec); void handshakeDone(boost::beast::error_code ec);
void sendRequest(); void sendRequest();
void headRequested(boost::beast::error_code ec, std::size_t bytesTransferred);
void requested(boost::beast::error_code ec, std::size_t bytesTransferred); void requested(boost::beast::error_code ec, std::size_t bytesTransferred);
void onChunkHeader(std::uint64_t chunkSize, boost::beast::string_view extensions, boost::beast::error_code &ec); void onChunkHeader(std::uint64_t chunkSize, boost::beast::string_view extensions, boost::beast::error_code &ec);
std::size_t onChunkBody(std::uint64_t bytesLeftInThisChunk, boost::beast::string_view chunkBodyData, boost::beast::error_code &ec); std::size_t onChunkBody(std::uint64_t bytesLeftInThisChunk, boost::beast::string_view chunkBodyData, boost::beast::error_code &ec);
void chunkReceived(boost::beast::error_code ec, std::size_t bytesTransferred); void chunkReceived(boost::beast::error_code ec, std::size_t bytesTransferred);
bool continueReadingChunks(); bool continueReadingChunks();
void headReceived(boost::beast::error_code ec, std::size_t bytesTransferred);
void received(boost::beast::error_code ec, std::size_t bytesTransferred); void received(boost::beast::error_code ec, std::size_t bytesTransferred);
void closeGracefully();
void closed(boost::beast::error_code ec); void closed(boost::beast::error_code ec);
public: public:
Request request; Request request;
MultiResponse response; MultiResponse response;
EmptyResponse headResponse;
std::string destinationFilePath; std::string destinationFilePath;
boost::beast::http::verb method = boost::beast::http::verb::get;
bool skip = false;
private: private:
boost::asio::ip::tcp::resolver m_resolver; boost::asio::ip::tcp::resolver m_resolver;
std::variant<RawSocket, SslStream> m_stream; std::variant<RawSocket, SslStream> m_stream;
boost::beast::flat_buffer m_buffer; boost::beast::flat_buffer m_buffer;
std::unique_ptr<ChunkProcessing> m_chunkProcessing; std::unique_ptr<ChunkProcessing> m_chunkProcessing;
Request m_headRequest;
Handler m_handler; Handler m_handler;
HeadHandler m_headHandler;
}; };
template <typename ResponseType> template <typename ResponseType>
@ -114,11 +126,12 @@ inline Session::Session(boost::asio::io_context &ioContext, const Handler &handl
} }
template <typename ResponseType> template <typename ResponseType>
inline Session::Session(boost::asio::io_context &ioContext, Handler &&handler) inline Session::Session(boost::asio::io_context &ioContext, Handler &&handler, HeadHandler &&headHandler)
: response(ResponseType{}) : response(ResponseType{})
, m_resolver(ioContext) , m_resolver(ioContext)
, m_stream(RawSocket{ ioContext }) , m_stream(RawSocket{ ioContext })
, m_handler(std::move(handler)) , m_handler(std::move(handler))
, m_headHandler(std::move(headHandler))
{ {
} }
@ -132,11 +145,12 @@ inline Session::Session(boost::asio::io_context &ioContext, boost::asio::ssl::co
} }
template <typename ResponseType> template <typename ResponseType>
inline Session::Session(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, Handler &&handler) inline Session::Session(boost::asio::io_context &ioContext, boost::asio::ssl::context &sslContext, Handler &&handler, HeadHandler &&headHandler)
: response(ResponseType{}) : response(ResponseType{})
, m_resolver(ioContext) , m_resolver(ioContext)
, m_stream(SslStream{ ioContext, sslContext }) , m_stream(SslStream{ ioContext, sslContext })
, m_handler(std::move(handler)) , m_handler(std::move(handler))
, m_headHandler(std::move(headHandler))
{ {
} }
@ -145,6 +159,11 @@ LIBREPOMGR_EXPORT std::variant<std::string, std::shared_ptr<Session>> runSession
std::string_view userName = std::string_view(), std::string_view password = std::string_view(), std::string_view userName = std::string_view(), std::string_view password = std::string_view(),
boost::beast::http::verb verb = boost::beast::http::verb::get, std::optional<std::uint64_t> bodyLimit = std::nullopt, boost::beast::http::verb verb = boost::beast::http::verb::get, std::optional<std::uint64_t> bodyLimit = std::nullopt,
Session::ChunkHandler &&chunkHandler = Session::ChunkHandler()); Session::ChunkHandler &&chunkHandler = Session::ChunkHandler());
LIBREPOMGR_EXPORT std::variant<std::string, std::shared_ptr<Session>> runSessionFromUrl(boost::asio::io_context &ioContext,
boost::asio::ssl::context &sslContext, std::string_view url, Session::Handler &&handler, Session::HeadHandler &&headHandler,
std::string &&destinationPath = std::string(), std::string_view userName = std::string_view(), std::string_view password = std::string_view(),
boost::beast::http::verb verb = boost::beast::http::verb::get, std::optional<std::uint64_t> bodyLimit = std::nullopt,
Session::ChunkHandler &&chunkHandler = Session::ChunkHandler());
} // namespace WebClient } // namespace WebClient
} // namespace LibRepoMgr } // namespace LibRepoMgr

View File

@ -92,7 +92,7 @@ int main(int argc, const char *argv[])
} else { } else {
db.filesPath = db.filesPathFromRegularPath(); db.filesPath = db.filesPathFromRegularPath();
} }
db.loadPackages(true); db.loadPackagesFromConfiguredPaths(true, true);
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
std::cerr << "Unable to load database \"" << db.name << "\": " << e.what() << '\n'; std::cerr << "Unable to load database \"" << db.name << "\": " << e.what() << '\n';
} }

View File

@ -30,12 +30,14 @@ int main(int argc, const char *argv[])
OperationArgument runArg("run", 'r', "runs the server"); OperationArgument runArg("run", 'r', "runs the server");
ConfigValueArgument configFileArg("config-file", 'c', "specifies the path of the config file", { "path" }); ConfigValueArgument configFileArg("config-file", 'c', "specifies the path of the config file", { "path" });
configFileArg.setEnvironmentVariable(PROJECT_VARNAME_UPPER "_CONFIG_FILE"); configFileArg.setEnvironmentVariable(PROJECT_VARNAME_UPPER "_CONFIG_FILE");
runArg.setSubArguments({ &configFileArg }); ConfigValueArgument forceLoadingDBsArg("force-loading-dbs", 'f', "forces loading DBs, even if DB files have not been modified since last parse");
runArg.setSubArguments({ &configFileArg, &forceLoadingDBsArg });
runArg.setImplicit(true); runArg.setImplicit(true);
runArg.setCallback([&setup, &configFileArg](const ArgumentOccurrence &) { runArg.setCallback([&setup, &configFileArg, &forceLoadingDBsArg](const ArgumentOccurrence &) {
if (configFileArg.firstValue()) { if (const auto configFilePath = configFileArg.firstValue()) {
setup.configFilePath = configFileArg.firstValue(); setup.configFilePath = configFilePath;
} }
setup.building.forceLoadingDbs = forceLoadingDBsArg.isPresent();
setup.run(); setup.run();
}); });
HelpArgument helpArg(parser); HelpArgument helpArg(parser);