2013-05-13 23:13:54 -04:00
|
|
|
===============================================================================
|
|
|
|
= =
|
|
|
|
= Ocarina 6.0 =
|
|
|
|
= =
|
|
|
|
===============================================================================
|
|
|
|
|
2013-06-19 14:07:36 -04:00
|
|
|
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.
|
2013-05-13 23:13:54 -04:00
|
|
|
|
2013-05-26 11:08:41 -04:00
|
|
|
I will also create unit tests as I add features so bugs can be found faster.
|
2013-07-06 11:42:13 -04:00
|
|
|
Unit tests will be created for each module (file) in my backend library code.
|
2013-05-26 11:08:41 -04:00
|
|
|
|
|
|
|
|
2013-07-04 18:03:15 -04:00
|
|
|
|
2013-05-13 23:13:54 -04:00
|
|
|
Files:
|
2013-07-06 11:42:13 -04:00
|
|
|
$HOME/.ocarina{-debug}/
|
|
|
|
album.db
|
|
|
|
artist.db
|
|
|
|
genre.db
|
|
|
|
groups.idx
|
|
|
|
library.db
|
|
|
|
playlists.lst
|
|
|
|
track.db
|
|
|
|
/usr/bin/
|
|
|
|
ocarina
|
|
|
|
/usr/lib/ocarina/
|
2013-05-26 11:08:41 -04:00
|
|
|
ocarina/
|
|
|
|
design.txt
|
2013-08-25 10:56:59 -04:00
|
|
|
ocarina/gui/
|
|
|
|
*
|
2013-05-26 11:08:41 -04:00
|
|
|
ocarina/include/
|
2013-06-30 23:15:13 -04:00
|
|
|
audio.h
|
2013-06-27 16:20:38 -04:00
|
|
|
database.h
|
|
|
|
database.hpp
|
2013-06-27 23:48:09 -04:00
|
|
|
file.h
|
2013-07-04 17:44:40 -04:00
|
|
|
filter.h
|
2013-07-06 11:42:13 -04:00
|
|
|
groups.h
|
2013-07-05 10:39:55 -04:00
|
|
|
idle.h
|
2013-08-07 21:21:13 -04:00
|
|
|
idle.hpp
|
2013-06-27 23:03:14 -04:00
|
|
|
index.h
|
2013-05-26 11:08:41 -04:00
|
|
|
library.h
|
|
|
|
playlist.h
|
2013-06-28 00:12:56 -04:00
|
|
|
prefs.h
|
2013-07-04 18:03:15 -04:00
|
|
|
print.h
|
2013-07-28 22:45:49 -04:00
|
|
|
test.h
|
2013-07-04 18:03:15 -04:00
|
|
|
version.h
|
2013-05-26 11:08:41 -04:00
|
|
|
ocarina/lib/
|
2013-06-30 23:15:13 -04:00
|
|
|
audio.cpp
|
2013-06-27 16:20:38 -04:00
|
|
|
database.cpp
|
2013-06-27 23:48:09 -04:00
|
|
|
file.cpp
|
2013-07-06 11:42:13 -04:00
|
|
|
filter.cpp
|
|
|
|
groups.cpp
|
2013-07-05 10:39:55 -04:00
|
|
|
idle.cpp
|
2013-06-27 23:03:14 -04:00
|
|
|
index.cpp
|
2013-05-26 11:08:41 -04:00
|
|
|
library.cpp
|
|
|
|
playlist.cpp
|
2013-06-28 00:12:56 -04:00
|
|
|
prefs.cpp
|
2013-07-28 22:45:49 -04:00
|
|
|
test.cpp
|
2013-06-27 16:20:38 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-04 18:03:15 -04:00
|
|
|
Install:
|
|
|
|
Ocarina will be compiled into a single executable placed under
|
|
|
|
/usr/bin/. Any extra files needed to run will be placed under
|
|
|
|
/usr/lib/ocarina/.
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-05 12:14:51 -04:00
|
|
|
Printing: (include/print.h>
|
2013-07-04 18:03:15 -04:00
|
|
|
Sometimes text needs to be printed to the screen so users (or debuggers)
|
2013-08-07 21:07:07 -04:00
|
|
|
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.
|
|
|
|
|
2013-07-04 18:03:15 -04:00
|
|
|
|
|
|
|
API:
|
|
|
|
void print(string fmt, ...)
|
2013-08-07 21:07:07 -04:00
|
|
|
Print text to the screen.
|
2013-07-04 18:03:15 -04:00
|
|
|
void dprint(string fmt, ...)
|
2013-08-07 21:07:07 -04:00
|
|
|
Print text to the screen when debugging or testing is enabled.
|
2013-07-04 18:03:15 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-28 22:45:49 -04:00
|
|
|
Test framework: (lib/test.cpp)
|
|
|
|
Unit tests will require a basic framework for manipulating files and
|
|
|
|
directories in the filesystem. Compiling the tests/ directory will
|
|
|
|
enable the CONFIG_TEST option, which in turn compiles the test library.
|
|
|
|
|
|
|
|
API:
|
|
|
|
void rm_test_config()
|
|
|
|
Remove the $XDG_CONFG_HOME/ocarina-test/ directory.
|
|
|
|
void rm_test_data()
|
|
|
|
Remove the $XDG_DATA_HOME/ocarina-test/ directory.
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-27 10:25:15 -04:00
|
|
|
Versioning: (include/version.h)
|
|
|
|
This file contains a simple function for returning a string stating
|
|
|
|
the current version.
|
2013-07-05 10:39:55 -04:00
|
|
|
|
2013-07-27 10:25:15 -04:00
|
|
|
API:
|
|
|
|
const char *get_version();
|
|
|
|
Returns a string describing the current version.
|
2013-07-05 10:39:55 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-06-27 23:48:09 -04:00
|
|
|
On-disk files: (lib/file.cpp)
|
2013-07-27 11:39:05 -04:00
|
|
|
Data will be stored in the user's home directory according to the
|
|
|
|
XDG / freedesktop.org specification. This means storing data in
|
|
|
|
$XDG_DATA_HOME/ocarina{-debug|-test}/ and storing configuration in
|
|
|
|
$XDG_CONFIG_HOME/ocarina{-debug|-test}/. In addition, I will support
|
|
|
|
importing data from Ocarina 5.10 for conversion to the new format.
|
|
|
|
|
|
|
|
In theory my file format will not change often, so it should be
|
|
|
|
possible to use multiple Ocarina versions with the same data. However,
|
|
|
|
should the format need to change I will only support forward
|
|
|
|
compatibility. This means that downgrades will not be possible after
|
|
|
|
a file format change. To keep the code even cleaner, I will only
|
|
|
|
support updating from the previous file format version. This means
|
|
|
|
that legacy support will be removed after the first file format change.
|
2013-06-27 16:20:38 -04:00
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
Items should be written to a file with either a space or new line
|
|
|
|
separating multiple values.
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Notation:
|
2013-06-27 22:41:03 -04:00
|
|
|
File << aaaaa << bbbbb << endl is translated into "aaaaa bbbbb\n"
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- File version:
|
2013-07-27 11:39:05 -04:00
|
|
|
#define FILE_VERSION 0
|
|
|
|
|
|
|
|
- Hint where the file is located:
|
|
|
|
enum FileLocHint {
|
|
|
|
FILE_TYPE_CONFIG,
|
|
|
|
FILE_TYPE_DATA,
|
|
|
|
FILE_TYPE_LEGACY,
|
2013-08-10 23:52:52 -04:00
|
|
|
FILE_TYPE_INVALID,
|
2013-07-27 11:39:05 -04:00
|
|
|
}
|
2013-06-27 23:48:09 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Open mode:
|
2013-06-27 23:48:09 -04:00
|
|
|
enum OpenMode {
|
|
|
|
OPEN_READ,
|
|
|
|
OPEN_WRITE,
|
2013-07-28 19:57:07 -04:00
|
|
|
NOT_OPEN,
|
2013-06-27 23:48:09 -04:00
|
|
|
}
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- File:
|
2013-07-28 22:33:40 -04:00
|
|
|
class File : std::fstream {
|
2013-06-27 23:48:09 -04:00
|
|
|
private:
|
|
|
|
unsigned int version;
|
|
|
|
OpenMode mode;
|
2013-07-27 11:39:05 -04:00
|
|
|
FileLocHint hint;
|
2013-06-27 23:48:09 -04:00
|
|
|
string filepath;
|
2013-07-28 19:57:07 -04:00
|
|
|
|
2013-06-27 23:48:09 -04:00
|
|
|
public:
|
2013-07-27 11:39:05 -04:00
|
|
|
File(string, FileLocHint);
|
2013-07-28 19:57:07 -04:00
|
|
|
~File();
|
2013-07-27 11:39:05 -04:00
|
|
|
const char *get_filepath();
|
2013-07-28 22:33:40 -04:00
|
|
|
const unsigned int get_version();
|
2013-07-27 11:39:05 -04:00
|
|
|
bool exists();
|
|
|
|
bool open(OpenMode);
|
2013-07-28 19:57:07 -04:00
|
|
|
bool close();
|
2013-07-28 22:33:40 -04:00
|
|
|
string getline();
|
2013-06-27 23:48:09 -04:00
|
|
|
}
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- File format:
|
2013-07-27 11:39:05 -04:00
|
|
|
File << FILE_VERSION << endl;
|
2013-07-28 22:33:40 -04:00
|
|
|
File << <OTHER_DATA>;
|
2013-06-27 23:48:09 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- API:
|
2013-07-27 11:39:05 -04:00
|
|
|
File : File(string filepath, FileLocHint hint)
|
|
|
|
Resolve filepath to one of:
|
|
|
|
XDG_{CONFIG|DATA}_HOME/ocarina/filepath
|
|
|
|
XDG_{CONFIG|DATA}_HOME/ocarina-debug/filepath
|
|
|
|
XDG_{CONFIG|DATA}_HOME/ocarina-test/filepath
|
|
|
|
$HOME/.ocarina/
|
|
|
|
$HOME/.ocarina-debug/
|
|
|
|
|
2013-08-10 23:52:52 -04:00
|
|
|
If filepath is an empty string, set the file hint to
|
|
|
|
FILE_TYPE_INVALID and do not set the filepath field.
|
|
|
|
|
2013-07-28 19:57:07 -04:00
|
|
|
File : ~File()
|
|
|
|
Close the file stream if it is open.
|
|
|
|
|
2013-08-14 21:13:32 -04:00
|
|
|
const char *File : get_filepath()
|
2013-07-27 11:39:05 -04:00
|
|
|
Return the full filepath to the file.
|
|
|
|
|
2013-08-14 21:13:32 -04:00
|
|
|
const unsigned int File : get_version()
|
2013-07-28 22:33:40 -04:00
|
|
|
Return the file version number.
|
|
|
|
|
2013-07-27 11:39:05 -04:00
|
|
|
bool File : exists()
|
|
|
|
Return true if the file exists in the filesystem.
|
|
|
|
Return false otherwise.
|
|
|
|
|
|
|
|
bool File : open(OpenMode mode)
|
|
|
|
When opening a file for reading (mode == OPEN_READ),
|
|
|
|
- Return false 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
|
|
|
|
- Create missing directories as needed
|
|
|
|
- Write version information to the start of the file
|
|
|
|
- Return true
|
|
|
|
|
2013-08-10 23:52:52 -04:00
|
|
|
Return false if hint == FILE_TYPE_INVALID.
|
2013-07-28 19:57:07 -04:00
|
|
|
Return false if the file is already open.
|
|
|
|
Return false if there are any other errors.
|
|
|
|
|
2013-07-28 22:33:40 -04:00
|
|
|
bool File : close()
|
2013-07-28 19:57:07 -04:00
|
|
|
Close a file after IO.
|
2013-07-27 11:39:05 -04:00
|
|
|
|
2013-07-28 22:33:40 -04:00
|
|
|
string File : getline();
|
|
|
|
Read an entire line from the file and return it to the caller.
|
|
|
|
In theory a return value optimization will occur so returning
|
|
|
|
a string by value won't be a problem.
|
2013-06-27 23:48:09 -04:00
|
|
|
|
2013-06-27 16:20:38 -04:00
|
|
|
|
|
|
|
|
|
|
|
Database: (lib/database.cpp)
|
|
|
|
Ocarina 5.x created a different save file format for each type of
|
|
|
|
data that needed to be stored (preferences, library paths, playlists).
|
|
|
|
I intend to unify everything into a generic file format that can be
|
|
|
|
accessed through a generic database interface. The database code will
|
|
|
|
be in charge of printing the "valid" bit for each DatabaseEntry so that
|
|
|
|
child classes do not need to call into the parent class. If valid ==
|
|
|
|
true, the DatabaseEntry will be streamed out followed by a newline. If
|
|
|
|
valid == false the database will print the next entry in the vector.
|
|
|
|
|
|
|
|
Modules should inherit from the DatabasEntry class and implement their
|
|
|
|
own read() and write() functions. The "valid" field will be stored
|
|
|
|
before these functions are called, and the entry will be skipped if
|
|
|
|
valid is set to false.
|
|
|
|
|
|
|
|
The Database class is a templated class, so code could potentially
|
|
|
|
get messy. Normal class declarations can still exist in the file
|
|
|
|
include/database.h and member functions can be written in the file
|
|
|
|
include/database.hpp, which will be included by database.h. Any
|
|
|
|
function not relying on a template can be written in lib/database.cpp.
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- DatabaseEntry:
|
2013-06-27 16:20:38 -04:00
|
|
|
class DatabaseEntry { /* let database modify valid flag */
|
|
|
|
private:
|
|
|
|
bool valid;
|
|
|
|
public:
|
2013-08-14 21:13:32 -04:00
|
|
|
virtual void write(File &) = 0;
|
|
|
|
virtual void read(File &) = 0;
|
2013-06-27 16:20:38 -04:00
|
|
|
};
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
File << <CHILD_CLASS_DATA>
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Database:
|
2013-06-27 16:20:38 -04:00
|
|
|
template <class T>
|
|
|
|
class Database {
|
|
|
|
private:
|
|
|
|
unsigned int _size; /* Number of valid rows */
|
2013-06-27 23:48:09 -04:00
|
|
|
File filename;
|
2013-06-27 16:20:38 -04:00
|
|
|
vector<T> db;
|
|
|
|
public:
|
|
|
|
Database::Database(filename);
|
|
|
|
void load();
|
|
|
|
void save();
|
2013-08-14 21:13:32 -04:00
|
|
|
|
2013-06-27 23:16:21 -04:00
|
|
|
unsigned int insert(T);
|
2013-06-27 16:20:38 -04:00
|
|
|
void delete(unsigned int);
|
2013-08-14 21:13:32 -04:00
|
|
|
unsigned int size();
|
|
|
|
unsigned int num_rows();
|
|
|
|
|
|
|
|
unsigned int first();
|
|
|
|
unsigned int last();
|
|
|
|
unsigned int next();
|
2013-08-07 22:00:14 -04:00
|
|
|
T &operator[](unsigned int);
|
2013-06-27 16:20:38 -04:00
|
|
|
};
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 23:48:09 -04:00
|
|
|
File << db.size() << endl
|
2013-06-27 22:41:03 -04:00
|
|
|
File << INDEX_0 << db[INDEX_0].valid << db[INDEX_0] << endl;
|
|
|
|
File << Index_1 << db[INDEX_1].valid << db[INDEX_1] << endl;
|
|
|
|
...
|
2013-06-27 16:20:38 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- API:
|
2013-08-14 21:13:32 -04:00
|
|
|
Database : Database(filename);
|
2013-08-10 23:38:57 -04:00
|
|
|
Initializes database to use ~/.ocarina{-debug}/filename.
|
2013-08-07 22:00:14 -04:00
|
|
|
|
2013-08-14 21:13:32 -04:00
|
|
|
void Database : load();
|
|
|
|
Reads data from file.
|
2013-08-07 22:00:14 -04:00
|
|
|
|
2013-08-14 21:13:32 -04:00
|
|
|
void Database : save();
|
2013-06-27 16:20:38 -04:00
|
|
|
Saves data to file.
|
2013-08-07 22:00:14 -04:00
|
|
|
|
|
|
|
template <class T>
|
2013-08-14 21:13:32 -04:00
|
|
|
unsigned int Database : insert(T &);
|
|
|
|
Adds a new item to the db, returns the id of the item.
|
|
|
|
|
|
|
|
void Database : delete(unsigned int index);
|
|
|
|
Mark db[index] as invalid (quick deletion).
|
|
|
|
|
|
|
|
unsigned int Database : size();
|
|
|
|
Returns number of valid rows in the database.
|
2013-08-07 22:00:14 -04:00
|
|
|
|
2013-08-14 21:13:32 -04:00
|
|
|
unsigned int Database : num_rows();
|
|
|
|
Return db.size().
|
2013-08-07 22:00:14 -04:00
|
|
|
|
2013-08-14 21:13:32 -04:00
|
|
|
unsigned int Database : first();
|
|
|
|
Return the id to the first valid row or return db.size()
|
|
|
|
if there are no valid rows.
|
|
|
|
|
|
|
|
unsigned int Database : last();
|
|
|
|
Return the id of the last valid row or return db.size()
|
|
|
|
if there are no valid rows.
|
|
|
|
|
|
|
|
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.
|
2013-08-07 22:00:14 -04:00
|
|
|
|
|
|
|
template <class T>
|
2013-08-14 21:13:32 -04:00
|
|
|
T &Database : operator[unsigned int index]
|
|
|
|
Return a reference to db[index].
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Idle queue: (lib/idle.cpp)
|
|
|
|
The idle queue is used to schedule tasks to run at a later time. Idle
|
|
|
|
tasks must inherit from the IdleBase class so that multiple templated
|
|
|
|
IdleTask pointers can be placed on the same idle queue.
|
|
|
|
|
|
|
|
- IdleBase:
|
|
|
|
class IdleBase {
|
|
|
|
IdleBase();
|
|
|
|
~IdleBase();
|
|
|
|
virtual void run() = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
- IdleTask:
|
|
|
|
template <class T>
|
|
|
|
class IdleTask : IdleBase {
|
|
|
|
private:
|
|
|
|
void (*func)(T *);
|
|
|
|
T *data;
|
|
|
|
|
|
|
|
public:
|
|
|
|
IdleTask(void (*)(T *), T *);
|
|
|
|
void run();
|
|
|
|
};
|
|
|
|
|
|
|
|
- Queue:
|
|
|
|
deque(IdleBase *> idle_queue;
|
|
|
|
float queued = 0.0
|
|
|
|
float serviced = 0.0
|
|
|
|
|
|
|
|
- API:
|
|
|
|
template <class T>
|
|
|
|
void idle :: schedule(void (*)(T *), T *);
|
|
|
|
Schedule a function to run later (queued++). This should
|
|
|
|
be written in the idle.hpp file since it is a function
|
|
|
|
template.
|
|
|
|
|
|
|
|
bool idle :: run_task()
|
|
|
|
If there are tasks on the queue:
|
|
|
|
run the next task
|
|
|
|
scheduled++
|
|
|
|
return true
|
|
|
|
else:
|
|
|
|
queued = 0
|
|
|
|
scheduled = 0
|
|
|
|
return false
|
|
|
|
|
|
|
|
float idle :: get_progress()
|
|
|
|
Return (serviced / queued) to the caller. If there are no
|
|
|
|
tasks, return 1.0 to indicate that the queue is finished.
|
2013-05-13 23:13:54 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-06-27 23:03:14 -04:00
|
|
|
Index: (lib/index.cpp)
|
|
|
|
An inverted index allows me to map multiple values to a single key.
|
2013-08-25 10:36:15 -04:00
|
|
|
Keys are tracked separately from the rest of the map so they can be
|
|
|
|
found and iterated over without writing ugly code.
|
2013-06-27 23:03:14 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Index:
|
2013-06-27 23:03:14 -04:00
|
|
|
class Index {
|
|
|
|
private:
|
2013-08-25 10:36:15 -04:00
|
|
|
map<string, set<unsigned int>> index;
|
|
|
|
set<string> keys;
|
|
|
|
File file;
|
2013-06-27 23:03:14 -04:00
|
|
|
public:
|
|
|
|
Index::Index(filename);
|
|
|
|
void load();
|
|
|
|
void save();
|
2013-08-25 10:36:15 -04:00
|
|
|
void insert(key, unsigned int);
|
|
|
|
void remove(key);
|
|
|
|
void remove(key, unsigned int);
|
|
|
|
const set<string>::iterator keys_begin();
|
|
|
|
const set<string>::iterator keys_end();
|
|
|
|
const set<unsigned int> &operator[](string);
|
2013-06-27 23:03:14 -04:00
|
|
|
};
|
|
|
|
|
2013-08-25 10:36:15 -04:00
|
|
|
File << keys.size() << endl;
|
2013-06-28 00:12:56 -04:00
|
|
|
File << key << endl;
|
|
|
|
File << map[key].size() << int_0 << int_1 << ... << int_n << endl;
|
2013-06-27 23:03:14 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- API:
|
2013-08-25 10:36:15 -04:00
|
|
|
Index : Index(filename);
|
2013-07-04 17:44:40 -04:00
|
|
|
Initializes an index using ~/.ocarina{-debug}K/filename. Pass
|
|
|
|
an empty string if you do not want this index to be saved.
|
2013-08-25 10:36:15 -04:00
|
|
|
|
|
|
|
void Index : load();
|
2013-06-27 23:03:14 -04:00
|
|
|
Reads data from a file. Call after static initialization of
|
|
|
|
Ocarina to ensure idle tasks are configured
|
2013-08-25 10:36:15 -04:00
|
|
|
|
|
|
|
void Index : save();
|
2013-06-27 23:03:14 -04:00
|
|
|
Saves data to file
|
2013-08-25 10:36:15 -04:00
|
|
|
|
|
|
|
void Index : insert(key, unsigned int);
|
2013-06-27 23:03:14 -04:00
|
|
|
1) If key does not exist, create it.
|
2013-08-25 10:36:15 -04:00
|
|
|
2) Add int to the set for the given key
|
|
|
|
|
|
|
|
void Index : remove(key);
|
|
|
|
Remove a key from the index;
|
|
|
|
|
|
|
|
Index : remove(key, unsigned int);
|
2013-06-27 23:03:14 -04:00
|
|
|
1) Remove int from the set of values associated with key
|
2013-08-25 10:36:15 -04:00
|
|
|
2) If the set is empty, remove the key
|
|
|
|
|
|
|
|
const set<string>::iterator void Index : keys_begin()
|
|
|
|
Return an iterator pointing to the beginning of the keys set.
|
|
|
|
|
|
|
|
const set<string>::iterator void Index : keys_end()
|
|
|
|
Return an iterator pointing to the end of the keys set.
|
|
|
|
|
|
|
|
Index : operator[](string key);
|
2013-06-27 23:03:14 -04:00
|
|
|
Return the set associated with key
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-06 11:42:13 -04:00
|
|
|
Filter: (lib/filter.cpp)
|
2013-08-25 10:36:15 -04:00
|
|
|
Filtering is used to generate a subset of songs displayed by the UI to
|
|
|
|
that users can choose from. The inverted index is generated at startup
|
|
|
|
so there is no need for a remove() function, since it will be wiped
|
|
|
|
the next time the application starts.
|
2013-07-06 11:42:13 -04:00
|
|
|
|
|
|
|
- Index:
|
|
|
|
Index filter_index("");
|
2013-08-25 10:36:15 -04:00
|
|
|
map<string, string> lowercase_cache;
|
|
|
|
unsigned int lowercase_cache_hits;
|
2013-07-06 11:42:13 -04:00
|
|
|
|
|
|
|
- Parsing:
|
|
|
|
1) Convert the provided string into a list of words, using whitespace
|
2013-08-25 10:36:15 -04:00
|
|
|
and the following characters as delimiters: \/,;()_-~+"
|
2013-07-06 11:42:13 -04:00
|
|
|
|
|
|
|
For each word:
|
|
|
|
2) Check the lowercase_cache to see if we have seen the word before,
|
|
|
|
a) If we have, return the stored string
|
|
|
|
b) Convert the string to lowercase and strip out remaining
|
|
|
|
special characters. Add the result to the lowercase_cache;
|
|
|
|
|
|
|
|
- API:
|
2013-08-25 10:36:15 -04:00
|
|
|
void filter :: add(string, track_id);
|
|
|
|
Parse the string into substrings following the "Parsing"
|
|
|
|
section (above). Add each (substring, track_id) pair to the
|
|
|
|
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}.
|
|
|
|
|
2013-07-06 11:42:13 -04:00
|
|
|
void filter :: search(string, set<track_id> &);
|
2013-08-25 10:36:15 -04:00
|
|
|
Parse the string into substrings following the "Parsing"
|
|
|
|
section (above). We want to find track_ids that match ALL
|
|
|
|
substrings, so take the intersection of all sets returned by
|
|
|
|
the filter_index for a given substring.
|
|
|
|
|
|
|
|
void filter :: print_cache_stats();
|
|
|
|
Print cache hit and size information.
|
|
|
|
|
|
|
|
void filter :: get_index();
|
|
|
|
Return the index storing all the filter data.
|
|
|
|
(Only available if -DCONFIG_TEST is set)
|
2013-07-06 11:42:13 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
2013-08-28 20:59:57 -04:00
|
|
|
In Ocarina 6.0, groups are a wrapper around a specific index. Future
|
|
|
|
releases will store user-defined groups in a file on disk.
|
|
|
|
|
2013-07-06 11:42:13 -04:00
|
|
|
- Index:
|
2013-08-28 20:59:57 -04:00
|
|
|
Index group_idx()
|
2013-07-06 11:42:13 -04:00
|
|
|
|
|
|
|
- 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
|
2013-08-28 20:59:57 -04:00
|
|
|
void group :: add(name, track_id)
|
2013-07-06 11:42:13 -04:00
|
|
|
group_idx.insert(name, track_id);
|
2013-08-28 20:59:57 -04:00
|
|
|
void group :: del(name, track_id)
|
2013-07-06 11:42:13 -04:00
|
|
|
grou_idx.delete(name, track_id)
|
2013-08-28 20:59:57 -04:00
|
|
|
void void group :: list(list<string> &);
|
|
|
|
return group_idx.keys();
|
|
|
|
void group :: get_tracks(name):
|
|
|
|
return group_idx[name]
|
2013-07-06 11:42:13 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-06-19 14:07:36 -04:00
|
|
|
Library: (lib/library.cpp)
|
2013-06-27 22:21:07 -04:00
|
|
|
The library manages databases containing track information added by the
|
|
|
|
user. Ocarina 6 splits the library into multiple database tables for
|
|
|
|
storing content. The library will exist in a library namespace to
|
|
|
|
to make functions and classes more unique.
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 22:21:07 -04:00
|
|
|
When a library : Track is created, it should be added to the "Library"
|
|
|
|
group if it is NOT a member of the banned songs group.
|
2013-06-19 14:07:36 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Album:
|
2013-06-27 22:21:07 -04:00
|
|
|
class library :: Album : public DatabaseEntry {
|
2013-06-19 14:07:36 -04:00
|
|
|
string name;
|
|
|
|
short year;
|
|
|
|
};
|
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
File << year << name
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Artist:
|
2013-06-27 22:21:07 -04:00
|
|
|
class library :: Artist : public DatabaseEntry {
|
2013-06-19 14:07:36 -04:00
|
|
|
string name;
|
|
|
|
};
|
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
File << name
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Genre:
|
2013-06-27 22:21:07 -04:00
|
|
|
class library :: Genre : public DatabaseEntry {
|
2013-06-21 18:18:53 -04:00
|
|
|
string name;
|
|
|
|
};
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
File << name
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Path:
|
2013-06-27 22:41:03 -04:00
|
|
|
class library :: Path : public DatabaseEntry {
|
2013-06-27 22:21:07 -04:00
|
|
|
string root_path;
|
2013-06-27 16:20:38 -04:00
|
|
|
bool enabled;
|
|
|
|
};
|
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
File << enabled << root_path
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Track:
|
2013-06-27 22:21:07 -04:00
|
|
|
class library :: Track : public DatabaseEntry {
|
2013-06-27 16:20:38 -04:00
|
|
|
unsigned int artist_id;
|
|
|
|
unsigned int album_id;
|
|
|
|
unsigned int genre_id;
|
|
|
|
unsigned int library_id;
|
|
|
|
|
|
|
|
short track;
|
|
|
|
short last_year;
|
|
|
|
short last_month;
|
|
|
|
short last_day;
|
|
|
|
unsigned int play_count;
|
|
|
|
unsigned int length;
|
2013-08-28 20:59:57 -04:00
|
|
|
bool banned;
|
2013-06-27 16:20:38 -04:00
|
|
|
string title;
|
|
|
|
string length_str;
|
|
|
|
string filepath;
|
|
|
|
};
|
|
|
|
|
2013-06-27 22:41:03 -04:00
|
|
|
File << artist_id << album_id << genre_id << library_id << track << last_year
|
2013-08-28 20:59:57 -04:00
|
|
|
File << last_year << last_month << last_day << play_count << length << banned << endl
|
2013-06-27 22:41:03 -04:00
|
|
|
File << title << endl;
|
|
|
|
File << filepath << endl;
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Track: /* This struct lies outside the library namespace */
|
2013-06-27 22:41:03 -04:00
|
|
|
struct Track {
|
2013-06-27 22:21:07 -04:00
|
|
|
library :: Album *album;
|
|
|
|
library :: Artist *artist;
|
|
|
|
library :: Genre *genre;
|
|
|
|
library :: Library *library;
|
|
|
|
library :: Track *track;
|
|
|
|
};
|
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Databases:
|
2013-06-27 23:16:21 -04:00
|
|
|
Database<library :: Album> album_db(album.db);
|
|
|
|
Database<library :: Artist> artist_db(artist.db);
|
|
|
|
Database<library :: Album> genre_db(genre.db);
|
|
|
|
Database<library :: Library> library_db(library.db);
|
|
|
|
Database<library :: Track> track_db(track.db);
|
2013-06-27 16:20:38 -04:00
|
|
|
|
2013-06-28 00:12:56 -04:00
|
|
|
- Updating algorithm:
|
2013-06-27 22:21:07 -04:00
|
|
|
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.
|
2013-07-04 17:44:40 -04:00
|
|
|
2b) Else, add track to the library, to the groups "All Music" and
|
|
|
|
"Library", and then to the filter index.
|
2013-06-27 23:03:14 -04:00
|
|
|
3) Save all databases
|
2013-06-27 22:21:07 -04:00
|
|
|
|
|
|
|
The taglib library should be used for finding artist, album, etc. tags
|
|
|
|
for each track.
|
|
|
|
|
|
|
|
Use idle tasks for step 2 to break up tagging new files into chunks.
|
|
|
|
This way the user will still be able to use Ocarina and scanning can
|
|
|
|
happen while idle.
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-08-28 20:59:57 -04:00
|
|
|
- Testing:
|
|
|
|
A test library should be created to test adding tags and anything else.
|
|
|
|
The command `arecord -d 10 </dev/zero | lame - -b 32 -h silence.mp3`
|
|
|
|
will create a 10 second long, silent mp3 file. Do something with this
|
|
|
|
command and figure out how to apply tags!
|
|
|
|
|
2013-05-26 11:08:41 -04:00
|
|
|
- API
|
2013-06-27 23:48:09 -04:00
|
|
|
library :: init();
|
|
|
|
Initialize all databases
|
2013-06-27 22:21:07 -04:00
|
|
|
library :: add_path(string dir);
|
2013-05-26 11:08:41 -04:00
|
|
|
Add new row to paths table, update
|
2013-06-27 22:21:07 -04:00
|
|
|
library :: del_path(unsigned int lib_id);
|
|
|
|
Invalidate a path row
|
|
|
|
library :: update_path(lib_id);
|
|
|
|
Update the given library path, if valid.
|
|
|
|
const Database<LibraryEntry> &library :: get_db();
|
|
|
|
Returns the database containing library information.
|
|
|
|
struct Track library :: resolve(track_id)
|
|
|
|
Fill out a Track structure for the provided track_id
|
2013-05-26 11:08:41 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-27 10:25:15 -04:00
|
|
|
Playlist: (lib/playlist.cpp)
|
|
|
|
Playlists are a list of songs that the user has configured to play. I
|
|
|
|
will create a pool of playlists that will be filled by user actions.
|
|
|
|
|
|
|
|
Playlists will be put on a "deck" that is used to give an order to the
|
|
|
|
next songs played. When deck :: next() is called, find the first
|
|
|
|
playlist with PL_ENABLED set and call that playlists next() function.
|
|
|
|
|
|
|
|
When a playlist is empty, remove it from the deck.
|
|
|
|
|
|
|
|
- Flags:
|
|
|
|
enum playlist_flags {
|
|
|
|
PL_ENABLED (1 << 0),
|
|
|
|
PL_RANDOM (1 << 1),
|
|
|
|
PL_DRAIN (1 << 2),
|
|
|
|
};
|
|
|
|
|
|
|
|
- Playlist:
|
|
|
|
class Playlist : public Database {
|
|
|
|
private:
|
|
|
|
database<track_id> tracks; /* Keyed on track id */
|
|
|
|
unsigned int cur;
|
|
|
|
unsigned int flags;
|
|
|
|
public:
|
|
|
|
Playlist();
|
|
|
|
void add(vector<track_id> &);
|
|
|
|
void del(vector<track_id> &);
|
|
|
|
void set_flag(playlist_flags);
|
|
|
|
const unsigned int get_flags();
|
|
|
|
unsigned int size()
|
|
|
|
|
|
|
|
File &operator<<(File &);
|
|
|
|
File &operator>>(File &);
|
|
|
|
|
|
|
|
void sort();
|
|
|
|
void next();
|
|
|
|
}
|
|
|
|
|
|
|
|
File << flags << cur << tracks[0] << tracks[1] << ... << tracks[N];
|
|
|
|
|
|
|
|
- Deck:
|
|
|
|
list<Playlist> deck;
|
|
|
|
File << deck[0] << endl;
|
|
|
|
File << deck[1] << endl;
|
|
|
|
File << deck[N] << endl;
|
|
|
|
|
|
|
|
- API
|
|
|
|
deck :: init();
|
|
|
|
Read in the playlist file
|
|
|
|
deck :: new();
|
|
|
|
Adds a new playlist to the deck
|
|
|
|
deck :: rm(N)
|
|
|
|
Removes playlist N from the deck
|
|
|
|
Playlist *deck :: get(N)
|
|
|
|
Return playlist N from the deck
|
|
|
|
|
|
|
|
- TODO <<<<<
|
|
|
|
What if each playlist has its own playlist_id for tracks? This would
|
|
|
|
allow for simpler removals, since I won't need to search for a track id.
|
|
|
|
I can easily create a function for mapping a list of playlist_ids to
|
|
|
|
track_ids...
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-08-07 21:07:07 -04:00
|
|
|
Audio: (lib/audio.cpp)
|
|
|
|
This file will introduce an "audio" namespace containing all of the
|
|
|
|
functions interacting with gstreamer. This will create a wrapper
|
|
|
|
namespace that will be easier to work with than using raw gstreamer
|
|
|
|
functions.
|
|
|
|
|
|
|
|
The audio layer will also control the "pause after N tracks" feature
|
|
|
|
so songs can be loaded without neeting to pass in a "begin playback"
|
|
|
|
flag every time.
|
|
|
|
|
|
|
|
- Internal:
|
|
|
|
Set up a message bus to look for end-of-stream and error messages so
|
|
|
|
the next song can be played. This function should call the play
|
|
|
|
function after loading a track and after checking the "pause after N"
|
|
|
|
count.
|
|
|
|
|
|
|
|
- API:
|
|
|
|
audio :: init(argc, argv)
|
|
|
|
Initialize the gstreamer layer and reload the track that was
|
|
|
|
last loaded before shutdown. Please only pass --gst-* options
|
|
|
|
for argv.
|
|
|
|
audio :: load_file(filepath)
|
|
|
|
Loads a file path but does not begin playback.
|
|
|
|
audio :: play()
|
|
|
|
Begin playback
|
|
|
|
audio :: pause()
|
|
|
|
Pause playback
|
|
|
|
audio :: seek_to(X)
|
|
|
|
Seek to a position X seconds into the track
|
|
|
|
audio :: stop()
|
|
|
|
pause()
|
|
|
|
seek_to(0)
|
|
|
|
audio :: pause_after(N)
|
|
|
|
Pause after N tracks, pass a negative number to disable.
|
|
|
|
audio :: position()
|
|
|
|
Return the number of seconds that the song has played
|
|
|
|
audio :: duration()
|
|
|
|
Return the duration of the current song in seconds
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-07-27 10:25:15 -04:00
|
|
|
Preferences: (lib/prefs.cpp)
|
|
|
|
Preferences make use of a special index where the set<int> is always
|
|
|
|
size 1. Preferences will be in the prefs namespace.
|
|
|
|
|
|
|
|
- Index:
|
|
|
|
Index prefs(prefs.idx);
|
|
|
|
|
|
|
|
- API:
|
|
|
|
prefs :: set(string, val);
|
|
|
|
prefs.replace(string, val);
|
|
|
|
prefs :: get(string)
|
|
|
|
return prefs[string].begin()
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-08-25 10:56:59 -04:00
|
|
|
Gui: (ocarina/*)
|
|
|
|
The GUI will be written in C++ using gtkmm3 for (hopefully) cleaner code.
|
|
|
|
|
|
|
|
- Design requirements:
|
|
|
|
- gtkmm3
|
|
|
|
- Front-end for the library to add, remove and modify paths
|
|
|
|
- This can be hidden since it's not a common task
|
|
|
|
- Double-click to play songs
|
|
|
|
- Front-end for groups to add and remove tracks
|
|
|
|
- This is a common task and should not be hidden
|
|
|
|
- Don't save / restore window size
|
|
|
|
- Some window managers already do this automatically
|
|
|
|
- Set reasonable defaults
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-06-21 18:18:53 -04:00
|
|
|
Future work:
|
|
|
|
I want to set reasonable expectations for Ocarina 6 so that I don't
|
|
|
|
have to spend a large amount of time coding before releasing something
|
|
|
|
to the wild. This section will be a list of features that I want, but
|
|
|
|
should be deferred to a future release so basic support can be coded.
|
|
|
|
|
|
|
|
Hint: If feature B depends on A, implement A in 6.x and B in 6.x+1
|
|
|
|
|
2013-07-04 17:44:40 -04:00
|
|
|
- New default groups: (6.1)
|
|
|
|
Unplayed tracks
|
|
|
|
|
2013-06-21 18:18:53 -04:00
|
|
|
- Categories: (6.1)
|
|
|
|
Use these to make "groups of groups" for better organization.
|
|
|
|
Different categories can include Album, Artist and Genere
|
|
|
|
dynamic groups in addition to user created groups (see below)
|
|
|
|
|
|
|
|
The Artist, Album and Genre "tables" can be used to populate
|
|
|
|
these categories.
|
|
|
|
|
|
|
|
- User created song groups: (6.2)
|
|
|
|
Basic add and remove features can be implemented using the
|
|
|
|
Library and Banned Songs groups. This will give me a chance
|
|
|
|
to test saving groups on a small scale before letting users
|
|
|
|
create anything they want.
|
|
|
|
|
|
|
|
- Save a user's playlist as a group: (6.2)
|
|
|
|
|
|
|
|
- Library defragment: (6.1)
|
|
|
|
Ocarina 6.0 will leave holes in the library when tracks are
|
|
|
|
deleted, potentially leading to fragmentation and larger-than-
|
|
|
|
needed file sizes. A "defragment" utility can be created to
|
|
|
|
clean up unused slots.
|
|
|
|
|
|
|
|
To help with fixing groups, a mapping of (old values) ->
|
|
|
|
(new values) should be kept.
|
2013-06-30 23:15:13 -04:00
|
|
|
|
|
|
|
- Fix track durations: (6.1)
|
|
|
|
Some tracks in my library are tagged with the wrong duration,
|
|
|
|
so fix them as they are played.
|
|
|
|
|
|
|
|
- Track tag editor: (6.2)
|
|
|
|
Make a pop-up window for editing the tags of a track. Be sure
|
|
|
|
to update the library information and the on-disk file.
|
2013-07-04 17:44:40 -04:00
|
|
|
|
|
|
|
- Album art: (6.1)
|
|
|
|
|
|
|
|
- Playlist custom sorting: (6.1)
|
|
|
|
Click column headers to choos sort order
|
|
|
|
Keep a list of fields that the user has selected and place new
|
|
|
|
fields in the front of this list. Use a recursive stable sort
|
|
|
|
to do the sorting.
|
2013-08-14 21:13:32 -04:00
|
|
|
|
|
|
|
- Better design file format: (6.1)
|
2013-08-25 10:36:15 -04:00
|
|
|
Todo list in each document instead of all at once in the footer.
|
|
|
|
Leave the Todo list out of the official "design document" and
|
|
|
|
keep it in each individual section instead.
|
2013-08-14 21:13:32 -04:00
|
|
|
XML?
|
|
|
|
Code formatting?
|
2013-08-25 10:36:15 -04:00
|
|
|
|
|
|
|
- Copy a song group to a different directory: (6.x)
|
|
|
|
This can be useful for keeping an external device (like an
|
|
|
|
Android phone) updated with the music you want to listen to.
|
|
|
|
Complications: I have an mp3 mirror of all my music, and I
|
|
|
|
want the mp3s to be synced. Perhaps track mirrors in Ocarina?
|
|
|
|
|
|
|
|
- Mirror directory: (6.x)
|
|
|
|
I rip music to .flac, but keep an mp3 mirror directory to sync
|
|
|
|
to other computers and phones. An Ocarina tool to manage a
|
|
|
|
COMPLETE library mirror might be a good idea so I no longer
|
|
|
|
need to manage it externally. This can still be done with a
|
|
|
|
script, a cron job, and maybe a "mirror this track" option in
|
|
|
|
the library? Perhaps create a mirror group?
|