mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 08:00:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			259 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			259 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Prevent redefinition of __cpp_lib_format
 | |
| #if defined(_MSC_VER)
 | |
| #   include <yvals_core.h>
 | |
| #endif
 | |
| 
 | |
| // Enable chrono formatters
 | |
| #ifndef __cpp_lib_format
 | |
| // NOLINTNEXTLINE(bugprone-reserved-identifier, cppcoreguidelines-macro-usage)
 | |
| #   define __cpp_lib_format 202110L
 | |
| #endif
 | |
| 
 | |
| #include <scwx/provider/warnings_provider.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| #include <scwx/util/time.hpp>
 | |
| 
 | |
| #include <mutex>
 | |
| 
 | |
| #if defined(_MSC_VER)
 | |
| #   pragma warning(push, 0)
 | |
| #endif
 | |
| 
 | |
| #define LIBXML_HTML_ENABLED
 | |
| #include <cpr/cpr.h>
 | |
| 
 | |
| #if (__cpp_lib_chrono < 201907L)
 | |
| #   include <date/date.h>
 | |
| #endif
 | |
| 
 | |
| #if defined(_MSC_VER)
 | |
| #   pragma warning(pop)
 | |
| #endif
 | |
| 
 | |
| namespace scwx::provider
 | |
| {
 | |
| 
 | |
| static const std::string logPrefix_ = "scwx::provider::warnings_provider";
 | |
| static const auto        logger_    = util::Logger::Create(logPrefix_);
 | |
| 
 | |
| class WarningsProvider::Impl
 | |
| {
 | |
| public:
 | |
|    struct FileInfoRecord
 | |
|    {
 | |
|       FileInfoRecord(std::string contentLength, std::string lastModified) :
 | |
|           contentLengthStr_ {std::move(contentLength)},
 | |
|           lastModifiedStr_ {std::move(lastModified)}
 | |
|       {
 | |
|       }
 | |
| 
 | |
|       std::string contentLengthStr_ {};
 | |
|       std::string lastModifiedStr_ {};
 | |
|    };
 | |
| 
 | |
|    using WarningFileMap = std::map<std::string, FileInfoRecord>;
 | |
| 
 | |
|    explicit Impl(std::string baseUrl) :
 | |
|        baseUrl_ {std::move(baseUrl)}, files_ {}, filesMutex_ {}
 | |
|    {
 | |
|    }
 | |
| 
 | |
|    ~Impl()                       = default;
 | |
|    Impl(const Impl&)             = delete;
 | |
|    Impl& operator=(const Impl&)  = delete;
 | |
|    Impl(const Impl&&)            = delete;
 | |
|    Impl& operator=(const Impl&&) = delete;
 | |
| 
 | |
|    bool UpdateFileRecord(const cpr::Response& response,
 | |
|                          const std::string&   filename);
 | |
| 
 | |
|    std::string baseUrl_;
 | |
| 
 | |
|    WarningFileMap files_;
 | |
|    std::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::vector<std::shared_ptr<awips::TextProductFile>>
 | |
| WarningsProvider::LoadUpdatedFiles(
 | |
|    std::chrono::sys_time<std::chrono::hours> startTime)
 | |
| {
 | |
|    using namespace std::chrono;
 | |
| 
 | |
| #if (__cpp_lib_chrono >= 201907L)
 | |
|    namespace df = std;
 | |
| 
 | |
|    static constexpr std::string_view kDateTimeFormat {
 | |
|       "warnings_{:%Y%m%d_%H}.txt"};
 | |
| #else
 | |
|    using namespace date;
 | |
|    namespace df = date;
 | |
| 
 | |
| #   define kDateTimeFormat "warnings_%Y%m%d_%H.txt"
 | |
| #endif
 | |
| 
 | |
|    std::vector<
 | |
|       std::pair<std::string,
 | |
|                 cpr::AsyncWrapper<std::optional<cpr::AsyncResponse>, false>>>
 | |
|                                                         asyncCallbacks;
 | |
|    std::vector<std::shared_ptr<awips::TextProductFile>> updatedFiles;
 | |
| 
 | |
|    const std::chrono::sys_time<std::chrono::hours> now =
 | |
|       std::chrono::floor<std::chrono::hours>(std::chrono::system_clock::now());
 | |
|    std::chrono::sys_time<std::chrono::hours> currentHour =
 | |
|       (startTime != std::chrono::sys_time<std::chrono::hours> {}) ?
 | |
|          startTime :
 | |
|          now - std::chrono::hours {1};
 | |
| 
 | |
|    logger_->trace("Querying files newer than: {}", util::TimeString(startTime));
 | |
| 
 | |
|    while (currentHour <= now)
 | |
|    {
 | |
|       const std::string filename = df::format(kDateTimeFormat, currentHour);
 | |
|       const std::string url      = p->baseUrl_ + "/" + filename;
 | |
| 
 | |
|       logger_->trace("HEAD request for file: {}", filename);
 | |
| 
 | |
|       asyncCallbacks.emplace_back(
 | |
|          filename,
 | |
|          cpr::HeadCallback(
 | |
|             [url, filename, this](
 | |
|                cpr::Response headResponse) -> std::optional<cpr::AsyncResponse>
 | |
|             {
 | |
|                if (headResponse.status_code == cpr::status::HTTP_OK)
 | |
|                {
 | |
|                   const bool updated =
 | |
|                      p->UpdateFileRecord(headResponse, filename);
 | |
| 
 | |
|                   if (updated)
 | |
|                   {
 | |
|                      logger_->trace("GET request for file: {}", filename);
 | |
|                      return cpr::GetAsync(cpr::Url {url});
 | |
|                   }
 | |
|                }
 | |
|                else if (headResponse.status_code != cpr::status::HTTP_NOT_FOUND)
 | |
|                {
 | |
|                   logger_->warn("HEAD request for file failed: {} ({})",
 | |
|                                 url,
 | |
|                                 headResponse.status_line);
 | |
|                }
 | |
| 
 | |
|                return std::nullopt;
 | |
|             },
 | |
|             cpr::Url {url}));
 | |
| 
 | |
|       // Query the next hour
 | |
|       currentHour += 1h;
 | |
|    }
 | |
| 
 | |
|    for (auto& asyncCallback : asyncCallbacks)
 | |
|    {
 | |
|       auto& filename = asyncCallback.first;
 | |
|       auto& callback = asyncCallback.second;
 | |
| 
 | |
|       if (callback.valid())
 | |
|       {
 | |
|          // Wait for futures to complete
 | |
|          callback.wait();
 | |
|          auto asyncResponse = callback.get();
 | |
| 
 | |
|          if (asyncResponse.has_value())
 | |
|          {
 | |
|             auto response = asyncResponse.value().get();
 | |
| 
 | |
|             if (response.status_code == cpr::status::HTTP_OK)
 | |
|             {
 | |
|                logger_->debug("Loading file: {}", filename);
 | |
| 
 | |
|                // Load file
 | |
|                const std::shared_ptr<awips::TextProductFile> textProductFile {
 | |
|                   std::make_shared<awips::TextProductFile>()};
 | |
|                std::istringstream responseBody {response.text};
 | |
|                if (textProductFile->LoadData(filename, responseBody))
 | |
|                {
 | |
|                   updatedFiles.push_back(textProductFile);
 | |
|                }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                logger_->warn("Could not load file: {} ({})",
 | |
|                              filename,
 | |
|                              response.status_line);
 | |
|             }
 | |
|          }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->error("Invalid future state");
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return updatedFiles;
 | |
| }
 | |
| 
 | |
| bool WarningsProvider::Impl::UpdateFileRecord(const cpr::Response& response,
 | |
|                                               const std::string&   filename)
 | |
| {
 | |
|    bool updated = false;
 | |
| 
 | |
|    auto contentLengthIt = response.header.find("Content-Length");
 | |
|    auto lastModifiedIt  = response.header.find("Last-Modified");
 | |
| 
 | |
|    std::string contentLength {};
 | |
|    std::string lastModified {};
 | |
| 
 | |
|    if (contentLengthIt != response.header.cend())
 | |
|    {
 | |
|       contentLength = contentLengthIt->second;
 | |
|    }
 | |
|    if (lastModifiedIt != response.header.cend())
 | |
|    {
 | |
|       lastModified = lastModifiedIt->second;
 | |
|    }
 | |
| 
 | |
|    const std::unique_lock lock(filesMutex_);
 | |
| 
 | |
|    auto it = files_.find(filename);
 | |
|    if (it != files_.cend())
 | |
|    {
 | |
|       auto& existingRecord = it->second;
 | |
| 
 | |
|       // If the size or last modified changes, request an update
 | |
| 
 | |
|       if (!contentLength.empty() &&
 | |
|           contentLength != existingRecord.contentLengthStr_)
 | |
|       {
 | |
|          // Size changed
 | |
|          existingRecord.contentLengthStr_ = contentLengthIt->second;
 | |
|          updated                          = true;
 | |
|       }
 | |
|       else if (!lastModified.empty() &&
 | |
|                lastModified != existingRecord.lastModifiedStr_)
 | |
|       {
 | |
|          // Last modified changed
 | |
|          existingRecord.lastModifiedStr_ = lastModifiedIt->second;
 | |
|          updated                         = true;
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       // File not found
 | |
|       files_.emplace(std::piecewise_construct,
 | |
|                      std::forward_as_tuple(filename),
 | |
|                      std::forward_as_tuple(contentLength, lastModified));
 | |
|       updated = true;
 | |
|    }
 | |
| 
 | |
|    return updated;
 | |
| }
 | |
| 
 | |
| } // namespace scwx::provider
 | 
