database: Design and test updates
I add: - Autosaving option - Real iterators - Better accessor functions The new design and unit test also allows me to remove the 300,000+ line "database.good" file that the old tests were based on. Signed-off-by: Anna Schumaker <schumaker.anna@gmail.com>
This commit is contained in:
parent
b251f27bb5
commit
fc8dc9d55e
258
DESIGN
258
DESIGN
|
@ -115,7 +115,7 @@ On-disk files:
|
|||
}
|
||||
|
||||
- File:
|
||||
class File : std::fstream {
|
||||
class File : public std::fstream {
|
||||
private:
|
||||
unsigned int version;
|
||||
OpenMode mode;
|
||||
|
@ -207,8 +207,7 @@ Database Entry:
|
|||
|
||||
virtual void write(File &) = 0;
|
||||
virtual void read(File &) = 0;
|
||||
virtual void print() = 0;
|
||||
}
|
||||
};
|
||||
|
||||
- API:
|
||||
DatabaseEntry :: DatabaseEntry():
|
||||
|
@ -227,33 +226,138 @@ Database Entry:
|
|||
void DatabaseEntry :: read(File &);
|
||||
This function is called to read a DatabaseEntry from a file.
|
||||
|
||||
void DatabaseEntry :: print();
|
||||
This function will only exist if CONFIG_TEST is enabled and is
|
||||
called to print a DatabaseEntry to console.
|
||||
|
||||
|
||||
|
||||
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.
|
||||
Database:
|
||||
Databases are a generic store for information used by Ocarina. Users
|
||||
need to inherit from a DatabaseEntry class (above) to properly use a
|
||||
database.
|
||||
|
||||
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.h and member functions are 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.
|
||||
|
||||
- Automatic saving
|
||||
Databases can save automatically whenever a new value is inserted or
|
||||
deleted. This will be more efficient for Databases that do not change
|
||||
often but will be a huge performance hit for Databases that have many
|
||||
changes at once. All databases will be loaded automatically from disk.
|
||||
|
||||
- Primary keys
|
||||
Databases use the primary_key() function of a DatabaseEntry to enforce
|
||||
uniqueness. This key is used when inserting a new value into the
|
||||
Database, and will not be updated after.
|
||||
|
||||
- Valid bit
|
||||
The "valid" bit of a DatabaseEntry is completely managed by the entry's
|
||||
Database container. It will be set to true when an entry is inserted
|
||||
and false when deleted. The Database is also in charge of writing the
|
||||
valid bit to file.
|
||||
|
||||
- Database:
|
||||
template <class T>
|
||||
class Database {
|
||||
private:
|
||||
std::vector<T> _db;
|
||||
std::map<std::string, unsigned int> _keys;
|
||||
unsigned int _size; /* Number of valid rows */
|
||||
bool _autosave;
|
||||
File _file;
|
||||
|
||||
public:
|
||||
typedef std::vector<T>::iterator iterator;
|
||||
typedef std::vector<T>::const_iterator const_iterator;
|
||||
|
||||
Database(std::string, bool);
|
||||
void save();
|
||||
void load();
|
||||
|
||||
unsigned int insert(T);
|
||||
void remove(unsigned int);
|
||||
unsigned int size();
|
||||
unsigned int actual_size();
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
iterator next(iterator &);
|
||||
|
||||
iterator at(unsigned int);
|
||||
iterator find(const std::string &);
|
||||
};
|
||||
|
||||
- File format:
|
||||
File << db.size() << endl
|
||||
File << INDEX_0 << db[INDEX_0].valid << db[INDEX_0] << endl;
|
||||
File << INDEX_1 << db[INDEX_1].valid << db[INDEX_1] << endl;
|
||||
...
|
||||
|
||||
- API:
|
||||
Database :: Database(std::string filepath, bool autosave);
|
||||
Initialize a database using "filepath" as a location to store
|
||||
data on disk. If the file already exists, read the data into
|
||||
the backing vector.
|
||||
|
||||
void Database :: save();
|
||||
Save the database to disk.
|
||||
|
||||
void Database :: load();
|
||||
Load the database from disk.
|
||||
|
||||
unsigned int Database :: insert(T &item);
|
||||
Look up the item in the _keys map.
|
||||
If we find an item with the same key:
|
||||
- Return the index of the item to the caller.
|
||||
Otherwise:
|
||||
- Add the new item to the end of the _db.
|
||||
- Add the new item to _keys.
|
||||
- Set item.valid = true.
|
||||
_ Increment _size.
|
||||
- If autosave == true: save().
|
||||
- Return the index of the new item.
|
||||
|
||||
unsigned int Database :: remove();
|
||||
- Remove the item from the _keys map.
|
||||
- Set item.valid = false.
|
||||
- If autosave == true: save().
|
||||
- Decrement _size.
|
||||
|
||||
unsigned int Database :: size();
|
||||
return _size;
|
||||
|
||||
unsigned int Database :: actual_size();
|
||||
return _db.size();
|
||||
|
||||
iterator Database :: begin();
|
||||
Return _db.end() if there are no valid entries
|
||||
If the first entry is valid:
|
||||
- return _db.begin();
|
||||
Otherwise:
|
||||
- return an iterator to the first valid entry.
|
||||
|
||||
iterator Database :: end();
|
||||
return _db.end();
|
||||
|
||||
iterator Database :: next(iterator &cur);
|
||||
Return the next DatabaseEntry with valid == true or _db.end()
|
||||
if there are no valid entries left.
|
||||
|
||||
iterator Database :: at(unsigned int i);
|
||||
If _db[i].valid == true:
|
||||
Return an iterator pointing to _db[i];
|
||||
Otherwise:
|
||||
Return _db.end();
|
||||
|
||||
iterator Database :: find(const std::string &key);
|
||||
If key is in the _keys map:
|
||||
Return an iterator pointing to the corresponding entry.
|
||||
Otherwise:
|
||||
Return _db.end();
|
||||
|
||||
|
||||
|
||||
|
||||
- IndexEntry:
|
||||
class IndexEntry : public DatabaseEntry {
|
||||
public:
|
||||
|
@ -269,116 +373,6 @@ Database: (lib/database.cpp)
|
|||
File << key << endl;
|
||||
File << values.size() << values[0] << .. << values[N] << endl;
|
||||
|
||||
- Database:
|
||||
template <class T>
|
||||
class Database {
|
||||
private:
|
||||
vector<T> db;
|
||||
map<std::string, unsigned int> keys;
|
||||
unsigned int _size; /* Number of valid rows */
|
||||
File file;
|
||||
|
||||
public:
|
||||
Database::Database(filename, flags);
|
||||
void load();
|
||||
void save();
|
||||
void clear();
|
||||
void print();
|
||||
void print_keys();
|
||||
|
||||
unsigned int insert(T);
|
||||
void remove(unsigned int);
|
||||
unsigned int size();
|
||||
unsigned int num_rows();
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
File << db.size() << endl
|
||||
File << INDEX_0 << db[INDEX_0].valid << db[INDEX_0] << endl;
|
||||
File << INDEX_1 << db[INDEX_1].valid << db[INDEX_1] << endl;
|
||||
...
|
||||
|
||||
- API:
|
||||
Database :: Database(filename, flags);
|
||||
Initializes database to use ~/.ocarina{-debug}/filename.
|
||||
Set up flags.
|
||||
|
||||
void Database :: load();
|
||||
Reads a saved database from disk.
|
||||
|
||||
void Database :: save();
|
||||
Saves the database to disk.
|
||||
|
||||
void Database :: clear();
|
||||
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_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_TEST is enabled.
|
||||
Print out the collected primary keys in the database.
|
||||
|
||||
template <class T>
|
||||
unsigned int Database :: insert(T &);
|
||||
Adds a new item to the db and marks it as valid. A reverse
|
||||
mapping is also created into the keys map to map the primary
|
||||
key back to the id of the newly added item.
|
||||
|
||||
void Database :: remove(unsigned int index);
|
||||
Mark db[index] as invalid (quick deletion).
|
||||
|
||||
unsigned int Database :: size();
|
||||
Returns number of valid rows in the database.
|
||||
|
||||
unsigned int Database :: num_rows();
|
||||
Return db.size().
|
||||
|
||||
unsigned int Database :: first();
|
||||
Return the id to the first valid row. Return db.size() if
|
||||
there are no valid rows.
|
||||
|
||||
unsigned int Database :: last();
|
||||
Return the id of the last valid row. 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 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 -EEXIST if the key is not found in the mapping.
|
||||
Throw -EINVAL if the key points to an invalid entry.
|
||||
|
||||
template <class T>
|
||||
T &Database :: operator[](unsigned int index);
|
||||
Return a reference to db[index].
|
||||
Throw -EEXIST if index is out of range.
|
||||
Throw -EINVAL if index is invalid.
|
||||
|
||||
|
||||
|
||||
Filter: (lib/filter.cpp)
|
||||
|
|
|
@ -20,9 +20,6 @@ public:
|
|||
virtual std::string primary_key() = 0;
|
||||
virtual void write(File &) = 0;
|
||||
virtual void read(File &) = 0;
|
||||
#ifdef CONFIG_TEST
|
||||
virtual void print() = 0;
|
||||
#endif /* CONFIG_TEST */
|
||||
};
|
||||
|
||||
|
||||
|
@ -48,55 +45,57 @@ public:
|
|||
template <class T>
|
||||
class Database {
|
||||
private:
|
||||
std::vector<T> db;
|
||||
std::map<const std::string, unsigned int> keys;
|
||||
std::vector<T> _db;
|
||||
std::map<const std::string, unsigned int> _keys;
|
||||
unsigned int _size;
|
||||
File file;
|
||||
bool _autosave;
|
||||
File _file;
|
||||
|
||||
void autosave();
|
||||
|
||||
public:
|
||||
Database(std::string);
|
||||
typedef typename std::vector<T>::iterator iterator;
|
||||
typedef typename std::vector<T>::const_iterator const_iterator;
|
||||
|
||||
Database(std::string, bool);
|
||||
~Database();
|
||||
void save();
|
||||
void load();
|
||||
#ifdef CONFIG_TEST
|
||||
void clear();
|
||||
void print();
|
||||
void print_keys();
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
unsigned int insert(T);
|
||||
void remove(unsigned int);
|
||||
unsigned int size();
|
||||
unsigned int num_rows();
|
||||
unsigned int actual_size();
|
||||
|
||||
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);
|
||||
iterator begin();
|
||||
iterator end();
|
||||
iterator next(iterator &);
|
||||
|
||||
iterator at(unsigned int);
|
||||
iterator find(const std::string &);
|
||||
};
|
||||
|
||||
static inline void index_insert(Database<IndexEntry> &db,
|
||||
const std::string &key,
|
||||
unsigned int val)
|
||||
{
|
||||
try {
|
||||
db.find(key).insert(val);
|
||||
} catch (int error) {
|
||||
Database<IndexEntry>::iterator it = db.find(key);
|
||||
if (it == db.end()) {
|
||||
db.insert(IndexEntry(key, val));
|
||||
}
|
||||
} else
|
||||
it->insert(val);
|
||||
}
|
||||
|
||||
static inline void index_remove(Database<IndexEntry> &db,
|
||||
const std::string &key,
|
||||
unsigned int val)
|
||||
{
|
||||
unsigned int i = db.find_index(key);
|
||||
db[i].remove(val);
|
||||
if (db[i].values.size() == 0)
|
||||
db.remove(db.find_index(key));
|
||||
Database<IndexEntry>::iterator it = db.find(key);
|
||||
if (it != db.end()) {
|
||||
it->remove(val);
|
||||
if (it->values.size() == 0)
|
||||
db.remove(it - db.begin());
|
||||
}
|
||||
}
|
||||
|
||||
#include "database.hpp"
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#include <error.h>
|
||||
|
||||
template <class T>
|
||||
Database<T> :: Database(std::string filepath)
|
||||
: _size(0), file(filepath, FILE_TYPE_DATA)
|
||||
Database<T> :: Database(std::string filepath, bool autosave)
|
||||
: _size(0), _autosave(autosave), _file(filepath, FILE_TYPE_DATA)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -23,21 +23,25 @@ Database<T> :: ~Database()
|
|||
template <class T>
|
||||
void Database<T> :: save()
|
||||
{
|
||||
try {
|
||||
file.open(OPEN_WRITE);
|
||||
} catch (int error) {
|
||||
if (_file.open(OPEN_WRITE) == false)
|
||||
return;
|
||||
|
||||
_file << _db.size() << std::endl;
|
||||
for (unsigned int i = 0; i < _db.size(); i++) {
|
||||
_file << _db[i].valid << " ";
|
||||
if (_db[i].valid == true)
|
||||
_db[i].write(_file);
|
||||
_file << std::endl;
|
||||
}
|
||||
|
||||
file << db.size() << std::endl;
|
||||
for (unsigned int i = 0; i < db.size(); i++) {
|
||||
file << db[i].valid << " ";
|
||||
if (db[i].valid == true)
|
||||
db[i].write(file);
|
||||
file << std::endl;
|
||||
}
|
||||
_file.close();
|
||||
}
|
||||
|
||||
file.close();
|
||||
template <class T>
|
||||
void Database<T> :: autosave()
|
||||
{
|
||||
if (_autosave == true)
|
||||
save();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -45,90 +49,55 @@ void Database<T> :: load()
|
|||
{
|
||||
unsigned int db_size;
|
||||
|
||||
if (file.exists() == false)
|
||||
if (_file.exists() == false)
|
||||
return;
|
||||
else if (_file.open(OPEN_READ) == false)
|
||||
return;
|
||||
|
||||
try {
|
||||
file.open(OPEN_READ);
|
||||
} catch (int error) {
|
||||
return;
|
||||
}
|
||||
|
||||
file >> db_size;
|
||||
db.resize(db_size);
|
||||
_file >> db_size;
|
||||
_db.resize(db_size);
|
||||
for (unsigned int i = 0; i < db_size; i++) {
|
||||
file >> db[i].valid;
|
||||
if (db[i].valid == true) {
|
||||
db[i].read(file);
|
||||
keys.insert(std::pair<std::string, unsigned int>(db[i].primary_key(), i));
|
||||
_file >> _db[i].valid;
|
||||
if (_db[i].valid == true) {
|
||||
_db[i].read(_file);
|
||||
_keys.insert(std::pair<std::string, unsigned int>(_db[i].primary_key(), i));
|
||||
_size++;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
_file.close();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
template <class T>
|
||||
void Database<T> :: clear()
|
||||
{
|
||||
db.clear();
|
||||
keys.clear();
|
||||
_size = 0;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Database<T> :: print()
|
||||
{
|
||||
:: print("Allocated rows: %u\n", db.size());
|
||||
:: print("Valid rows: %u\n", _size);
|
||||
for (unsigned int i = 0; i < db.size(); i++) {
|
||||
if (db[i].valid == true) {
|
||||
:: print("db[%u] = ", i);
|
||||
db[i].print();
|
||||
:: print("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Database<T> :: print_keys()
|
||||
{
|
||||
std::map<const std::string, unsigned int>::iterator it;
|
||||
:: print("Found keys:");
|
||||
for (it = keys.begin(); it != keys.end(); it++)
|
||||
:: print(" %s", it->first.c_str());
|
||||
:: print("\n");
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
template <class T>
|
||||
unsigned int Database<T> :: insert(T val)
|
||||
{
|
||||
unsigned int id;
|
||||
typename std::map<const std::string, unsigned int>::iterator it;
|
||||
iterator it = find(val.primary_key());
|
||||
|
||||
it = keys.find(val.primary_key());
|
||||
if (it != keys.end())
|
||||
return it->second;
|
||||
if (it != end())
|
||||
return it - _db.begin();
|
||||
|
||||
/*
|
||||
* Check primary key stuff here
|
||||
*/
|
||||
id = db.size();
|
||||
db.push_back(val);
|
||||
keys.insert(std::pair<const std::string, unsigned int>(val.primary_key(), id));
|
||||
db[id].valid = true;
|
||||
id = _db.size();
|
||||
_db.push_back(val);
|
||||
_keys[val.primary_key()] = id;
|
||||
_db[id].valid = true;
|
||||
_size++;
|
||||
autosave();
|
||||
return id;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Database<T> :: remove(unsigned int id)
|
||||
{
|
||||
keys.erase(db[id].primary_key());
|
||||
db[id].valid = false;
|
||||
if (id >= actual_size())
|
||||
return;
|
||||
_keys.erase(_db[id].primary_key());
|
||||
_db[id].valid = false;
|
||||
_size--;
|
||||
autosave();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
@ -138,82 +107,53 @@ unsigned int Database<T> :: size()
|
|||
}
|
||||
|
||||
template <class T>
|
||||
unsigned int Database<T> :: num_rows()
|
||||
unsigned int Database<T> :: actual_size()
|
||||
{
|
||||
return db.size();
|
||||
return _db.size();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
unsigned int Database<T> :: first()
|
||||
typename Database<T>::iterator Database<T> :: begin()
|
||||
{
|
||||
for (unsigned int i = 0; i < db.size(); i++) {
|
||||
if (db[i].valid == true)
|
||||
return i;
|
||||
}
|
||||
|
||||
return db.size();
|
||||
iterator it = _db.begin();
|
||||
if ( (*it).valid == true )
|
||||
return it;
|
||||
return next(it);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
unsigned int Database<T> :: last()
|
||||
typename Database<T>::iterator Database<T> :: end()
|
||||
{
|
||||
if (_size == 0)
|
||||
return db.size();
|
||||
|
||||
for (unsigned int i = db.size() - 1; i >= 0; i--) {
|
||||
if (db[i].valid == true)
|
||||
return i;
|
||||
}
|
||||
|
||||
return db.size();
|
||||
return _db.end();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
unsigned int Database<T> :: next(unsigned int id)
|
||||
typename Database<T>::iterator Database<T> :: next(iterator &it)
|
||||
{
|
||||
for (unsigned int i = id + 1; i < db.size(); i++) {
|
||||
if (db[i].valid == true)
|
||||
return i;
|
||||
}
|
||||
|
||||
return db.size();
|
||||
do {
|
||||
it++;
|
||||
} while ((it != end()) && ((*it).valid == false));
|
||||
return it;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool Database<T> :: has_key(const std::string &key)
|
||||
typename Database<T>::iterator Database<T> :: at(unsigned int id)
|
||||
{
|
||||
if (id >= actual_size())
|
||||
return end();
|
||||
|
||||
if (_db[id].valid == false)
|
||||
return end();
|
||||
return _db.begin() + id;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
typename Database<T>::iterator Database<T> :: find(const std::string &key)
|
||||
{
|
||||
std::map<const std::string, unsigned int>::iterator it;
|
||||
it = keys.find(key);
|
||||
return it != keys.end();
|
||||
it = _keys.find(key);
|
||||
if (it == _keys.end())
|
||||
return end();
|
||||
return _db.begin() + it->second;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
unsigned int Database<T> :: find_index(const std::string &key)
|
||||
{
|
||||
std::map<const std::string, unsigned int>::iterator it;
|
||||
it = keys.find(key);
|
||||
if (it == keys.end())
|
||||
throw -E_EXIST;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T &Database<T> :: find(const std::string &key)
|
||||
{
|
||||
unsigned int index = find_index(key);
|
||||
if (db[index].valid == false)
|
||||
throw -E_INVAL;
|
||||
return db[index];
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T &Database<T> :: operator[](unsigned int id)
|
||||
{
|
||||
if (id >= db.size())
|
||||
throw -E_EXIST;
|
||||
else if (db[id].valid == false)
|
||||
throw -E_INVAL;
|
||||
return db[id];
|
||||
}
|
||||
|
||||
#endif /* OCARINA_DATABASE_HPP */
|
||||
|
|
|
@ -36,9 +36,6 @@ namespace library
|
|||
std::string primary_key();
|
||||
void read(File &);
|
||||
void write(File &);
|
||||
#ifdef CONFIG_TEST
|
||||
void print();
|
||||
#endif /* CONFIG_TEST */
|
||||
};
|
||||
|
||||
|
||||
|
@ -55,9 +52,6 @@ namespace library
|
|||
std::string primary_key();
|
||||
void read(File &);
|
||||
void write(File &);
|
||||
#ifdef CONFIG_TEST
|
||||
void print();
|
||||
#endif /* CONFIG_TEST */
|
||||
};
|
||||
|
||||
|
||||
|
@ -72,9 +66,6 @@ namespace library
|
|||
std::string primary_key();
|
||||
void read(File &);
|
||||
void write(File &);
|
||||
#ifdef CONFIG_TEST
|
||||
void print();
|
||||
#endif /* CONFIG_TEST */
|
||||
};
|
||||
|
||||
|
||||
|
@ -107,9 +98,6 @@ namespace library
|
|||
std::string primary_key();
|
||||
void read(File &);
|
||||
void write(File &);
|
||||
#ifdef CONFIG_TEST
|
||||
void print();
|
||||
#endif /* CONFIG_TEST */
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include <map>
|
||||
#include <set>
|
||||
|
||||
static Database<IndexEntry> filter_index("");
|
||||
static Database<IndexEntry> filter_index("", false);
|
||||
static std::map<std::string, std::string> lowercase_cache;
|
||||
static unsigned int lowercase_cache_hits = 0;
|
||||
|
||||
|
@ -107,10 +107,10 @@ void filter :: add(const std::string &text, unsigned int track_id)
|
|||
|
||||
static void find_intersection(std::string &text, std::set<unsigned int> &res)
|
||||
{
|
||||
IndexEntry *terms = &filter_index.find(text);
|
||||
Database<IndexEntry>::iterator it = filter_index.find(text);
|
||||
std::set<unsigned int> tmp;
|
||||
|
||||
set_intersection(terms->values.begin(), terms->values.end(),
|
||||
set_intersection(it->values.begin(), it->values.end(),
|
||||
res.begin(), res.end(),
|
||||
std::inserter<std::set<unsigned int> >(tmp, tmp.begin()));
|
||||
res.swap(tmp);
|
||||
|
@ -127,7 +127,7 @@ void filter :: search(const std::string &text, std::set<unsigned int> &res)
|
|||
|
||||
it = parsed.begin();
|
||||
try {
|
||||
res = filter_index.find(*it).values;
|
||||
res = filter_index.find(*it)->values;
|
||||
} catch (...) {
|
||||
return;
|
||||
}
|
||||
|
|
191
lib/library.cpp
191
lib/library.cpp
|
@ -11,11 +11,11 @@
|
|||
#include <sstream>
|
||||
#include <time.h>
|
||||
|
||||
static Database<library :: Album> album_db("album.db");
|
||||
static Database<library :: AGInfo> artist_db("artist.db");
|
||||
static Database<library :: AGInfo> genre_db("genre.db");
|
||||
static Database<library :: Track> track_db("track.db");
|
||||
static Database<library :: Library> library_db("library.db");
|
||||
static Database<library :: Album> album_db("album.db", false);
|
||||
static Database<library :: AGInfo> artist_db("artist.db", false);
|
||||
static Database<library :: AGInfo> genre_db("genre.db", false);
|
||||
static Database<library :: Track> track_db("track.db", false);
|
||||
static Database<library :: Library> library_db("library.db", false);
|
||||
|
||||
struct ImportData {
|
||||
std::string filepath;
|
||||
|
@ -76,16 +76,6 @@ void library :: AGInfo :: write(File &f)
|
|||
f << name;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
void library :: AGInfo :: print()
|
||||
{
|
||||
if (db_type == DB_ARTIST)
|
||||
:: print("Artist: %s", name.c_str());
|
||||
else
|
||||
:: print("Genre: %s", name.c_str());
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
@ -129,13 +119,6 @@ void library :: Album :: write(File &f)
|
|||
f << artist_id << " " << year << " " << name;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
void library :: Album :: print()
|
||||
{
|
||||
:: print("Album: %s (%u) by %s", name.c_str(), year, artist_db[artist_id].name.c_str());
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
@ -169,18 +152,6 @@ void library :: Library :: write(File &f)
|
|||
f << enabled << " " << root_path;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
void library :: Library :: print()
|
||||
{
|
||||
:: print("%s", root_path.c_str());
|
||||
if (enabled == true)
|
||||
:: print(" (enabled)");
|
||||
else
|
||||
:: print(" (disabled)");
|
||||
:: print(", size = %u", size);
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
@ -204,7 +175,7 @@ library :: Track :: Track(TagLib :: Tag *tag, TagLib :: AudioProperties *audio,
|
|||
unsigned int minutes, seconds;
|
||||
|
||||
full_path = path;
|
||||
filepath = path.substr(library_db[library_id].root_path.size() + 1);
|
||||
filepath = path.substr(library_db.at(library_id)->root_path.size() + 1);
|
||||
title_lower = filter :: to_lowercase(title);
|
||||
|
||||
minutes = length / 60;
|
||||
|
@ -228,7 +199,7 @@ library :: Track :: Track(struct ImportData *data, unsigned int lib,
|
|||
unsigned int minutes, seconds;
|
||||
|
||||
full_path = data->filepath;
|
||||
filepath = full_path.substr(library_db[library_id].root_path.size() + 1);
|
||||
filepath = full_path.substr(library_db.at(library_id)->root_path.size() + 1);
|
||||
title_lower = filter :: to_lowercase(title);
|
||||
|
||||
minutes = length / 60;
|
||||
|
@ -254,8 +225,8 @@ void library :: Track :: read(File &f)
|
|||
title = f.getline();
|
||||
filepath = f.getline();
|
||||
title_lower = filter :: to_lowercase(title);
|
||||
full_path = library_db[library_id].root_path + "/" + filepath;
|
||||
library_db[library_id].size++;
|
||||
full_path = library_db.at(library_id)->root_path + "/" + filepath;
|
||||
library_db.at(library_id)->size++;
|
||||
}
|
||||
|
||||
void library :: Track :: write(File &f)
|
||||
|
@ -267,20 +238,6 @@ void library :: Track :: write(File &f)
|
|||
f << filepath;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
void library :: Track :: print()
|
||||
{
|
||||
:: print("%u. %s by %s from %s (%u)\n", track, title.c_str(),
|
||||
artist_db[artist_id].name.c_str(),
|
||||
album_db[album_id].name.c_str(), album_db[album_id].year);
|
||||
:: print(" Genre: %s, Length: %u (seconds)\n",
|
||||
genre_db[genre_id].name.c_str(), length);
|
||||
:: print(" Play count: %u, last played %u/%u/%u\n", play_count,
|
||||
last_day, last_month, last_year);
|
||||
:: print(" %s", filepath.c_str());
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
@ -313,11 +270,11 @@ static void read_tags(unsigned int lib_id, const std :: string &path)
|
|||
genre_id = genre_db.insert(library :: AGInfo(library :: DB_GENRE, tag));
|
||||
track_id = track_db.insert(library :: Track(tag, audio, lib_id,
|
||||
artist_id, album_id, genre_id, path));
|
||||
library_db[lib_id].size++;
|
||||
library_db.at(lib_id)->size++;
|
||||
|
||||
filter::add(artist_db[artist_id].name, track_id);
|
||||
filter::add(album_db[album_id].name, track_id);
|
||||
filter::add(track_db[track_id].title, track_id);
|
||||
filter::add(artist_db.at(artist_id)->name, track_id);
|
||||
filter::add(album_db.at(album_id)->name, track_id);
|
||||
filter::add(track_db.at(track_id)->title, track_id);
|
||||
get_callbacks()->on_library_track_add(track_id);
|
||||
}
|
||||
|
||||
|
@ -333,7 +290,7 @@ static bool process_path(unsigned int lib_id, const std :: string &dir,
|
|||
scan.path = path;
|
||||
idle :: schedule (do_scan_path, scan);
|
||||
} else {
|
||||
if (track_db.has_key(path) == false) {
|
||||
if (track_db.find(path) != track_db.end()) {
|
||||
read_tags(lib_id, path);
|
||||
changed = true;
|
||||
}
|
||||
|
@ -370,7 +327,7 @@ static void do_scan_path(struct scan_info &scan)
|
|||
if (changed == true) {
|
||||
save_all_dbs();
|
||||
get_callbacks()->on_library_update(scan.lib_id,
|
||||
&library_db[scan.lib_id]);
|
||||
&(*library_db.at(scan.lib_id)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -382,26 +339,27 @@ static void do_validate_library(unsigned int &lib_id)
|
|||
if (track_db.size() == 0)
|
||||
return;
|
||||
|
||||
for (unsigned int i = track_db.first(); i <= track_db.last(); i = track_db.next(i)) {
|
||||
if (track_db[i].library_id != lib_id)
|
||||
Database<library :: Track>::iterator it;
|
||||
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
|
||||
if ((*it).library_id != lib_id)
|
||||
continue;
|
||||
|
||||
path = library_db[lib_id].root_path + "/" + track_db[i].filepath;
|
||||
path = library_db.at(lib_id)->root_path + "/" + (*it).filepath;
|
||||
if (g_file_test(path.c_str(), G_FILE_TEST_EXISTS) == false) {
|
||||
dprint("Removing file: %s\n", path.c_str());
|
||||
track_db.remove(i);
|
||||
library_db[lib_id].size--;
|
||||
track_db.remove(it - track_db.begin());
|
||||
library_db.at(lib_id)->size--;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed == true)
|
||||
get_callbacks()->on_library_update(lib_id, &library_db[lib_id]);
|
||||
get_callbacks()->on_library_update(lib_id, &(*library_db.at(lib_id)));
|
||||
}
|
||||
|
||||
static void do_update_library(unsigned int lib_id)
|
||||
{
|
||||
struct scan_info scan = { lib_id, library_db[lib_id].root_path };
|
||||
struct scan_info scan = { lib_id, library_db.at(lib_id)->root_path };
|
||||
idle :: schedule(do_validate_library, lib_id);
|
||||
idle :: schedule(do_scan_path, scan);
|
||||
}
|
||||
|
@ -430,11 +388,11 @@ static void do_import_track(File &f, unsigned int lib_id)
|
|||
genre_id = genre_db.insert(library :: AGInfo(library :: DB_GENRE, genre));
|
||||
track_id = track_db.insert(library :: Track(&data, lib_id, artist_id,
|
||||
album_id, genre_id));
|
||||
library_db[lib_id].size++;
|
||||
library_db.at(lib_id)->size++;
|
||||
|
||||
filter::add(artist_db[artist_id].name, track_id);
|
||||
filter::add(album_db[album_id].name, track_id);
|
||||
filter::add(track_db[track_id].title, track_id);
|
||||
filter::add(artist_db.at(artist_id)->name, track_id);
|
||||
filter::add(album_db.at(album_id)->name, track_id);
|
||||
filter::add(track_db.at(track_id)->title, track_id);
|
||||
get_callbacks()->on_library_track_add(track_id);
|
||||
|
||||
if (banned == true)
|
||||
|
@ -460,20 +418,20 @@ static void do_import_library(std::string &s)
|
|||
f >> id >> enabled >> next_id >> size;
|
||||
|
||||
/* Assign this path a new id */
|
||||
if (library_db.has_key(path)) {
|
||||
if (library_db.find(path) != library_db.end()) {
|
||||
print("Library already contains path: %s, skipping\n", path.c_str());
|
||||
return;
|
||||
}
|
||||
print("Adding path: %s\n", path.c_str());
|
||||
id = library_db.insert(library :: Library(path, enabled));
|
||||
get_callbacks()->on_library_add(id, &library_db[id]);
|
||||
get_callbacks()->on_library_add(id, &(*library_db.at(id)));
|
||||
library_db.save();
|
||||
|
||||
f.getline(); /* Get rest of line */
|
||||
for (unsigned int i = 0; i < size; i++)
|
||||
do_import_track(f, id);
|
||||
save_all_dbs();
|
||||
get_callbacks()->on_library_update(id, &library_db[id]);
|
||||
get_callbacks()->on_library_update(id, &(*library_db.at(id)));
|
||||
|
||||
library :: update_path(id);
|
||||
}
|
||||
|
@ -494,16 +452,22 @@ void library :: init()
|
|||
library_db.load();
|
||||
track_db.load();
|
||||
|
||||
for (i = track_db.first(); i < track_db.num_rows(); i = track_db.next(i)) {
|
||||
filter::add(artist_db[track_db[i].artist_id].name, i);
|
||||
filter::add(album_db[track_db[i].album_id].name, i);
|
||||
filter::add(track_db[i].title, i);
|
||||
Database<Track>::iterator it;
|
||||
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
|
||||
i = (it - track_db.begin());
|
||||
filter::add(artist_db.at((*it).artist_id)->name, i);
|
||||
filter::add(album_db.at((*it).album_id)->name, i);
|
||||
filter::add((*it).title, i);
|
||||
|
||||
if (library_db[track_db[i].library_id].enabled)
|
||||
if (library_db.at((*it).library_id)->enabled)
|
||||
get_callbacks()->on_library_track_add(i);
|
||||
}
|
||||
for (i = library_db.first(); i < library_db.num_rows(); i = library_db.next(i))
|
||||
get_callbacks()->on_library_add(i, &library_db[i]);
|
||||
|
||||
Database<Library>::iterator l_it;
|
||||
for (l_it = library_db.begin(); l_it != library_db.end(); l_it = library_db.next(l_it)) {
|
||||
i = l_it - library_db.begin();
|
||||
get_callbacks()->on_library_add(i, &(*library_db.at(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void library :: add_path(const std::string &dir)
|
||||
|
@ -511,24 +475,25 @@ void library :: add_path(const std::string &dir)
|
|||
unsigned int id;
|
||||
if (g_file_test(dir.c_str(), G_FILE_TEST_IS_DIR) == false)
|
||||
throw -E_INVAL;
|
||||
if (library_db.has_key(dir))
|
||||
if (library_db.find(dir) != library_db.end())
|
||||
return;
|
||||
|
||||
id = library_db.insert(library :: Library(dir, true));
|
||||
library_db.save();
|
||||
|
||||
get_callbacks()->on_library_add(id, &library_db[id]);
|
||||
get_callbacks()->on_library_add(id, &(*library_db.at(id)));
|
||||
update_path(id);
|
||||
}
|
||||
|
||||
void library :: del_path(unsigned int id)
|
||||
{
|
||||
unsigned int t;
|
||||
Database<Track>::iterator it;
|
||||
|
||||
for (t = track_db.first(); t < track_db.num_rows(); t = track_db.next(t)) {
|
||||
if (track_db[t].library_id == id) {
|
||||
get_callbacks()->on_library_track_del(t);
|
||||
track_db.remove(t);
|
||||
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
|
||||
if ((*it).library_id == id) {
|
||||
unsigned int track_id = it - track_db.begin();
|
||||
get_callbacks()->on_library_track_del(track_id);
|
||||
track_db.remove(track_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,27 +507,30 @@ void library :: update_path(unsigned int id)
|
|||
{
|
||||
if (id > library_db.size())
|
||||
return;
|
||||
if (library_db[id].valid == false)
|
||||
if (library_db.at(id)->valid == false)
|
||||
return;
|
||||
do_update_library(id);
|
||||
}
|
||||
|
||||
void library :: update_all()
|
||||
{
|
||||
unsigned int i;
|
||||
for (i = library_db.first(); i < library_db.num_rows(); i = library_db.next(i))
|
||||
update_path(i);
|
||||
Database<Library>::iterator it;
|
||||
|
||||
for (it = library_db.begin(); it != library_db.end(); it = library_db.next(it))
|
||||
update_path(it - library_db.begin());
|
||||
}
|
||||
|
||||
void library :: set_enabled(unsigned int id, bool enabled)
|
||||
{
|
||||
unsigned int t;
|
||||
Database<Track>::iterator it;
|
||||
|
||||
library_db[id].enabled = enabled;
|
||||
library_db.at(id)->enabled = enabled;
|
||||
library_db.save();
|
||||
|
||||
for (t = track_db.first(); t < track_db.num_rows(); t = track_db.next(t)) {
|
||||
if (track_db[t].library_id == id) {
|
||||
for (it = track_db.begin(); it != track_db.end(); it = track_db.next(it)) {
|
||||
if ((*it).library_id == id) {
|
||||
t = it - track_db.begin();
|
||||
if (enabled)
|
||||
get_callbacks()->on_library_track_add(t);
|
||||
else
|
||||
|
@ -573,27 +541,27 @@ void library :: set_enabled(unsigned int id, bool enabled)
|
|||
|
||||
void library :: lookup(unsigned int id, library :: Song *song)
|
||||
{
|
||||
if (id >= track_db.num_rows())
|
||||
if (id >= track_db.actual_size())
|
||||
throw -E_EXIST;
|
||||
|
||||
song->track = &track_db[id];
|
||||
song->track = &(*track_db.at(id));
|
||||
if (song->track->valid == false)
|
||||
throw -E_EXIST;
|
||||
|
||||
song->track_id = id;
|
||||
song->artist = &artist_db[song->track->artist_id];
|
||||
song->album = &album_db[song->track->album_id];
|
||||
song->genre = &genre_db[song->track->genre_id];
|
||||
song->library = &library_db[song->track->library_id];
|
||||
song->artist = &(*artist_db.at(song->track->artist_id));
|
||||
song->album = &(*album_db.at(song->track->album_id));
|
||||
song->genre = &(*genre_db.at(song->track->genre_id));
|
||||
song->library = &(*library_db.at(song->track->library_id));
|
||||
}
|
||||
|
||||
library :: Library *library :: lookup_path(unsigned int id)
|
||||
{
|
||||
if (id >= library_db.num_rows())
|
||||
if (id >= library_db.actual_size())
|
||||
throw -E_EXIST;
|
||||
if (library_db[id].valid == false)
|
||||
if (library_db.at(id)->valid == false)
|
||||
throw -E_EXIST;
|
||||
return &library_db[id];
|
||||
return &(*library_db.at(id));
|
||||
}
|
||||
|
||||
void library :: import()
|
||||
|
@ -622,10 +590,10 @@ void library :: track_played(unsigned int id)
|
|||
time_t the_time = time(NULL);
|
||||
struct tm *now = localtime(&the_time);
|
||||
|
||||
track_db[id].play_count++;
|
||||
track_db[id].last_day = now->tm_mday;
|
||||
track_db[id].last_month = now->tm_mon + 1;
|
||||
track_db[id].last_year = now->tm_year + 1900;
|
||||
track_db.at(id)->play_count++;
|
||||
track_db.at(id)->last_day = now->tm_mday;
|
||||
track_db.at(id)->last_month = now->tm_mon + 1;
|
||||
track_db.at(id)->last_year = now->tm_year + 1900;
|
||||
|
||||
track_db.save();
|
||||
get_callbacks()->on_library_track_updated(id);
|
||||
|
@ -636,28 +604,19 @@ void library :: print_db(DB_Type type)
|
|||
{
|
||||
switch (type) {
|
||||
case DB_ALBUM:
|
||||
album_db.print();
|
||||
break;
|
||||
case DB_ARTIST:
|
||||
artist_db.print();
|
||||
break;
|
||||
case DB_GENRE:
|
||||
genre_db.print();
|
||||
break;
|
||||
case DB_LIBRARY:
|
||||
library_db.print();
|
||||
break;
|
||||
case DB_TRACK:
|
||||
track_db.print();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void library :: reset()
|
||||
{
|
||||
album_db.clear();
|
||||
artist_db.clear();
|
||||
genre_db.clear();
|
||||
library_db.clear();
|
||||
track_db.clear();
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include <playlist.h>
|
||||
|
||||
static std::set<unsigned int> empty_set;
|
||||
static Database<IndexEntry> playlist_db("playlist.db");
|
||||
static Database<IndexEntry> playlist_db("playlist.db", false);
|
||||
static Playqueue playlist_pq(PQ_ENABLED);
|
||||
static std::string cur_pq;
|
||||
|
||||
|
@ -85,11 +85,10 @@ void playlist :: select(const std::string &name)
|
|||
const std::set<unsigned int> &playlist :: get_tracks(const std::string &name)
|
||||
{
|
||||
if ((name == "Banned") || (name == "Favorites")) {
|
||||
try {
|
||||
return playlist_db.find(name).values;
|
||||
} catch (int error) {
|
||||
return empty_set;
|
||||
}
|
||||
Database<IndexEntry>::iterator it = playlist_db.find(name);
|
||||
if (it != playlist_db.end())
|
||||
return it->values;
|
||||
return empty_set;
|
||||
}
|
||||
throw -E_EXIST;
|
||||
}
|
||||
|
@ -102,6 +101,5 @@ Playqueue *playlist :: get_pq()
|
|||
#ifdef CONFIG_TEST
|
||||
void playlist :: clear()
|
||||
{
|
||||
playlist_db.clear();
|
||||
}
|
||||
#endif /* CONFIG_TEST */
|
||||
|
|
|
@ -8,7 +8,7 @@ if sys.argv.count("tests") > 0:
|
|||
|
||||
src = SConscript("src/Sconscript")
|
||||
|
||||
tests = [ "version", "file", "db_entry" ]
|
||||
tests = [ "version", "file", "db_entry", "database" ]
|
||||
#scripts = [ "print", "file", "database", "index", "filter", "idle", "playlist",
|
||||
# "library", "playqueue", "deck", "audio", "gui" ]
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
#!/bin/bash
|
||||
# Copyright 2014 (c) Anna Schumaker
|
||||
|
||||
. $(dirname $0)/_functions
|
||||
|
||||
function test_autosave
|
||||
{
|
||||
new_test "Database Test (n = $1, autosave = true)"
|
||||
src/database.run -a $1
|
||||
if [ ! -f $DATA_DIR/database.db ]; then
|
||||
echo "ERROR: $DATA_DIR/database.db doesn't exist!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function test_noautosave
|
||||
{
|
||||
new_test "Database Test (n = $1, autosave = false)"
|
||||
src/database.run $1
|
||||
if [ -f $DATA_DIR/database.db ]; then
|
||||
echo "ERROR: $DATA_DIR/database.db exists!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function run_test
|
||||
{
|
||||
rm $DATA_DIR/* 2>/dev/null || true
|
||||
|
||||
if [ $1 -le 1000 ]; then
|
||||
test_autosave $1
|
||||
else
|
||||
test_noautosave $1
|
||||
fi
|
||||
}
|
||||
|
||||
run_test 10
|
||||
echo
|
||||
run_test 100
|
||||
echo
|
||||
run_test 1000
|
||||
echo
|
||||
run_test 10000
|
||||
echo
|
||||
run_test 100000
|
|
@ -1,6 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
Import("Test", "CONFIG")
|
||||
|
||||
CONFIG.DATABASE = True
|
||||
|
||||
Test("database", "database.cpp")
|
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 (c) Anna Schumaker.
|
||||
*/
|
||||
#include <database.h>
|
||||
#include <print.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
class DBTest : public DatabaseEntry {
|
||||
public:
|
||||
unsigned int value;
|
||||
|
||||
DBTest();
|
||||
DBTest(unsigned int);
|
||||
void write(File &);
|
||||
void read(File &);
|
||||
void print();
|
||||
};
|
||||
|
||||
DBTest :: DBTest()
|
||||
{
|
||||
}
|
||||
|
||||
DBTest :: DBTest(unsigned int val)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
value = val;
|
||||
ss << value;
|
||||
primary_key = ss.str();
|
||||
}
|
||||
|
||||
void DBTest :: write(File &file)
|
||||
{
|
||||
file << value;
|
||||
}
|
||||
|
||||
void DBTest :: read(File &file)
|
||||
{
|
||||
file >> value;
|
||||
}
|
||||
|
||||
void DBTest :: print()
|
||||
{
|
||||
:: print("%u", value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void print_db(Database<DBTest> &db)
|
||||
{
|
||||
print("Database size: %u\n", db.size());
|
||||
print("Num rows: %u\n", db.num_rows());
|
||||
print("First: %u\n", db.first());
|
||||
print("Last: %u\n", db.last());
|
||||
|
||||
for (unsigned int i = db.first(); i != db.num_rows(); i = db.next(i))
|
||||
print("db[%u] = %u\n", i, db[i].value);
|
||||
}
|
||||
|
||||
void test_insertion(Database<DBTest> &db)
|
||||
{
|
||||
for (unsigned int i = 1; i <= 100000; i++)
|
||||
db.insert(DBTest(i));
|
||||
print_db(db);
|
||||
}
|
||||
|
||||
void test_deletion(Database<DBTest> &db)
|
||||
{
|
||||
for (unsigned int i = db.first(); i < db.num_rows(); i = db.next(i)) {
|
||||
db.remove(i);
|
||||
i = db.next(i);
|
||||
}
|
||||
print_db(db);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test everything except reloading.
|
||||
*/
|
||||
void test_0()
|
||||
{
|
||||
print("Test 0\n");
|
||||
Database<DBTest> db("test.db");
|
||||
|
||||
test_insertion(db);
|
||||
test_deletion(db);
|
||||
test_deletion(db);
|
||||
|
||||
db.save();
|
||||
}
|
||||
|
||||
/*
|
||||
* Reload the database from disk.
|
||||
*/
|
||||
void test_1()
|
||||
{
|
||||
print("\nTest 1\n");
|
||||
Database<DBTest> db("test.db");
|
||||
db.load();
|
||||
print_db(db);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to save a db with an empty filepath.
|
||||
*/
|
||||
void test_2()
|
||||
{
|
||||
print("\nTest 2\n");
|
||||
Database<DBTest> db("");
|
||||
|
||||
test_insertion(db);
|
||||
db.save();
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to load a db with an empty filepath
|
||||
*/
|
||||
void test_3()
|
||||
{
|
||||
print("\nTest 3\n");
|
||||
Database<DBTest> db("");
|
||||
db.load();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test unique insertion
|
||||
*/
|
||||
void test_4()
|
||||
{
|
||||
print("\nTest 4\n");
|
||||
Database<DBTest> db("");
|
||||
|
||||
for (unsigned int i = 0; i < 100; i++)
|
||||
db.insert(DBTest(i % 5));
|
||||
|
||||
print_db(db);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test the database's built-in print() function
|
||||
*/
|
||||
void test_5()
|
||||
{
|
||||
print("\nTest 5\n");
|
||||
Database<DBTest> db("");
|
||||
for (unsigned int i = 0; i < 10; i++)
|
||||
db.insert(DBTest(i));
|
||||
db.print();
|
||||
}
|
||||
|
||||
/*
|
||||
* Test the database's clear() function
|
||||
*/
|
||||
void test_6()
|
||||
{
|
||||
print("\nTest 6\n");
|
||||
Database<DBTest> db("");
|
||||
for (unsigned int i = 0; i < 10; i++)
|
||||
db.insert(DBTest(i));
|
||||
print_db(db);
|
||||
db.clear();
|
||||
print_db(db);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print databases with no valid rows
|
||||
*/
|
||||
void test_7()
|
||||
{
|
||||
print("\nTest 7\n");
|
||||
Database<DBTest> db("");
|
||||
print_db(db);
|
||||
|
||||
for (unsigned int i = 0; i < 10; i++)
|
||||
db.insert(DBTest(i));
|
||||
print_db(db);
|
||||
|
||||
for (unsigned int i = 0; i < 10; i++)
|
||||
db.remove(i);
|
||||
print_db(db);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
test_0();
|
||||
test_1();
|
||||
test_2();
|
||||
test_3();
|
||||
test_4();
|
||||
test_5();
|
||||
test_6();
|
||||
test_7();
|
||||
|
||||
return 0;
|
||||
}
|
300097
tests/database/database.good
300097
tests/database/database.good
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright 2014 (c) Anna Schumaker.
|
||||
* Test a Database
|
||||
*/
|
||||
|
||||
#include <database.h>
|
||||
#include <print.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
unsigned int test_num = 0;
|
||||
|
||||
|
||||
class IntEntry : public DatabaseEntry {
|
||||
public:
|
||||
unsigned int val;
|
||||
|
||||
IntEntry();
|
||||
IntEntry(unsigned int);
|
||||
std::string primary_key();
|
||||
void write(File &);
|
||||
void read(File &);
|
||||
void print();
|
||||
};
|
||||
|
||||
IntEntry :: IntEntry() : val(0) {}
|
||||
IntEntry :: IntEntry(unsigned int v) : val(v) {}
|
||||
std::string IntEntry :: primary_key()
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << val;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void IntEntry :: write(File &f) { f << val; }
|
||||
void IntEntry :: read(File &f) { f >> val; }
|
||||
void IntEntry :: print() { :: print(primary_key().c_str()); }
|
||||
|
||||
void test_results(bool success, unsigned int line)
|
||||
{
|
||||
print(" %u: ", test_num);
|
||||
if (success)
|
||||
print("Success!\n");
|
||||
else {
|
||||
print("FAILED (%u) =(\n", line);
|
||||
exit(1);
|
||||
}
|
||||
test_num++;
|
||||
}
|
||||
|
||||
void test_size(Database<IntEntry> &db, unsigned int size,
|
||||
unsigned int actual, unsigned int line)
|
||||
{
|
||||
test_results( (db.size() == size) || (db.actual_size() == actual), line );
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool autosave = false;
|
||||
unsigned int n = 0, size = 0, actual = 0;
|
||||
char c;
|
||||
|
||||
while ((c = getopt(argc, argv, "a")) != -1) {
|
||||
switch (c) {
|
||||
case 'a':
|
||||
autosave = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
n = atoi(argv[optind]);
|
||||
Database<IntEntry> db("database.db", autosave);
|
||||
|
||||
|
||||
/**
|
||||
* 0: Test initial size
|
||||
*/
|
||||
test_size(db, size, actual, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 1: Test insertion
|
||||
*/
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
if (db.insert(IntEntry(i)) != i)
|
||||
test_results(false, __LINE__);
|
||||
}
|
||||
test_results(true, __LINE__);
|
||||
size += n;
|
||||
actual += n;
|
||||
|
||||
|
||||
/**
|
||||
* 2: Test that size changes
|
||||
*/
|
||||
test_size(db, size, actual, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 3: Test inserting ... again.
|
||||
*/
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
if (db.insert(IntEntry(i)) != i)
|
||||
test_results(false, __LINE__);
|
||||
}
|
||||
test_results(true, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 4: Test that size didn't change
|
||||
*/
|
||||
test_size(db, size, actual, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 5: Test that size changes when removing
|
||||
* Also test out-of-bounds removal
|
||||
*
|
||||
* Note: This test removes all even-index entries
|
||||
*/
|
||||
for (unsigned int i = 0; i < n + 10; i+=2) {
|
||||
db.remove(i);
|
||||
size--;
|
||||
}
|
||||
test_size(db, size, actual, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 6: Test that removing again doesn't change anything
|
||||
*/
|
||||
for (unsigned int i = 0; i < n; i+=2)
|
||||
db.remove(i);
|
||||
test_size(db, size, actual, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 7: Test iterating
|
||||
*/
|
||||
Database<IntEntry>::iterator it;
|
||||
unsigned int index = 1;
|
||||
for (it = db.begin(); it != db.end(); it = db.next(it)) {
|
||||
if ((*it).val != index)
|
||||
test_results(false, __LINE__);
|
||||
index+=2;
|
||||
};
|
||||
test_results(true, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 8. Test access by id
|
||||
*/
|
||||
for (unsigned int i = 0; i < n + 10; i++) {
|
||||
Database<IntEntry>::iterator it = db.at(i);
|
||||
if (((i % 2) == 0) && (it != db.end()))
|
||||
test_results(false, __LINE__);
|
||||
if ((i >= n) && (it != db.end()))
|
||||
test_results(false, __LINE__);
|
||||
}
|
||||
test_results(true, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 9. Test inserting once again
|
||||
*/
|
||||
for (unsigned int i = 0; i < n; i++) {
|
||||
index = db.insert(i);
|
||||
if ((i % 2) == 0) {
|
||||
size++;
|
||||
actual++;
|
||||
if (index != (n + (i / 2)))
|
||||
test_results(false, __LINE__);
|
||||
} else {
|
||||
if (index != i)
|
||||
test_results(false, __LINE__);
|
||||
}
|
||||
}
|
||||
test_results(true, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 10. Test that size changed for every other insert
|
||||
*/
|
||||
test_size(db, size, actual, __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* Everything after this point tests loading from a file
|
||||
*/
|
||||
if (autosave == false)
|
||||
return 0;
|
||||
|
||||
Database<IntEntry> db2("database.db", autosave);
|
||||
db2.load();
|
||||
|
||||
|
||||
/**
|
||||
* 11. Sizes should match
|
||||
*/
|
||||
test_size(db2, db.size(), db.actual_size(), __LINE__);
|
||||
|
||||
|
||||
/**
|
||||
* 12. Values should match
|
||||
*/
|
||||
Database<IntEntry>::iterator it2 = db2.begin();
|
||||
for (it = db.begin(); it != db.end(); it++) {
|
||||
if (it->valid != it2->valid)
|
||||
test_results(false, __LINE__);
|
||||
if (it->valid == true) {
|
||||
if (it->val != it2->val)
|
||||
test_results(false, __LINE__);
|
||||
}
|
||||
it2++;
|
||||
}
|
||||
test_results(true, __LINE__);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue