Music Hub  ..
A session-wide music playback service
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 
25 #include "dbus_property_notifier.h"
26 #include "mpris.h"
27 #include "mpris/media_player2.h"
28 #include "player_implementation.h"
29 #include "player_skeleton.h"
30 #include "service_implementation.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 
41 namespace media = lomiri::MediaHubService;
42 
43 using namespace media;
44 
45 namespace lomiri {
46 namespace MediaHubService {
47 
48 struct SessionInfo {
50  QString objectPath;
51  QString uuid;
52 };
53 
54 struct OwnerInfo {
55  QString profile;
56  bool isAttached = false;
57  QString serviceName;
58 };
59 
61 {
62  Q_DECLARE_PUBLIC(ServiceSkeleton)
63 
64 public:
67  ServiceSkeleton *q);
68 
69  void onCurrentPlayerChanged();
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 
90  media::apparmor::lomiri::RequestContextResolver::Ptr request_context_resolver;
91  media::apparmor::lomiri::RequestAuthenticator::Ptr request_authenticator;
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,
109  ServiceImplementation *impl,
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 {
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)
133  if (player->audioStreamRole() == Player::AudioStreamRole::multimedia) {
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 
157  m_connection,
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 
202 bool 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 
211  ServiceImplementation *impl,
212  QObject *parent):
213  QObject(parent),
214  d_ptr(new ServiceSkeletonPrivate(configuration, impl, this))
215 {
216 }
217 
219 {
220 }
221 
222 void 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 
260 void ServiceSkeleton::DetachSession(const QString &uuid)
261 {
262  Q_D(ServiceSkeleton);
263  try
264  {
265  Player::PlayerKey key;
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 
287 void ServiceSkeleton::ReattachSession(const QString &uuid)
288 {
289  Q_D(ServiceSkeleton);
290  Player::PlayerKey key;
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 
343 void ServiceSkeleton::DestroySession(const QString &uuid)
344 {
345  Q_D(ServiceSkeleton);
346 
347  Player::PlayerKey key;
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 
399 QDBusObjectPath 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 
440 QDBusObjectPath ServiceSkeleton::ResumeSession(Player::PlayerKey key)
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 
456 void ServiceSkeleton::PauseOtherSessions(Player::PlayerKey key)
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 }
lomiri::MediaHubService::ServiceSkeletonPrivate::m_connection
QDBusConnection m_connection
Definition: service_skeleton.cpp:92
mpris.h
QObject
mpris::Service::Errors::PlayerKeyNotFound::name
static const QString & name()
Definition: mpris.h:114
dbus_property_notifier.h
lomiri::MediaHubService::PlayerImplementation
Definition: player_implementation.h:44
lomiri::MediaHubService::ServiceSkeletonPrivate::named_player_map
QMap< QString, Player::PlayerKey > named_player_map
Definition: service_skeleton.cpp:94
mpris::Service::Errors::DestroyingSession::name
static const QString & name()
Definition: mpris.h:78
mpris::MediaPlayer2::setPlayer
void setPlayer(lomiri::MediaHubService::PlayerImplementation *impl)
Definition: media_player2.cpp:343
lomiri::MediaHubService::ServiceSkeletonPrivate::createSessionInfo
SessionInfo createSessionInfo()
Definition: service_skeleton.cpp:71
lomiri::MediaHubService::ServiceSkeletonPrivate::uuidIsValid
bool uuidIsValid(const QString &uuid, Player::PlayerKey &key) const
Definition: service_skeleton.cpp:202
mpris::Service::Errors::DetachingSession::name
static const QString & name()
Definition: mpris.h:54
lomiri::MediaHubService::ServiceSkeleton::~ServiceSkeleton
~ServiceSkeleton()
Definition: service_skeleton.cpp:218
lomiri::MediaHubService::ServiceSkeleton
Definition: service_skeleton.h:41
lomiri::MediaHubService::OwnerInfo::profile
QString profile
Definition: service_skeleton.cpp:55
track_list_skeleton.h
lomiri::MediaHubService::SessionInfo
Definition: service_skeleton.cpp:48
lomiri::MediaHubService::ServiceSkeletonPrivate::m_mprisAdaptor
mpris::MediaPlayer2 m_mprisAdaptor
Definition: service_skeleton.cpp:99
lomiri::MediaHubService::ServiceSkeleton::Configuration
Definition: service_skeleton.h:48
lomiri::MediaHubService::PlayerSkeleton::Configuration
Definition: player_skeleton.h:92
lomiri::MediaHubService::Player::Client
Definition: player.h:61
mpris::Service::Errors::ReattachingSession::name
static const QString & name()
Definition: mpris.h:66
player_implementation.h
lomiri::MediaHubService::SessionInfo::objectPath
QString objectPath
Definition: service_skeleton.cpp:50
lomiri::MediaHubService::ServiceSkeletonPrivate::onCurrentPlayerChanged
void onCurrentPlayerChanged()
Definition: service_skeleton.cpp:125
lomiri::MediaHubService::Player::PlayerKey
uint32_t PlayerKey
Definition: player.h:55
media_player2.h
xesam.h
lomiri::MediaHubService::TrackListSkeleton
Definition: track_list_skeleton.h:44
lomiri::MediaHubService::ServiceSkeletonPrivate::q_ptr
ServiceSkeleton * q_ptr
Definition: service_skeleton.cpp:102
mpris::MediaPlayer2::registerObject
bool registerObject()
Definition: media_player2.cpp:361
lomiri::MediaHubService::ServiceSkeletonPrivate::impl
ServiceImplementation * impl
Definition: service_skeleton.cpp:101
lomiri::MediaHubService::ServiceSkeleton::CreateSession
void CreateSession(QDBusObjectPath &op, QString &uuid)
Definition: service_skeleton.cpp:222
MH_ERROR
#define MH_ERROR(...)
Definition: logging.h:41
lomiri::MediaHubService::PlayerImplementation::trackList
QSharedPointer< TrackListImplementation > trackList()
Definition: player_implementation.cpp:778
lomiri::MediaHubService::ServiceSkeleton::ServiceSkeleton
ServiceSkeleton(const Configuration &configuration, ServiceImplementation *impl, QObject *parent=nullptr)
Definition: service_skeleton.cpp:210
lomiri::MediaHubService::Player::Client::name
QString name
Definition: player.h:63
lomiri::MediaHubService::Player::invalidKey
static const PlayerKey invalidKey
Definition: player.h:59
lomiri::MediaHubService::ServiceSkeletonPrivate::uuid_player_map
QMap< QString, Player::PlayerKey > uuid_player_map
Definition: service_skeleton.cpp:96
lomiri::MediaHubService::ServiceSkeletonPrivate::playerKeyFromUuid
bool playerKeyFromUuid(const QString &uuid, Player::PlayerKey &key) const
Definition: service_skeleton.cpp:193
lomiri::MediaHubService
Definition: context.h:28
lomiri::MediaHubService::ServiceSkeletonPrivate::pathForPlayer
QString pathForPlayer(Player::PlayerKey key) const
Definition: service_skeleton.cpp:141
lomiri::MediaHubService::ServiceSkeletonPrivate::request_authenticator
media::apparmor::lomiri::RequestAuthenticator::Ptr request_authenticator
Definition: service_skeleton.cpp:91
lomiri::MediaHubService::PlayerImplementation::audioStreamRole
Player::AudioStreamRole audioStreamRole() const
Definition: player_implementation.cpp:746
lomiri::MediaHubService::ServiceSkeletonPrivate::player_owner_map
QMap< media::Player::PlayerKey, OwnerInfo > player_owner_map
Definition: service_skeleton.cpp:98
lomiri::MediaHubService::ServiceSkeletonPrivate
Definition: service_skeleton.cpp:60
lomiri::MediaHubService::ServiceImplementation::playerByKey
PlayerImplementation * playerByKey(Player::PlayerKey key) const
Definition: service_implementation.cpp:358
lomiri::MediaHubService::ServiceImplementation::currentPlayerChanged
void currentPlayerChanged()
lomiri::MediaHubService::OwnerInfo
Definition: service_skeleton.cpp:54
lomiri::MediaHubService::PlayerSkeleton
Definition: player_skeleton.h:48
lomiri::MediaHubService::apparmor
Definition: context.h:30
lomiri::MediaHubService::SessionInfo::uuid
QString uuid
Definition: service_skeleton.cpp:51
player_skeleton.h
lomiri::MediaHubService::ServiceImplementation
Definition: service_implementation.h:37
lomiri::MediaHubService::SessionInfo::key
Player::PlayerKey key
Definition: service_skeleton.cpp:49
mpris::MediaPlayer2
Definition: media_player2.h:42
service_skeleton.h
MH_DEBUG
#define MH_DEBUG(...)
Definition: logging.h:38
track_list_implementation.h
mpris::Service::Errors::CreatingFixedSession::name
static const QString & name()
Definition: mpris.h:90
lomiri
Definition: dbus_utils.h:24
lomiri::MediaHubService::OwnerInfo::serviceName
QString serviceName
Definition: service_skeleton.cpp:57
mpris::Service::Errors::CreatingSession::name
static const QString & name()
Definition: mpris.h:42
lomiri::MediaHubService::ServiceSkeletonPrivate::ServiceSkeletonPrivate
ServiceSkeletonPrivate(const ServiceSkeleton::Configuration &config, ServiceImplementation *impl, ServiceSkeleton *q)
Definition: service_skeleton.cpp:107
MH_WARNING
#define MH_WARNING(...)
Definition: logging.h:40
logging.h
lomiri::MediaHubService::ServiceSkeletonPrivate::exportPlayer
void exportPlayer(const SessionInfo &sessionInfo)
Definition: service_skeleton.cpp:146
lomiri::MediaHubService::ServiceSkeletonPrivate::request_context_resolver
media::apparmor::lomiri::RequestContextResolver::Ptr request_context_resolver
Definition: service_skeleton.cpp:90
service_implementation.h
lomiri::MediaHubService::Player::Client::key
PlayerKey key
Definition: player.h:62
lomiri::MediaHubService::ServiceImplementation::currentPlayer
Player::PlayerKey currentPlayer() const
Definition: service_implementation.cpp:371
mpris::Service::Errors::ResumingSession::name
static const QString & name()
Definition: mpris.h:102