mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 09:20:06 +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) |                      source/scwx/common/products.test.cpp) | ||||||
| set(SRC_NETWORK_TESTS source/scwx/network/dir_list.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 | 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 | set(SRC_QT_CONFIG_TESTS source/scwx/qt/config/county_database.test.cpp | ||||||
|                         source/scwx/qt/config/radar_site.test.cpp) |                         source/scwx/qt/config/radar_site.test.cpp) | ||||||
| set(SRC_QT_MANAGER_TESTS source/scwx/qt/manager/settings_manager.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) | bool TextProductFile::LoadData(std::istream& is) | ||||||
| { | { | ||||||
|    logger_->debug("Loading Data"); |    logger_->trace("Loading Data"); | ||||||
| 
 | 
 | ||||||
|    while (!is.eof()) |    while (!is.eof()) | ||||||
|    { |    { | ||||||
|  |  | ||||||
|  | @ -139,7 +139,7 @@ bool WmoHeader::Parse(std::istream& is) | ||||||
| 
 | 
 | ||||||
|    if (is.eof()) |    if (is.eof()) | ||||||
|    { |    { | ||||||
|       logger_->debug("Reached end of file"); |       logger_->trace("Reached end of file"); | ||||||
|       headerValid = false; |       headerValid = false; | ||||||
|    } |    } | ||||||
|    else |    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_level3_data_provider.hpp | ||||||
|                  include/scwx/provider/aws_nexrad_data_provider.hpp |                  include/scwx/provider/aws_nexrad_data_provider.hpp | ||||||
|                  include/scwx/provider/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 | set(SRC_PROVIDER source/scwx/provider/aws_level2_data_provider.cpp | ||||||
|                  source/scwx/provider/aws_level3_data_provider.cpp |                  source/scwx/provider/aws_level3_data_provider.cpp | ||||||
|                  source/scwx/provider/aws_nexrad_data_provider.cpp |                  source/scwx/provider/aws_nexrad_data_provider.cpp | ||||||
|                  source/scwx/provider/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 | set(HDR_UTIL include/scwx/util/environment.hpp | ||||||
|              include/scwx/util/float.hpp |              include/scwx/util/float.hpp | ||||||
|              include/scwx/util/hash.hpp |              include/scwx/util/hash.hpp | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat