mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 19:00:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			212 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <scwx/provider/warnings_provider.hpp>
 | |
| #include <scwx/network/dir_list.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| 
 | |
| #include <ranges>
 | |
| #include <regex>
 | |
| #include <shared_mutex>
 | |
| 
 | |
| #if defined(_MSC_VER)
 | |
| #   pragma warning(push, 0)
 | |
| #endif
 | |
| 
 | |
| #define LIBXML_HTML_ENABLED
 | |
| #include <cpr/cpr.h>
 | |
| #include <libxml/HTMLparser.h>
 | |
| 
 | |
| #if !defined(_MSC_VER)
 | |
| #   include <date/date.h>
 | |
| #endif
 | |
| 
 | |
| #if defined(_MSC_VER)
 | |
| #   pragma warning(pop)
 | |
| #endif
 | |
| 
 | |
| 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;
 | |
| 
 | |
| #if !defined(_MSC_VER)
 | |
|    using namespace date;
 | |
| #endif
 | |
| 
 | |
|    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
 | |
|       std::chrono::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
 | 
