1 /// 2 /// Playlist / Artist / Album / Song data types. 3 /// @file musictypes.h - pianod 4 /// @author Perette Barella 5 /// @date 2014-12-09 6 /// @copyright Copyright (c) 2014-2020 Devious Fish. All rights reserved. 7 /// 8 9 #pragma once 10 11 #include <config.h> 12 13 #include <string> 14 #include <stack> 15 #include <vector> 16 #include <list> 17 #include <unordered_map> 18 #include <type_traits> 19 20 #include "logging.h" 21 #include "ownership.h" 22 #include "ratings.h" 23 24 namespace Media { 25 class Source; 26 class Player; 27 enum class SelectionMethod; 28 } 29 30 #include "fundamentals.h" 31 #include "ownership.h" 32 33 class Filter; 34 class SongList; 35 class PianodConnection; 36 37 class MusicThingie; 38 class PianodArtist; 39 class PianodAlbum; 40 class PianodSong; 41 class PianodPlaylist; 42 43 class MusicAutoReleasePool : private std::stack<MusicThingie *> { 44 friend class MusicThingie; 45 MusicAutoReleasePool *previousPool; add(MusicThingie * item)46 inline void add (MusicThingie *item) { 47 push (item); 48 } 49 void unadd (MusicThingie *item); 50 public: 51 MusicAutoReleasePool (); 52 ~MusicAutoReleasePool (); 53 }; 54 55 /** Base class for songs, albums, artists, playlists, genres, etc. 56 This class is reference counted using an autorelease pool; 57 the pool must be periodically released (such as at end end of 58 the main run loop) to free things up. 59 60 This offers a simplistic but effective and efficient garbage 61 collection scheme. For more information, see Cocoa/NextStep. 62 63 @warning This class and derived ones must be dynamically allocated. 64 Static, global or automatic (stack) objects will be released 65 improperly and deleted, which is inappropriate. */ 66 class MusicThingie : public SubordinateOwnership { 67 friend class MusicAutoReleasePool; 68 private: 69 mutable short useCount = 1; 70 static MusicAutoReleasePool *releasePool; 71 protected: 72 // Non-public destructor prevents allocation outside heap (C++PL/4 525) 73 virtual ~MusicThingie (void); 74 public: 75 enum class Type: char { 76 Playlist = 'p', 77 Artist = 'a', 78 Album = 'l', 79 Song = 's', 80 81 PlaylistSuggestion = 'e', 82 ArtistSuggestion = 't', 83 AlbumSuggestion = 'b', 84 SongSuggestion = 'n', 85 86 PlaylistSeed = 'y', 87 ArtistSeed = 'd', 88 AlbumSeed = 'u', 89 SongSeed = 'g', 90 91 SongRating = 'i' 92 }; 93 static std::string TypeName (Type type); 94 95 MusicThingie (void); 96 isPrimary(Type t)97 static inline constexpr bool isPrimary (Type t) { 98 return (t == Type::Playlist || 99 t == Type::Artist || 100 t == Type::Album || 101 t == Type::Song); 102 } isPrimary(void)103 inline bool isPrimary (void) const { 104 return isPrimary (type()); 105 } isSuggestion(const Type t)106 static inline constexpr bool isSuggestion (const Type t) { 107 return (t == Type::PlaylistSuggestion || 108 t == Type::ArtistSuggestion || 109 t == Type::AlbumSuggestion || 110 t == Type::SongSuggestion); 111 } isSuggestion(void)112 inline bool isSuggestion (void) const { 113 return isSuggestion (type()); 114 } isSeed(const Type t)115 static inline constexpr bool isSeed (const Type t) { 116 return (t == Type::PlaylistSeed || 117 t == Type::ArtistSeed || 118 t == Type::AlbumSeed || 119 t == Type::SongSeed || 120 t == Type::SongRating); 121 } isSeed(void)122 inline bool isSeed (void) const { 123 return isSeed (type()); 124 } 125 isPlaylist(const Type t)126 static inline constexpr bool isPlaylist (const Type t) { 127 return (t == Type::Playlist || 128 t == Type::PlaylistSuggestion || 129 t == Type::PlaylistSeed); 130 } isPlaylist(void)131 inline bool isPlaylist (void) const { 132 return isPlaylist (type()); 133 } isSong(const Type t)134 static inline constexpr bool isSong (const Type t) { 135 return (t == Type::Song || 136 t == Type::SongSuggestion || 137 t == Type::SongSeed || 138 t == Type::SongRating); 139 } isSong(void)140 inline bool isSong (void) const { 141 return isSong (type()); 142 } isAlbum(const Type t)143 static inline constexpr bool isAlbum (const Type t) { 144 return (t == Type::Album || 145 t == Type::AlbumSuggestion || 146 t == Type::AlbumSeed); 147 } isAlbum(void)148 inline bool isAlbum (void) const { 149 return isAlbum (type()); 150 } isArtist(const Type t)151 static inline constexpr bool isArtist (const Type t) { 152 return (t == Type::Artist || 153 t == Type::ArtistSuggestion || 154 t == Type::ArtistSeed); 155 } isArtist(void)156 inline bool isArtist (void) const { 157 return isArtist (type()); 158 } isValidType(const Type t)159 static inline constexpr bool isValidType (const Type t) { 160 return (isSong (t) || isAlbum (t) || isArtist (t) || isPlaylist (t)); 161 }; isValidType(void)162 inline bool isValidType (void) const { 163 return isValidType (type()); 164 } 165 static Type primaryType (const Type t); primaryType(void)166 inline Type primaryType (void) const { 167 return primaryType (type()); 168 } 169 170 // Speed up and ease typecasting asArtist()171 virtual PianodArtist *asArtist () { return nullptr; } asAlbum()172 virtual PianodAlbum *asAlbum () { return nullptr; } asSong()173 virtual PianodSong *asSong () { return nullptr; } asPlaylist()174 virtual PianodPlaylist *asPlaylist () { return nullptr; } asArtist()175 virtual const PianodArtist *asArtist () const { return nullptr; } asAlbum()176 virtual const PianodAlbum *asAlbum () const { return nullptr; } asSong()177 virtual const PianodSong *asSong () const { return nullptr; } asPlaylist()178 virtual const PianodPlaylist *asPlaylist () const { return nullptr; } 179 180 std::string operator()(void) const; 181 182 /// Claim an instance. retain(void)183 inline void retain (void) const { useCount++; }; 184 /// Abandon an instance. 185 #ifndef NDEBUG 186 inline 187 #endif release(void)188 void release (void) { 189 assert (useCount > 0); 190 #ifndef NDEBUG 191 flog (LOG_ALLOCS, "Releasing ", (*this)(), ", useCount=", useCount - 1); 192 #else 193 if (useCount == 1) { 194 flog (LOG_ALLOCS, "Deleting ", (*this)()); 195 } 196 #endif 197 if (--useCount == 0) delete this; 198 }; 199 static void autorelease (void); getUseCount(void)200 inline int getUseCount (void) const { return useCount; }; 201 202 /// Defer the ownership to the source 203 virtual Ownership *parentOwner (void) const; 204 /// MediaSource from which this thingie originates. 205 virtual Media::Source *const source (void) const = 0; 206 /** Get the primary id of this thingie. If this is an artist, the artist ID; 207 if an album, the album ID; etc. */ 208 virtual const std::string id (void) const = 0; 209 /** Return the inner ID when used in a specific context */ 210 virtual const std::string &id (Type type) const = 0; 211 /** Return the most specific name of this, whatever type it is. */ 212 virtual const std::string &name (void) const = 0; 213 /** Return the type letter for a thingie. This is used as the first letter 214 of the ID, so IDs can be routed to the appropriate dataset/handler. */ 215 virtual Type type (void) const = 0; 216 /** Transmit the thingie's data on a connection. */ 217 virtual PianodConnection &transmit (PianodConnection &recipient) const = 0; 218 /** Check if a filter matches this item */ 219 virtual bool matches (const Filter &filter) const = 0; 220 /** Check if the primary name of this thingie matches. If this is an artist, 221 compares to the artist name; if an album, the album name; etc. */ 222 virtual bool operator==(const std::string &compare) const = 0; 223 /** Compare a thingie's name to another of the equivalent or decendent type. 224 @return Whether primary names are the same. 225 If compared object is not equivalent or decendent, returns false., */ 226 virtual bool operator==(const MusicThingie &compare) const = 0; 227 inline bool operator !=(const std::string &compare) { 228 return !(*this == compare); 229 } 230 inline bool operator !=(const MusicThingie &compare) { 231 return !(*this == compare); 232 } 233 virtual SongList songs (); 234 }; 235 236 /** Base class for storing lists of thingies, which need to be 237 reference counted accurately. Copy constructor and assignment 238 operations herein correctly manage these things. Provides fundamental 239 operations for manipulating lists, though default base class of list 240 may not be efficient for direct access to items. If this is 241 frequent, use another base list type. */ 242 // Strage mechanism for all thingies, which need to be reference counted. 243 template <class Content, class Base = std::list<Content> > class AnyThingieList : protected Base { 244 public: 245 using value_type = Content; 246 using list_type = Base; 247 using this_type = AnyThingieList<Content, Base>; 248 249 using Base::empty; 250 using Base::size; 251 using Base::begin; 252 using Base::end; 253 using Base::front; 254 using Base::back; 255 using typename Base::size_type; 256 257 AnyThingieList () = default; 258 259 /** Copy-construct items into this list. Let the base constructor 260 deal with copying, so it optimizes for efficiency; afterward we 261 just need to increment the reference counts. 262 @param list The list to construct with. */ AnyThingieList(const this_type & list)263 AnyThingieList (const this_type &list) : Base (list) { 264 for (auto item : list) { 265 item->retain(); 266 } 267 }; 268 269 /** Move-construct items into this list. 270 @param list The list to construct with. */ AnyThingieList(this_type && list)271 AnyThingieList (this_type &&list) : Base (std::move (list)) { 272 assert (list.empty()); 273 }; 274 275 /// Replace items in this list with those copied from another list. 276 const AnyThingieList<Content, Base> &operator =(const this_type &list) { 277 // Do work from back of list for vector efficiency. 278 clear(); 279 copy (list.begin(), list.end(), back_inserter (*this)); 280 return *this; 281 }; 282 283 /// Replace items in this list with those moved in from another list. 284 const AnyThingieList<Content, Base> &operator =(this_type &&list) { 285 // Clear from back of list for vector efficiency. 286 clear(); 287 Base::operator= (std::move (list)); 288 assert (list.empty()); 289 return *this; 290 }; 291 292 /// Release items when being destroyed. ~AnyThingieList(void)293 ~AnyThingieList (void) { 294 clear(); 295 } 296 297 /// Clear out the contents clear(void)298 void clear (void) { 299 while (!empty()) { 300 pop_back(); 301 } 302 } 303 push_back(Content add)304 void push_back(Content add) { 305 add->retain(); 306 this->insert (end(), add); 307 } 308 push_front(Content add)309 void push_front (Content add) { 310 add->retain(); 311 this->insert (begin(), add); 312 } 313 pop_back(void)314 void pop_back (void) { 315 assert (!empty()); 316 back()->release(); 317 list_type::pop_back(); 318 } 319 pop_front(void)320 void pop_front (void) { 321 front()->release(); 322 this->erase (begin()); 323 } 324 325 /** Append another thingielist to this one. 326 @param from The list to append. */ join(this_type & from)327 void join (this_type &from) { 328 this->insert (end(), from.begin(), from.end()); 329 from.list_type::clear(); 330 } 331 332 /** Purge any items from a particular source from the list. 333 @param source The source whose items to remove. */ purge(const Media::Source * source)334 bool purge (const Media::Source *source) { 335 bool changed = false; 336 auto it = begin(); 337 while (it != end()) { 338 if (source == (*it)->source()) { 339 (*it)->release(); 340 it = this->erase (it); 341 changed = true; 342 } else { 343 it++; 344 } 345 } 346 return changed; 347 } 348 }; 349 350 /** A container for holding any kinds of MusicThingies: 351 artists, albums, songs or playlists. Disparate types 352 may be co-mingled in the container. */ 353 class ThingieList : public AnyThingieList <MusicThingie *> { 354 public: 355 ThingieList () = default; 356 ThingieList (const ThingieList &) = default; 357 ThingieList (ThingieList &&) = default; 358 ThingieList &operator =(const ThingieList &) = default; 359 ThingieList &operator =(ThingieList &&) = default; 360 ThingieList (const SongList &songs); 361 void limitTo (MusicThingie::Type type); 362 }; 363 364 365 366 /* 367 * Artists 368 */ 369 370 /** Base class for artists, derived from MusicThingies. */ 371 class PianodArtist : public MusicThingie { 372 public: 373 virtual const std::string &artistId (void) const = 0; ///< Item's artist ID. 374 virtual const std::string id (void) const override; ///< Get primary ID with media manager ID on it 375 virtual const std::string &id (Type type) const override; ///< Get ID for a context name(void)376 virtual const std::string &name (void) const override { return artist(); }; type(void)377 virtual Type type (void) const override { return Type::Artist; }; ///< Typecode for artist objects. typetype(void)378 static inline Type typetype (void) { return Type::Artist; }; ///< Typecode for artist type. 379 virtual PianodConnection &transmit (PianodConnection &recipient) const override; 380 virtual bool matches (const Filter &filter) const override; asArtist()381 virtual PianodArtist *asArtist () override final { return this; } asArtist()382 virtual const PianodArtist *asArtist () const override final { return this; } 383 384 virtual const std::string &artist (void) const = 0; ///< Get artist name. 385 386 /** See if the artist has a certain name. 387 @return True/false. Uses fuzzy logic, skipping 'a'/'an'/'the' and adjusting 388 for /first last/ to /last, first/ */ 389 virtual bool operator==(const std::string &compare) const override; 390 /** Compare artist's name to that of an artist/album/song. 391 @return True/false. Uses fuzzy logic, skipping 'a'/'an'/'the' and adjusting 392 for /first last/ to /last, first/ */ 393 virtual bool operator==(const MusicThingie &compare) const override; 394 }; 395 396 397 /* 398 * Albums 399 */ 400 401 /** Base class for albums, these are also MusicThingies and artists. */ 402 class PianodAlbum : public PianodArtist { 403 public: 404 virtual const std::string &albumId (void) const = 0; ///< Item's album ID 405 virtual const std::string id (void) const override; ///< Primary ID is album ID. 406 virtual const std::string &id (Type type) const override; ///< Get ID for a context name(void)407 virtual const std::string &name (void) const override { return albumTitle(); }; type(void)408 virtual Type type (void) const override { return Type::Album; }; ///< Typecode for album objects. typetype(void)409 static inline Type typetype (void) { return Type::Album; }; ///< Typecode for album type. 410 virtual PianodConnection &transmit (PianodConnection &recipient) const override; 411 virtual bool matches (const Filter &filter) const override; asAlbum()412 virtual PianodAlbum *asAlbum () override final { return this; } asAlbum()413 virtual const PianodAlbum *asAlbum () const override final { return this; } 414 415 virtual const std::string &albumTitle (void) const = 0; 416 virtual const std::string &coverArtUrl(void) const = 0; compilation()417 virtual bool compilation () const { return false; }; ///< Is album a compilation? 418 virtual bool operator==(const std::string &compare) const override; 419 virtual bool operator==(const MusicThingie &compare) const override; 420 }; 421 422 423 /* 424 * Songs 425 */ 426 427 /** Base class for songs, these are also MusicThingies, artists and albums. */ 428 class PianodSong : public PianodAlbum { 429 protected: 430 time_t expiration = 0; ///< Time at which a queued item becomes invalid, or 0 if good forever 431 time_t last_played = 0; ///< Time song was last played, 0 if never or unknown. 432 public: 433 virtual const std::string &songId (void) const = 0; 434 virtual const std::string id (void) const override; 435 virtual const std::string &id (Type type) const override; ///< Get ID for a context name(void)436 virtual const std::string &name (void) const override { return title(); }; type(void)437 virtual Type type (void) const override { return Type::Song; }; typetype(void)438 static inline Type typetype (void) { return Type::Song; }; 439 virtual PianodConnection &transmit (PianodConnection &recipient) const override; 440 virtual bool matches (const Filter &filter) const override; asSong()441 virtual PianodSong *asSong () override final { return this; } asSong()442 virtual const PianodSong *asSong () const override final { return this; } 443 expires(void)444 inline bool expires (void) { return expiration != 0; }; expired(void)445 inline bool expired (void) { return expires() && expiration < time (NULL); }; 446 /** Get time a song last played. 447 @return Timestamp, or 0 if it has never played/last play is unknown. */ lastPlayed(void)448 inline time_t lastPlayed (void) const { return last_played; }; lastPlayed(time_t t)449 inline void lastPlayed (time_t t) { last_played = t; }; 450 451 virtual const std::string &title (void) const = 0; ///< Get the song's title 452 virtual PianodPlaylist *playlist (void) const = 0; ///< Get a playlist instance 453 virtual const std::string &playlistName (void) const; 454 virtual const std::string &genre (void) const = 0; ///< Get genre of this song 455 virtual const std::string &infoUrl (void) const; 456 /** Get track number (sequence number within an album/original media). 457 @return Track number, or 0 if unknown. */ 458 virtual int trackNumber (void) const = 0; 459 /** Duration of song in seconds. 460 @return Play time of song, or 0 if unknown */ 461 virtual int duration (void) const = 0; 462 /** Year of release of song. 463 @return Year, or 0 if unknown. */ 464 virtual int year (void) const = 0; 465 ratingScheme(void)466 virtual RatingScheme ratingScheme (void) const { return RatingScheme::Nobody; }; 467 /// Rate a song. 468 /// @param value The rating to assign. 469 /// @param user The user making the rating; use depends on source. 470 virtual RESPONSE_CODE rate (Rating value, User *user) = 0; 471 /// Get a song's rating. 472 virtual Rating rating (const User *user) const = 0; rateOverplayed(User *)473 virtual RESPONSE_CODE rateOverplayed (User *) { return E_NOT_IMPLEMENTED; }; 474 float averageRating () const; 475 476 virtual bool operator==(const std::string &compare) const override; 477 virtual bool operator==(const MusicThingie &compare) const override; 478 479 // Actions and action checks 480 virtual SongList songs () override; 481 virtual bool canSkip (time_t *whenAllowed); 482 Media::Player *play (const AudioSettings &audio); 483 }; 484 485 486 487 488 489 490 /** A container for holding songs. Used for queues, history, 491 and search results where only songs apply. */ 492 class SongList : public AnyThingieList <PianodSong *, std::vector<PianodSong *> > { 493 public: 494 using this_type::reserve; 495 using this_type::operator[]; 496 using this_type::AnyThingieList; 497 SongList () = default; 498 SongList (const SongList &SongList); 499 SongList (SongList &&) = default; 500 SongList &operator =(const SongList &list); 501 SongList &operator =(SongList &&) = default; 502 503 void shuffle (void); 504 void mixedMerge (const SongList &adds); 505 void join (SongList &from); 506 }; 507 508 509 /* 510 * Playlists 511 */ 512 513 /** Base class for playlists, but still a MusicThingie. */ 514 class PianodPlaylist : public MusicThingie { 515 public: 516 enum PlaylistType { 517 SINGLE, 518 MIX, 519 EVERYTHING, 520 TRANSIENT 521 }; 522 523 virtual const std::string &playlistId (void) const = 0; ///< Item's playlist ID. 524 virtual const std::string id (void) const override; ///< Primary ID is playlist ID. 525 virtual const std::string &id (Type type) const override; ///< Id for a context. name(void)526 virtual const std::string &name (void) const override { return playlistName(); }; type(void)527 virtual Type type (void) const override { return Type::Playlist; }; ///< Typecode for playlist objects. typetype(void)528 static inline Type typetype (void) { return Type::Playlist; }; ///< Typecode for playlist type. 529 virtual PianodConnection &transmit (PianodConnection &recipient) const override; 530 virtual bool matches (const Filter &filter) const override; asPlaylist()531 virtual PianodPlaylist *asPlaylist () override final { return this; } asPlaylist()532 virtual const PianodPlaylist *asPlaylist () const override final { return this; } 533 534 virtual PlaylistType playlistType (void) const = 0; ///< Mix, everything, transient or single list 535 virtual const std::string &playlistName (void) const = 0; ///< Name of the playlist 536 virtual const std::string &genre (void) const = 0; ///< Get genre of this playlist 537 538 virtual SongList getRandomSongs (const UserList &users, 539 Media::SelectionMethod selectionMethod); 540 virtual bool operator==(const std::string &compare) const override; 541 virtual bool operator==(const MusicThingie &compare) const override; 542 virtual bool includedInMix (void) const = 0; 543 virtual void includedInMix (bool include) = 0; 544 RESPONSE_CODE rate (Rating value, User *user); 545 Rating rating (const User *user) const; 546 float averageRating () const; 547 548 virtual bool canSeed (MusicThingie::Type seedType) const; 549 virtual bool seed (MusicThingie::Type seedType, const MusicThingie *music) const; 550 virtual void seed (MusicThingie::Type seedType, MusicThingie *music, bool value); 551 virtual ThingieList getSeeds (void) const; 552 virtual SongList songs () override; 553 virtual SongList songs (const Filter &filter); 554 555 virtual void rename (const std::string &newname) = 0; 556 virtual void erase () = 0; 557 }; 558 559 /** Container for lists of playlists. */ 560 class PlaylistList : public AnyThingieList <PianodPlaylist *> { 561 public: 562 using this_type::AnyThingieList; 563 }; 564 565 /** Template to make a corresponding transient playlist. */ 566 template<class BasePlaylist> 567 class PianodTransientPlaylist : virtual public BasePlaylist { 568 public: playlistType(void)569 virtual PianodPlaylist::PlaylistType playlistType (void) const override { return PianodPlaylist::TRANSIENT; }; 570 includedInMix(void)571 virtual bool includedInMix (void) const override { return false; }; includedInMix(bool)572 virtual void includedInMix (bool) override { throw CommandError (E_MEDIA_TRANSIENT); }; 573 canSeed(MusicThingie::Type)574 virtual bool canSeed (MusicThingie::Type) const override { return false; }; seed(MusicThingie::Type,const MusicThingie *)575 virtual bool seed (MusicThingie::Type, const MusicThingie *) const override { 576 assert (!"Queried seed for transient playlist."); 577 return false; 578 } seed(MusicThingie::Type,MusicThingie *,bool)579 virtual void seed (MusicThingie::Type, MusicThingie *, bool) override { 580 assert (!"Set seed for transient playlist"); 581 } 582 rename(const std::string &)583 virtual void rename (const std::string &) override { throw CommandError (E_MEDIA_TRANSIENT); }; erase()584 virtual void erase () override { throw CommandError (E_MEDIA_TRANSIENT); }; 585 }; 586 587 588 589 /* 590 * Thingie Management 591 */ 592 593 /// Look up table for thingie type names and type codes. 594 typedef const LookupTable<MusicThingie::Type> ThingieTypesLookup; 595 extern ThingieTypesLookup THINGIETYPES; 596 597