mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-04 06:30:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			483 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			483 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <scwx/qt/manager/placefile_manager.hpp>
 | 
						|
#include <scwx/qt/manager/resource_manager.hpp>
 | 
						|
#include <scwx/qt/util/network.hpp>
 | 
						|
#include <scwx/gr/placefile.hpp>
 | 
						|
#include <scwx/network/cpr.hpp>
 | 
						|
#include <scwx/util/logger.hpp>
 | 
						|
 | 
						|
#include <shared_mutex>
 | 
						|
#include <vector>
 | 
						|
 | 
						|
#include <QDir>
 | 
						|
#include <QGuiApplication>
 | 
						|
#include <QScreen>
 | 
						|
#include <QUrl>
 | 
						|
#include <boost/algorithm/string.hpp>
 | 
						|
#include <boost/asio/post.hpp>
 | 
						|
#include <boost/asio/steady_timer.hpp>
 | 
						|
#include <boost/asio/thread_pool.hpp>
 | 
						|
#include <boost/tokenizer.hpp>
 | 
						|
#include <cpr/cpr.h>
 | 
						|
 | 
						|
namespace scwx
 | 
						|
{
 | 
						|
namespace qt
 | 
						|
{
 | 
						|
namespace manager
 | 
						|
{
 | 
						|
 | 
						|
static const std::string logPrefix_ = "scwx::qt::manager::placefile_manager";
 | 
						|
static const auto        logger_    = scwx::util::Logger::Create(logPrefix_);
 | 
						|
 | 
						|
class PlacefileManager::Impl
 | 
						|
{
 | 
						|
public:
 | 
						|
   class PlacefileRecord;
 | 
						|
 | 
						|
   explicit Impl(PlacefileManager* self) : self_ {self} {}
 | 
						|
   ~Impl() {}
 | 
						|
 | 
						|
   static void LoadResources(const std::shared_ptr<gr::Placefile>& placefile);
 | 
						|
 | 
						|
   boost::asio::thread_pool threadPool_ {1u};
 | 
						|
 | 
						|
   PlacefileManager* self_;
 | 
						|
 | 
						|
   std::shared_ptr<config::RadarSite> radarSite_ {};
 | 
						|
 | 
						|
   std::vector<std::shared_ptr<PlacefileRecord>> placefileRecords_ {};
 | 
						|
   std::unordered_map<std::string, std::shared_ptr<PlacefileRecord>>
 | 
						|
                     placefileRecordMap_ {};
 | 
						|
   std::shared_mutex placefileRecordLock_ {};
 | 
						|
};
 | 
						|
 | 
						|
class PlacefileManager::Impl::PlacefileRecord
 | 
						|
{
 | 
						|
public:
 | 
						|
   explicit PlacefileRecord(Impl*                          impl,
 | 
						|
                            const std::string&             name,
 | 
						|
                            std::shared_ptr<gr::Placefile> placefile,
 | 
						|
                            bool                           enabled = true) :
 | 
						|
       p {impl}, name_ {name}, placefile_ {placefile}, enabled_ {enabled}
 | 
						|
   {
 | 
						|
   }
 | 
						|
   ~PlacefileRecord()
 | 
						|
   {
 | 
						|
      std::unique_lock lock(refreshMutex_);
 | 
						|
      refreshTimer_.cancel();
 | 
						|
   }
 | 
						|
 | 
						|
   void Update();
 | 
						|
   void UpdateAsync();
 | 
						|
   void UpdatePlacefile(const std::shared_ptr<gr::Placefile>& placefile);
 | 
						|
 | 
						|
   Impl* p;
 | 
						|
 | 
						|
   std::string                    name_;
 | 
						|
   std::shared_ptr<gr::Placefile> placefile_;
 | 
						|
   bool                           enabled_;
 | 
						|
   bool                           thresholded_ {false};
 | 
						|
   boost::asio::thread_pool       threadPool_ {1u};
 | 
						|
   boost::asio::steady_timer      refreshTimer_ {threadPool_};
 | 
						|
   std::mutex                     refreshMutex_ {};
 | 
						|
};
 | 
						|
 | 
						|
PlacefileManager::PlacefileManager() : p(std::make_unique<Impl>(this)) {}
 | 
						|
PlacefileManager::~PlacefileManager() = default;
 | 
						|
 | 
						|
bool PlacefileManager::placefile_enabled(const std::string& name)
 | 
						|
{
 | 
						|
   std::shared_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   auto it = p->placefileRecordMap_.find(name);
 | 
						|
   if (it != p->placefileRecordMap_.cend())
 | 
						|
   {
 | 
						|
      return it->second->enabled_;
 | 
						|
   }
 | 
						|
   return false;
 | 
						|
}
 | 
						|
 | 
						|
bool PlacefileManager::placefile_thresholded(const std::string& name)
 | 
						|
{
 | 
						|
   std::shared_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   auto it = p->placefileRecordMap_.find(name);
 | 
						|
   if (it != p->placefileRecordMap_.cend())
 | 
						|
   {
 | 
						|
      return it->second->thresholded_;
 | 
						|
   }
 | 
						|
   return false;
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<gr::Placefile>
 | 
						|
PlacefileManager::placefile(const std::string& name)
 | 
						|
{
 | 
						|
   std::shared_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   auto it = p->placefileRecordMap_.find(name);
 | 
						|
   if (it != p->placefileRecordMap_.cend())
 | 
						|
   {
 | 
						|
      return it->second->placefile_;
 | 
						|
   }
 | 
						|
   return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::set_placefile_enabled(const std::string& name,
 | 
						|
                                             bool               enabled)
 | 
						|
{
 | 
						|
   std::shared_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   auto it = p->placefileRecordMap_.find(name);
 | 
						|
   if (it != p->placefileRecordMap_.cend())
 | 
						|
   {
 | 
						|
      auto record      = it->second;
 | 
						|
      record->enabled_ = enabled;
 | 
						|
 | 
						|
      lock.unlock();
 | 
						|
 | 
						|
      Q_EMIT PlacefileEnabled(name, enabled);
 | 
						|
 | 
						|
      // Update the placefile
 | 
						|
      // TODO: Only update if it's out of date, or if the radar site has changed
 | 
						|
      if (enabled)
 | 
						|
      {
 | 
						|
         it->second->UpdateAsync();
 | 
						|
      }
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::set_placefile_thresholded(const std::string& name,
 | 
						|
                                                 bool               thresholded)
 | 
						|
{
 | 
						|
   std::shared_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   auto it = p->placefileRecordMap_.find(name);
 | 
						|
   if (it != p->placefileRecordMap_.cend())
 | 
						|
   {
 | 
						|
      it->second->thresholded_ = thresholded;
 | 
						|
 | 
						|
      lock.unlock();
 | 
						|
 | 
						|
      Q_EMIT PlacefileUpdated(name);
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::set_placefile_url(const std::string& name,
 | 
						|
                                         const std::string& newUrl)
 | 
						|
{
 | 
						|
   std::string normalizedUrl = util::network::NormalizeUrl(newUrl);
 | 
						|
 | 
						|
   std::unique_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   auto it    = p->placefileRecordMap_.find(name);
 | 
						|
   auto itNew = p->placefileRecordMap_.find(normalizedUrl);
 | 
						|
   if (it != p->placefileRecordMap_.cend() &&
 | 
						|
       itNew == p->placefileRecordMap_.cend())
 | 
						|
   {
 | 
						|
      auto placefileRecord        = it->second;
 | 
						|
      placefileRecord->name_      = normalizedUrl;
 | 
						|
      placefileRecord->placefile_ = nullptr;
 | 
						|
      p->placefileRecordMap_.erase(it);
 | 
						|
      p->placefileRecordMap_.emplace(normalizedUrl, placefileRecord);
 | 
						|
 | 
						|
      lock.unlock();
 | 
						|
 | 
						|
      Q_EMIT PlacefileRenamed(name, normalizedUrl);
 | 
						|
 | 
						|
      // Queue a placefile update
 | 
						|
      placefileRecord->UpdateAsync();
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::SetRadarSite(
 | 
						|
   std::shared_ptr<config::RadarSite> radarSite)
 | 
						|
{
 | 
						|
   if (p->radarSite_ == radarSite || radarSite == nullptr)
 | 
						|
   {
 | 
						|
      // No action needed
 | 
						|
      return;
 | 
						|
   }
 | 
						|
 | 
						|
   logger_->debug("SetRadarSite: {}", radarSite->id());
 | 
						|
 | 
						|
   p->radarSite_ = radarSite;
 | 
						|
 | 
						|
   // Update all enabled records
 | 
						|
   std::shared_lock lock(p->placefileRecordLock_);
 | 
						|
   for (auto& record : p->placefileRecords_)
 | 
						|
   {
 | 
						|
      if (record->enabled_)
 | 
						|
      {
 | 
						|
         record->UpdateAsync();
 | 
						|
      }
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
std::vector<std::shared_ptr<gr::Placefile>>
 | 
						|
PlacefileManager::GetActivePlacefiles()
 | 
						|
{
 | 
						|
   std::vector<std::shared_ptr<gr::Placefile>> placefiles;
 | 
						|
 | 
						|
   std::shared_lock lock {p->placefileRecordLock_};
 | 
						|
 | 
						|
   for (const auto& record : p->placefileRecords_)
 | 
						|
   {
 | 
						|
      if (record->enabled_ && record->placefile_ != nullptr)
 | 
						|
      {
 | 
						|
         placefiles.emplace_back(record->placefile_);
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return placefiles;
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::AddUrl(const std::string& urlString)
 | 
						|
{
 | 
						|
   std::string normalizedUrl = util::network::NormalizeUrl(urlString);
 | 
						|
 | 
						|
   std::unique_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
   // Determine if the placefile has been loaded previously
 | 
						|
   auto it = std::find_if(p->placefileRecords_.begin(),
 | 
						|
                          p->placefileRecords_.end(),
 | 
						|
                          [&normalizedUrl](auto& record)
 | 
						|
                          { return record->name_ == normalizedUrl; });
 | 
						|
   if (it != p->placefileRecords_.end())
 | 
						|
   {
 | 
						|
      logger_->debug("Placefile already added: {}", normalizedUrl);
 | 
						|
      return;
 | 
						|
   }
 | 
						|
 | 
						|
   // Placefile is new, proceed with adding
 | 
						|
   logger_->info("AddUrl: {}", normalizedUrl);
 | 
						|
 | 
						|
   // Add an empty placefile record for the new URL
 | 
						|
   auto& record =
 | 
						|
      p->placefileRecords_.emplace_back(std::make_shared<Impl::PlacefileRecord>(
 | 
						|
         p.get(), normalizedUrl, nullptr, false));
 | 
						|
   p->placefileRecordMap_.insert_or_assign(normalizedUrl, record);
 | 
						|
 | 
						|
   lock.unlock();
 | 
						|
 | 
						|
   Q_EMIT PlacefileUpdated(normalizedUrl);
 | 
						|
 | 
						|
   // Queue a placefile update
 | 
						|
   record->UpdateAsync();
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::LoadFile(const std::string& filename)
 | 
						|
{
 | 
						|
   const std::string placefileName =
 | 
						|
      QDir::toNativeSeparators(QString::fromStdString(filename)).toStdString();
 | 
						|
 | 
						|
   logger_->debug("LoadFile: {}", placefileName);
 | 
						|
 | 
						|
   boost::asio::post(
 | 
						|
      p->threadPool_,
 | 
						|
      [placefileName, this]()
 | 
						|
      {
 | 
						|
         // Load file
 | 
						|
         std::shared_ptr<gr::Placefile> placefile =
 | 
						|
            gr::Placefile::Load(placefileName);
 | 
						|
 | 
						|
         if (placefile == nullptr)
 | 
						|
         {
 | 
						|
            return;
 | 
						|
         }
 | 
						|
 | 
						|
         std::unique_lock lock(p->placefileRecordLock_);
 | 
						|
 | 
						|
         // Determine if the placefile has been loaded previously
 | 
						|
         auto it = p->placefileRecordMap_.find(placefileName);
 | 
						|
         if (it != p->placefileRecordMap_.end())
 | 
						|
         {
 | 
						|
            // If the placefile has been loaded previously, update it
 | 
						|
            it->second->UpdatePlacefile(placefile);
 | 
						|
 | 
						|
            lock.unlock();
 | 
						|
 | 
						|
            Q_EMIT PlacefileUpdated(placefileName);
 | 
						|
         }
 | 
						|
         else
 | 
						|
         {
 | 
						|
            // If this is a new placefile, add it
 | 
						|
            auto& record = p->placefileRecords_.emplace_back(
 | 
						|
               std::make_shared<Impl::PlacefileRecord>(
 | 
						|
                  p.get(), placefileName, placefile));
 | 
						|
            p->placefileRecordMap_.insert_or_assign(placefileName, record);
 | 
						|
 | 
						|
            lock.unlock();
 | 
						|
 | 
						|
            Q_EMIT PlacefileEnabled(placefileName, record->enabled_);
 | 
						|
            Q_EMIT PlacefileUpdated(placefileName);
 | 
						|
         }
 | 
						|
      });
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::Impl::PlacefileRecord::Update()
 | 
						|
{
 | 
						|
   logger_->debug("Update: {}", name_);
 | 
						|
 | 
						|
   // Make a copy of name in the event it changes.
 | 
						|
   const std::string name {name_};
 | 
						|
 | 
						|
   std::shared_ptr<gr::Placefile> updatedPlacefile {};
 | 
						|
 | 
						|
   QUrl url = QUrl::fromUserInput(QString::fromStdString(name));
 | 
						|
   if (url.isLocalFile())
 | 
						|
   {
 | 
						|
      updatedPlacefile = gr::Placefile::Load(name);
 | 
						|
   }
 | 
						|
   else
 | 
						|
   {
 | 
						|
      std::string decodedUrl {name};
 | 
						|
      auto        queryPos = decodedUrl.find('?');
 | 
						|
      if (queryPos != std::string::npos)
 | 
						|
      {
 | 
						|
         decodedUrl.erase(queryPos);
 | 
						|
      }
 | 
						|
 | 
						|
      if (p->radarSite_ == nullptr)
 | 
						|
      {
 | 
						|
         // Wait to process until a radar site is selected
 | 
						|
         return;
 | 
						|
      }
 | 
						|
 | 
						|
      auto dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
 | 
						|
 | 
						|
      // Specify parameters
 | 
						|
      auto parameters = cpr::Parameters {
 | 
						|
         {"version", "1.2"}, // Placefile Version Supported
 | 
						|
         {"dpi", fmt::format("{:0.0f}", dpi)},
 | 
						|
         {"lat", fmt::format("{:0.3f}", p->radarSite_->latitude())},
 | 
						|
         {"lon", fmt::format("{:0.3f}", p->radarSite_->longitude())}};
 | 
						|
 | 
						|
      // Iterate through each query parameter in the URL
 | 
						|
      if (url.hasQuery())
 | 
						|
      {
 | 
						|
         auto query = url.query(QUrl::ComponentFormattingOption::FullyEncoded)
 | 
						|
                         .toStdString();
 | 
						|
 | 
						|
         boost::char_separator<char> delimiter("&");
 | 
						|
         boost::tokenizer            tokens(query, delimiter);
 | 
						|
 | 
						|
         for (auto& token : tokens)
 | 
						|
         {
 | 
						|
            std::vector<std::string> split {};
 | 
						|
            boost::split(split, token, boost::is_any_of("="));
 | 
						|
            if (split.size() >= 2)
 | 
						|
            {
 | 
						|
               // Token is a key=value parameter
 | 
						|
               parameters.Add({split[0], split[1]});
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
               // Token is a single key with no value
 | 
						|
               parameters.Add({token, {}});
 | 
						|
            }
 | 
						|
         }
 | 
						|
      }
 | 
						|
 | 
						|
      // Send HTTP GET request
 | 
						|
      auto response =
 | 
						|
         cpr::Get(cpr::Url {decodedUrl}, network::cpr::GetHeader(), parameters);
 | 
						|
 | 
						|
      if (cpr::status::is_success(response.status_code))
 | 
						|
      {
 | 
						|
         std::istringstream responseBody {response.text};
 | 
						|
         updatedPlacefile = gr::Placefile::Load(name, responseBody);
 | 
						|
      }
 | 
						|
      else if (response.status_code == 0)
 | 
						|
      {
 | 
						|
         logger_->error("Error loading placefile: {}", response.error.message);
 | 
						|
      }
 | 
						|
      else
 | 
						|
      {
 | 
						|
         logger_->error("Error loading placefile: {}", response.status_line);
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   if (updatedPlacefile != nullptr)
 | 
						|
   {
 | 
						|
      // Load placefile resources
 | 
						|
      Impl::LoadResources(updatedPlacefile);
 | 
						|
 | 
						|
      // Check the name matches, in case the name updated
 | 
						|
      if (name_ == name)
 | 
						|
      {
 | 
						|
         // Update the placefile
 | 
						|
         placefile_ = updatedPlacefile;
 | 
						|
 | 
						|
         // Notify slots of the placefile update
 | 
						|
         Q_EMIT p->self_->PlacefileUpdated(name);
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   // TODO: Update refresh timer
 | 
						|
   // TODO: Can running this function out of sync with an existing refresh timer
 | 
						|
   // cause issues?
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::Impl::PlacefileRecord::UpdateAsync()
 | 
						|
{
 | 
						|
   boost::asio::post(threadPool_, [this]() { Update(); });
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::Impl::PlacefileRecord::UpdatePlacefile(
 | 
						|
   const std::shared_ptr<gr::Placefile>& placefile)
 | 
						|
{
 | 
						|
   // Update placefile
 | 
						|
   placefile_ = placefile;
 | 
						|
 | 
						|
   // TODO: Update refresh timer
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<PlacefileManager> PlacefileManager::Instance()
 | 
						|
{
 | 
						|
   static std::weak_ptr<PlacefileManager> placefileManagerReference_ {};
 | 
						|
   static std::mutex                      instanceMutex_ {};
 | 
						|
 | 
						|
   std::unique_lock lock(instanceMutex_);
 | 
						|
 | 
						|
   std::shared_ptr<PlacefileManager> placefileManager =
 | 
						|
      placefileManagerReference_.lock();
 | 
						|
 | 
						|
   if (placefileManager == nullptr)
 | 
						|
   {
 | 
						|
      placefileManager           = std::make_shared<PlacefileManager>();
 | 
						|
      placefileManagerReference_ = placefileManager;
 | 
						|
   }
 | 
						|
 | 
						|
   return placefileManager;
 | 
						|
}
 | 
						|
 | 
						|
void PlacefileManager::Impl::LoadResources(
 | 
						|
   const std::shared_ptr<gr::Placefile>& placefile)
 | 
						|
{
 | 
						|
   const auto iconFiles = placefile->icon_files();
 | 
						|
 | 
						|
   const QUrl baseUrl =
 | 
						|
      QUrl::fromUserInput(QString::fromStdString(placefile->name()));
 | 
						|
 | 
						|
   std::vector<std::string> urlStrings;
 | 
						|
   urlStrings.reserve(iconFiles.size());
 | 
						|
 | 
						|
   std::transform(iconFiles.cbegin(),
 | 
						|
                  iconFiles.cend(),
 | 
						|
                  std::back_inserter(urlStrings),
 | 
						|
                  [&baseUrl](auto& iconFile)
 | 
						|
                  {
 | 
						|
                     // Resolve target URL relative to base URL
 | 
						|
                     QUrl fileUrl =
 | 
						|
                        QUrl(QString::fromStdString(iconFile->filename_));
 | 
						|
                     QUrl resolvedUrl = baseUrl.resolved(fileUrl);
 | 
						|
 | 
						|
                     return resolvedUrl.toString().toStdString();
 | 
						|
                  });
 | 
						|
 | 
						|
   ResourceManager::LoadImageResources(urlStrings);
 | 
						|
}
 | 
						|
 | 
						|
} // namespace manager
 | 
						|
} // namespace qt
 | 
						|
} // namespace scwx
 |