4#include "Stroika/Frameworks/StroikaPreComp.h"
10#include "Stroika/Foundation/Containers/Mapping.h"
11#include "Stroika/Foundation/Containers/Sequence.h"
12#include "Stroika/Foundation/Execution/Exceptions.h"
27using Memory::MakeSharedPtr;
29using namespace Stroika::Frameworks;
30using namespace Stroika::Frameworks::UPnP;
31using namespace Stroika::Frameworks::UPnP::SSDP;
32using namespace Stroika::Frameworks::UPnP::SSDP::Client;
39class CachingListener::Rep_ {
44 SSDP::Advertisement fAdvertisement;
50 Rep_ (
const Options& options)
52 , fListener_{[this] (const SSDP::Advertisement& a) { Heard_ (a,
false); }, options.fListenerOptions}
54 if (options.fSearchFor) {
55 fSearch_.emplace ([
this] (
const SSDP::Advertisement& a) { Heard_ (a,
true); },
57 .fMulticastTTL = options.fMulticastTTL,
58 .fInterfaces = options.fListenerOptions.fInterfaces,
59 .fFollowNetworkChanges = options.fListenerOptions.fFollowNetworkChanges});
64 IgnoreExceptionsForCall (Stop ());
66 CallbackID AddOnChangeCallback (
const function<
void (
const SSDP::Advertisement& d)>& callOnChanges)
68 return fCallbacks_.Add (callOnChanges);
70 void RemoveOnChangeCallback (CallbackID callOnChanges)
72 fCallbacks_.Remove (callOnChanges);
76 return fListener_.GetNetworkInterfaces ();
83 result += i.fValue.fAdvertisement;
90 [[maybe_unused]] lock_guard lifecycle{fLifecycleMutex_};
92 [[maybe_unused]] lock_guard noting{fNoting_};
93 Require (not fStarted_);
95 CheckExpiriesWhenDue_ ();
99 fSearch_->Start (*fOptions_.fSearchFor, fOptions_.fSearchRepeatInterval);
104 [[maybe_unused]] lock_guard lifecycle{fLifecycleMutex_};
113 [[maybe_unused]] lock_guard noting{fNoting_};
115 checks = exchange (fChecks_, {});
119 IntervalTimer::Manager::sThe.
Remove (c);
126 void Heard_ (
const SSDP::Advertisement& heard,
bool searchAnswer)
128 const String& usn = heard.fUSN;
132 [[maybe_unused]] lock_guard noting{fNoting_};
133 if (searchAnswer or heard.fAlive ==
true) {
134 if (searchAnswer and MaybeSentBeforeByebye_ (heard)) {
137 SSDP::Advertisement a = heard;
140 fSoonestExpiry_ = fSoonestExpiry_ ? min (*fSoonestExpiry_, expiresAt) : expiresAt;
141 CheckExpiriesWhenDue_ ();
144 auto cache = fCache_.rwget ();
145 Locations_ locations = cache->LookupValue (usn);
146 optional<Entry_> e = locations.Lookup (a.fLocation);
147 added = not e.has_value ();
151 e->fAdvertisement = a;
152 e->fHeardOn.Add (a.fReceivedOn, expiresAt);
153 locations.Add (a.fLocation, *e);
154 cache->Add (usn, locations);
157 fCallbacks_.Call (a);
160 else if (heard.fAlive ==
false) {
165 const String device = DeviceOf_ (usn);
166 fByebyes_.Add (device, Time::GetTickCount () + kAnswerMaybeStaleFor_);
169 auto cache = fCache_.rwget ();
171 cache->Where ([&] (const
KeyValuePair<
String, Locations_>& i) {
return DeviceOf_ (i.fKey) == device; })) {
175 if (heard.fReceivedOn) {
176 e.fHeardOn.RemoveIf (heard.fReceivedOn);
177 e.fHeardOn.RemoveIf (nullopt);
180 e.fHeardOn.RemoveAll ();
182 if (e.fHeardOn.empty ()) {
186 keptHere.Add (l.fKey, e);
189 if (keptHere.empty ()) {
190 cache->Remove (u.fKey);
193 cache->Add (u.fKey, keptHere);
197 for (
const SSDP::Advertisement& a : gone) {
198 fCallbacks_.Call (a);
206 [[maybe_unused]] lock_guard noting{fNoting_};
213 IntervalTimer::Manager::sThe.Remove (c);
215 fChecks_.RemoveAll ();
219 if (fSoonestExpiry_ and *fSoonestExpiry_ <= now) {
221 fSoonestExpiry_ = nullopt;
226 e.fHeardOn.RemoveAll (
228 if (e.fHeardOn.empty ()) {
229 expired += Gone_ (e);
232 keptHere.Add (i.fKey, e);
234 fSoonestExpiry_ = fSoonestExpiry_ ? min (*fSoonestExpiry_, h.fValue) : h.fValue;
238 if (not keptHere.empty ()) {
239 kept.
Add (usn.fKey, keptHere);
242 fCache_.store (kept);
244 CheckExpiriesWhenDue_ ();
245 for (
const SSDP::Advertisement& a : expired) {
246 fCallbacks_.Call (a);
252 void CheckExpiriesWhenDue_ ()
254 if (fStarted_ and fSoonestExpiry_ and (not fCheckAt_ or *fSoonestExpiry_ < *fCheckAt_)) {
255 fCheckAt_ = *fSoonestExpiry_;
256 fChecks_ += IntervalTimer::Manager::sThe.AddOneShot ([
this] () { Expire_ (); },
272 bool MaybeSentBeforeByebye_ (
const SSDP::Advertisement& answer)
276 if (not fByebyes_.ContainsKey (DeviceOf_ (answer.fUSN))) {
279 optional<Entry_> held = fCache_.cget ()->LookupValue (answer.fUSN).Lookup (answer.fLocation);
280 return not held or not held->fHeardOn.ContainsKey (answer.fReceivedOn);
286 optional<size_t> i = usn.
Find (
"::"sv);
290 static SSDP::Advertisement Gone_ (
const Entry_& e)
292 SSDP::Advertisement a = e.fAdvertisement;
298 const Options fOptions_;
299 mutex fLifecycleMutex_;
304 optional<Time::TimePointSeconds> fSoonestExpiry_;
306 optional<Time::TimePointSeconds> fCheckAt_;
308 bool fStarted_{
false};
311 optional<Search> fSearch_;
319CachingListener::CachingListener (
const Options& options)
320 : fRep_{MakeSharedPtr<Rep_> (options)}
352 return fRep_->AddOnChangeCallback (callOnChanges);
357 fRep_->RemoveOnChangeCallback (callOnChanges);
362 return fRep_->GetNetworkInterfaces ();
367 return fRep_->GetAdvertisements ();
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual String SubString(SZ from) const
nonvirtual optional< size_t > Find(Character c, CompareOptions co=eWithCase) const
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
a cross between Mapping<KEY, T> and Collection<T> and Set<T>
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void Remove(size_t i)
The callbacks an object calls when something happens - each added, and removed by the ID Add gave it ...
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
A Listener that remembers: the SSDP advertisements in force around it - each kept from its ssdp:alive...
nonvirtual CallbackID AddOnChangeCallback(const function< void(const SSDP::Advertisement &d)> &callOnChanges)
Call callOnChanges too, with each change from now on - what it holds already, GetAdvertisements () re...
CachingListener(const Options &options=kDefaultOptions)
nonvirtual Containers::Collection< SSDP::Advertisement > GetAdvertisements() const
The advertisements in force: each added, and not yet removed - fAlive true. Safe to call from any thr...
nonvirtual IO::Network::InterfacesByID GetNetworkInterfaces() const
The network interfaces it is listening on now (.
nonvirtual void RemoveOnChangeCallback(CallbackID callOnChanges)
Once this returns, that callback is not running, and is never called again - except that,...
Listener::CallbackID CallbackID
Names a callback AddOnChangeCallback added, for RemoveOnChangeCallback - Listener's.
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...
IO::Network::InternetProtocol::IP::IPVersionSupport fIPVersion