1 /*
2    Vimpc
3    Copyright (C) 2010 Nathan Sweetman
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18    library.cpp - handling of the mpd music library
19    */
20 
21 #include "library.hpp"
22 
23 #include "algorithm.hpp"
24 #include "browse.hpp"
25 #include "clientstate.hpp"
26 #include "directory.hpp"
27 #include "events.hpp"
28 #include "mpdclient.hpp"
29 #include "playlist.hpp"
30 #include "vimpc.hpp"
31 
32 #include <algorithm>
33 
34 const std::string VariousArtist = "Various Artists";
35 
36 using namespace Mpc;
37 
Library()38 Library::Library() :
39    settings_       (Main::Settings::Instance()),
40    variousArtist_  (NULL),
41    lastAlbumEntry_ (NULL),
42    lastArtistEntry_(NULL)
43 {
44    AddCallback(Main::Buffer_Remove, [this] (LibraryEntry * const entry) { CheckIfVariousRemoved(entry); });
45 
46    Main::Vimpc::EventHandler(Event::AllMetaDataReady, [this] (EventData const & Data) { Sort(); });
47 
48    settings_.RegisterCallback(Setting::AlbumArtist, [this] (bool Value)
49    {
50       // Clear the cached format prints for every song
51       for (auto song : uriMap_)
52       {
53          song.second->FormatString("");
54       }
55 
56       RecreateLibraryFromURIs();
57    });
58 }
59 
~Library()60 Library::~Library()
61 {
62    Clear();
63 }
64 
Clear(bool Delete)65 void Library::Clear(bool Delete)
66 {
67    lastAlbumEntry_   = NULL;
68    lastArtistEntry_  = NULL;
69 
70    Main::Playlist().Clear();
71 
72    // \todo investigate this, adds considerable overhead in callgrind
73    Main::Browse().Clear();
74 
75    uriMap_.clear();
76 
77    while (Size() > 0)
78    {
79       int const Pos = Size() - 1;
80       LibraryEntry * entry = Get(Pos);
81       Remove(Pos, 1);
82 
83       if ((Delete == true) && (entry->parent_ == NULL))
84       {
85          delete entry;
86       }
87    }
88 }
89 
RecreateLibraryFromURIs()90 void Library::RecreateLibraryFromURIs()
91 {
92    // collapse everything
93    ForEachParent([] (Mpc::LibraryEntry * entry) { Mpc::MarkUnexpanded(entry); });
94 
95    // clear the songs
96    FUNCTION<void (LibraryEntry *)> function = [this] (LibraryEntry * entry) { entry->song_ = NULL; };
97 
98    for (int i = 0; i < Size(); ++i)
99    {
100       ForEachChild(i, function);
101    }
102 
103    // get rid of every entry
104    while (Size() > 0)
105    {
106       int const Pos = Size() - 1;
107       LibraryEntry * entry = Get(Pos);
108       Remove(Pos, 1);
109 
110       if (entry->parent_ == NULL)
111       {
112          delete entry;
113       }
114    }
115 
116    for (auto song : uriMap_)
117    {
118       Add(song.second);
119    }
120 }
121 
Add(Mpc::Song * song)122 void Library::Add(Mpc::Song * song)
123 {
124    std::string artist = song->Artist();
125    std::string const album  = song->Album();
126    std::string const albumartist = song->AlbumArtist();
127 
128    if ((settings_.Get(Setting::AlbumArtist) == true) && (albumartist != "Unknown Artist"))
129    {
130       artist = albumartist;
131    }
132 
133    CreateVariousArtist();
134 
135    if ((lastAlbumEntry_ == NULL) ||
136        (Algorithm::iequals(lastAlbumEntry_->album_, album,
137                           settings_.Get(Setting::IgnoreTheGroup), true) == false))
138    {
139       lastAlbumEntry_  = NULL;
140 
141       if ((lastArtistEntry_ == NULL) ||
142           (Algorithm::iequals(lastArtistEntry_->artist_, artist) == false))
143       {
144          lastArtistEntry_ = NULL;
145 
146          uint32_t i = 0;
147 
148          for (i = 0;
149               ((i < Size()) &&
150                ((Get(i)->type_ != Mpc::ArtistType) ||
151                 (Algorithm::iequals(Get(i)->artist_, artist,
152                                    settings_.Get(Setting::IgnoreTheGroup), true) == false)));
153                ++i);
154 
155          if (i < Size())
156          {
157             lastArtistEntry_ = Get(i);
158          }
159          else
160          {
161             lastArtistEntry_ = CreateArtistEntry(artist);
162          }
163       }
164 
165       for (auto entry : lastArtistEntry_->children_)
166       {
167          if ((entry->type_ == Mpc::AlbumType) &&
168              (Algorithm::iequals(entry->album_, album) == true))
169          {
170             lastAlbumEntry_ = entry;
171             break;
172          }
173       }
174 
175       if (lastAlbumEntry_ == NULL)
176       {
177          lastAlbumEntry_ = CreateAlbumEntry(song);
178          lastAlbumEntry_->parent_ = lastArtistEntry_;
179          lastArtistEntry_->children_.push_back(lastAlbumEntry_);
180       }
181    }
182 
183    if ((lastArtistEntry_ != NULL) && (lastAlbumEntry_ != NULL) &&
184        (lastArtistEntry_ != variousArtist_) &&
185        (lastArtistEntry_->children_.back() == lastAlbumEntry_) &&
186        (Algorithm::iequals(lastAlbumEntry_->album_, album)  == true) &&
187        (Algorithm::iequals(lastArtistEntry_->artist_, artist,
188            settings_.Get(Setting::IgnoreTheGroup), true) == false))
189    {
190       lastArtistEntry_->children_.pop_back();
191 
192       if (lastArtistEntry_->children_.size() == 0)
193       {
194          Remove(Index(lastArtistEntry_), 1);
195          delete lastArtistEntry_;
196       }
197 
198       if (lastAlbumEntry_->parent_ != variousArtist_)
199       {
200          variousArtist_->children_.push_back(lastAlbumEntry_);
201       }
202 
203       lastAlbumEntry_->parent_ = variousArtist_;
204       lastArtistEntry_ = variousArtist_;
205    }
206 
207    Mpc::LibraryEntry * const entry = new Mpc::LibraryEntry();
208    entry->expanded_ = true;
209    entry->artist_   = artist;
210    entry->album_    = album;
211    entry->song_     = song;
212    entry->type_     = Mpc::SongType;
213    entry->parent_   = lastAlbumEntry_;
214    song->SetEntry(entry);
215 
216    uriMap_[song->URI()] = song;
217 
218    if (lastAlbumEntry_ != NULL)
219    {
220       lastAlbumEntry_->children_.push_back(entry);
221    }
222 }
223 
CreateVariousArtist()224 void Library::CreateVariousArtist()
225 {
226    if (variousArtist_ == NULL)
227    {
228       variousArtist_ = new Mpc::LibraryEntry();
229       variousArtist_->expanded_ = false;
230       variousArtist_->artist_   = VariousArtist;
231       variousArtist_->type_     = Mpc::ArtistType;
232       Add(variousArtist_);
233    }
234 }
235 
CreateArtistEntry(std::string artist)236 Mpc::LibraryEntry * Library::CreateArtistEntry(std::string artist)
237 {
238    Mpc::LibraryEntry * const entry = new Mpc::LibraryEntry();
239 
240    entry->expanded_ = false;
241    entry->artist_   = artist;
242    entry->type_     = Mpc::ArtistType;
243 
244    Add(entry);
245    return entry;
246 }
247 
CreateAlbumEntry(Mpc::Song * song)248 Mpc::LibraryEntry * Library::CreateAlbumEntry(Mpc::Song * song)
249 {
250    Mpc::LibraryEntry * const entry = new Mpc::LibraryEntry();
251    entry->expanded_ = false;
252    entry->artist_   = song->Artist();
253    entry->album_    = song->Album();
254    entry->type_     = Mpc::AlbumType;
255    return entry;
256 }
257 
Song(std::string uri) const258 Mpc::Song * Library::Song(std::string uri) const
259 {
260    std::map<std::string, Mpc::Song *>::const_iterator it = uriMap_.find(uri);
261 
262    if (it != uriMap_.end())
263    {
264       return it->second;
265    }
266 
267    return NULL;
268 }
269 
270 
Sort()271 void Library::Sort()
272 {
273    Mpc::LibraryEntry::LibraryComparator comparator;
274 
275    Main::Buffer<Library::BufferType>::Sort(comparator);
276 
277    for (uint32_t i = 0; (i < Size()); ++i)
278    {
279       Sort(Get(i));
280    }
281 }
282 
Sort(LibraryEntry * entry)283 void Library::Sort(LibraryEntry * entry)
284 {
285    Mpc::LibraryEntry::LibraryComparator entryComparator;
286 
287    if (entry->children_.empty() == false)
288    {
289        std::sort(entry->children_.begin(), entry->children_.end(), entryComparator);
290 
291        for (auto child : entry->children_)
292        {
293           if (child->children_.empty() == false)
294           {
295              Sort(child);
296           }
297        }
298    }
299 }
300 
AddToPlaylist(Mpc::Song::SongCollection Collection,Mpc::Client & client,Mpc::ClientState & clientState,uint32_t position)301 void Library::AddToPlaylist(Mpc::Song::SongCollection Collection, Mpc::Client & client, Mpc::ClientState & clientState, uint32_t position)
302 {
303    if (position < Size())
304    {
305       if (Collection == Mpc::Song::Single)
306       {
307          AddToPlaylist(client, clientState, Get(position));
308       }
309       else
310       {
311          Mpc::CommandList list(client);
312 
313          for (uint32_t i = 0; i < Size(); ++i)
314          {
315             AddToPlaylist(client, clientState, Get(i));
316          }
317       }
318    }
319 }
320 
RemoveFromPlaylist(Mpc::Song::SongCollection Collection,Mpc::Client & client,Mpc::ClientState & clientState,uint32_t position)321 void Library::RemoveFromPlaylist(Mpc::Song::SongCollection Collection, Mpc::Client & client, Mpc::ClientState & clientState, uint32_t position)
322 {
323    if (position < Size())
324    {
325       if (Collection == Mpc::Song::Single)
326       {
327          RemoveFromPlaylist(client, Get(position));
328       }
329       else
330       {
331          Mpc::CommandList list(client);
332 
333          for (uint32_t i = 0; i < Size(); ++i)
334          {
335             RemoveFromPlaylist(client, Get(i));
336          }
337       }
338    }
339 }
340 
AddToPlaylist(Mpc::Client & client,Mpc::ClientState & clientState,Mpc::LibraryEntry const * const entry,int32_t position)341 void Library::AddToPlaylist(Mpc::Client & client, Mpc::ClientState & clientState, Mpc::LibraryEntry const * const entry, int32_t position)
342 {
343    if ((entry->type_ == Mpc::SongType) && (entry->song_ != NULL))
344    {
345       if (position != -1)
346       {
347          client.Add(*(entry->song_), position);
348       }
349       else if ((Main::Settings::Instance().Get(Setting::AddPosition) == Setting::AddEnd) ||
350                (clientState.GetCurrentSongPos() == -1))
351       {
352          client.Add(*(entry->song_));
353       }
354       else
355       {
356          client.Add(*(entry->song_), clientState.GetCurrentSongPos() + 1);
357       }
358    }
359    else
360    {
361       int current = -1;
362 
363       if ((Main::Settings::Instance().Get(Setting::AddPosition) == Setting::AddNext) &&
364           (clientState.GetCurrentSongPos() != -1))
365       {
366          current = clientState.GetCurrentSongPos() + 1;
367       }
368 
369       for (auto child : entry->children_)
370       {
371          AddToPlaylist(client, clientState, child, current);
372 
373          if (current != -1)
374          {
375             current++;
376          }
377       }
378    }
379 }
380 
RemoveFromPlaylist(Mpc::Client & client,Mpc::LibraryEntry const * const entry)381 void Library::RemoveFromPlaylist(Mpc::Client & client, Mpc::LibraryEntry const * const entry)
382 {
383    if ((entry->type_ == Mpc::SongType) && (entry->song_ != NULL))
384    {
385       int32_t PlaylistIndex = Main::Playlist().Index(entry->song_);
386 
387       if (PlaylistIndex >= 0)
388       {
389          client.Delete(PlaylistIndex);
390       }
391    }
392    else
393    {
394       for (auto child : entry->children_)
395       {
396          RemoveFromPlaylist(client, child);
397       }
398    }
399 }
400 
ForEachChild(uint32_t index,FUNCTION<void (Mpc::Song *)> callback) const401 void Library::ForEachChild(uint32_t index, FUNCTION<void (Mpc::Song *)> callback) const
402 {
403    for (auto child : Get(index)->children_)
404    {
405       if (child->type_ == AlbumType)
406       {
407          for (auto child2 : child->children_)
408          {
409             (callback)(child2->song_);
410          }
411       }
412       else if (child->type_ == SongType)
413       {
414          (callback)(child->song_);
415       }
416    }
417 }
418 
ForEachChild(uint32_t index,FUNCTION<void (Mpc::LibraryEntry *)> callback) const419 void Library::ForEachChild(uint32_t index, FUNCTION<void (Mpc::LibraryEntry *)> callback) const
420 {
421    for (auto child : Get(index)->children_)
422    {
423       if (child->type_ == AlbumType)
424       {
425          for (auto child2 : child->children_)
426          {
427             (callback)(child2);
428          }
429       }
430 
431       (callback)(child);
432    }
433 }
434 
ForEachSong(FUNCTION<void (Mpc::Song *)> callback) const435 void Library::ForEachSong(FUNCTION<void (Mpc::Song *)> callback) const
436 {
437    for (uint32_t i = 0; i < Size(); ++i)
438    {
439       if (Get(i)->type_ == ArtistType)
440       {
441          for (auto child : Get(i)->children_)
442          {
443             if (child->type_ == AlbumType)
444             {
445                for (auto child2 : child->children_)
446                {
447                   (callback)(child2->song_);
448                }
449             }
450          }
451       }
452    }
453 }
454 
ForEachParent(FUNCTION<void (Mpc::LibraryEntry *)> callback) const455 void Library::ForEachParent(FUNCTION<void (Mpc::LibraryEntry *)> callback) const
456 {
457    for (uint32_t i = 0; i < Size(); ++i)
458    {
459       if (Get(i)->type_ == ArtistType)
460       {
461          for (auto child : Get(i)->children_)
462          {
463             if (child->type_ == AlbumType)
464             {
465                (callback)(child);
466             }
467          }
468 
469          (callback)(Get(i));
470       }
471    }
472 }
473 
474 
Expand(uint32_t line)475 void Library::Expand(uint32_t line)
476 {
477    uint32_t position = line;
478 
479    if ((Get(line)->expanded_ == false) && (Get(line)->type_ != Mpc::SongType))
480    {
481       Get(line)->expanded_ = true;
482 
483       for (auto child : Get(line)->children_)
484       {
485          Add(child, ++position);
486       }
487    }
488 }
489 
Collapse(uint32_t line)490 void Library::Collapse(uint32_t line)
491 {
492    Mpc::LibraryEntry * const entry     = Get(line);
493    Mpc::LibraryEntry * const parent    = entry->parent_;
494    Mpc::LibraryEntry * entryToCollapse = entry;
495 
496    if ((entry->expanded_ == false) || (entry->type_ == Mpc::SongType))
497    {
498       entryToCollapse = parent;
499    }
500 
501    if (Index(entryToCollapse) >= 0)
502    {
503       uint32_t Pos = Index(entryToCollapse);
504 
505       // Separated function out into variable as compile fails on g++ 4.7.2
506       // if passed directly to function using the lambda
507       FUNCTION<void (LibraryEntry *)> function = [this] (LibraryEntry * exp) { RemoveAndUnexpand(exp); };
508       ForEachChild(Pos, function);
509       entryToCollapse->expanded_ = false;
510    }
511 }
512 
513 
String(uint32_t position) const514 std::string Library::String(uint32_t position) const
515 {
516    Mpc::EntryType const type = Get(position)->type_;
517 
518    std::string Result = "";
519 
520    if (type == Mpc::ArtistType)
521    {
522       Result = Get(position)->artist_;
523    }
524    else if (type == Mpc::AlbumType)
525    {
526       Result = Get(position)->album_;
527    }
528    else if (type == Mpc::SongType)
529    {
530       Result = Get(position)->song_->FormatString(settings_.Get(Setting::LibraryFormat));
531    }
532 
533    return Result;
534 }
535 
PrintString(uint32_t position) const536 std::string Library::PrintString(uint32_t position) const
537 {
538    Mpc::EntryType const type = Get(position)->type_;
539 
540    std::string Result = "";
541 
542    if (type == Mpc::ArtistType)
543    {
544       std::string artist(Get(position)->artist_);
545       std::string const find = "$";
546       std::string const replace = "\\$";
547       for(std::string::size_type i = 0; (i = artist.find(find, i)) != std::string::npos;)
548       {
549          artist.replace(i, find.length(), replace);
550          i += replace.length();
551       }
552       Result = "$B " + artist + "$R$B";
553    }
554    else if (type == Mpc::AlbumType)
555    {
556       Result = "    " + Get(position)->album_;
557    }
558    else if (type == Mpc::SongType)
559    {
560       Result = "       " + Get(position)->song_->FormatString(settings_.Get(Setting::LibraryFormat));
561    }
562 
563    return Result;
564 }
565 
566 
RemoveAndUnexpand(LibraryEntry * const entry)567 void Library::RemoveAndUnexpand(LibraryEntry * const entry)
568 {
569    if (Index(entry) != -1)
570    {
571       Remove(Index(entry), 1);
572       entry->expanded_ = false;
573    }
574 }
575 
576 
CheckIfVariousRemoved(LibraryEntry * const entry)577 void Library::CheckIfVariousRemoved(LibraryEntry * const entry)
578 {
579    if (entry == variousArtist_)
580    {
581       variousArtist_ = NULL;
582    }
583 
584    if (entry == lastArtistEntry_)
585    {
586       lastArtistEntry_ = NULL;
587    }
588 
589    if (entry == lastAlbumEntry_)
590    {
591       lastAlbumEntry_  = NULL;
592       lastArtistEntry_ = NULL;
593    }
594 }
595 
596 
MarkUnexpanded(LibraryEntry * const entry)597 void Mpc::MarkUnexpanded(LibraryEntry * const entry)
598 {
599    entry->expanded_ = false;
600 }
601 
602 /* vim: set sw=3 ts=3: */
603