ocarina/lib/playqueue.cpp

384 lines
7.7 KiB
C++
Raw Normal View History

/*
* Copyright 2013 (c) Anna Schumaker.
*/
#include <callback.h>
#include <library.h>
#include <playqueue.h>
#include <stdlib.h>
#include <algorithm>
#include <sstream>
#define O_MINUTES (60)
#define O_HOURS (60 * O_MINUTES)
#define O_DAYS (24 * O_HOURS)
Playqueue :: Playqueue()
: flags(0), cur(-1), length(0)
{
}
Playqueue :: Playqueue(playqueue_flags f)
: flags(f), cur(-1), length(0)
{
}
Playqueue :: ~Playqueue()
{
}
void Playqueue :: write(File &f)
{
f << flags << " " << tracks.size();
for (unsigned int i = 0; i < tracks.size(); i++)
f << " " << tracks[i];
}
void Playqueue :: read(File &f)
{
unsigned int n;
f >> flags >> n;
tracks.resize(n);
for (unsigned int i = 0; i < n; i++)
f >> tracks[i];
}
void Playqueue :: set_flag(playqueue_flags f)
{
flags |= f;
get_callbacks()->on_queue_changed();
}
void Playqueue :: unset_flag(playqueue_flags f)
{
flags &= ~f;
get_callbacks()->on_queue_changed();
}
const unsigned int Playqueue :: get_flags()
{
return flags;
}
unsigned int Playqueue :: get_length()
{
return length;
}
static inline void add_duration(std::stringstream &ss, unsigned int dur,
unsigned int remaining, const std::string &field)
{
if (dur > 0) {
ss << dur << " " << field;
if (dur > 1)
ss << "s";
if (remaining > 0)
ss << ", ";
}
}
std::string Playqueue :: get_length_str()
{
std::stringstream ss;
unsigned int len = length;
unsigned int days = len / O_DAYS;
len -= days * O_DAYS;
add_duration(ss, days, len, "day");
unsigned int hours = len / O_HOURS;
len -= hours *O_HOURS;
add_duration(ss, hours, len, "hour");
unsigned int mins = len / O_MINUTES;
add_duration(ss, mins, len, "minute");
unsigned int secs = len - (mins * O_MINUTES);
add_duration(ss, secs, 0, "second");
return ss.str();
}
/*
* Returns:
* 0: lhs == rhs
* < 0: lhs < rhs
* > 0: lhs > rhs
*/
static inline int compare_uint(unsigned int a, unsigned int b)
{
if (a == b)
return 0;
if (a < b)
return -1;
return 1;
}
/*
* Returns:
* 0: lhs == rhs
* < 0: lhs < rhs, or rhs is empty
* > 0: lhs > rhs, or lhs is empty
*/
static inline int compare_string(const std::string &a, const std::string &b)
{
if (a.size() == 0)
return 1;
else if (b.size() == 0)
return -1;
return a.compare(b);
}
/*
* std::string.compare() returns
* 0: Strings are equal
* < 0: a < b
* > 0: a > b
*/
static inline int track_compare(library :: Song &lhs, library :: Song &rhs,
sort_t field)
{
switch (field) {
case SORT_ARTIST:
return compare_string(lhs.artist->key_lower, rhs.artist->key_lower);
case SORT_ALBUM:
return compare_string(lhs.album->name_lower, rhs.album->name_lower);
case SORT_COUNT:
return compare_uint(lhs.track->track, rhs.track->track);
case SORT_GENRE:
return compare_string(lhs.genre->key_lower, rhs.genre->key_lower);
case SORT_LENGTH:
return compare_uint(lhs.track->length, rhs.track->length);
case SORT_PLAYED:
return compare_uint(lhs.track->play_count, rhs.track->play_count);
case SORT_TITLE:
return compare_string(lhs.track->title_lower, rhs.track->title_lower);
case SORT_TRACK:
return compare_uint(lhs.track->track, rhs.track->track);
default: //case SORT_YEAR
int ret = compare_uint(lhs.album->year, rhs.album->year);
if (ret != 0)
return ret;
return compare_string(lhs.album->name_lower, rhs.album->name_lower);
}
}
static bool track_less_than(library :: Song &lhs, library :: Song &rhs,
std::list<sort_info> &order)
{
std::list<sort_info>::iterator it;
int res;
for (it = order.begin(); it != order.end(); it++) {
if (it->ascending == true)
res = track_compare(lhs, rhs, it->field);
else
res = track_compare(rhs, lhs, it->field);
if (res != 0)
return res < 0;
}
return res;
}
unsigned int Playqueue :: find_sorted_id(library :: Song &rhs)
{
library :: Song lhs;
unsigned int begin = 0, end = (tracks.size() - 1), mid;
if (tracks.size() == 0)
return 0;
while (end > begin) {
mid = begin + ((end - begin) / 2);
library :: lookup(tracks[mid], &lhs);
if (track_less_than(lhs, rhs, sort_order))
begin = mid + 1;
else {
if (mid == begin)
return begin;
else
end = mid - 1;
}
}
library :: lookup(tracks[begin], &lhs);
if (track_less_than(lhs, rhs, sort_order))
return begin + 1;
return begin;
}
unsigned int Playqueue :: add(unsigned int track_id)
{
unsigned int id = tracks.size();
library :: Song song;
library :: lookup(track_id, &song);
if (sort_order.size() > 0)
id = find_sorted_id(song);
tracks.insert(tracks.begin() + id, track_id);
length += song.track->length;
get_callbacks()->on_queue_track_add(this, id);
if (!(flags & PQ_DISABLE_CHANGED_SIZE))
get_callbacks()->on_queue_changed();
return id;
}
unsigned int Playqueue :: add_front(unsigned int track_id)
{
library :: Song song;
tracks.insert(tracks.begin(), track_id);
library :: lookup(track_id, &song);
length += song.track->length;
get_callbacks()->on_queue_track_add(this, 0);
if (!(flags & PQ_DISABLE_CHANGED_SIZE))
get_callbacks()->on_queue_changed();
return 0;
}
void Playqueue :: del(unsigned int plist_id)
{
library :: Song song;
unsigned int track_id = tracks[plist_id];
tracks.erase(tracks.begin() + plist_id);
library :: lookup(track_id, &song);
length -= song.track->length;
get_callbacks()->on_queue_track_del(this, plist_id);
if (!(flags & PQ_DISABLE_CHANGED_SIZE))
get_callbacks()->on_queue_changed();
}
void Playqueue :: del_track(unsigned int track_id)
{
unsigned int i = 0;
while (i < tracks.size()) {
if (tracks[i] == track_id)
del(i);
else
i++;
}
}
void Playqueue :: track_updated(unsigned int track_id)
{
for (unsigned int i = 0; i < tracks.size(); i++) {
if (tracks[i] == track_id)
get_callbacks()->on_queue_track_changed(this, i);
}
}
unsigned int Playqueue :: size()
{
return tracks.size();
}
/* Sorting function */
class SortTracks {
private:
std::list<sort_info> fields;
public:
SortTracks(std::list<sort_info> f) : fields(f) {}
bool operator()(unsigned int a, unsigned int b)
{
library::Song lhs, rhs;
library :: lookup(a, &lhs);
library :: lookup(b, &rhs);
return track_less_than(lhs, rhs, fields);
}
};
void Playqueue :: _add_sort(sort_t field, bool ascending)
{
struct sort_info info;
std::list<sort_info>::iterator it;
/* Is field already in the sort_order? */
for (it = sort_order.begin(); it != sort_order.end(); it++) {
if (it->field == field) {
it->ascending = !it->ascending;
return;
}
}
info.field = field;
info.ascending = ascending;
sort_order.push_back(info);
if (sort_order.size() >= 4)
sort_order.erase(sort_order.begin());
}
void Playqueue :: add_sort(sort_t field, bool ascending)
{
if (flags & PQ_NEVER_SORT)
return;
_add_sort(field, ascending);
std::stable_sort(tracks.begin(), tracks.end(), SortTracks(sort_order));
for (unsigned int i = 0; i < tracks.size(); i++)
get_callbacks()->on_queue_track_changed(this, i);
get_callbacks()->on_queue_changed();
}
void Playqueue :: reset_sort(sort_t field, bool ascending)
{
if (flags & PQ_NEVER_SORT)
return;
if (sort_order.front().field != field)
sort_order.clear();
add_sort(field, ascending);
}
void Playqueue :: force_clear_sort()
{
sort_order.clear();
}
std::list<sort_info> &Playqueue :: get_sort_order()
{
return sort_order;
}
unsigned int Playqueue :: operator[](unsigned int i)
{
return tracks[i];
}
unsigned int Playqueue :: next()
{
unsigned int res;
if (tracks.size() == 0)
throw -E_EXIST;
else if (tracks.size() == 1)
cur = 0;
else if (flags & PQ_RANDOM)
cur += rand() % (tracks.size() / 2) + 1;
else
cur++;
if (cur >= tracks.size())
cur -= tracks.size();
res = tracks[cur];
if (!(flags & PQ_REPEAT)) {
del(cur);
cur--;
}
return res;
}
void Playqueue :: set_cur(unsigned int c)
{
cur = c;
}
#ifdef CONFIG_TEST
void Playqueue :: reset()
{
tracks.clear();
set_cur(0);
}
#endif /* CONFIG_TEST */