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:
parent
e1d527f077
commit
31e6bcef3b
406
design.txt
406
design.txt
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
|
@ -1,5 +1,6 @@
|
|||
== Files ==
|
||||
ocarina/gui/
|
||||
ocarina6.glade
|
||||
*
|
||||
|
||||
== Depends ==
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
|
@ -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.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
version.h
|
||||
|
||||
== Depends ==
|
||||
install
|
||||
error
|
||||
|
||||
Versioning: (include/version.h)
|
||||
This file contains a simple function for returning a string stating
|
||||
|
|
Loading…
Reference in New Issue