core/database: Add support for defragmenting databases

Removing items from the database leaves a NULL pointer "hole" that is
never filled in.  This doesn't affect correctness, but it could be
wasteful as items are added and removed.  This patch adds a function to
defragment the database without changing the order of items.

Implements #66: Add support for rebalancing databases
Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2016-08-12 13:47:30 -04:00
parent 8d9139aea5
commit 86d7fe43ed
4 changed files with 44 additions and 9 deletions

9
TODO
View File

@ -12,15 +12,6 @@ Future work:
- Save a user's playlist as a group: - Save a user's playlist as a group:
- Library defragment:
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.
- Fix track durations: - Fix track durations:
Some tracks in my library are tagged with the wrong duration, Some tracks in my library are tagged with the wrong duration,
so fix them as they are played. so fix them as they are played.

View File

@ -165,6 +165,31 @@ void db_remove(struct database *db, struct db_entry *item)
db_autosave(db); db_autosave(db);
} }
bool db_defrag(struct database *db)
{
struct db_entry *dbe;
unsigned int i, cur;
if (db->db_size == db_actual_size(db))
return false;
for (cur = 0, i = 1; cur < db->db_size; cur++) {
if (DB_ENTRY_AT(db, cur))
continue;
while (i <= cur || !DB_ENTRY_AT(db, i))
i++;
dbe = DB_ENTRY_AT(db, i);
g_ptr_array_index(db->db_entries, i) = NULL;
g_ptr_array_index(db->db_entries, cur) = dbe;
dbe->dbe_index = cur;
}
g_ptr_array_set_size(db->db_entries, db->db_size);
db_autosave(db);
return true;
}
unsigned int db_actual_size(const struct database *db) unsigned int db_actual_size(const struct database *db)
{ {
if (db->db_entries) if (db->db_entries)

View File

@ -123,6 +123,12 @@ struct db_entry *db_insert(struct database *, const gchar *);
/* Called to remove an item from the database. */ /* Called to remove an item from the database. */
void db_remove(struct database *, struct db_entry *); void db_remove(struct database *, struct db_entry *);
/*
* Called to shrink the database by removing any NULL pointers without
* changing the order of items in the database.
* Returns true if the database has been modified.
*/
bool db_defrag(struct database *);
/* Returns the database item at the requested index. */ /* Returns the database item at the requested index. */
struct db_entry *db_at(const struct database *, unsigned int); struct db_entry *db_at(const struct database *, unsigned int);

View File

@ -219,6 +219,19 @@ static void test_database(gconstpointer arg)
} }
g_assert_cmpuint(i, ==, N + 1); g_assert_cmpuint(i, ==, N + 1);
/* db_defrag(): Remove all NULL pointers */
i = 0;
g_assert_true(db_defrag(&db));
g_assert_cmpuint(db.db_size, ==, N / 2);
g_assert_cmpuint(db_actual_size(&db), ==, N / 2);
db_for_each(dbe, next, &db) {
g_assert_nonnull(dbe);
g_assert_cmpuint(INT_ENTRY(dbe)->ie_val, ==, (i * 2) + 1);
g_assert(dbe == db_at(&db, i));
i++;
}
g_assert_false(db_defrag(&db));
db_deinit(&db); db_deinit(&db);
g_assert_cmpuint(db.db_size, ==, 0); g_assert_cmpuint(db.db_size, ==, 0);
g_assert_cmpuint(db_actual_size(&db), ==, 0); g_assert_cmpuint(db_actual_size(&db), ==, 0);