Audacity 3.2.0
AsyncPluginValidator.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file AsyncPluginValidation.cpp
6
7 @author Vitaly Sverchinsky
8
9 Part of lib-module-manager library
10
11**********************************************************************/
12
14
15#include <optional>
16#include <mutex>
17
18#include "BasicUI.h"
19#include "IPCChannel.h"
20#include "IPCServer.h"
21#include "spinlock.h"
22#include "XMLTagHandler.h"
23
24#include "PluginIPCUtils.h"
25#include "PluginDescriptor.h"
26#include "PluginHost.h"
27#include "XMLFileReader.h"
28
30
33 public std::enable_shared_from_this<Impl>
34{
35 //Variables below are accessed from any thread
36
38 std::optional<wxString> mRequest;
39 std::atomic<std::chrono::system_clock::duration::rep> mLastTimeActive;
40
41 //The main reason to use spinlock instead of std::mutex here
42 //is that in most cases there will be no attempts for simultaneous
43 //data access (except OnConnect/Disconnect cases), because of
44 //protocol design
46
47 //Variable below is accessed only from the main/UI thread
48
50 std::unique_ptr<IPCServer> mServer;
51
52 //Variables below is accessed only from worker threads
53
55
56 //called on main thread only!
57 void StartHost()
58 {
59 auto server = std::make_unique<IPCServer>(*this);
60 if(!PluginHost::Start(server->GetConnectPort()))
61 throw std::runtime_error("cannot start plugin host process");
62 mLastTimeActive = std::chrono::system_clock::now().time_since_epoch().count();
63 mServer = std::move(server);
64 }
65
66 void HandleInternalError(const wxString& msg) noexcept
67 {
68 try
69 {
70 BasicUI::CallAfter([wptr = weak_from_this(), msg]
71 {
72 if(auto self = wptr.lock(); self && self->mDelegate != nullptr)
73 self->mDelegate->OnInternalError(msg);
74 });
75 }
76 catch(...)
77 {
78 //no way to report about a problem, though shouldn't be the case...
79 }
80 }
81
83 {
84 try
85 {
86 BasicUI::CallAfter([wptr = weak_from_this(), result = detail::PluginValidationResult { result }]
87 {
88 if(auto self = wptr.lock())
89 {
90 if(self->mDelegate == nullptr)
91 return;
92
93 //Release the current value to keep state invariant
94 //Caller is free to use Validate now
95 std::optional<wxString> request;
96 {
97 std::lock_guard lck_sync(self->mSync);
98 self->mRequest.swap(request);
99 }
100
101 if(!request.has_value())
102 {
103 //Invalid state error
104 self->mDelegate->OnInternalError(result.GetErrorMessage());
105 return;
106 }
107
108 if(result.IsValid())
109 {
110 for(auto& desc : result.GetDescriptors())
111 self->mDelegate->OnPluginFound(PluginDescriptor { desc });
112 }
113 else
114 {
115 wxString providerId;
116 wxString pluginPath;
117 detail::ParseRequestString(*request, providerId, pluginPath);
118
119 self->mDelegate->OnPluginValidationFailed(providerId, pluginPath);
120 }
121 self->mDelegate->OnValidationFinished();
122 }
123 });
124 }
125 catch(...)
126 {
128 }
129 }
130
131public:
132
133 Impl(Impl&) = delete;
134 Impl(Impl&&) = delete;
135 Impl& operator=(Impl&) = delete;
136 Impl& operator=(Impl&&) = delete;
137
138 Impl(Delegate& delegate) : mDelegate(&delegate) { }
139
140 ~Impl() override
141 {
142 //important to reset delegate before IPCChannelStatusCallback::OnDisconnect
143 //is called by server
144 mDelegate = nullptr;
145 mServer.reset();
146 }
147
148 void SetDelegate(Delegate* delegate)
149 {
150 mDelegate = delegate;
151 }
152
153 std::chrono::system_clock::time_point InactiveSince() const noexcept
154 {
155 using std::chrono::system_clock;
156
157 return system_clock::time_point { system_clock::duration{mLastTimeActive.load()} };
158 }
159
160 void OnConnect(IPCChannel& channel) noexcept override
161 {
162 std::lock_guard lck(mSync);
163
164 mChannel = &channel;
165 if(mRequest)
166 {
167 try
168 {
169 detail::PutMessage(channel, *mRequest);
170 }
171 catch(...)
172 {
173 HandleInternalError("Can't send message to host");
174 }
175 }
176 }
177
178 void OnDisconnect() noexcept override
179 {
180 {
181 std::lock_guard lck(mSync);
182 mChannel = nullptr;
183 }
185 result.SetError("Disconnect");
186 HandleResult(std::move(result));
187 }
188
189 void OnConnectionError() noexcept override
190 {
191 HandleInternalError("Can't connect");
192 }
193
194 void OnDataAvailable(const void* data, size_t size) noexcept override
195 {
196 try
197 {
198 mMessageReader.ConsumeBytes(data, size);
199 mLastTimeActive = std::chrono::system_clock::now().time_since_epoch().count();
200 while(mMessageReader.CanPop())
201 {
202 auto message = mMessageReader.Pop();
203 if(message.IsEmpty())
204 continue;
205
207 XMLFileReader xmlReader;
208 xmlReader.ParseString(&result, message);
209
210 HandleResult(std::move(result));
211 }
212 }
213 catch(...)
214 {
215 HandleInternalError("Can't process response from the host");
216 }
217 }
218
219 void Validate(const wxString& providerId, const wxString& pluginPath)
220 {
221 std::lock_guard lck(mSync);
222
223 //one request at a time
224 assert(!mRequest.has_value());
225
226 mRequest = detail::MakeRequestString(providerId, pluginPath);
227 if(mChannel)
228 detail::PutMessage(*mChannel, *mRequest);
229 else
230 //create host process on demand
231 StartHost();
232 }
233};
234
236{
237 mImpl = std::make_unique<Impl>(delegate);
238}
239
241
242void AsyncPluginValidator::Validate(const wxString& providerId, const wxString& pluginPath)
243{
244 mImpl->Validate(providerId, pluginPath);
245}
246
248{
249 mImpl->SetDelegate(delegate);
250}
251
252std::chrono::system_clock::time_point AsyncPluginValidator::InactiveSince() const noexcept
253{
254 return mImpl->InactiveSince();
255}
256
257
Toolkit-neutral facade for basic user interface services.
Used to talk back to calling side.
void HandleInternalError(const wxString &msg) noexcept
void SetDelegate(Delegate *delegate)
void Validate(const wxString &providerId, const wxString &pluginPath)
void OnDisconnect() noexcept override
Invalidates IPCChannel passed as argument in OnConnect.
void OnConnectionError() noexcept override
Called when connection attempt fails.
void OnDataAvailable(const void *data, size_t size) noexcept override
Called when data chunk received as a result of a preceding call to IPCChannel::Send....
Impl & operator=(Impl &&)=delete
std::unique_ptr< IPCServer > mServer
std::optional< wxString > mRequest
std::atomic< std::chrono::system_clock::duration::rep > mLastTimeActive
void OnConnect(IPCChannel &channel) noexcept override
Called when connection established.
detail::InputMessageReader mMessageReader
Impl & operator=(Impl &)=delete
std::chrono::system_clock::time_point InactiveSince() const noexcept
void HandleResult(detail::PluginValidationResult &&result) noexcept
void Validate(const wxString &providerId, const wxString &pluginPath)
Each call to Validate should result in appropriate call OnValidationFinished, until then it's not all...
void SetDelegate(Delegate *delegate)
std::shared_ptr< Impl > mImpl
std::chrono::system_clock::time_point InactiveSince() const noexcept
AsyncPluginValidator(AsyncPluginValidator &)=delete
Interface for sending data from client to server or vice versa, complemented by IPCChannelStatusCallb...
Definition: IPCChannel.h:22
Interface for listening connection status changes.
Definition: IPCChannel.h:37
static bool Start(int connectPort)
Attempts to start a host application (should be called from the main application)
Definition: PluginHost.cpp:190
Reads a file and passes the results through an XMLTagHandler.
Definition: XMLFileReader.h:19
bool ParseString(XMLTagHandler *baseHandler, const wxString &xmldata)
void SetError(const wxString &msg)
Intended for locking of resources that are only lightly contended and locked for very short times,...
Definition: spinlock.h:22
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:206
bool ParseRequestString(const wxString &req, wxString &providerId, wxString &pluginPath)
void PutMessage(IPCChannel &channel, const wxString &value)
wxString MakeRequestString(const wxString &providerId, const wxString &pluginPath)