Music Hub  ..
A session-wide music playback service
track_list_implementation.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2013-2015 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
19 #include <algorithm>
20 #include <random>
21 #include <sstream>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <tuple>
25 #include <unistd.h>
26 
27 #include <dbus/dbus.h>
28 
30 
31 #include "engine.h"
32 
34 
35 namespace dbus = core::dbus;
36 namespace media = core::ubuntu::media;
37 
39 {
40  typedef std::map<Track::Id, std::tuple<Track::UriType, Track::MetaData>> MetaDataCache;
41 
42  dbus::Object::Ptr object;
43  size_t track_counter;
44  MetaDataCache meta_data_cache;
45  std::shared_ptr<media::Engine::MetaDataExtractor> extractor;
46  // Used for caching the original tracklist order to be used to restore the order
47  // to the live TrackList after shuffle is turned off
49  bool shuffle;
50 
52  {
53  if (meta_data_cache.count(id) == 0)
54  {
55  // FIXME: This code seems to conflict badly when called multiple times in a row: causes segfaults
56 #if 0
57  try {
58  meta_data_cache[id] = std::make_tuple(
59  uri,
60  extractor->meta_data_for_track_with_uri(uri));
61  } catch (const std::runtime_error &e) {
62  std::cerr << "Failed to retrieve metadata for track '" << uri << "' (" << e.what() << ")" << std::endl;
63  }
64 #else
65  meta_data_cache[id] = std::make_tuple(
66  uri,
68 #endif
69  } else
70  {
71  std::get<0>(meta_data_cache[id]) = uri;
72  }
73  }
74 
75  media::TrackList::Container::iterator get_shuffled_insert_it()
76  {
77  media::TrackList::Container::iterator random_it = shuffled_tracks.begin();
78  if (random_it == shuffled_tracks.end())
79  return random_it;
80 
81  // This is slightly biased, but not much, as RAND_MAX >= 32767, which is
82  // much more than the average number of tracks.
83  // Note that for N tracks we have N + 1 possible insertion positions.
84  std::advance(random_it, rand() % (shuffled_tracks.size() + 1));
85  return random_it;
86  }
87 };
88 
90  const dbus::Bus::Ptr& bus,
91  const dbus::Object::Ptr& object,
92  const std::shared_ptr<media::Engine::MetaDataExtractor>& extractor,
93  const media::apparmor::ubuntu::RequestContextResolver::Ptr& request_context_resolver,
94  const media::apparmor::ubuntu::RequestAuthenticator::Ptr& request_authenticator)
95  : media::TrackListSkeleton(bus, object, request_context_resolver, request_authenticator),
98 {
99  can_edit_tracks().set(true);
100 }
101 
103 {
104 }
105 
107 {
108  const auto it = d->meta_data_cache.find(id);
109 
110  if (it == d->meta_data_cache.end())
111  return Track::UriType{};
112 
113  return std::get<0>(it->second);
114 }
115 
117 {
118  const auto it = d->meta_data_cache.find(id);
119 
120  if (it == d->meta_data_cache.end())
121  return Track::MetaData{};
122 
123  return std::get<1>(it->second);
124 }
125 
127  const media::Track::UriType& uri,
128  const media::Track::Id& position,
129  bool make_current)
130 {
131  MH_TRACE("");
132 
133  std::stringstream ss;
134  ss << d->object->path().as_string() << "/" << d->track_counter++;
135  Track::Id id{ss.str()};
136 
137  MH_DEBUG("Adding Track::Id: %s", id);
138  MH_DEBUG("\tURI: %s", uri);
139 
140  const auto current = get_current_track();
141 
142  auto result = tracks().update([this, id, position, make_current](TrackList::Container& container)
143  {
144  auto it = std::find(container.begin(), container.end(), position);
145  container.insert(it, id);
146 
147  return true;
148  });
149 
150  if (result)
151  {
152  d->updateCachedTrackMetadata(id, uri);
153 
154  if (d->shuffle)
155  d->shuffled_tracks.insert(d->get_shuffled_insert_it(), id);
156 
157  if (make_current)
158  {
159  set_current_track(id);
160  go_to(id);
161  } else {
163  }
164 
165  MH_DEBUG("Signaling that we just added track id: %s", id);
166  // Signal to the client that a track was added to the TrackList
167  on_track_added()(id);
168 
169  // Signal to the client that the current track has changed for the first
170  // track added to the TrackList
171  if (tracks().get().size() == 1)
172  on_track_changed()(id);
173  }
174 }
175 
177 {
178  MH_TRACE("");
179 
180  const auto current = get_current_track();
181 
182  Track::Id current_id;
183  ContainerURI tmp;
184  for (const auto uri : uris)
185  {
186  // TODO: Refactor this code to use a smaller common function shared with add_track_with_uri_at()
187  std::stringstream ss;
188  ss << d->object->path().as_string() << "/" << d->track_counter++;
189  Track::Id id{ss.str()};
190  MH_DEBUG("Adding Track::Id: %s", id);
191  MH_DEBUG("\tURI: %s", uri);
192 
193  tmp.push_back(id);
194 
195  Track::Id insert_position = position;
196 
197  auto it = std::find(tracks().get().begin(), tracks().get().end(), insert_position);
198  const auto result = tracks().update([this, id, position, it, &insert_position](TrackList::Container& container)
199  {
200  container.insert(it, id);
201  // Make sure the next insert position is after the current insert position
202  // Update the Track::Id after which to insert the next one from uris
203  insert_position = id;
204 
205  return true;
206  });
207 
208  if (result)
209  {
210  d->updateCachedTrackMetadata(id, uri);
211 
212  if (d->shuffle)
213  d->shuffled_tracks.insert(d->get_shuffled_insert_it(), id);
214 
215  // Signal to the client that the current track has changed for the first track added to the TrackList
216  if (tracks().get().size() == 1)
217  current_id = id;
218  }
219  }
220 
222 
223  MH_DEBUG("Signaling that we just added %d tracks to the TrackList", tmp.size());
224  on_tracks_added()(tmp);
225 
226  if (!current_id.empty())
227  on_track_changed()(current_id);
228 }
229 
231  const media::Track::Id& to)
232 {
233  MH_TRACE("");
234 
235  MH_DEBUG("-----------------------------------------------------");
236  if (id.empty() or to.empty())
237  {
238  MH_ERROR("Can't move track since 'id' or 'to' are empty");
239  return false;
240  }
241 
242  if (id == to)
243  {
244  MH_ERROR("Can't move track to it's same position");
245  return false;
246  }
247 
248  if (tracks().get().size() == 1)
249  {
250  MH_ERROR("Can't move track since TrackList contains only one track");
251  return false;
252  }
253 
254  bool ret = false;
255  const media::Track::Id current_id = *current_iterator();
256  MH_DEBUG("current_track id: %s", current_id);
257  // Get an iterator that points to the track that is the insertion point
258  auto insert_point_it = std::find(tracks().get().begin(), tracks().get().end(), to);
259  if (insert_point_it != tracks().get().end())
260  {
261  const auto result = tracks().update([this, id, to, current_id, &insert_point_it]
262  (TrackList::Container& container)
263  {
264  // Get an iterator that points to the track to move within the TrackList
265  auto to_move_it = std::find(tracks().get().begin(), tracks().get().end(), id);
266  if (to_move_it != tracks().get().end())
267  {
268  container.erase(to_move_it);
269  }
270  else
271  {
272  throw media::TrackList::Errors::FailedToFindMoveTrackDest
273  ("Failed to find destination track " + to);
274  }
275 
276  // Insert id at the location just before insert_point_it
277  container.insert(insert_point_it, id);
278 
279  const auto new_current_track_it = std::find(tracks().get().begin(), tracks().get().end(), current_id);
280  if (new_current_track_it != tracks().get().end())
281  {
282  const bool r = update_current_iterator(new_current_track_it);
283  if (!r)
284  {
285  throw media::TrackList::Errors::FailedToMoveTrack();
286  }
287  MH_DEBUG("*** Updated current_iterator, id: %s", *current_iterator());
288  }
289  else
290  {
291  MH_ERROR("Can't update current_iterator - failed to find track after move");
292  throw media::TrackList::Errors::FailedToMoveTrack();
293  }
294 
295  return true;
296  });
297 
298  if (result)
299  {
300  MH_DEBUG("TrackList after move");
301  for(const auto track : tracks().get())
302  {
303  MH_DEBUG("%s", track);
304  }
305  const media::TrackList::TrackIdTuple ids = std::make_tuple(id, to);
306  // Signal to the client that track 'id' was moved within the TrackList
307  on_track_moved()(ids);
308  ret = true;
309  }
310  }
311  else
312  {
313  throw media::TrackList::Errors::FailedToFindMoveTrackSource
314  ("Failed to find source track " + id);
315  }
316 
317  MH_DEBUG("-----------------------------------------------------");
318 
319  return ret;
320 }
321 
323 {
324  const auto result = tracks().update([id](TrackList::Container& container)
325  {
326  container.erase(std::find(container.begin(), container.end(), id));
327  return true;
328  });
329 
331 
332  if (result)
333  {
334  d->meta_data_cache.erase(id);
335 
336  if (d->shuffle)
337  d->shuffled_tracks.erase(find(d->shuffled_tracks.begin(),
338  d->shuffled_tracks.end(), id));
339 
340  on_track_removed()(id);
341 
342  // Make sure playback stops if all tracks were removed
343  if (tracks().get().empty())
345  }
346 }
347 
349 {
350  MH_TRACE("");
351  // Signal the Player instance to go to a specific track for playback
352  on_go_to_track()(track);
353  on_track_changed()(track);
354 }
355 
357 {
358  d->shuffle = shuffle;
359 
360  if (shuffle) {
361  d->shuffled_tracks = tracks().get();
362  random_shuffle(d->shuffled_tracks.begin(), d->shuffled_tracks.end());
363  }
364 }
365 
367 {
368  return d->shuffle;
369 }
370 
372 {
373  return d->shuffled_tracks;
374 }
375 
377 {
378  MH_TRACE("");
379 
380  // Make sure playback stops
382  // And make sure there is no "current" track
384 
385  tracks().update([this](TrackList::Container& container)
386  {
387  container.clear();
389 
390  d->track_counter = 0;
391  d->shuffled_tracks.clear();
392 
393  return true;
394  });
395 }
const core::Signal< Track::Id > & on_go_to_track() const
void add_track_with_uri_at(const Track::UriType &uri, const Track::Id &position, bool make_current)
media::Track::Id get_current_track(void)
const core::Property< Container > & tracks() const
const core::Signal< Track::Id > & on_track_changed() const
const core::Signal< Track::Id > & on_track_removed() const
#define MH_ERROR(...)
Definition: logger.h:128
Track::UriType query_uri_for_track(const Track::Id &id)
void set_current_track(const media::Track::Id &id)
const core::Signal< void > & on_end_of_tracklist() const
#define MH_DEBUG(...)
Definition: logger.h:123
const core::Signal< ContainerURI > & on_tracks_added() const
TrackListImplementation(const core::dbus::Bus::Ptr &bus, const core::dbus::Object::Ptr &object, const std::shared_ptr< Engine::MetaDataExtractor > &extractor, const core::ubuntu::media::apparmor::ubuntu::RequestContextResolver::Ptr &request_context_resolver, const core::ubuntu::media::apparmor::ubuntu::RequestAuthenticator::Ptr &request_authenticator)
void updateCachedTrackMetadata(const media::Track::Id &id, const media::Track::UriType &uri)
bool update_current_iterator(const TrackList::ConstIterator &it)
const media::TrackList::Container & shuffled_tracks()
const core::Signal< Track::Id > & on_track_added() const
Track::MetaData query_meta_data_for_track(const Track::Id &id)
#define MH_TRACE(...)
Definition: logger.h:121
std::shared_ptr< media::Engine::MetaDataExtractor > extractor
std::tuple< Track::Id, Track::Id > TrackIdTuple
Definition: track_list.h:46
std::string UriType
Definition: track.h:40
std::vector< Track::Id > Container
Definition: track_list.h:43
const core::Signal< TrackIdTuple > & on_track_moved() const
const core::Signal< void > & on_track_list_reset() const
bool move_track(const Track::Id &id, const Track::Id &to)
void add_tracks_with_uri_at(const ContainerURI &uris, const Track::Id &position)
std::map< Track::Id, std::tuple< Track::UriType, Track::MetaData > > MetaDataCache
void go_to(const Track::Id &track)
std::vector< Track::UriType > ContainerURI
Definition: track_list.h:44
const core::Property< bool > & can_edit_tracks() const
const TrackList::ConstIterator & current_iterator()
media::TrackList::Container::iterator get_shuffled_insert_it()