From f6f50d03a09d6bc423324206d274336e9905bbb4 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sat, 8 Sep 2007 11:17:19 +0200 Subject: [PATCH 1/1] Merge the new afs code. --- afs.c | 902 ++++++++++++++++++ afs.h | 199 ++++ aft.c | 1690 +++++++++++++++++++++++++++++++++ attribute.c | 395 ++++++++ audiod.c | 11 +- blob.c | 393 ++++++++ configure.ac | 3 +- error.h | 138 ++- gcc-compat.h | 2 + gui.c | 14 +- gui_common.c | 5 +- gui_common.h | 4 + hash.h | 32 + mood.c | 978 +++++++++++++++++++ mp3_afh.c | 2 +- osl.c | 2185 +++++++++++++++++++++++++++++++++++++++++++ osl.h | 206 ++++ osl_core.h | 586 ++++++++++++ para.h | 5 - playlist.c | 142 +++ playlist_selector.c | 5 +- portable_io.h | 62 ++ rbtree.c | 455 +++++++++ rbtree.h | 172 ++++ score.c | 364 +++++++ sdl_gui.c | 7 +- sha1.c | 23 + sha1.h | 5 + stat.c | 51 - string.c | 156 +++ string.h | 12 + 31 files changed, 9126 insertions(+), 78 deletions(-) create mode 100644 afs.c create mode 100644 afs.h create mode 100644 aft.c create mode 100644 attribute.c create mode 100644 blob.c create mode 100644 gui_common.h create mode 100644 hash.h create mode 100644 mood.c create mode 100644 osl.c create mode 100644 osl.h create mode 100644 osl_core.h create mode 100644 playlist.c create mode 100644 portable_io.h create mode 100644 rbtree.c create mode 100644 rbtree.h create mode 100644 score.c create mode 100644 sha1.c create mode 100644 sha1.h diff --git a/afs.c b/afs.c new file mode 100644 index 00000000..c5673c65 --- /dev/null +++ b/afs.c @@ -0,0 +1,902 @@ +#include "para.h" +#include "error.h" +#include /* readdir() */ +#include +#include + + +#include "net.h" +#include "afs.h" +#include "ipc.h" +#include "string.h" + +/** \file afs.c Paraslash's audio file selector. */ + +/** + * Compare two osl objects of string type. + * + * \param obj1 Pointer to the first object. + * \param obj2 Pointer to the second object. + * + * In any case, only \p MIN(obj1->size, obj2->size) characters of each string + * are taken into account. + * + * \return It returns an integer less than, equal to, or greater than zero if + * \a obj1 is found, respectively, to be less than, to match, or be greater than + * obj2. + * + * \sa strcmp(3), strncmp(3), osl_compare_func. + */ +int string_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + const char *str1 = (const char *)obj1->data; + const char *str2 = (const char *)obj2->data; + return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size)); +} + +/** The osl tables used by afs. \sa blob.c */ +enum afs_table_num { + /** Contains audio file information. See aft.c. */ + TBLNUM_AUDIO_FILES, + /** The table for the paraslash attributes. See attribute.c. */ + TBLNUM_ATTRIBUTES, + /** + * Paraslash's scoring system is based on Gaussian normal + * distributions, and the relevant data is stored in the rbtrees of an + * osl table containing only volatile columns. See score.c for + * details. + */ + TBLNUM_SCORES, + /** + * A standard blob table containing the mood definitions. For details + * see mood.c. + */ + TBLNUM_MOODS, + /** A blob table containing lyrics on a per-song basis. */ + TBLNUM_LYRICS, + /** Another blob table for images (for example album cover art). */ + TBLNUM_IMAGES, + /** Yet another blob table for storing standard playlists. */ + TBLNUM_PLAYLIST, + /** How many tables are in use? */ + NUM_AFS_TABLES +}; + +static struct table_info afs_tables[NUM_AFS_TABLES]; + + +/** + * A wrapper for strtol(3). + * + * \param str The string to be converted to a long integer. + * \param result The converted value is stored here. + * + * \return Positive on success, -E_ATOL on errors. + * + * \sa strtol(3), atoi(3). + */ +int para_atol(const char *str, long *result) +{ + char *endptr; + long val; + int ret, base = 10; + + errno = 0; /* To distinguish success/failure after call */ + val = strtol(str, &endptr, base); + ret = -E_ATOL; + if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) + goto out; /* overflow */ + if (errno != 0 && val == 0) + goto out; /* other error */ + if (endptr == str) + goto out; /* No digits were found */ + if (*endptr != '\0') + goto out; /* Further characters after number */ + *result = val; + ret = 1; +out: + return ret; +} + +/** + * Struct to let para_server call a function specified from child context. + * + * Commands that need to change the state of para_server can't + * change the relevant data structures directly because commands + * are executed in a child process, i.e. they get their own + * virtual address space. This structure must be used to let + * para_server (i.e. the parent process) call a function specified + * by the child (the command handler). + * + * \sa fork(2), ipc.c. + */ +struct callback_data { + /** The function to be called. */ + callback_function *handler; + /** The sma for the parameters of the callback function. */ + int query_shmid; + /** The size of the query sma. */ + size_t query_size; + /** If the callback produced a result, it is stored in this sma. */ + int result_shmid; + /** The size of the result sma. */ + size_t result_size; + /** The return value of the callback function. */ + int callback_ret; + /** The return value of the callback() procedure. */ + int sma_ret; +}; + +static struct callback_data *shm_callback_data; +static int callback_mutex; +static int child_mutex; +static int result_mutex; + +/** + * Ask the parent process to call a given function. + * + * \param f The function to be called. + * \param query Pointer to arbitrary data for the callback. + * \param result Callback result will be stored here. + * + * This function creates a shared memory area, copies the buffer pointed to by + * \a buf to that area and notifies the parent process that \a f should be + * called ASAP. It provides proper locking via semaphores to protect against + * concurent access to the shared memory area and against concurrent access by + * another child process that asks to call the same function. + * + * \return Negative, if the shared memory area could not be set up. The return + * value of the callback function otherwise. + * + * \sa shm_new(), shm_attach(), shm_detach(), mutex_lock(), mutex_unlock(), + * shm_destroy(), struct callback_data, send_option_arg_callback_request(), + * send_standard_callback_request(). + */ +int send_callback_request(callback_function *f, struct osl_object *query, + struct osl_object *result) +{ + struct callback_data cbd = {.handler = f}; + int ret; + void *query_sma; + + assert(query->data && query->size); + ret = shm_new(query->size); + if (ret < 0) + return ret; + cbd.query_shmid = ret; + cbd.query_size = query->size; + ret = shm_attach(cbd.query_shmid, ATTACH_RW, &query_sma); + if (ret < 0) + goto out; + memcpy(query_sma, query->data, query->size); + ret = shm_detach(query_sma); + if (ret < 0) + goto out; + /* prevent other children from interacting */ + mutex_lock(child_mutex); + /* prevent parent from messing with shm_callback_data. */ + mutex_lock(callback_mutex); + /* all three mutexes are locked, set parameters for callback */ + *shm_callback_data = cbd; + /* unblock parent */ + mutex_unlock(callback_mutex); + kill(getppid(), SIGUSR1); /* wake up parent */ + /* + * At this time only the parent can run. It will execute our callback + * and unlock the result_mutex when ready to indicate that the child + * may use the result. So let's sleep on this mutex. + */ + mutex_lock(result_mutex); + /* No need to aquire the callback mutex again */ + ret = shm_callback_data->sma_ret; + if (ret < 0) /* sma problem, callback might not have been executed */ + goto unlock_child_mutex; + if (shm_callback_data->result_shmid >= 0) { /* parent provided a result */ + void *sma; + ret = shm_attach(shm_callback_data->result_shmid, ATTACH_RO, + &sma); + if (ret >= 0) { + if (result) { /* copy result */ + result->size = shm_callback_data->result_size; + result->data = para_malloc(result->size); + memcpy(result->data, sma, result->size); + ret = shm_detach(sma); + if (ret < 0) + PARA_ERROR_LOG("can not detach result\n"); + } else + PARA_WARNING_LOG("no result pointer\n"); + } else + PARA_ERROR_LOG("attach result failed: %d\n", ret); + if (shm_destroy(shm_callback_data->result_shmid) < 0) + PARA_ERROR_LOG("destroy result failed\n"); + } else { /* no result from callback */ + if (result) { + PARA_WARNING_LOG("callback has no result\n"); + result->data = NULL; + result->size = 0; + } + } + ret = shm_callback_data->callback_ret; +unlock_child_mutex: + /* give other children a chance */ + mutex_unlock(child_mutex); +out: + if (shm_destroy(cbd.query_shmid) < 0) + PARA_ERROR_LOG("%s\n", "shm destroy error"); + PARA_DEBUG_LOG("callback_ret: %d\n", ret); + return ret; +} + +/** + * Send a callback request passing an options structure and an argument vector. + * + * \param options pointer to an arbitrary data structure. + * \param argc Argument count. + * \param argv Standard argument vector. + * \param f The callback function. + * \param result The result of the query is stored here. + * + * Some commands have a couple of options that are parsed in child context for + * syntactic correctness and are stored in a special options structure for that + * command. This function allows to pass such a structure together with a list + * of further arguments (often a list of audio files) to the parent process. + * + * \sa send_standard_callback_request(), send_callback_request(). + */ +int send_option_arg_callback_request(struct osl_object *options, + int argc, const char **argv, callback_function *f, + struct osl_object *result) +{ + char *p; + int i, ret; + struct osl_object query = {.size = options? options->size : 0}; + + for (i = 0; i < argc; i++) + query.size += strlen(argv[i]) + 1; + query.data = para_malloc(query.size); + p = query.data; + if (options) { + memcpy(query.data, options->data, options->size); + p += options->size; + } + for (i = 0; i < argc; i++) { + strcpy(p, argv[i]); /* OK */ + p += strlen(argv[i]) + 1; + } + ret = send_callback_request(f, &query, result); + free(query.data); + return ret; +} + +/** + * Send a callback request with an argument vector only. + * + * \param argc The same meaning as in send_option_arg_callback_request(). + * \param argv The same meaning as in send_option_arg_callback_request(). + * \param f The same meaning as in send_option_arg_callback_request(). + * \param result The same meaning as in send_option_arg_callback_request(). + * + * This is similar to send_option_arg_callback_request(), but no options buffer + * is passed to the parent process. + * + * \return The return value of the underlying call to + * send_option_arg_callback_request(). + */ +int send_standard_callback_request(int argc, const char **argv, + callback_function *f, struct osl_object *result) +{ + return send_option_arg_callback_request(NULL, argc, argv, f, result); +} + +/* + * write input from fd to dynamically allocated char array, + * but maximal max_size byte. Return size. + */ +static int fd2buf(int fd, char **buf, int max_size) +{ + const size_t chunk_size = 1024; + size_t size = 2048; + char *p; + int ret; + + *buf = para_malloc(size * sizeof(char)); + p = *buf; + while ((ret = read(fd, p, chunk_size)) > 0) { + p += ret; + if ((p - *buf) + chunk_size >= size) { + char *tmp; + + size *= 2; + if (size > max_size) { + ret = -E_INPUT_TOO_LARGE; + goto out; + } + tmp = para_realloc(*buf, size); + p = (p - *buf) + tmp; + *buf = tmp; + } + } + if (ret < 0) { + ret = -E_READ; + goto out; + } + ret = p - *buf; +out: + if (ret < 0 && *buf) + free(*buf); + return ret; +} + +/** + * Read from stdin, and send the result to the parent process. + * + * \param arg_obj Pointer to the arguments to \a f. + * \param f The callback function. + * \param max_len Don't read more than that many bytes from stdin. + * \param result The result of the query is stored here. + * + * This function is used by commands that wish to let para_server store + * arbitrary data specified by the user (for instance the add_blob family of + * commands). First, at most \a max_len bytes are read from stdin, the result + * is concatenated with the buffer given by \a arg_obj, and the combined buffer + * is made available to the parent process via shared memory. + * + * \return Negative on errors, the return value of the underlying call to + * send_callback_request() otherwise. + */ +int stdin_command(struct osl_object *arg_obj, callback_function *f, + unsigned max_len, struct osl_object *result) +{ + char *stdin_buf; + size_t stdin_len; + struct osl_object query; + int ret = fd2buf(STDIN_FILENO, &stdin_buf, max_len); + + if (ret < 0) + return ret; + stdin_len = ret; + query.size = arg_obj->size + stdin_len; + query.data = para_malloc(query.size); + memcpy(query.data, arg_obj->data, arg_obj->size); + memcpy((char *)query.data + arg_obj->size, stdin_buf, stdin_len); + free(stdin_buf); + ret = send_callback_request(f, &query, result); + free(query.data); + return ret; +} + +static void para_init_random_seed(void) +{ + struct timeval now; + unsigned int seed; + + gettimeofday(&now, NULL); + seed = now.tv_usec; + srand(seed); +} + +/** + * Open the audio file with highest score. + * + * \param afd Audio file data is returned here. + * + * This stores all information for streaming the "best" audio file + * in the \a afd structure. + * + * \return Positive on success, negative on errors. + * + * \sa close_audio_file(), open_and_update_audio_file(). + */ +int open_next_audio_file(struct audio_file_data *afd) +{ + struct osl_row *aft_row; + int ret; + for (;;) { + ret = score_get_best(&aft_row, &afd->score); + if (ret < 0) + return ret; + ret = open_and_update_audio_file(aft_row, afd); + if (ret >= 0) + return ret; + } +} + +/** + * Free all resources which were allocated by open_next_audio_file(). + * + * \param afd The structure previously filled in by open_next_audio_file(). + * + * \return The return value of the underlying call to para_munmap(). + * + * \sa open_next_audio_file(). + */ +int close_audio_file(struct audio_file_data *afd) +{ + free(afd->afhi.chunk_table); + return para_munmap(afd->map.data, afd->map.size); +} + +static void play_loop(enum play_mode current_play_mode) +{ + int i, ret; + struct audio_file_data afd; + + afd.current_play_mode = current_play_mode; + for (i = 0; i < 0; i++) { + ret = open_next_audio_file(&afd); + if (ret < 0) { + PARA_ERROR_LOG("failed to open next audio file: %d\n", ret); + return; + } + PARA_NOTICE_LOG("next audio file: %s, score: %li\n", afd.path, afd.score); + sleep(1); + close_audio_file(&afd); + } +} + +static enum play_mode init_admissible_files(void) +{ + int ret; + char *given_mood, *given_playlist; + + given_mood = "mood_that_was_given_at_the_command_line"; + given_playlist = "given_playlist"; + + if (given_mood) { + ret = mood_open(given_mood); + if (ret >= 0) { + if (given_playlist) + PARA_WARNING_LOG("ignoring playlist %s\n", + given_playlist); + return PLAY_MODE_MOOD; + } + } + if (given_playlist) { + ret = playlist_open(given_playlist); + if (ret >= 0) + return PLAY_MODE_PLAYLIST; + } + ret = mood_open(NULL); /* open first available mood */ + if (ret >= 0) + return PLAY_MODE_MOOD; + mood_open(""); /* open dummy mood, always successful */ + return PLAY_MODE_MOOD; +} + +static int afs_init(void) +{ + int ret, shmid; + void *shm_area; + enum play_mode current_play_mode; + + para_init_random_seed(); + + ret = attribute_init(&afs_tables[TBLNUM_ATTRIBUTES]); + PARA_DEBUG_LOG("ret %d\n", ret); + if (ret < 0) + return ret; + ret = moods_init(&afs_tables[TBLNUM_MOODS]); + if (ret < 0) + goto moods_init_error; + ret = playlists_init(&afs_tables[TBLNUM_PLAYLIST]); + if (ret < 0) + goto playlists_init_error; + ret = lyrics_init(&afs_tables[TBLNUM_LYRICS]); + if (ret < 0) + goto lyrics_init_error; + ret = images_init(&afs_tables[TBLNUM_IMAGES]); + if (ret < 0) + goto images_init_error; + ret = score_init(&afs_tables[TBLNUM_SCORES]); + if (ret < 0) + goto score_init_error; + ret = aft_init(&afs_tables[TBLNUM_AUDIO_FILES]); + if (ret < 0) + goto aft_init_error; + + current_play_mode = init_admissible_files(); + play_loop(current_play_mode); + + ret = shm_new(sizeof(struct callback_data)); + if (ret < 0) + return ret; + shmid = ret; + ret = shm_attach(shmid, ATTACH_RW, &shm_area); + if (ret < 0) + return ret; + shm_callback_data = shm_area; + ret = mutex_new(); + if (ret < 0) + return ret; + callback_mutex = ret; + ret = mutex_new(); + if (ret < 0) + return ret; + child_mutex = ret; + ret = mutex_new(); + if (ret < 0) + return ret; + result_mutex = ret; + mutex_lock(result_mutex); + return 1; +aft_init_error: + score_shutdown(OSL_MARK_CLEAN); +score_init_error: + images_shutdown(OSL_MARK_CLEAN); +images_init_error: + lyrics_shutdown(OSL_MARK_CLEAN); +lyrics_init_error: + playlists_shutdown(OSL_MARK_CLEAN); +playlists_init_error: + moods_shutdown(OSL_MARK_CLEAN); +moods_init_error: + attribute_shutdown(OSL_MARK_CLEAN); + return ret; +} + +static uint32_t afs_socket_cookie; +static int para_random(unsigned max) +{ + return ((max + 0.0) * (rand() / (RAND_MAX + 1.0))); +} + +int setup(void) +{ + int ret, afs_server_socket[2]; + + para_init_random_seed(); + ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, afs_server_socket); + if (ret < 0) + exit(EXIT_FAILURE); + afs_socket_cookie = para_random((uint32_t)-1); + ret = fork(); + if (ret < 0) + exit(EXIT_FAILURE); + if (!ret) { /* child (afs) */ + char *socket_name = "/tmp/afs_command_socket"; + struct sockaddr_un unix_addr; + int fd; + + unlink(socket_name); + ret = create_local_socket(socket_name, &unix_addr, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + if (ret < 0) + exit(EXIT_FAILURE); + fd = ret; + if (listen(fd , 5) < 0) { + PARA_EMERG_LOG("%s", "can not listen on socket\n"); + exit(EXIT_FAILURE); + } + ret = afs_init(); + if (ret < 0) + exit(EXIT_FAILURE); + PARA_NOTICE_LOG("accepting\n"); + ret = para_accept(fd, &unix_addr, sizeof(struct sockaddr_un)); + return ret; + } + ret = fork(); + if (ret < 0) + exit(EXIT_FAILURE); + if (!ret) { /* child (handler) */ + PARA_NOTICE_LOG("reading stdin\n"); + for (;;) { + char buf[255]; + read(0, buf, 255); + PARA_NOTICE_LOG("read: %s\n", buf); + } + + } + for (;;) { + sleep(10); + PARA_NOTICE_LOG("sending next requerst\n"); + } +} + + +static int create_all_tables(void) +{ + int i, ret; + + for (i = 0; i < NUM_AFS_TABLES; i++) { + struct table_info *ti = afs_tables + i; + + if (ti->flags & TBLFLAG_SKIP_CREATE) + continue; + ret = osl_create_table(ti->desc); + if (ret < 0) + return ret; + } + return 1; +} + +/* TODO load tables after init */ +static int com_init(__a_unused int fd, int argc, const char **argv) +{ + int i, j, ret; + if (argc == 1) + return create_all_tables(); + for (i = 1; i < argc; i++) { + for (j = 0; j < NUM_AFS_TABLES; j++) { + struct table_info *ti = afs_tables + j; + + if (ti->flags & TBLFLAG_SKIP_CREATE) + continue; + if (strcmp(argv[i], ti->desc->name)) + continue; + PARA_NOTICE_LOG("creating table %s\n", argv[i]); + ret = osl_create_table(ti->desc); + if (ret < 0) + return ret; + break; + } + if (j == NUM_AFS_TABLES) + return -E_BAD_TABLE_NAME; + } + return 1; +} +/** Describes a command of para_server. */ +struct command { + /** The name of the command. */ + const char *name; + /** The handler function. */ + int (*handler)(int fd, int argc, const char **argv); +}; + +static struct command cmd[] = { +{ + .name = "add", + .handler = com_add, +}, +{ + .name = "addlyr", + .handler = com_addlyr, +}, +{ + .name = "addimg", + .handler = com_addimg, +}, +{ + .name = "addmood", + .handler = com_addmood, +}, +{ + .name = "addpl", + .handler = com_addpl, +}, +{ + .name = "catlyr", + .handler = com_catlyr, +}, +{ + .name = "catimg", + .handler = com_catimg, +}, +{ + .name = "mvimg", + .handler = com_mvimg, +}, +{ + .name = "mvlyr", + .handler = com_mvlyr, +}, +{ + .name = "mvmood", + .handler = com_mvmood, +}, +{ + .name = "mvpl", + .handler = com_mvpl, +}, +{ + .name = "catmood", + .handler = com_catmood, +}, +{ + .name = "catpl", + .handler = com_catpl, +}, +{ + .name = "rmatt", + .handler = com_rmatt, +}, +{ + .name = "init", + .handler = com_init, +}, +{ + .name = "lsatt", + .handler = com_lsatt, +}, +{ + .name = "ls", + .handler = com_afs_ls, +}, +{ + .name = "lslyr", + .handler = com_lslyr, +}, +{ + .name = "lsimg", + .handler = com_lsimg, +}, +{ + .name = "lsmood", + .handler = com_lsmood, +}, +{ + .name = "lspl", + .handler = com_lspl, +}, +{ + .name = "setatt", + .handler = com_setatt, +}, +{ + .name = "addatt", + .handler = com_addatt, +}, +{ + .name = "rm", + .handler = com_afs_rm, +}, +{ + .name = "rmlyr", + .handler = com_rmlyr, +}, +{ + .name = "rmimg", + .handler = com_rmimg, +}, +{ + .name = "rmmood", + .handler = com_rmmood, +}, +{ + .name = "rmpl", + .handler = com_rmpl, +}, +{ + .name = "touch", + .handler = com_touch, +}, +{ + .name = NULL, +} +}; + +static void call_callback(void) +{ + struct osl_object query, result = {.data = NULL}; + int ret, ret2; + + shm_callback_data->result_shmid = -1; /* no result */ + ret = shm_attach(shm_callback_data->query_shmid, ATTACH_RW, + &query.data); + if (ret < 0) + goto out; + query.size = shm_callback_data->query_size; + shm_callback_data->callback_ret = shm_callback_data->handler(&query, + &result); + if (result.data && result.size) { + void *sma; + ret = shm_new(result.size); + if (ret < 0) + goto detach_query; + shm_callback_data->result_shmid = ret; + shm_callback_data->result_size = result.size; + ret = shm_attach(shm_callback_data->result_shmid, ATTACH_RW, &sma); + if (ret < 0) + goto destroy_result; + memcpy(sma, result.data, result.size); + ret = shm_detach(sma); + if (ret < 0) { + PARA_ERROR_LOG("detach result failed\n"); + goto destroy_result; + } + } + ret = 1; + goto detach_query; +destroy_result: + if (shm_destroy(shm_callback_data->result_shmid) < 0) + PARA_ERROR_LOG("destroy result failed\n"); + shm_callback_data->result_shmid = -1; +detach_query: + free(result.data); + ret2 = shm_detach(query.data); + if (ret2 < 0) { + PARA_ERROR_LOG("detach query failed\n"); + if (ret >= 0) + ret = ret2; + } +out: + if (ret < 0) + PARA_ERROR_LOG("sma error %d\n", ret); + shm_callback_data->sma_ret = ret; + shm_callback_data->handler = NULL; + mutex_unlock(result_mutex); /* wake up child */ +} + +static void dummy(__a_unused int s) +{} + +static void afs_shutdown(enum osl_close_flags flags) +{ + score_shutdown(flags); + attribute_shutdown(flags); + mood_close(); + playlist_close(); + moods_shutdown(flags); + playlists_shutdown(flags); + lyrics_shutdown(flags); + images_shutdown(flags); + aft_shutdown(flags); +} + +static int got_sigchld; +static void sigchld_handler(__a_unused int s) +{ + got_sigchld = 1; +} + +static void server_loop(int child_pid) +{ +// int status; + + PARA_DEBUG_LOG("server pid: %d, child pid: %d\n", + getpid(), child_pid); + for (;;) { + mutex_lock(callback_mutex); + if (shm_callback_data->handler) + call_callback(); + mutex_unlock(callback_mutex); + usleep(100); + if (!got_sigchld) + continue; + mutex_destroy(result_mutex); + mutex_destroy(callback_mutex); + mutex_destroy(child_mutex); + afs_shutdown(OSL_MARK_CLEAN); + exit(EXIT_SUCCESS); + } +} + +#if 0 +int main(int argc, const char **argv) +{ + int i, ret = -E_AFS_SYNTAX; + + signal(SIGUSR1, dummy); + signal(SIGCHLD, sigchld_handler); + if (argc < 2) + goto out; + ret = setup(); +// ret = afs_init(); + if (ret < 0) { + PARA_EMERG_LOG("afs_init returned %d\n", ret); + exit(EXIT_FAILURE); + } + ret = fork(); + if (ret < 0) { + ret = -E_FORK; + goto out; + } + if (ret) + server_loop(ret); + for (i = 0; cmd[i].name; i++) { + if (strcmp(cmd[i].name, argv[1])) + continue; + ret = cmd[i].handler(1, argc - 1 , argv + 1); + goto out; + + } + PARA_ERROR_LOG("unknown command: %s\n", argv[1]); + ret = -1; +out: + if (ret < 0) + PARA_ERROR_LOG("error %d\n", ret); + else + PARA_DEBUG_LOG("%s", "success\n"); + afs_shutdown(0); + return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; +} +#endif diff --git a/afs.h b/afs.h new file mode 100644 index 00000000..fdc42f1f --- /dev/null +++ b/afs.h @@ -0,0 +1,199 @@ +#include +#include "osl.h" +#include "afh.h" +#include "hash.h" + +#define DATABASE_DIR "/home/maan/tmp/osl" /* FIXME */ + +struct afs_info { + uint64_t last_played; + uint64_t attributes; + uint32_t num_played; + uint32_t image_id; + uint32_t lyrics_id; + uint8_t audio_format_id; +}; + +enum afs_table_flags {TBLFLAG_SKIP_CREATE}; + +struct table_info { + const struct osl_table_description *desc; + struct osl_table *table; + enum afs_table_flags flags; +}; + +enum ls_sorting_method { + LS_SORT_BY_PATH, /* -sp (default) */ + LS_SORT_BY_SCORE, /* -ss */ + LS_SORT_BY_LAST_PLAYED, /* -sl */ + LS_SORT_BY_NUM_PLAYED, /* -sn */ + LS_SORT_BY_FREQUENCY, /* -sf */ + LS_SORT_BY_CHANNELS, /* -sc */ + LS_SORT_BY_IMAGE_ID, /* -si */ + LS_SORT_BY_LYRICS_ID, /* -sy */ + LS_SORT_BY_BITRATE, /* -sb */ + LS_SORT_BY_DURATION, /* -sd */ + LS_SORT_BY_AUDIO_FORMAT, /* -sa */ + LS_SORT_BY_HASH, /* -sh */ +}; + +enum ls_listing_mode { + LS_MODE_SHORT, + LS_MODE_LONG, + LS_MODE_VERBOSE, + LS_MODE_MBOX +}; + +enum ls_flags { + LS_FLAG_FULL_PATH = 1, + LS_FLAG_ADMISSIBLE_ONLY = 2, + LS_FLAG_REVERSE = 4, +}; + +struct ls_widths { + unsigned short score_width; + unsigned short image_id_width; + unsigned short lyrics_id_width; + unsigned short bitrate_width; + unsigned short frequency_width; + unsigned short duration_width; + unsigned short num_played_width; +}; + +struct ls_data { + struct audio_format_info afhi; + struct afs_info afsi; + char *path; + long score; + HASH_TYPE *hash; +}; + +struct ls_options { + unsigned flags; + enum ls_sorting_method sorting; + enum ls_listing_mode mode; + char **patterns; + int num_patterns; + struct ls_widths widths; + uint32_t array_size; + uint32_t num_matching_paths; + struct ls_data *data; + struct ls_data **data_ptr; +}; + +enum play_mode {PLAY_MODE_MOOD, PLAY_MODE_PLAYLIST}; + +struct audio_file_data { + enum play_mode current_play_mode; + long score; + struct afs_info afsi; + struct audio_format_info afhi; + char *path; + struct osl_object map; +}; + +/* afs */ +typedef int callback_function(const struct osl_object *, struct osl_object *); +int send_callback_request(callback_function *f, struct osl_object *query, + struct osl_object *result); +int send_standard_callback_request(int argc, const char **argv, + callback_function *f, struct osl_object *result); +int send_option_arg_callback_request(struct osl_object *options, + int argc, const char **argv, callback_function *f, + struct osl_object *result); +int stdin_command(struct osl_object *arg_obj, callback_function *f, + unsigned max_len, struct osl_object *result); +int string_compare(const struct osl_object *obj1, const struct osl_object *obj2); +int para_atol(const char *str, long *result); +int open_next_audio_file(struct audio_file_data *afd); +int close_audio_file(struct audio_file_data *afd); + +/* score */ +int score_init(struct table_info *ti); +void score_shutdown(enum osl_close_flags flags); +int admissible_file_loop(void *data, osl_rbtree_loop_func *func); +int admissible_file_loop_reverse(void *data, osl_rbtree_loop_func *func); +int score_get_best(struct osl_row **aft_row, long *score); +int get_score_and_aft_row(struct osl_row *score_row, long *score, struct osl_row **aft_row); +int score_add(const struct osl_row *row, long score); +int score_update(const struct osl_row *aft_row, long new_score); +int get_num_admissible_files(unsigned *num); +int score_delete(const struct osl_row *aft_row); +int row_belongs_to_score_table(const struct osl_row *aft_row); + +/* attribute */ +int attribute_init(struct table_info *ti); +void attribute_shutdown(enum osl_close_flags flags); +void get_attribute_bitmap(uint64_t *atts, char *buf); +int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum); +int com_lsatt(int fd, int argc, const char **argv); +int com_setatt(int fd, int argc, const char **argv); +int com_addatt(int fd, int argc, const char **argv); +int com_rmatt(int fd, int argc, const char **argv); +int get_attribute_text(uint64_t *atts, const char *delim, char **text); + +/* aft */ +int aft_init(struct table_info *ti); +void aft_shutdown(enum osl_close_flags flags); +int aft_get_row_of_path(char *path, struct osl_row **row); +int open_and_update_audio_file(struct osl_row *aft_row, struct audio_file_data *afd); +int load_afsi(struct afs_info *afsi, struct osl_object *obj); +void save_afsi(struct afs_info *afsi, struct osl_object *obj); +int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi); +int get_audio_file_path_of_row(const struct osl_row *row, char **path); +int get_afsi_object_of_row(const void *row, struct osl_object *obj); +int audio_file_loop(void *private_data, osl_rbtree_loop_func *func); +int com_touch(int fd, int argc, const char **argv); +int com_add(int fd, int argc, const char **argv); +int com_afs_ls(int fd, int argc, const char **argv); +int com_afs_rm(int fd, int argc, const char **argv); + +/* mood */ +int mood_open(char *mood_name); +void mood_close(void); +int mood_update_audio_file(const struct osl_row *aft_row, struct afs_info *old_afsi); +int mood_reload(void); +int mood_delete_audio_file(const struct osl_row *aft_row); + + +/* playlist */ +int playlist_open(char *name); +void playlist_close(void); +int playlist_update_audio_file(struct osl_row *aft_row); + +/** evaluates to 1 if x < y, to -1 if x > y and to 0 if x == y */ +#define NUM_COMPARE(x, y) ((int)((x) < (y)) - (int)((x) > (y))) + + +#define DECLARE_BLOB_SYMBOLS(table_name, cmd_prefix) \ + int table_name ## _init(struct table_info *ti); \ + void table_name ## _shutdown(enum osl_close_flags flags); \ + int com_add ## cmd_prefix(int fd, int argc, const char **argv); \ + int com_rm ## cmd_prefix(int fd, int argc, const char **argv); \ + int com_cat ## cmd_prefix(int fd, int argc, const char **argv); \ + int com_ls ## cmd_prefix(int fd, int argc, const char **argv); \ + int com_mv ## cmd_prefix(int fd, int argc, const char **argv); \ + int cmd_prefix ## _get_name_by_id(uint32_t id, char **name); \ + extern struct osl_table *table_name ## _table; + +DECLARE_BLOB_SYMBOLS(lyrics, lyr); +DECLARE_BLOB_SYMBOLS(images, img); +DECLARE_BLOB_SYMBOLS(moods, mood); +DECLARE_BLOB_SYMBOLS(playlists, pl); + +enum blob_table_columns {BLOBCOL_ID, BLOBCOL_NAME, BLOBCOL_DEF, NUM_BLOB_COLUMNS}; +#define DEFINE_BLOB_TABLE_DESC(table_name) \ + const struct osl_table_description table_name ## _table_desc = { \ + .dir = DATABASE_DIR, \ + .name = #table_name, \ + .num_columns = NUM_BLOB_COLUMNS, \ + .flags = OSL_LARGE_TABLE, \ + .column_descriptions = blob_cols \ + }; + +#define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table; + +#define INIT_BLOB_TABLE(table_name) \ + DEFINE_BLOB_TABLE_DESC(table_name); \ + DEFINE_BLOB_TABLE_PTR(table_name); + diff --git a/aft.c b/aft.c new file mode 100644 index 00000000..aaeab8a3 --- /dev/null +++ b/aft.c @@ -0,0 +1,1690 @@ +#include "para.h" +#include "error.h" +#include +#include +#include "afs.h" +#include "string.h" + +int mp3_get_file_info(char *map, size_t numbytes, + struct audio_format_info *afi); /* FXIME */ + +#define AFS_AUDIO_FILE_DIR "/home/mp3" + +static void *audio_file_table; + +/** + * Describes the structure of the mmapped-afs info struct. + * + * \sa struct afs_info. + */ +enum afsi_offsets { + /** Where .last_played is stored. */ + AFSI_LAST_PLAYED_OFFSET = 0, + /** Storage position of the attributes bitmap. */ + AFSI_ATTRIBUTES_OFFSET = 8, + /** Storage position of the .num_played field. */ + AFSI_NUM_PLAYED_OFFSET = 16, + /** Storage position of the .image_id field. */ + AFSI_IMAGE_ID_OFFSET = 20, + /** Storage position of the .lyrics_id field. */ + AFSI_LYRICS_ID_OFFSET = 24, + /** Storage position of the .audio_format_id field. */ + AFSI_AUDIO_FORMAT_ID_OFFSET = 28, + /** On-disk storage space needed. */ + AFSI_SIZE = 29 +}; + +/** + * Convert a struct afs_info to an osl object. + * + * \param afsi Pointer to the audio file info to be converted. + * \param obj Result pointer. + * + * \sa load_afsi(). + */ +void save_afsi(struct afs_info *afsi, struct osl_object *obj) +{ + struct afs_info default_afs_info = { + .last_played = time(NULL) - 365 * 24 * 60 * 60, + .attributes = 0, + .num_played = 0, + .image_id = 0, + .lyrics_id = 0, + .audio_format_id = 5, /* FIXME */ + }; + char *buf = obj->data; + + if (!afsi) + afsi = &default_afs_info; + + write_u64(buf + AFSI_LAST_PLAYED_OFFSET, afsi->last_played); + write_u64(buf + AFSI_ATTRIBUTES_OFFSET, afsi->attributes); + write_u32(buf + AFSI_NUM_PLAYED_OFFSET, afsi->num_played); + write_u32(buf + AFSI_IMAGE_ID_OFFSET, afsi->image_id); + write_u32(buf + AFSI_LYRICS_ID_OFFSET, afsi->lyrics_id); + write_u8(buf + AFSI_AUDIO_FORMAT_ID_OFFSET, + afsi->audio_format_id); +} + +/** + * Get the audio file selector info struct stored in an osl object. + * + * \param afsi Points to the audio_file info structure to be filled in. + * \param obj The osl object holding the data. + * + * \return Positive on success, negative on errors. Possible errors: \p E_BAD_AFS. + * + * \sa save_afsi(). + */ +int load_afsi(struct afs_info *afsi, struct osl_object *obj) +{ + char *buf = obj->data; + if (obj->size < AFSI_SIZE) + return -E_BAD_AFS; + afsi->last_played = read_u64(buf + AFSI_LAST_PLAYED_OFFSET); + afsi->attributes = read_u64(buf + AFSI_ATTRIBUTES_OFFSET); + afsi->num_played = read_u32(buf + AFSI_NUM_PLAYED_OFFSET); + afsi->image_id = read_u32(buf + AFSI_IMAGE_ID_OFFSET); + afsi->lyrics_id = read_u32(buf + AFSI_LYRICS_ID_OFFSET); + afsi->audio_format_id = read_u8(buf + + AFSI_AUDIO_FORMAT_ID_OFFSET); + return 1; +} + +/** The columns of the audio file table. */ +enum audio_file_table_columns { + /** The hash on the content of the audio file. */ + AFTCOL_HASH, + /** The full path in the filesystem. */ + AFTCOL_PATH, + /** The audio file selector info. */ + AFTCOL_AFSI, + /** The audio format handler info. */ + AFTCOL_AFHI, + /** The chunk table info and the chunk table of the audio file. */ + AFTCOL_CHUNKS, + /** The number of columns of this table. */ + NUM_AFT_COLUMNS +}; + +static struct osl_column_description aft_cols[] = { + [AFTCOL_HASH] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, + .name = "hash", + .compare_function = osl_hash_compare, + .data_size = HASH_SIZE + }, + [AFTCOL_PATH] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_RBTREE | OSL_UNIQUE, + .name = "path", + .compare_function = string_compare, + }, + [AFTCOL_AFSI] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_FIXED_SIZE, + .name = "afs_info", + .data_size = AFSI_SIZE + }, + [AFTCOL_AFHI] = { + .storage_type = OSL_MAPPED_STORAGE, + .name = "afh_info", + }, + [AFTCOL_CHUNKS] = { + .storage_type = OSL_DISK_STORAGE, + .name = "chunks", + } +}; + +static const struct osl_table_description audio_file_table_desc = { + .dir = DATABASE_DIR, + .name = "audio_files", + .num_columns = NUM_AFT_COLUMNS, + .flags = OSL_LARGE_TABLE, + .column_descriptions = aft_cols +}; + +static char *prefix_path(const char *prefix, int len, const char *path) +{ + int speclen; + char *n; + + for (;;) { + char c; + if (*path != '.') + break; + c = path[1]; + /* "." */ + if (!c) { + path++; + break; + } + /* "./" */ + if (c == '/') { + path += 2; + continue; + } + if (c != '.') + break; + c = path[2]; + if (!c) + path += 2; + else if (c == '/') + path += 3; + else + break; + /* ".." and "../" */ + /* Remove last component of the prefix */ + do { + if (!len) + return NULL; + len--; + } while (len && prefix[len-1] != '/'); + continue; + } + if (!len) + return para_strdup(path); + speclen = strlen(path); + n = para_malloc(speclen + len + 1); + memcpy(n, prefix, len); + memcpy(n + len, path, speclen+1); + return n; +} + +/* + * We fundamentally don't like some paths: we don't want + * dot or dot-dot anywhere. + * + * Also, we don't want double slashes or slashes at the + * end that can make pathnames ambiguous. + */ +static int verify_dotfile(const char *rest) +{ + /* + * The first character was '.', but that has already been discarded, we + * now test the rest. + */ + switch (*rest) { + /* "." is not allowed */ + case '\0': case '/': + return 1; + + case '.': + if (rest[1] == '\0' || rest[1] == '/') + return -1; + } + return 1; +} + +static int verify_path(const char *path, char **resolved_path) +{ + const char *orig_path = path; + char c; + const char prefix[] = AFS_AUDIO_FILE_DIR "/"; + const size_t prefix_len = strlen(prefix); + + PARA_DEBUG_LOG("path: %s\n", path); + c = *path++; + if (!c) + return -E_BAD_PATH; + while (c) { + if (c == '/') { + c = *path++; + switch (c) { + default: + continue; + case '/': case '\0': + break; + case '.': + if (verify_dotfile(path) > 0) + continue; + } + *resolved_path = NULL; + return -E_BAD_PATH; + } + c = *path++; + } + if (*orig_path == '/') + *resolved_path = prefix_path("", 0, orig_path); + else + *resolved_path = prefix_path(prefix, prefix_len, orig_path); + PARA_DEBUG_LOG("resolved: %s\n", *resolved_path); + return *resolved_path? 1: -E_BAD_PATH; +} + +enum afhi_offsets { + AFHI_SECONDS_TOTAL_OFFSET = 0, + AFHI_BITRATE_OFFSET = 4, + AFHI_FREQUENCY_OFFSET = 8, + AFHI_CHANNELS_OFFSET = 12, + AFHI_INFO_STRING_OFFSET = 13, + MIN_AFHI_SIZE = 14 +}; + +static unsigned sizeof_afhi_buf(const struct audio_format_info *afhi) +{ + if (!afhi) + return 0; + return strlen(afhi->info_string) + MIN_AFHI_SIZE; +} + +static void save_afhi(struct audio_format_info *afhi, char *buf) +{ + if (!afhi) + return; + write_u32(buf + AFHI_SECONDS_TOTAL_OFFSET, + afhi->seconds_total); + write_u32(buf + AFHI_BITRATE_OFFSET, afhi->bitrate); + write_u32(buf + AFHI_FREQUENCY_OFFSET, afhi->frequency); + write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels); + strcpy(buf + AFHI_INFO_STRING_OFFSET, afhi->info_string); /* OK */ + PARA_DEBUG_LOG("last byte written: %p\n", buf + AFHI_INFO_STRING_OFFSET + strlen(afhi->info_string)); +} + +static void load_afhi(const char *buf, struct audio_format_info *afhi) +{ + afhi->seconds_total = read_u32(buf + AFHI_SECONDS_TOTAL_OFFSET); + afhi->bitrate = read_u32(buf + AFHI_BITRATE_OFFSET); + afhi->frequency = read_u32(buf + AFHI_FREQUENCY_OFFSET); + afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET); + strcpy(afhi->info_string, buf + AFHI_INFO_STRING_OFFSET); +} + +static unsigned sizeof_chunk_info_buf(struct audio_format_info *afhi) +{ + if (!afhi) + return 0; + return 4 * afhi->chunks_total + 20; + +} + +/** The offsets of the data contained in the AFTCOL_CHUNKS column. */ +enum chunk_info_offsets { + /** The total number of chunks (4 bytes). */ + CHUNKS_TOTAL_OFFSET = 0, + /** The length of the audio file header (4 bytes). */ + HEADER_LEN_OFFSET = 4, + /** The start of the audio file header (4 bytes). */ + HEADER_OFFSET_OFFSET = 8, + /** The seconds part of the chunk time (4 bytes). */ + CHUNK_TV_TV_SEC_OFFSET = 12, + /** The microseconds part of the chunk time (4 bytes). */ + CHUNK_TV_TV_USEC = 16, + /** Chunk table entries start here. */ + CHUNK_TABLE_OFFSET = 20, +}; + +/* TODO: audio format handlers could just produce this */ +static void save_chunk_info(struct audio_format_info *afhi, char *buf) +{ + int i; + + if (!afhi) + return; + write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total); + write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len); + write_u32(buf + HEADER_OFFSET_OFFSET, afhi->header_offset); + write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec); + write_u32(buf + CHUNK_TV_TV_USEC, afhi->chunk_tv.tv_usec); + for (i = 0; i < afhi->chunks_total; i++) + write_u32(buf + CHUNK_TABLE_OFFSET + 4 * i, afhi->chunk_table[i]); +} + +static int load_chunk_info(struct osl_object *obj, struct audio_format_info *afhi) +{ + char *buf = obj->data; + int i; + + if (obj->size < CHUNK_TABLE_OFFSET) + return -E_BAD_DATA_SIZE; + + afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET); + afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET); + afhi->header_offset = read_u32(buf + HEADER_OFFSET_OFFSET); + afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET); + afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC); + + if (afhi->chunks_total * 4 + CHUNK_TABLE_OFFSET > obj->size) + return -E_BAD_DATA_SIZE; + afhi->chunk_table = para_malloc(afhi->chunks_total * sizeof(size_t)); + for (i = 0; i < afhi->chunks_total; i++) + afhi->chunk_table[i] = read_u32(buf + CHUNK_TABLE_OFFSET + 4 * i); + return 1; +} + +/** + * Get the row of the audio file table corresponding to the given path. + * + * \param path The full path of the audio file. + * \param row Result pointer. + * + * \return The return value of the underlying call to osl_get_row(). + */ +int aft_get_row_of_path(char *path, struct osl_row **row) +{ + struct osl_object obj = {.data = path, .size = strlen(path) + 1}; + return osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row); +} + +/** + * Get the row of the audio file table corresponding to the given hash value. + * + * \param hash The hash value of the desired audio file. + * \param row resul pointer. + * + * \return The return value of the underlying call to osl_get_row(). + */ +int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row) +{ + const struct osl_object obj = {.data = hash, .size = HASH_SIZE}; + return osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row); +} + +/** + * Get the osl object holding the audio file selector info of a row. + * + * \param row Pointer to a row in the audio file table. + * \param obj Result pointer. + * + * \return The return value of the underlying call to osl_get_object(). + */ +int get_afsi_object_of_row(const void *row, struct osl_object *obj) +{ + return osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj); +} + +/** + * Get the osl object holding the audio file selector info, given a path. + * + * + * \param path The full path of the audio file. + * \param obj Result pointer. + * + * \return Positive on success, negative on errors. + */ +int get_afsi_object_of_path(char *path, struct osl_object *obj) +{ + struct osl_row *row; + int ret = aft_get_row_of_path(path, &row); + if (ret < 0) + return ret; + return get_afsi_object_of_row(row, obj); +} + +/** + * Get the audio file selector info, given a row of the audio file table. + * + * \param row Pointer to a row in the audio file table. + * \param afsi Result pointer. + * + * \return Positive on success, negative on errors. + */ +int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi) +{ + struct osl_object obj; + int ret = get_afsi_object_of_row(row, &obj); + if (ret < 0) + return ret; + return load_afsi(afsi, &obj); +} + +/** + * Get the path of an audio file, given a row of the audio file table. + * + * \param row Pointer to a row in the audio file table. + * \param path Result pointer. + * + * \return Positive on success, negative on errors. + */ +int get_audio_file_path_of_row(const struct osl_row *row, char **path) +{ + struct osl_object path_obj; + int ret = osl_get_object(audio_file_table, row, AFTCOL_PATH, + &path_obj); + if (ret < 0) + return ret; + *path = path_obj.data; + return 1; +} + +/** + * Get the object containing the hash value of an audio file, given a row. + * + * \param row Pointer to a row of the audio file table. + * \param obj Result pointer. + * + * \return The return value of the underlying call to osl_get_object(). + * + * \sa get_hash_of_row(). + */ +int get_hash_object_of_aft_row(const void *row, struct osl_object *obj) +{ + return osl_get_object(audio_file_table, row, AFTCOL_HASH, obj); +} + +/** + * Get the hash value of an audio file, given a row of the audio file table. + * + * \param row Pointer to a row of the audio file table. + * \param hash Result pointer. + * + * \a hash points to mapped data and must not be freed by the caller. + * + * \return The return value of the underlying call to + * get_hash_object_of_aft_row(). + */ +static int get_hash_of_row(const void *row, HASH_TYPE **hash) +{ + struct osl_object obj; + int ret = get_hash_object_of_aft_row(row, &obj); + + if (ret < 0) + return ret; + *hash = obj.data; + return 1; +} + +/** + * Get the audio format handler info, given a row of the audio file table. + * + * \param row Pointer to a row of the audio file table. + * \param afhi Result pointer. + * + * \return The return value of the underlying call to osl_get_object(). + * + * \sa get_chunk_table_of_row(). + */ +int get_afhi_of_row(const void *row, struct audio_format_info *afhi) +{ + struct osl_object obj; + int ret = osl_get_object(audio_file_table, row, AFTCOL_AFHI, + &obj); + if (ret < 0) + return ret; + load_afhi(obj.data, afhi); + return 1; +} + +/** + * Get the chunk table of an audio file, given a row of the audio file table. + * + * \param row Pointer to a row of the audio file table. + * \param afhi Result pointer. + * + * \return The return value of the underlying call to osl_open_disk_object(). + * + * \sa get_afhi_of_row(). + */ +int get_chunk_table_of_row(const void *row, struct audio_format_info *afhi) +{ + struct osl_object obj; + int ret = osl_open_disk_object(audio_file_table, row, AFTCOL_CHUNKS, + &obj); + if (ret < 0) + return ret; + ret = load_chunk_info(&obj, afhi); + osl_close_disk_object(&obj); + return ret; +} + +/** + * Mmap the given audio file and update statistics. + * + * \param aft_row Determines the audio file to be opened and updated. + * \param afd Result pointer. + * + * On success, the numplayed field of the audio file selector info is increased + * and the lastplayed time is set to the current time. Finally, the score of + * the audio file is updated. + * + * \return Positive on success, negative on errors. + */ +int open_and_update_audio_file(struct osl_row *aft_row, struct audio_file_data *afd) +{ + HASH_TYPE *aft_hash, file_hash[HASH_SIZE]; + struct osl_object afsi_obj; + struct afs_info new_afsi; + int ret = get_hash_of_row(aft_row, &aft_hash); + + if (ret < 0) + return ret; + ret = get_audio_file_path_of_row(aft_row, &afd->path); + if (ret < 0) + return ret; + ret = get_afsi_object_of_row(aft_row, &afsi_obj); + if (ret < 0) + return ret; + ret = load_afsi(&afd->afsi, &afsi_obj); + if (ret < 0) + return ret; + ret = get_afhi_of_row(aft_row, &afd->afhi); + if (ret < 0) + return ret; + ret = get_chunk_table_of_row(aft_row, &afd->afhi); + if (ret < 0) + return ret; + ret = mmap_full_file(afd->path, O_RDONLY, &afd->map); + if (ret < 0) + goto err; + hash_function(afd->map.data, afd->map.size, file_hash); + ret = -E_HASH_MISMATCH; + if (hash_compare(file_hash, aft_hash)) + goto err; + new_afsi = afd->afsi; + new_afsi.num_played++; + new_afsi.last_played = time(NULL); + save_afsi(&new_afsi, &afsi_obj); /* in-place update */ + if (afd->current_play_mode == PLAY_MODE_PLAYLIST) + ret = playlist_update_audio_file(aft_row); + else + ret = mood_update_audio_file(aft_row, &afd->afsi); + return ret; +err: + free(afd->afhi.chunk_table); + return ret; +} + +time_t now; + +static int get_local_time(uint64_t *seconds, char *buf, size_t size) +{ + struct tm t; + + if (!localtime_r((time_t *)seconds, &t)) + return -E_LOCALTIME; + if (*seconds + 6 * 30 * 24 * 3600 > now) { + if (!strftime(buf, size, "%b %e %k:%M", &t)) + return -E_STRFTIME; + return 1; + } + if (!strftime(buf, size, "%b %e %Y", &t)) + return -E_STRFTIME; + return 1; +} + +#define GET_NUM_DIGITS(x, num) { \ + typeof((x)) _tmp = PARA_ABS(x); \ + *num = 1; \ + if ((x)) \ + while ((_tmp) > 9) { \ + (_tmp) /= 10; \ + (*num)++; \ + } \ + } + +static short unsigned get_duration(int seconds_total, char *buf, short unsigned max_width) +{ + short unsigned width; + int s = seconds_total; + unsigned hours = s / 3600, mins = (s % 3600) / 60, secs = s % 60; + + if (s < 3600) { /* less than one hour => m:ss or mm:ss */ + GET_NUM_DIGITS(mins, &width); /* 1 or 2 */ + width += 3; /* 4 or 5 */ + if (buf) + sprintf(buf, "%*u:%02u", max_width - width + 1, mins, secs); + return width; + } + /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */ + GET_NUM_DIGITS(hours, &width); + width += 6; + if (buf) + sprintf(buf, "%*u:%02u:%02u", max_width - width + 1, hours, mins, secs); + return width; +} + +static char *make_attribute_line(const char *att_bitmap, struct afs_info *afsi) +{ + char *att_text, *att_line; + + get_attribute_text(&afsi->attributes, " ", &att_text); + if (!att_text) + return para_strdup(att_bitmap); + att_line = make_message("%s (%s)", att_bitmap, att_text); + free(att_text); + return att_line; +} + +static char *make_lyrics_line(struct afs_info *afsi) +{ + char *lyrics_name; + lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name); + if (!lyrics_name) + return make_message("%u", afsi->lyrics_id); + return make_message("%u (%s)", afsi->lyrics_id, lyrics_name); +} + +static char *make_image_line(struct afs_info *afsi) +{ + char *image_name; + img_get_name_by_id(afsi->image_id, &image_name); + if (!image_name) + return make_message("%u", afsi->image_id); + return make_message("%u (%s)", afsi->image_id, image_name); +} + +static int print_list_item(struct ls_data *d, struct ls_options *opts, + struct para_buffer *b) +{ + int ret; + char att_buf[65]; + char last_played_time[30]; + char duration_buf[30]; /* nobody has an audio file long enough to overflow this */ + char score_buf[30] = ""; + struct afs_info *afsi = &d->afsi; + struct audio_format_info *afhi = &d->afhi; + struct ls_widths *w = &opts->widths; + int have_score = opts->flags & LS_FLAG_ADMISSIBLE_ONLY; + + if (opts->mode == LS_MODE_SHORT) { + para_printf(b, "%s\n", d->path); + return 1; + } + get_attribute_bitmap(&afsi->attributes, att_buf); + ret = get_local_time(&afsi->last_played, last_played_time, + sizeof(last_played_time)); + if (ret < 0) + return ret; + get_duration(afhi->seconds_total, duration_buf, w->duration_width); + if (have_score) { + if (opts->mode == LS_MODE_LONG) + sprintf(score_buf, "%*li ", w->score_width, d->score); + else + sprintf(score_buf, "%li ", d->score); + } + + if (opts->mode == LS_MODE_LONG) { + para_printf(b, + "%s" /* score */ + "%s " /* attributes */ + "%*d " /* image_id */ + "%*d " /* lyrics_id */ + "%*d " /* bitrate */ + "%s " /* audio format */ + "%*d " /* frequency */ + "%d " /* channels */ + "%s " /* duration */ + "%*d " /* num_played */ + "%s " /* last_played */ + "%s\n", /* path */ + score_buf, + att_buf, + w->image_id_width, afsi->image_id, + w->lyrics_id_width, afsi->lyrics_id, + w->bitrate_width, afhi->bitrate, + "mp3", /* FIXME */ + w->frequency_width, afhi->frequency, + afhi->channels, + duration_buf, + w->num_played_width, afsi->num_played, + last_played_time, + d->path + ); + return 1; + } + if (opts->mode == LS_MODE_VERBOSE) { + HASH_TYPE asc_hash[2 * HASH_SIZE + 1]; + char *att_line, *lyrics_line, *image_line; + + hash_to_asc(d->hash, asc_hash); + att_line = make_attribute_line(att_buf, afsi); + lyrics_line = make_lyrics_line(afsi); + image_line = make_image_line(afsi); + para_printf(b, + "%s: %s\n" /* path */ + "%s%s%s" /* score */ + "attributes: %s\n" + "hash: %s\n" + "image_id: %s\n" + "lyrics_id: %s\n" + "bitrate: %dkbit/s\n" + "format: %s\n" + "frequency: %dHz\n" + "channels: %d\n" + "duration: %s\n" + "num_played: %d\n" + "last_played: %s\n\n", + (opts->flags & LS_FLAG_FULL_PATH)? + "path" : "file", d->path, + have_score? "score: " : "", score_buf, + have_score? "\n" : "", + att_line, + asc_hash, + image_line, + lyrics_line, + afhi->bitrate, + "mp3", /* FIXME */ + afhi->frequency, + afhi->channels, + duration_buf, + afsi->num_played, + last_played_time + ); + free(att_line); + free(lyrics_line); + free(image_line); + return 1; + } + return 1; +} + +static int ls_audio_format_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afsi.audio_format_id, d2->afsi.audio_format_id); +} + +static int ls_duration_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afhi.seconds_total, d2->afhi.seconds_total); +} + +static int ls_bitrate_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afhi.bitrate, d2->afhi.bitrate); +} + +static int ls_lyrics_id_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afsi.lyrics_id, d2->afsi.lyrics_id); +} + +static int ls_image_id_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afsi.image_id, d2->afsi.image_id); +} + +static int ls_channels_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afhi.channels, d2->afhi.channels); +} + +static int ls_frequency_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afhi.frequency, d2->afhi.frequency); +} + +static int ls_num_played_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afsi.num_played, d2->afsi.num_played); +} + +static int ls_last_played_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->afsi.last_played, d2->afsi.last_played); +} + +static int ls_score_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return NUM_COMPARE(d1->score, d2->score); +} + +static int ls_path_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return strcmp(d1->path, d2->path); +} + +static int sort_matching_paths(struct ls_options *options) +{ + size_t nmemb = options->num_matching_paths; + size_t size = sizeof(uint32_t); + int (*compar)(const void *, const void *); + int i; + + options->data_ptr = para_malloc(nmemb * sizeof(*options->data_ptr)); + for (i = 0; i < nmemb; i++) + options->data_ptr[i] = options->data + i; + + /* In these cases the array is already sorted */ + if (options->sorting == LS_SORT_BY_PATH + && !(options->flags & LS_FLAG_ADMISSIBLE_ONLY) + && (options->flags & LS_FLAG_FULL_PATH)) + return 1; + if (options->sorting == LS_SORT_BY_SCORE && + options->flags & LS_FLAG_ADMISSIBLE_ONLY) + return 1; + + switch (options->sorting) { + case LS_SORT_BY_PATH: + compar = ls_path_compare; break; + case LS_SORT_BY_SCORE: + compar = ls_score_compare; break; + case LS_SORT_BY_LAST_PLAYED: + compar = ls_last_played_compare; break; + case LS_SORT_BY_NUM_PLAYED: + compar = ls_num_played_compare; break; + case LS_SORT_BY_FREQUENCY: + compar = ls_frequency_compare; break; + case LS_SORT_BY_CHANNELS: + compar = ls_channels_compare; break; + case LS_SORT_BY_IMAGE_ID: + compar = ls_image_id_compare; break; + case LS_SORT_BY_LYRICS_ID: + compar = ls_lyrics_id_compare; break; + case LS_SORT_BY_BITRATE: + compar = ls_bitrate_compare; break; + case LS_SORT_BY_DURATION: + compar = ls_duration_compare; break; + case LS_SORT_BY_AUDIO_FORMAT: + compar = ls_audio_format_compare; break; + default: + return -E_BAD_SORT; + } + qsort(options->data_ptr, nmemb, size, compar); + return 1; +} + +/* row is either an aft_row or a row of the score table */ +/* TODO: Only compute widths if we need them */ +static int prepare_ls_row(struct osl_row *row, void *ls_opts) +{ + int ret, i; + struct ls_options *options = ls_opts; + struct ls_data *d; + struct ls_widths *w; + unsigned short num_digits; + unsigned tmp; + struct osl_row *aft_row; + long score; + char *path; + + if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { + ret = get_score_and_aft_row(row, &score, &aft_row); + if (ret < 0) + return ret; + } else + aft_row = row; + ret = get_audio_file_path_of_row(aft_row, &path); + if (ret < 0) + return ret; + if (!(options->flags & LS_FLAG_FULL_PATH)) { + char *p = strrchr(path, '/'); + if (p) + path = p + 1; + } + if (options->num_patterns) { + for (i = 0; i < options->num_patterns; i++) { + ret = fnmatch(options->patterns[i], path, FNM_PATHNAME); + if (!ret) + break; + if (ret == FNM_NOMATCH) + continue; + return -E_FNMATCH; + } + if (i >= options->num_patterns) /* no match */ + return 1; + } + tmp = options->num_matching_paths++; + if (options->num_matching_paths > options->array_size) { + options->array_size++; + options->array_size *= 2; + options->data = para_realloc(options->data, options->array_size + * sizeof(*options->data)); + } + d = options->data + tmp; + ret = get_afsi_of_row(aft_row, &d->afsi); + if (ret < 0) + return ret; + ret = get_afhi_of_row(aft_row, &d->afhi); + if (ret < 0) + return ret; + d->path = path; + ret = get_hash_of_row(aft_row, &d->hash); + if (ret < 0) + return ret; + w = &options->widths; + GET_NUM_DIGITS(d->afsi.image_id, &num_digits); + w->image_id_width = PARA_MAX(w->image_id_width, num_digits); + GET_NUM_DIGITS(d->afsi.lyrics_id, &num_digits); + w->lyrics_id_width = PARA_MAX(w->lyrics_id_width, num_digits); + GET_NUM_DIGITS(d->afhi.bitrate, &num_digits); + w->bitrate_width = PARA_MAX(w->bitrate_width, num_digits); + GET_NUM_DIGITS(d->afhi.frequency, &num_digits); + w->frequency_width = PARA_MAX(w->frequency_width, num_digits); + GET_NUM_DIGITS(d->afsi.num_played, &num_digits); + w->num_played_width = PARA_MAX(w->num_played_width, num_digits); + /* just get the number of chars to print this amount of time */ + tmp = get_duration(d->afhi.seconds_total, NULL, 0); + w->duration_width = PARA_MAX(w->duration_width, tmp); + if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { + GET_NUM_DIGITS(score, &num_digits); + num_digits++; /* add one for the sign (space or "-") */ + w->score_width = PARA_MAX(w->score_width, num_digits); + d->score = score; + } + return 1; +} + +static int com_ls_callback(const struct osl_object *query, + struct osl_object *ls_output) +{ + struct ls_options *opts = query->data; + char *p, *pattern_start = (char *)query->data + sizeof(*opts); + struct para_buffer b = {.buf = NULL, .size = 0}; + int i = 0, ret; + + PARA_NOTICE_LOG("%d patterns\n", opts->num_patterns); + if (opts->num_patterns) { + opts->patterns = para_malloc(opts->num_patterns * sizeof(char *)); + for (i = 0, p = pattern_start; i < opts->num_patterns; i++) { + opts->patterns[i] = p; + p += strlen(p) + 1; + PARA_NOTICE_LOG("pattern %d: %s\n", i, opts->patterns[i]); + } + } else + opts->patterns = NULL; + if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) + ret = admissible_file_loop(opts, prepare_ls_row); + else + ret = osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, + prepare_ls_row); + if (ret < 0) + goto out; + ret = opts->num_patterns? -E_NO_MATCH : 1; + if (!opts->num_matching_paths) { + PARA_NOTICE_LOG("no match, ret: %d\n", ret); + goto out; + } + ret = sort_matching_paths(opts); + if (ret < 0) + goto out; + if (opts->flags & LS_FLAG_REVERSE) + for (i = opts->num_matching_paths - 1; i >= 0; i--) { + ret = print_list_item(opts->data_ptr[i], opts, &b); + if (ret < 0) + break; + } + else + for (i = 0; i < opts->num_matching_paths; i++) { + ret = print_list_item(opts->data_ptr[i], opts, &b); + if (ret < 0) + break; + } + ret = 1; +out: + ls_output->data = b.buf; + ls_output->size = b.size; + free(opts->data); + free(opts->data_ptr); + free(opts->patterns); + return ret; +} + +/* + * TODO: flags -h (sort by hash) + * + * long list: list hash, attributes as (xx--x-x-), file size, lastplayed + * full list: list everything, including afsi, afhi, atts as clear text + * + * */ +int com_afs_ls(__a_unused int fd, int argc, const char **argv) +{ + int i, ret; + unsigned flags = 0; + enum ls_sorting_method sort = LS_SORT_BY_PATH; + enum ls_listing_mode mode = LS_MODE_SHORT; + struct ls_options opts = {.patterns = NULL}; + struct osl_object query = {.data = &opts, .size = sizeof(opts)}, + ls_output; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "-l", 2)) { + if (!*(arg + 2)) { + mode = LS_MODE_LONG; + continue; + } + if (*(arg + 3)) + return -E_AFT_SYNTAX; + switch(*(arg + 2)) { + case 's': + mode = LS_MODE_SHORT; + continue; + case 'l': + mode = LS_MODE_LONG; + continue; + case 'v': + mode = LS_MODE_VERBOSE; + continue; + case 'm': + mode = LS_MODE_MBOX; + continue; + default: + return -E_AFT_SYNTAX; + } + } + if (!strcmp(arg, "-p")) { + flags |= LS_FLAG_FULL_PATH; + continue; + } + if (!strcmp(arg, "-a")) { + flags |= LS_FLAG_ADMISSIBLE_ONLY; + continue; + } + if (!strcmp(arg, "-r")) { + flags |= LS_FLAG_REVERSE; + continue; + } + if (!strncmp(arg, "-s", 2)) { + if (!*(arg + 2) || *(arg + 3)) + return -E_AFT_SYNTAX; + switch(*(arg + 2)) { + case 'p': + sort = LS_SORT_BY_PATH; + continue; + case 's': /* -ss implies -a */ + sort = LS_SORT_BY_SCORE; + flags |= LS_FLAG_ADMISSIBLE_ONLY; + continue; + case 'l': + sort = LS_SORT_BY_LAST_PLAYED; + continue; + case 'n': + sort = LS_SORT_BY_NUM_PLAYED; + continue; + case 'f': + sort = LS_SORT_BY_FREQUENCY; + continue; + case 'c': + sort = LS_SORT_BY_CHANNELS; + continue; + case 'i': + sort = LS_SORT_BY_IMAGE_ID; + continue; + case 'y': + sort = LS_SORT_BY_LYRICS_ID; + continue; + case 'b': + sort = LS_SORT_BY_BITRATE; + continue; + case 'd': + sort = LS_SORT_BY_DURATION; + continue; + case 'a': + sort = LS_SORT_BY_AUDIO_FORMAT; + continue; + default: + return -E_AFT_SYNTAX; + } + } + return -E_AFT_SYNTAX; + } + time(&now); + opts.flags = flags; + opts.sorting = sort; + opts.mode = mode; + opts.num_patterns = argc - i; + ret = send_option_arg_callback_request(&query, opts.num_patterns, + argv + i, com_ls_callback, &ls_output); + if (ret >= 0 && ls_output.data) { + printf("%s\n", (char *)ls_output.data); + free(ls_output.data); + } + return ret; +} + +/** + * Call the given function for each file in the audio file table. + * + * \param private_data An arbitrary data pointer, passed to \a func. + * \param func The custom function to be called. + * + * \return The return value of the underlying call to osl_rbtree_loop(). + */ +int audio_file_loop(void *private_data, osl_rbtree_loop_func *func) +{ + return osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data, + func); +} + +static void *find_hash_sister(HASH_TYPE *hash) +{ + const struct osl_object obj = {.data = hash, .size = HASH_SIZE}; + struct osl_row *row; + + osl_get_row(audio_file_table, AFTCOL_HASH, &obj, &row); + return row; +} + +#define AFTROW_HEADER_SIZE 4 + +enum aft_row_offsets { + AFTROW_AFHI_OFFSET_POS = 0, + AFTROW_CHUNKS_OFFSET_POS = 2, + AFTROW_HASH_OFFSET = AFTROW_HEADER_SIZE, + AFTROW_FLAGS_OFFSET = (AFTROW_HASH_OFFSET + HASH_SIZE), + AFTROW_PATH_OFFSET = (AFTROW_FLAGS_OFFSET + 4) +}; + +/* never save the afsi, as the server knows it too. Note that afhi might be NULL. + * In this case, afhi won't be stored in the buffer */ +static void save_audio_file_info(HASH_TYPE *hash, const char *path, + struct audio_format_info *afhi, + uint32_t flags, struct osl_object *obj) +{ + size_t path_len = strlen(path) + 1; + size_t afhi_size = sizeof_afhi_buf(afhi); + size_t size = AFTROW_PATH_OFFSET + path_len + afhi_size + + sizeof_chunk_info_buf(afhi); + char *buf = para_malloc(size); + uint16_t pos; + + memcpy(buf + AFTROW_HASH_OFFSET, hash, HASH_SIZE); + write_u32(buf + AFTROW_FLAGS_OFFSET, flags); + strcpy(buf + AFTROW_PATH_OFFSET, path); + + pos = AFTROW_PATH_OFFSET + path_len; + PARA_DEBUG_LOG("size: %zu, afhi starts at %d\n", size, pos); + PARA_DEBUG_LOG("last afhi byte: %p, pos %d\n", buf + pos + afhi_size - 1, + pos + afhi_size - 1); + write_u16(buf + AFTROW_AFHI_OFFSET_POS, pos); + save_afhi(afhi, buf + pos); + + pos += afhi_size; + PARA_DEBUG_LOG("size: %zu, chunks start at %d\n", size, pos); + write_u16(buf + AFTROW_CHUNKS_OFFSET_POS, pos); + save_chunk_info(afhi, buf + pos); + PARA_DEBUG_LOG("last byte in buf: %p\n", buf + size - 1); + obj->data = buf; + obj->size = size; +} + +/* +input: +~~~~~~ +HS: hash sister +PB: path brother +F: force flag given + +output: +~~~~~~~ +AFHI: whether afhi and chunk table are computed and sent +ACTION: table modifications to be performed + ++---+----+-----+------+---------------------------------------------------+ +| HS | PB | F | AFHI | ACTION ++---+----+-----+------+---------------------------------------------------+ +| Y | Y | Y | Y | if HS != PB: remove PB. HS: force afhi update, +| | update path, keep afsi ++---+----+-----+------+---------------------------------------------------+ +| Y | Y | N | N | if HS == PB: do not send callback request at all. +| | otherwise: remove PB, HS: update path, keep afhi, +| | afsi. ++---+----+-----+------+---------------------------------------------------+ +| Y | N | Y | Y | (rename) force afhi update of HS, update path of +| | HS, keep afsi ++---+----+-----+------+---------------------------------------------------+ +| Y | N | N | N | (file rename) update path of HS, keep afsi, afhi ++---+----+-----+------+---------------------------------------------------+ +| N | Y | Y | Y | (file change) update afhi, hash, of PB, keep afsi +| | (force has no effect) ++---+----+-----+------+---------------------------------------------------+ +| N | Y | N | Y | (file change) update afhi, hash of PB, keep afsi ++---+----+-----+------+---------------------------------------------------+ +| N | N | Y | Y | (new file) create new entry (force has no effect) ++---+----+-----+------+---------------------------------------------------+ +| N | N | N | Y | (new file) create new entry ++---+----+-----+------+---------------------------------------------------+ + +afhi <=> force or no HS + +*/ + + +#define ADD_FLAG_LAZY 1 +#define ADD_FLAG_FORCE 2 +#define ADD_FLAG_VERBOSE 4 + +static int com_add_callback(const struct osl_object *query, + __a_unused struct osl_object *result) +{ + char *buf = query->data, *path; + struct osl_row *pb, *aft_row; + const struct osl_row *hs; + struct osl_object objs[NUM_AFT_COLUMNS]; + HASH_TYPE *hash; + char asc[2 * HASH_SIZE + 1]; + int ret; + char afsi_buf[AFSI_SIZE]; + uint32_t flags = read_u32(buf + AFTROW_FLAGS_OFFSET); + + hash = buf + AFTROW_HASH_OFFSET; + hash_to_asc(hash, asc);; + objs[AFTCOL_HASH].data = buf + AFTROW_HASH_OFFSET; + objs[AFTCOL_HASH].size = HASH_SIZE; + + path = buf + AFTROW_PATH_OFFSET; + objs[AFTCOL_PATH].data = path; + objs[AFTCOL_PATH].size = strlen(path) + 1; + + PARA_DEBUG_LOG("request to add %s with hash %s\n", path, asc); + hs = find_hash_sister(hash); + ret = aft_get_row_of_path(path, &pb); + if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) + return ret; + if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) { + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("ignoring duplicate %p\n", path); + return 1; + } + if (hs && hs != pb) { + struct osl_object obj; + if (pb) { /* hs trumps pb, remove pb */ + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("removing path brother\n"); + ret = osl_del_row(audio_file_table, pb); + if (ret < 0) + return ret; + pb = NULL; + } + /* file rename, update hs' path */ + ret = osl_get_object(audio_file_table, hs, AFTCOL_PATH, &obj); + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("rename %s -> %s\n", (char *)obj.data, path); + ret = osl_update_object(audio_file_table, hs, AFTCOL_PATH, + &objs[AFTCOL_PATH]); + if (ret < 0) + return ret; + if (!(flags & ADD_FLAG_FORCE)) + return ret; + } + /* no hs or force mode, child must have sent afhi */ + uint16_t afhi_offset = read_u16(buf + AFTROW_AFHI_OFFSET_POS); + uint16_t chunks_offset = read_u16(buf + AFTROW_CHUNKS_OFFSET_POS); + + objs[AFTCOL_AFHI].data = buf + afhi_offset; + objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset; + if (!objs[AFTCOL_AFHI].size) /* "impossible" */ + return -E_NO_AFHI; + objs[AFTCOL_CHUNKS].data = buf + chunks_offset; + objs[AFTCOL_CHUNKS].size = query->size - chunks_offset; + if (pb && !hs) { /* update pb's hash */ + char old_asc[2 * HASH_SIZE + 1]; + HASH_TYPE *old_hash; + ret = get_hash_of_row(pb, &old_hash); + if (ret < 0) + return ret; + hash_to_asc(old_hash, old_asc); + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("file change: %s %s -> %s\n", path, + old_asc, asc); + ret = osl_update_object(audio_file_table, pb, AFTCOL_HASH, + &objs[AFTCOL_HASH]); + if (ret < 0) + return ret; + } + if (hs || pb) { /* (hs != NULL and pb != NULL) implies hs == pb */ + const void *row = pb? pb : hs; + /* update afhi and chunk_table */ + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("updating audio format handler info (%zd bytes)\n", + objs[AFTCOL_AFHI].size); + ret = osl_update_object(audio_file_table, row, AFTCOL_AFHI, + &objs[AFTCOL_AFHI]); + if (ret < 0) + return ret; + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("updating chunk table\n"); + ret = osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, + &objs[AFTCOL_CHUNKS]); + if (ret < 0) + return ret; + ret = mood_update_audio_file(row, NULL); + if (ret < 0) + return ret; + } + /* new entry, use default afsi */ + if (flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("adding %s\n", path); + objs[AFTCOL_AFSI].data = &afsi_buf; + objs[AFTCOL_AFSI].size = AFSI_SIZE; + save_afsi(NULL, &objs[AFTCOL_AFSI]); + ret = osl_add_and_get_row(audio_file_table, objs, &aft_row); + if (ret < 0) + return ret; + return mood_update_audio_file(aft_row, NULL); +} + +struct private_add_data { + int fd; + uint32_t flags; +}; + +static int add_one_audio_file(const char *arg, const void *private_data) +{ + int ret; + const struct private_add_data *pad = private_data; + struct audio_format_info afhi, *afhi_ptr = NULL; + struct osl_row *pb, *hs; /* path brother/hash sister */ + struct osl_object map, obj = {.data = NULL}; + char *path; + HASH_TYPE hash[HASH_SIZE]; + + afhi.header_offset = 0; + afhi.header_len = 0; + ret = verify_path(arg, &path); + if (ret < 0) + return ret; + ret = aft_get_row_of_path(path, &pb); + if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) + goto out_free; + ret = 1; + if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */ + if (pad->flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("lazy-ignore: %s\n", path); + goto out_free; + } + /* we still want to add this file. Compute its hash and look it up */ + ret = mmap_full_file(path, O_RDONLY, &map); + if (ret < 0) + goto out_free; + hash_function(map.data, map.size, hash); + hs = find_hash_sister(hash); + /* + * return success if we're pretty sure that we already know this file + */ + ret = 1; + if (pb && hs && hs == pb && (!(pad->flags & ADD_FLAG_FORCE))) { + if (pad->flags & ADD_FLAG_VERBOSE) + PARA_NOTICE_LOG("not forcing update: %s\n", path); + goto out_unmap; + } + /* + * we won't recalculate the audio format info and the chunk table if + * there is a hash sister unless in FORCE mode. + */ + if (!hs || (pad->flags & ADD_FLAG_FORCE)) { + ret = mp3_get_file_info(map.data, map.size, &afhi); + if (ret < 0) { + PARA_WARNING_LOG("audio format of %s not recognized, skipping\n", path); + ret = 1; + goto out_unmap; + } + afhi_ptr = &afhi; + } + munmap(map.data, map.size); + save_audio_file_info(hash, path, afhi_ptr, pad->flags, &obj); + /* ask parent to consider this entry for adding */ + ret = send_callback_request(com_add_callback, &obj, NULL); + goto out_free; + +out_unmap: + munmap(map.data, map.size); +out_free: + free(obj.data); + free(path); + if (afhi_ptr) + free(afhi_ptr->chunk_table); + return ret; +} + +int com_add(int fd, int argc, const char **argv) +{ + int i, ret; + struct private_add_data pad = {.fd = fd, .flags = 0}; + struct stat statbuf; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-l")) { + pad.flags |= ADD_FLAG_LAZY; + continue; + } + if (!strcmp(arg, "-f")) { + pad.flags |= ADD_FLAG_FORCE; + continue; + } + if (!strcmp(arg, "-v")) { + pad.flags |= ADD_FLAG_VERBOSE; + continue; + } + } + if (argc <= i) + return -E_AFT_SYNTAX; + for (; i < argc; i++) { + ret = stat(argv[i], &statbuf); + if (ret < 0) + return -E_AFS_STAT; + if (S_ISDIR(statbuf.st_mode)) { + ret = for_each_file_in_dir(argv[i], + add_one_audio_file, &pad); + if (ret < 0) + return ret; + continue; + } + ret = add_one_audio_file(argv[i], &pad); + if (ret < 0) + goto out; + } + ret = 1; +out: + return ret; + +} + +struct com_touch_options { + long num_played; + long last_played; + long lyrics_id; + long image_id; +}; + +static int com_touch_callback(const struct osl_object *query, + __a_unused struct osl_object *result) +{ + struct com_touch_options *cto = query->data; + char *p = (char *)query->data + sizeof(*cto); + size_t len; + int ret, no_options = cto->num_played < 0 && cto->last_played < 0 && + cto->lyrics_id < 0 && cto->image_id < 0; + + for (;p < (char *)query->data + query->size; p += len + 1) { + struct afs_info old_afsi, new_afsi; + struct osl_object obj; + struct osl_row *row; + + len = strlen(p); + ret = aft_get_row_of_path(p, &row); + if (ret < 0) + return ret; + ret = get_afsi_object_of_row(row, &obj); + if (ret < 0) + return ret; + ret = load_afsi(&old_afsi, &obj); + if (ret < 0) + return ret; + new_afsi = old_afsi; + if (no_options) { + new_afsi.num_played++; + new_afsi.last_played = time(NULL); + } else { + if (cto->lyrics_id >= 0) + new_afsi.lyrics_id = cto->lyrics_id; + if (cto->image_id >= 0) + new_afsi.image_id = cto->image_id; + if (cto->num_played >= 0) + new_afsi.num_played = cto->num_played; + if (cto->last_played >= 0) + new_afsi.last_played = cto->last_played; + } + save_afsi(&new_afsi, &obj); /* in-place update */ + ret = mood_update_audio_file(row, &old_afsi); + if (ret < 0) + return ret; + } + return 1; +} + +int com_touch(__a_unused int fd, int argc, const char **argv) +{ + struct com_touch_options cto = { + .num_played = -1, + .last_played = -1, + .lyrics_id = -1, + .image_id = -1 + }; + struct osl_object options = {.data = &cto, .size = sizeof(cto)}; + int i, ret; + + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "-n", 2)) { + ret = para_atol(arg + 2, &cto.num_played); + if (ret < 0) + goto err; + continue; + } + if (!strncmp(arg, "-l", 2)) { + ret = para_atol(arg + 2, &cto.last_played); + if (ret < 0) + goto err; + continue; + } + if (!strncmp(arg, "-y", 2)) { + ret = para_atol(arg + 2, &cto.lyrics_id); + if (ret < 0) + goto err; + continue; + } + if (!strncmp(arg, "-i", 2)) { + ret = para_atol(arg + 2, &cto.image_id); + if (ret < 0) + goto err; + continue; + } + } + ret = -E_AFT_SYNTAX; + if (i >= argc) + goto err; + return send_option_arg_callback_request(&options, argc - i, + argv + i, com_touch_callback, NULL); +err: + return ret; +} + +struct com_rm_options { + uint32_t flags; +}; + +static int com_rm_callback(const struct osl_object *query, + __a_unused struct osl_object *result) +{ + struct com_rm_options *cro = query->data; + char *p = (char *)query->data + sizeof(*cro); + size_t len; + int ret; + + for (;p < (char *)query->data + query->size; p += len + 1) { + struct osl_row *row; + + len = strlen(p); + ret = aft_get_row_of_path(p, &row); + if (ret < 0) + return ret; + ret = mood_delete_audio_file(row); + if (ret < 0) + return ret; + ret = osl_del_row(audio_file_table, row); + if (ret < 0) + return ret; + } + return 1; +} + +/* + * TODO options: -v verbose, -f dont stop if file not found + * -h remove by hash, use fnmatch + * + * */ + +int com_afs_rm(__a_unused int fd, int argc, const char **argv) +{ + struct com_rm_options cro = {.flags = 0}; + struct osl_object options = {.data = &cro, .size = sizeof(cro)}; + int i, ret; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + } + ret = -E_AFT_SYNTAX; + if (i >= argc) + goto err; + return send_option_arg_callback_request(&options, argc - i, + argv + i, com_rm_callback, NULL); +err: + return ret; +} + +/** + * Close the audio file table. + * + * \param flags Ususal flags that are passed to osl_close_table(). + * + * \sa osl_close_table(). + */ +void aft_shutdown(enum osl_close_flags flags) +{ + osl_close_table(audio_file_table, flags); +} + +/** + * Open the audio file table. + * + * \param ti Gets initialized by this function + * + * \return Positive on success, negative on errors. + * + * \sa osl_open_table(). + */ +int aft_init(struct table_info *ti) +{ + int ret; + + ti->desc = &audio_file_table_desc; + ret = osl_open_table(ti->desc, &ti->table); + if (ret >= 0) { + unsigned num; + audio_file_table = ti->table; + osl_get_num_rows(audio_file_table, &num); + PARA_INFO_LOG("audio file table contains %d files\n", num); + return ret; + } + audio_file_table = NULL; + return ret == -E_NOENT? 1 : ret; +} diff --git a/attribute.c b/attribute.c new file mode 100644 index 00000000..db38fadd --- /dev/null +++ b/attribute.c @@ -0,0 +1,395 @@ +#include "para.h" +#include "error.h" +#include "afs.h" +#include "string.h" + +static void *attribute_table; +static int greatest_att_bitnum; + +static int char_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + const unsigned char *c1 = (const unsigned char*)obj1->data; + const unsigned char *c2 = (const unsigned char*)obj2->data; + if (*c1 > *c2) + return 1; + if (*c1 < *c2) + return -1; + return 0; +} + +enum attribute_table_columns {ATTCOL_BITNUM, ATTCOL_NAME, NUM_ATT_COLUMNS}; + +static struct osl_column_description att_cols[] = { + [ATTCOL_BITNUM] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, + .name = "bitnum", + .compare_function = char_compare, + .data_size = 1 + }, + [ATTCOL_NAME] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_RBTREE | OSL_UNIQUE, + .name = "name", + .compare_function = string_compare, + } +}; + +static const struct osl_table_description attribute_table_desc = { + .dir = DATABASE_DIR, + .name = "attributes", + .num_columns = NUM_ATT_COLUMNS, + .flags = 0, + .column_descriptions = att_cols +}; + +static void find_greatest_att_bitnum(void) +{ + unsigned char c = 63; + do { + struct osl_row *row; + struct osl_object obj = {.data = &c, .size = 1}; + if (osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, + &row) >= 0) { + greatest_att_bitnum = c; + return; + } + } while (c--); + PARA_INFO_LOG("%s\n", "no attributes"); + greatest_att_bitnum = -E_NO_ATTRIBUTES; +} + +int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum) +{ + struct osl_object obj = {.data = (char *)att_name, + .size = strlen(att_name) + 1}; + struct osl_row *row; + int ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row); + + if (ret < 0) + return ret; + ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, &obj); + if (ret < 0) + return ret; + *bitnum = *(unsigned char *)obj.data; + return 1; +} + +#define LAA_FLAG_ALPHA 1 +#define LAA_FLAG_LONG 2 + +struct private_laa_data { + int fd; + unsigned flags; +}; + +static int log_attribute(struct osl_row *row, void *private_data) +{ + struct private_laa_data *pld = private_data; + int ret; + struct osl_object name_obj, bitnum_obj; + + ret = osl_get_object(attribute_table, row, ATTCOL_NAME, &name_obj); + if (ret < 0) + return ret; + if (!(pld->flags & LAA_FLAG_LONG)) { + printf("%s\n", (char *)name_obj.data); + return 1; + } + ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, &bitnum_obj); + if (ret < 0) + return ret; + printf("%u\t%s\n", *(unsigned char*)bitnum_obj.data, + (char *)name_obj.data); + return 1; +} + +int com_lsatt(int fd, int argc, const char **argv) +{ + struct private_laa_data pld = {.fd = fd, .flags = 0}; + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-a")) { + pld.flags |= LAA_FLAG_ALPHA; + continue; + } + if (!strcmp(arg, "-l")) { + pld.flags |= LAA_FLAG_LONG; + continue; + } + } + if (argc > i) + return -E_ATTR_SYNTAX; + if (pld.flags & LAA_FLAG_ALPHA) + return osl_rbtree_loop(attribute_table, ATTCOL_NAME, + &pld, log_attribute); + return osl_rbtree_loop(attribute_table, ATTCOL_BITNUM, + &pld, log_attribute); +} + +static int com_setatt_callback(const struct osl_object *query, + __a_unused struct osl_object *result) +{ + char *p; + uint64_t add_mask = 0, del_mask = 0; + int ret; + size_t len; + struct osl_object obj; + struct osl_row *row; + + for (p = query->data; p < (char *)query->data + query->size; p += len + 1) { + char c; + + len = strlen(p); + if (!*p) + return -E_ATTR_SYNTAX; + c = p[len - 1]; + if (c != '+' && c != '-') + break; + p[len - 1] = '\0'; + obj.data = p; + obj.size = len + 1; + ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row); + if (ret < 0) + return ret; + ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, + &obj); + if (ret < 0) + return ret; + if (c == '+') + add_mask |= (1UL << *(unsigned char *)obj.data); + else + del_mask |= (1UL << *(unsigned char *)obj.data); + } + if (!add_mask && !del_mask) + return -E_ATTR_SYNTAX; + PARA_DEBUG_LOG("masks: %llx:%llx\n", add_mask, del_mask); + for (; p < (char *)query->data + query->size; p += len + 1) { /* TODO: fnmatch */ + struct afs_info old_afsi, new_afsi; + struct osl_row *aft_row; + + len = strlen(p); + ret = aft_get_row_of_path(p, &aft_row); + if (ret < 0) + return ret; + ret = get_afsi_object_of_row(p, &obj); + if (ret < 0) + return ret; + ret = load_afsi(&old_afsi, &obj); + if (ret < 0) + return ret; + new_afsi = old_afsi; + new_afsi.attributes |= add_mask; + new_afsi.attributes &= ~del_mask; + save_afsi(&new_afsi, &obj); /* in-place update */ + ret = mood_update_audio_file(aft_row, &old_afsi); + if (ret < 0) + return ret; + } + return 1; +} + +int com_setatt(__a_unused int fd, int argc, const char **argv) +{ + if (argc < 2) + return -E_ATTR_SYNTAX; + return send_standard_callback_request(argc, argv, com_setatt_callback, + NULL); +} + +/* TODO: make it faster by only extracting the attribute member from afsi */ +static int logical_and_attribute(struct osl_row *aft_row, void *attribute_ptr) +{ + struct afs_info afsi; + uint64_t *att = attribute_ptr; + struct osl_object obj; + int ret = get_afsi_object_of_row(aft_row, &obj); + if (ret < 0) + return ret; + ret = load_afsi(&afsi, &obj); + if (ret < 0) + return ret; + afsi.attributes &= *att; + save_afsi(&afsi, &obj); + return 1; +} + +static int com_addatt_callback(const struct osl_object *query, + __a_unused struct osl_object *result) +{ + char *p = query->data; + uint64_t atts_added = 0; + int ret; + + while (p < (char *)query->data + query->size) { + struct osl_object objs[NUM_ATT_COLUMNS]; + struct osl_row *row; + unsigned char bitnum; + + objs[ATTCOL_BITNUM].size = 1; + objs[ATTCOL_NAME].data = p; + objs[ATTCOL_NAME].size = strlen(p) + 1; + ret = osl_get_row(attribute_table, ATTCOL_NAME, + &objs[ATTCOL_NAME], &row); /* expected to fail */ + if (ret >= 0) + return -E_ATTR_EXISTS; + if (ret != -E_RB_KEY_NOT_FOUND) /* error */ + return ret; + /* find smallest non-used attribute */ + for (bitnum = 0; bitnum < 64; bitnum++) { + objs[ATTCOL_BITNUM].data = &bitnum; + ret = osl_get_row(attribute_table, ATTCOL_BITNUM, + &objs[ATTCOL_BITNUM], &row); + if (ret == -E_RB_KEY_NOT_FOUND) + break; /* this bitnum is unused, use it */ + if (ret < 0) /* error */ + return ret; + /* this bit is already in use, try next bit */ + } + if (bitnum == 64) + return -E_ATTR_TABLE_FULL; + ret = osl_add_row(attribute_table, objs); + if (ret < 0) + return ret; + greatest_att_bitnum = PARA_MAX(greatest_att_bitnum, bitnum); + atts_added |= 1 << bitnum; + p += strlen(p) + 1; + } + if (!atts_added) + return 1; + atts_added = ~atts_added; + ret = audio_file_loop(&atts_added, logical_and_attribute); + if (ret < 0) + return ret; + find_greatest_att_bitnum(); + return mood_reload(); +} + +int com_addatt(__a_unused int fd, int argc, const char **argv) +{ + if (argc < 2) + return -E_ATTR_SYNTAX; + return send_standard_callback_request(argc, argv, com_addatt_callback, + NULL); +} + +static int com_rmatt_callback(const struct osl_object *query, + __a_unused struct osl_object *result) +{ + char *p = query->data; + int ret, atts_removed = 0; + while (p < (char *)query->data + query->size) { + struct osl_object obj = { + .data = p, + .size = strlen(p) + 1 + }; + struct osl_row *row; + ret = osl_get_row(attribute_table, ATTCOL_NAME, + &obj, &row); + if (ret < 0) + return ret; + ret = osl_del_row(attribute_table, row); + if (ret < 0) + return ret; + atts_removed++; + p += strlen(p) + 1; + } + find_greatest_att_bitnum(); + if (!atts_removed) + return 1; + return mood_reload(); +} + +int com_rmatt(__a_unused int fd, int argc, const char **argv) +{ + if (argc < 2) + return -E_ATTR_SYNTAX; + return send_standard_callback_request(argc, argv, com_rmatt_callback, + NULL); +} + +void get_attribute_bitmap(uint64_t *atts, char *buf) +{ + int i; + const uint64_t one = 1; + + for (i = 0; i <= greatest_att_bitnum; i++) + buf[greatest_att_bitnum - i] = (*atts & (one << i))? 'x' : '-'; + buf[i] = '\0'; +} +/** + * Get a string containing the set attributes in text form. + * + * \param atts The attribute bitmap. + * \param delim The delimiter to separate matching attribute names. + * \param text Result pointer. + * + * \return Positive on success, negative on errors. If no attributes have + * been defined, \a *text is NULL. + */ +int get_attribute_text(uint64_t *atts, const char *delim, char **text) +{ + int i, ret; + const uint64_t one = 1; + + *text = NULL; + if (greatest_att_bitnum < 0) /* no attributes available */ + return 1; + for (i = 0; i <= greatest_att_bitnum; i++) { + unsigned char bn = i; + struct osl_object obj = {.data = &bn, .size = 1}; + struct osl_row *row; + + if (!(*atts & (one << i))) + continue; + ret = osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, &row); + if (ret < 0) + goto err; + ret = osl_get_object(attribute_table, row, ATTCOL_NAME, &obj); + if (ret < 0) + goto err; + if (*text) { + char *tmp = make_message("%s%s%s", *text, delim, (char *)obj.data); + free(*text); + *text = tmp; + } else + *text = para_strdup(obj.data); + } + if (!*text) /* no attributes set */ + *text = para_strdup(""); + return 1; +err: + free(*text); + return ret; +} + +void attribute_shutdown(enum osl_close_flags flags) +{ + osl_close_table(attribute_table, flags); +} + +int attribute_init(struct table_info *ti) +{ + int ret; + + ti->desc = &attribute_table_desc; + ret = osl_open_table(ti->desc, &ti->table); + greatest_att_bitnum = -1; /* no atts available */ + if (ret >= 0) { + attribute_table = ti->table; + find_greatest_att_bitnum(); + return ret; + } + attribute_table = NULL; + if (ret == -E_NOENT) + return 1; + return ret; +} diff --git a/audiod.c b/audiod.c index 9b79121b..d4abb3b2 100644 --- a/audiod.c +++ b/audiod.c @@ -497,7 +497,7 @@ out: return count; } -static void check_stat_line(char *line) +static int check_stat_line(char *line, __a_unused void *data) { int itemnum; size_t ilen = 0; @@ -506,14 +506,14 @@ static void check_stat_line(char *line) // PARA_INFO_LOG("line: %s\n", line); if (!line) - return; + return 1; itemnum = stat_line_valid(line); if (itemnum < 0) { PARA_WARNING_LOG("invalid status line: %s\n", line); - return; + return 1; } if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME) - return; + return 1; tmp = make_message("%s\n", line); stat_client_write(tmp, itemnum); free(tmp); @@ -557,6 +557,7 @@ static void check_stat_line(char *line) stat_task->clock_diff_count--; break; } + return 1; } static void try_to_close_slot(int slot_num) @@ -1068,7 +1069,7 @@ static void status_post_select(__a_unused struct sched *s, struct task *t) return; } bytes_left = for_each_line(st->pcd->buf, st->pcd->loaded, - &check_stat_line); + &check_stat_line, NULL); if (st->pcd->loaded != bytes_left) { st->last_status_read = *now; st->pcd->loaded = bytes_left; diff --git a/blob.c b/blob.c new file mode 100644 index 00000000..25dd99e0 --- /dev/null +++ b/blob.c @@ -0,0 +1,393 @@ +#include "para.h" +#include "error.h" +#include "afs.h" +#include "string.h" + +/** \file blob.c Macros and functions for blob handling. */ + +static struct osl_column_description blob_cols[] = { + [BLOBCOL_ID] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_RBTREE | OSL_UNIQUE | OSL_FIXED_SIZE, + .name = "id", + .data_size = 4, + .compare_function = uint32_compare + }, + [BLOBCOL_NAME] = { + .storage_type = OSL_MAPPED_STORAGE, + .storage_flags = OSL_RBTREE | OSL_UNIQUE, + .name = "name", + .compare_function = string_compare + }, + [BLOBCOL_DEF] = { + .storage_type = OSL_DISK_STORAGE, + .storage_flags = 0, + .name = "definition" + } +}; + +/** \cond doxygen isn't smart enough to recognize these */ +INIT_BLOB_TABLE(lyrics); +INIT_BLOB_TABLE(images); +INIT_BLOB_TABLE(moods); +INIT_BLOB_TABLE(playlists); +/** \endcond */ + +/** Flags that may be passed to the \p ls functions of each blob type. */ +enum blob_ls_flags { + /** List both id and name. */ + BLOB_LS_FLAG_LONG = 1, + /** Reverse sort order. */ + BLOB_LS_FLAG_REVERSE = 2, + /** Sort by id instead of name. */ + BLOB_LS_FLAG_SORT_BY_ID = 4, +}; + +/** Data passed to \p com_lsbob_callback(). */ +struct com_lsblob_options { + /** Given flags for the ls command. */ + uint32_t flags; +}; + +/** Structure passed to the \p print_blob loop function. */ +struct lsblob_loop_data { + struct com_lsblob_options *opts; + struct para_buffer *pb; + struct osl_table *table; +}; + +static int print_blob(struct osl_row *row, void *loop_data) +{ + struct osl_object obj; + char *name; + uint32_t id; + int ret; + struct lsblob_loop_data *lld = loop_data; + + ret = osl_get_object(lld->table, row, BLOBCOL_NAME, &obj); + if (ret < 0) + return ret; + name = obj.data; + if (!*name) /* ignore dummy row */ + return 1; + ret = osl_get_object(lld->table, row, BLOBCOL_ID, &obj); + if (ret < 0) + return ret; + id = *(uint32_t *)obj.data; + if (lld->opts->flags & BLOB_LS_FLAG_LONG) + para_printf(lld->pb, "%u\t%s\n", id, name); + else + para_printf(lld->pb, "%s\n", name); + return 1; +} + +int com_lsblob_callback(struct osl_table *table, + const struct osl_object *query, struct osl_object *ls_output) +{ + struct para_buffer pb = {.buf = NULL}; + struct lsblob_loop_data lld = {.opts = query->data, .pb = &pb, .table = table}; + int ret; + + if (lld.opts->flags & BLOB_LS_FLAG_REVERSE) { + if (lld.opts->flags & BLOB_LS_FLAG_SORT_BY_ID) + ret = osl_rbtree_loop(lld.table, BLOBCOL_ID, &lld, print_blob); + else + ret = osl_rbtree_loop_reverse(lld.table, BLOBCOL_NAME, &lld, print_blob); + } else { + if (lld.opts->flags & BLOB_LS_FLAG_SORT_BY_ID) + ret = osl_rbtree_loop_reverse(lld.table, BLOBCOL_ID, &lld, print_blob); + else + ret = osl_rbtree_loop(lld.table, BLOBCOL_NAME, &lld, print_blob); + } + ls_output->data = pb.buf; + ls_output->size = pb.size; + return ret; +} + +static int com_lsblob(callback_function *f, __a_unused int fd, int argc, const char **argv) +{ + struct com_lsblob_options clbo = {.flags = 0}; + struct osl_object query = {.data = &clbo, .size = sizeof(clbo)}, + ls_output; + int i, ret; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strcmp(arg, "-l")) { + clbo.flags |= BLOB_LS_FLAG_LONG; + continue; + } + if (!strcmp(arg, "-i")) { + clbo.flags |= BLOB_LS_FLAG_SORT_BY_ID; + continue; + } + if (!strcmp(arg, "-r")) { + clbo.flags |= BLOB_LS_FLAG_REVERSE; + continue; + } + } + if (argc > i) + return -E_BLOB_SYNTAX; + ret = send_option_arg_callback_request(&query, argc - i, + argv + i, f, &ls_output); + if (ret >= 0 && ls_output.data) + printf("%s\n", (char *)ls_output.data); + free(ls_output.data); + return ret; +} + +static int com_catblob_callback(struct osl_table *table, + const struct osl_object *query, struct osl_object *output) +{ + struct osl_object obj; + int ret; + struct osl_row *row; + + ret = osl_get_row(table, BLOBCOL_NAME, query, &row); + if (ret < 0) + return ret; + ret = osl_open_disk_object(table, row, BLOBCOL_DEF, &obj); + if (ret < 0) + return ret; + output->data = para_malloc(obj.size); + output->size = obj.size; + memcpy(output->data, obj.data, obj.size); + return osl_close_disk_object(&obj); +} +static int com_catblob(callback_function *f, __a_unused int fd, int argc, + const char **argv) +{ + struct osl_object cat_output = {.data = NULL}; + int ret; + + if (argc != 2) + return -E_BLOB_SYNTAX; + if (!*argv[1]) /* empty name is reserved of the dummy row */ + return -E_BLOB_SYNTAX; + ret = send_standard_callback_request(1, argv + 1, f, &cat_output); + if (ret >= 0 && cat_output.data) + printf("%s\n", (char *)cat_output.data); + free(cat_output.data); + return ret; + +} + +static int com_addblob_callback(struct osl_table *table, + const struct osl_object *query, + __a_unused struct osl_object *result) +{ + struct osl_object objs[NUM_BLOB_COLUMNS]; + char *name = query->data; + size_t name_len = strlen(name) + 1; + uint32_t id; + unsigned num_rows; + int ret; + + ret = osl_get_num_rows(table, &num_rows); + if (ret < 0) + return ret; + if (!num_rows) { /* this is the first entry ever added */ + /* insert dummy row containing the id */ + id = 2; /* this entry will be entry #1, so 2 is the next */ + objs[BLOBCOL_ID].data = &id; + objs[BLOBCOL_ID].size = sizeof(id); + objs[BLOBCOL_NAME].data = ""; + objs[BLOBCOL_NAME].size = 1; + objs[BLOBCOL_DEF].data = ""; + objs[BLOBCOL_DEF].size = 1; + ret = osl_add_row(table, objs); + if (ret < 0) + return ret; + } else { /* get id of the dummy row and increment it */ + struct osl_row *row; + struct osl_object obj = {.data = "", .size = 1}; + ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); + if (ret < 0) + return ret; + ret = osl_get_object(table, row, BLOBCOL_ID, &obj); + if (ret < 0) + return ret; + id = *(uint32_t *)obj.data + 1; + obj.data = &id; + ret = osl_update_object(table, row, BLOBCOL_ID, &obj); + if (ret < 0) + return ret; + } + id--; + objs[BLOBCOL_ID].data = &id; + objs[BLOBCOL_ID].size = sizeof(id); + objs[BLOBCOL_NAME].data = name; + objs[BLOBCOL_NAME].size = name_len; + objs[BLOBCOL_DEF].data = name + name_len; + objs[BLOBCOL_DEF].size = query->size - name_len; + return osl_add_row(table, objs); +} + +static int com_addblob(callback_function *f, __a_unused int fd, int argc, + const char **argv) +{ + struct osl_object arg_obj; + + if (argc != 2) + return -E_BLOB_SYNTAX; + if (!*argv[1]) /* empty name is reserved for the dummy row */ + return -E_BLOB_SYNTAX; + PARA_NOTICE_LOG("argv[1]: %s\n", argv[1]); + arg_obj.size = strlen(argv[1]) + 1; + arg_obj.data = (char *)argv[1]; + return stdin_command(&arg_obj, f, 10 * 1024 * 1024, NULL); +} + +static int com_rmblob_callback(struct osl_table *table, + const struct osl_object *query, + __a_unused struct osl_object *result) +{ + char *p = query->data; + size_t len; + int ret; + + for (; p < (char *)query->data + query->size; p += len + 1) { + struct osl_row *row; + struct osl_object obj; + + len = strlen(p); + obj.data = p; + obj.size = len + 1; + ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); + if (ret < 0) + return ret; + ret = osl_del_row(table, row); + if (ret < 0) + return ret; + } + return 1; +} + +static int com_rmblob(callback_function *f, __a_unused int fd, int argc, + const char **argv) +{ + if (argc < 2) + return -E_MOOD_SYNTAX; + return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f, + NULL); +} + +static int com_mvblob_callback(struct osl_table *table, + const struct osl_object *query, + __a_unused struct osl_object *result) +{ + char *src = (char *) query->data; + struct osl_object obj = {.data = src, .size = strlen(src) + 1}; + char *dest = src + obj.size; + struct osl_row *row; + int ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); + + if (ret < 0) + return ret; + obj.data = dest; + obj.size = strlen(dest) + 1; + return osl_update_object(table, row, BLOBCOL_NAME, &obj); +} + +static int com_mvblob(callback_function *f, __a_unused int fd, + int argc, const char **argv) +{ + if (argc != 3) + return -E_MOOD_SYNTAX; + return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f, + NULL); +} + +#define DEFINE_BLOB_COMMAND(cmd_name, table_name, cmd_prefix) \ + static int com_ ## cmd_name ## cmd_prefix ## _callback(const struct osl_object *query, \ + struct osl_object *output) \ + { \ + return com_ ## cmd_name ## blob_callback(table_name ## _table, query, output); \ + } \ + int com_ ## cmd_name ## cmd_prefix(__a_unused int fd, int argc, const char **argv) \ + { \ + return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, fd, argc, argv); \ + } + +static int blob_get_name_by_id(struct osl_table *table, uint32_t id, + char **name) +{ + struct osl_row *row; + struct osl_object obj = {.data = &id, .size = sizeof(id)}; + int ret; + + *name = NULL; + if (!id) + return 1; + ret = osl_get_row(table, BLOBCOL_ID, &obj, &row); + if (ret < 0) + return ret; + ret = osl_get_object(table, row, BLOBCOL_NAME, &obj); + if (ret < 0) + return ret; + *name = (char *)obj.data; + return 1; +} +/** Define the \p get_name_by_id function for this blob type. */ +#define DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix) \ + int cmd_prefix ## _get_name_by_id(uint32_t id, char **name) \ + { \ + return blob_get_name_by_id(table_name ## _table, id, name); \ + } + +/** Define the \p shutdown function for this blob type. */ +#define DEFINE_BLOB_SHUTDOWN(table_name) \ + void table_name ## _shutdown(enum osl_close_flags flags) \ + { \ + osl_close_table(table_name ## _table, flags); \ + table_name ## _table = NULL; \ + } + +static int blob_init(struct osl_table **table, + const struct osl_table_description *desc, + struct table_info *ti) +{ + int ret; + + ti->desc = desc; + ret = osl_open_table(ti->desc, &ti->table); + if (ret >= 0) { + *table = ti->table; + return ret; + } + *table = NULL; + return ret == -E_NOENT? 1 : ret; +} + +/** Define the \p init function for this blob type. */ +#define DEFINE_BLOB_INIT(table_name) \ + int table_name ## _init(struct table_info *ti) \ + { \ + return blob_init(&table_name ## _table, \ + &table_name ## _table_desc, ti); \ + } + + +/** Define all functions for this blob type. */ +#define DEFINE_BLOB_FUNCTIONS(table_name, cmd_prefix) \ + DEFINE_BLOB_COMMAND(ls, table_name, cmd_prefix) \ + DEFINE_BLOB_COMMAND(cat, table_name, cmd_prefix) \ + DEFINE_BLOB_COMMAND(add, table_name, cmd_prefix) \ + DEFINE_BLOB_COMMAND(rm, table_name, cmd_prefix) \ + DEFINE_BLOB_COMMAND(mv, table_name, cmd_prefix) \ + DEFINE_GET_NAME_BY_ID(table_name, cmd_prefix); \ + DEFINE_BLOB_SHUTDOWN(table_name); \ + DEFINE_BLOB_INIT(table_name); + +/** \cond doxygen isn't smart enough to recognize these */ +DEFINE_BLOB_FUNCTIONS(lyrics, lyr); +DEFINE_BLOB_FUNCTIONS(images, img); +DEFINE_BLOB_FUNCTIONS(moods, mood); +DEFINE_BLOB_FUNCTIONS(playlists, pl); +/** \endcond */ diff --git a/configure.ac b/configure.ac index a0d3785c..316adf19 100644 --- a/configure.ac +++ b/configure.ac @@ -109,7 +109,8 @@ server_cmdline_objs="server.cmdline server_command_list random_selector_command_ playlist_selector_command_list" server_errlist_objs="server mp3_afh vss command net string signal random_selector time daemon stat crypt http_send afs_common close_on_fork playlist_selector - ipc dccp dccp_send fd user_list chunk_queue" + ipc dccp dccp_send fd user_list chunk_queue afs osl aft mood score attribute + blob playlist sha1 rbtree" server_ldflags="" server_audio_formats=" mp3" diff --git a/error.h b/error.h index 060268db..8c113912 100644 --- a/error.h +++ b/error.h @@ -64,6 +64,16 @@ enum para_subsystem { SS_OSX_WRITE, SS_USER_LIST, SS_CHUNK_QUEUE, + SS_AFS, + SS_OSL, + SS_AFT, + SS_MOOD, + SS_SCORE, + SS_ATTRIBUTE, + SS_BLOB, + SS_PLAYLIST, + SS_SHA1, + SS_RBTREE, NUM_SS }; @@ -78,12 +88,125 @@ enum para_subsystem { #define ORTP_SEND_ERRORS #define GUI_ERRORS #define RINGBUFFER_ERRORS - - +#define SCORE_ERRORS +#define SHA1_ERRORS extern const char **para_errlist[]; /** \endcond */ +#define OSL_ERRORS \ + PARA_ERROR(OSL_OPENDIR, "can not open directory"), \ + PARA_ERROR(OSL_CHDIR, "fixme"), \ + PARA_ERROR(BAD_DB_DIR, "fixme"), \ + PARA_ERROR(NO_COLUMN_DESC, "fixme"), \ + PARA_ERROR(BAD_BASENAME, "fixme"), \ + PARA_ERROR(BAD_STORAGE_TYPE, "fixme"), \ + PARA_ERROR(BAD_STORAGE_FLAGS, "fixme"), \ + PARA_ERROR(NO_COLUMN_NAME, "fixme"), \ + PARA_ERROR(NO_COLUMNS, "fixme"), \ + PARA_ERROR(BAD_COLUMN_NAME, "fixme"), \ + PARA_ERROR(NO_UNIQUE_RBTREE_COLUMN, "fixme"), \ + PARA_ERROR(NO_RBTREE_COL, "fixme"), \ + PARA_ERROR(DUPLICATE_COL_NAME, "fixme"), \ + PARA_ERROR(BAD_STORAGE_SIZE, "fixme"), \ + PARA_ERROR(NO_COMPARE_FUNC, "fixme"), \ + PARA_ERROR(NULL_OBJECT, "fixme"), \ + PARA_ERROR(BAD_DATA_SIZE, "fixme"), \ + PARA_ERROR(NOT_MAPPED, "fixme"), \ + PARA_ERROR(ALREADY_MAPPED, "fixme"), \ + PARA_ERROR(BAD_SIZE, "fixme"), \ + PARA_ERROR(TRUNC, "fixme"), \ + PARA_ERROR(UNLINK, "fixme"), \ + PARA_ERROR(EXIST, "fixme"), \ + PARA_ERROR(ISDIR, "fixme"), \ + PARA_ERROR(NOTDIR, "fixme"), \ + PARA_ERROR(NOENT, "fixme"), \ + PARA_ERROR(OSL_PERM, "fixme"), \ + PARA_ERROR(BAD_TABLE, "fixme"), \ + PARA_ERROR(BAD_TABLE_HEADER, "fixme"), \ + PARA_ERROR(BAD_TABLE_DESC, "fixme"), \ + PARA_ERROR(RB_KEY_EXISTS, "fixme"), \ + PARA_ERROR(RB_KEY_NOT_FOUND, "fixme"), \ + PARA_ERROR(BAD_ID, "fixme"), \ + PARA_ERROR(INDEX_CORRUPTION, "fixme"), \ + PARA_ERROR(BAD_OFFSET, "fixme"), \ + PARA_ERROR(INVALID_OBJECT, "fixme"), \ + PARA_ERROR(MKDIR, "fixme"), \ + PARA_ERROR(OPEN, "fixme"), \ + PARA_ERROR(STAT, "fixme"), \ + PARA_ERROR(FSTAT, "fixme"), \ + PARA_ERROR(RENAME, "fixme"), \ + PARA_ERROR(EMPTY, "fixme"), \ + PARA_ERROR(NOSPC, "fixme"), \ + PARA_ERROR(MMAP, "fixme"), \ + PARA_ERROR(MUNMAP, "fixme"), \ + PARA_ERROR(WRITE, "fixme"), \ + PARA_ERROR(LSEEK, "fixme"), \ + PARA_ERROR(BUSY, "fixme"), \ + PARA_ERROR(SHORT_TABLE, "fixme"), \ + PARA_ERROR(NO_MAGIC, "fixme"), \ + PARA_ERROR(VERSION_MISMATCH, "fixme"), \ + PARA_ERROR(BAD_COLUMN_NUM, "fixme"), \ + PARA_ERROR(BAD_TABLE_FLAGS, "fixme"), \ + PARA_ERROR(RBTREE_EMPTY, "fixme"), \ + PARA_ERROR(BAD_ROW, "fixme"), \ + PARA_ERROR(OSL_GETCWD, "can not get current working directory"), \ + PARA_ERROR(OSL_LSTAT, "fixme"), \ + + +#define RBTREE_ERRORS \ + + +#define AFS_ERRORS \ + PARA_ERROR(AFS_SYNTAX, "fixme"), \ + PARA_ERROR(FORK, "fixme"), \ + PARA_ERROR(BAD_TABLE_NAME, "fixme"), \ + PARA_ERROR(INPUT_TOO_LARGE, "fixme"), \ + + +#define MOOD_ERRORS \ + PARA_ERROR(MOOD_SYNTAX, "fixme"), \ + PARA_ERROR(MOOD_REGEX, "fixme"), \ + PARA_ERROR(NO_MOOD, "fixme"), \ + PARA_ERROR(MOOD_LOADED, "fixme"), \ + PARA_ERROR(MOOD_BUSY, "fixme"), \ + PARA_ERROR(NOT_ADMISSIBLE, "fixme"), \ + PARA_ERROR(READ, "fixme"), \ + PARA_ERROR(ATOL, "fixme"), \ + + +#define ATTRIBUTE_ERRORS \ + PARA_ERROR(ATTR_SYNTAX, "fixme"), \ + PARA_ERROR(ATTR_EXISTS, "fixme"), \ + PARA_ERROR(ATTR_TABLE_FULL, "fixme"), \ + PARA_ERROR(NO_ATTRIBUTES, "fixme"), \ + +#define BLOB_ERRORS \ + PARA_ERROR(BLOB_SYNTAX, "fixme"), \ + PARA_ERROR(DUMMY_ROW, "fixme"), \ + + +#define PLAYLIST_ERRORS \ + PARA_ERROR(PLAYLIST_SYNTAX, "fixme"), \ + PARA_ERROR(NO_PLAYLIST, "fixme"), \ + PARA_ERROR(PLAYLIST_LOADED, "fixme"), \ + PARA_ERROR(PLAYLIST_EMPTY, "fixme"), \ + + +#define AFT_ERRORS \ + PARA_ERROR(BAD_AFS, "fixme"), \ + PARA_ERROR(LOCALTIME, "fixme"), \ + PARA_ERROR(STRFTIME, "fixme"), \ + PARA_ERROR(BAD_PATH, "fixme"), \ + PARA_ERROR(BAD_SORT, "fixme"), \ + PARA_ERROR(FNMATCH, "fixme"), \ + PARA_ERROR(NO_MATCH, "fixme"), \ + PARA_ERROR(NO_AFHI, "fixme"), \ + PARA_ERROR(AFT_SYNTAX, "fixme"), \ + PARA_ERROR(AFS_STAT, "fixme"), \ + PARA_ERROR(HASH_MISMATCH, "fixme"), \ + + #define USER_LIST_ERRORS \ PARA_ERROR(USERLIST, "failed to open user list file"), \ @@ -555,6 +678,17 @@ SS_ENUM(CLIENT_COMMON); SS_ENUM(AUDIOC); SS_ENUM(USER_LIST); SS_ENUM(CHUNK_QUEUE); + +SS_ENUM(AFS); +SS_ENUM(OSL); +SS_ENUM(AFT); +SS_ENUM(MOOD); +SS_ENUM(SCORE); +SS_ENUM(ATTRIBUTE); +SS_ENUM(BLOB); +SS_ENUM(PLAYLIST); +SS_ENUM(SHA1); +SS_ENUM(RBTREE); /** \endcond */ #undef PARA_ERROR /* rest of the world only sees the error text */ diff --git a/gcc-compat.h b/gcc-compat.h index 985ebd23..0ec8fdd7 100644 --- a/gcc-compat.h +++ b/gcc-compat.h @@ -39,3 +39,5 @@ # else # define __must_check /* no warn_unused_result */ # endif + +#define _static_inline_ static inline diff --git a/gui.c b/gui.c index a2d3e6cd..37d0d8e9 100644 --- a/gui.c +++ b/gui.c @@ -11,7 +11,7 @@ #include "gui.h" #include #include "ringbuffer.h" -#include "string.h" +#include "gui_common.h" #include "fd.h" #include "error.h" #include "signal.h" @@ -447,11 +447,12 @@ __printf_2_3 static void outputf(int color, const char* fmt,...) wrefresh(bot.win); } -static void add_output_line(char *line) +static int add_output_line(char *line, __a_unused void *data) { if (!curses_active) - return; + return 1; rb_add_entry(COLOR_OUTPUT, para_strdup(line)); + return 1; } void para_log(int ll, const char *fmt,...) @@ -735,7 +736,7 @@ reap_next_child: /* * print status line if line starts with known command. */ -static void check_stat_line(char *line) +static int check_stat_line(char *line, __a_unused void *data) { int i; @@ -747,7 +748,7 @@ static void check_stat_line(char *line) stat_content[i] = para_strdup(line); print_stat_item(i); } - return; + return 1; } /* @@ -922,7 +923,8 @@ check_return: if (cp_numread <= 0 && !cbo) /* command complete */ return 0; if (cbo) - cbo = for_each_line(command_buf, cbo, &add_output_line); + cbo = for_each_line(command_buf, cbo, + &add_output_line, NULL); if (cp_numread <= 0) cbo = 0; wrefresh(bot.win); diff --git a/gui_common.c b/gui_common.c index 26c3d61b..6ca226e6 100644 --- a/gui_common.c +++ b/gui_common.c @@ -1,4 +1,5 @@ #include "para.h" +#include "string.h" #include "fd.h" extern const char *status_item_list[NUM_STAT_ITEMS]; @@ -17,7 +18,7 @@ int para_open_audiod_pipe(char *cmd) return ret; } -int read_audiod_pipe(int fd, void (*line_handler)(char *) ) +int read_audiod_pipe(int fd, line_handler_t *line_handler) { static char buf[4096]; const ssize_t bufsize = sizeof(buf) - 1; @@ -30,7 +31,7 @@ int read_audiod_pipe(int fd, void (*line_handler)(char *) ) if (ret > 0) { loaded += ret; buf[loaded] = '\0'; - loaded = for_each_line(buf, loaded, line_handler); + loaded = for_each_line(buf, loaded, line_handler, NULL); } return ret; } diff --git a/gui_common.h b/gui_common.h new file mode 100644 index 00000000..0e8671f5 --- /dev/null +++ b/gui_common.h @@ -0,0 +1,4 @@ +#include "string.h" +int para_open_audiod_pipe(char *); +int read_audiod_pipe(int fd, line_handler_t *line_handler); + diff --git a/hash.h b/hash.h new file mode 100644 index 00000000..99f4486a --- /dev/null +++ b/hash.h @@ -0,0 +1,32 @@ +#include "portable_io.h" +#define HASH_TYPE unsigned char + +//#include "super_fast_hash.h" +//#define hash_function super_fast_hash +#include "sha1.h" +#define hash_function sha1_hash + +static inline int hash_compare(HASH_TYPE *h1, HASH_TYPE *h2) +{ + int i; + + for (i = 0; i < HASH_SIZE; i++) { + if (h1[i] < h2[i]) + return -1; + if (h1[i] > h2[i]) + return 1; + } + return 0; +} + +static inline void hash_to_asc(HASH_TYPE *hash, char *asc) +{ + int i; + const char hexchar[] = "0123456789abcdef"; + + for (i = 0; i < HASH_SIZE; i++) { + asc[2 * i] = hexchar[hash[i] >> 4]; + asc[2 * i + 1] = hexchar[hash[i] & 0xf]; + } + asc[2 * HASH_SIZE] = '\0'; +} diff --git a/mood.c b/mood.c new file mode 100644 index 00000000..b65561d5 --- /dev/null +++ b/mood.c @@ -0,0 +1,978 @@ +#include "para.h" +#include "error.h" +#include "afs.h" +#include "list.h" +#include "string.h" + +/** \file mood.c Paraslash's mood handling functions. */ + +/** + * Contains statistical data of the currently admissible audio files. + * + * It is used to assign normalized score values to each admissbile audio file. + */ +struct afs_statistics { + /** sum of num played over all admissible files */ + int64_t num_played_sum; + /** sum of last played times over all admissible files */ + int64_t last_played_sum; + /** quadratic deviation of num played time */ + int64_t num_played_qd; + /** quadratic deviation of last played time */ + int64_t last_played_qd; + /** number of admissible files */ + unsigned num; +}; +struct afs_statistics statistics; + +/** + * Assign scores according to a mood_method. + * + * Each mood_method has its own mood_score_function. The first parameter passed + * to that function is a pointer to a row of the audio file table. It + * determines the audio file for which a score is to be assigned. The second + * argument depends on the mood method this function is used for. It usually is + * the argument given at the end of a mood line. + * + * Mood score functions must return values between -100 and +100 inclisively. + * Boolean score functions should always return either -100 or +100. + * + * \sa struct mood_method, mood_parser. + */ +typedef int mood_score_function(const struct osl_row*, void *); + +/** + * Preprocess a mood line. + * + * The mood_parser of a mood_method is called once at mood open time for each + * line of the current mood definition that contains the mood_method's name as + * a keyword. The line is passed to the mood_parser as the first argument. The + * mood_parser must determine whether the line is syntactically correct and + * return a positive value if so and a negative value otherwise. + * + * Some mood parsers preprocess the data given in the mood line to compute a + * structure which depends of the particular mood_method and which is used + * later in the mood_score_function of the mood_method. The mood_parser may + * store a pointer to its structure via the second argument. + * + * \sa mood_open(), mood_cleanup_function, mood_score_function. + */ +typedef int mood_parser(const char *, void **); + +/** + * Deallocate resources which were allocated by the mood_parser. + * + * This optional function of a mood_method is used to free any resources + * allocated in mood_open() by the mood_parser. The argument passed is a + * pointer to the mood_method specific data structure that was returned by the + * mood_parser. + * + * \sa mood_parser. + */ +typedef void mood_cleanup_function(void *); + +/** + * Used for scoring and to determine whether a file is admissible. + */ +struct mood_method { + /* The name of the method. */ + const char *name; + /** Pointer to the mood parser. */ + mood_parser *parser; + /** Pointer to the score function */ + mood_score_function *score_function; + /** Optional cleanup function. */ + mood_cleanup_function *cleanup; +}; + +/** + * Each line of the current mood corresponds to a mood_item. + */ +struct mood_item { + /** The method this line is referring to. */ + const struct mood_method *method; + /** The data structure computed by the mood parser. */ + void *parser_data; + /** The given score value, or zero if none was given. */ + long score_arg; + /** Non-zero if random scoring was requested. */ + int random_score; + /** Whether the "not" keyword was given in the mood line. */ + int logical_not; + /** The position in the list of items. */ + struct list_head mood_item_node; +}; + +/** + * Created from the mood definition by mood_open(). + * + * When a mood is opened, each line of its definition is investigated, and a + * corresponding mood item is produced. Each mood line starts with \p accept, + * \p deny, or \p score which determins the type of the mood line. For each + * such type a linked list is maintained whose entries are the mood items. + * + * \sa mood_item, mood_open(). + */ +struct mood { + /** the name of this mood */ + char *name; + /** The list of mood items of type \p accept. */ + struct list_head accept_list; + /** The list of mood items of type \p deny. */ + struct list_head deny_list; + /** The list of mood items of type \p score. */ + struct list_head score_list; +}; + +static struct mood *current_mood; + +/** + * Rough approximation to sqrt. + * + * \param x Integer of which to calculate the sqrt. + * + * \return An integer res with res * res <= x. + */ +static uint64_t int_sqrt(uint64_t x) +{ + uint64_t op, res, one = 1; + op = x; + res = 0; + + one = one << 62; + while (one > op) + one >>= 2; + + while (one != 0) { + if (op >= res + one) { + op = op - (res + one); + res = res + 2 * one; + } + res /= 2; + one /= 4; + } +// PARA_NOTICE_LOG("sqrt(%llu) = %llu\n", x, res); + return res; +} + +static int mm_played_rarely_score_function(const struct osl_row *row, + __a_unused void *ignored) +{ + struct afs_info afsi; + unsigned num; + int ret = get_afsi_of_row(row, &afsi); + + if (ret < 0) + return 0; + ret = get_num_admissible_files(&num); + if (ret < 0) + return 0; + if (statistics.num_played_sum - num * afsi.num_played + > int_sqrt(statistics.num_played_qd * num)) + return 100; + return -100; +} + +static int mm_played_rarely_parser(const char *arg, __a_unused void **ignored) +{ + if (*arg) + PARA_WARNING_LOG("ignored junk at eol: %s\n", arg); + return 1; +} + +static int mm_name_like_score_function(const struct osl_row *row, void *preg) +{ + char *path; + int ret = get_audio_file_path_of_row(row, &path); + + if (ret < 0) + return 0; + ret = regexec((regex_t *)preg, path, 42, NULL, 0); + return (ret == REG_NOMATCH)? -100 : 100; +} + +static int mm_name_like_parser(const char *arg, void **regex) +{ + regex_t *preg = para_malloc(sizeof(*preg)); + int ret = regcomp(preg, arg, REG_NOSUB); + + if (ret) { + free(preg); + return -E_MOOD_REGEX; + } + *regex = preg; + return 1; +} + +static void mm_name_like_cleanup(void *preg) +{ + regfree(preg); + free(preg); +} + +static int mm_is_set_parser(const char *arg, void **bitnum) +{ + unsigned char *c = para_malloc(1); + int ret = get_attribute_bitnum_by_name(arg, c); + + if (ret >= 0) + *bitnum = c; + else + free(c); + return ret; +} + +static int mm_is_set_score_function(const struct osl_row *row, void *bitnum) +{ + unsigned char *bn = bitnum; + struct afs_info afsi; + int ret = get_afsi_of_row(row, &afsi); + + if (ret < 0) + return 0; + if (afsi.attributes & (1ULL << *bn)) + return 100; + return -100; +} + +static int para_random(unsigned max) +{ + return ((max + 0.0) * (rand() / (RAND_MAX + 1.0))); +} + +/* returns 1 if row matches score item, -1 otherwise */ +static int add_item_score(const void *row, struct mood_item *item, long *score, + long *score_arg_sum) +{ + int ret = 100; + + *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg); + if (item->method) { + ret = item->method->score_function(row, item->parser_data); + if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not)) + return -1; /* no match */ + } + if (item->random_score) + *score += PARA_ABS(ret) * para_random(100); + else + *score += PARA_ABS(ret) * item->score_arg; + return 1; +} + +static int compute_mood_score(const void *row, long *result) +{ + struct mood_item *item; + int match = 0; + long score_arg_sum = 0, score = 0; + + if (!current_mood) + return -E_NO_MOOD; + /* reject audio file if it matches any entry in the deny list */ + list_for_each_entry(item, ¤t_mood->deny_list, mood_item_node) + if (add_item_score(row, item, &score, &score_arg_sum) > 0) + return -E_NOT_ADMISSIBLE; + list_for_each_entry(item, ¤t_mood->accept_list, mood_item_node) + if (add_item_score(row, item, &score, &score_arg_sum) > 0) + match = 1; + /* reject if there is no matching entry in the accept list */ + if (!match && !list_empty(¤t_mood->accept_list)) + return -E_NOT_ADMISSIBLE; + list_for_each_entry(item, ¤t_mood->score_list, mood_item_node) { + PARA_INFO_LOG("random: %d\n", para_random(100)); + add_item_score(row, item, &score, &score_arg_sum); + } + if (score_arg_sum) + score /= score_arg_sum; + *result = score; + return 1; +} + +static const struct mood_method mood_methods[] = { +{ + .parser = mm_played_rarely_parser, + .score_function = mm_played_rarely_score_function, + .name = "played_rarely" +}, +{ + .parser = mm_is_set_parser, + .score_function = mm_is_set_score_function, + .name = "is_set" +}, +{ + .parser = mm_name_like_parser, + .score_function = mm_name_like_score_function, + .cleanup = mm_name_like_cleanup, + .name = "name_like" +}, +{ + .parser = NULL +} +}; + +static void cleanup_list_entry(struct mood_item *item) +{ + if (item->method && item->method->cleanup) + item->method->cleanup(item->parser_data); + else + free(item->parser_data); + list_del(&item->mood_item_node); + free(item); +} + +static void destroy_mood(struct mood *m) +{ + struct mood_item *tmp, *item; + + if (!m) + return; + list_for_each_entry_safe(item, tmp, &m->accept_list, mood_item_node) + cleanup_list_entry(item); + list_for_each_entry_safe(item, tmp, &m->deny_list, mood_item_node) + cleanup_list_entry(item); + list_for_each_entry_safe(item, tmp, &m->score_list, mood_item_node) + cleanup_list_entry(item); + free(m->name); + free(m); +} + +static struct mood *alloc_new_mood(const char *name) +{ + struct mood *m = para_calloc(sizeof(struct mood)); + m->name = para_strdup(name); + INIT_LIST_HEAD(&m->accept_list); + INIT_LIST_HEAD(&m->deny_list); + INIT_LIST_HEAD(&m->score_list); + return m; +} + +/** The different types of a mood line. */ +enum mood_line_type { + /** Invalid. */ + ML_INVALID, + /** Accept line. */ + ML_ACCEPT, + /** Deny line. */ + ML_DENY, + /** Score line. */ + ML_SCORE +}; + +/* + * ] | deny [with score ] | score > + * [if] [not] [options] + * is either an integer or "random" which assigns a random score to + * all matching files + */ + +/* TODO: Use current_mood as private_data*/ +static int parse_mood_line(char *mood_line, __a_unused void *private_data) +{ + char **argv; + char *delim = " \t"; + unsigned num_words; + char **w; + int i, ret; + enum mood_line_type mlt = ML_INVALID; + struct mood_item *mi = NULL; + struct mood *m = current_mood; + char *buf = para_strdup(mood_line); + + num_words = split_args(buf, &argv, delim); + ret = 1; + if (!num_words) /* empty line */ + goto out; + w = argv; + if (**w == '#') /* comment */ + goto out; + if (!strcmp(*w, "accept")) + mlt = ML_ACCEPT; + else if (!strcmp(*w, "deny")) + mlt = ML_DENY; + else if (!strcmp(*w, "score")) + mlt = ML_SCORE; + ret = -E_MOOD_SYNTAX; + if (mlt == ML_INVALID) + goto out; + mi = para_calloc(sizeof(struct mood_item)); + if (mlt != ML_SCORE) { + ret = -E_MOOD_SYNTAX; + w++; + if (!*w) + goto out; + if (!strcmp(*w, "with")) { + w++; + if (!*w) + goto out; + } + } + if (mlt == ML_SCORE || !strcmp(*w, "score")) { + ret = -E_MOOD_SYNTAX; + w++; + if (!*w) + goto out; + if (strcmp(*w, "random")) { + mi->random_score = 0; + ret = para_atol(*w, &mi->score_arg); + if (ret < 0) + goto out; + } else { + mi->random_score = 1; + if (!*(w + 1)) + goto success; /* the line "score random" is valid */ + } + } else + mi->score_arg = 0; + ret = -E_MOOD_SYNTAX; + w++; + if (!*w) + goto out; + if (!strcmp(*w, "if")) { + ret = -E_MOOD_SYNTAX; + w++; + if (!*w) + goto out; + } + if (!strcmp(*w, "not")) { + ret = -E_MOOD_SYNTAX; + w++; + if (!*w) + goto out; + mi->logical_not = 1; + } else + mi->logical_not = 0; + for (i = 0; mood_methods[i].parser; i++) { + if (strcmp(*w, mood_methods[i].name)) + continue; + break; + } + ret = -E_MOOD_SYNTAX; + if (!mood_methods[i].parser) + goto out; + w++; + ret = mood_methods[i].parser(*w, &mi->parser_data); + if (ret < 0) + goto out; + mi->method = &mood_methods[i]; +success: + if (mlt == ML_ACCEPT) + para_list_add(&mi->mood_item_node, &m->accept_list); + else if (mlt == ML_DENY) + para_list_add(&mi->mood_item_node, &m->deny_list); + else + para_list_add(&mi->mood_item_node, &m->score_list); + PARA_DEBUG_LOG("%s entry added, method: %p\n", mlt == ML_ACCEPT? "accept" : + (mlt == ML_DENY? "deny" : "score"), mi->method); + ret = 1; +out: + free(argv); + free(buf); + if (ret >= 0) + return ret; + if (mi) { + free(mi->parser_data); + free(mi); + } + return ret; +} + +static int load_mood(const void *row) +{ + int ret; + struct mood *new_mood, *old_mood = current_mood; + struct osl_object objs[NUM_BLOB_COLUMNS]; + + ret = osl_get_object(moods_table, row, BLOBCOL_NAME, &objs[BLOBCOL_NAME]); + if (ret < 0) + return ret; + if (objs[BLOBCOL_NAME].size <= 1) + return -E_DUMMY_ROW; + ret = osl_open_disk_object(moods_table, row, BLOBCOL_DEF, &objs[BLOBCOL_DEF]); + if (ret < 0) + return ret; + new_mood = alloc_new_mood((char*)objs[BLOBCOL_NAME].data); + current_mood = new_mood; + ret = for_each_line_ro(objs[BLOBCOL_DEF].data, objs[BLOBCOL_DEF].size, + parse_mood_line, NULL); + osl_close_disk_object(&objs[BLOBCOL_DEF]); + if (ret < 0) { + PARA_ERROR_LOG("unable to load mood %s: %d\n", + (char *)objs[BLOBCOL_NAME].data, ret); + destroy_mood(new_mood); + current_mood = old_mood; + return ret; + } + destroy_mood(old_mood); + current_mood = new_mood; + PARA_INFO_LOG("loaded mood %s\n", current_mood->name); + return 1; +} + +/* returns -E_MOOD_LOADED on _success_ to terminate the loop */ +static int mood_loop(struct osl_row *row, __a_unused void *private_data) +{ + int ret = load_mood(row); + if (ret < 0) { + if (ret != -E_DUMMY_ROW) + PARA_NOTICE_LOG("invalid mood (%d), trying next mood\n", ret); + return 1; + } + return -E_MOOD_LOADED; +} + +static int load_first_available_mood(void) +{ + int ret = osl_rbtree_loop(moods_table, BLOBCOL_NAME, NULL, + mood_loop); + if (ret == -E_MOOD_LOADED) /* success */ + return 1; + if (ret < 0) + return ret; /* error */ + PARA_NOTICE_LOG("no valid mood found\n"); + return -E_NO_MOOD; +} + +#if 0 +static unsigned int_log2(uint64_t x) +{ + unsigned res = 0; + + while (x) { + x /= 2; + res++; + } + return res; +} +#endif + +static int64_t normalized_value(int64_t x, int64_t n, int64_t sum, int64_t qd) +{ + if (!n || !qd) + return 0; + return 100 * (n * x - sum) / (int64_t)int_sqrt(n * qd); +} + +static long compute_num_played_score(struct afs_info *afsi) +{ + return -normalized_value(afsi->num_played, statistics.num, + statistics.num_played_sum, statistics.num_played_qd); +} + +static long compute_last_played_score(struct afs_info *afsi) +{ + return -normalized_value(afsi->last_played, statistics.num, + statistics.last_played_sum, statistics.last_played_qd); +} + +static long compute_dynamic_score(const struct osl_row *aft_row) +{ + struct afs_info afsi; + int64_t score, nscore = 0, lscore = 0; + int ret; + + ret = get_afsi_of_row(aft_row, &afsi); + if (ret < 0) + return -100; + nscore = compute_num_played_score(&afsi); + lscore = compute_last_played_score(&afsi); + score = nscore + lscore; + return score; +} + +static int add_afs_statistics(const struct osl_row *row) +{ + uint64_t n, x, s; + struct afs_info afsi; + int ret; + + ret = get_afsi_of_row(row, &afsi); + if (ret < 0) + return ret; + n = statistics.num; + x = afsi.last_played; + s = statistics.last_played_sum; + if (n > 0) + statistics.last_played_qd += (x - s / n) * (x - s / n) * n / (n + 1); + statistics.last_played_sum += x; + + x = afsi.num_played; + s = statistics.num_played_sum; + if (n > 0) + statistics.num_played_qd += (x - s / n) * (x - s / n) * n / (n + 1); + statistics.num_played_sum += x; + statistics.num++; + return 1; +} + +static int del_afs_statistics(const struct osl_row *row) +{ + uint64_t n, s, q, a, new_s; + struct afs_info afsi; + int ret; + ret = get_afsi_of_row(row, &afsi); + if (ret < 0) + return ret; + n = statistics.num; + assert(n); + if (n == 1) { + memset(&statistics, 0, sizeof(statistics)); + return 1; + } + + s = statistics.last_played_sum; + q = statistics.last_played_qd; + a = afsi.last_played; + new_s = s - a; + statistics.last_played_sum = new_s; + statistics.last_played_qd = q + s * s / n - a * a + - new_s * new_s / (n - 1); + + s = statistics.num_played_sum; + q = statistics.num_played_qd; + a = afsi.num_played; + new_s = s - a; + statistics.num_played_sum = new_s; + statistics.num_played_qd = q + s * s / n - a * a + - new_s * new_s / (n - 1); + + statistics.num--; + return 1; +} + +/** + * Structure used during mood_open(). + * + * At mood open time, we look at each file in the audio file table in order to + * determine whether it is admissible. If a file happens to be admissible, its + * mood score is computed by calling each relevant mood_score_function. Next, + * we update the afs_statistics and add a struct admissible_file_info to a + * temporary array. + * + * If all files have been processed that way, the final score of each + * admissible file is computed by adding the dynamic score (which depends on + * the afs_statistics) to the mood score. Finally, all audio files in the + * array are added to the score table and the admissible array is freed. + * + * \sa mood_method, admissible_array. + */ +struct admissible_file_info +{ + /** The admissible audio file. */ + void *aft_row; + /** Its score. */ + long score; +}; + +/** The temporary array of admissible files. */ +struct admissible_array { + /** The size of the array */ + unsigned size; + /** Pointer to the array of admissible files. */ + struct admissible_file_info *array; +}; + +/** + * Add an entry to the array of admissible files. + * + * \param aft_row The audio file to be added. + * \param private_data Pointer to a struct admissible_file_info. + * + * \return Negative on errors, positive on success. + */ +static int add_if_admissible(struct osl_row *aft_row, void *private_data) +{ + int ret; + struct admissible_array *aa = private_data; + long score = 0; + + score = 0; + ret = compute_mood_score(aft_row, &score); + if (ret < 0) + return (ret == -E_NOT_ADMISSIBLE)? 1 : ret; + if (statistics.num >= aa->size) { + aa->size *= 2; + aa->size += 100; + aa->array = para_realloc(aa->array, + aa->size * sizeof(struct admissible_file_info)); + } + aa->array[statistics.num].aft_row = aft_row; + aa->array[statistics.num].score = score; + ret = add_afs_statistics(aft_row); + if (ret < 0) + return ret; + return 1; +} + +/** + * Compute the new quadratic deviation in case one element changes. + * + * \param n Number of elements. + * \param old_qd The quadratic deviation before the change. + * \param old_val The value that was repaced. + * \param new_val The replacement value. + * \param old_sum The sum of all elements before the update. + * + * \return The new quadratic deviation resulting from replacing old_val + * by new_val. + * + * Given n real numbers a_1, ..., a_n, their sum S = a_1 + ... + a_n, + * their quadratic deviation + * + * q = (a_1 - S/n)^2 + ... + (a_n - S/n)^2, + * + * and a real number b, the quadratic deviation q' of a_1,...a_{n-1}, b (ie. + * the last number a_n was replaced by b) may be computed in O(1) time in terms + * of n, q, a_n, b, and S as + * + * q' = q + d * s - (2 * S + d) * d / n, + * + * where d = b - a_n, and s = b + a_n. + * + * Example: n = 3, a_1 = 3, a_2 = 5, a_3 = 7, b = 10. Then S = 15, q = 8, d = 3, + * s = 17, so + * + * q + d * s - (2 * S + d) * d / n = 8 + 51 - 33 = 26, + * + * which equals q' = (3 - 6)^2 + (5 - 6)^2 + (10 - 6)^2. + * + */ +_static_inline_ int64_t update_quadratic_deviation(int64_t n, int64_t old_qd, + int64_t old_val, int64_t new_val, int64_t old_sum) +{ + int64_t delta = new_val - old_val; + int64_t sigma = new_val + old_val; + return old_qd + delta * sigma - (2 * old_sum + delta) * delta / n; +} + +static int update_afs_statistics(struct afs_info *old_afsi, struct afs_info *new_afsi) +{ + unsigned n; + int ret = get_num_admissible_files(&n); + + if (ret < 0) + return ret; + assert(n); + + statistics.last_played_qd = update_quadratic_deviation(n, + statistics.last_played_qd, old_afsi->last_played, + new_afsi->last_played, statistics.last_played_sum); + statistics.last_played_sum += new_afsi->last_played - old_afsi->last_played; + + statistics.num_played_qd = update_quadratic_deviation(n, + statistics.num_played_qd, old_afsi->num_played, + new_afsi->num_played, statistics.num_played_sum); + statistics.num_played_sum += new_afsi->num_played - old_afsi->num_played; + return 1; +} + +static int add_to_score_table(const struct osl_row *aft_row, long mood_score) +{ + long score = (compute_dynamic_score(aft_row) + mood_score) / 3; + return score_add(aft_row, score); +} + +static int delete_from_statistics_and_score_table(const struct osl_row *aft_row) +{ + int ret = del_afs_statistics(aft_row); + if (ret < 0) + return ret; + return score_delete(aft_row); +} + +/** + * Delete one entry from the statitics and from the score table. + * + * \param aft_row The audio file which is no longer admissible. + * + * \return Positive on success, negative on errors. + * + * \sa score_delete(), mood_update_audio_file(). + */ +int mood_delete_audio_file(const struct osl_row *aft_row) +{ + int ret; + + ret = row_belongs_to_score_table(aft_row); + if (ret < 0) + return ret; + if (!ret) /* not admissible, nothing to do */ + return 1; + return delete_from_statistics_and_score_table(aft_row); +} + +/** + * Compute the new score of an audio file. + * + * \param aft_row Determines the audio file. + * \param old_afsi The audio file selector info before updating. + * + * The \a old_afsi argument may be \p NULL which indicates that no changes to + * the audio file info were made. + * + * \return Positive on success, negative on errors. + */ +int mood_update_audio_file(const struct osl_row *aft_row, struct afs_info *old_afsi) +{ + long score, percent; + int ret, is_admissible, was_admissible = 0; + struct afs_info afsi; + + if (!current_mood) + return 1; /* nothing to do */ + ret = row_belongs_to_score_table(aft_row); + if (ret < 0) + return ret; + was_admissible = ret; + ret = compute_mood_score(aft_row, &score); + is_admissible = (ret > 0); + if (!was_admissible && !is_admissible) + return 1; + if (was_admissible && !is_admissible) + return delete_from_statistics_and_score_table(aft_row); + if (!was_admissible && is_admissible) { + ret = add_afs_statistics(aft_row); + if (ret < 0) + return ret; + return add_to_score_table(aft_row, score); + } + /* update score */ + ret = get_afsi_of_row(aft_row, &afsi); + if (ret < 0) + return ret; + if (old_afsi) { + ret = update_afs_statistics(old_afsi, &afsi); + if (ret < 0) + return ret; + } + score += compute_num_played_score(&afsi); + score += compute_last_played_score(&afsi); + score /= 3; + PARA_NOTICE_LOG("score: %li\n", score); + percent = (score + 100) / 3; + if (percent > 100) + percent = 100; + else if (percent < 0) + percent = 0; + PARA_NOTICE_LOG("re-inserting at %lu%%\n", percent); + return score_update(aft_row, percent); +} + +static void log_statistics(void) +{ + unsigned n = statistics.num; + + if (!n) { + PARA_NOTICE_LOG("no admissible files\n"); + return; + } + PARA_NOTICE_LOG("last_played mean: %lli, last_played sigma: %lli\n", + statistics.last_played_sum / n, int_sqrt(statistics.last_played_qd / n)); + PARA_NOTICE_LOG("num_played mean: %lli, num_played sigma: %lli\n", + statistics.num_played_sum / n, int_sqrt(statistics.num_played_qd / n)); +} + +/** + * Open the given mood. + * + * \param mood_name The name of the mood to open. + * + * There are two special cases: If \a mood_name is \a NULL, load the + * first available mood. If \a mood_name is the empty string "", load + * the dummy mood that accepts every audio file and uses a scoring method + * based only on the \a last_played information. + * + * \return Positive on success, negative on errors. Loading the dummy mood + * always succeeds. + * + * \sa struct admissible_file_info, struct admissible_array, struct + * afs_info::last_played, mood_close(). + */ +int mood_open(char *mood_name) +{ + int i, ret; + struct admissible_array aa = { + .size = 0, + .array = NULL + }; + + if (!mood_name) { + ret = load_first_available_mood(); + if (ret < 0) + return ret; + } else if (*mood_name) { + struct osl_row *row; + struct osl_object obj = { + .data = mood_name, + .size = strlen(mood_name) + 1 + }; + ret = osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row); + if (ret < 0) { + PARA_NOTICE_LOG("no such mood: %s\n", mood_name); + return ret; + } + ret = load_mood(row); + if (ret < 0) + return ret; + } else { + destroy_mood(current_mood); + current_mood = alloc_new_mood("dummy"); + } + PARA_NOTICE_LOG("loaded mood %s\n", current_mood->name); + PARA_INFO_LOG("%s\n", "computing statistics of admissible files"); + ret = audio_file_loop(&aa, add_if_admissible); + if (ret < 0) + return ret; + log_statistics(); + PARA_NOTICE_LOG("%d admissible files \n", statistics.num); + for (i = 0; i < statistics.num; i++) { + struct admissible_file_info *a = aa.array + i; + ret = add_to_score_table(a->aft_row, a->score); + if (ret < 0) + goto out; + } + PARA_NOTICE_LOG("score add complete\n"); + ret = 1; +out: + free(aa.array); + return ret; +} + +/** + * Close the current mood. + * + * Free all resources of the current mood which were allocated during + * mood_open(). + */ +void mood_close(void) +{ + destroy_mood(current_mood); + current_mood = NULL; + memset(&statistics, 0, sizeof(statistics)); +} + +/** + * Close and re-open the current mood. + * + * This function is used if changes to the audio file table or the + * attribute table were made that render the current list of admissible + * files useless. For example, if an attribute is removed from the + * attribute table, this function is called. + * + * \return Positive on success, negative on errors. If no mood is currently + * open, the function returns success. + * + * \sa mood_open(), mood_close(). + */ +int mood_reload(void) +{ + int ret; + char *mood_name; + + if (!current_mood) + return 1; + score_shutdown(0); + mood_name = para_strdup(current_mood->name); + mood_close(); + ret = mood_open(mood_name); + free(mood_name); + return ret; +} diff --git a/mp3_afh.c b/mp3_afh.c index 0eb1188e..dde71c50 100644 --- a/mp3_afh.c +++ b/mp3_afh.c @@ -399,7 +399,7 @@ err_out: /* * Read mp3 information from audio file */ -static int mp3_get_file_info(char *map, size_t numbytes, +int mp3_get_file_info(char *map, size_t numbytes, struct audio_format_info *afi) { int ret; diff --git a/osl.c b/osl.c new file mode 100644 index 00000000..1fc45bd4 --- /dev/null +++ b/osl.c @@ -0,0 +1,2185 @@ +/* + * Copyright (C) 2007 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file osl.c Object storage layer functions. */ +#include "para.h" +#include "error.h" +#include "list.h" +#include "osl_core.h" +#include /* readdir() */ +#include + +//#define FMT_OFF_T "%li" + + +/** + * A wrapper for lseek(2). + * + * \param fd The filedescriptor whose offset is to be to repositioned. + * \param offset A value-result parameter. + * \param whence Usual repositioning directive. + * + * Reposition the offset of the file descriptor \a fd to the argument \a offset + * according to the directive \a whence. Upon successful return, \a offset + * contains the resulting offset location as measured in bytes from the + * beginning of the file. + * + * \return Positive on success. Otherwise, the function returns \p -E_LSEEK. + * + * \sa lseek(2). + */ +int para_lseek(int fd, off_t *offset, int whence) +{ + *offset = lseek(fd, *offset, whence); + int ret = -E_LSEEK; + if (*offset == -1) + return ret; + return 1; +} + +/** + * Waraper for the write system call. + * + * \param fd The file descriptor to write to. + * \param buf The buffer to write. + * \param size The length of \a buf in bytes. + * + * This function writes out the given bufffer and retries if an interrupt + * occured during the write. + * + * \return On success, the number of bytes written is returned, otherwise, the + * function returns \p -E_WRITE. + * + * \sa write(2). + */ +ssize_t para_write(int fd, const void *buf, size_t size) +{ + ssize_t ret; + + for (;;) { + ret = write(fd, buf, size); + if ((ret < 0) && (errno == EAGAIN || errno == EINTR)) + continue; + return ret >= 0? ret : -E_WRITE; + } +} + +/** + * Write the whole buffer to a file descriptor. + * + * \param fd The file descriptor to write to. + * \param buf The buffer to write. + * \param size The length of \a buf in bytes. + * + * This function writes the given buffer and continues on short writes and + * when interrupted by a signal. + * + * \return Positive on success, negative on errors. Possible errors: any + * errors returned by para_write(). + * + * \sa para_write(). + */ +ssize_t para_write_all(int fd, const void *buf, size_t size) +{ + PARA_DEBUG_LOG("writing %zu bytes\n", size); + const char *b = buf; + while (size) { + ssize_t ret = para_write(fd, b, size); + PARA_DEBUG_LOG("ret: %d\n", ret); + if (ret < 0) + return ret; + b += ret; + size -= ret; + } + return 1; +} +/** + * Wrapper for the open(2) system call. + * + * \param path The filename. + * \param flags The usual open(2) flags. + * \param mode Specifies the permissions to use. + * + * The mode parameter must be specified when O_CREAT is in the flags, and is ignored + * otherwise. + * + * \return Positive on success, negative on errors. Possible errors: \p + * E_EXIST, \p E_ISDIR, \p E_NOENT, \p E_OSL_PERM. + * + * \sa open(2). + */ +int para_open(const char *path, int flags, mode_t mode) +{ + PARA_DEBUG_LOG("opening %s\n", path); + int ret = open(path, flags, mode); + + if (ret >= 0) + return ret; + switch (errno) { + case EEXIST: + ret = -E_EXIST; + break; + case EISDIR: + ret = -E_ISDIR; + break; + case ENOENT: + ret = -E_NOENT; + break; + case EPERM: + ret = -E_OSL_PERM; + break; + }; + PARA_ERROR_LOG("failed to open %s: %s\n", path, strerror(errno)); + return ret; +} + +/** + * Open a file, write the given buffer and close the file. + * + * \param filename Full path to the file to open. + * \param buf The buffer to write to the file. + * \param size The size of \a buf. + * + * \return Positive on success, negative on errors. Possible errors include: + * any errors from para_open() or para_write(). + * + * \sa para_open(), para_write(). + */ +int para_write_file(const char *filename, const void *buf, size_t size) +{ + int ret, fd; + + ret = para_open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (ret < 0) + return ret; + fd = ret; + ret = para_write_all(fd, buf, size); + if (ret < 0) + goto out; + ret = 1; +out: + close(fd); + return ret; +} + +static int append_file(const char *filename, char *header, size_t header_size, + char *data, size_t data_size, uint32_t *new_pos) +{ + int ret, fd; + + PARA_DEBUG_LOG("appending %lu + %ld bytes\n", header_size, data_size); + ret = para_open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (ret < 0) + return ret; + fd = ret; + if (header && header_size) { + ret = para_write_all(fd, header, header_size); + if (ret < 0) + goto out; + } + ret = para_write_all(fd, data, data_size); + if (ret < 0) + goto out; + if (new_pos) { + off_t offset = 0; + ret = para_lseek(fd, &offset, SEEK_END); + if (ret < 0) + goto out; +// PARA_DEBUG_LOG("new file size: " FMT_OFF_T "\n", offset); + *new_pos = offset; + } + ret = 1; +out: + close(fd); + return ret; +} + +/** + * Map a file into memory. + * + * \param path Name of the regular file to map. + * \param open_mode Either \p O_RDONLY or \p O_RDWR. + * \param obj On success, the mapping is returned here. + * + * \return Positive on success, negative on errors. Possible errors include: \p + * E_FSTAT, any errors returned by para_open(), \p E_EMPTY, \p E_MMAP. + * + * \sa para_open(), mmap(2). + */ +int mmap_full_file(const char *path, int open_mode, struct osl_object *obj) +{ + int fd, ret, mmap_prot, mmap_flags; + struct stat file_status; + + if (open_mode == O_RDONLY) { + mmap_prot = PROT_READ; + mmap_flags = MAP_PRIVATE; + } else { + mmap_prot = PROT_READ | PROT_WRITE; + mmap_flags = MAP_SHARED; + } + ret = para_open(path, open_mode, 0); + if (ret < 0) + return ret; + fd = ret; + ret = -E_FSTAT; + if (fstat(fd, &file_status) < 0) + goto out; + obj->size = file_status.st_size; + ret = -E_EMPTY; + PARA_DEBUG_LOG("%s: size %zu\n", path, obj->size); + if (!obj->size) + goto out; + obj->data = mmap(NULL, obj->size, mmap_prot, mmap_flags, fd, 0); + if (obj->data == MAP_FAILED) { + obj->data = NULL; + ret = -E_MMAP; + goto out; + } + ret = 1; +out: + close(fd); + return ret; +} + +/** + * Traverse the given directory recursively. + * + * \param dirname The directory to traverse. + * \param func The function to call for each entry. + * \param private_data Pointer to an arbitrary data structure. + * + * For each regular file in \a dirname, the supplied function \a func is + * called. The full path of the regular file and the \a private_data pointer + * are passed to \a func. + * + * \return On success, 1 is returned. Otherwise, this function returns a + * negative value which indicates the kind of the error. + */ +int for_each_file_in_dir(const char *dirname, + int (*func)(const char *, const void *), const void *private_data) +{ + DIR *dir = NULL; + struct dirent *entry; + /* + * Opening the current directory (".") and calling fchdir() to return + * is usually faster and more reliable than saving cwd in some buffer + * and calling chdir() afterwards (see man 3 getcwd). + */ + int cwd_fd = open(".", O_RDONLY); + struct stat s; + int ret = -1; + +// PARA_DEBUG_LOG("dirname: %s\n", dirname); + if (cwd_fd < 0) + return -E_OSL_GETCWD; + ret = -E_OSL_CHDIR; + if (chdir(dirname) < 0) + goto out; + ret = -E_OSL_OPENDIR; + dir = opendir("."); + if (!dir) + goto out; + /* scan cwd recursively */ + while ((entry = readdir(dir))) { + mode_t m; + char *tmp; + + if (!strcmp(entry->d_name, ".")) + continue; + if (!strcmp(entry->d_name, "..")) + continue; + ret = -E_OSL_LSTAT; + if (lstat(entry->d_name, &s) == -1) + continue; + m = s.st_mode; + if (!S_ISREG(m) && !S_ISDIR(m)) + continue; + tmp = make_message("%s/%s", dirname, entry->d_name); + if (!S_ISDIR(m)) { + ret = func(tmp, private_data); + free(tmp); + if (ret < 0) + goto out; + continue; + } + /* directory */ + ret = for_each_file_in_dir(tmp, func, private_data); + free(tmp); + if (ret < 0) + goto out; + } + ret = 1; +out: + if (dir) + closedir(dir); + if (fchdir(cwd_fd) < 0 && ret >= 0) + ret = -E_OSL_CHDIR; + close(cwd_fd); + return ret; +} + +int para_mkdir(const char *path, mode_t mode) +{ + if (!mkdir(path, mode)) + return 1; + if (errno == EEXIST) + return -E_EXIST; + if (errno == ENOSPC) + return -E_NOSPC; + if (errno == ENOTDIR) + return -E_NOTDIR; + if (errno == EPERM) + return E_OSL_PERM; + return -E_MKDIR; +} + +static int verify_basename(const char *name) +{ + if (!name) + return -E_BAD_BASENAME; + if (!*name) + return -E_BAD_BASENAME; + if (strchr(name, '/')) + return -E_BAD_BASENAME; + if (!strcmp(name, "..")) + return -E_BAD_BASENAME; + if (!strcmp(name, ".")) + return -E_BAD_BASENAME; + return 1; +} + +/** + * Compare two osl objects pointing to unsigned integers of 32 bit size. + * + * \param obj1 Pointer to the first integer. + * \param obj2 Pointer to the second integer. + * + * \return The values required for an osl compare function. + * + * \sa osl_compare_func, osl_hash_compare(). + */ +int uint32_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + uint32_t d1 = read_u32((const char *)obj1->data); + uint32_t d2 = read_u32((const char *)obj2->data); + + if (d1 < d2) + return 1; + if (d1 > d2) + return -1; + return 0; +} + +/** + * Compare two osl objects pointing to hash values. + * + * \param obj1 Pointer to the first hash object. + * \param obj2 Pointer to the second hash object. + * + * \return The values required for an osl compare function. + * + * \sa osl_compare_func, uint32_compare(). + */ +int osl_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + return hash_compare((HASH_TYPE *)obj1->data, (HASH_TYPE *)obj2->data); +} + +static char *disk_storage_dirname(const struct osl_table *t, unsigned col_num, + const char *ds_name) +{ + char *dirname, *column_name = column_filename(t, col_num); + + if (!(t->desc->flags & OSL_LARGE_TABLE)) + return column_name; + dirname = make_message("%s/%.2s", column_name, ds_name); + free(column_name); + return dirname; +} + +static char *disk_storage_name_of_object(const struct osl_table *t, + const struct osl_object *obj) +{ + HASH_TYPE hash[HASH_SIZE]; + hash_object(obj, hash); + return disk_storage_name_of_hash(t, hash); +} + +static int disk_storage_name_of_row(const struct osl_table *t, + const struct osl_row *row, char **name) +{ + struct osl_object obj; + int ret = osl_get_object(t, row, t->disk_storage_name_column, &obj); + + if (ret < 0) + return ret; + *name = disk_storage_name_of_object(t, &obj); + return 1; +} + +static void column_name_hash(const char *col_name, HASH_TYPE *hash) +{ + return hash_function(col_name, strlen(col_name), hash); +} + +static int init_column_descriptions(struct osl_table *t) +{ + int i, j, ret; + const struct osl_column_description *cd; + + ret = -E_BAD_TABLE_DESC; + ret = verify_basename(t->desc->name); + if (ret < 0) + goto err; + ret = -E_BAD_DB_DIR; + if (!t->desc->dir) + goto err; + /* the size of the index header without column descriptions */ + t->index_header_size = IDX_COLUMN_DESCRIPTIONS; + FOR_EACH_COLUMN(i, t->desc, cd) { + struct osl_column *col = t->columns + i; + if (cd->storage_flags & OSL_RBTREE) { + if (!cd->compare_function) + return -E_NO_COMPARE_FUNC; + } + if (cd->storage_type == OSL_NO_STORAGE) + continue; + ret = -E_NO_COLUMN_NAME; + if (!cd->name || !cd->name[0]) + goto err; + ret = verify_basename(cd->name); + if (ret < 0) + goto err; + t->index_header_size += index_column_description_size(cd->name); + column_name_hash(cd->name, col->name_hash); + ret = -E_DUPLICATE_COL_NAME; + for (j = i + 1; j < t->desc->num_columns; j++) { + const char *name2 = get_column_description(t->desc, + j)->name; + if (cd->name && name2 && !strcmp(cd->name, name2)) + goto err; + } + } + return 1; +err: + return ret; +} + +/** + * Initialize a struct table from given table description. + * + * \param desc The description of the osl table. + * \param table_ptr Result is returned here. + * + * This function performs several sanity checks on \p desc and returns if any + * of these tests fail. On success, a struct \p osl_table is allocated and + * initialized with data derived from \p desc. + * + * \return Positive on success, negative on errors. Possible errors include: \p + * E_BAD_TABLE_DESC, \p E_NO_COLUMN_DESC, \p E_NO_COLUMNS, \p + * E_BAD_STORAGE_TYPE, \p E_BAD_STORAGE_FLAGS, \p E_BAD_STORAGE_SIZE, \p + * E_NO_UNIQUE_RBTREE_COLUMN, \p E_NO_RBTREE_COL. + * + * \sa struct osl_table. + */ +int init_table_structure(const struct osl_table_description *desc, + struct osl_table **table_ptr) +{ + const struct osl_column_description *cd; + struct osl_table *t = para_calloc(sizeof(*t)); + int i, ret = -E_BAD_TABLE_DESC, have_disk_storage_name_column = 0; + + PARA_INFO_LOG("creating table structure for '%s' from table " + "description\n", desc->name); + if (!desc) + goto err; + ret = -E_NO_COLUMN_DESC; + if (!desc->column_descriptions) + goto err; + ret = -E_NO_COLUMNS; + if (!desc->num_columns) + goto err; + t->columns = para_calloc(desc->num_columns * sizeof(struct osl_column)); + t->desc = desc; + FOR_EACH_COLUMN(i, t->desc, cd) { + enum osl_storage_type st = cd->storage_type; + enum osl_storage_flags sf = cd->storage_flags; + struct osl_column *col = &t->columns[i]; + + ret = -E_BAD_STORAGE_TYPE; + if (st != OSL_MAPPED_STORAGE && st != OSL_DISK_STORAGE + && st != OSL_NO_STORAGE) + goto err; + ret = -E_BAD_STORAGE_FLAGS; + if (st == OSL_DISK_STORAGE && sf & OSL_RBTREE) + goto err; + ret = -E_BAD_STORAGE_SIZE; + if (sf & OSL_FIXED_SIZE && !cd->data_size) + goto err; + switch (st) { + case OSL_DISK_STORAGE: + t->num_disk_storage_columns++; + break; + case OSL_MAPPED_STORAGE: + t->num_mapped_columns++; + col->index_offset = t->index_entry_size; + t->index_entry_size += 8; + break; + case OSL_NO_STORAGE: + col->volatile_num = t->num_volatile_columns; + t->num_volatile_columns++; + break; + } + if (sf & OSL_RBTREE) { + col->rbtree_num = t->num_rbtrees; + t->num_rbtrees++; + if ((sf & OSL_UNIQUE) && (st == OSL_MAPPED_STORAGE)) { + if (!have_disk_storage_name_column) + t->disk_storage_name_column = i; + have_disk_storage_name_column = 1; + } + } + } + ret = -E_NO_UNIQUE_RBTREE_COLUMN; + if (t->num_disk_storage_columns && !have_disk_storage_name_column) + goto err; + ret = -E_NO_RBTREE_COL; + if (!t->num_rbtrees) + goto err; + /* success */ + PARA_INFO_LOG("OK. Index entry size: %u\n", t->index_entry_size); + ret = init_column_descriptions(t); + if (ret < 0) + goto err; + *table_ptr = t; + return 1; +err: + free(t->columns); + free(t); + return ret; +} + +/** + * Read the table description from index header. + * + * \param map The memory mapping of the index file. + * \param desc The values found in the index header are returned here. + * + * Read the index header, check for the paraslash magic string and the table version number. + * Read all information stored in the index header into \a desc. + * + * \return Positive on success, negative on errors. + * + * \sa struct osl_table_description, osl_create_table. + */ +int read_table_desc(struct osl_object *map, struct osl_table_description *desc) +{ + char *buf = map->data; + uint8_t version; + uint16_t header_size; + int ret, i; + unsigned offset; + struct osl_column_description *cd; + + if (map->size < MIN_INDEX_HEADER_SIZE(1)) + return -E_SHORT_TABLE; + if (strncmp(buf + IDX_PARA_MAGIC, PARA_MAGIC, strlen(PARA_MAGIC))) + return -E_NO_MAGIC; + version = read_u8(buf + IDX_VERSION); + if (version < MIN_TABLE_VERSION || version > MAX_TABLE_VERSION) + return -E_VERSION_MISMATCH; + desc->num_columns = read_u8(buf + IDX_TABLE_FLAGS); + desc->flags = read_u8(buf + IDX_TABLE_FLAGS); + desc->num_columns = read_u16(buf + IDX_NUM_COLUMNS); + PARA_DEBUG_LOG("%u columns\n", desc->num_columns); + if (!desc->num_columns) + return -E_NO_COLUMNS; + header_size = read_u16(buf + IDX_HEADER_SIZE); + if (map->size < header_size) + return -E_BAD_SIZE; + desc->column_descriptions = para_calloc(desc->num_columns + * sizeof(struct osl_column_description)); + offset = IDX_COLUMN_DESCRIPTIONS; + FOR_EACH_COLUMN(i, desc, cd) { + char *null_byte; + + ret = -E_SHORT_TABLE; + if (map->size < offset + MIN_IDX_COLUMN_DESCRIPTION_SIZE) { + PARA_ERROR_LOG("map size = %u < %u = offset + min desc size\n", + map->size, offset + MIN_IDX_COLUMN_DESCRIPTION_SIZE); + goto err; + } + cd->storage_type = read_u16(buf + offset + IDX_CD_STORAGE_TYPE); + cd->storage_flags = read_u16(buf + offset + + IDX_CD_STORAGE_FLAGS); + cd->data_size = read_u32(buf + offset + IDX_CD_DATA_SIZE); + null_byte = memchr(buf + offset + IDX_CD_NAME, '\0', + map->size - offset - IDX_CD_NAME); + ret = -E_INDEX_CORRUPTION; + if (!null_byte) + goto err; + cd->name = para_strdup(buf + offset + IDX_CD_NAME); + offset += index_column_description_size(cd->name); + } + if (offset != header_size) { + ret = -E_INDEX_CORRUPTION; + PARA_ERROR_LOG("real header size = %u != %u = stored header size\n", + offset, header_size); + goto err; + } + return 1; +err: + FOR_EACH_COLUMN(i, desc, cd) + free(cd->name); + return ret; +} + +/* + * check whether the table description given by \p t->desc matches the on-disk + * table structure stored in the index of \a t. + */ +static int compare_table_descriptions(struct osl_table *t) +{ + int i, ret; + struct osl_table_description desc; + const struct osl_column_description *cd1, *cd2; + + /* read the on-disk structure into desc */ + ret = read_table_desc(&t->index_map, &desc); + if (ret < 0) + return ret; + ret = -E_BAD_TABLE_FLAGS; + if (desc.flags != t->desc->flags) + goto out; + ret = E_BAD_COLUMN_NUM; + if (desc.num_columns != t->desc->num_columns) + goto out; + FOR_EACH_COLUMN(i, t->desc, cd1) { + cd2 = get_column_description(&desc, i); + ret = -E_BAD_STORAGE_TYPE; + if (cd1->storage_type != cd2->storage_type) + goto out; + ret = -E_BAD_STORAGE_FLAGS; + if (cd1->storage_flags != cd2->storage_flags) { + PARA_ERROR_LOG("sf1 = %u != %u = sf2\n", + cd1->storage_flags, cd2->storage_flags); + goto out; + } + ret = -E_BAD_DATA_SIZE; + if (cd1->storage_flags & OSL_FIXED_SIZE) + if (cd1->data_size != cd2->data_size) + goto out; + ret = -E_BAD_COLUMN_NAME; + if (strcmp(cd1->name, cd2->name)) + goto out; + } + PARA_INFO_LOG("table description of '%s' matches on-disk data, good\n", + t->desc->name); + ret = 1; +out: + FOR_EACH_COLUMN(i, &desc, cd1) + free(cd1->name); + free(desc.column_descriptions); + return ret; +} + +static int create_table_index(struct osl_table *t) +{ + char *buf, *filename; + int i, ret; + size_t size = t->index_header_size; + const struct osl_column_description *cd; + unsigned offset; + + PARA_INFO_LOG("creating %zu byte index for table %s\n", size, + t->desc->name); + buf = para_calloc(size); + sprintf(buf + IDX_PARA_MAGIC, "%s", PARA_MAGIC); + write_u8(buf + IDX_TABLE_FLAGS, t->desc->flags); + write_u8(buf + IDX_DIRTY_FLAG, 0); + write_u8(buf + IDX_VERSION, CURRENT_TABLE_VERSION); + write_u16(buf + IDX_NUM_COLUMNS, t->desc->num_columns); + write_u16(buf + IDX_HEADER_SIZE, t->index_header_size); + offset = IDX_COLUMN_DESCRIPTIONS; + FOR_EACH_COLUMN(i, t->desc, cd) { + write_u16(buf + offset + IDX_CD_STORAGE_TYPE, + cd->storage_type); + write_u16(buf + offset + IDX_CD_STORAGE_FLAGS, + cd->storage_flags); + if (cd->storage_flags & OSL_FIXED_SIZE) + write_u32(buf + offset + IDX_CD_DATA_SIZE, + cd->data_size); + strcpy(buf + offset + IDX_CD_NAME, cd->name); + offset += index_column_description_size(cd->name); + } + assert(offset = size); + filename = index_filename(t->desc); + ret = para_write_file(filename, buf, size); + free(buf); + free(filename); + return ret; +} + +/** + * Create a new osl table. + * + * \param desc Pointer to the table description. + * + * \return Positive on success, negative on errors. Possible errors include: \p + * E_BAD_TABLE_DESC, \p E_BAD_DB_DIR, \p E_BAD_BASENAME, \p E_NO_COMPARE_FUNC, \p + * E_NO_COLUMN_NAME, \p E_DUPLICATE_COL_NAME, \p E_MKDIR, any errors returned + * by para_open(). + */ +int osl_create_table(const struct osl_table_description *desc) +{ + const struct osl_column_description *cd; + char *table_dir = NULL, *filename; + struct osl_table *t; + int i, ret = init_table_structure(desc, &t); + + if (ret < 0) + return ret; + PARA_INFO_LOG("creating %s\n", desc->name); + FOR_EACH_COLUMN(i, t->desc, cd) { + if (cd->storage_type == OSL_NO_STORAGE) + continue; + if (!table_dir) { + ret = para_mkdir(desc->dir, 0777); + if (ret < 0 && ret != -E_EXIST) + goto out; + table_dir = make_message("%s/%s", desc->dir, + desc->name); + ret = para_mkdir(table_dir, 0777); + if (ret < 0) + goto out; + } + filename = column_filename(t, i); + PARA_INFO_LOG("filename: %s\n", filename); + if (cd->storage_type == OSL_MAPPED_STORAGE) { + ret = para_open(filename, O_RDWR | O_CREAT | O_EXCL, + 0644); + free(filename); + if (ret < 0) + goto out; + close(ret); + continue; + } + /* DISK STORAGE */ + ret = para_mkdir(filename, 0777); + free(filename); + if (ret < 0) + goto out; + } + if (t->num_mapped_columns) { + ret = create_table_index(t); + if (ret < 0) + goto out; + } + ret = 1; +out: + free(table_dir); + free(t->columns); + free(t); + return ret; +} + +static int table_is_dirty(struct osl_table *t) +{ + char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG; + uint8_t dirty = read_u8(buf) & 0x1; + return !!dirty; +} + +static void mark_table_dirty(struct osl_table *t) +{ + char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG; + write_u8(buf, read_u8(buf) | 1); +} + +static void mark_table_clean(struct osl_table *t) +{ + char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG; + write_u8(buf, read_u8(buf) & 0xfe); +} + +/** + * Unmap all mapped files of an osl table. + * + * \param t Pointer to a mapped table. + * \param flags Options for unmapping. + * + * \return Positive on success, negative on errors. Possible errors include: + * E_NOT_MAPPED, E_MUNMAP. + * + * \sa map_table(), enum osl_close_flags, para_munmap(). + */ +int unmap_table(struct osl_table *t, enum osl_close_flags flags) +{ + unsigned i; + const struct osl_column_description *cd; + int ret; + + if (!t->num_mapped_columns) /* can this ever happen? */ + return 1; + PARA_INFO_LOG("unmapping table '%s'\n", t->desc->name); + if (!t->index_map.data) + return -E_NOT_MAPPED; + if (flags & OSL_MARK_CLEAN) + mark_table_clean(t); + ret = para_munmap(t->index_map.data, t->index_map.size); + if (ret < 0) + return ret; + t->index_map.data = NULL; + if (!t->num_rows) + return 1; + FOR_EACH_MAPPED_COLUMN(i, t, cd) { + struct osl_object map = t->columns[i].data_map; + if (!map.data) + continue; + ret = para_munmap(map.data, map.size); + if (ret < 0) + return ret; + map.data = NULL; + } + return 1; +} + +/** + * Map the index file and all columns of type \p OSL_MAPPED_STORAGE into memory. + * + * \param t Pointer to an initialized table structure. + * \param flags Mapping options. + * + * \return Negative return value on errors; on success the number of rows + * (including invalid rows) is returned. + * + * \sa unmap_table(), enum map_table_flags, osl_open_table(), mmap(2). + */ +int map_table(struct osl_table *t, enum map_table_flags flags) +{ + char *filename; + const struct osl_column_description *cd; + int i = 0, ret, num_rows = 0; + + if (!t->num_mapped_columns) + return 0; + if (t->index_map.data) + return -E_ALREADY_MAPPED; + filename = index_filename(t->desc); + PARA_DEBUG_LOG("mapping table '%s' (index: %s)\n", t->desc->name, filename); + ret = mmap_full_file(filename, flags & MAP_TBL_FL_MAP_RDONLY? + O_RDONLY : O_RDWR, &t->index_map); + free(filename); + if (ret < 0) + return ret; + if (flags & MAP_TBL_FL_VERIFY_INDEX) { + ret = compare_table_descriptions(t); + if (ret < 0) + goto err; + } + ret = -E_BUSY; + if (!(flags & MAP_TBL_FL_IGNORE_DIRTY)) { + if (table_is_dirty(t)) { + PARA_ERROR_LOG("%s is dirty\n", t->desc->name); + goto err; + } + } + mark_table_dirty(t); + num_rows = table_num_rows(t); + if (!num_rows) + return num_rows; + /* map data files */ + FOR_EACH_MAPPED_COLUMN(i, t, cd) { + struct stat statbuf; + filename = column_filename(t, i); + ret = -E_STAT; + if (stat(filename, &statbuf) < 0) { + free(filename); + goto err; + } + if (!(S_IFREG & statbuf.st_mode)) { + free(filename); + goto err; + } + ret = mmap_full_file(filename, O_RDWR, + &t->columns[i].data_map); + free(filename); + if (ret < 0) + goto err; + } + return num_rows; +err: /* unmap what is already mapped */ + for (i--; i >= 0; i--) { + struct osl_object map = t->columns[i].data_map; + para_munmap(map.data, map.size); + map.data = NULL; + } + para_munmap(t->index_map.data, t->index_map.size); + t->index_map.data = NULL; + return ret; +} + +/** + * Retrieve a mapped object by row and column number. + * + * \param t Pointer to an open osl table. + * \param col_num Number of the mapped column containing the object to retrieve. + * \param row_num Number of the row containing the object to retrieve. + * \param obj The result is returned here. + * + * It is considered an error if \a col_num does not refer to a column + * of storage type \p OSL_MAPPED_STORAGE. + * + * \return Positive on success, negative on errors. Possible errors include: + * \p E_BAD_ID, \p E_INVALID_OBJECT. + * + * \sa osl_storage_type. + */ +int get_mapped_object(const struct osl_table *t, unsigned col_num, + uint32_t row_num, struct osl_object *obj) +{ + struct osl_column *col = &t->columns[col_num]; + uint32_t offset; + char *header; + unsigned char *index_entry; + int ret; + + if (t->num_rows <= row_num) + return -E_BAD_ID; + ret = get_index_entry(t, row_num, col_num, &index_entry); + if (ret < 0) + return ret; + offset = read_u32(index_entry); + obj->size = read_u32(index_entry + 4) - 1; + PARA_DEBUG_LOG("index_entry: %p\n", index_entry); + header = col->data_map.data + offset; + obj->data = header + 1; + if (read_u8(header) == 0xff) { + PARA_ERROR_LOG("col %d, size %ld, offset %ld\n", col_num, + obj->size, offset); + return -E_INVALID_OBJECT; + } + PARA_DEBUG_LOG("mapped obj row_num: %u, col %u, size: %d\n", row_num, + col_num, obj->size); + return 1; +} + +static int search_rbtree(const struct osl_object *obj, + const struct osl_table *t, unsigned col_num, + struct rb_node **result, struct rb_node ***rb_link) +{ + struct osl_column *col = &t->columns[col_num]; + struct rb_node **new = &col->rbtree.rb_node, *parent = NULL; + const struct osl_column_description *cd = + get_column_description(t->desc, col_num); + enum osl_storage_type st = cd->storage_type; + while (*new) { + struct osl_row *this_row = get_row_pointer(*new, + col->rbtree_num); + int ret; + struct osl_object this_obj; + parent = *new; + if (st == OSL_MAPPED_STORAGE) { + ret = get_mapped_object(t, col_num, this_row->id, + &this_obj); + if (ret < 0) + return ret; + } else + this_obj = this_row->volatile_objects[col->volatile_num]; + ret = cd->compare_function(obj, &this_obj); + if (!ret) { + if (result) + *result = get_rb_node_pointer(this_row, + col->rbtree_num); + return 1; + } + if (ret < 0) + new = &((*new)->rb_left); + else + new = &((*new)->rb_right); + } + if (result) + *result = parent; + if (rb_link) + *rb_link = new; + return -E_RB_KEY_NOT_FOUND; +} + +static int insert_rbtree(struct osl_table *t, unsigned col_num, + const struct osl_row *row, const struct osl_object *obj) +{ + struct rb_node *parent, **rb_link; + unsigned rbtree_num; + struct rb_node *n; + int ret = search_rbtree(obj, t, col_num, &parent, &rb_link); + + if (ret > 0) + return -E_RB_KEY_EXISTS; + rbtree_num = t->columns[col_num].rbtree_num; + n = get_rb_node_pointer(row, rbtree_num); + rb_link_node(n, parent, rb_link); + rb_insert_color(n, &t->columns[col_num].rbtree); + return 1; +} + +static void remove_rb_node(struct osl_table *t, unsigned col_num, + const struct osl_row *row) +{ + struct osl_column *col = &t->columns[col_num]; + const struct osl_column_description *cd = + get_column_description(t->desc, col_num); + enum osl_storage_flags sf = cd->storage_flags; + struct rb_node *victim, *splice_out_node, *tmp; + if (!(sf & OSL_RBTREE)) + return; + /* + * Which node is removed/spliced out actually depends on how many + * children the victim node has: If it has no children, it gets + * deleted. If it has one child, it gets spliced out. If it has two + * children, its successor (which has at most a right child) gets + * spliced out. + */ + victim = get_rb_node_pointer(row, col->rbtree_num); + if (victim->rb_left && victim->rb_right) + splice_out_node = rb_next(victim); + else + splice_out_node = victim; + /* Go up to the root and decrement the size of each node in the path. */ + for (tmp = splice_out_node; tmp; tmp = rb_parent(tmp)) + tmp->size--; + rb_erase(victim, &col->rbtree); +} + +static int add_row_to_rbtrees(struct osl_table *t, uint32_t id, + struct osl_object *volatile_objs, struct osl_row **row_ptr) +{ + unsigned i; + int ret; + struct osl_row *row = allocate_row(t->num_rbtrees); + const struct osl_column_description *cd; + + PARA_DEBUG_LOG("row: %p, id: %u\n", row, id); + row->id = id; + row->volatile_objects = volatile_objs; + FOR_EACH_RBTREE_COLUMN(i, t, cd) { + if (cd->storage_type == OSL_MAPPED_STORAGE) { + struct osl_object obj; + ret = get_mapped_object(t, i, id, &obj); + if (ret < 0) + goto err; + ret = insert_rbtree(t, i, row, &obj); + } else { /* volatile */ + const struct osl_object *obj + = volatile_objs + t->columns[i].volatile_num; + PARA_DEBUG_LOG("inserting %p\n", obj->data); + ret = insert_rbtree(t, i, row, obj); + } + if (ret < 0) + goto err; + } + if (row_ptr) + *row_ptr = row; + return 1; +err: /* rollback changes, i.e. remove added entries from rbtrees */ + while (i) + remove_rb_node(t, i--, row); + free(row); + return ret; +} + +static void free_volatile_objects(const struct osl_table *t, + enum osl_close_flags flags) +{ + int i, j; + struct rb_node *n; + struct osl_column *rb_col; + const struct osl_column_description *cd; + + if (!t->num_volatile_columns) + return; + /* find the first rbtree column (any will do) */ + FOR_EACH_RBTREE_COLUMN(i, t, cd) + break; + rb_col = t->columns + i; + /* walk that rbtree and free all volatile objects */ + for (n = rb_first(&rb_col->rbtree); n; n = rb_next(n)) { + struct osl_row *r = get_row_pointer(n, rb_col->rbtree_num); + if (flags & OSL_FREE_VOLATILE) + for (j = 0; j < t->num_volatile_columns; j++) + free(r->volatile_objects[j].data); + free(r->volatile_objects); + } +} + +/** + * Erase all rbtree nodes and free resources. + * + * \param t Pointer to an open osl table. + * + * This function is called by osl_close_table(). + */ +void clear_rbtrees(struct osl_table *t) +{ + const struct osl_column_description *cd; + unsigned i, rbtrees_cleared = 0; + + FOR_EACH_RBTREE_COLUMN(i, t, cd) { + struct osl_column *col = &t->columns[i]; + struct rb_node *n; + rbtrees_cleared++; + for (n = rb_first(&col->rbtree); n;) { + struct osl_row *r; + rb_erase(n, &col->rbtree); + if (rbtrees_cleared == t->num_rbtrees) { + r = get_row_pointer(n, col->rbtree_num); + n = rb_next(n); + free(r); + } else + n = rb_next(n); + } + } + +} + +/** + * Close an osl table. + * + * \param t Pointer to the table to be closed. + * \param flags Options for what should be cleaned up. + * + * If osl_open_table() succeeds, the resulting table pointer must later be + * passed to this function in order to flush all changes to the filesystem and + * to free the resources that were allocated by osl_open_table(). + * + * \return Positive on success, negative on errors. Possible errors: \p E_BAD_TABLE, + * errors returned by unmap_table(). + * + * \sa osl_open_table(), unmap_table(). + */ +int osl_close_table(struct osl_table *t, enum osl_close_flags flags) +{ + int ret; + + if (!t) + return -E_BAD_TABLE; + free_volatile_objects(t, flags); + clear_rbtrees(t); + ret = unmap_table(t, flags); + if (ret < 0) + PARA_ERROR_LOG("unmap_table failed: %d\n", ret); + free(t->columns); + free(t); + return ret; +} + +/** + * Find out whether the given row number corresponds to an invalid row. + * + * \param t Pointer to the osl table. + * \param row_num The number of the row in question. + * + * By definition, a row is considered invalid if all its index entries + * are invalid. + * + * \return Positive if \a row_num corresponds to an invalid row, + * zero if it corresponds to a valid row, negative on errors. + */ +int row_is_invalid(struct osl_table *t, uint32_t row_num) +{ + unsigned char *index_entry; + int i, ret = get_index_entry_start(t, row_num, &index_entry); + + if (ret < 0) + return ret; + for (i = 0; i < t->index_entry_size; i++) { + if (index_entry[i] != 0xff) + return 0; + } + PARA_INFO_LOG("row %d is invalid\n", row_num); + return 1; +} + +/** + * Invalidate a row of an osl table. + * + * \param t Pointer to an open osl table. + * \param row_num Number of the row to mark as invalid. + * + * This function marks each mapped object in the index entry of \a row as + * invalid. + * + * \return Positive on success, negative on errors. + */ +int mark_row_invalid(struct osl_table *t, uint32_t row_num) +{ + unsigned char *index_entry; + int i, ret = get_index_entry_start(t, row_num, &index_entry); + + PARA_INFO_LOG("marking row %d as invalid\n", row_num); + if (ret < 0) + return ret; + for (i = 0; i < t->index_entry_size; i++) + index_entry[i] = 0xff; + return 1; +} + +/** + * Initialize all rbtrees and compute number of invalid rows. + * + * \param t The table containing the rbtrees to be initialized. + * + * \return Positive on success, negative on errors. + */ +int init_rbtrees(struct osl_table *t) +{ + int i, ret; + const struct osl_column_description *cd; + + /* create rbtrees */ + FOR_EACH_RBTREE_COLUMN(i, t, cd) + t->columns[i].rbtree = RB_ROOT; + /* add valid rows to rbtrees */ + t->num_invalid_rows = 0; + for (i = 0; i < t->num_rows; i++) { + ret = row_is_invalid(t, i); + if (ret < 0) + return ret; + if (ret) { + t->num_invalid_rows++; + continue; + } + ret = add_row_to_rbtrees(t, i, NULL, NULL); + if (ret < 0) + return ret; + } + return 1; +} + +/** + * Open an osl table. + * + * Each osl table must be opened before its data can be accessed. + * + * \param table_desc Describes the table to be opened. + * \param result Contains a pointer to the open table on success. + * + * The table description given by \a desc should coincide with the + * description used at creation time. + * + * \return Positive on success, negative on errors. Possible errors include: + * errors returned by init_table_structure(), \p E_NOENT, \p E_STAT, \p \p + * E_NOTDIR, \p E_BAD_TABLE_DESC, \p E_BAD_DB_DIR, \p E_NO_COMPARE_FUNC, \p + * E_NO_COLUMN_NAME, errors returned by init_rbtrees(). + */ +int osl_open_table(const struct osl_table_description *table_desc, + struct osl_table **result) +{ + int i, ret; + struct osl_table *t; + const struct osl_column_description *cd; + + PARA_INFO_LOG("opening table %s\n", table_desc->name); + ret = init_table_structure(table_desc, &t); + if (ret < 0) + return ret; + FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) { + /* check if directory exists */ + char *dirname = column_filename(t, i); + struct stat statbuf; + ret = stat(dirname, &statbuf); + free(dirname); + if (ret < 0) { + if (errno == ENOENT) + ret = -E_NOENT; + else + ret = -E_STAT; + goto err; + } + ret = -E_NOTDIR; + if (!S_ISDIR(statbuf.st_mode)) + goto err; + } + ret = map_table(t, MAP_TBL_FL_VERIFY_INDEX); + if (ret < 0) + goto err; + t->num_rows = ret; + PARA_DEBUG_LOG("num rows: %d\n", t->num_rows); + ret = init_rbtrees(t); + if (ret < 0) { + osl_close_table(t, OSL_MARK_CLEAN); /* ignore further errors */ + return ret; + } + *result = t; + return 1; +err: + free(t->columns); + free(t); + return ret; +} + +static int create_disk_storage_object_dir(const struct osl_table *t, + unsigned col_num, const char *ds_name) +{ + char *dirname; + int ret; + + if (!(t->desc->flags & OSL_LARGE_TABLE)) + return 1; + dirname = disk_storage_dirname(t, col_num, ds_name); + ret = para_mkdir(dirname, 0777); + free(dirname); + if (ret < 0 && ret != -E_EXIST) + return ret; + return 1; +} + +static int write_disk_storage_file(const struct osl_table *t, unsigned col_num, + const struct osl_object *obj, const char *ds_name) +{ + int ret; + char *filename; + + ret = create_disk_storage_object_dir(t, col_num, ds_name); + if (ret < 0) + return ret; + filename = disk_storage_path(t, col_num, ds_name); + ret = para_write_file(filename, obj->data, obj->size); + free(filename); + return ret; +} + +static int append_map_file(const struct osl_table *t, unsigned col_num, + const struct osl_object *obj, uint32_t *new_size) +{ + char *filename = column_filename(t, col_num); + int ret; + char header = 0; /* zero means valid object */ + + PARA_DEBUG_LOG("appending %ld + 1 byte\n", obj->size); + ret = append_file(filename, &header, 1, obj->data, obj->size, + new_size); + free(filename); + return ret; +} + +static int append_index_entry(const struct osl_table *t, char *new_index_entry) +{ + char *filename; + int ret; + + if (!t->num_mapped_columns) + return 1; + filename = index_filename(t->desc); + PARA_DEBUG_LOG("appending %ld bytes\n", t->index_entry_size); + ret = append_file(filename, NULL, 0, new_index_entry, + t->index_entry_size, NULL); + free(filename); + return ret; +} + +/** + * A wrapper for truncate(2) + * + * \param path Name of the regular file to truncate + * \param size Number of bytes to \b shave \b off + * + * Truncate the regular file named by \a path by \a size bytes. + * + * \return Positive on success, negative on errors. Possible errors include: \p + * E_STAT, \p E_BAD_SIZE, \p E_TRUNC. + * + * \sa truncate(2) + */ +int para_truncate(const char *path, off_t size) +{ + int ret; + struct stat statbuf; + + ret = -E_STAT; + if (stat(path, &statbuf) < 0) + goto out; + ret = -E_BAD_SIZE; + if (statbuf.st_size < size) + goto out; + ret = -E_TRUNC; + if (truncate(path, statbuf.st_size - size) < 0) + goto out; + ret = 1; +out: + return ret; +} + +static int truncate_mapped_file(const struct osl_table *t, unsigned col_num, + off_t size) +{ + char *filename = column_filename(t, col_num); + int ret = para_truncate(filename, size); + free(filename); + return ret; +} + +static int delete_disk_storage_file(const struct osl_table *t, unsigned col_num, + const char *ds_name) +{ + char *dirname, *filename = disk_storage_path(t, col_num, ds_name); + int ret = unlink(filename); + + PARA_INFO_LOG("deleted %s\n", filename); + free(filename); + if (ret < 0) { + if (errno == ENOENT) + return -E_NOENT; + return -E_UNLINK; + } + if (!(t->desc->flags & OSL_LARGE_TABLE)) + return 1; + dirname = disk_storage_dirname(t, col_num, ds_name); + rmdir(dirname); + free(dirname); + return 1; +} + +/** + * Add a new row to an osl table and retrieve this row. + * + * \param t Pointer to an open osl table. + * \param objects Array of objects to be added. + * \param row Result pointer. + * + * The \a objects parameter must point to an array containing one object per + * column. The order of the objects in the array is given by the table + * description of \a table. Several sanity checks are performed during object + * insertion and the function returns without modifying the table if any of + * these tests fail. In fact, it is atomic in the sense that it either + * succeeds or leaves the table unchanged (i.e. either all or none of the + * objects are added to the table). + * + * It is considered an error if an object is added to a column with associated + * rbtree if this object is equal to an object already contained in that column + * (i.e. the compare function for the column's rbtree returns zero). + * + * Possible errors include: \p E_RB_KEY_EXISTS, \p E_BAD_DATA_SIZE. + * + * \return Positive on success, negative on errors. + * + * \sa struct osl_table_description, osl_compare_func, osl_add_row(). + */ +int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects, + struct osl_row **row) +{ + int i, ret; + char *ds_name = NULL; + struct rb_node **rb_parents = NULL, ***rb_links = NULL; + char *new_index_entry = NULL; + struct osl_object *volatile_objs = NULL; + const struct osl_column_description *cd; + + if (!t) + return -E_BAD_TABLE; + rb_parents = para_malloc(t->num_rbtrees * sizeof(struct rn_node*)); + rb_links = para_malloc(t->num_rbtrees * sizeof(struct rn_node**)); + if (t->num_mapped_columns) + new_index_entry = para_malloc(t->index_entry_size); + /* pass 1: sanity checks */ + PARA_DEBUG_LOG("sanity tests: %p:%p\n", objects[0].data, + objects[1].data); + FOR_EACH_COLUMN(i, t->desc, cd) { + enum osl_storage_type st = cd->storage_type; + enum osl_storage_flags sf = cd->storage_flags; + +// ret = -E_NULL_OBJECT; +// if (!objects[i]) +// goto out; + if (st == OSL_DISK_STORAGE) + continue; + if (sf & OSL_RBTREE) { + unsigned rbtree_num = t->columns[i].rbtree_num; + ret = -E_RB_KEY_EXISTS; + PARA_DEBUG_LOG("checking whether %p exists\n", + objects[i].data); + if (search_rbtree(objects + i, t, i, + &rb_parents[rbtree_num], + &rb_links[rbtree_num]) > 0) + goto out; + } + if (sf & OSL_FIXED_SIZE) { + PARA_DEBUG_LOG("fixed size. need: %d, have: %d\n", + objects[i].size, cd->data_size); + ret = -E_BAD_DATA_SIZE; + if (objects[i].size != cd->data_size) + goto out; + } + } + if (t->num_disk_storage_columns) + ds_name = disk_storage_name_of_object(t, + &objects[t->disk_storage_name_column]); + ret = unmap_table(t, OSL_MARK_CLEAN); + if (ret < 0) + goto out; + PARA_DEBUG_LOG("sanity tests passed%s\n", ""); + /* pass 2: create data files, append map data */ + FOR_EACH_COLUMN(i, t->desc, cd) { + enum osl_storage_type st = cd->storage_type; + if (st == OSL_NO_STORAGE) + continue; + if (st == OSL_MAPPED_STORAGE) { + uint32_t new_size; + struct osl_column *col = &t->columns[i]; + PARA_DEBUG_LOG("appending object of size %ld\n", + objects[i].size); + ret = append_map_file(t, i, objects + i, &new_size); + if (ret < 0) + goto rollback; + update_index_entry(new_index_entry, col, new_size, + objects[i].size); + continue; + } + /* DISK_STORAGE */ + ret = write_disk_storage_file(t, i, objects + i, ds_name); + if (ret < 0) + goto rollback; + } + ret = append_index_entry(t, new_index_entry); + if (ret < 0) + goto rollback; + ret = map_table(t, MAP_TBL_FL_VERIFY_INDEX); + if (ret < 0) { /* truncate index and rollback changes */ + char *filename = index_filename(t->desc); + para_truncate(filename, t->index_entry_size); + free(filename); + goto rollback; + } + /* pass 3: add entry to rbtrees */ + if (t->num_volatile_columns) { + volatile_objs = para_calloc(t->num_volatile_columns + * sizeof(struct osl_object)); + FOR_EACH_VOLATILE_COLUMN(i, t, cd) + volatile_objs[t->columns[i].volatile_num] = objects[i]; + } + t->num_rows++; + PARA_DEBUG_LOG("adding new entry as row #%d\n", t->num_rows - 1); + ret = add_row_to_rbtrees(t, t->num_rows - 1, volatile_objs, row); + if (ret < 0) + goto out; + PARA_DEBUG_LOG("added new entry as row #%d\n", t->num_rows - 1); + ret = 1; + goto out; +rollback: /* rollback all changes made, ignore further errors */ + for (i--; i >= 0; i--) { + cd = get_column_description(t->desc, i); + enum osl_storage_type st = cd->storage_type; + if (st == OSL_NO_STORAGE) + continue; + + if (st == OSL_MAPPED_STORAGE) + truncate_mapped_file(t, i, objects[i].size); + else /* disk storage */ + delete_disk_storage_file(t, i, ds_name); + } + /* ignore error and return previous error value */ + map_table(t, MAP_TBL_FL_VERIFY_INDEX); +out: + free(new_index_entry); + free(ds_name); + free(rb_parents); + free(rb_links); + return ret; +} + +/** + * Add a new row to an osl table. + * + * \param t Same meaning as osl_add_and_get_row(). + * \param objects Same meaning as osl_add_and_get_row(). + * + * \return The return value of the underlying call to osl_add_and_get_row(). + * + * This is equivalent to osl_add_and_get_row(t, objects, NULL). + */ +int osl_add_row(struct osl_table *t, struct osl_object *objects) +{ + return osl_add_and_get_row(t, objects, NULL); +} + +/** + * Retrieve an object identified by row and column + * + * \param t Pointer to an open osl table. + * \param r Pointer to the row. + * \param col_num The column number. + * \param object The result pointer. + * + * The column determined by \a col_num must be of type \p OSL_MAPPED_STORAGE + * or \p OSL_NO_STORAGE, i.e. no disk storage objects may be retrieved by this + * function. + * + * \return Positive if object was found, negative on errors. Possible errors + * include: \p E_BAD_TABLE, \p E_BAD_STORAGE_TYPE. + * + * \sa osl_storage_type, osl_open_disk_object(). + */ +int osl_get_object(const struct osl_table *t, const struct osl_row *r, + unsigned col_num, struct osl_object *object) +{ + const struct osl_column_description *cd; + + if (!t) + return -E_BAD_TABLE; + cd = get_column_description(t->desc, col_num); + /* col must not be disk storage */ + if (cd->storage_type == OSL_DISK_STORAGE) + return -E_BAD_STORAGE_TYPE; + if (cd->storage_type == OSL_MAPPED_STORAGE) + return get_mapped_object(t, col_num, r->id, object); + /* volatile */ + *object = r->volatile_objects[t->columns[col_num].volatile_num]; + return 1; +} + +static int mark_mapped_object_invalid(const struct osl_table *t, uint32_t id, + unsigned col_num) +{ + struct osl_object obj; + char *p; + int ret = get_mapped_object(t, col_num, id, &obj); + + if (ret < 0) + return ret; + p = obj.data; + p--; + *p = 0xff; + return 1; +} + +/** + * Delete a row from an osl table. + * + * \param t Pointer to an open osl table. + * \param row Pointer to the row to delete. + * + * This removes all disk storage objects, removes all rbtree nodes, and frees + * all volatile objects belonging to the given row. For mapped columns, the + * data is merely marked invalid and may be pruned from time to time by + * para_fsck. + * + * \return Positive on success, negative on errors. Possible errors include: + * \p E_BAD_TABLE, errors returned by osl_get_object(). + */ +int osl_del_row(struct osl_table *t, struct osl_row *row) +{ + struct osl_row *r = row; + int i, ret; + const struct osl_column_description *cd; + + if (!t) + return -E_BAD_TABLE; + PARA_INFO_LOG("deleting row %p\n", row); + + if (t->num_disk_storage_columns) { + char *ds_name; + ret = disk_storage_name_of_row(t, r, &ds_name); + if (ret < 0) + goto out; + FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) + delete_disk_storage_file(t, i, ds_name); + free(ds_name); + } + FOR_EACH_COLUMN(i, t->desc, cd) { + struct osl_column *col = t->columns + i; + enum osl_storage_type st = cd->storage_type; + remove_rb_node(t, i, r); + if (st == OSL_MAPPED_STORAGE) { + mark_mapped_object_invalid(t, r->id, i); + continue; + } + if (st == OSL_NO_STORAGE) + free(r->volatile_objects[col->volatile_num].data); + } + if (t->num_mapped_columns) { + ret = mark_row_invalid(t, r->id); + if (ret < 0) + goto out; + t->num_invalid_rows++; + } else + t->num_rows--; + ret = 1; +out: + free(r->volatile_objects); + free(r); + return ret; +} + +/* test if column has an rbtree */ +static int check_rbtree_col(const struct osl_table *t, unsigned col_num, + struct osl_column **col) +{ + if (!t) + return -E_BAD_TABLE; + if (!(get_column_description(t->desc, col_num)->storage_flags & OSL_RBTREE)) + return -E_BAD_STORAGE_FLAGS; + *col = t->columns + col_num; + return 1; +} + +/** + * Get the row that contains the given object. + * + * \param t Pointer to an open osl table. + * \param col_num The number of the column to be searched. + * \param obj The object to be looked up. + * \param result Points to the row containing \a obj. + * + * Lookup \a obj in \a t and return the row containing \a obj. The column + * specified by \a col_num must have an associated rbtree. + * + * \return Positive on success, negative on errors. If an error occured, \a + * result is set to \p NULL. Possible errors include: \p E_BAD_TABLE, \p + * E_BAD_STORAGE_FLAGS, errors returned by get_mapped_object(), \p + * E_RB_KEY_NOT_FOUND. + * + * \sa osl_storage_flags + */ +int osl_get_row(const struct osl_table *t, unsigned col_num, + const struct osl_object *obj, struct osl_row **result) +{ + int ret; + struct rb_node *node; + struct osl_row *row; + struct osl_column *col; + + *result = NULL; + ret = check_rbtree_col(t, col_num, &col); + if (ret < 0) + return ret; + ret = search_rbtree(obj, t, col_num, &node, NULL); + if (ret < 0) + return ret; + row = get_row_pointer(node, t->columns[col_num].rbtree_num); + *result = row; + return 1; +} + +static int rbtree_loop(struct osl_column *col, void *private_data, + osl_rbtree_loop_func *func) +{ + struct rb_node *n; + + for (n = rb_first(&col->rbtree); n; n = rb_next(n)) { + struct osl_row *r = get_row_pointer(n, col->rbtree_num); + int ret = func(r, private_data); + if (ret < 0) + return ret; + } + return 1; +} + +static int rbtree_loop_reverse(struct osl_column *col, void *private_data, + osl_rbtree_loop_func *func) +{ + struct rb_node *n; + + for (n = rb_last(&col->rbtree); n; n = rb_prev(n)) { + struct osl_row *r = get_row_pointer(n, col->rbtree_num); + int ret = func(r, private_data); + if (ret < 0) + return ret; + } + return 1; +} + +/** + * Loop over all nodes in an rbtree. + * + * \param t Pointer to an open osl table. + * \param col_num The column to use for iterating over the elements. + * \param private_data Pointer that gets passed to \a func. + * \param func The function to be called for each node in the rbtree. + * + * This function does an in-order walk of the rbtree associated with \a + * col_num. It is an error if the \p OSL_RBTREE flag is not set for this + * column. For each node in the rbtree, the given function \a func is called + * with two \p void* pointers as arguments: The first argument points to the + * row that contains the object corresponding to the rbtree node currently + * traversed, and the \a private_data pointer is passed to \a func as the + * second argument. The loop terminates either if \a func returns a negative + * value, or if all nodes of the tree have been visited. + * + * + * \return Positive on success, negative on errors. If the termination of the + * loop was caused by \a func returning a negative value, this value is + * returned. + * + * \sa osl_storage_flags, osl_rbtree_loop_reverse(), osl_compare_func. + */ +int osl_rbtree_loop(const struct osl_table *t, unsigned col_num, + void *private_data, osl_rbtree_loop_func *func) +{ + struct osl_column *col; + + int ret = check_rbtree_col(t, col_num, &col); + if (ret < 0) + return ret; + return rbtree_loop(col, private_data, func); +} + +/** + * Loop over all nodes in an rbtree in reverse order. + * + * \param t Identical meaning as in \p osl_rbtree_loop(). + * \param col_num Identical meaning as in \p osl_rbtree_loop(). + * \param private_data Identical meaning as in \p osl_rbtree_loop(). + * \param func Identical meaning as in \p osl_rbtree_loop(). + * + * This function is identical to \p osl_rbtree_loop(), the only difference + * is that the tree is walked in reverse order. + * + * \return The same return value as \p osl_rbtree_loop(). + * + * \sa osl_rbtree_loop(). + */ +int osl_rbtree_loop_reverse(const struct osl_table *t, unsigned col_num, + void *private_data, osl_rbtree_loop_func *func) +{ + struct osl_column *col; + + int ret = check_rbtree_col(t, col_num, &col); + if (ret < 0) + return ret; + return rbtree_loop_reverse(col, private_data, func); +} + +/* TODO: Rollback changes on errors */ +static int rename_disk_storage_objects(struct osl_table *t, + struct osl_object *old_obj, struct osl_object *new_obj) +{ + int i, ret; + const struct osl_column_description *cd; + char *old_ds_name, *new_ds_name; + + if (!t->num_disk_storage_columns) + return 1; /* nothing to do */ + if (old_obj->size == new_obj->size && !memcmp(new_obj->data, + old_obj->data, new_obj->size)) + return 1; /* object did not change */ + old_ds_name = disk_storage_name_of_object(t, old_obj); + new_ds_name = disk_storage_name_of_object(t, new_obj); + FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) { + char *old_filename, *new_filename; + ret = create_disk_storage_object_dir(t, i, new_ds_name); + if (ret < 0) + goto out; + old_filename = disk_storage_path(t, i, old_ds_name); + new_filename = disk_storage_path(t, i, new_ds_name); + ret = para_rename(old_filename, new_filename); + free(old_filename); + free(new_filename); + if (ret < 0) + goto out; + } + ret = 1; +out: + free(old_ds_name); + free(new_ds_name); + return ret; + +} + +/** + * Change an object in an osl table. + * + * \param t Pointer to an open osl table. + * \param r Pointer to the row containing the object to be updated. + * \param col_num Number of the column containing the object to be updated. + * \param obj Pointer to the replacement object. + * + * This function gets rid of all references to the old object. This includes + * removal of the rbtree node in case there is an rbtree associated with \a + * col_num. It then inserts \a obj into the table and the rbtree if neccessary. + * + * If the \p OSL_RBTREE flag is set for \a col_num, you \b MUST call this + * function in order to change the contents of an object, even for volatile or + * mapped columns of constant size (which may be updated directly if \p + * OSL_RBTREE is not set). Otherwise the rbtree might become corrupted. + * + * \return Positive on success, negative on errors. Possible errors include: \p + * E_BAD_TABLE, \p E_RB_KEY_EXISTS, \p E_BAD_SIZE, \p E_NOENT, \p E_UNLINK, + * errors returned by para_write_file(), \p E_MKDIR. + */ +int osl_update_object(struct osl_table *t, const struct osl_row *r, + unsigned col_num, struct osl_object *obj) +{ + struct osl_column *col; + const struct osl_column_description *cd; + int ret; + + if (!t) + return -E_BAD_TABLE; + col = &t->columns[col_num]; + cd = get_column_description(t->desc, col_num); + if (cd->storage_flags & OSL_RBTREE) { + if (search_rbtree(obj, t, col_num, NULL, NULL) > 0) + return -E_RB_KEY_EXISTS; + } + if (cd->storage_flags & OSL_FIXED_SIZE) { + if (obj->size != cd->data_size) + return -E_BAD_SIZE; + } + remove_rb_node(t, col_num, r); + if (cd->storage_type == OSL_NO_STORAGE) { /* TODO: If fixed size, reuse object? */ + free(r->volatile_objects[col->volatile_num].data); + r->volatile_objects[col->volatile_num] = *obj; + } else if (cd->storage_type == OSL_DISK_STORAGE) { + char *ds_name; + ret = disk_storage_name_of_row(t, r, &ds_name); + if (ret < 0) + return ret; + ret = delete_disk_storage_file(t, col_num, ds_name); + if (ret < 0 && ret != -E_NOENT) { + free(ds_name); + return ret; + } + ret = write_disk_storage_file(t, col_num, obj, ds_name); + free(ds_name); + if (ret < 0) + return ret; + } else { /* mapped storage */ + struct osl_object old_obj; + ret = get_mapped_object(t, col_num, r->id, &old_obj); + if (ret < 0) + return ret; + /* + * If the updated column is the disk storage name column, the + * disk storage name changes, so we have to rename all disk + * storage objects accordingly. + */ + if (col_num == t->disk_storage_name_column) { + ret = rename_disk_storage_objects(t, &old_obj, obj); + if (ret < 0) + return ret; + } + if (cd->storage_flags & OSL_FIXED_SIZE) + memcpy(old_obj.data, obj->data, cd->data_size); + else { /* TODO: if the size doesn't change, use old space */ + uint32_t new_data_map_size; + unsigned char *index_entry; + ret = get_index_entry_start(t, r->id, &index_entry); + if (ret < 0) + return ret; + ret = mark_mapped_object_invalid(t, r->id, col_num); + if (ret < 0) + return ret; + ret = append_map_file(t, col_num, obj, + &new_data_map_size); + if (ret < 0) + return ret; + update_index_entry(index_entry, col, new_data_map_size, + obj->size); + } + } + if (cd->storage_flags & OSL_RBTREE) { + ret = insert_rbtree(t, col_num, r, obj); + if (ret < 0) + return ret; + } + return 1; +} + +/** + * Retrieve an object of type \p OSL_DISK_STORAGE by row and column. + * + * \param t Pointer to an open osl table. + * \param r Pointer to the row containing the object. + * \param col_num The column number. + * \param obj Points to the result upon successful return. + * + * For columns of type \p OSL_DISK_STORAGE, this function must be used to + * retrieve one of its containing objects. Afterwards, osl_close_disk_object() + * must be called in order to deallocate the resources. + * + * \return Positive on success, negative on errors. Possible errors include: + * \p E_BAD_TABLE, \p E_BAD_STORAGE_TYPE, errors returned by osl_get_object(). + * + * \sa osl_get_object(), osl_storage_type, osl_close_disk_object(). + */ +int osl_open_disk_object(const struct osl_table *t, const struct osl_row *r, + unsigned col_num, struct osl_object *obj) +{ + const struct osl_column_description *cd; + char *ds_name, *filename; + int ret; + + if (!t) + return -E_BAD_TABLE; + cd = get_column_description(t->desc, col_num); + if (cd->storage_type != OSL_DISK_STORAGE) + return -E_BAD_STORAGE_TYPE; + + ret = disk_storage_name_of_row(t, r, &ds_name); + if (ret < 0) + return ret; + filename = disk_storage_path(t, col_num, ds_name); + free(ds_name); + PARA_DEBUG_LOG("filename: %s\n", filename); + ret = mmap_full_file(filename, O_RDONLY, obj); + free(filename); + return ret; +} + +/** + * Free resources that were allocated during osl_open_disk_object(). + * + * \param obj Pointer to the object previously returned by open_disk_object(). + * + * \return The return value of the underlying call to para_munmap(). + * + * \sa para_munmap(). + */ +int osl_close_disk_object(struct osl_object *obj) +{ + return para_munmap(obj->data, obj->size); +} + +/** + * Get the number of rows of the given table. + * + * \param t Pointer to an open osl table. + * \param num_rows Result is returned here. + * + * The number of rows returned via \a num_rows excluding any invalid rows. + * + * \return Positive on success, \p -E_BAD_TABLE if \a t is \p NULL. + */ +int osl_get_num_rows(const struct osl_table *t, unsigned *num_rows) +{ + if (!t) + return -E_BAD_TABLE; + assert(t->num_rows >= t->num_invalid_rows); + *num_rows = t->num_rows - t->num_invalid_rows; + return 1; +} + +/** + * Get the rank of a row. + * + * \param t An open osl table. + * \param r The row to get the rank of. + * \param col_num The number of an rbtree column. + * \param rank Result pointer. + * + * The rank is, by definition, the position of the row in the linear order + * determined by an inorder tree walk of the rbtree associated with column + * number \a col_num of \a table. + * + * \return Positive on success, negative on errors. + * + * \sa osl_get_nth_row(). + */ +int osl_get_rank(const struct osl_table *t, struct osl_row *r, + unsigned col_num, unsigned *rank) +{ + struct osl_object obj; + struct osl_column *col; + struct rb_node *node; + int ret = check_rbtree_col(t, col_num, &col); + + if (ret < 0) + return ret; + ret = osl_get_object(t, r, col_num, &obj); + if (ret < 0) + return ret; + ret = search_rbtree(&obj, t, col_num, &node, NULL); + if (ret < 0) + return ret; + ret = rb_rank(node, rank); + if (ret < 0) + return -E_BAD_ROW; + return 1; +} + +/** + * Get the row with n-th greatest value. + * + * \param t Pointer to an open osl table. + * \param col_num The column number. + * \param n The rank of the desired row. + * \param result Row is returned here. + * + * Retrieve the n-th order statistic with respect to the compare function + * of the rbtree column \a col_num. In other words, get that row with + * \a n th greatest value in column \a col_num. It's an error if + * \a col_num is not a rbtree column, or if \a n is larger than the + * number of rows in the table. + * + * \return Positive on success, negative on errors. Possible errors: + * \p E_BAD_TABLE, \p E_BAD_STORAGE_FLAGS, \p E_RB_KEY_NOT_FOUND. + * + * \sa osl_storage_flags, osl_compare_func, osl_get_row(), + * osl_rbtree_last_row(), osl_rbtree_first_row(), osl_get_rank(). + */ +int osl_get_nth_row(const struct osl_table *t, unsigned col_num, + unsigned n, struct osl_row **result) +{ + struct osl_column *col; + struct rb_node *node; + int ret = check_rbtree_col(t, col_num, &col); + + if (ret < 0) + return ret; + node = rb_nth(col->rbtree.rb_node, n); + if (!node) + return -E_RB_KEY_NOT_FOUND; + *result = get_row_pointer(node, col->rbtree_num); + return 1; +} + +/** + * Get the row corresponding to the smallest rbtree node of a column. + * + * \param t An open rbtree table. + * \param col_num The number of the rbtree column. + * \param result A pointer to the first row is returned here. + * + * The rbtree node of the smallest object (with respect to the corresponding + * compare function) is selected and the row containing this object is + * returned. It is an error if \a col_num refers to a column without an + * associated rbtree. + * + * \return Positive on success, negative on errors. + * + * \sa osl_get_nth_row(), osl_rbtree_last_row(). + */ +int osl_rbtree_first_row(const struct osl_table *t, unsigned col_num, + struct osl_row **result) +{ + return osl_get_nth_row(t, col_num, 1, result); +} + +/** + * Get the row corresponding to the greatest rbtree node of a column. + * + * \param t The same meaning as in \p osl_rbtree_first_row(). + * \param col_num The same meaning as in \p osl_rbtree_first_row(). + * \param result The same meaning as in \p osl_rbtree_first_row(). + * + * This function works just like osl_rbtree_first_row(), the only difference + * is that the row containing the greatest rather than the smallest object is + * returned. + * + * \return Positive on success, negative on errors. + * + * \sa osl_get_nth_row(), osl_rbtree_first_row(). + */ +int osl_rbtree_last_row(const struct osl_table *t, unsigned col_num, + struct osl_row **result) +{ + unsigned num_rows; + int ret = osl_get_num_rows(t, &num_rows); + + if (ret < 0) + return ret; + return osl_get_nth_row(t, col_num, num_rows, result); +} diff --git a/osl.h b/osl.h new file mode 100644 index 00000000..50570ee1 --- /dev/null +++ b/osl.h @@ -0,0 +1,206 @@ +#include +/* + * Copyright (C) 2007 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file osl.h User interface for the object storage layer. */ + +/** decribes an object of the object storage layer (osl) */ +struct osl_object { + /** Pointer to the data of the object. */ + void *data; + /** The object's size. */ + off_t size; +}; + +/** Flags that change the internal handling of osl tables. */ +enum osl_table_flags { + /** This table will have many rows. */ + OSL_LARGE_TABLE = 1 +}; + +/** The three different types of storage for an osl column */ +enum osl_storage_type { + /** + * All data for this column is stored in one file which gets mmapped by + * osl_open_table(). This is suitable for columns that do not hold much + * data. + */ + OSL_MAPPED_STORAGE, + /** + * Each entry is stored on disk and is loaded on demand by + * open_disk_object(). This is the preferable storage type for large + * objects that need not be in memory all the time. + */ + OSL_DISK_STORAGE, + /** + * Objects for columns of this type are volatile: They are only stored + * in memory and are discarded once the table gets closed. + */ + OSL_NO_STORAGE +}; + +/** + * Additional per-column flags + */ +enum osl_storage_flags { + /** + * Build an rbtree for this column. This is only possible if the + * storage type of the column is either \a OSL_MAPPED_STORAGE or \a + * OSL_NO_STORAGE. In order to lookup objects in the table by using \a + * osl_get_row(), the lookup column must have an associated rbtree. + * + * \sa osl_storage_type, osl_get_row() + */ + OSL_RBTREE = 1, + /** The data for this column will have constant size. */ + OSL_FIXED_SIZE = 2, + /** All values of this column will be different. */ + OSL_UNIQUE = 4 +}; + +struct osl_table; +struct osl_row; + +/** + * In order to build up an rbtree a compare function for the objects must be + * specified. Such a function always takes pointers to the two objects to be + * compared. It must return -1, zero, or 1, if the first argument is considered + * to be respectively less than, equal to, or greater than the second. If two + * members compare as equal, their order in the sorted array is undefined. + */ +typedef int osl_compare_func(const struct osl_object *obj1, + const struct osl_object *obj2); +typedef int osl_rbtree_loop_func(struct osl_row *row, void *data); + +osl_compare_func osl_hash_compare, uint32_compare; + +/** + * Describes one column of a osl table. + */ +struct osl_column_description { + /** One of zje tree possible types of storage */ + enum osl_storage_type storage_type; + /** Specifies further properties of the column */ + enum osl_storage_flags storage_flags; + /** + * The column name determines the name of the directory where all data + * for this column will be stored. Its hash is stored in the table + * header. This field is ignored if the storage type is \a NO_STORAGE + */ + char *name; + /** + * For columns with an associated rbtree, this must point to a function + * that compares the values of two objects, either a builtin function + * or a function defined by the application may be supplied. This + * field is ignored if the column does not have an associated rbtree. + * + * \sa osl_storage_flags, osl_compare_func + */ + osl_compare_func *compare_function; + /** + * If the \a OSL_FIXED_SIZE flag is set for this column, this value + * determines the fixed size of all objects of this column. It is + * ignored, if \a OSL_FIXED_SIZE is not set. + */ + uint32_t data_size; +}; + +/** + * Describes one osl table. + */ +struct osl_table_description { + /** The directory which contains all files of this table. */ + const char *dir; + /** + * The table name. A subdirectory of \a dir called \a name is created + * at table creation time. It must be a valid name for a subdirectory. + * In particular, no slashes are allowed for \a name. + */ + const char *name; + /** The number of columns of this table. */ + uint16_t num_columns; + /** Further table-wide information. */ + enum osl_table_flags flags; + /** The array desribing the individual columns of the table. */ + struct osl_column_description *column_descriptions; +}; + +/** Flags to be passed to \a osl_close_table(). */ +enum osl_close_flags { + /** + * The table header contains a "dirty" flag which specifies whether + * the table is currently open by another process. This flag specifies + * that the dirty flag should be cleared before closing the table. + */ + OSL_MARK_CLEAN = 1, + /** + * If the table contains columns of type \a OSL_NO_STORAGE and this + * flag is passed to osl_close_table(), free(3) is called for each + * object of each column of type \a OSL_NO_STORAGE. + */ + OSL_FREE_VOLATILE = 2 +}; + + + +int osl_create_table(const struct osl_table_description *desc); +int osl_open_table(const struct osl_table_description *desc, + struct osl_table **result); +int osl_close_table(struct osl_table *t, enum osl_close_flags flags); +int osl_get_row(const struct osl_table *t, unsigned col_num, + const struct osl_object *obj, struct osl_row **result); +int osl_get_object(const struct osl_table *t, const struct osl_row *row, + unsigned col_num, struct osl_object *object); +int osl_open_disk_object(const struct osl_table *t, + const struct osl_row *r, unsigned col_num, struct osl_object *obj); +int osl_close_disk_object(struct osl_object *obj); +int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects, + struct osl_row **row); +int osl_add_row(struct osl_table *t, struct osl_object *objects); +int osl_del_row(struct osl_table *t, struct osl_row *row); +int osl_rbtree_loop(const struct osl_table *t, unsigned col_num, + void *private_data, osl_rbtree_loop_func *func); +int osl_rbtree_loop_reverse(const struct osl_table *t, unsigned col_num, + void *private_data, osl_rbtree_loop_func *func); +int osl_update_object(struct osl_table *t, const struct osl_row *r, + unsigned col_num, struct osl_object *obj); +int osl_get_num_rows(const struct osl_table *t, unsigned *num_rows); +int osl_rbtree_first_row(const struct osl_table *t, unsigned col_num, + struct osl_row **result); +int osl_rbtree_last_row(const struct osl_table *t, unsigned col_num, + struct osl_row **result); +int osl_get_nth_row(const struct osl_table *t, unsigned col_num, + unsigned n, struct osl_row **result); +int osl_get_rank(const struct osl_table *t, struct osl_row *r, + unsigned col_num, unsigned *rank); + +int for_each_file_in_dir(const char *dirname, + int (*func)(const char *, const void *), const void *private_data); +int para_open(const char *pathname, int flags, mode_t mode); +int mmap_full_file(const char *filename, int open_mode, struct osl_object *obj); +ssize_t para_write_all(int fd, const void *buf, size_t size); +int para_lseek(int fd, off_t *offset, int whence); +int para_write_file(const char *filename, const void *buf, size_t size); +int para_mkdir(const char *path, mode_t mode); + +/** + * A wrapper for munmap(2). + * + * \param start The start address of the memory mapping. + * \param length The size of the mapping. + * + * \return Positive on success, \p -E_MUNMAP on errors. + * + * \sa munmap(2), mmap_full_file(). + */ +_static_inline_ int para_munmap(void *start, size_t length) +{ + if (munmap(start, length) >= 0) + return 1; + PARA_ERROR_LOG("munmap (%p/%zu) failed: %s\n", start, length, + strerror(errno)); + return -E_MUNMAP; +} diff --git a/osl_core.h b/osl_core.h new file mode 100644 index 00000000..27b808a7 --- /dev/null +++ b/osl_core.h @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2007 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file osl_core.h Object storage layer details, not visible to users. */ + +#include "rbtree.h" +#include "osl.h" +#include "string.h" +#include "hash.h" + +/** Internal representation of a column of an osl table. */ +struct osl_column { + /** The memory mapping of this comumn (only used for mapped columns). */ + struct osl_object data_map; + /** The root of the rbtree (only used for columns with rbtrees). */ + struct rb_root rbtree; + /** The index in the array of rb nodes (only used for columns with rbtrees). */ + unsigned rbtree_num; + /** Index for volatile_objects of struct osl_row. */ + unsigned volatile_num; + /** + * Starting point of the data for this column within the index + * (only used for mapped columns). + */ + uint16_t index_offset; + /** + * The hash value of the name of this column. + * + * This is only used for mapped and disk storage columns). + */ + HASH_TYPE name_hash[HASH_SIZE]; +}; + +/** Internal representation of an osl table */ +struct osl_table { + /** Pointer to the table description */ + const struct osl_table_description *desc; + /** The size of the index header of this table. */ + uint16_t index_header_size; + /** Contains the mapping of the table's index file */ + struct osl_object index_map; + /** Total number of rows, including invalid rows. */ + unsigned num_rows; + /** Keeps track of the number of invalid rows in the table. */ + uint32_t num_invalid_rows; + /** Number of columns of type \p OSL_MAPPED_STORAGE. */ + unsigned num_mapped_columns; + /** Number of columns of type \p OSL_NO_STORAGE. */ + unsigned num_volatile_columns; + /** Number of columns of type \p OSL_DISK_STORAGE. */ + unsigned num_disk_storage_columns; + /** Number of columns with associated rbtree. */ + unsigned num_rbtrees; + /** + * The number of the column that determines the name of the disk + * storage objcts. + */ + unsigned disk_storage_name_column; + /** The number of bytes of an index entry of a row. */ + unsigned index_entry_size; + /** Pointer to the internal representation of the columns. */ + struct osl_column *columns; +}; + +/** Internal representation of a row of an osl table */ +struct osl_row { + /** The row number only present if there is at least one mapped column. */ + off_t id; + /** Array of size \a num_volatile_columns. */ + struct osl_object *volatile_objects; +}; + +int read_table_desc(struct osl_object *map, struct osl_table_description *desc); +int init_table_structure(const struct osl_table_description *desc, + struct osl_table **table_ptr); +int row_is_invalid(struct osl_table *t, uint32_t id); +int get_mapped_object(const struct osl_table *t, unsigned col_num, + uint32_t id, struct osl_object *obj); +int para_truncate(const char *filename, off_t size); +int unmap_table(struct osl_table *t, enum osl_close_flags flags); +int init_rbtrees(struct osl_table *t); + +/** + * Flags to be specified for map_table(). + * + * \sa map_table(). + */ +enum map_table_flags { + /** + * Check whether the entries in the table index match the entries in + * the table desctiption. + */ + MAP_TBL_FL_VERIFY_INDEX = 1, + /** Do not complain even if the dirty flag is set. */ + MAP_TBL_FL_IGNORE_DIRTY = 2, + /** Use read-only mappings. */ + MAP_TBL_FL_MAP_RDONLY = 4 +}; + +int map_table(struct osl_table *t, enum map_table_flags flags); +void clear_rbtrees(struct osl_table *t); +int mark_row_invalid(struct osl_table *t, uint32_t row_num); + + +/** + * Get the description of a column by column number + * + * \param d Pointer to the table description. + * \param col_num The number of the column to get the desctiption for. + * + * \return The table description. + * + * \sa struct osl_column_description. + */ +_static_inline_ struct osl_column_description *get_column_description( + const struct osl_table_description *d, unsigned col_num) +{ + return &d->column_descriptions[col_num]; +} + +/** + * Format of the header of the index file of an osl table. + * + * Bytes 16-31 are currently unused. + * + * \sa enum index_column_desc_offsets, HASH_SIZE, osl_table_flags. + */ +enum index_header_offsets { + /** Bytes 0-8: PARASLASH. */ + IDX_PARA_MAGIC = 0, + /** Byte 9: Dirty flag (nonzero if table is mapped). */ + IDX_DIRTY_FLAG = 9, + /** Byte 10: osl table version number. */ + IDX_VERSION = 10, + /** Byte 11: Table flags.*/ + IDX_TABLE_FLAGS = 11, + /** Byte 12,13: Number of columns. */ + IDX_NUM_COLUMNS, + /** Byte 14,15 Size of the index header. */ + IDX_HEADER_SIZE = 14, + /** Column descriptions start here. */ + IDX_COLUMN_DESCRIPTIONS = 32 +}; + +/** + * Format of the column description in the index header. + * + * \sa index_header_offsets. + */ +enum index_column_desc_offsets { + /** Bytes 0,1: Storage_type. */ + IDX_CD_STORAGE_TYPE = 0, + /** Bytes 2,3: Storage_flags. */ + IDX_CD_STORAGE_FLAGS = 2, + /** Bytes 4 - 7: Data_size (only used for fixed size columns). */ + IDX_CD_DATA_SIZE = 4, + /** Bytes 8 - ... Name of the column. */ + IDX_CD_NAME = 8, +}; + +/** Magic string contained in the header of the index file of each osl table. */ +#define PARA_MAGIC "PARASLASH" + +/** + * The minimal number of bytes for a column in the index header. + * + * The column name starts at byte IDX_CD_NAME and must at least contain one + * character, plus the terminating NULL byte. + */ +#define MIN_IDX_COLUMN_DESCRIPTION_SIZE (IDX_CD_NAME + 2) + +/** + * Get the number of bytes used for a column in the index header. + * + * \param name The name of the column. + * + * The amount of space used for a column in the index header of a table depends + * on the (length of the) name of the column. + * + * \return The total number of bytes needed to store one column of this name. + */ +_static_inline_ size_t index_column_description_size(const char *name) +{ + return MIN_IDX_COLUMN_DESCRIPTION_SIZE + strlen(name) - 1; +} + +#define CURRENT_TABLE_VERSION 1 +#define MIN_TABLE_VERSION 1 +#define MAX_TABLE_VERSION 1 +/** An index header must be at least that many bytes long. */ +#define MIN_INDEX_HEADER_SIZE(num_cols) (MIN_IDX_COLUMN_DESCRIPTION_SIZE \ + * num_cols + IDX_COLUMN_DESCRIPTIONS) + +/** + * Get the number of rows from the size of the memory mapping. + * + * \param t Pointer to an open table. + * + * \return The number of rows, including invalid rows. + */ +_static_inline_ unsigned table_num_rows(const struct osl_table *t) +{ + return (t->index_map.size - t->index_header_size) + / t->index_entry_size; +} + +/** + * Get the path of the index file from a table description. + * + * \param d The table description. + * + * \return The full path of the index file. Result must be freed by + * the caller. + */ +_static_inline_ char *index_filename(const struct osl_table_description *d) +{ + return make_message("%s/%s/index", d->dir, d->name); +} + +/** + * Get the path of storage of a column. + * + * \param t Pointer to an initialized table. + * \param col_num The number of the column to get the path for. + * + * \return The full path of the mapped data file (mapped storage) or the + * data directory (disk storage). Result must be freed by the caller. + * + * \sa osl_storage_type. + */ +_static_inline_ char *column_filename(const struct osl_table *t, unsigned col_num) +{ + char asc[2 * HASH_SIZE + 1]; + hash_to_asc(t->columns[col_num].name_hash, asc); + return make_message("%s/%s/%s", t->desc->dir, t->desc->name, asc); +} + +/** + * Get the start of an index entry + * + * \param t Pointer to a table which has been mapped. + * \param row_num The number of the row whose index entry should be retrieved. + * \param index_entry Result is returned here. + * + * \return Positive on success, \p -E_INDEX_CORRUPTION otherwise. + * + * \sa get_index_entry(). + */ +_static_inline_ int get_index_entry_start(const struct osl_table *t, uint32_t row_num, + unsigned char **index_entry) +{ + uint32_t index_offset; + index_offset = t->index_header_size + t->index_entry_size * row_num; + if (index_offset + 8 > t->index_map.size) { + *index_entry = NULL; + return -E_INDEX_CORRUPTION; + } + *index_entry = (unsigned char *)(t->index_map.data) + index_offset; + return 1; +} + +/** + * Get the index entry of a row/column pair. + * + * \param t Pointer to a table which has been mapped. + * \param row_num The number of the row whose index entry should be retrieved. + * \param col_num The number of the column whose index entry should be retrieved. + * \param index_entry Result pointer. + * + * \return Positive on success, \p -E_INDEX_CORRUPTION otherwise. + * + * \sa get_index_entry_start(). + */ +_static_inline_ int get_index_entry(const struct osl_table *t, uint32_t row_num, + uint32_t col_num, unsigned char **index_entry) +{ + int ret = get_index_entry_start(t, row_num, index_entry); + if (ret < 0) + return ret; + *index_entry += t->columns[col_num].index_offset; + return ret; +} + +/** + * Change an index entry of a column after object was added. + * + * \param index_entry_start This determines the row. + * \param col Pointer to the column. + * \param map_size The new size of the data file. + * \param object_size The size of the object just appended to the data file. + * + * This is called right after an object was appended to the data file for a + * mapped column. + * + * \sa get_index_entry_start(). + */ +_static_inline_ void update_index_entry(char *index_entry_start, struct osl_column *col, + uint32_t map_size, uint32_t object_size) +{ + write_u32(index_entry_start + col->index_offset, map_size - object_size - 1); + write_u32(index_entry_start + col->index_offset + 4, object_size + 1); +} + +/** + * Get the full path of a disk storage object + * + * \param t Pointer to an initialized table. + * \param col_num The number of the column the disk storage object belongs to. + * \param ds_name The disk storage name of the object. + * + * \return Pointer to the full path which must be freed by the caller. + * + * \sa column_filename(), disk_storage_name_of_row(). + */ +_static_inline_ char *disk_storage_path(const struct osl_table *t, + unsigned col_num, const char *ds_name) +{ + char *dirname = column_filename(t, col_num); + char *filename = make_message("%s/%s", dirname, ds_name); + free(dirname); + return filename; +} + +/** + * Get the column description of the next column of a given type. + * + * \param type the desired storage type. + * \param col_num the column to start the search. + * \param t the osl table. + * \param cd result is returned here. + * + * \return On success, \a cd contains the column description of the next column + * of type \a type, and the number of that column is returned. Otherwise, \a + * cd is set to \p NULL and the function returns t->num_columns + 1. + * + * \sa FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, FOR_EACH_RBTREE_COLUMN, + * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN, osl_storage_type. + */ +_static_inline_ int next_column_of_type(enum osl_storage_type type, int col_num, + const struct osl_table *t, + const struct osl_column_description **cd) +{ + *cd = NULL; + while (col_num < t->desc->num_columns) { + *cd = get_column_description(t->desc, col_num); + if ((*cd)->storage_type == type) + break; + col_num++; + } + return col_num; +} + +/** + * Find the next column with an associated rbtree. + * + * \param col_num The column to start the search. + * \param t The osl table. + * \param cd Result is returned here. + + * \return On success, \a cd contains the column description of the next column + * with associated rbtree, and the number of that column is returned. + * Otherwise, \a cd is set to \p NULL and the function returns t->num_columns + + * 1. + * + * \sa FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, FOR_EACH_RBTREE_COLUMN, + * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN, osl_storage_flags. + */ +_static_inline_ int next_rbtree_column(int col_num, const struct osl_table *t, + const struct osl_column_description **cd) +{ + *cd = NULL; + while (col_num < t->desc->num_columns) { + *cd = get_column_description(t->desc, col_num); + if ((*cd)->storage_flags & OSL_RBTREE) + break; + col_num++; + } + return col_num; +} + + +/* quite some dirty hacks */ + +/** Round up size of struct osl_row to multiple of 8 */ +#define RB_NODES_OFFSET ((sizeof(struct osl_row) + 7) / 8 * 8) + +/** + * Allocate a new osl row. + * + * \param num_rbtrees The number of rbtrees for this row. + * + * \return A pointer to a zeroed-out area suitable for holding an osl row + * with \a num_rbtrees rbtree columns. + */ +_static_inline_ struct osl_row *allocate_row(unsigned num_rbtrees) +{ + size_t s = RB_NODES_OFFSET + num_rbtrees * sizeof(struct rb_node); + return para_calloc(s); +} + +/** + * Compute the pointer to a rbtree node embedded in a osl row. + * + * \param row The row to extract the rbtree pointer from. + * \param rbtree_num The number of the rbtree node to extract. + * + * \return A pointer to the \a rbtree_num th node contained in \a row. + */ +_static_inline_ struct rb_node *get_rb_node_pointer(const struct osl_row *row, uint32_t rbtree_num) +{ + return ((struct rb_node *)(((char *)row) + RB_NODES_OFFSET)) + rbtree_num; +} + +/** + * Get a pointer to the struct containing the given rbtree node. + * + * \param node Pointer to the rbtree node. + * \param rbtree_num Number of \a node in the array of rbtree nodes. + * + * \return A pointer to the row containing \a node. + */ +_static_inline_ struct osl_row *get_row_pointer(const struct rb_node *node, + unsigned rbtree_num) +{ + node -= rbtree_num; + return (struct osl_row *)(((char *)node) - RB_NODES_OFFSET); +} + +/** + * Compute a cryptographic hash of an osl object. + * + * \param obj the Object to compute the hash value from. + * \param hash Result is returned here. + */ +static inline void hash_object(const struct osl_object *obj, HASH_TYPE *hash) +{ + return hash_function(obj->data, obj->size, hash); +} + +/** + * Get the relative path of an object, given the hash value. + * + * \param t Pointer to an initialized osl table. + * \param hash An arbitrary hash value. + * + * This function is typically called with \a hash being the hash of the object + * stored in the disk storage name column of a row. If the OSL_LARGE_TABLE + * flag is set, the first two hex digits get separated with a slash from the + * remaining digits. + * + * \return The relative path for all disk storage objects corresponding to \a + * hash. + * + * \sa struct osl_table:disk_storage_name_column. + */ +static inline char *disk_storage_name_of_hash(const struct osl_table *t, HASH_TYPE *hash) +{ + char asc[2 * HASH_SIZE + 2]; + + hash_to_asc(hash, asc); + if (t->desc->flags & OSL_LARGE_TABLE) + return make_message("%.2s/%s", asc, asc + 2); + return para_strdup(asc); +} + +/** + * A wrapper for rename(2). + * + * \param old_path The source path. + * \param new_path The destination path. + * + * \return positive in success, \p -E_RENAME on errors. + * + * \sa rename(2). + */ +static inline int para_rename(const char *old_path, const char *new_path) +{ + if (rename(old_path, new_path) < 0) + return -E_RENAME; + return 1; +} + +/** + * Iterate over each column of an initialized table. + * + * \param col A pointer to a struct osl_column. + * \param desc Pointer to the table description. + * \param cd Pointer to struct osl_column_description. + * + * On each iteration, \a col will point to the next column of the table and \a + * cd will point to the column description of this column. + * + * \sa struct osl_column FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, + * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN, + * FOR_EACH_VOLATILE_COLUMN. + */ +#define FOR_EACH_COLUMN(col, desc, cd) \ + for (col = 0; col < (desc)->num_columns && \ + (cd = get_column_description(desc, col)); col++) + +/** + * Iterate over each column with associated rbtree. + * + * \param col Same meaning as in FOR_EACH_COLUMN(). + * \param table Same meaning as in FOR_EACH_COLUMN(). + * \param cd Same meaning as in FOR_EACH_COLUMN(). + * + * \sa osl_storage_flags::OSL_RBTREE, FOR_EACH_COLUMN, FOR_EACH_COLUMN_OF_TYPE, + * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN, + * FOR_EACH_VOLATILE_COLUMN. + */ +#define FOR_EACH_RBTREE_COLUMN(col, table, cd) \ + for (col = next_rbtree_column(0, table, &cd); \ + col < table->desc->num_columns; \ + col = next_rbtree_column(++col, table, &cd)) + +/** + * Iterate over each column of given storage type. + * + * \param type The osl_storage_type to iterate over. + * \param col Same meaning as in FOR_EACH_COLUMN(). + * \param table Same meaning as in FOR_EACH_COLUMN(). + * \param cd Same meaning as in FOR_EACH_COLUMN(). + * + * \sa osl_storage_type, FOR_EACH_COLUMN, FOR_EACH_RBTREE_COLUMN, + * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN, + * FOR_EACH_VOLATILE_COLUMN. + */ +#define FOR_EACH_COLUMN_OF_TYPE(type, col, table, cd) \ + for (col = next_column_of_type(type, 0, table, &cd); \ + col < table->desc->num_columns; \ + col = next_column_of_type(type, ++col, table, &cd)) + +/** + * Iterate over each mapped column. + * + * \param col Same meaning as in FOR_EACH_COLUMN(). + * \param table Same meaning as in FOR_EACH_COLUMN(). + * \param cd Same meaning as in FOR_EACH_COLUMN(). + * + * Just like FOR_EACH_COLUMN(), but skip columns which are + * not of type \p OSL_MAPPED_STORAGE. + * + * \sa osl_storage_flags::OSL_MAPPED_STORAGE, FOR_EACH_COLUMN, + * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, + * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN. + */ +#define FOR_EACH_MAPPED_COLUMN(col, table, cd) \ + FOR_EACH_COLUMN_OF_TYPE(OSL_MAPPED_STORAGE, col, table, cd) + +/** + * Iterate over each disk storage column. + * + * \param col Same meaning as in FOR_EACH_COLUMN(). + * \param table Same meaning as in FOR_EACH_COLUMN(). + * \param cd Same meaning as in FOR_EACH_COLUMN(). + * + * Just like FOR_EACH_COLUMN(), but skip columns which are + * not of type \p OSL_DISK_STORAGE. + * + * \sa osl_storage_flags::OSL_DISK_STORAGE, FOR_EACH_COLUMN, + * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, + * FOR_EACH_VOLATILE_COLUMN. + */ +#define FOR_EACH_DISK_STORAGE_COLUMN(col, table, cd) \ + FOR_EACH_COLUMN_OF_TYPE(OSL_DISK_STORAGE, col, table, cd) + +/** + * Iterate over each volatile column. + * + * \param col Same meaning as in FOR_EACH_COLUMN(). + * \param table Same meaning as in FOR_EACH_COLUMN(). + * \param cd Same meaning as in FOR_EACH_COLUMN(). + * + * Just like FOR_EACH_COLUMN(), but skip columns which are + * not of type \p OSL_NO_STORAGE. + * + * \sa osl_storage_flags::OSL_NO_STORAGE, FOR_EACH_COLUMN, + * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, + * FOR_EACH_DISK_STORAGE_COLUMN. + */ +#define FOR_EACH_VOLATILE_COLUMN(col, table, cd) \ + FOR_EACH_COLUMN_OF_TYPE(OSL_NO_STORAGE, col, table, cd) diff --git a/para.h b/para.h index a4753d4b..f41e8b67 100644 --- a/para.h +++ b/para.h @@ -140,10 +140,6 @@ /** sent by para_client, followed by the decrypted challenge number */ #define CHALLENGE_RESPONSE_MSG "challenge_response:" -/* gui_common */ -int para_open_audiod_pipe(char *); -int read_audiod_pipe(int, void (*)(char *)); - /* exec */ int para_exec_cmdline_pid(pid_t *pid, const char *cmdline, int *fds); @@ -177,7 +173,6 @@ int stat_item_valid(const char *item); int stat_line_valid(const char *); void stat_client_write(const char *msg, int itemnum); int stat_client_add(int fd, long unsigned mask); -size_t for_each_line(char *buf, size_t n, void (*line_handler)(char *)); #define FOR_EACH_STAT_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++) __printf_2_3 void para_log(int, const char*, ...); diff --git a/playlist.c b/playlist.c new file mode 100644 index 00000000..9153bf1f --- /dev/null +++ b/playlist.c @@ -0,0 +1,142 @@ +#include "para.h" +#include "error.h" +#include "afs.h" +#include "string.h" + +/** \file playlist.c Functions for loading and saving playlists. */ + +/** Structure used for adding entries to a playlist. */ +struct playlist_info { + /** The name of the playlist. */ + char *name; + /** The number of entries currently in the playlist. */ + unsigned length; +}; +static struct playlist_info playlist; + +/** + * Re-insert an audio file into the tree of admissible files. + * + * \param aft_row Determines the audio file. + * + * \return The return value of score_update(). + */ +int playlist_update_audio_file(struct osl_row *aft_row) +{ + /* always re-insert to the top of the tree */ + return score_update(aft_row, 0); +} + +static int add_playlist_entry(char *line, void *private_data) +{ + struct osl_row *aft_row; + struct playlist_info *pli = private_data; + int ret = aft_get_row_of_path(line, &aft_row); + + if (ret < 0) { + PARA_NOTICE_LOG("path not found in audio file table: %s\n", + line); + return 1; + } + ret = score_add(aft_row, -pli->length); + if (ret < 0) { + PARA_ERROR_LOG("failed to add %s: %d\n", line, ret); + return ret; + } + pli->length++; + return 1; +} + +static int load_playlist(struct osl_row *row) +{ + struct osl_object obj; + int ret; + + ret = osl_get_object(playlists_table, row, BLOBCOL_NAME, &obj); + if (ret < 0) + return ret; + playlist.name = para_strdup(obj.data); + playlist.length = 0; + ret = osl_open_disk_object(playlists_table, row, BLOBCOL_DEF, &obj); + if (ret < 0) + goto err; + ret = for_each_line_ro(obj.data, obj.size, add_playlist_entry, + &playlist); + osl_close_disk_object(&obj); + if (ret < 0) + goto err; + ret = -E_PLAYLIST_EMPTY; + if (!playlist.length) + goto err; + PARA_NOTICE_LOG("loaded playlist %s (%u files)\n", playlist.name, + playlist.length); + return 1; +err: + free(playlist.name); + return ret; +} + +/* returns -E_PLAYLIST_LOADED on _success_ to terminate the loop */ +static int playlist_loop(struct osl_row *row, __a_unused void *private_data) +{ + int ret = load_playlist(row); + if (ret < 0) { + if (ret != -E_DUMMY_ROW) + PARA_NOTICE_LOG("unable to load playlist, trying next\n"); + return 1; + } + return -E_PLAYLIST_LOADED; +} + +static int load_first_available_playlist(void) +{ + int ret = osl_rbtree_loop(playlists_table, BLOBCOL_NAME, NULL, + playlist_loop); + if (ret == -E_PLAYLIST_LOADED) /* success */ + return 1; + if (ret < 0) + return ret; /* error */ + PARA_NOTICE_LOG("no valid playlist found\n"); + return -E_NO_PLAYLIST; +} + +/** + * Close the current playlist. + * + * \sa playlist_open(). + */ +void playlist_close(void) +{ + free(playlist.name); + playlist.name = NULL; +} + +/** + * Open the given playlist. + * + * \param name The name of the playlist to open. + * + * If name is \p NULL, load the first available playlist. Files which are + * listed in the playlist, but not contained in the database are ignored. + * This is not considered an error. + * + * \return Positive on success, negative on errors. Possible errors + * include: Given playlist not found, -E_NO_PLAYLIST (no playlist defined). + */ +int playlist_open(char *name) +{ + struct osl_object obj; + int ret; + struct osl_row *row; + + if (!name) + return load_first_available_playlist(); + obj.data = name; + obj.size = strlen(obj.data); + ret = osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row); + if (ret < 0) { + PARA_NOTICE_LOG("failed to load playlist %s\n", name); + return ret; + } + return load_playlist(row); +} diff --git a/playlist_selector.c b/playlist_selector.c index a497ec13..8372dacc 100644 --- a/playlist_selector.c +++ b/playlist_selector.c @@ -53,7 +53,7 @@ static struct audio_file_selector *self; extern struct misc_meta_data *mmd; -static void playlist_add(char *path) +static int playlist_add(char *path, __a_unused void *data) { if (playlist_len >= playlist_size) { playlist_size = 2 * playlist_size + 1; @@ -61,6 +61,7 @@ static void playlist_add(char *path) } PARA_DEBUG_LOG("adding #%d/%d: %s\n", playlist_len, playlist_size, path); playlist[playlist_len++] = para_strdup(path); + return 1; } static int send_playlist_to_server(const char *buf, size_t size) @@ -212,7 +213,7 @@ static void pls_post_select(__a_unused fd_set *rfds, __a_unused fd_set *wfds) goto out; } PARA_DEBUG_LOG("loading new playlist (%zd bytes)\n", pcd->size); - ret = for_each_line((char *)shm, pcd->size, &playlist_add); + ret = for_each_line((char *)shm, pcd->size, &playlist_add, NULL); shm_detach(shm); PARA_NOTICE_LOG("new playlist (%d entries)\n", playlist_len); sprintf(mmd->selector_info, "dbinfo1:new playlist: %d files\n" diff --git a/portable_io.h b/portable_io.h new file mode 100644 index 00000000..1f3922f8 --- /dev/null +++ b/portable_io.h @@ -0,0 +1,62 @@ +static inline uint64_t read_portable(unsigned bits, const char *buf) +{ + uint64_t ret = 0; + int i, num_bytes = bits / 8; + + for (i = 0; i < num_bytes; i++) { + unsigned char c = buf[i]; + ret += ((uint64_t)c << (8 * i)); + } + return ret; +} + +static inline uint64_t read_u64(const char *buf) +{ + return read_portable(64, buf); +} + +static inline uint32_t read_u32(const char *buf) +{ + return read_portable(32, buf); +} + +static inline uint16_t read_u16(const char *buf) +{ + return read_portable(16, buf); +} + +static inline uint8_t read_u8(const char *buf) +{ + return read_portable(8, buf); +} + +static inline void write_portable(unsigned bits, char *buf, uint64_t val) +{ + int i, num_bytes = bits / 8; +// fprintf(stderr, "val: %lu\n", val); + for (i = 0; i < num_bytes; i++) { + buf[i] = val & 0xff; +// fprintf(stderr, "buf[%d]=%x\n", i, buf[i]); + val = val >> 8; + } +} + +static inline void write_u64(char *buf, uint64_t val) +{ + write_portable(64, buf, val); +} + +static inline void write_u32(char *buf, uint32_t val) +{ + write_portable(32, buf, (uint64_t) val); +} + +static inline void write_u16(char *buf, uint16_t val) +{ + write_portable(16, buf, (uint64_t) val); +} + +static inline void write_u8(char *buf, uint8_t val) +{ + write_portable(8, buf, (uint64_t) val); +} diff --git a/rbtree.c b/rbtree.c new file mode 100644 index 00000000..ed8f0ac7 --- /dev/null +++ b/rbtree.c @@ -0,0 +1,455 @@ +/* + Red Black Trees + (C) 1999 Andrea Arcangeli + (C) 2002 David Woodhouse + (C) 2007 Andre Noll + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + linux/lib/rbtree.c +*/ + +#include "stddef.h" +#include "rbtree.h" + +static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *right = node->rb_right; + struct rb_node *parent = rb_parent(node); + + if ((node->rb_right = right->rb_left)) + rb_set_parent(right->rb_left, node); + right->rb_left = node; + + rb_set_parent(right, parent); + + if (parent) + { + if (node == parent->rb_left) + parent->rb_left = right; + else + parent->rb_right = right; + } + else + root->rb_node = right; + rb_set_parent(node, right); + right->size = node->size; + node->size = 1; + if (node->rb_right) + node->size += node->rb_right->size; + if (node->rb_left) + node->size += node->rb_left->size; +} + +static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *left = node->rb_left; + struct rb_node *parent = rb_parent(node); + + if ((node->rb_left = left->rb_right)) + rb_set_parent(left->rb_right, node); + left->rb_right = node; + + rb_set_parent(left, parent); + + if (parent) + { + if (node == parent->rb_right) + parent->rb_right = left; + else + parent->rb_left = left; + } + else + root->rb_node = left; + rb_set_parent(node, left); + left->size = node->size; + node->size = 1; + if (node->rb_right) + node->size += node->rb_right->size; + if (node->rb_left) + node->size += node->rb_left->size; +} + +void rb_insert_color(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *parent, *gparent; + + while ((parent = rb_parent(node)) && rb_is_red(parent)) + { + gparent = rb_parent(parent); + + if (parent == gparent->rb_left) + { + { + register struct rb_node *uncle = gparent->rb_right; + if (uncle && rb_is_red(uncle)) + { + rb_set_black(uncle); + rb_set_black(parent); + rb_set_red(gparent); + node = gparent; + continue; + } + } + + if (parent->rb_right == node) + { + register struct rb_node *tmp; + __rb_rotate_left(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + + rb_set_black(parent); + rb_set_red(gparent); + __rb_rotate_right(gparent, root); + } else { + { + register struct rb_node *uncle = gparent->rb_left; + if (uncle && rb_is_red(uncle)) + { + rb_set_black(uncle); + rb_set_black(parent); + rb_set_red(gparent); + node = gparent; + continue; + } + } + + if (parent->rb_left == node) + { + register struct rb_node *tmp; + __rb_rotate_right(parent, root); + tmp = parent; + parent = node; + node = tmp; + } + + rb_set_black(parent); + rb_set_red(gparent); + __rb_rotate_left(gparent, root); + } + } + + rb_set_black(root->rb_node); +} + +static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, + struct rb_root *root) +{ + struct rb_node *other; + + while ((!node || rb_is_black(node)) && node != root->rb_node) + { + if (parent->rb_left == node) + { + other = parent->rb_right; + if (rb_is_red(other)) + { + rb_set_black(other); + rb_set_red(parent); + __rb_rotate_left(parent, root); + other = parent->rb_right; + } + if ((!other->rb_left || rb_is_black(other->rb_left)) && + (!other->rb_right || rb_is_black(other->rb_right))) + { + rb_set_red(other); + node = parent; + parent = rb_parent(node); + } + else + { + if (!other->rb_right || rb_is_black(other->rb_right)) + { + struct rb_node *o_left; + if ((o_left = other->rb_left)) + rb_set_black(o_left); + rb_set_red(other); + __rb_rotate_right(other, root); + other = parent->rb_right; + } + rb_set_color(other, rb_color(parent)); + rb_set_black(parent); + if (other->rb_right) + rb_set_black(other->rb_right); + __rb_rotate_left(parent, root); + node = root->rb_node; + break; + } + } + else + { + other = parent->rb_left; + if (rb_is_red(other)) + { + rb_set_black(other); + rb_set_red(parent); + __rb_rotate_right(parent, root); + other = parent->rb_left; + } + if ((!other->rb_left || rb_is_black(other->rb_left)) && + (!other->rb_right || rb_is_black(other->rb_right))) + { + rb_set_red(other); + node = parent; + parent = rb_parent(node); + } + else + { + if (!other->rb_left || rb_is_black(other->rb_left)) + { + register struct rb_node *o_right; + if ((o_right = other->rb_right)) + rb_set_black(o_right); + rb_set_red(other); + __rb_rotate_left(other, root); + other = parent->rb_left; + } + rb_set_color(other, rb_color(parent)); + rb_set_black(parent); + if (other->rb_left) + rb_set_black(other->rb_left); + __rb_rotate_right(parent, root); + node = root->rb_node; + break; + } + } + } + if (node) + rb_set_black(node); +} + +void rb_erase(struct rb_node *node, struct rb_root *root) +{ + struct rb_node *child, *parent; + int color; + + if (!node->rb_left) + child = node->rb_right; + else if (!node->rb_right) + child = node->rb_left; + else + { + struct rb_node *old = node, *left; + + node = node->rb_right; + while ((left = node->rb_left) != NULL) + node = left; + child = node->rb_right; + parent = rb_parent(node); + color = rb_color(node); + + if (child) + rb_set_parent(child, parent); + if (parent == old) { + parent->rb_right = child; + parent = node; + } else + parent->rb_left = child; + + node->rb_parent_color = old->rb_parent_color; + node->rb_right = old->rb_right; + node->rb_left = old->rb_left; + node->size = old->size; + + if (rb_parent(old)) + { + if (rb_parent(old)->rb_left == old) + rb_parent(old)->rb_left = node; + else + rb_parent(old)->rb_right = node; + } else + root->rb_node = node; + + rb_set_parent(old->rb_left, node); + if (old->rb_right) + rb_set_parent(old->rb_right, node); + goto color; + } + + parent = rb_parent(node); + color = rb_color(node); + + if (child) + rb_set_parent(child, parent); + if (parent) + { + if (parent->rb_left == node) + parent->rb_left = child; + else + parent->rb_right = child; + } + else + root->rb_node = child; + + color: + if (color == RB_BLACK) + __rb_erase_color(child, parent, root); +} + +/* + * This function returns the first node (in sort order) of the tree. + */ +struct rb_node *rb_first(struct rb_root *root) +{ + struct rb_node *n; + + n = root->rb_node; + if (!n) + return NULL; + while (n->rb_left) + n = n->rb_left; + return n; +} + +struct rb_node *rb_last(struct rb_root *root) +{ + struct rb_node *n; + + n = root->rb_node; + if (!n) + return NULL; + while (n->rb_right) + n = n->rb_right; + return n; +} + +struct rb_node *rb_next(struct rb_node *node) +{ + struct rb_node *parent; + + if (rb_parent(node) == node) + return NULL; + + /* If we have a right-hand child, go down and then left as far + as we can. */ + if (node->rb_right) { + node = node->rb_right; + while (node->rb_left) + node=node->rb_left; + return node; + } + + /* No right-hand children. Everything down and left is + smaller than us, so any 'next' node must be in the general + direction of our parent. Go up the tree; any time the + ancestor is a right-hand child of its parent, keep going + up. First time it's a left-hand child of its parent, said + parent is our 'next' node. */ + while ((parent = rb_parent(node)) && node == parent->rb_right) + node = parent; + + return parent; +} + +struct rb_node *rb_prev(struct rb_node *node) +{ + struct rb_node *parent; + + if (rb_parent(node) == node) + return NULL; + + /* If we have a left-hand child, go down and then right as far + as we can. */ + if (node->rb_left) { + node = node->rb_left; + while (node->rb_right) + node=node->rb_right; + return node; + } + + /* No left-hand children. Go up till we find an ancestor which + is a right-hand child of its parent */ + while ((parent = rb_parent(node)) && node == parent->rb_left) + node = parent; + + return parent; +} + +void rb_replace_node(struct rb_node *victim, struct rb_node *new, + struct rb_root *root) +{ + struct rb_node *parent = rb_parent(victim); + + /* Set the surrounding nodes to point to the replacement */ + if (parent) { + if (victim == parent->rb_left) + parent->rb_left = new; + else + parent->rb_right = new; + } else { + root->rb_node = new; + } + if (victim->rb_left) + rb_set_parent(victim->rb_left, new); + if (victim->rb_right) + rb_set_parent(victim->rb_right, new); + + /* Copy the pointers/colour from the victim to the replacement */ + *new = *victim; +} + +/** + * Get the n-th node (in sort order) of the tree. + * + * \param node The root of the subtree to consider. + * \param n The order statistic to compute. + * + * \return Pointer to the \a n th greatest node on success, \p NULL on errors. + */ +struct rb_node *rb_nth(struct rb_node *node, unsigned n) +{ + unsigned size = 1; + + if (!node) + return NULL; + if (node->rb_left) + size += node->rb_left->size; + if (n == size) + return node; + if (n < size) + return rb_nth(node->rb_left, n); + return rb_nth(node->rb_right, n - size); +} + +/** + * Get the rank of a node in O(log n) time. + * + * \param node The node to get the rank of. + * \param rank Result pointer. + * + * \return Positive on success, -1 on errors. + */ +int rb_rank(struct rb_node *node, unsigned *rank) +{ + *rank = 1; + struct rb_node *parent; + + if (!node) + return -1; + if (node->rb_left) + *rank += node->rb_left->size; + while ((parent = rb_parent(node))) { + if (node == parent->rb_right) { + (*rank)++; + if (parent->rb_left) + *rank += parent->rb_left->size; + } + node = parent; + } + return 1; +} diff --git a/rbtree.h b/rbtree.h new file mode 100644 index 00000000..1dafb792 --- /dev/null +++ b/rbtree.h @@ -0,0 +1,172 @@ +/* + Red Black Trees + (C) 1999 Andrea Arcangeli + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + linux/include/linux/rbtree.h + + To use rbtrees you'll have to implement your own insert and search cores. + This will avoid us to use callbacks and to drop drammatically performances. + I know it's not the cleaner way, but in C (not in C++) to get + performances and genericity... + + Some example of insert and search follows here. The search is a plain + normal search over an ordered tree. The insert instead must be implemented + int two steps: as first thing the code must insert the element in + order as a red leaf in the tree, then the support library function + rb_insert_color() must be called. Such function will do the + not trivial work to rebalance the rbtree if necessary. + +----------------------------------------------------------------------- +static inline struct page * rb_search_page_cache(struct inode * inode, + unsigned long offset) +{ + struct rb_node * n = inode->i_rb_page_cache.rb_node; + struct page * page; + + while (n) + { + page = rb_entry(n, struct page, rb_page_cache); + + if (offset < page->offset) + n = n->rb_left; + else if (offset > page->offset) + n = n->rb_right; + else + return page; + } + return NULL; +} + +static inline struct page * __rb_insert_page_cache(struct inode * inode, + unsigned long offset, + struct rb_node * node) +{ + struct rb_node ** p = &inode->i_rb_page_cache.rb_node; + struct rb_node * parent = NULL; + struct page * page; + + while (*p) + { + parent = *p; + page = rb_entry(parent, struct page, rb_page_cache); + + if (offset < page->offset) + p = &(*p)->rb_left; + else if (offset > page->offset) + p = &(*p)->rb_right; + else + return page; + } + + rb_link_node(node, parent, p); + + return NULL; +} + +static inline struct page * rb_insert_page_cache(struct inode * inode, + unsigned long offset, + struct rb_node * node) +{ + struct page * ret; + if ((ret = __rb_insert_page_cache(inode, offset, node))) + goto out; + rb_insert_color(node, &inode->i_rb_page_cache); + out: + return ret; +} +----------------------------------------------------------------------- +*/ + +#ifndef _LINUX_RBTREE_H +#define _LINUX_RBTREE_H + + +/** get the struct this entry is embedded in */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + + +struct rb_node +{ + unsigned long rb_parent_color; +#define RB_RED 0 +#define RB_BLACK 1 + struct rb_node *rb_right; + struct rb_node *rb_left; + unsigned size; +} __attribute__((aligned(sizeof(long)))); + /* The alignment might seem pointless, but allegedly CRIS needs it */ + +struct rb_root +{ + struct rb_node *rb_node; +}; + + +#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3)) +#define rb_color(r) ((r)->rb_parent_color & 1) +#define rb_is_red(r) (!rb_color(r)) +#define rb_is_black(r) rb_color(r) +#define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0) +#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0) + +static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) +{ + rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p; +} +static inline void rb_set_color(struct rb_node *rb, int color) +{ + rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; +} + +#define RB_ROOT (struct rb_root) { NULL, } +#define rb_entry(ptr, type, member) container_of(ptr, type, member) + +#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) +#define RB_EMPTY_NODE(node) (rb_parent(node) == node) +#define RB_CLEAR_NODE(node) (rb_set_parent(node, node)) + +extern void rb_insert_color(struct rb_node *, struct rb_root *); +extern void rb_erase(struct rb_node *, struct rb_root *); + +/* Find logical next and previous nodes in a tree */ +extern struct rb_node *rb_next(struct rb_node *); +extern struct rb_node *rb_prev(struct rb_node *); +extern struct rb_node *rb_first(struct rb_root *); +extern struct rb_node *rb_last(struct rb_root *); +extern struct rb_node *rb_nth(struct rb_node *node, unsigned n); +extern int rb_rank(struct rb_node *node, unsigned *rank); + +/* Fast replacement of a single node without remove/rebalance/add/rebalance */ +extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, + struct rb_root *root); + +static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, + struct rb_node ** rb_link) +{ + node->size = 1; + node->rb_parent_color = (unsigned long )parent; + node->rb_left = node->rb_right = NULL; + + *rb_link = node; + /* Fixup the size fields in the tree */ + while ((node = rb_parent(node))) + node->size++; +} + +#endif /* _LINUX_RBTREE_H */ diff --git a/score.c b/score.c new file mode 100644 index 00000000..2cfadf86 --- /dev/null +++ b/score.c @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2007 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file score.c Scoring functions to determine the audio file streaming order. */ +#include "para.h" +#include "error.h" +#include "afs.h" +#include "list.h" +#include "string.h" + +int mood_compute_score(const void *row, long *score); + +static void *score_table; + +static int ptr_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + void *d1 = *(void **)obj1->data; + void *d2 = *(void **)obj2->data; + return NUM_COMPARE(d1, d2); +} + +/** + * Compare the score of two audio files + * + * \param obj1 Pointer to the first score object. + * \param obj2 Pointer to the second score object. + * + * This function first compares the score values as usual integers. If they compare as + * equal, the addresss of \a obj1 and \a obj2 are compared. So this compare function + * returns zero if and only if \a obj1 and \a obj2 point to the same memory area. + * + * \sa osl_compare_function. + */ +static int score_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + int d1 = *(int*)obj1->data; + int d2 = *(int*)obj2->data; + int ret = NUM_COMPARE(d2, d1); + + if (ret) + return ret; + return NUM_COMPARE(obj2->data, obj1->data); +} + +/** + * The score table consists of two colums: The \a aft_row column contains + * pointers to the rows of the audio file table, and the score column contains + * the current score of the audio file associated with that row. + */ +enum score_table_columns { + /** The row of the audio file. */ + SCORECOL_AFT_ROW, + /** The score */ + SCORECOL_SCORE, + /** This table has two columns */ + NUM_SCORE_COLUMNS +}; + +static struct osl_column_description score_cols[] = { + [SCORECOL_AFT_ROW] = { + .storage_type = OSL_NO_STORAGE, + .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, + .name = "aft_row", + .compare_function = ptr_compare, + .data_size = sizeof(void *) + }, + [SCORECOL_SCORE] = { + .storage_type = OSL_NO_STORAGE, + .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE, + .name = "score", + .compare_function = score_compare, + .data_size = sizeof(long) + } +}; + +static struct osl_table_description score_table_desc = { + .dir = DATABASE_DIR, + .name = "score", + .num_columns = NUM_SCORE_COLUMNS, + .flags = 0, + .column_descriptions = score_cols +}; + +/** + * Compute the number of files in score table. + * + * \param num Result is returned here. + * + * \return Positive on success, negative on errors. + * + * \sa osl_get_num_rows(). + */ +int get_num_admissible_files(unsigned *num) +{ + return osl_get_num_rows(score_table, num); +} + +/** + * Get the score of the audio file associated with given row of the score table. + * + * \param score_row Pointer to the row in the score table. + * \param score Result is returned here on success. + * + * On errors (negative return value) the content of \a score is undefined. + * + * \return The return value of the underlying call to osl_get_object(). + */ +static int get_score_of_row(void *score_row, long *score) +{ + struct osl_object obj; + int ret = osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj); + + if (ret >= 0) + *score = *(long *)obj.data; + return ret; +} + +/** + * Add an entry to the table of admissible files. + * + * \param aft_row The audio file to be added. + * \param score The score for this file. + * + * \return The return value of the underlying call to osl_add_row(). + */ +int score_add(const struct osl_row *aft_row, long score) +{ + int ret; + struct osl_object score_objs[NUM_SCORE_COLUMNS]; + size_t size; + + assert(aft_row); + size = score_table_desc.column_descriptions[SCORECOL_AFT_ROW].data_size; + score_objs[SCORECOL_AFT_ROW].data = para_malloc(size); + score_objs[SCORECOL_AFT_ROW].size = size; + *(const void **)(score_objs[SCORECOL_AFT_ROW].data) = aft_row; + + size = score_table_desc.column_descriptions[SCORECOL_SCORE].data_size; + score_objs[SCORECOL_SCORE].data = para_malloc(size); + score_objs[SCORECOL_SCORE].size = size; + *(int *)(score_objs[SCORECOL_SCORE].data) = score; + +// PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data)); + ret = osl_add_row(score_table, score_objs); + if (ret < 0) { + PARA_ERROR_LOG("failed to add row: %d\n", ret); + free(score_objs[SCORECOL_AFT_ROW].data); + free(score_objs[SCORECOL_SCORE].data); + } + return ret; +} + +static int get_nth_score(unsigned n, long *score) +{ + struct osl_row *row; + int ret = osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row); + + if (ret < 0) + return ret; + return get_score_of_row(row, score); +} + +/** + * Replace a row of the score table. + * + * \param aft_row Determines the audio file to change. + * \param percent The position to re-insert the audio file. + * + * The percent paramenter must be between \p 0 and 100 and. A value of zero + * means to re-insert the audio file into the score table with a score lower + * than any other admissible file. + * + * \return Positive on success, negative on errors. Possible errors: Errors + * returned by osl_get_row(), get_num_admissible_files(), osl_get_nth_row(), + * osl_get_object(), osl_update_object(). + */ +int score_update(const struct osl_row *aft_row, long percent) +{ + struct osl_row *row; + long new_score; + unsigned n, new_pos; + struct osl_object obj = {.data = &aft_row, .size = sizeof(void **)}; + int ret = osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, &row); + + if (ret < 0) + return ret; + ret = get_num_admissible_files(&n); + if (ret < 0) + return ret; + new_pos = 1 + (n - 1) * percent / 100; + ret = get_nth_score(new_pos, &new_score); + if (ret < 0) + return ret; + new_score--; + obj.size = sizeof(long); + obj.data = para_malloc(obj.size); + *(long *)obj.data = new_score; + PARA_NOTICE_LOG("new score: %ld, position: %u/%u\n", new_score, + new_pos, n); + return osl_update_object(score_table, row, SCORECOL_SCORE, &obj); +} + +/** + * Given an admissible file, get the corresponding row in the aft and the score. + * + * \param score_row Determines the admissible file. + * \param score Result pointer. + * \param aft_row Result pointer. + * + * \return Negative on errors, positive on success. Possible errors: Errors + * returned by osl_get_object(). + */ +int get_score_and_aft_row(struct osl_row *score_row, long *score, + struct osl_row **aft_row) +{ + struct osl_object obj; + int ret = get_score_of_row(score_row, score); + + if (ret < 0) + return ret; + ret = osl_get_object(score_table, score_row, SCORECOL_AFT_ROW, &obj); + if (ret < 0) + return ret; + *aft_row = *(void **)obj.data; + return 1; +} + +static int get_score_row_from_aft_row(const struct osl_row *aft_row, + struct osl_row **score_row) +{ + struct osl_object obj = {.data = &aft_row, .size = sizeof(void **)}; + return osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row); + +} + +/** + * Loop over all files in the score table. + * + * \param data A pointer to arbitrary data. + * \param func Function to be called for each admissible file. + * + * \return The return value of the underlying call to osl_rbtree_loop(). + * + * This is used for the ls command. The \a data parameter is passed as the + * second argument to \a func. + * + * \sa admissible_file_loop_reverse(). + */ +int admissible_file_loop(void *data, osl_rbtree_loop_func *func) +{ + return osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func); +} + +/** + * Loop over all files in the score table in reverse order. + * + * \param data As in admissible_file_loop(). + * \param func As in admissible_file_loop(). + * + * \return Same return value as admissible_file_loop(). + * + * \sa admissible_file_loop(), osl_rbtree_loop_reverse(). + */ +int admissible_file_loop_reverse(void *data, osl_rbtree_loop_func *func) +{ + return osl_rbtree_loop_reverse(score_table, SCORECOL_SCORE, data, func); +} + +/** + * Get the admissible audio file with highest score. + * + * \param aft_row Points to the row in the aft of the "best" audio file. + * \param score Highest score value in the score table. + * + * \return Positive on success, negative on errors. Possible errors: Errors + * returned by osl_rbtree_last_row(), osl_get_object(). + */ +int score_get_best(struct osl_row **aft_row, long *score) +{ + struct osl_row *row; + struct osl_object obj; + int ret = osl_rbtree_last_row(score_table, SCORECOL_SCORE, &row); + + if (ret < 0) + return ret; + ret = osl_get_object(score_table, row, SCORECOL_AFT_ROW, &obj); + if (ret < 0) + return ret; + *aft_row = *(void **)obj.data; + return get_score_of_row(row, score); +} + +/** + * Remove an entry from the rbtree of admissible files. + * + * \param aft_row The file which is no longer admissible. + * + * \return Positive on success, negative on errors. Possible errors: + * Errors returned by osl_get_row() and osl_del_row(). + * + * \sa score_add(), score_shutdown(). + */ +int score_delete(const struct osl_row *aft_row) +{ + struct osl_row *score_row; + int ret = get_score_row_from_aft_row(aft_row, &score_row); + + if (ret < 0) + return ret; + return osl_del_row(score_table, score_row); +} + +/** + * Find out whether an audio file is contained in the score table. + * + * \param aft_row The row of the audio file table. + * + * \return Positive, if \a aft_row belongs to the audio file table, + * zero if not, negative on errors. + */ +int row_belongs_to_score_table(const struct osl_row *aft_row) +{ + struct osl_row *score_row; + int ret = get_score_row_from_aft_row(aft_row, &score_row); + if (ret >= 0) + return 1; + if (ret == -E_RB_KEY_NOT_FOUND) + return 0; + return ret; +} + +/** + * Close the score table. + * + * \param flags Options passed to osl_close_table(). + * + * \sa score_init(). + */ +void score_shutdown(enum osl_close_flags flags) +{ + osl_close_table(score_table, flags | OSL_FREE_VOLATILE); +} + +/** + * Init the scoring subsystem. + * + * \param ti Gets filled in by the function. + * + * \return The return value of the underlying call to osl_open_table(). + * + * \sa score_shutdown(). + */ +int score_init(struct table_info *ti) +{ + ti->desc = &score_table_desc; + ti->flags = TBLFLAG_SKIP_CREATE; + int ret = osl_open_table(ti->desc, &ti->table); + + score_table = ti->table; + return ret; +} diff --git a/sdl_gui.c b/sdl_gui.c index 6aa78546..d30aad80 100644 --- a/sdl_gui.c +++ b/sdl_gui.c @@ -7,7 +7,7 @@ /** \file sdl_gui.c SDL-based interface for paraslash */ #include "para.h" -#include "string.h" +#include "gui_common.h" #include "fd.h" @@ -660,17 +660,18 @@ static void do_update(int i) * Check if buf is a known status line. If so call do_update and return 1. * Return 0 otherwise. */ -void update_status(char *buf) +int update_status(char *buf, __a_unused void *data) { int i; i = stat_line_valid(buf); if (i < 0) - return; + return 1; //free(stat_items[i].content); stat_items[i].content = para_strdup(buf + strlen(status_item_list[i]) + 1); do_update(i); + return 1; } /* diff --git a/sha1.c b/sha1.c new file mode 100644 index 00000000..bc2ec336 --- /dev/null +++ b/sha1.c @@ -0,0 +1,23 @@ +#include "para.h" +#include + +/** \file sha1.c Secure Hash Algorithm, provided by openssl. */ + +/** + * Compute the sha1 hash. + * + * \param data Pointer to the data to compute the hash valure from. + * \param len The length of \a data in bytes. + * \param sha1 Result pointer + * + * \a sha1 must point to an area at least 20 bytes large. + * + * \sa sha(3), openssl(1). + * */ +void sha1_hash(const char *data, unsigned long len, unsigned char *sha1) +{ + SHA_CTX c; + SHA1_Init(&c); + SHA1_Update(&c, data, len); + SHA1_Final(sha1, &c); +} diff --git a/sha1.h b/sha1.h new file mode 100644 index 00000000..4dce20de --- /dev/null +++ b/sha1.h @@ -0,0 +1,5 @@ +/** \file sha1.h Secure Hash Algorithm prototype */ + +/** Size of the hahs value in bytes. */ +#define HASH_SIZE 20 +void sha1_hash(const char *data, unsigned long len, unsigned char *sha1); diff --git a/stat.c b/stat.c index 23db4246..202dff8f 100644 --- a/stat.c +++ b/stat.c @@ -216,54 +216,3 @@ int stat_line_valid(const char *line) return -E_UNKNOWN_STAT_ITEM; } -/** - * call a custom function for each complete line - * - * \param buf the buffer containing data seperated by newlines - * \param n the number of bytes in \a buf - * \param line_handler the custom function - * - * If \a line_handler is \p NULL, return number of complete lines in buf. - * Otherwise, call \a line_handler for each complete line. The rest of the - * buffer (last chunk containing incomplete line is moved to the beginning of - * the buffer. - * - * \return If line_handler is not NULL, this function returns the number - * of bytes not handled to \a line_handler. - */ -size_t for_each_line(char *buf, size_t n, void (*line_handler)(char *)) -{ - char *start = buf, *end; - size_t num_lines = 0, bytes_left = n; - -// PARA_INFO_LOG("buf: %s", buf); - while (bytes_left) { - char *next_null; - char *next_cr; - - next_cr = memchr(start, '\n', bytes_left); - next_null = memchr(start, '\0', bytes_left); - if (!next_cr && !next_null) - break; - if (next_cr && next_null) { - end = next_cr < next_null? next_cr : next_null; - } else if (next_null) { - end = next_null; - } else - end = next_cr; - num_lines++; - if (line_handler) { - *end = '\0'; -// PARA_INFO_LOG("calling line handler: %s\n", start); - line_handler(start); - } - start = ++end; - bytes_left = buf + n - start; - } - if (!line_handler) - return num_lines; - if (bytes_left && bytes_left != n) - memmove(buf, start, bytes_left); - return bytes_left; -} - diff --git a/string.c b/string.c index 8b9f1633..510592ab 100644 --- a/string.c +++ b/string.c @@ -392,3 +392,159 @@ __malloc char *para_hostname(void) uname(&u); return para_strdup(u.nodename); } + +enum for_each_line_modes{LINE_MODE_RO, LINE_MODE_RW}; + +static int for_each_complete_line(enum for_each_line_modes mode, char *buf, + size_t size, line_handler_t *line_handler, void *private_data) +{ + char *start = buf, *end; + int ret, i, num_lines = 0; + +// PARA_NOTICE_LOG("buf: %s\n", buf); + while (start < buf + size) { + char *next_null; + char *next_cr; + + next_cr = memchr(start, '\n', buf + size - start); + next_null = memchr(start, '\0', buf + size - start); + if (!next_cr && !next_null) + break; + if (next_cr && next_null) { + end = next_cr < next_null? next_cr : next_null; + } else if (next_null) { + end = next_null; + } else + end = next_cr; + num_lines++; + if (!line_handler) { + start = ++end; + continue; + } + if (mode == LINE_MODE_RO) { + size_t s = end - start; + char *b = para_malloc(s + 1); + memcpy(b, start, s); + b[s] = '\0'; +// PARA_NOTICE_LOG("b: %s, start: %s\n", b, start); + ret = line_handler(b, private_data); + free(b); + } else { + *end = '\0'; + ret = line_handler(start, private_data); + } + if (ret < 0) + return ret; + start = ++end; + } + if (!line_handler || mode == LINE_MODE_RO) + return num_lines; + i = buf + size - start; + if (i && i != size) + memmove(buf, start, i); + return i; +} + +/** + * call a custom function for each complete line + * + * \param buf the buffer containing data seperated by newlines + * \param size the number of bytes in \a buf + * \param line_handler the custom function + * \param private_data pointer to data which is passed to \a line_handler + * + * If \p line_handler is \p NULL, the function returns the number of complete + * lines in \p buf. Otherwise, \p line_handler is called for each complete + * line in \p buf. The first argument to \p line_handler is the current line, + * and \p private_data is passed as the second argument. The function returns + * if \p line_handler returns a negative value or no more lines are in the + * buffer. The rest of the buffer (last chunk containing an incomplete line) + * is moved to the beginning of the buffer. + * + * \return If \p line_handler is not \p NULL, this function returns the number + * of bytes not handled to \p line_handler on success, or the negative return + * value of the \p line_handler on errors. + * + * \sa for_each_line_ro() + */ +int for_each_line(char *buf, size_t size, line_handler_t *line_handler, + void *private_data) +{ + return for_each_complete_line(LINE_MODE_RW, buf, size, line_handler, + private_data); +} + +/** + * call a custom function for each complete line + * + * \param buf same meaning as in \p for_each_line + * \param size same meaning as in \p for_each_line + * \param line_handler same meaning as in \p for_each_line + * \param private_data same meaning as in \p for_each_line + * + * This function behaves like \p for_each_line() with the following differences: + * + * - buf is left unchanged + * + * \return On success, the function returns the number of complete lines in \p + * buf, otherwise the (negative) return value of \p line_handler is returned. + * + * \sa for_each_line() + */ +int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, + void *private_data) +{ + return for_each_complete_line(LINE_MODE_RO, buf, size, line_handler, + private_data); +} + +/** + * Safely print into a buffer at a given offset + * + * \param b Determines the buffer, its size, and the offset. + * \param fmt The format string. + * + * This function prints into the buffer given by \a b at the offset which is + * also given by \a b. If there is not enough space to hold the result, the + * buffer size is doubled until the underlying call to vsnprintf() succeeds. + * Upon return, the offset of \a b is adjusted accordingly so that subsequent + * calls to this function append data to what is already contained in the + * buffer. + * + * It's OK to call this function with \p b->buf being \p NULL. In this case, an + * initial buffer is allocated. + * + * \return The number of bytes printed into the buffer (not including the + * therminating \p NULL byte). + * + * \sa make_message(), vsnprintf(3). + */ +__printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) +{ + int ret; + + if (!b->buf) { + b->buf = para_malloc(128); + b->size = 128; + b->offset = 0; + } else if (b->size <= b->offset + 1) { + b->size *= 2; + b->buf = para_realloc(b->buf, b->size); + } + while (1) { + char *p = b->buf + b->offset; + size_t size = b->size - b->offset; + va_list ap; + va_start(ap, fmt); + ret = vsnprintf(p, size, fmt, ap); + va_end(ap); + if (ret > -1 && ret < size) { /* success */ + b->offset += ret; + break; + } + /* try again with more space */ + b->size *= 2; + b->buf = para_realloc(b->buf, b->size); + } + return ret; +} diff --git a/string.h b/string.h index bb65a3e4..0c2090b3 100644 --- a/string.h +++ b/string.h @@ -6,6 +6,12 @@ /** \file string.h exported sybmols from string.c */ +struct para_buffer { + char *buf; + size_t size; + size_t offset; +}; + __must_check __malloc void *para_realloc(void *p, size_t size); __must_check __malloc void *para_malloc(size_t size); __must_check __malloc void *para_calloc(size_t size); @@ -22,4 +28,10 @@ __must_check __malloc char *para_homedir(void); __must_check unsigned split_args(char *args, char ***argv_ptr, const char *delim); __malloc char *para_hostname(void); void valid_fd_012(void); +__printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...); +typedef int line_handler_t(char *, void *); +int for_each_line(char *buf, size_t size, line_handler_t *line_handler, + void *private_data); +int for_each_line_ro(char *buf, size_t size, line_handler_t line_handler, + void *private_data); -- 2.39.2