Music Hub ..
A session-wide music playback service
Loading...
Searching...
No Matches
service_skeleton.cpp
Go to the documentation of this file.
1/*
2 * Copyright © 2013-2014 Canonical Ltd.
3 * Copyright © 2022 UBports Foundation.
4 *
5 * Contact: Alberto Mardegan <mardy@users.sourceforge.net>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License version 3,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authored by: Thomas Voß <thomas.voss@canonical.com>
20 * Jim Hodapp <jim.hodapp@canonical.com>
21 */
22
23#include "service_skeleton.h"
24
26#include "mpris.h"
27#include "mpris/media_player2.h"
29#include "player_skeleton.h"
32#include "track_list_skeleton.h"
33
34#include "xesam.h"
35
36#include "logging.h"
37
38#include <QDBusMessage>
39#include <QUuid>
40
42
43using namespace media;
44
45namespace lomiri {
46namespace MediaHubService {
47
50 QString objectPath;
51 QString uuid;
52};
53
54struct OwnerInfo {
55 QString profile;
56 bool isAttached = false;
57 QString serviceName;
58};
59
61{
62 Q_DECLARE_PUBLIC(ServiceSkeleton)
63
64public:
68
70
72 {
73 static unsigned int session_counter = 0;
74
75 const unsigned int current_session = session_counter++;
76
77 return {
78 Player::PlayerKey(current_session),
79 pathForPlayer(current_session),
80 QUuid::createUuid().toString(),
81 };
82 }
83
84 QString pathForPlayer(Player::PlayerKey key) const;
85 void exportPlayer(const SessionInfo &sessionInfo);
86
87 bool playerKeyFromUuid(const QString &uuid, Player::PlayerKey &key) const;
88 bool uuidIsValid(const QString &uuid, Player::PlayerKey &key) const;
89
92 QDBusConnection m_connection;
93 // We map named/fixed player instances to their respective keys.
94 QMap<QString, Player::PlayerKey> named_player_map;
95 // We map UUIDs to their respective keys.
96 QMap<QString, Player::PlayerKey> uuid_player_map;
97 // We keep a list of keys and their respective owners and states.
98 QMap<media::Player::PlayerKey, OwnerInfo> player_owner_map;
100
103};
104
105}} // namespace
106
108 const ServiceSkeleton::Configuration &config,
110 ServiceSkeleton *q):
111 request_context_resolver(QSharedPointer<apparmor::lomiri::DBusDaemonRequestContextResolver>::create()),
112 request_authenticator(QSharedPointer<apparmor::lomiri::ExistingAuthenticator>::create()),
113 m_connection(config.connection),
114 impl(impl),
115 q_ptr(q)
116{
118 q, [this]() { onCurrentPlayerChanged(); });
119 bool ok = m_mprisAdaptor.registerObject();
120 if (!ok) {
121 MH_ERROR() << "Failed to register MPRIS object";
122 }
123}
124
126{
127 Player::PlayerKey key = impl->currentPlayer();
128
129 if (key != Player::invalidKey) {
130 PlayerImplementation *player = impl->playerByKey(key);
131
132 // We only care to allow the MPRIS controls to apply to multimedia player (i.e. audio, video)
134 m_mprisAdaptor.setPlayer(player);
135 }
136 } else {
137 m_mprisAdaptor.setPlayer(nullptr);
138 }
139}
140
142{
143 return "/com/lomiri/MediaHub/Service/sessions/" + QString::number(key);
144}
145
147{
148 Q_Q(ServiceSkeleton);
149
150 PlayerImplementation *player = impl->playerByKey(info.key);
151 if (!player) {
152 MH_ERROR("Requested adaptor for non-existing player %d", info.key);
153 return;
154 }
155
158 player,
161 };
162
163 /* Use the player as parent object to cause automatic
164 * distruction of the adaptor when the player dies.
165 */
166 auto *adaptor = new PlayerSkeleton(conf, player);
167 player->setObjectName(info.objectPath);
168
169 bool ok = adaptor->registerAt(info.objectPath);
170 if (!ok) {
171 MH_ERROR("Failed to export player %d", info.key);
172 return;
173 }
174
175 auto trackList = player->trackList();
176 auto trackListAdaptor =
180 trackList.data(),
181 trackList.data());
182 ok = m_connection.registerObject(trackList->objectName(),
183 trackListAdaptor,
184 QDBusConnection::ExportAllSlots |
185 QDBusConnection::ExportScriptableSignals |
186 QDBusConnection::ExportAllProperties);
187 if (!ok) {
188 MH_ERROR("Failed to export TrackList for %d", info.key);
189 return;
190 }
191}
192
194 Player::PlayerKey &key) const
195{
196 const auto i = uuid_player_map.find(uuid);
197 if (i == uuid_player_map.end()) return false;
198 key = i.value();
199 return true;
200}
201
202bool ServiceSkeletonPrivate::uuidIsValid(const QString &uuid,
203 Player::PlayerKey &key) const
204{
205 bool ok = playerKeyFromUuid(uuid, key);
206 if (!ok) return false;
207 return impl->playerByKey(key) != nullptr;
208}
209
212 QObject *parent):
213 QObject(parent),
214 d_ptr(new ServiceSkeletonPrivate(configuration, impl, this))
215{
216}
217
221
222void ServiceSkeleton::CreateSession(QDBusObjectPath &op, QString &uuid)
223{
224 Q_D(ServiceSkeleton);
225
226 QDBusMessage msg = message();
227 QDBusConnection bus = connection();
228
229 const auto sessionInfo = d->createSessionInfo();
230 const Player::Client client = { sessionInfo.key, msg.service() };
231
232 MH_DEBUG("Session created by request of: %s, key: %d, uuid: %s",
233 qUtf8Printable(client.name), client.key, qUtf8Printable(sessionInfo.uuid));
234
235 try
236 {
237 d->impl->create_session(client);
238 d->uuid_player_map[sessionInfo.uuid] = client.key;
239
240 d->request_context_resolver->resolve_context_for_dbus_name_async(client.name,
241 [this, client](const media::apparmor::lomiri::Context& context)
242 {
243 Q_D(ServiceSkeleton);
244 MH_DEBUG(" -- app_name='%s', attached", qUtf8Printable(context.str()));
245 d->player_owner_map[client.key] = OwnerInfo { context.str(), true, client.name };
246 });
247 } catch(const std::runtime_error& e)
248 {
249 sendErrorReply(
251 e.what());
252 return;
253 }
254
255 d->exportPlayer(sessionInfo);
256 uuid = sessionInfo.uuid;
257 op = QDBusObjectPath(sessionInfo.objectPath);
258}
259
260void ServiceSkeleton::DetachSession(const QString &uuid)
261{
262 Q_D(ServiceSkeleton);
263 try
264 {
266 if (!d->uuidIsValid(uuid, key)) return;
267 if (!d->player_owner_map.contains(key)) return;
268
269 auto &info = d->player_owner_map[key];
270 // Check if session is attached(1) and that the detachment
271 // request comes from the same peer(2) that created the session.
272 if (info.isAttached && info.serviceName == message().service()) { // Player is attached
273 info.isAttached = false; // Detached
274 info.serviceName.clear(); // Clear registered sender/peer
275
276 auto player = d->impl->playerByKey(key);
277 player->setLifetime(Player::Lifetime::resumable);
278 }
279 } catch(const std::runtime_error& e)
280 {
281 sendErrorReply(
283 e.what());
284 }
285}
286
287void ServiceSkeleton::ReattachSession(const QString &uuid)
288{
289 Q_D(ServiceSkeleton);
291 if (!d->uuidIsValid(uuid, key)) {
293 "Invalid session");
294 return;
295 }
296
297 QDBusObjectPath op(d->pathForPlayer(key));
298
299 QDBusMessage msg = message();
300 QDBusConnection bus = connection();
301 msg.setDelayedReply(true);
302
303 try
304 {
305 d->request_context_resolver->resolve_context_for_dbus_name_async(msg.service(),
306 [this, msg, bus, key, op](const media::apparmor::lomiri::Context& context)
307 {
308 Q_D(ServiceSkeleton);
309 auto &info = d->player_owner_map[key];
310 MH_DEBUG(" -- reattach app_name='%s', info='%s', '%s'",
311 qUtf8Printable(context.str()),
312 qUtf8Printable(info.profile),
313 qUtf8Printable(info.serviceName));
314 if (info.profile == context.str()) {
315 info.isAttached = true; // Set to Attached
316 info.serviceName = msg.service(); // Register new owner
317
318 // Signal player reconnection
319 PlayerImplementation *player = d->impl->playerByKey(key);
320 player->reconnect();
321
322 auto reply = msg.createReply();
323 reply << QVariant::fromValue(op);
324
325 bus.send(reply);
326 }
327 else {
328 auto reply = msg.createErrorReply(
329 mpris::Service::Errors::ReattachingSession::name(),
330 "Invalid permissions for the requested session");
331 bus.send(reply);
332 return;
333 }
334 });
335 } catch(const std::runtime_error& e)
336 {
337 sendErrorReply(
339 e.what());
340 }
341}
342
343void ServiceSkeleton::DestroySession(const QString &uuid)
344{
345 Q_D(ServiceSkeleton);
346
348 if (!d->uuidIsValid(uuid, key)) {
350 "Invalid session");
351 return;
352 }
353
354 try
355 {
356 QDBusMessage msg = message();
357 QDBusConnection bus = connection();
358 msg.setDelayedReply(true);
359
360 d->request_context_resolver->resolve_context_for_dbus_name_async(msg.service(),
361 [this, msg, bus, uuid, key](const media::apparmor::lomiri::Context& context)
362 {
363 Q_D(ServiceSkeleton);
364 auto info = d->player_owner_map.value(key);
365 MH_DEBUG(" -- Destroying app_name='%s', info='%s', '%s'",
366 qUtf8Printable(context.str()),
367 qUtf8Printable(info.profile),
368 qUtf8Printable(info.serviceName));
369 if (info.profile == context.str()) {
370 // Remove control entries from the map, at this point
371 // the session is no longer usable.
372 d->uuid_player_map.remove(uuid);
373 d->player_owner_map.remove(key);
374
375 // Reset lifecycle to non-resumable on the now-abandoned session
376 PlayerImplementation *player = d->impl->playerByKey(key);
377
378 // Delete player instance by abandonment
379 player->setLifetime(media::Player::Lifetime::normal);
380 player->abandon();
381
382 bus.send(msg.createReply());
383 }
384 else {
385 auto reply = msg.createErrorReply(
386 mpris::Service::Errors::DestroyingSession::name(),
387 "Invalid permissions for the requested session");
388 bus.send(reply);
389 return;
390 }
391 });
392 } catch(const std::runtime_error& e)
393 {
395 e.what());
396 }
397}
398
399QDBusObjectPath ServiceSkeleton::CreateFixedSession(const QString &name)
400{
401 Q_D(ServiceSkeleton);
402
403 try
404 {
405 if (d->named_player_map.contains(name) == 0) {
406 // Create new session
407 auto sessionInfo = d->createSessionInfo();
408
409 QDBusMessage msg = message();
410 const Player::Client client = { sessionInfo.key, msg.service() };
411 QDBusObjectPath op(sessionInfo.objectPath);
412
413 auto session = d->impl->create_session(client);
414 session->setLifetime(media::Player::Lifetime::resumable);
415
416 d->exportPlayer(sessionInfo);
417 d->named_player_map.insert(name, client.key);
418 return op;
419 } else {
420 // Resume previous session
421 const auto player = d->named_player_map.contains(name) ?
422 d->impl->playerByKey(d->named_player_map[name]) : nullptr;
423 if (not player) {
424 sendErrorReply(
426 "Unable to locate player session");
427 return {};
428 }
429
430 return QDBusObjectPath(d->pathForPlayer(player->key()));
431 }
432 } catch(const std::runtime_error& e)
433 {
435 e.what());
436 }
437 return {};
438}
439
441{
442 Q_D(ServiceSkeleton);
443
444 // FIXME This method does nothing, and never did
445
446 auto player = d->impl->playerByKey(key);
447 if (not player) {
449 "Unable to locate player session");
450 return {};
451 }
452
453 return QDBusObjectPath(d->pathForPlayer(key));
454}
455
457{
458 Q_D(ServiceSkeleton);
459
460 try {
461 d->impl->pause_other_sessions(key);
462 }
463 catch (const std::out_of_range &e) {
464 MH_WARNING("Failed to look up Player instance for key %d\
465 , no valid Player instance for that key value and cannot set current player.\
466 This most likely means that media-hub-server has crashed and restarted.", key);
467 sendErrorReply(
469 "Player key not found");
470 }
471}
QSharedPointer< TrackListImplementation > trackList()
static const PlayerKey invalidKey
Definition player.h:59
QMap< media::Player::PlayerKey, OwnerInfo > player_owner_map
bool uuidIsValid(const QString &uuid, Player::PlayerKey &key) const
media::apparmor::lomiri::RequestContextResolver::Ptr request_context_resolver
void exportPlayer(const SessionInfo &sessionInfo)
media::apparmor::lomiri::RequestAuthenticator::Ptr request_authenticator
QMap< QString, Player::PlayerKey > uuid_player_map
bool playerKeyFromUuid(const QString &uuid, Player::PlayerKey &key) const
ServiceSkeletonPrivate(const ServiceSkeleton::Configuration &config, ServiceImplementation *impl, ServiceSkeleton *q)
QString pathForPlayer(Player::PlayerKey key) const
QMap< QString, Player::PlayerKey > named_player_map
void PauseOtherSessions(Player::PlayerKey key)
void CreateSession(QDBusObjectPath &op, QString &uuid)
ServiceSkeleton(const Configuration &configuration, ServiceImplementation *impl, QObject *parent=nullptr)
QDBusObjectPath ResumeSession(Player::PlayerKey key)
QDBusObjectPath CreateFixedSession(const QString &name)
QSharedPointer< RequestContextResolver > Ptr
Definition lomiri.h:85
#define MH_ERROR(...)
Definition logging.h:41
#define MH_WARNING(...)
Definition logging.h:40
#define MH_DEBUG(...)
Definition logging.h:38
static const QString & name()
Definition mpris.h:90
static const QString & name()
Definition mpris.h:42
static const QString & name()
Definition mpris.h:78
static const QString & name()
Definition mpris.h:54
static const QString & name()
Definition mpris.h:114
static const QString & name()
Definition mpris.h:66
static const QString & name()
Definition mpris.h:102