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