mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-29 05:40:23 -04:00
database: simple hashmap based database implementation
This commit is contained in:
parent
61105df13b
commit
ea5cdcbe52
3 changed files with 534 additions and 2 deletions
23
configure.ac
23
configure.ac
|
|
@ -626,10 +626,11 @@ AM_CONDITIONAL([HAVE_LIBSAMPLERATE], [test "x$HAVE_LIBSAMPLERATE" = x1])
|
|||
|
||||
HAVE_TDB=0
|
||||
HAVE_GDBM=0
|
||||
HAVE_SIMPLEDB=0
|
||||
|
||||
AC_ARG_WITH(
|
||||
[database],
|
||||
AS_HELP_STRING([--with-database=auto|tdb|gdbm],[Choose database backend.]),[],[with_database=auto])
|
||||
AS_HELP_STRING([--with-database=auto|tdb|gdbm|simple],[Choose database backend.]),[],[with_database=auto])
|
||||
|
||||
if test "x${with_database}" = "xauto" -o "x${with_database}" = "xtdb" ; then
|
||||
PKG_CHECK_MODULES(TDB, [ tdb ],
|
||||
|
|
@ -659,7 +660,12 @@ if test "x${with_database}" = "xauto" -o "x${with_database}" = "xgdbm" ; then
|
|||
fi
|
||||
fi
|
||||
|
||||
if test "x${HAVE_TDB}" != x1 -a "x${HAVE_GDBM}" != x1; then
|
||||
if test "x${with_database}" = "xauto" -o "x${with_database}" = "xsimple" ; then
|
||||
HAVE_SIMPLEDB=1
|
||||
with_database=simple
|
||||
fi
|
||||
|
||||
if test "x${HAVE_TDB}" != x1 -a "x${HAVE_GDBM}" != x1 -a "x${HAVE_SIMPLEDB}" != x1; then
|
||||
AC_MSG_ERROR([*** missing database backend])
|
||||
fi
|
||||
|
||||
|
|
@ -671,6 +677,10 @@ if test "x${HAVE_GDBM}" = x1 ; then
|
|||
AC_DEFINE([HAVE_GDBM], 1, [Have gdbm?])
|
||||
fi
|
||||
|
||||
if test "x${HAVE_SIMPLEDB}" = x1 ; then
|
||||
AC_DEFINE([HAVE_SIMPLEDB], 1, [Have simple?])
|
||||
fi
|
||||
|
||||
AC_SUBST(TDB_CFLAGS)
|
||||
AC_SUBST(TDB_LIBS)
|
||||
AC_SUBST(HAVE_TDB)
|
||||
|
|
@ -681,6 +691,9 @@ AC_SUBST(GDBM_LIBS)
|
|||
AC_SUBST(HAVE_GDBM)
|
||||
AM_CONDITIONAL([HAVE_GDBM], [test "x$HAVE_GDBM" = x1])
|
||||
|
||||
AC_SUBST(HAVE_SIMPLEDB)
|
||||
AM_CONDITIONAL([HAVE_SIMPLEDB], [test "x$HAVE_SIMPLEDB" = x1])
|
||||
|
||||
#### OSS support (optional) ####
|
||||
|
||||
AC_ARG_ENABLE([oss-output],
|
||||
|
|
@ -1491,6 +1504,11 @@ if test "x${HAVE_TDB}" = "x1" ; then
|
|||
ENABLE_TDB=yes
|
||||
fi
|
||||
|
||||
ENABLE_SIMPLEDB=no
|
||||
if test "x${HAVE_SIMPLEDB}" = "x1" ; then
|
||||
ENABLE_SIMPLEDB=yes
|
||||
fi
|
||||
|
||||
ENABLE_OPENSSL=no
|
||||
if test "x${HAVE_OPENSSL}" = "x1" ; then
|
||||
ENABLE_OPENSSL=yes
|
||||
|
|
@ -1540,6 +1558,7 @@ echo "
|
|||
Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL}
|
||||
Enable tdb: ${ENABLE_TDB}
|
||||
Enable gdbm: ${ENABLE_GDBM}
|
||||
Enable simple database: ${ENABLE_SIMPLEDB}
|
||||
|
||||
System User: ${PA_SYSTEM_USER}
|
||||
System Group: ${PA_SYSTEM_GROUP}
|
||||
|
|
|
|||
|
|
@ -870,6 +870,9 @@ libpulsecore_@PA_MAJORMINORMICRO@_la_CFLAGS += $(TDB_CFLAGS)
|
|||
libpulsecore_@PA_MAJORMINORMICRO@_la_LIBADD += $(TDB_LIBS)
|
||||
endif
|
||||
|
||||
if HAVE_SIMPLEDB
|
||||
libpulsecore_@PA_MAJORMINORMICRO@_la_SOURCES += pulsecore/database-simple.c
|
||||
endif
|
||||
|
||||
# We split the foreign code off to not be annoyed by warnings we don't care about
|
||||
noinst_LTLIBRARIES = libpulsecore-foreign.la
|
||||
|
|
|
|||
510
src/pulsecore/database-simple.c
Normal file
510
src/pulsecore/database-simple.c
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2009 Nokia Corporation
|
||||
Contact: Maemo Multimedia <multimedia@maemo.org>
|
||||
|
||||
PulseAudio is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation; either version 2.1 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
PulseAudio is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with PulseAudio; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
USA.
|
||||
***/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/hashmap.h>
|
||||
|
||||
#include "database.h"
|
||||
|
||||
|
||||
typedef struct simple_data {
|
||||
char *filename;
|
||||
char *tmp_filename;
|
||||
pa_hashmap *map;
|
||||
pa_bool_t read_only;
|
||||
} simple_data;
|
||||
|
||||
typedef struct entry {
|
||||
pa_datum key;
|
||||
pa_datum data;
|
||||
} entry;
|
||||
|
||||
void pa_datum_free(pa_datum *d) {
|
||||
pa_assert(d);
|
||||
|
||||
pa_xfree(d->data);
|
||||
d->data = NULL;
|
||||
d->size = 0;
|
||||
}
|
||||
|
||||
static int compare_func(const void *a, const void *b) {
|
||||
const pa_datum *aa, *bb;
|
||||
|
||||
aa = (const pa_datum*)a;
|
||||
bb = (const pa_datum*)b;
|
||||
|
||||
if (aa->size != bb->size)
|
||||
return aa->size > bb->size ? 1 : -1;
|
||||
|
||||
return memcmp(aa->data, bb->data, aa->size);
|
||||
}
|
||||
|
||||
/* pa_idxset_string_hash_func modified for our use */
|
||||
static unsigned hash_func(const void *p) {
|
||||
const pa_datum *d;
|
||||
unsigned hash = 0;
|
||||
const char *c;
|
||||
unsigned i;
|
||||
|
||||
d = (const pa_datum*)p;
|
||||
c = d->data;
|
||||
|
||||
for (i = 0; i < d->size; i++) {
|
||||
hash = 31 * hash + (unsigned) *c;
|
||||
c++;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static entry* new_entry(const pa_datum *key, const pa_datum *data) {
|
||||
entry *e;
|
||||
|
||||
pa_assert(key);
|
||||
pa_assert(data);
|
||||
|
||||
e = pa_xnew0(entry, 1);
|
||||
e->key.data = key->size > 0 ? pa_xmemdup(key->data, key->size) : NULL;
|
||||
e->key.size = key->size;
|
||||
e->data.data = data->size > 0 ? pa_xmemdup(data->data, data->size) : NULL;
|
||||
e->data.size = data->size;
|
||||
return e;
|
||||
}
|
||||
|
||||
static void free_entry(entry *e) {
|
||||
if (e) {
|
||||
if (e->key.data)
|
||||
pa_xfree(e->key.data);
|
||||
if (e->data.data)
|
||||
pa_xfree(e->data.data);
|
||||
pa_xfree(e);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_uint(FILE *f, uint32_t *res) {
|
||||
size_t items = 0;
|
||||
uint8_t values[4];
|
||||
uint32_t tmp;
|
||||
int i;
|
||||
|
||||
items = fread(&values, sizeof(values), sizeof(uint8_t), f);
|
||||
|
||||
if (feof(f)) /* EOF */
|
||||
return 0;
|
||||
|
||||
if (ferror(f))
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
tmp = values[i];
|
||||
*res += (tmp << (i*8));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static int read_data(FILE *f, void **data, ssize_t *length) {
|
||||
size_t items = 0;
|
||||
uint32_t data_len = 0;
|
||||
|
||||
pa_assert(f);
|
||||
|
||||
*data = NULL;
|
||||
*length = 0;
|
||||
|
||||
if ((items = read_uint(f, &data_len)) <= 0)
|
||||
return -1;
|
||||
|
||||
if (data_len > 0) {
|
||||
*data = pa_xmalloc0(data_len);
|
||||
items = fread(*data, data_len, 1, f);
|
||||
|
||||
if (feof(f)) /* EOF */
|
||||
goto reset;
|
||||
|
||||
if (ferror(f))
|
||||
goto reset;
|
||||
|
||||
*length = data_len;
|
||||
|
||||
} else { /* no data? */
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
reset:
|
||||
pa_xfree(*data);
|
||||
*data = NULL;
|
||||
*length = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int fill_data(simple_data *db, FILE *f) {
|
||||
pa_datum key;
|
||||
pa_datum data;
|
||||
void *d = NULL;
|
||||
ssize_t l = 0;
|
||||
pa_bool_t append = FALSE;
|
||||
enum { FIELD_KEY = 0, FIELD_DATA } field = FIELD_KEY;
|
||||
|
||||
pa_assert(db);
|
||||
pa_assert(db->map);
|
||||
|
||||
errno = 0;
|
||||
|
||||
key.size = 0;
|
||||
key.data = NULL;
|
||||
|
||||
while (!read_data(f, &d, &l)) {
|
||||
|
||||
switch (field) {
|
||||
case FIELD_KEY:
|
||||
key.data = d;
|
||||
key.size = l;
|
||||
field = FIELD_DATA;
|
||||
break;
|
||||
case FIELD_DATA:
|
||||
data.data = d;
|
||||
data.size = l;
|
||||
append = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
entry *e = pa_xnew0(entry, 1);
|
||||
e->key.data = key.data;
|
||||
e->key.size = key.size;
|
||||
e->data.data = data.data;
|
||||
e->data.size = data.size;
|
||||
pa_hashmap_put(db->map, &e->key, e);
|
||||
append = FALSE;
|
||||
field = FIELD_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
if (ferror(f)) {
|
||||
pa_log_warn("read error. %s", pa_cstrerror(errno));
|
||||
pa_database_clear((pa_database*)db);
|
||||
}
|
||||
|
||||
if (field == FIELD_DATA && d)
|
||||
pa_xfree(d);
|
||||
|
||||
return pa_hashmap_size(db->map);
|
||||
}
|
||||
|
||||
pa_database* pa_database_open(const char *fn, pa_bool_t for_write) {
|
||||
FILE *f;
|
||||
char *path;
|
||||
simple_data *db;
|
||||
|
||||
pa_assert(fn);
|
||||
|
||||
path = pa_sprintf_malloc("%s."CANONICAL_HOST".simple", fn);
|
||||
errno = 0;
|
||||
|
||||
f = fopen(path, "r");
|
||||
|
||||
if (f || errno == ENOENT) { /* file not found is ok */
|
||||
db = pa_xnew0(simple_data, 1);
|
||||
db->map = pa_hashmap_new(hash_func, compare_func);
|
||||
db->filename = pa_xstrdup(path);
|
||||
db->tmp_filename = pa_sprintf_malloc(".%s.tmp", db->filename);
|
||||
db->read_only = !for_write;
|
||||
|
||||
if (f) {
|
||||
fill_data(db, f);
|
||||
fclose(f);
|
||||
}
|
||||
} else {
|
||||
if (errno == 0)
|
||||
errno = EIO;
|
||||
db = NULL;
|
||||
}
|
||||
|
||||
pa_xfree(path);
|
||||
|
||||
return (pa_database*) db;
|
||||
}
|
||||
|
||||
void pa_database_close(pa_database *database) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
pa_assert(db);
|
||||
|
||||
pa_database_sync(database);
|
||||
pa_database_clear(database);
|
||||
pa_xfree(db->filename);
|
||||
pa_xfree(db->tmp_filename);
|
||||
pa_hashmap_free(db->map, NULL, NULL);
|
||||
pa_xfree(db);
|
||||
}
|
||||
|
||||
pa_datum* pa_database_get(pa_database *database, const pa_datum *key, pa_datum* data) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
entry *e;
|
||||
|
||||
pa_assert(db);
|
||||
pa_assert(key);
|
||||
pa_assert(data);
|
||||
|
||||
e = pa_hashmap_get(db->map, key);
|
||||
|
||||
if (!e)
|
||||
return NULL;
|
||||
|
||||
data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
|
||||
data->size = e->data.size;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
int pa_database_set(pa_database *database, const pa_datum *key, const pa_datum* data, pa_bool_t overwrite) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
entry *e;
|
||||
int ret = 0;
|
||||
|
||||
pa_assert(db);
|
||||
pa_assert(key);
|
||||
pa_assert(data);
|
||||
|
||||
if (db->read_only)
|
||||
return -1;
|
||||
|
||||
e = new_entry(key, data);
|
||||
|
||||
if (pa_hashmap_put(db->map, &e->key, e) < 0) {
|
||||
/* entry with same key exists in hashmap */
|
||||
entry *r;
|
||||
if (overwrite) {
|
||||
r = pa_hashmap_remove(db->map, key);
|
||||
pa_hashmap_put(db->map, &e->key, e);
|
||||
} else {
|
||||
/* wont't overwrite, so clean new entry */
|
||||
r = e;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free_entry(r);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int pa_database_unset(pa_database *database, const pa_datum *key) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
entry *e;
|
||||
|
||||
pa_assert(db);
|
||||
pa_assert(key);
|
||||
|
||||
e = pa_hashmap_remove(db->map, key);
|
||||
if (!e)
|
||||
return -1;
|
||||
|
||||
free_entry(e);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_database_clear(pa_database *database) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
entry *e;
|
||||
|
||||
pa_assert(db);
|
||||
|
||||
while ((e = pa_hashmap_steal_first(db->map)))
|
||||
free_entry(e);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
signed pa_database_size(pa_database *database) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
pa_assert(db);
|
||||
|
||||
return (signed) pa_hashmap_size(db->map);
|
||||
}
|
||||
|
||||
pa_datum* pa_database_first(pa_database *database, pa_datum *key, pa_datum *data) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
entry *e;
|
||||
|
||||
pa_assert(db);
|
||||
pa_assert(key);
|
||||
|
||||
e = pa_hashmap_first(db->map);
|
||||
|
||||
if (!e)
|
||||
return NULL;
|
||||
|
||||
key->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL;
|
||||
key->size = e->key.size;
|
||||
|
||||
if (data) {
|
||||
data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
|
||||
data->size = e->data.size;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
pa_datum* pa_database_next(pa_database *database, const pa_datum *key, pa_datum *next, pa_datum *data) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
entry *e;
|
||||
entry *search;
|
||||
void *state;
|
||||
pa_bool_t pick_now;
|
||||
|
||||
pa_assert(db);
|
||||
pa_assert(next);
|
||||
|
||||
if (!key)
|
||||
return pa_database_first(database, next, data);
|
||||
|
||||
search = pa_hashmap_get(db->map, key);
|
||||
|
||||
state = NULL;
|
||||
pick_now = FALSE;
|
||||
|
||||
while ((e = pa_hashmap_iterate(db->map, &state, NULL))) {
|
||||
if (pick_now)
|
||||
break;
|
||||
|
||||
if (search == e)
|
||||
pick_now = TRUE;
|
||||
}
|
||||
|
||||
if (!pick_now || !e)
|
||||
return NULL;
|
||||
|
||||
next->data = e->key.size > 0 ? pa_xmemdup(e->key.data, e->key.size) : NULL;
|
||||
next->size = e->key.size;
|
||||
|
||||
if (data) {
|
||||
data->data = e->data.size > 0 ? pa_xmemdup(e->data.data, e->data.size) : NULL;
|
||||
data->size = e->data.size;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
static int write_uint(FILE *f, const uint32_t num) {
|
||||
size_t items;
|
||||
uint8_t values[4];
|
||||
int i;
|
||||
errno = 0;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
values[i] = (num >> (i*8)) & 0xFF;
|
||||
|
||||
items = fwrite(&values, sizeof(values), sizeof(uint8_t), f);
|
||||
|
||||
if (ferror(f))
|
||||
return -1;
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static int write_data(FILE *f, void *data, const size_t length) {
|
||||
size_t items;
|
||||
uint32_t len;
|
||||
|
||||
len = length;
|
||||
if ((items = write_uint(f, len)) <= 0)
|
||||
return -1;
|
||||
|
||||
items = fwrite(data, length, 1, f);
|
||||
|
||||
if (ferror(f) || items != 1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_entry(FILE *f, const entry *e) {
|
||||
pa_assert(f);
|
||||
pa_assert(e);
|
||||
|
||||
if (write_data(f, e->key.data, e->key.size) < 0)
|
||||
return -1;
|
||||
if (write_data(f, e->data.data, e->data.size) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_database_sync(pa_database *database) {
|
||||
simple_data *db = (simple_data*)database;
|
||||
FILE *f;
|
||||
void *state;
|
||||
entry *e;
|
||||
|
||||
pa_assert(db);
|
||||
|
||||
if (db->read_only)
|
||||
return 0;
|
||||
|
||||
errno = 0;
|
||||
|
||||
f = fopen(db->tmp_filename, "w");
|
||||
|
||||
if (!f)
|
||||
goto fail;
|
||||
|
||||
state = NULL;
|
||||
while((e = pa_hashmap_iterate(db->map, &state, NULL))) {
|
||||
if (write_entry(f, e) < 0) {
|
||||
pa_log_warn("error while writing to file. %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
f = NULL;
|
||||
|
||||
if (rename(db->tmp_filename, db->filename) < 0) {
|
||||
pa_log_warn("error while renaming file. %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (f)
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue