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-05-13 23:13:54 -04:00
|
|
|
Files:
|
2013-05-26 11:08:41 -04:00
|
|
|
ocarina/
|
|
|
|
design.txt
|
|
|
|
ocarina/gui/
|
|
|
|
ocarina/include/
|
2013-06-27 16:20:38 -04:00
|
|
|
database.h
|
|
|
|
database.hpp
|
2013-06-21 18:18:53 -04:00
|
|
|
group.h
|
2013-05-26 11:08:41 -04:00
|
|
|
library.h
|
|
|
|
playlist.h
|
|
|
|
ocarina/lib/
|
2013-06-27 16:20:38 -04:00
|
|
|
database.cpp
|
2013-06-21 18:18:53 -04:00
|
|
|
group.cpp
|
2013-05-26 11:08:41 -04:00
|
|
|
library.cpp
|
|
|
|
playlist.cpp
|
|
|
|
ocarina/tests/
|
|
|
|
|
2013-06-19 14:07:36 -04:00
|
|
|
$HOME/.ocarina{-debug}/
|
2013-06-27 16:20:38 -04:00
|
|
|
album.db
|
|
|
|
artist.db
|
|
|
|
genre.db
|
|
|
|
library.db
|
|
|
|
track.db
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
On-disk files:
|
|
|
|
I use the disk to store data between sessions, this could include
|
|
|
|
library state and user preferences. In theory, file formats do not
|
|
|
|
change often so updating between file formats should be possible.
|
|
|
|
Supporting all previous file formats can create a lot of clutter in
|
|
|
|
the code, so I will ONLY support updating from the previous file-format
|
|
|
|
version. Ocarina 5.x used two different numbers to represent the
|
|
|
|
current file format (library = 2 and playlist = 3). I want to unify
|
|
|
|
this into a single number shared across all files for simplicity, and
|
|
|
|
then create a class to read and write data on disk.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
Structures and constants:
|
|
|
|
#define FILE_VERSION 4
|
|
|
|
|
|
|
|
class DatabaseEntry { /* let database modify valid flag */
|
|
|
|
private:
|
|
|
|
bool valid;
|
|
|
|
public:
|
|
|
|
virtual istream &operator>>(istream &) = 0;
|
|
|
|
virtual ostream &operator<<(ostream &) = 0;
|
|
|
|
friend class Database;
|
|
|
|
};
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 16:20:38 -04:00
|
|
|
template <class T>
|
|
|
|
class Database {
|
|
|
|
private:
|
|
|
|
unsigned int _size; /* Number of valid rows */
|
|
|
|
string filename;
|
|
|
|
vector<T> db;
|
|
|
|
public:
|
|
|
|
Database::Database(filename);
|
|
|
|
void load();
|
|
|
|
void save();
|
|
|
|
void insert(T);
|
|
|
|
void delete(unsigned int);
|
|
|
|
const unsigned int &size();
|
|
|
|
const T &operator[](unsigned int);
|
|
|
|
};
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 16:20:38 -04:00
|
|
|
File formats:
|
|
|
|
Database:
|
|
|
|
FILE_VERSION db.size()
|
|
|
|
INDEX db[INDEX].valid DatabaseEntry
|
|
|
|
INDEX db[INDEX].valid DatabaseEntry
|
|
|
|
...
|
|
|
|
|
|
|
|
DatabaseEntry:
|
|
|
|
<CHILD_CLASS_DATA>
|
|
|
|
|
|
|
|
API:
|
|
|
|
Database.Database(filename);
|
|
|
|
Initializes database to use ~/.ocarina{-debug}/filename
|
|
|
|
Database.load();
|
|
|
|
Reads data from file. Call after static initialization of
|
|
|
|
Ocarina to ensure idle tasks are configured.
|
|
|
|
Database.save();
|
|
|
|
Saves data to file.
|
|
|
|
Database.insert(T &);
|
|
|
|
Adds a new item to the db
|
|
|
|
Database.delete(unsigned int index);
|
|
|
|
Mark db[index] as invalid (quick deletion)
|
|
|
|
Database.size();
|
|
|
|
Returns number of valid rows in the database
|
|
|
|
Database.operator[unsigned int index]
|
|
|
|
Return a reference to db[index]
|
2013-05-13 23:13:54 -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-27 22:21:07 -04:00
|
|
|
Structures:
|
|
|
|
class library :: Album : public DatabaseEntry {
|
2013-06-19 14:07:36 -04:00
|
|
|
string name;
|
|
|
|
short year;
|
|
|
|
};
|
|
|
|
|
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: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:21:07 -04:00
|
|
|
class library :: Root : public DatabaseEntry {
|
|
|
|
string root_path;
|
2013-06-27 16:20:38 -04:00
|
|
|
bool enabled;
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
string title;
|
|
|
|
string length_str;
|
|
|
|
string filepath;
|
|
|
|
};
|
|
|
|
|
2013-06-27 22:21:07 -04:00
|
|
|
struct Track { /* Leave this outside of the namespace */
|
|
|
|
library :: Album *album;
|
|
|
|
library :: Artist *artist;
|
|
|
|
library :: Genre *genre;
|
|
|
|
library :: Library *library;
|
|
|
|
library :: Track *track;
|
|
|
|
};
|
|
|
|
|
|
|
|
Database<library :: Album> album_db;
|
|
|
|
Database<library :: Artist> artist_db;
|
|
|
|
Database<library :: Album> genre_db;
|
|
|
|
Database<library :: Library> library_db;
|
|
|
|
Database<library :: Track> track_db;
|
2013-06-27 16:20:38 -04:00
|
|
|
|
|
|
|
File formats:
|
|
|
|
Album:
|
|
|
|
year name
|
|
|
|
|
|
|
|
Artist:
|
|
|
|
name
|
|
|
|
|
|
|
|
Genre:
|
|
|
|
name
|
|
|
|
|
|
|
|
Track:
|
2013-06-27 22:21:07 -04:00
|
|
|
artist_id album_id genre_id library_id track last_year last_month last_day play_count length length_str
|
2013-06-27 16:20:38 -04:00
|
|
|
title
|
|
|
|
filepath
|
|
|
|
|
|
|
|
Library:
|
|
|
|
enabled base_path;
|
2013-05-26 11:08:41 -04:00
|
|
|
|
2013-06-27 22:21:07 -04:00
|
|
|
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.
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
- API
|
|
|
|
/* Path management */
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Playlist: (lib/playlist.cpp)
|
|
|
|
A playlist is a simple list of songs that can be played either randomly
|
|
|
|
or in a user-defined order. It would probably be best to use a linked
|
|
|
|
list or vector to represent playlists, rather than creating a SQLite
|
|
|
|
table. I will be able to easily rearrange tracks in the playlist this
|
|
|
|
way. This will also make it easier to deal with playlist renames and
|
|
|
|
reordering by the user.
|
|
|
|
|
|
|
|
- API
|
|
|
|
/* Playlist management */
|
|
|
|
new_playlist();
|
|
|
|
del_playlist(playlist);
|
|
|
|
add_to_playlist(playlist, songid);
|
|
|
|
rm_from_playlist(playlist, songid);
|
|
|
|
playlist_size(playlist)
|
|
|
|
set_flag(playlist, flag)
|
|
|
|
|
|
|
|
- Flags
|
|
|
|
PL_ENABLED (1 << 0)
|
|
|
|
PL_RANDOM (1 << 1)
|
|
|
|
PL_DRAIN (1 << 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
2013-06-21 18:18:53 -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.
|
|
|
|
|
|
|
|
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
|
|
|
|
Unplayed tracks
|
|
|
|
Tracks with a play count of 0
|
2013-05-26 11:08:41 -04:00
|
|
|
|
|
|
|
|
|
|
|
- API
|
2013-06-21 18:18:53 -04:00
|
|
|
list_groups();
|
|
|
|
Return a list of group names
|
|
|
|
group_get_tracks(name):
|
|
|
|
Return a list of tracks that are in group "name"
|
|
|
|
|
|
|
|
Track.add_to_group(name);
|
|
|
|
Add a track to a group
|
|
|
|
Track.rm_from_group(name);
|
|
|
|
Remove a track from a group
|
|
|
|
|
|
|
|
- Design TODO <<<<<
|
|
|
|
- I need a way to loop over each track in the library to create
|
|
|
|
dynamic groups. Whole library iterator?
|
|
|
|
- A "Track" class with functions for accessing tags and with access
|
|
|
|
to groups. Perhaps make "Track" its own file, outside of the
|
|
|
|
library and group code?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
- 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.
|