design: Modifications needed by the GUI

This patch writes out various modifications the GUI will require the
back-end library to make.  This includes:

- Rename playlists to playqueues and groups to playlists.
- Make library information accessable.
- Use try/catch style errors instead of returning bool in a few places.
- Extra playqueue accessablity functions.

Signed-off-by: Anna Schumaker <schumaker.anna@gmail.com>
This commit is contained in:
Anna Schumaker 2013-12-28 14:01:38 -05:00 committed by Anna Schumaker
parent e1d527f077
commit 31e6bcef3b
16 changed files with 567 additions and 368 deletions

View File

@ -4,13 +4,13 @@
= =
===============================================================================
My main goal for Ocarina 6.x is to plan out all of my actions before writing
code. In the past I was adding features as I thought of them before thinking
out how everything works together, and this made Ocarina difficult to maintain
because I had no overall plan. This document aims to fix that.
Ocarina 6.0 is the 6th (re)writing of the Ocarina music player - a lightweight,
GTK+ based music player. Improvements over the 5.x series will include the
existence of both a design document and unit tests, two items that will help
the main developer stay focused and make maintenance easier.
I will also create unit tests as I add features so bugs can be found faster.
Unit tests will be created for each module (file) in my backend library code.
Ocarina 6.0 will use Gstreamer 1.0 for audio playback and GTK-MM 3 for user
interface development.
@ -19,8 +19,8 @@ Files:
album.db
artist.db
genre.db
groups.idx
library.db
playlist.db
playlists.lst
track.db
/usr/bin/
@ -30,18 +30,20 @@ Files:
design.txt
ocarina/gui/
*
ocarina6.glade
ocarina/include/
audio.h
database.h
database.hpp
deck.h
error.h
file.h
filter.h
groups.h
idle.h
idle.hpp
library.h
playlist.h
playqueue.h
print.h
version.h
ocarina/lib/
@ -50,10 +52,10 @@ Files:
deck.cpp
file.cpp
filter.cpp
groups.cpp
idle.cpp
library.cpp
playlist.cpp
playqueue.cpp
@ -64,18 +66,32 @@ Install:
Errors: (include/error.h
This file contains an enum defining error codes used throughout the
codebase.
Error Codes:
enum error_t {
EEXIST = 1,
ELEGACY = 2,
EINVAL = 3,
EOPEN = 4,
ENOTRACK = 5,
EAUDIO = 6,
};
Printing: (include/print.h>
Sometimes text needs to be printed to the screen so users (or debuggers)
know what is going on. Enabling dprint() when CONFIG_TEST is enabled
means I will only need a single test.good file for output comparing.
can trace what is going on.
API:
void print(string fmt, ...)
Print text to the screen.
void dprint(string fmt, ...)
Print text to the screen when debugging or testing is enabled.
void print(string fmt, ...);
void dprint(string fmt, ...);
Print text to the screen. The dprint() option will only only
be implemented when when CONFIG_DEBUG or CONFIG_TEST is enabled,
and will be an empty function otherwise.
@ -108,7 +124,7 @@ On-disk files: (lib/file.cpp)
separating multiple values.
- Notation:
File << aaaaa << bbbbb << endl is translated into "aaaaa bbbbb\n"
"File << aaaaa << bbbbb << endl" is translated into "aaaaa bbbbb\n"
- File version:
#define FILE_VERSION 0
@ -142,8 +158,8 @@ On-disk files: (lib/file.cpp)
const char *get_filepath();
const unsigned int get_version();
bool exists();
bool open(OpenMode);
bool close();
void open(OpenMode);
void close();
string getline();
}
@ -152,7 +168,7 @@ On-disk files: (lib/file.cpp)
File << <OTHER_DATA>;
- API:
File :: File(string filepath, FileLocHint hint)
File :: File(string filepath, FileLocHint hint);
Resolve filepath to one of:
XDG_{CONFIG|DATA}_HOME/ocarina/filepath
XDG_{CONFIG|DATA}_HOME/ocarina-debug/filepath
@ -163,37 +179,34 @@ On-disk files: (lib/file.cpp)
If filepath is an empty string, set the file hint to
FILE_TYPE_INVALID and do not set the filepath field.
File :: ~File()
File :: ~File();
Close the file stream if it is open.
const char *File :: get_filepath()
const char *File :: get_filepath();
Return the full filepath to the file.
const unsigned int File :: get_version()
const unsigned int File :: get_version();
Return the file version number.
bool File :: exists()
bool File :: exists();
Return true if the file exists in the filesystem.
Return false otherwise.
bool File :: open(OpenMode mode)
void File :: open(OpenMode mode);
When opening a file for reading (mode == OPEN_READ),
- Return false if the file does not exist
- Throw -EEXIST if the file does not exist
- Open the file
- Read in version from the start of the file
- Return true
When opening a file for writing (mode == OPEN_WRITE),
- Return false if the file has FILE_TYPE_LEGACY set
- Throw -ELEGACY if the file has FILE_TYPE_LEGACY set
- Create missing directories as needed
- Write version information to the start of the file
- Return true
Return false if hint == FILE_TYPE_INVALID.
Return false if the file is already open.
Return false if there are any other errors.
Throw -EINVAL if hint == FILE_TYPE_INVALID.
Throw -EOPEN if the file is already open.
bool File :: close()
void File :: close();
Close a file after IO.
string File :: getline();
@ -260,6 +273,7 @@ Database: (lib/database.cpp)
map<std::string, unsigned int> keys;
unsigned int _size; /* Number of valid rows */
File file;
public:
Database::Database(filename, flags);
void load();
@ -276,6 +290,7 @@ Database: (lib/database.cpp)
unsigned int first();
unsigned int last();
unsigned int next(unsigned int &);
bool has_key(const std :: string &);
unsigned int find_index(const std::string &);
T &find(const std::string &);
T &operator[](unsigned int);
@ -298,17 +313,17 @@ Database: (lib/database.cpp)
Saves the database to disk.
void Database :: clear();
This function exists only if CONFIG_DEBUG is enabled.
This function exists only if CONFIG_TEST is enabled.
Clear the database contents in-memory, but do NOT write
to disk.
void Database :: print()
This function exists only If CONFIG_DEBUG is enabled.
This function exists only If CONFIG_TEST is enabled.
Following a similar format for writing to disk, print the
database to the console in a human-readable format.
void Database :: print_keys()
This function exists only if CONFIG_DEBUG is enabled.
This function exists only if CONFIG_TEST is enabled.
Print out the collected primary keys in the database.
template <class T>
@ -332,20 +347,29 @@ Database: (lib/database.cpp)
unsigned int Database :: last();
Return the id of the last valid row.
unsigned int Database :: next(unsigned int &id)
Return the id of the next valid row or return db.size()
if there are no remaining valid rows.
unsigned int Database :: next(unsigned int &id);
Return the id of the next valid row or throw -EINVAL if there
are no remaining valid rows.
bool Database :: has_key(const std::string &key);
Return true if an item with primary key "key" exists in the
database, and false otherwise.
unsigned int Database :: find_index(const std::string key);
If the key exists in the database, return the index of the
database item with that key. Throw -EEXIST if the key is not
in the database.
template <class T>
T &Database :: find(const std::string &key);
Search for primary key "key" in the database. The reverse
mapping should be used to make this operation faster. Throw
an empty exception if the key is not found in the mapping.
-EEXIST if the key is not found in the mapping.
template <class T>
T &Database :: operator[unsigned int index]
Return a reference to db[index]. If index is out of range,
throw an empty exception.
T &Database :: operator[](unsigned int index);
Return a reference to db[index]. Throw -EEXIST if index is out
of range.
@ -377,8 +401,8 @@ Filter: (lib/filter.cpp)
filter_index.
To generate substrings, iterate over the word starting from
the front. For example: "dalek" would contain the substrings
{d, da, dal, dale, dalek}.
the front. For example: "goron" would contain the substrings
{g, go, gor, goro, goron}.
void filter :: search(string, set<track_id> &);
Parse the string into substrings following the "Parsing"
@ -386,6 +410,11 @@ Filter: (lib/filter.cpp)
substrings, so take the intersection of all sets returned by
the filter_index for a given substring.
const std::string & filter :: to_lowercase(const std::string &string);
Split the string into words following step 1 of "Parsing"
(above). Assemble and return a result string using the lower
case cache to convert each term to lowercase.
void filter :: print_cache_stats();
Print cache hit and size information.
@ -395,38 +424,37 @@ Filter: (lib/filter.cpp)
Groups: (lib/group.cpp)
Groups are going to be a new feature in Ocarina 6 and can compare
directly to Gmail-style labels. Ocarina 6 will create dynamic groups
that cannot be deleted by the user based on library status. Similar
to the library, groups should exist in their own namespace.
Playlists: (lib/playlist.cpp)
Playlists are going to be a new feature in Ocarina 6 and can compare
directly to Gmail-style labels. Ocarina 6.0 will support two different
playlists that the user can add songs to: banned and favorites.
In Ocarina 6.0, groups are a wrapper around a specific index. Future
releases will store user-defined groups in a file on disk.
Future releases will add support for more playlists.
- Index:
Database<database :: IndexEntry> group_db
- Database:
Database<database :: IndexEntry> playlist_db
- Default groups:
All music
All tracks are added to this group
Library
Banned Songs
These groups are mutually exclusive. A track is either
in the Library or the Banned Songs group
- Default playlists:
Favorites:
The user will add music they really like to this playlist.
Banned:
The user should add music they do not like to this playlist.
Tracks should be removed from the Library playlist when they
are banned and added back to the playlist when they are
un-banned.
- API
void group :: add(name, track_id)
group_idx.insert(name, track_id);
void playlist :: add(name, track_id)
playlist_idx.insert(name, track_id);
void group :: del(name, track_id)
grou_idx.delete(name, track_id)
void playlist :: del(name, track_id)
playlist_idx.delete(name, track_id)
void void group :: list(list<string> &);
return group_idx.keys();
void void playlist :: list(list<string> &);
return playlist_idx.keys();
void group :: get_tracks(name):
return group_idx[name]
void playlist :: get_tracks(name):
return playlist_idx[name]
@ -518,6 +546,7 @@ Library: (lib/library.cpp)
class library :: Album : public DatabaseEntry {
public:
string name;
string name_lc;
unsigned int year;
unsigned int artist_id;
};
@ -528,6 +557,7 @@ Library: (lib/library.cpp)
class library :: Artist : public DatabaseEntry {
public:
string name;
string name_lc;
};
File << name
@ -536,6 +566,7 @@ Library: (lib/library.cpp)
class library :: Genre : public DatabaseEntry {
public:
string name;
string name_lc;
};
File << name
@ -544,12 +575,16 @@ Library: (lib/library.cpp)
class library :: Library : public DatabaseEntry {
public:
string root_path;
unsigned int count;
bool enabled;
};
File << enabled << root_path
- Track:
The primary key for a track is the full filepath (library.root_path +
track.filepath)
class library :: Track : public DatabaseEntry {
public:
unsigned int library_id;
@ -566,6 +601,7 @@ Library: (lib/library.cpp)
bool banned;
string title;
string title_lc;
string length_str;
string filepath;
};
@ -592,17 +628,13 @@ Library: (lib/library.cpp)
Database<library :: Track> track_db(track.db);
- Updating algorithm:
set<pair<lib_id, track_path>> known_tracks;
1) For each track currently in the library, check if the track exists
in the filesystem.
1a) If the track does exist, add to the known_tracks map.
1b) Else, mark track invalid.
2) For each file in the scan directory, check if (lib_id, track_path)
exists in the known_tracks map.
2a) If the file is in the map, do nothing.
2b) Else, add track to the library, to the groups "All Music" and
"Library", and then to the filter index.
in the filesystem and mark the track invalid if it does not.
2) For each file in the scan directory, check if the track exists in
the track is already in the track_db.
2a) If the file is in the db, do nothing.
2b) Else, add track to the library and the filter index.
3) Save all databases
The taglib library should be used for finding artist, album, etc. tags
@ -619,16 +651,17 @@ Library: (lib/library.cpp)
- API
void library :: init();
Initialize databases and read files from disk. Fill out
groups and prepare filter as tracks are read.
Initialize databases and read files from disk. While reading
the library:
- Update the count of treacks in each library path
- Find the lowercase text of artist, album, genre, track
bool library :: add_path(string dir);
void library :: add_path(string dir);
If dir is not a directory:
return false
throw -EINVAL
Add new row to the library_db table, begin an update only
on the new path.
return true
void library :: del_path(unsigned int lib_id);
Invalidate a library_db row and all tracks owned by that path
@ -636,8 +669,13 @@ Library: (lib/library.cpp)
void library :: update_path(lib_id);
Update the given library_db row, if valid.
struct Song library :: lookup(track_id)
Fill out a Song structure for the provided track_id
struct Song library :: lookup(track_id);
Fill out a Song structure for the provided track_id. Throw
-EEXIST if there is no track mapping to track_id.
struct library :: Library *library :: get_path_info(unsigned int id);
Return the library path with index id. Throw -EEXIST if there
is no such path.
#ifdef CONFIG_DEBUG
void library :: print_db(DB_Type);
@ -649,71 +687,105 @@ endif /* CONFIG_DEBUG */
Playlist: (lib/playlist.cpp)
Playlists are a list of songs that the user has requested to play.
Playqueue: (lib/playqueue.cpp)
Playqueues are a list of songs that the user has requested to play.
- Flags:
enum playlist_flags {
PL_ENABLED (1 << 0),
PL_RANDOM (1 << 1),
PL_LOCKED (1 << 2),
enum playqueue_flags {
PQ_ENABLED (1 << 0),
PQ_RANDOM (1 << 1),
PQ_REPEAT (1 << 2),
};
- Playlist:
class Playlist {
- Sort order:
enum sort_t {
SORT_ARTIST = 1,
SORT_ALBUM = 2,
SORT_COUNT = 3,
SORT_GENRE = 4,
SORT_LENGTH = 5,
SORT_PLAYED = 6,
SORT_TITLE = 7,
SORT_TRACK = 8,
SORT_YEAR = 9,
};
- Playqueue:
class Playqueue {
private:
vector<track_id> tracks;
list<sort_t> sort_order;
unsigned int cur;
unsigned int flags;
public:
Playlist(flags);
Playqueue(flags);
void write(File &);
void read(File &);
void set_flag(playlist_flags);
void unset_flag(playlist_flags);
void set_flag(playqueue_flags);
void unset_flag(playqueue_flags);
const unsigned int get_flags();
unsigned int add(track_id);
void del(playlist_id);
unsigned int add_front(track_id);
void del(playqueue_id);
unsigned int size();
void reset_sort();
void add_sort(sort_t, bool);
void sort();
unsigned int next();
void reset_cur();
}
File << flags << tracks.size() << tracks[0] << tracks[1] << ... << tracks[N];
- API
Playlist :: Playlist(unsigned int flags);
Create a new playlist with the appropriate flags set.
Playqueue :: Playlist(unsigned int flags);
Create a new playqueue with the appropriate flags set.
sort_order = { (SORT_ARTIST, true), (SORT_YEAR, true),
(SORT_TRACK, true) };
unsigned int Playlist :: add(unsigned int track_id);
Add a new track to the tracks vector and return the index.
unsigned int Playqueue :: add(unsigned int track_id);
unsigned int Playqueue :: add_front(unsigned int track_id);
Add a new track to the tracks vector and return the index. If
add_front is called, the track will be added to the front of
the playqueue (index = 0);
void del(unsigned int playlist_id);
Erase tracks[playlist_id] from the tracks vector.
void Playqueue :: del(unsigned int playqueue_id);
Erase tracks[playqueue_id] from the tracks vector.
void set_flag(playlist_flags flag);
void unset_flag(playlist_flags flag);
void Playqueue :: set_flag(playqueue_flags flag);
void Playqueue :: unset_flag(playqueue_flags flag);
Set or unset the given flag.
const unsigned int get_flags();
const unsigned int Playqueue :: get_flags();
Return the currently enabled flags.
unsigned int size();
unsigned int Playqueue :: size();
Return tracks.size();
void write(File &);
void read(File &);
Read or write the playlist to the file.
void Playqueue :: write(File &);
void Playqueue :: read(File &);
Read or write the playqueue to the file.
void sort();
Sort a playlist in Artist -> Year -> Track number order.
void Playqueue :: reset_sort();
Reset the sort_order list to empty.
unsigned int next();
void Playqueue :: add_sort(sort_t type, bool ascending);
Add a new term to the sort order.
void Playqueue :: sort();
Perform a stable sort on the entire playqueue. Compare tracks
based on the sort_order list.
unsigned int Playqueue :: next();
Return the next track_id to play.
if (tracks.size() == 0)
throw -EEXIST;
if (flags & PL_RANDOM):
cur += rand() % tracks.size();
else:
@ -723,18 +795,24 @@ Playlist: (lib/playlist.cpp)
cur -= tracks.size();
track = tracks[cur];
if (!(flags & PL_LOCKED)):
if (!(flags & PL_REPEAT)):
tracks.erase(cur);
return track;
void Playqueue :: reset_cur();
This function is intended to be used by the audio layer when
managing the recently played playqueue.
cur = 0;
Deck: (lib/deck.cpp)
The playlist deck is used to hold the temporary playlists created by
The playqueue deck is used to hold the temporary playqueues created by
the user.
- Deck:
list<Playlist> deck;
list<Playqueue> deck;
File << deck.size() << endl;
File << deck[0] << endl;
@ -743,39 +821,39 @@ Deck: (lib/deck.cpp)
- API
void deck :: read(File &);
void deck :: write(File &);
Read or write the playlist file. This will be called
Read or write the playqueue file. This will be called
from the audio layer to store state.
Playlist *deck :: create();
Adds a new playlist to the end of the deck and returns a
Playqueue *deck :: create();
Adds a new playqueue to the end of the deck and returns a
pointer to it.
void deck :: remove(N);
Remove playlist N from the deck.
Remove playqueue N from the deck.
Playlist *deck :: get(N);
Return playlist N from the deck.
Playqueue *deck :: get(N);
Return playqueue N from the deck.
void deck :: move(M, N);
Move playlist at index M to index N.
Move playqueue at index M to index N.
unsigned int deck :: next();
Iterate through the deck until you find a playlist with the
flag PL_ENABLED set. Call next() on this playlist and return
Iterate through the deck until you find a playqueue with the
flag PL_ENABLED set. Call next() on this playqueue and return
the result.
If the playlist is empty after calling next(), remove it from
If the playqueue is empty after calling next(), remove it from
the deck.
If there are no playable IDs, throw -1.
void deck :: reset();
This function only exists if CONFIG_DEBUG is enabled. Erase
all the playlist information and reset the deck list.
all the playqueue information and reset the deck list.
void deck :: print_info();
This function only exists if CONFIG_DEBUG is enabled. Print
out helpful stats about the current state of the playlist deck.
out helpful stats about the current state of the playqueue deck.
@ -798,55 +876,79 @@ Audio: (lib/audio.cpp)
function after loading a track and after checking the "pause after N"
count.
The audio layer will also create an internal playqueue for tracking
recently played songs.
- State:
The audio layer will store the current trackid to disk, and then save
the playqueue deck.
File << current_track << endl
deck.write(File);
- API:
void audio :: init(argc, argv)
void audio :: init(argc, argv);
Initialize the gstreamer layer and reload the track that was
last loaded before shutdown. Gstreamer is supposed to remove
options from the argv array as they are processed, so pass
pointers to argc and argv to this function.
void audio :: quit()
Read in the state file.
void audio :: quit();
Clean up memory allocated by gstreamer.
Write out the state file.
bool audio :: play()
bool audio :: pause()
void audio :: play();
void audio :: pause();
Change the gstreamer state to either GST_STATE_PLAYING or
GST_STATE_PAUSED. Return true on success and false otherwise.
GST_STATE_PAUSED. Throw -EAUDIO if there is an error changing
state.
bool audio :: seek_to(long)
Seek to a position X nanoseconds into the track. Return true
if track is loaded and the seek isn't out of bounds. False
otherwise.
void audio :: seek_to(long);
Seek to a position X nanoseconds into the track. Throw -EAUDIO
if there is an error seeking to the requested position.
Seconds can be converted to nanoseconds by multiplying with
GST_SECOND.
void audio :: stop()
void audio :: stop();
pause()
seek_to(0)
bool audio :: next()
void audio :: next();
Call the deck :: next() function to get the next trackid,
and load that file into the gstreamer pipeline. Do not change
the state of the pipeline (if nothing is playing yet, don't
call play()). Return true if a track has been loaded into the
pipeline, false otherwise.
call play()). Throw -ENOTRACK if there is no track to load
into the pipeline.
void audio :: previous()
When a track is loaded:
Is it already in the recently played playqueue?
If yes, remove it from the playqueue.
Add to the front of the recently played playqueue.
Reset the current pointer in the playqueue to 0.
Write out the state file.
void audio :: previous();
Load recent.next() into the playlist.
void audio :: previous();
Call the playlist :: previous() function to iterate backwards
through the recently played playlist. Load the returned trackid
through the recently played playqueue. Load the returned trackid
without changing the pipeline state.
trackid audio :: current_trackid()
trackid audio :: current_trackid();
Return the trackid of the currently playing song.
unsigned int audio :: position()
unsigned int audio :: position();
Return the number of seconds that the song has played.
unsigned int audio :: duration()
unsigned int audio :: duration();
Return the duration of the current song in seconds.
void audio :: pause_after(bool enabled, unsigned int N)
void audio :: pause_after(bool enabled, unsigned int N);
Pause after N tracks. The first parameter is a bool
representing if this feature is enabled or not (true == enabled,
false == disabled).
@ -855,11 +957,11 @@ Audio: (lib/audio.cpp)
is received by the gstreamer pipeline, and not when calling
next().
bool audio :: pause_enabled()
bool audio :: pause_enabled();
Return true if pausing is enabled, and false if pausing is
disabled.
unsigned int audio :: pause_count()
unsigned int audio :: pause_count();
Return the number of tracks that will be played before
playback pauses.
@ -959,7 +1061,3 @@ Future work:
- Some way to enable / disable tests during development
- Run tests based on dependencies
- Fix tests that will only work on my computer
- Exceptions: (6.1)
- Don't return error codes, throw exceptions like C++ is
designed to do.

View File

@ -26,55 +26,79 @@ Audio: (lib/audio.cpp)
function after loading a track and after checking the "pause after N"
count.
The audio layer will also create an internal playqueue for tracking
recently played songs.
- State:
The audio layer will store the current trackid to disk, and then save
the playqueue deck.
File << current_track << endl
deck.write(File);
- API:
void audio :: init(argc, argv)
void audio :: init(argc, argv);
Initialize the gstreamer layer and reload the track that was
last loaded before shutdown. Gstreamer is supposed to remove
options from the argv array as they are processed, so pass
pointers to argc and argv to this function.
void audio :: quit()
Read in the state file.
void audio :: quit();
Clean up memory allocated by gstreamer.
Write out the state file.
bool audio :: play()
bool audio :: pause()
void audio :: play();
void audio :: pause();
Change the gstreamer state to either GST_STATE_PLAYING or
GST_STATE_PAUSED. Return true on success and false otherwise.
GST_STATE_PAUSED. Throw -EAUDIO if there is an error changing
state.
bool audio :: seek_to(long)
Seek to a position X nanoseconds into the track. Return true
if track is loaded and the seek isn't out of bounds. False
otherwise.
void audio :: seek_to(long);
Seek to a position X nanoseconds into the track. Throw -EAUDIO
if there is an error seeking to the requested position.
Seconds can be converted to nanoseconds by multiplying with
GST_SECOND.
void audio :: stop()
void audio :: stop();
pause()
seek_to(0)
bool audio :: next()
void audio :: next();
Call the deck :: next() function to get the next trackid,
and load that file into the gstreamer pipeline. Do not change
the state of the pipeline (if nothing is playing yet, don't
call play()). Return true if a track has been loaded into the
pipeline, false otherwise.
call play()). Throw -ENOTRACK if there is no track to load
into the pipeline.
void audio :: previous()
When a track is loaded:
Is it already in the recently played playqueue?
If yes, remove it from the playqueue.
Add to the front of the recently played playqueue.
Reset the current pointer in the playqueue to 0.
Write out the state file.
void audio :: previous();
Load recent.next() into the playlist.
void audio :: previous();
Call the playlist :: previous() function to iterate backwards
through the recently played playlist. Load the returned trackid
through the recently played playqueue. Load the returned trackid
without changing the pipeline state.
trackid audio :: current_trackid()
trackid audio :: current_trackid();
Return the trackid of the currently playing song.
unsigned int audio :: position()
unsigned int audio :: position();
Return the number of seconds that the song has played.
unsigned int audio :: duration()
unsigned int audio :: duration();
Return the duration of the current song in seconds.
void audio :: pause_after(bool enabled, unsigned int N)
void audio :: pause_after(bool enabled, unsigned int N);
Pause after N tracks. The first parameter is a bool
representing if this feature is enabled or not (true == enabled,
false == disabled).
@ -83,10 +107,10 @@ Audio: (lib/audio.cpp)
is received by the gstreamer pipeline, and not when calling
next().
bool audio :: pause_enabled()
bool audio :: pause_enabled();
Return true if pausing is enabled, and false if pausing is
disabled.
unsigned int audio :: pause_count()
unsigned int audio :: pause_count();
Return the number of tracks that will be played before
playback pauses.

View File

@ -65,6 +65,7 @@ Database: (lib/database.cpp)
map<std::string, unsigned int> keys;
unsigned int _size; /* Number of valid rows */
File file;
public:
Database::Database(filename, flags);
void load();
@ -81,6 +82,7 @@ Database: (lib/database.cpp)
unsigned int first();
unsigned int last();
unsigned int next(unsigned int &);
bool has_key(const std :: string &);
unsigned int find_index(const std::string &);
T &find(const std::string &);
T &operator[](unsigned int);
@ -103,17 +105,17 @@ Database: (lib/database.cpp)
Saves the database to disk.
void Database :: clear();
This function exists only if CONFIG_DEBUG is enabled.
This function exists only if CONFIG_TEST is enabled.
Clear the database contents in-memory, but do NOT write
to disk.
void Database :: print()
This function exists only If CONFIG_DEBUG is enabled.
This function exists only If CONFIG_TEST is enabled.
Following a similar format for writing to disk, print the
database to the console in a human-readable format.
void Database :: print_keys()
This function exists only if CONFIG_DEBUG is enabled.
This function exists only if CONFIG_TEST is enabled.
Print out the collected primary keys in the database.
template <class T>
@ -137,17 +139,26 @@ Database: (lib/database.cpp)
unsigned int Database :: last();
Return the id of the last valid row.
unsigned int Database :: next(unsigned int &id)
Return the id of the next valid row or return db.size()
if there are no remaining valid rows.
unsigned int Database :: next(unsigned int &id);
Return the id of the next valid row or throw -EINVAL if there
are no remaining valid rows.
bool Database :: has_key(const std::string &key);
Return true if an item with primary key "key" exists in the
database, and false otherwise.
unsigned int Database :: find_index(const std::string key);
If the key exists in the database, return the index of the
database item with that key. Throw -EEXIST if the key is not
in the database.
template <class T>
T &Database :: find(const std::string &key);
Search for primary key "key" in the database. The reverse
mapping should be used to make this operation faster. Throw
an empty exception if the key is not found in the mapping.
-EEXIST if the key is not found in the mapping.
template <class T>
T &Database :: operator[unsigned int index]
Return a reference to db[index]. If index is out of range,
throw an empty exception.
T &Database :: operator[](unsigned int index);
Return a reference to db[index]. Throw -EEXIST if index is out
of range.

View File

@ -7,14 +7,14 @@
playlists.lst
== Depends ==
playlist
playqueue
Deck: (lib/deck.cpp)
The playlist deck is used to hold the temporary playlists created by
The playqueue deck is used to hold the temporary playqueues created by
the user.
- Deck:
list<Playlist> deck;
list<Playqueue> deck;
File << deck.size() << endl;
File << deck[0] << endl;
@ -23,36 +23,36 @@ Deck: (lib/deck.cpp)
- API
void deck :: read(File &);
void deck :: write(File &);
Read or write the playlist file. This will be called
Read or write the playqueue file. This will be called
from the audio layer to store state.
Playlist *deck :: create();
Adds a new playlist to the end of the deck and returns a
Playqueue *deck :: create();
Adds a new playqueue to the end of the deck and returns a
pointer to it.
void deck :: remove(N);
Remove playlist N from the deck.
Remove playqueue N from the deck.
Playlist *deck :: get(N);
Return playlist N from the deck.
Playqueue *deck :: get(N);
Return playqueue N from the deck.
void deck :: move(M, N);
Move playlist at index M to index N.
Move playqueue at index M to index N.
unsigned int deck :: next();
Iterate through the deck until you find a playlist with the
flag PL_ENABLED set. Call next() on this playlist and return
Iterate through the deck until you find a playqueue with the
flag PL_ENABLED set. Call next() on this playqueue and return
the result.
If the playlist is empty after calling next(), remove it from
If the playqueue is empty after calling next(), remove it from
the deck.
If there are no playable IDs, throw -1.
void deck :: reset();
This function only exists if CONFIG_DEBUG is enabled. Erase
all the playlist information and reset the deck list.
all the playqueue information and reset the deck list.
void deck :: print_info();
This function only exists if CONFIG_DEBUG is enabled. Print
out helpful stats about the current state of the playlist deck.
out helpful stats about the current state of the playqueue deck.

20
design/error.txt Normal file
View File

@ -0,0 +1,20 @@
== Files ==
ocarina/include/
error.h
== Depends ==
install
Errors: (include/error.h
This file contains an enum defining error codes used throughout the
codebase.
Error Codes:
enum error_t {
EEXIST = 1,
ELEGACY = 2,
EINVAL = 3,
EOPEN = 4,
ENOTRACK = 5,
EAUDIO = 6,
};

View File

@ -26,7 +26,7 @@ On-disk files: (lib/file.cpp)
separating multiple values.
- Notation:
File << aaaaa << bbbbb << endl is translated into "aaaaa bbbbb\n"
"File << aaaaa << bbbbb << endl" is translated into "aaaaa bbbbb\n"
- File version:
#define FILE_VERSION 0
@ -60,8 +60,8 @@ On-disk files: (lib/file.cpp)
const char *get_filepath();
const unsigned int get_version();
bool exists();
bool open(OpenMode);
bool close();
void open(OpenMode);
void close();
string getline();
}
@ -70,7 +70,7 @@ On-disk files: (lib/file.cpp)
File << <OTHER_DATA>;
- API:
File :: File(string filepath, FileLocHint hint)
File :: File(string filepath, FileLocHint hint);
Resolve filepath to one of:
XDG_{CONFIG|DATA}_HOME/ocarina/filepath
XDG_{CONFIG|DATA}_HOME/ocarina-debug/filepath
@ -81,37 +81,34 @@ On-disk files: (lib/file.cpp)
If filepath is an empty string, set the file hint to
FILE_TYPE_INVALID and do not set the filepath field.
File :: ~File()
File :: ~File();
Close the file stream if it is open.
const char *File :: get_filepath()
const char *File :: get_filepath();
Return the full filepath to the file.
const unsigned int File :: get_version()
const unsigned int File :: get_version();
Return the file version number.
bool File :: exists()
bool File :: exists();
Return true if the file exists in the filesystem.
Return false otherwise.
bool File :: open(OpenMode mode)
void File :: open(OpenMode mode);
When opening a file for reading (mode == OPEN_READ),
- Return false if the file does not exist
- Throw -EEXIST if the file does not exist
- Open the file
- Read in version from the start of the file
- Return true
When opening a file for writing (mode == OPEN_WRITE),
- Return false if the file has FILE_TYPE_LEGACY set
- Throw -ELEGACY if the file has FILE_TYPE_LEGACY set
- Create missing directories as needed
- Write version information to the start of the file
- Return true
Return false if hint == FILE_TYPE_INVALID.
Return false if the file is already open.
Return false if there are any other errors.
Throw -EINVAL if hint == FILE_TYPE_INVALID.
Throw -EOPEN if the file is already open.
bool File :: close()
void File :: close();
Close a file after IO.
string File :: getline();

View File

@ -35,8 +35,8 @@ Filter: (lib/filter.cpp)
filter_index.
To generate substrings, iterate over the word starting from
the front. For example: "dalek" would contain the substrings
{d, da, dal, dale, dalek}.
the front. For example: "goron" would contain the substrings
{g, go, gor, goro, goron}.
void filter :: search(string, set<track_id> &);
Parse the string into substrings following the "Parsing"
@ -44,6 +44,11 @@ Filter: (lib/filter.cpp)
substrings, so take the intersection of all sets returned by
the filter_index for a given substring.
const std::string & filter :: to_lowercase(const std::string &string);
Split the string into words following step 1 of "Parsing"
(above). Assemble and return a result string using the lower
case cache to convert each term to lowercase.
void filter :: print_cache_stats();
Print cache hit and size information.

View File

@ -79,7 +79,3 @@ Future work:
- Some way to enable / disable tests during development
- Run tests based on dependencies
- Fix tests that will only work on my computer
- Exceptions: (6.1)
- Don't return error codes, throw exceptions like C++ is
designed to do.

View File

@ -1,43 +0,0 @@
== Files ==
ocarina/include/
groups.h
ocarina/lib/
groups.cpp
$HOME/.ocarina{-debug}/
groups.idx
== Depends ==
database
Groups: (lib/group.cpp)
Groups are going to be a new feature in Ocarina 6 and can compare
directly to Gmail-style labels. Ocarina 6 will create dynamic groups
that cannot be deleted by the user based on library status. Similar
to the library, groups should exist in their own namespace.
In Ocarina 6.0, groups are a wrapper around a specific index. Future
releases will store user-defined groups in a file on disk.
- Index:
Database<database :: IndexEntry> group_db
- Default groups:
All music
All tracks are added to this group
Library
Banned Songs
These groups are mutually exclusive. A track is either
in the Library or the Banned Songs group
- API
void group :: add(name, track_id)
group_idx.insert(name, track_id);
void group :: del(name, track_id)
grou_idx.delete(name, track_id)
void void group :: list(list<string> &);
return group_idx.keys();
void group :: get_tracks(name):
return group_idx[name]

View File

@ -1,5 +1,6 @@
== Files ==
ocarina/gui/
ocarina6.glade
*
== Depends ==

View File

@ -8,10 +8,10 @@
= =
===============================================================================
My main goal for Ocarina 6.x is to plan out all of my actions before writing
code. In the past I was adding features as I thought of them before thinking
out how everything works together, and this made Ocarina difficult to maintain
because I had no overall plan. This document aims to fix that.
Ocarina 6.0 is the 6th (re)writing of the Ocarina music player - a lightweight,
GTK+ based music player. Improvements over the 5.x series will include the
existence of both a design document and unit tests, two items that will help
the main developer stay focused and make maintenance easier.
I will also create unit tests as I add features so bugs can be found faster.
Unit tests will be created for each module (file) in my backend library code.
Ocarina 6.0 will use Gstreamer 1.0 for audio playback and GTK-MM 3 for user
interface development.

View File

@ -35,6 +35,7 @@ Library: (lib/library.cpp)
class library :: Album : public DatabaseEntry {
public:
string name;
string name_lc;
unsigned int year;
unsigned int artist_id;
};
@ -45,6 +46,7 @@ Library: (lib/library.cpp)
class library :: Artist : public DatabaseEntry {
public:
string name;
string name_lc;
};
File << name
@ -53,6 +55,7 @@ Library: (lib/library.cpp)
class library :: Genre : public DatabaseEntry {
public:
string name;
string name_lc;
};
File << name
@ -61,12 +64,16 @@ Library: (lib/library.cpp)
class library :: Library : public DatabaseEntry {
public:
string root_path;
unsigned int count;
bool enabled;
};
File << enabled << root_path
- Track:
The primary key for a track is the full filepath (library.root_path +
track.filepath)
class library :: Track : public DatabaseEntry {
public:
unsigned int library_id;
@ -83,6 +90,7 @@ Library: (lib/library.cpp)
bool banned;
string title;
string title_lc;
string length_str;
string filepath;
};
@ -109,17 +117,13 @@ Library: (lib/library.cpp)
Database<library :: Track> track_db(track.db);
- Updating algorithm:
set<pair<lib_id, track_path>> known_tracks;
1) For each track currently in the library, check if the track exists
in the filesystem.
1a) If the track does exist, add to the known_tracks map.
1b) Else, mark track invalid.
2) For each file in the scan directory, check if (lib_id, track_path)
exists in the known_tracks map.
2a) If the file is in the map, do nothing.
2b) Else, add track to the library, to the groups "All Music" and
"Library", and then to the filter index.
in the filesystem and mark the track invalid if it does not.
2) For each file in the scan directory, check if the track exists in
the track is already in the track_db.
2a) If the file is in the db, do nothing.
2b) Else, add track to the library and the filter index.
3) Save all databases
The taglib library should be used for finding artist, album, etc. tags
@ -136,16 +140,17 @@ Library: (lib/library.cpp)
- API
void library :: init();
Initialize databases and read files from disk. Fill out
groups and prepare filter as tracks are read.
Initialize databases and read files from disk. While reading
the library:
- Update the count of treacks in each library path
- Find the lowercase text of artist, album, genre, track
bool library :: add_path(string dir);
void library :: add_path(string dir);
If dir is not a directory:
return false
throw -EINVAL
Add new row to the library_db table, begin an update only
on the new path.
return true
void library :: del_path(unsigned int lib_id);
Invalidate a library_db row and all tracks owned by that path
@ -153,8 +158,13 @@ Library: (lib/library.cpp)
void library :: update_path(lib_id);
Update the given library_db row, if valid.
struct Song library :: lookup(track_id)
Fill out a Song structure for the provided track_id
struct Song library :: lookup(track_id);
Fill out a Song structure for the provided track_id. Throw
-EEXIST if there is no track mapping to track_id.
struct library :: Library *library :: get_path_info(unsigned int id);
Return the library path with index id. Throw -EEXIST if there
is no such path.
#ifdef CONFIG_DEBUG
void library :: print_db(DB_Type);

View File

@ -3,84 +3,40 @@
playlist.h
ocarina/lib/
playlist.cpp
$HOME/.ocarina{-debug}/
playlist.db
== Depends ==
file
database
Playlist: (lib/playlist.cpp)
Playlists are a list of songs that the user has requested to play.
Playlists: (lib/playlist.cpp)
Playlists are going to be a new feature in Ocarina 6 and can compare
directly to Gmail-style labels. Ocarina 6.0 will support two different
playlists that the user can add songs to: banned and favorites.
- Flags:
enum playlist_flags {
PL_ENABLED (1 << 0),
PL_RANDOM (1 << 1),
PL_LOCKED (1 << 2),
};
Future releases will add support for more playlists.
- Playlist:
class Playlist {
private:
vector<track_id> tracks;
unsigned int cur;
unsigned int flags;
public:
Playlist(flags);
void write(File &);
void read(File &);
- Database:
Database<database :: IndexEntry> playlist_db
void set_flag(playlist_flags);
void unset_flag(playlist_flags);
const unsigned int get_flags();
unsigned int add(track_id);
void del(playlist_id);
unsigned int size();
void sort();
unsigned int next();
}
File << flags << tracks.size() << tracks[0] << tracks[1] << ... << tracks[N];
- Default playlists:
Favorites:
The user will add music they really like to this playlist.
Banned:
The user should add music they do not like to this playlist.
Tracks should be removed from the Library playlist when they
are banned and added back to the playlist when they are
un-banned.
- API
Playlist :: Playlist(unsigned int flags);
Create a new playlist with the appropriate flags set.
void playlist :: add(name, track_id)
playlist_idx.insert(name, track_id);
unsigned int Playlist :: add(unsigned int track_id);
Add a new track to the tracks vector and return the index.
void playlist :: del(name, track_id)
playlist_idx.delete(name, track_id)
void del(unsigned int playlist_id);
Erase tracks[playlist_id] from the tracks vector.
void void playlist :: list(list<string> &);
return playlist_idx.keys();
void set_flag(playlist_flags flag);
void unset_flag(playlist_flags flag);
Set or unset the given flag.
const unsigned int get_flags();
Return the currently enabled flags.
unsigned int size();
Return tracks.size();
void write(File &);
void read(File &);
Read or write the playlist to the file.
void sort();
Sort a playlist in Artist -> Year -> Track number order.
unsigned int next();
Return the next track_id to play.
if (flags & PL_RANDOM):
cur += rand() % tracks.size();
else:
cur += 1;
if (cur > = tracks.size())
cur -= tracks.size();
track = tracks[cur];
if (!(flags & PL_LOCKED)):
tracks.erase(cur);
return track;
void playlist :: get_tracks(name):
return playlist_idx[name]

126
design/playqueue.txt Normal file
View File

@ -0,0 +1,126 @@
== Files ==
ocarina/include/
playqueue.h
ocarina/lib/
playqueue.cpp
== Depends ==
file
Playqueue: (lib/playqueue.cpp)
Playqueues are a list of songs that the user has requested to play.
- Flags:
enum playqueue_flags {
PQ_ENABLED (1 << 0),
PQ_RANDOM (1 << 1),
PQ_REPEAT (1 << 2),
};
- Sort order:
enum sort_t {
SORT_ARTIST = 1,
SORT_ALBUM = 2,
SORT_COUNT = 3,
SORT_GENRE = 4,
SORT_LENGTH = 5,
SORT_PLAYED = 6,
SORT_TITLE = 7,
SORT_TRACK = 8,
SORT_YEAR = 9,
};
- Playqueue:
class Playqueue {
private:
vector<track_id> tracks;
list<sort_t> sort_order;
unsigned int cur;
unsigned int flags;
public:
Playqueue(flags);
void write(File &);
void read(File &);
void set_flag(playqueue_flags);
void unset_flag(playqueue_flags);
const unsigned int get_flags();
unsigned int add(track_id);
unsigned int add_front(track_id);
void del(playqueue_id);
unsigned int size();
void reset_sort();
void add_sort(sort_t, bool);
void sort();
unsigned int next();
void reset_cur();
}
File << flags << tracks.size() << tracks[0] << tracks[1] << ... << tracks[N];
- API
Playqueue :: Playlist(unsigned int flags);
Create a new playqueue with the appropriate flags set.
sort_order = { (SORT_ARTIST, true), (SORT_YEAR, true),
(SORT_TRACK, true) };
unsigned int Playqueue :: add(unsigned int track_id);
unsigned int Playqueue :: add_front(unsigned int track_id);
Add a new track to the tracks vector and return the index. If
add_front is called, the track will be added to the front of
the playqueue (index = 0);
void Playqueue :: del(unsigned int playqueue_id);
Erase tracks[playqueue_id] from the tracks vector.
void Playqueue :: set_flag(playqueue_flags flag);
void Playqueue :: unset_flag(playqueue_flags flag);
Set or unset the given flag.
const unsigned int Playqueue :: get_flags();
Return the currently enabled flags.
unsigned int Playqueue :: size();
Return tracks.size();
void Playqueue :: write(File &);
void Playqueue :: read(File &);
Read or write the playqueue to the file.
void Playqueue :: reset_sort();
Reset the sort_order list to empty.
void Playqueue :: add_sort(sort_t type, bool ascending);
Add a new term to the sort order.
void Playqueue :: sort();
Perform a stable sort on the entire playqueue. Compare tracks
based on the sort_order list.
unsigned int Playqueue :: next();
Return the next track_id to play.
if (tracks.size() == 0)
throw -EEXIST;
if (flags & PL_RANDOM):
cur += rand() % tracks.size();
else:
cur += 1;
if (cur > = tracks.size())
cur -= tracks.size();
track = tracks[cur];
if (!(flags & PL_REPEAT)):
tracks.erase(cur);
return track;
void Playqueue :: reset_cur();
This function is intended to be used by the audio layer when
managing the recently played playqueue.
cur = 0;

View File

@ -3,17 +3,15 @@
print.h
== Depends ==
install
error
Printing: (include/print.h>
Sometimes text needs to be printed to the screen so users (or debuggers)
know what is going on. Enabling dprint() when CONFIG_TEST is enabled
means I will only need a single test.good file for output comparing.
can trace what is going on.
API:
void print(string fmt, ...)
Print text to the screen.
void dprint(string fmt, ...)
Print text to the screen when debugging or testing is enabled.
void print(string fmt, ...);
void dprint(string fmt, ...);
Print text to the screen. The dprint() option will only only
be implemented when when CONFIG_DEBUG or CONFIG_TEST is enabled,
and will be an empty function otherwise.

View File

@ -3,7 +3,7 @@
version.h
== Depends ==
install
error
Versioning: (include/version.h)
This file contains a simple function for returning a string stating