mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 16:00:08 +00:00
Warnings provider to access warnings text products
This commit is contained in:
parent
d5d9285736
commit
a2616b0ee0
7 changed files with 301 additions and 6 deletions
58
test/source/scwx/provider/warnings_provider.test.cpp
Normal file
58
test/source/scwx/provider/warnings_provider.test.cpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#include <scwx/provider/warnings_provider.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace provider
|
||||
{
|
||||
|
||||
static const std::string& kDefaultUrl {"https://warnings.allisonhouse.com"};
|
||||
static const std::string& kAlternateUrl {"http://warnings.cod.edu"};
|
||||
|
||||
class WarningsProviderTest : public testing::TestWithParam<std::string>
|
||||
{
|
||||
};
|
||||
TEST_P(WarningsProviderTest, ListFiles)
|
||||
{
|
||||
WarningsProvider provider(GetParam());
|
||||
|
||||
auto [newObjects, totalObjects] = provider.ListFiles();
|
||||
|
||||
EXPECT_GT(newObjects, 0);
|
||||
EXPECT_GT(totalObjects, 0);
|
||||
EXPECT_EQ(newObjects, totalObjects);
|
||||
}
|
||||
|
||||
TEST_P(WarningsProviderTest, LoadUpdatedFiles)
|
||||
{
|
||||
WarningsProvider provider(GetParam());
|
||||
|
||||
auto [newObjects, totalObjects] = provider.ListFiles();
|
||||
auto updatedFiles = provider.LoadUpdatedFiles();
|
||||
|
||||
EXPECT_GT(newObjects, 0);
|
||||
EXPECT_GT(totalObjects, 0);
|
||||
EXPECT_EQ(newObjects, totalObjects);
|
||||
EXPECT_EQ(updatedFiles.size(), newObjects);
|
||||
|
||||
auto [newObjects2, totalObjects2] = provider.ListFiles();
|
||||
auto updatedFiles2 = provider.LoadUpdatedFiles();
|
||||
|
||||
// There should be no more than 2 updated warnings files since the last query
|
||||
// (assumption that the previous newest file was updated, and a new file was
|
||||
// created on the hour)
|
||||
EXPECT_LE(newObjects2, 2);
|
||||
EXPECT_EQ(updatedFiles2.size(), newObjects2);
|
||||
|
||||
// The total number of objects may have changed, since the oldest file could
|
||||
// have dropped off the list
|
||||
EXPECT_GT(totalObjects2, 0);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(WarningsProvider,
|
||||
WarningsProviderTest,
|
||||
testing::Values(kDefaultUrl, kAlternateUrl));
|
||||
|
||||
} // namespace provider
|
||||
} // namespace scwx
|
||||
|
|
@ -17,7 +17,8 @@ set(SRC_COMMON_TESTS source/scwx/common/color_table.test.cpp
|
|||
source/scwx/common/products.test.cpp)
|
||||
set(SRC_NETWORK_TESTS source/scwx/network/dir_list.test.cpp)
|
||||
set(SRC_PROVIDER_TESTS source/scwx/provider/aws_level2_data_provider.test.cpp
|
||||
source/scwx/provider/aws_level3_data_provider.test.cpp)
|
||||
source/scwx/provider/aws_level3_data_provider.test.cpp
|
||||
source/scwx/provider/warnings_provider.test.cpp)
|
||||
set(SRC_QT_CONFIG_TESTS source/scwx/qt/config/county_database.test.cpp
|
||||
source/scwx/qt/config/radar_site.test.cpp)
|
||||
set(SRC_QT_MANAGER_TESTS source/scwx/qt/manager/settings_manager.test.cpp)
|
||||
|
|
|
|||
36
wxdata/include/scwx/provider/warnings_provider.hpp
Normal file
36
wxdata/include/scwx/provider/warnings_provider.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include <scwx/awips/text_product_file.hpp>
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace provider
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Warnings Provider
|
||||
*/
|
||||
class WarningsProvider
|
||||
{
|
||||
public:
|
||||
explicit WarningsProvider(const std::string& baseUrl);
|
||||
~WarningsProvider();
|
||||
|
||||
WarningsProvider(const WarningsProvider&) = delete;
|
||||
WarningsProvider& operator=(const WarningsProvider&) = delete;
|
||||
|
||||
WarningsProvider(WarningsProvider&&) noexcept;
|
||||
WarningsProvider& operator=(WarningsProvider&&) noexcept;
|
||||
|
||||
std::pair<size_t, size_t>
|
||||
ListFiles(std::chrono::system_clock::time_point newerThan = {});
|
||||
std::vector<std::shared_ptr<awips::TextProductFile>>
|
||||
LoadUpdatedFiles(std::chrono::system_clock::time_point newerThan = {});
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> p;
|
||||
};
|
||||
|
||||
} // namespace provider
|
||||
} // namespace scwx
|
||||
|
|
@ -67,7 +67,7 @@ bool TextProductFile::LoadFile(const std::string& filename)
|
|||
|
||||
bool TextProductFile::LoadData(std::istream& is)
|
||||
{
|
||||
logger_->debug("Loading Data");
|
||||
logger_->trace("Loading Data");
|
||||
|
||||
while (!is.eof())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public:
|
|||
WmoHeader::WmoHeader() : p(std::make_unique<WmoHeaderImpl>()) {}
|
||||
WmoHeader::~WmoHeader() = default;
|
||||
|
||||
WmoHeader::WmoHeader(WmoHeader&&) noexcept = default;
|
||||
WmoHeader::WmoHeader(WmoHeader&&) noexcept = default;
|
||||
WmoHeader& WmoHeader::operator=(WmoHeader&&) noexcept = default;
|
||||
|
||||
bool WmoHeader::operator==(const WmoHeader& o) const
|
||||
|
|
@ -139,7 +139,7 @@ bool WmoHeader::Parse(std::istream& is)
|
|||
|
||||
if (is.eof())
|
||||
{
|
||||
logger_->debug("Reached end of file");
|
||||
logger_->trace("Reached end of file");
|
||||
headerValid = false;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
198
wxdata/source/scwx/provider/warnings_provider.cpp
Normal file
198
wxdata/source/scwx/provider/warnings_provider.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#include <scwx/provider/warnings_provider.hpp>
|
||||
#include <scwx/network/dir_list.hpp>
|
||||
#include <scwx/util/logger.hpp>
|
||||
|
||||
#include <ranges>
|
||||
#include <regex>
|
||||
#include <shared_mutex>
|
||||
|
||||
#pragma warning(push, 0)
|
||||
#define LIBXML_HTML_ENABLED
|
||||
#include <cpr/cpr.h>
|
||||
#include <libxml/HTMLparser.h>
|
||||
#pragma warning(pop)
|
||||
|
||||
namespace scwx
|
||||
{
|
||||
namespace provider
|
||||
{
|
||||
|
||||
static const std::string logPrefix_ = "scwx::provider::warnings_provider";
|
||||
static const auto logger_ = util::Logger::Create(logPrefix_);
|
||||
|
||||
static constexpr std::chrono::seconds kUpdatePeriod_ {15};
|
||||
|
||||
class WarningsProvider::Impl
|
||||
{
|
||||
public:
|
||||
struct FileInfoRecord
|
||||
{
|
||||
std::chrono::system_clock::time_point startTime_ {};
|
||||
std::chrono::system_clock::time_point lastModified_ {};
|
||||
size_t size_ {};
|
||||
bool updated_ {};
|
||||
};
|
||||
|
||||
typedef std::map<std::string, FileInfoRecord> WarningFileMap;
|
||||
|
||||
explicit Impl(const std::string& baseUrl) :
|
||||
baseUrl_ {baseUrl}, files_ {}, filesMutex_ {}
|
||||
{
|
||||
}
|
||||
|
||||
~Impl() {}
|
||||
|
||||
std::string baseUrl_;
|
||||
|
||||
WarningFileMap files_;
|
||||
std::shared_mutex filesMutex_;
|
||||
};
|
||||
|
||||
WarningsProvider::WarningsProvider(const std::string& baseUrl) :
|
||||
p(std::make_unique<Impl>(baseUrl))
|
||||
{
|
||||
}
|
||||
WarningsProvider::~WarningsProvider() = default;
|
||||
|
||||
WarningsProvider::WarningsProvider(WarningsProvider&&) noexcept = default;
|
||||
WarningsProvider&
|
||||
WarningsProvider::operator=(WarningsProvider&&) noexcept = default;
|
||||
|
||||
std::pair<size_t, size_t>
|
||||
WarningsProvider::ListFiles(std::chrono::system_clock::time_point newerThan)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
static const std::regex reWarningsFilename {
|
||||
"warnings_[0-9]{8}_[0-9]{2}.txt"};
|
||||
static const std::string dateTimeFormat {"warnings_%Y%m%d_%H.txt"};
|
||||
|
||||
logger_->trace("Listing files");
|
||||
|
||||
size_t updatedObjects = 0;
|
||||
size_t totalObjects = 0;
|
||||
|
||||
// Perform a directory listing
|
||||
auto records = network::DirList(p->baseUrl_);
|
||||
|
||||
// Sort records by filename
|
||||
std::sort(records.begin(),
|
||||
records.end(),
|
||||
[](auto& a, auto& b) { return a.filename_ < b.filename_; });
|
||||
|
||||
// Filter warning records
|
||||
auto warningRecords =
|
||||
records |
|
||||
std::views::filter(
|
||||
[](auto& record)
|
||||
{
|
||||
return record.type_ == std::filesystem::file_type::regular &&
|
||||
std::regex_match(record.filename_, reWarningsFilename);
|
||||
});
|
||||
|
||||
std::unique_lock lock(p->filesMutex_);
|
||||
|
||||
Impl::WarningFileMap warningFileMap;
|
||||
|
||||
// Store records
|
||||
for (auto& record : warningRecords)
|
||||
{
|
||||
// Determine start time
|
||||
sys_time<hours> startTime;
|
||||
std::istringstream ssFilename {record.filename_};
|
||||
|
||||
ssFilename >> parse(dateTimeFormat, startTime);
|
||||
|
||||
// If start time is valid
|
||||
if (!ssFilename.fail())
|
||||
{
|
||||
// Determine if the record should be marked updated
|
||||
bool updated = true;
|
||||
auto it = p->files_.find(record.filename_);
|
||||
if (it != p->files_.cend())
|
||||
{
|
||||
auto& existingRecord = it->second;
|
||||
|
||||
updated = existingRecord.updated_ ||
|
||||
record.size_ != existingRecord.size_ ||
|
||||
record.mtime_ != existingRecord.lastModified_;
|
||||
}
|
||||
|
||||
// Update object counts, but only if newer than threshold
|
||||
if (newerThan < startTime)
|
||||
{
|
||||
if (updated)
|
||||
{
|
||||
++updatedObjects;
|
||||
}
|
||||
++totalObjects;
|
||||
}
|
||||
|
||||
// Store record
|
||||
warningFileMap.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(record.filename_),
|
||||
std::forward_as_tuple(
|
||||
startTime, record.mtime_, record.size_, updated));
|
||||
}
|
||||
}
|
||||
|
||||
p->files_ = std::move(warningFileMap);
|
||||
|
||||
return std::make_pair(updatedObjects, totalObjects);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<awips::TextProductFile>>
|
||||
WarningsProvider::LoadUpdatedFiles(
|
||||
std::chrono::system_clock::time_point newerThan)
|
||||
{
|
||||
logger_->debug("Loading updated files");
|
||||
|
||||
std::vector<std::shared_ptr<awips::TextProductFile>> updatedFiles;
|
||||
|
||||
std::vector<std::pair<std::string, cpr::AsyncResponse>> asyncResponses;
|
||||
|
||||
std::unique_lock lock(p->filesMutex_);
|
||||
|
||||
// For each warning file
|
||||
for (auto& record : p->files_)
|
||||
{
|
||||
// If file is updated, and time is later than the threshold
|
||||
if (record.second.updated_ && newerThan < record.second.startTime_)
|
||||
{
|
||||
// Retrieve warning file
|
||||
asyncResponses.emplace_back(
|
||||
record.first,
|
||||
cpr::GetAsync(cpr::Url {p->baseUrl_ + "/" + record.first}));
|
||||
|
||||
// Clear updated flag
|
||||
record.second.updated_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
// Wait for warning files to load
|
||||
for (auto& asyncResponse : asyncResponses)
|
||||
{
|
||||
cpr::Response response = asyncResponse.second.get();
|
||||
if (response.status_code == cpr::status::HTTP_OK)
|
||||
{
|
||||
logger_->debug("Loading file: {}", asyncResponse.first);
|
||||
|
||||
// Load file
|
||||
std::shared_ptr<awips::TextProductFile> textProductFile {
|
||||
std::make_shared<awips::TextProductFile>()};
|
||||
std::istringstream responseBody {response.text};
|
||||
if (textProductFile->LoadData(responseBody))
|
||||
{
|
||||
updatedFiles.push_back(textProductFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updatedFiles;
|
||||
}
|
||||
|
||||
} // namespace provider
|
||||
} // namespace scwx
|
||||
|
|
@ -48,12 +48,14 @@ set(HDR_PROVIDER include/scwx/provider/aws_level2_data_provider.hpp
|
|||
include/scwx/provider/aws_level3_data_provider.hpp
|
||||
include/scwx/provider/aws_nexrad_data_provider.hpp
|
||||
include/scwx/provider/nexrad_data_provider.hpp
|
||||
include/scwx/provider/nexrad_data_provider_factory.hpp)
|
||||
include/scwx/provider/nexrad_data_provider_factory.hpp
|
||||
include/scwx/provider/warnings_provider.hpp)
|
||||
set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp
|
||||
source/scwx/provider/aws_level3_data_provider.cpp
|
||||
source/scwx/provider/aws_nexrad_data_provider.cpp
|
||||
source/scwx/provider/nexrad_data_provider.cpp
|
||||
source/scwx/provider/nexrad_data_provider_factory.cpp)
|
||||
source/scwx/provider/nexrad_data_provider_factory.cpp
|
||||
source/scwx/provider/warnings_provider.cpp)
|
||||
set(HDR_UTIL include/scwx/util/environment.hpp
|
||||
include/scwx/util/float.hpp
|
||||
include/scwx/util/hash.hpp
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue