1 ///
2 /// PlayList / Artist / Album / Song types that are interbred
3 /// to form a library.
4 ///	@file		musiclibrary.h - pianod
5 ///	@author		Perette Barella
6 ///	@date		2014-12-09
7 ///	@copyright	Copyright (c) 2014-2020 Devious Fish. All rights reserved.
8 ///
9 
10 #pragma once
11 
12 #include <config.h>
13 
14 #include <cstdlib>
15 
16 #include <string>
17 #include <unordered_map>
18 #include <unordered_set>
19 #include <vector>
20 #include <exception>
21 
22 #include <parsnip.h>
23 
24 #include "musictypes.h"
25 #include "musickeys.h"
26 #include "filter.h"
27 
28 /// Memory-based index/database of music library contents.
29 namespace MusicLibrary {
30     class Playlist;
31     class Song;
32 
33     const static int BIAS_MINIMUM = 1;
34     const static int BIAS_MAXIMUM = 100;
35     const static int BIAS_NEUTRAL = 1; ///< Neutral biasing factor for choosing songs
36 
37     struct LibraryParameters;
38 
39     class Foundation {
40     private:
41         mutable time_t write_time = 0;
42     protected:
43         virtual bool restoreIndexFromFile (const std::string &filename) = 0;
44 
45         virtual bool writeIndexToFile (const std::string &filename) const = 0;
persist(Parsnip::SerialData & into)46         virtual void persist (Parsnip::SerialData &into) const { };
restore(const Parsnip::SerialData & data)47         virtual bool restore (const Parsnip::SerialData &data) { return true; };
48     public:
49         Media::Source *const source;
50         Foundation (Media::Source *owner);
51         typedef enum {
52             IMPORTANT = 300,
53             NOMINAL = 1800,
54             TRIVIAL = 60 * 60 * 6
55         } IMPORTANCE;
56         inline void markDirty (IMPORTANCE import = TRIVIAL) const {
57             time_t when = time (nullptr) + import;
58             if (write_time == 0 || when < write_time) {
59                 write_time = when;
60             }
61         }
62         bool load();
63         bool flush ();
64         float periodic ();
65 
66         virtual SongList getAllSongs (void) = 0;
67         virtual SongList getMatchingSongs (const Filter &criteria) = 0;
68         virtual bool removePlaylist (Playlist *play) = 0;
69         virtual ThingieList seedsForPlaylist (const Playlist *playlist) = 0;
70         virtual void populatePlaylist (Playlist *play, bool aggressive = false) = 0;
71         virtual SongList getSongsForPlaylist (PianodPlaylist *) = 0;
72         virtual SongList getPlaylistSongs (const Playlist *play, bool reassess = false) = 0;
73         SongList getRandomSongs (PianodPlaylist *playlist,
74                                  const UserList &users,
75                                  Media::SelectionMethod selectionMethod,
76                                  const LibraryParameters &settings);
77     };
78 
79 
80     /// A PianodPlaylist for music libraries.
81     class Playlist : public PianodPlaylist {
82         friend class TransientPlaylist;
83     private:
84         Foundation *const _library;
85         const std::string _id;
86         std::string _name;
87         mutable bool genres_dirty {true};
88         mutable std::string _genres;
89 
90         void calculateGenres() const;
91     public:
92         typedef std::unordered_set<std::string> SeedSet;
93         SeedSet seeds;
94         bool enabled = true;
95         Filter selector; ///< First selection of music from the library
96     public:
Playlist(Foundation * const library,const std::string & id,const std::string & name)97         Playlist (Foundation *const library, const std::string &id, const std::string &name)
98         : _library (library), _id (id), _name (name) { };
99         // Default constructor is not used, but necessary for TransientPlaylist to compile (note virtual base class).
Playlist()100         Playlist (): Playlist (nullptr, "", "") { assert (!"Default constructor called for MusicLibrary::Playlist"); };
library(void)101         inline Foundation *const library (void) const { return _library; };
parent(void)102         inline Foundation *const parent (void) const { return _library; };
source(void)103         virtual Media::Source *const source (void) const override final { return _library->source; };
includedInMix(void)104         virtual bool includedInMix (void) const override { return enabled; };
includedInMix(bool include)105         virtual void includedInMix (bool include) override { enabled = include; };
106 
playlistType(void)107         virtual PlaylistType playlistType (void) const override { return SINGLE; };
playlistId(void)108         virtual const std::string &playlistId (void) const override { return _id; };
playlistName(void)109         virtual const std::string &playlistName (void) const override { return _name; };
110         virtual const std::string &genre (void) const override;
111 
112         virtual bool canSeed (MusicThingie::Type seedType) const override;
113         virtual bool seed (MusicThingie::Type seedType, const MusicThingie *music) const override;
114         virtual ThingieList getSeeds (void) const override;
115         virtual void seed (MusicThingie::Type seedType, MusicThingie *music,
116                                     bool value) override;
117         virtual SongList songs () override;
118         virtual SongList songs (const Filter &filter) override;
119         virtual void rename (const std::string &newname) override;
120         virtual void erase () override;
121 
122         bool appliesTo (const PianodSong *song) const;
123         const std::string *getIdForSeed (MusicThingie::Type seedType,
124                                          const MusicThingie *music) const;
125         void invalidateSeeds (const MusicThingie *music);
126         virtual Parsnip::SerialData persist () const;
127         virtual void restore (const Parsnip::SerialData &data);
128     };
129 
130     class TransientPlaylist : public PianodTransientPlaylist <Playlist> {
131     public:
132         TransientPlaylist (Foundation *const library, const Filter &criteria);
133         virtual SongList songs () override;
134     };
135 
136     class Album;
137     class Song;
138 
139     /// A PianodArtist that contains a vector of albums.
140     class Artist : public PianodArtist {
141         friend class Album;
142         friend class Song;
143         friend class Playlist;
144     private:
145         Foundation *const _library;
146         const std::string _id;
147         const std::string _name;
148         std::vector<Album *> albums;
149     public:
150         Artist (Foundation *const library, const std::string &id, const std::string &name);
151         virtual ~Artist (void) override;
parent(void)152         inline Foundation *const parent (void) const { return _library; };
library(void)153         inline Foundation *const library (void) const { return _library; };
source(void)154         virtual Media::Source *const source (void) const override final { return _library->source; };
155 
artistId(void)156         virtual const std::string &artistId (void) const override { return _id; };
artist(void)157         virtual const std::string &artist (void) const override { return _name; };
158 
159         virtual SongList songs () override;
160         virtual Parsnip::SerialData persist () const;
161         virtual void restore (const Parsnip::SerialData &data);
empty()162         inline bool empty() const { return albums.empty(); };
getAlbums()163         const std::vector<Album *> &getAlbums() { return albums; };
164     };
165 
166     /// A PianodAlbum that contains a vector of songs
167     class Album : public PianodAlbum {
168         friend class Artist;
169         friend class Song;
170         friend class Playlist;
171         friend SongList Foundation::getRandomSongs (PianodPlaylist *playlist,
172                                                     const UserList &users,
173                                                     Media::SelectionMethod selectionMethod,
174                                                     const LibraryParameters &settings);
175     protected:
176         Artist * _artist;
177     private:
178         const std::string _id;
179         const std::string _name;
180         std::vector<Song *> _songs;
181         std::string _coverArt;
182         mutable std::string _artists;
183         mutable bool artists_dirty = false;
184     public:
185         Album (Artist *const parent, const std::string &id, const std::string &name);
186         virtual ~Album (void) override;
parent(void)187         inline Artist *const parent (void) const { return _artist; };
library(void)188         inline Foundation *const library (void) const { return _artist->_library; };
source(void)189         virtual Media::Source *const source (void) const override final { return _artist ->_library->source; };
190 
artistId(void)191         virtual const std::string &artistId (void) const override { return _artist->_id; };
192         virtual const std::string &artist (void) const override;
193 
albumId(void)194         virtual const std::string &albumId (void) const override { return _id; };
albumTitle(void)195         virtual const std::string &albumTitle (void) const override { return _name; };
coverArtUrl(void)196         virtual const std::string &coverArtUrl (void) const override { return _coverArt; };
197         virtual bool compilation () const override;
198 
coverArtUrl(const std::string & a)199         inline void coverArtUrl (const std::string &a) { _coverArt = a; };
200         void calculateArtists (void) const;
201         virtual void compilation (Artist *compilation_artist);
202 
203         virtual SongList songs () override;
204         virtual Parsnip::SerialData persist () const;
205         virtual void restore (const Parsnip::SerialData &data);
empty()206         inline bool empty() const { return _songs.empty(); };
getSongs()207         const std::vector<Song *> &getSongs() { return _songs; };
208     };
209 
210     /// A PianodSong made of inbred data structures.
211     class Song : public PianodSong {
212         friend class Playlist;
213         friend class Album;
214         friend SongList Foundation::getRandomSongs (PianodPlaylist *playlist,
215                                                     const UserList &users,
216                                                     Media::SelectionMethod selectionMethod,
217                                                     const LibraryParameters &settings);
218     protected:
219         Album *const _album;
220     private:
221         const std::string _id;
222         const std::string _name;
223 
224         Artist *_artist = nullptr;
225         std::string _genre;
226         int _duration = 0;
227         int _year = 0;
228         int _trackNumber = 0;
229 public: // For now
230         Playlist *_playlist = nullptr;
231     public:
232         Song (Album *const parent, const std::string &id, const std::string &name);
233         virtual ~Song (void) override;
parent(void)234         inline Album *const parent (void) const { return _album; };
library(void)235         inline Foundation *const library (void) const { return _album->_artist->_library; };
source(void)236         virtual Media::Source *const source (void) const override final { return _album->_artist->_library->source; };
songId(void)237         virtual const std::string &songId (void) const override { return _id; };
238 
artistId(void)239         virtual const std::string &artistId (void) const override { return _album->_artist ->_id; };
240         virtual const std::string &artist (void) const override;
241 
albumId(void)242         virtual const std::string &albumId (void) const override { return _album->_id; };
albumTitle(void)243         virtual const std::string &albumTitle (void) const override { return _album->_name; };
coverArtUrl(void)244         virtual const std::string &coverArtUrl (void) const override { return _album->_coverArt; };
compilation()245         virtual bool compilation () const override { return _album->compilation(); };
246 
title(void)247         virtual const std::string &title (void) const override { return _name; };
248 
249         virtual PianodPlaylist *playlist (void) const override;
genre(void)250         virtual const std::string &genre (void) const override { return _genre; }
duration(void)251         virtual int duration (void) const override { return _duration; };
year(void)252         virtual int year (void) const override { return _year; };
trackNumber(void)253         virtual int trackNumber (void) const override {return _trackNumber; };
albumTrackCount()254         inline int albumTrackCount () const { return _album->_songs.size(); };
255 
256         void artist (Artist *artist);
genre(const std::string & g)257         inline void genre (const std::string &g) { _genre = g; };
duration(int d)258         inline void duration (int d) { _duration = d; };
year(int y)259         inline void year (int y) { _year = y; };
trackNumber(int n)260         inline void trackNumber (int n) { _trackNumber = n; };
261 
262         virtual Parsnip::SerialData persist () const;
263         virtual void restore (const Parsnip::SerialData &data);
264 
265 
266         // Content management
ratingScheme(void)267         virtual RatingScheme ratingScheme (void) const override { return RatingScheme::Individual; };
268         virtual RESPONSE_CODE rate (Rating value, User *user) override;
269         virtual Rating rating (const User *user) const override;
270         virtual RESPONSE_CODE rateOverplayed (User *user) override;
271     };
272 
273 
274     /// Customized hash tables for music library.
275     template <class TThing, class TParent> class ThingieContainer
276     : public std::unordered_map<std::string, TThing *> {
277     public:
278         ~ThingieContainer();
279 
280         void clear ();
281         void purge (bool pred(const TThing *));
282 
283 
284         TThing *getById (const std::string &key) const;
285         TThing *getById (const Parsnip::SerialData &data, const char *field);
286 
287         /** Search the things looking for a name and parent match. */
288         TThing *getByName(const std::string &name, TParent *parent) const;
289         std::string getNewId (MusicThingie::Type item_type) const;
290         TThing *addItem (const std::string &name, std::string id, TParent *parent);
291         TThing *addOrGetItem (const std::string &name, std::string id, TParent *parent);
addOrGetItem(const std::string & name,TParent * parent)292         inline TThing *addOrGetItem (const std::string &name, TParent *parent) {
293             return addOrGetItem (name, "", parent);
294         }
295         TThing *addOrGetItem (const Parsnip::SerialData &data, TParent *parent,
296                               const std::string &namefield, const std::string &idfield);
297     };
298 
isCompilationAlbum(const Parsnip::SerialData & album)299     static inline bool isCompilationAlbum (const Parsnip::SerialData &album) {
300         return (album.contains (MusicStorage::AlbumIsCompilation) &&
301                 album [MusicStorage::AlbumIsCompilation].asBoolean());
302     }
303 
304 
305 
306 
307     template <class TSong = Song, class TAlbum = Album, class TArtist = Artist>
308     class Library : public Foundation
309     {
310     protected:
311         typedef ThingieContainer <TArtist, Foundation> artist_container;
312         typedef ThingieContainer <TAlbum, TArtist> album_container;
313         typedef ThingieContainer <TSong, TAlbum> song_container;
314         typedef ThingieContainer <Playlist, Foundation> playlist_container;
315         artist_container artists;
316         album_container albums;
317         song_container songs;
318         playlist_container playlists;
319 
Library(Media::Source * owner)320         Library (Media::Source *owner)
321         : Foundation (owner) {
322             // Derived classes should invoke restoreIndexFromFile;
323             // object is not fully constituted here.
324         };
325 
326         void purge (void);
327 
328     public:
329         virtual bool removePlaylist (Playlist *play) override;
330         virtual ThingieList seedsForPlaylist (const Playlist *playlist) override;
331         SongList getAllSongs (void) override;
332         SongList getMatchingSongs (const Filter &criteria) override;
333         ThingieList getSuggestions (const Filter &criteria, SearchRange what);
334         SongList getMixSongs (void);
335         /** Get a list of all songs assigned to a playlist.
336             @param reassess If false, only assigned songs are returned.
337             If true, songs in other playlists are also considered. */
338         virtual SongList getPlaylistSongs (const Playlist *play, bool reassess = false) override;
339         Playlist *findPlaylistForSong (TSong *song, bool enabled = true);
340         virtual void populatePlaylist (Playlist *play, bool aggressive = false) override;
341         void unpopulatePlaylist (Playlist *play);
342         /** Iterate over every song and replace its playlist assignment.
343             Applicable on initialization. */
mixRecalculate(void)344         void mixRecalculate (void) {
345             for (auto item : songs) {
346                 item.second->_playlist = findPlaylistForSong (item.second);
347             }
348         }
349         MusicThingie *getById (MusicThingie::Type type,
350                                const std::string &id);
351         virtual SongList getSongsForPlaylist (PianodPlaylist *playlist) override;
352         PianodPlaylist *createPlaylist (const std::string &name,
353                                         MusicThingie::Type type,
354                                         MusicThingie *from);
355         PianodPlaylist *createPlaylist (const std::string &name, const Filter &filter);
356         PianodPlaylist *formTransientPlaylist (const Filter &criteria);
357 
358         virtual bool writeIndexToFile (const std::string &filename) const override;
359         virtual bool restoreIndexFromFile (const std::string &filename) override;
360     };
361 };
362 
363