X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=aft.c;h=f6cfddf904d06a4b89acf826ce6ee63a0f44686b;hb=92c6b27d3a3fb01cf516be2fa8885d07ca92c9c7;hp=ac7d9a1a34c69412242bef6631841238f07d8aea;hpb=f51c95fa0edaaf651387456e716899433a0c579c;p=paraslash.git diff --git a/aft.c b/aft.c index ac7d9a1a..5595071f 100644 --- a/aft.c +++ b/aft.c @@ -1,21 +1,130 @@ -#include "para.h" -#include "error.h" +/* Copyright (C) 2007 Andre Noll , see file COPYING. */ + +/** \file aft.c Audio file table functions. */ + +#include #include #include +#include +#include +#include + +#include "server_cmd.lsg.h" +#include "para.h" +#include "error.h" +#include "crypt.h" +#include "string.h" #include "afh.h" #include "afs.h" -#include "net.h" -#include "string.h" -#include "vss.h" +#include "fd.h" +#include "ipc.h" +#include "portable_io.h" +#include "sideband.h" +#include "command.h" + +/* Data about one audio file. Needed for ls and stat output. */ +struct ls_data { + /* Usual audio format handler information. */ + struct afh_info afhi; + /* Audio file selector information. */ + struct afs_info afsi; + /* The full path of the audio file. */ + char *path; + /* The score value (if -a was given). */ + long score; + /* The hash value of the audio file data. */ + unsigned char *hash; +}; + +/* + * The internal state of the audio file table is described by the following + * variables which are private to aft.c. + */ +static struct osl_table *audio_file_table; /* NULL if table not open */ +static struct osl_row *current_aft_row; /* NULL if no audio file open */ +static unsigned char current_hash[HASH_SIZE]; /* only used on sighup */ + +static char *status_items; +static char *parser_friendly_status_items; +static struct ls_data status_item_ls_data; + +/** The different sorting methods of the ls command. */ +enum ls_sorting_method { + LS_SORT_BY_PATH, /**< -s=p (default) */ + LS_SORT_BY_SCORE, /**< -s=s */ + LS_SORT_BY_LAST_PLAYED, /**< -s=l */ + LS_SORT_BY_NUM_PLAYED, /**< -s=n */ + LS_SORT_BY_FREQUENCY, /**< -s=f */ + LS_SORT_BY_CHANNELS, /**< -s=c */ + LS_SORT_BY_IMAGE_ID, /**< -s=i */ + LS_SORT_BY_LYRICS_ID, /**< -s=y */ + LS_SORT_BY_BITRATE, /**< -s=b */ + LS_SORT_BY_DURATION, /**< -s=d */ + LS_SORT_BY_AUDIO_FORMAT, /**< -s=a */ + LS_SORT_BY_HASH, /**< -s=h */ +}; -#define AFS_AUDIO_FILE_DIR "/home/mp3" +/** The different listing modes of the ls command. */ +enum ls_listing_mode { + LS_MODE_SHORT, /**< Default listing mode. */ + LS_MODE_LONG, /**< -l or -l=l */ + LS_MODE_VERBOSE, /** -l=v */ + LS_MODE_MBOX, /** -l=m */ + LS_MODE_CHUNKS, /** -l=c */ + LS_MODE_PARSER, /** -l=p */ +}; + +/** + * The size of the individual output fields of the ls command. + * + * These depend on the content being listed. For example, if each listed file + * is shorter than an hour, the duration format is set to mm:ss. Otherwise it + * is set to hh:mm:ss. + */ +struct ls_widths { + /** size of the score field. */ + unsigned short score_width; + /** size of the image id field. */ + unsigned short image_id_width; + /** size of the lyrics id field. */ + unsigned short lyrics_id_width; + /** size of the bitrate field. */ + unsigned short bitrate_width; + /** size of the frequency field. */ + unsigned short frequency_width; + /** size of the duration field. */ + unsigned short duration_width; + /** size of the num played field. */ + unsigned short num_played_width; + /** size of the amp field. */ + unsigned short amp_width; + /** size of the audio format field. */ + unsigned short audio_format_width; +}; -static struct osl_table *audio_file_table; +/** Data passed from the ls command handler to its callback function. */ +struct ls_options { + struct lls_parse_result *lpr; + /* Derived from lpr */ + enum ls_sorting_method sorting; + /* Derived from lpr */ + enum ls_listing_mode mode; + /** Used for long listing mode to align the output fields. */ + struct ls_widths widths; + /** Size of the \a data array. */ + uint32_t array_size; + /** Number of used entries in the data array. */ + uint32_t num_matching_paths; + /** Array of matching entries. */ + struct ls_data *data; + /** Used to sort the array. */ + struct ls_data **data_ptr; +}; /** * Describes the layout of the mmapped-afs info struct. * - * \sa struct afs_info. + * \sa struct \ref afs_info. */ enum afsi_offsets { /** Where .last_played is stored. */ @@ -30,19 +139,23 @@ enum afsi_offsets { AFSI_LYRICS_ID_OFFSET = 24, /** Storage position of the .audio_format_id field. */ AFSI_AUDIO_FORMAT_ID_OFFSET = 28, + /** Storage position of the amplification field. */ + AFSI_AMP_OFFSET = 29, + /** 2 bytes reserved space for future usage. */ + AFSI_AUDIO_FORMAT_UNUSED_OFFSET = 30, /** On-disk storage space needed. */ - AFSI_SIZE = 29 + AFSI_SIZE = 32 }; -/** +/* * 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(). + * \sa \ref load_afsi(). */ -void save_afsi(struct afs_info *afsi, struct osl_object *obj) +static void save_afsi(struct afs_info *afsi, struct osl_object *obj) { char *buf = obj->data; @@ -53,23 +166,25 @@ void save_afsi(struct afs_info *afsi, struct osl_object *obj) write_u32(buf + AFSI_LYRICS_ID_OFFSET, afsi->lyrics_id); write_u8(buf + AFSI_AUDIO_FORMAT_ID_OFFSET, afsi->audio_format_id); + write_u8(buf + AFSI_AMP_OFFSET, afsi->amp); + memset(buf + AFSI_AUDIO_FORMAT_UNUSED_OFFSET, 0, 2); } -/** - * Get the audio file selector info struct stored in an osl object. +/* + * 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. + * \return Standard. * - * \sa save_afsi(). + * \sa \ref save_afsi(). */ -int load_afsi(struct afs_info *afsi, struct osl_object *obj) +static int load_afsi(struct afs_info *afsi, struct osl_object *obj) { char *buf = obj->data; if (obj->size < AFSI_SIZE) - return -E_BAD_AFS; + return -E_BAD_AFSI; 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); @@ -77,6 +192,7 @@ int load_afsi(struct afs_info *afsi, struct osl_object *obj) afsi->lyrics_id = read_u32(buf + AFSI_LYRICS_ID_OFFSET); afsi->audio_format_id = read_u8(buf + AFSI_AUDIO_FORMAT_ID_OFFSET); + afsi->amp = read_u8(buf + AFSI_AMP_OFFSET); return 1; } @@ -96,12 +212,20 @@ enum audio_file_table_columns { NUM_AFT_COLUMNS }; +/* compare function for the hash column */ +static int aft_hash_compare(const struct osl_object *obj1, + const struct osl_object *obj2) +{ + return hash_compare((unsigned char *)obj1->data, + (unsigned char *)obj2->data); +} + 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, + .compare_function = aft_hash_compare, .data_size = HASH_SIZE }, [AFTCOL_PATH] = { @@ -133,210 +257,164 @@ static struct osl_table_description audio_file_table_desc = { .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. + * Produce a canonicalized absolute pathname. * - * Also, we don't want double slashes or slashes at the - * end that can make pathnames ambiguous. + * Returns one if the resolved path a directory, zero if it is a regular file, + * negative on errors. */ -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 *orig_path, char **resolved_path) { - char c; - const char prefix[] = AFS_AUDIO_FILE_DIR "/"; - const char *path = orig_path; - const size_t prefix_len = strlen(prefix); - - c = *path++; - if (!c) - goto bad_path; - while (c) { - if (c == '/') { - c = *path++; - switch (c) { - default: - continue; - case '/': /* double slash */ - goto bad_path; - case '.': - if (verify_dotfile(path) < 0) - goto bad_path; - } - } - c = *path++; - } - if (*orig_path != '/') - *resolved_path = prefix_path(prefix, prefix_len, orig_path); + int ret; + char *path = NULL; + struct stat statbuf; + + if (*orig_path != '/') /* we only accept absolute paths */ + goto fail; + path = realpath(orig_path, NULL); + if (!path) + goto fail; + if (stat(path, &statbuf) < 0) + goto fail; + if (S_ISREG(statbuf.st_mode)) + ret = 0; + else if (S_ISDIR(statbuf.st_mode)) + ret = 1; else - *resolved_path = para_strdup(orig_path); - return 1; -bad_path: + goto fail; + *resolved_path = path; + return ret; +fail: + *resolved_path = NULL; + free(path); return -E_BAD_PATH; } +/** The on-disk layout of a afhi struct. */ enum afhi_offsets { + /** Where the number of seconds is stored. */ AFHI_SECONDS_TOTAL_OFFSET = 0, + /** Position of the bitrate. */ AFHI_BITRATE_OFFSET = 4, + /** Position of the frequency. */ AFHI_FREQUENCY_OFFSET = 8, - AFHI_CHANNELS_OFFSET = 12, - AFHI_INFO_STRING_OFFSET = 13, - MIN_AFHI_SIZE = 14 + /** Was: Location of the audio file header. */ + AFHI_UNUSED1_OFFSET = 12, + /* Length of the audio file header. Zero means: No header. */ + AFHI_HEADER_LEN_OFFSET = 16, + /** The total number of chunks (4 bytes). */ + CHUNKS_TOTAL_OFFSET = 20, + /** The length of the audio file header (4 bytes). */ + HEADER_LEN_OFFSET = 24, + /** Size of the largest chunk in bytes. (4 bytes). */ + AFHI_MAX_CHUNK_SIZE_OFFSET = 28, + /** The seconds part of the chunk time (4 bytes). */ + CHUNK_TV_TV_SEC_OFFSET = 32, + /** The microseconds part of the chunk time (4 bytes). */ + CHUNK_TV_TV_USEC_OFFSET = 36, + /** Number of channels is stored here. (1 byte) */ + AFHI_CHANNELS_OFFSET = 40, + /** The tag info position. */ + AFHI_INFO_STRING_OFFSET = 41, + /** Minimal on-disk size of a valid afhi struct. */ + MIN_AFHI_SIZE = 47, /* at least 6 null bytes for techinfo/tags */ }; -static unsigned sizeof_afhi_buf(const struct audio_format_info *afhi) +static unsigned sizeof_afhi_buf(const struct afh_info *afhi) { if (!afhi) return 0; - return strlen(afhi->info_string) + MIN_AFHI_SIZE; + return MIN_AFHI_SIZE + + strlen(afhi->techinfo) + + strlen(afhi->tags.artist) + + strlen(afhi->tags.title) + + strlen(afhi->tags.year) + + strlen(afhi->tags.album) + + strlen(afhi->tags.comment); } -static void save_afhi(struct audio_format_info *afhi, char *buf) +static void save_afhi(struct afh_info *afhi, char *buf) { + char *p; + if (!afhi) return; - write_u32(buf + AFHI_SECONDS_TOTAL_OFFSET, - afhi->seconds_total); + 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_u32(buf + AFHI_UNUSED1_OFFSET, 0); + write_u32(buf + AFHI_HEADER_LEN_OFFSET, afhi->header_len); 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)); + write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total); + write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len); + write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size); + write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec); + write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec); + p = buf + AFHI_INFO_STRING_OFFSET; + /* + * The below sprintf(3) calls are OK because our caller already made + * sure that buf is large enough. + */ + p += sprintf(p, "%s", afhi->techinfo) + 1; + p += sprintf(p, "%s", afhi->tags.artist) + 1; + p += sprintf(p, "%s", afhi->tags.title) + 1; + p += sprintf(p, "%s", afhi->tags.year) + 1; + p += sprintf(p, "%s", afhi->tags.album) + 1; + sprintf(p, "%s", afhi->tags.comment); } -static void load_afhi(const char *buf, struct audio_format_info *afhi) +/* does not load the chunk table */ +static void load_afhi(const char *buf, struct afh_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->header_len = read_u32(buf + AFHI_HEADER_LEN_OFFSET); afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET); - strcpy(afhi->info_string, buf + AFHI_INFO_STRING_OFFSET); + afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET); + afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET); + afhi->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_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_OFFSET); + afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET; + afhi->tags.artist = afhi->techinfo + strlen(afhi->techinfo) + 1; + afhi->tags.title = afhi->tags.artist + strlen(afhi->tags.artist) + 1; + afhi->tags.year = afhi->tags.title + strlen(afhi->tags.title) + 1; + afhi->tags.album = afhi->tags.year + strlen(afhi->tags.year) + 1; + afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1; } -static unsigned sizeof_chunk_info_buf(struct audio_format_info *afhi) +/* Only used for saving the chunk table, but not for loading. */ +static unsigned sizeof_chunk_table(struct afh_info *afhi) { - if (!afhi) + if (!afhi || !afhi->chunk_table) return 0; - return 4 * afhi->chunks_total + 20; - + return 4 * (afhi->chunks_total + 1); } -/** 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) +static void save_chunk_table(struct afh_info *afhi, char *buf) { - int i; + uint32_t n; - if (!afhi) + if (!afhi->chunk_table || afhi->chunks_total == 0) 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]); + for (n = 0; n <= afhi->chunks_total; n++) + write_u32(buf + 4 * n, afhi->chunk_table[n]); } -static int load_chunk_info(struct osl_object *obj, struct audio_format_info *afhi) +static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct) { - char *buf = obj->data; int i; + size_t sz; - 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; + if (!ct->data || ct->size < 4) { + afhi->chunk_table = NULL; + return; + } + sz = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1; + afhi->chunk_table = para_malloc(sz); + for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++) + afhi->chunk_table[i] = read_u32(ct->data + 4 * i); } /** @@ -345,41 +423,41 @@ static int load_chunk_info(struct osl_object *obj, struct audio_format_info *afh * \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(). + * \return Standard. */ -int aft_get_row_of_path(char *path, struct osl_row **row) +int aft_get_row_of_path(const char *path, struct osl_row **row) { - struct osl_object obj = {.data = path, .size = strlen(path) + 1}; + struct osl_object obj = {.data = (char *)path, .size = strlen(path) + 1}; - PARA_NOTICE_LOG("audio_file_table: %p\n", audio_file_table); - return osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row); + return osl(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. + * \param row Result pointer. * - * \return The return value of the underlying call to osl_get_row(). + * \return Standard. */ -int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row) +static int aft_get_row_of_hash(unsigned char *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); + return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row)); } -/** - * Get the osl object holding the audio file selector info of a row. +/* + * Get the audio file selector info object 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(). + * \return Standard. */ -int get_afsi_object_of_row(const void *row, struct osl_object *obj) +static int get_afsi_object_of_row(const struct osl_row *row, + struct osl_object *obj) { - return osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj); + return osl(osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj)); } /** @@ -391,7 +469,7 @@ int get_afsi_object_of_row(const void *row, struct osl_object *obj) * * \return Positive on success, negative on errors. */ -int get_afsi_object_of_path(char *path, struct osl_object *obj) +static int get_afsi_object_of_path(const char *path, struct osl_object *obj) { struct osl_row *row; int ret = aft_get_row_of_path(path, &row); @@ -417,23 +495,45 @@ int get_afsi_of_row(const struct osl_row *row, struct afs_info *afsi) return load_afsi(afsi, &obj); } +/* + * Get the audio file selector info, given the path of an audio table. + * + * \param path The full path of the audio file. + * \param afsi Result pointer. + * + * \return Standard. + */ +static int get_afsi_of_path(const char *path, struct afs_info *afsi) +{ + struct osl_object obj; + int ret = get_afsi_object_of_path(path, &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. + * The result is a pointer to memory-mapped data. The caller must not attempt + * to free it. + * + * \return Standard. */ 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); + int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_PATH, + &path_obj)); + if (ret < 0) - return ret; - *path = path_obj.data; - return 1; + *path = NULL; + else + *path = path_obj.data; + return ret; } /** @@ -444,11 +544,12 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path) * * \return The return value of the underlying call to osl_get_object(). * - * \sa get_hash_of_row(). + * \sa \ref get_hash_of_row(). */ -int get_hash_object_of_aft_row(const void *row, struct osl_object *obj) +static int get_hash_object_of_aft_row(const struct osl_row *row, + struct osl_object *obj) { - return osl_get_object(audio_file_table, row, AFTCOL_HASH, obj); + return osl(osl_get_object(audio_file_table, row, AFTCOL_HASH, obj)); } /** @@ -462,7 +563,7 @@ int get_hash_object_of_aft_row(const void *row, struct osl_object *obj) * \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) +static int get_hash_of_row(const struct osl_row *row, unsigned char **hash) { struct osl_object obj; int ret = get_hash_object_of_aft_row(row, &obj); @@ -481,226 +582,303 @@ static int get_hash_of_row(const void *row, HASH_TYPE **hash) * * \return The return value of the underlying call to osl_get_object(). * - * \sa get_chunk_table_of_row(). + * After the call the members of the afhi structure point to mapped memory + * which is owned by the osl table, Hence the caller must not attempt to free + * this memory by calling \ref clear_afhi(). */ -int get_afhi_of_row(const void *row, struct audio_format_info *afhi) +int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi) { struct osl_object obj; - int ret = osl_get_object(audio_file_table, row, AFTCOL_AFHI, - &obj); + int ret = osl(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) +/* returns shmid on success */ +static int save_afd(struct audio_file_data *afd) { - struct osl_object obj; - int ret = osl_open_disk_object(audio_file_table, row, AFTCOL_CHUNKS, - &obj); + size_t size = sizeof(*afd) + sizeof_chunk_table(&afd->afhi); + int shmid, ret = shm_new(size); + void *shm_afd; + char *buf; + if (ret < 0) return ret; - ret = load_chunk_info(&obj, afhi); - osl_close_disk_object(&obj); + shmid = ret; + ret = shm_attach(shmid, ATTACH_RW, &shm_afd); + if (ret < 0) + goto err; + buf = shm_afd; + buf += sizeof(*afd); + save_chunk_table(&afd->afhi, buf); + if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */ + set_max_chunk_size(&afd->afhi); + PARA_NOTICE_LOG("max chunk size unset, re-add required\n"); + } else + PARA_INFO_LOG("using max chunk size from afhi\n"); + afd->max_chunk_size = afd->afhi.max_chunk_size; + *(struct audio_file_data *)shm_afd = *afd; + shm_detach(shm_afd); + return shmid; +err: + shm_destroy(shmid); return ret; } /** - * Mmap the given audio file and update statistics. + * Extract a afd stored in a shared memory area. * - * \param aft_row Determines the audio file to be opened and updated. + * Attach the shared memory area given by \a shmid, load the audio file data + * stored therein and detach the area afterwards. Called by vss, after + * receiving a positive response to the request for the next audio file. + + + * \param shmid The identifier of the shared memory area containing the afd. * \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. + * \return Standard. */ -int open_and_update_audio_file(struct osl_row *aft_row, struct audio_file_data *afd) +int load_afd(int shmid, 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); + void *shm_afd; + int ret; + struct osl_object obj; + ret = shm_attach(shmid, ATTACH_RO, &shm_afd); 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); + ret = shm_size(shmid, &obj.size); 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); + goto detach; + *afd = *(struct audio_file_data *)shm_afd; + obj.data = shm_afd + sizeof(*afd); + obj.size -= sizeof(*afd); + load_chunk_table(&afd->afhi, &obj); + ret = 1; +detach: + shm_detach(shm_afd); return ret; } -time_t now; - -static int get_local_time(uint64_t *seconds, char *buf, size_t size) +static int get_local_time(uint64_t *seconds, char *buf, size_t size, + time_t current_time, enum ls_listing_mode lm) { - struct tm t; + struct tm *tm; + /* + * Omit year but show time if the given value is closer to the current + * time than this many seconds. + */ + const time_t m = 6 * 30 * 24 * 3600; /* six months */ - if (!localtime_r((time_t *)seconds, &t)) + tm = localtime((time_t *)seconds); + if (!tm) return -E_LOCALTIME; - if (*seconds + 6 * 30 * 24 * 3600 > now) { - if (!strftime(buf, size, "%b %e %k:%M", &t)) + if (lm == LS_MODE_MBOX) { + if (!strftime(buf, size, "%c", tm)) + return -E_STRFTIME; + return 1; + } + if (*seconds > current_time - m && *seconds < current_time + m) { + if (!strftime(buf, size, "%b %e %k:%M", tm)) return -E_STRFTIME; return 1; } - if (!strftime(buf, size, "%b %e %Y", &t)) + /* + * If the given time is more than six month away from the current time, + * we print only the year. The additional space character in the format + * string below makes the formatted date align nicely with dates that + * contain the time (those written by the above strftime() statement). + */ + if (!strftime(buf, size, "%b %e %Y", tm)) return -E_STRFTIME; return 1; } +/** Compute the number of (decimal) digits of a number. */ #define GET_NUM_DIGITS(x, num) { \ typeof((x)) _tmp = PARA_ABS(x); \ *num = 1; \ - if ((x)) \ + if ((_tmp)) \ while ((_tmp) > 9) { \ (_tmp) /= 10; \ (*num)++; \ } \ } -static short unsigned get_duration(int seconds_total, char *buf, short unsigned max_width) +__a_const static short unsigned get_duration_width(int seconds) { 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; - } + unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60; + + if (!hours) /* less than one hour => m:ss or mm:ss => 4 or 5 digits */ + return 4 + (mins > 9); /* 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; + return width + 6; +} + +static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) +{ + unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60; + short unsigned max_width; + + if (!hours) { /* m:ss or mm:ss */ + max_width = opts->mode == LS_MODE_LONG? + opts->widths.duration_width : 4; + sprintf(buf, "%*u:%02d", max_width - 3, mins, seconds % 60); + } else { /* more than one hour => h:mm:ss, hh:mm:ss, hhh:mm:ss, ... */ + max_width = opts->mode == LS_MODE_LONG? + opts->widths.duration_width : 7; + sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins, + seconds % 60); + } } -static char *make_attribute_line(const char *att_bitmap, struct afs_info *afsi) +static int write_attribute_items(struct para_buffer *b, + const char *att_bitmap, struct afs_info *afsi) { - char *att_text, *att_line; + char *att_text; + int ret; - 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); + WRITE_STATUS_ITEM(b, SI_attributes_bitmap, "%s\n", att_bitmap); + ret = get_attribute_text(&afsi->attributes, " ", &att_text); + if (ret < 0) + return ret; + WRITE_STATUS_ITEM(b, SI_attributes_txt, "%s\n", att_text); free(att_text); - return att_line; + return ret; } -static char *make_lyrics_line(struct afs_info *afsi) +static void write_lyrics_items(struct para_buffer *b, struct afs_info *afsi) { char *lyrics_name; + + WRITE_STATUS_ITEM(b, SI_lyrics_id, "%u\n", afsi->lyrics_id); 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); + WRITE_STATUS_ITEM(b, SI_lyrics_name, "%s\n", lyrics_name? + lyrics_name : "(none)"); } -static char *make_image_line(struct afs_info *afsi) +static void write_image_items(struct para_buffer *b, struct afs_info *afsi) { char *image_name; + + WRITE_STATUS_ITEM(b, SI_image_id, "%u\n", afsi->image_id); 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); + WRITE_STATUS_ITEM(b, SI_image_name, "%s\n", image_name? + image_name : "(none)"); +} + +static void write_filename_items(struct para_buffer *b, const char *path, + bool basename) +{ + char *val; + + if (basename) { + WRITE_STATUS_ITEM(b, SI_basename, "%s\n", path); + return; + } + WRITE_STATUS_ITEM(b, SI_path, "%s\n", path); + val = para_basename(path); + WRITE_STATUS_ITEM(b, SI_basename, "%s\n", val? val : ""); + val = para_dirname(path); + WRITE_STATUS_ITEM(b, SI_directory, "%s\n", val? val : ""); + free(val); +} + +static int print_chunk_table(struct ls_data *d, struct para_buffer *b) +{ + struct osl_object chunk_table_obj; + struct osl_row *aft_row; + int ret, i; + char *buf; + + ret = aft_get_row_of_hash(d->hash, &aft_row); + if (ret < 0) + return ret; + ret = osl(osl_open_disk_object(audio_file_table, aft_row, + AFTCOL_CHUNKS, &chunk_table_obj)); + if (ret < 0) + return ret; + para_printf(b, "%s\n" + "chunk_time: %lu:%lu\nchunk_offsets: ", + d->path, + (long unsigned) d->afhi.chunk_tv.tv_sec, + (long unsigned) d->afhi.chunk_tv.tv_usec + ); + buf = chunk_table_obj.data; + for ( + i = 0; + i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size; + i++ + ) + para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i)); + para_printf(b, "\n"); + ret = 1; + osl_close_disk_object(&chunk_table_obj); + return ret; } static int print_list_item(struct ls_data *d, struct ls_options *opts, - struct para_buffer *b) + struct para_buffer *b, time_t current_time) { + const struct lls_opt_result *r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, opts->lpr); + const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME, opts->lpr); + const struct lls_opt_result *r_d = SERVER_CMD_OPT_RESULT(LS, UNIX_DATE, opts->lpr); 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; + struct afh_info *afhi = &d->afhi; + char asc_hash[2 * HASH_SIZE + 1]; if (opts->mode == LS_MODE_SHORT) { para_printf(b, "%s\n", d->path); - return 1; + ret = 1; + goto out; + } + if (opts->mode == LS_MODE_CHUNKS) { + ret = print_chunk_table(d, b); + goto out; } 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 (lls_opt_given(r_d)) + sprintf(last_played_time, "%llu", + (long long unsigned)afsi->last_played); + else { + ret = get_local_time(&afsi->last_played, last_played_time, + sizeof(last_played_time), current_time, opts->mode); + if (ret < 0) + goto out; } - + get_duration_buf(afhi->seconds_total, duration_buf, opts); if (opts->mode == LS_MODE_LONG) { + struct ls_widths *w = &opts->widths; + if (lls_opt_given(r_a)) + para_printf(b, "%*li ", opts->widths.score_width, + d->score); para_printf(b, - "%s" /* score */ "%s " /* attributes */ - "%*d " /* image_id */ - "%*d " /* lyrics_id */ - "%*d " /* bitrate */ - "%s " /* audio format */ - "%*d " /* frequency */ - "%d " /* channels */ + "%*u " /* amp */ + "%*u " /* image_id */ + "%*u " /* lyrics_id */ + "%*u " /* bitrate */ + "%*s " /* audio format */ + "%*u " /* frequency */ + "%u " /* channels */ "%s " /* duration */ - "%*d " /* num_played */ + "%*u " /* num_played */ "%s " /* last_played */ "%s\n", /* path */ - score_buf, att_buf, + w->amp_width, afsi->amp, w->image_id_width, afsi->image_id, w->lyrics_id_width, afsi->lyrics_id, w->bitrate_width, afhi->bitrate, + w->audio_format_width, audio_format_name(afsi->audio_format_id), w->frequency_width, afhi->frequency, afhi->channels, @@ -709,83 +887,278 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, last_played_time, d->path ); - return 1; + ret = 1; + goto out; } - if (opts->mode == LS_MODE_VERBOSE) { - char 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); + if (opts->mode == LS_MODE_MBOX) { + const char *bn = para_basename(d->path); 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, - audio_format_name(afsi->audio_format_id), - afhi->frequency, - afhi->channels, - duration_buf, - afsi->num_played, - last_played_time - ); - free(att_line); - free(lyrics_line); - free(image_line); - return 1; + "From foo@localhost %s\n" + "Received: from\nTo: bar\nFrom: a\n" + "Subject: %s\n\n", + last_played_time, + bn? bn : "?"); } - return 1; + write_filename_items(b, d->path, lls_opt_given(r_b)); + if (lls_opt_given(r_a)) + WRITE_STATUS_ITEM(b, SI_score, "%li\n", d->score); + ret = write_attribute_items(b, att_buf, afsi); + if (ret < 0) + goto out; + write_image_items(b, afsi); + write_lyrics_items(b, afsi); + hash_to_asc(d->hash, asc_hash); + WRITE_STATUS_ITEM(b, SI_hash, "%s\n", asc_hash); + WRITE_STATUS_ITEM(b, SI_bitrate, "%dkbit/s\n", afhi->bitrate); + WRITE_STATUS_ITEM(b, SI_format, "%s\n", + audio_format_name(afsi->audio_format_id)); + WRITE_STATUS_ITEM(b, SI_frequency, "%dHz\n", afhi->frequency); + WRITE_STATUS_ITEM(b, SI_channels, "%d\n", afhi->channels); + WRITE_STATUS_ITEM(b, SI_duration, "%s\n", duration_buf); + WRITE_STATUS_ITEM(b, SI_seconds_total, "%" PRIu32 "\n", + afhi->seconds_total); + WRITE_STATUS_ITEM(b, SI_last_played, "%s\n", last_played_time); + WRITE_STATUS_ITEM(b, SI_num_played, "%u\n", afsi->num_played); + WRITE_STATUS_ITEM(b, SI_amplification, "%u\n", afsi->amp); + WRITE_STATUS_ITEM(b, SI_chunk_time, "%lu\n", tv2ms(&afhi->chunk_tv)); + WRITE_STATUS_ITEM(b, SI_num_chunks, "%" PRIu32 "\n", + afhi->chunks_total); + WRITE_STATUS_ITEM(b, SI_max_chunk_size, "%" PRIu32 "\n", + afhi->max_chunk_size); + WRITE_STATUS_ITEM(b, SI_techinfo, "%s\n", afhi->techinfo); + WRITE_STATUS_ITEM(b, SI_artist, "%s\n", afhi->tags.artist); + WRITE_STATUS_ITEM(b, SI_title, "%s\n", afhi->tags.title); + WRITE_STATUS_ITEM(b, SI_year, "%s\n", afhi->tags.year); + WRITE_STATUS_ITEM(b, SI_album, "%s\n", afhi->tags.album); + WRITE_STATUS_ITEM(b, SI_comment, "%s\n", afhi->tags.comment); + if (opts->mode == LS_MODE_MBOX) { + struct osl_object lyrics_def; + lyr_get_def_by_id(afsi->lyrics_id, &lyrics_def); + if (lyrics_def.data) { + para_printf(b, "Lyrics:\n~~~~~~~\n%s", + (char *)lyrics_def.data); + osl_close_disk_object(&lyrics_def); + } + } +out: + return ret; } -static int ls_audio_format_compare(const void *a, const void *b) +static void make_inode_status_items(struct para_buffer *pb) { - 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); -} + struct stat statbuf = {.st_size = 0}; + char *path, mtime_str[30] = "\0"; + struct tm mtime_tm; + int ret; -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); + ret = get_audio_file_path_of_row(current_aft_row, &path); + if (ret < 0) + goto out; + ret = stat(path, &statbuf); + if (ret < 0) + goto out; + localtime_r(&statbuf.st_mtime, &mtime_tm); + ret = strftime(mtime_str, 29, "%b %d %Y", &mtime_tm); + assert(ret > 0); /* number of bytes placed in mtime_str */ +out: + WRITE_STATUS_ITEM(pb, SI_mtime, "%s\n", mtime_str); + WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024); } -static int ls_bitrate_compare(const void *a, const void *b) +/** + * Deallocate and invalidate the status item strings. + * + * This needs to be a public function so that afs.c can call it on shutdown. + */ +void free_status_items(void) { - struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; - return NUM_COMPARE(d1->afhi.bitrate, d2->afhi.bitrate); + freep(&status_items); + freep(&parser_friendly_status_items); } -static int ls_lyrics_id_compare(const void *a, const void *b) +static void make_status_items(void) { - struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; - return NUM_COMPARE(d1->afsi.lyrics_id, d2->afsi.lyrics_id); -} + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); + char *argv[] = {"ls", "--admissible", "--listing-mode=verbose"}; + struct ls_options opts = {.mode = LS_MODE_VERBOSE}; + struct para_buffer pb = {.max_size = shm_get_shmmax() - 1}; + time_t current_time; + int ret; -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); -} + free_status_items(); + if (!status_item_ls_data.path) /* no audio file open */ + return; + ret = lls_parse(ARRAY_SIZE(argv), argv, cmd, &opts.lpr, NULL); + assert(ret >= 0); + time(¤t_time); + ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time); + if (ret < 0) + goto out; + make_inode_status_items(&pb); + status_items = pb.buf; + + memset(&pb, 0, sizeof(pb)); + pb.max_size = shm_get_shmmax() - 1; + pb.flags = PBF_SIZE_PREFIX; + ret = print_list_item(&status_item_ls_data, &opts, &pb, current_time); + if (ret < 0) + goto out; + make_inode_status_items(&pb); + parser_friendly_status_items = pb.buf; + ret = 1; +out: + if (ret < 0) { + PARA_WARNING_LOG("could not create status items: %s\n", + para_strerror(-ret)); + free_status_items(); + } + lls_free_parse_result(opts.lpr, cmd); +} + +/** + * Open the audio file with highest score and set up an afd structure. + * + * \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 shmid on success, negative on errors. + */ +int open_and_update_audio_file(struct audio_file_data *afd) +{ + unsigned char file_hash[HASH_SIZE]; + struct osl_object afsi_obj; + struct afs_info new_afsi; + int ret; + struct afsi_change_event_data aced; + struct osl_object map, chunk_table_obj; + struct ls_data *d = &status_item_ls_data; + unsigned char *tmp_hash; +again: + ret = score_get_best(¤t_aft_row, &d->score); + if (ret < 0) + return ret; + /* + * get_hash_of_row() and get_audio_file_path_of_row() initialize + * their pointer argument to point to memory-mapped files. These pointers + * become stale after a new audio file has been added or after the + * server process received SIGHUP. For in both cases libosl unmaps and + * remaps the underlying database files, and this remapping may well + * change the starting address of the mapping. To avoid stale pointer + * references we create copies on the heap. + */ + ret = get_hash_of_row(current_aft_row, &tmp_hash); + if (ret < 0) + return ret; + if (!d->hash) + d->hash = para_malloc(HASH_SIZE); + memcpy(d->hash, tmp_hash, HASH_SIZE); + free(d->path); + ret = get_audio_file_path_of_row(current_aft_row, &d->path); + if (ret < 0) + return ret; + PARA_NOTICE_LOG("%s\n", d->path); + d->path = para_strdup(d->path); + + ret = get_afsi_object_of_row(current_aft_row, &afsi_obj); + if (ret < 0) + return ret; + ret = load_afsi(&d->afsi, &afsi_obj); + if (ret < 0) + return ret; + ret = get_afhi_of_row(current_aft_row, &afd->afhi); + if (ret < 0) + return ret; + d->afhi = afd->afhi; + d->afhi.chunk_table = afd->afhi.chunk_table = NULL; + ret = osl(osl_open_disk_object(audio_file_table, current_aft_row, + AFTCOL_CHUNKS, &chunk_table_obj)); + if (ret < 0) { + if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id)) + return ret; + PARA_INFO_LOG("no chunk table for %s\n", d->path); + chunk_table_obj.data = NULL; + chunk_table_obj.size = 0; + } else { + PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size); + } + ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd); + if (ret < 0) + goto out; + hash_function(map.data, map.size, file_hash); + ret = hash_compare(file_hash, d->hash); + para_munmap(map.data, map.size); + if (ret) { + ret = -E_HASH_MISMATCH; + goto out; + } + new_afsi = d->afsi; + new_afsi.num_played++; + new_afsi.last_played = time(NULL); + save_afsi(&new_afsi, &afsi_obj); /* in-place update */ + + afd->audio_format_id = d->afsi.audio_format_id; + load_chunk_table(&afd->afhi, &chunk_table_obj); + aced.aft_row = current_aft_row; + aced.old_afsi = &d->afsi; + /* + * No need to update the status items as the AFSI_CHANGE event will + * recreate them. + */ + ret = afs_event(AFSI_CHANGE, NULL, &aced); + if (ret < 0) + goto out; + ret = save_afd(afd); +out: + free(afd->afhi.chunk_table); + if (chunk_table_obj.data) + osl_close_disk_object(&chunk_table_obj); + if (ret < 0) { + PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret)); + ret = score_delete(current_aft_row); + if (ret >= 0) + goto again; + } + return ret; +} + +static int ls_hash_compare(const void *a, const void *b) +{ + struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b; + return memcmp(d1->hash, d2->hash, HASH_SIZE); +} + +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) { @@ -823,10 +1196,18 @@ static int ls_path_compare(const void *a, const void *b) return strcmp(d1->path, d2->path); } +static inline bool admissible_only(struct ls_options *opts) +{ + return SERVER_CMD_OPT_GIVEN(LS, ADMISSIBLE, opts->lpr) + || opts->sorting == LS_SORT_BY_SCORE; +} + static int sort_matching_paths(struct ls_options *options) { + const struct lls_opt_result *r_b = SERVER_CMD_OPT_RESULT(LS, BASENAME, + options->lpr); size_t nmemb = options->num_matching_paths; - size_t size = sizeof(uint32_t); + size_t size = sizeof(*options->data_ptr); int (*compar)(const void *, const void *); int i; @@ -835,13 +1216,13 @@ static int sort_matching_paths(struct ls_options *options) 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; + if (admissible_only(options)) { + if (options->sorting == LS_SORT_BY_SCORE) + return 1; + } else { + if (options->sorting == LS_SORT_BY_PATH && !lls_opt_given(r_b)) + return 1; + } switch (options->sorting) { case LS_SORT_BY_PATH: @@ -866,6 +1247,8 @@ static int sort_matching_paths(struct ls_options *options) compar = ls_duration_compare; break; case LS_SORT_BY_AUDIO_FORMAT: compar = ls_audio_format_compare; break; + case LS_SORT_BY_HASH: + compar = ls_hash_compare; break; default: return -E_BAD_SORT; } @@ -879,38 +1262,42 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) { int ret, i; struct ls_options *options = ls_opts; + bool basename_given = SERVER_CMD_OPT_GIVEN(LS, BASENAME, options->lpr); struct ls_data *d; struct ls_widths *w; unsigned short num_digits; - unsigned tmp; + unsigned tmp, num_inputs; struct osl_row *aft_row; long score; char *path; - if (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { + if (admissible_only(options)) { ret = get_score_and_aft_row(row, &score, &aft_row); if (ret < 0) return ret; - } else + } else { aft_row = row; + score = 0; + } ret = get_audio_file_path_of_row(aft_row, &path); if (ret < 0) return ret; - if (!(options->flags & LS_FLAG_FULL_PATH)) { + if (basename_given) { 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); + num_inputs = lls_num_inputs(options->lpr); + if (num_inputs > 0) { + for (i = 0; i < num_inputs; i++) { + ret = fnmatch(lls_input(i, options->lpr), path, 0); if (!ret) break; if (ret == FNM_NOMATCH) continue; return -E_FNMATCH; } - if (i >= options->num_patterns) /* no match */ + if (i >= num_inputs) /* no match */ return 1; } tmp = options->num_matching_paths++; @@ -930,7 +1317,7 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) d->path = path; ret = get_hash_of_row(aft_row, &d->hash); if (ret < 0) - return ret; + goto err; w = &options->widths; GET_NUM_DIGITS(d->afsi.image_id, &num_digits); w->image_id_width = PARA_MAX(w->image_id_width, num_digits); @@ -942,192 +1329,153 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) 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 the number of chars to print this amount of time */ + num_digits = get_duration_width(d->afhi.seconds_total); + w->duration_width = PARA_MAX(w->duration_width, num_digits); + GET_NUM_DIGITS(d->afsi.amp, &num_digits); + w->amp_width = PARA_MAX(w->amp_width, num_digits); + num_digits = strlen(audio_format_name(d->afsi.audio_format_id)); + w->audio_format_width = PARA_MAX(w->audio_format_width, num_digits); + if (admissible_only(options)) { 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; +err: + return ret; } -static int com_ls_callback(const struct osl_object *query, - struct osl_object *ls_output) +static int com_ls_callback(struct afs_callback_arg *aca) { - struct ls_options *opts = query->data; - char *p, *pattern_start = (char *)query->data + sizeof(*opts); - struct para_buffer b = {.buf = NULL, .size = 0}; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); + struct ls_options *opts = aca->query.data; int i = 0, ret; + time_t current_time; + const struct lls_opt_result *r_r; - 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 = lls_deserialize_parse_result( + (char *)aca->query.data + sizeof(*opts), cmd, &opts->lpr); + assert(ret >= 0); + r_r = SERVER_CMD_OPT_RESULT(LS, REVERSE, opts->lpr); + + aca->pbout.flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0; + if (admissible_only(opts)) ret = admissible_file_loop(opts, prepare_ls_row); else - ret = osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, - prepare_ls_row); + ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, + prepare_ls_row)); if (ret < 0) goto out; - ret = opts->num_patterns? -E_NO_MATCH : 0; - if (!opts->num_matching_paths) { - PARA_NOTICE_LOG("no match, ret: %d\n", ret); + if (opts->num_matching_paths == 0) { + ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0; goto out; } ret = sort_matching_paths(opts); if (ret < 0) goto out; - if (opts->flags & LS_FLAG_REVERSE) + time(¤t_time); + if (lls_opt_given(r_r)) for (i = opts->num_matching_paths - 1; i >= 0; i--) { - ret = print_list_item(opts->data_ptr[i], opts, &b); + ret = print_list_item(opts->data_ptr[i], opts, + &aca->pbout, current_time); if (ret < 0) - break; + goto out; } else for (i = 0; i < opts->num_matching_paths; i++) { - ret = print_list_item(opts->data_ptr[i], opts, &b); + ret = print_list_item(opts->data_ptr[i], opts, + &aca->pbout, current_time); if (ret < 0) - break; + goto out; } - ret = 1; out: - ls_output->data = b.buf; - PARA_NOTICE_LOG("ls_outoute.data: %p\n", ls_output->data); - ls_output->size = b.size; + lls_free_parse_result(opts->lpr, cmd); 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(int fd, int argc, char * const * const argv) +static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) { - 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; - } + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); + struct ls_options *opts; + struct osl_object query; + const struct lls_opt_result *r_l = SERVER_CMD_OPT_RESULT(LS, LISTING_MODE, + lpr); + const struct lls_opt_result *r_s = SERVER_CMD_OPT_RESULT(LS, SORT, lpr); + int ret; + char *slpr; + + ret = lls_serialize_parse_result(lpr, cmd, NULL, &query.size); + assert(ret >= 0); + query.size += sizeof(*opts); + query.data = para_malloc(query.size); + opts = query.data; + memset(opts, 0, sizeof(*opts)); + slpr = query.data + sizeof(*opts); + ret = lls_serialize_parse_result(lpr, cmd, &slpr, NULL); + assert(ret >= 0); + opts->mode = LS_MODE_SHORT; + opts->sorting = LS_SORT_BY_PATH; + if (lls_opt_given(r_l)) { + const char *val = lls_string_val(0, r_l); + if (!strcmp(val, "l") || !strcmp(val, "long")) + opts->mode = LS_MODE_LONG; + else if (!strcmp(val, "s") || !strcmp(val, "short")) + opts->mode = LS_MODE_SHORT; + else if (!strcmp(val, "v") || !strcmp(val, "verbose")) + opts->mode = LS_MODE_VERBOSE; + else if (!strcmp(val, "m") || !strcmp(val, "mbox")) + opts->mode = LS_MODE_MBOX; + else if (!strcmp(val, "c") || !strcmp(val, "chunk-table")) + opts->mode = LS_MODE_MBOX; + else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly")) + opts->mode = LS_MODE_PARSER; + else { + ret = -E_AFT_SYNTAX; + goto out; } - 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) { - ret = send_buffer(fd, (char *)ls_output.data); - free(ls_output.data); + if (lls_opt_given(r_s)) { + const char *val = lls_string_val(0, r_s); + if (!strcmp(val, "p") || !strcmp(val, "path")) + opts->sorting = LS_SORT_BY_PATH; + else if (!strcmp(val, "s") || !strcmp(val, "score")) + opts->sorting = LS_SORT_BY_SCORE; + else if (!strcmp(val, "l") || !strcmp(val, "lastplayed")) + opts->sorting = LS_SORT_BY_LAST_PLAYED; + else if (!strcmp(val, "n") || !strcmp(val, "numplayed")) + opts->sorting = LS_SORT_BY_NUM_PLAYED; + else if (!strcmp(val, "f") || !strcmp(val, "frquency")) + opts->sorting = LS_SORT_BY_FREQUENCY; + else if (!strcmp(val, "c") || !strcmp(val, "channels")) + opts->sorting = LS_SORT_BY_CHANNELS; + else if (!strcmp(val, "i") || !strcmp(val, "image-id")) + opts->sorting = LS_SORT_BY_IMAGE_ID; + else if (!strcmp(val, "y") || !strcmp(val, "lyrics-id")) + opts->sorting = LS_SORT_BY_LYRICS_ID; + else if (!strcmp(val, "b") || !strcmp(val, "bitrate")) + opts->sorting = LS_SORT_BY_BITRATE; + else if (!strcmp(val, "d") || !strcmp(val, "duration")) + opts->sorting = LS_SORT_BY_DURATION; + else if (!strcmp(val, "a") || !strcmp(val, "audio-format")) + opts->sorting = LS_SORT_BY_AUDIO_FORMAT; + else if (!strcmp(val, "h") || !strcmp(val, "hash")) + opts->sorting = LS_SORT_BY_HASH; + else { + ret = -E_AFT_SYNTAX; + goto out; + } } + ret = send_callback_request(com_ls_callback, &query, + afs_cb_result_handler, cc); +out: + free(query.data); return ret; } +EXPORT_SERVER_CMD_HANDLER(ls); /** * Call the given function for each file in the audio file table. @@ -1135,603 +1483,1159 @@ int com_afs_ls(int fd, int argc, char * const * const argv) * \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(). + * \return Standard. */ int audio_file_loop(void *private_data, osl_rbtree_loop_func *func) { - return osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data, - func); + return osl(osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data, + func)); } -static struct osl_row *find_hash_sister(HASH_TYPE *hash) +static int find_hash_sister(unsigned char *hash, struct osl_row **result) { - const struct osl_object obj = {.data = hash, .size = HASH_SIZE}; - struct osl_row *row; + int ret = aft_get_row_of_hash(hash, result); + + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) + return 0; + return ret; +} + +static int find_path_brother(const char *path, struct osl_row **result) +{ + int ret = aft_get_row_of_path(path, result); - osl_get_row(audio_file_table, AFTCOL_HASH, &obj, &row); - return row; + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) + return 0; + return ret; } -enum aft_row_offsets { - AFTROW_AFHI_OFFSET_POS = 0, - AFTROW_CHUNKS_OFFSET_POS = 2, - AFTROW_AUDIO_FORMAT_OFFSET = 4, - AFTROW_FLAGS_OFFSET = 5, - AFTROW_HASH_OFFSET = 9, - AFTROW_PATH_OFFSET = (AFTROW_HASH_OFFSET + HASH_SIZE), +/** The format of the data stored by save_audio_file_data(). */ +enum com_add_buffer_offsets { + /* afhi (if present) starts at this offset. */ + CAB_AFHI_OFFSET_POS = 0, + /** Start of the chunk table (if present). */ + CAB_CHUNKS_OFFSET_POS = 4, + /** Start of the (serialized) lopsub parse result. */ + CAB_LPR_OFFSET = 8, + /** Audio format id. */ + CAB_AUDIO_FORMAT_ID_OFFSET = 12, + /** The hash of the audio file being added. */ + CAB_HASH_OFFSET = 13, + /** Start of the path of the audio file. */ + CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH_SIZE), }; -/* 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, +/* + * Store the given data to a single buffer. Doesn't need the audio file selector + * info struct as the server knows it as well. + * + * It's OK to call this with afhi == NULL. In this case, the audio format + * handler info won't be stored in the buffer. + */ +static void save_add_callback_buffer(unsigned char *hash, const char *path, + struct afh_info *afhi, const char *slpr, size_t slpr_size, uint8_t audio_format_num, 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); + size_t size = CAB_PATH_OFFSET + path_len + afhi_size + + sizeof_chunk_table(afhi) + slpr_size; char *buf = para_malloc(size); - uint16_t pos; - - write_u8(buf + AFTROW_AUDIO_FORMAT_OFFSET, audio_format_num); - write_u32(buf + AFTROW_FLAGS_OFFSET, flags); - - memcpy(buf + AFTROW_HASH_OFFSET, hash, HASH_SIZE); - 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 %zu\n", buf + pos + afhi_size - 1, - pos + afhi_size - 1); - write_u16(buf + AFTROW_AFHI_OFFSET_POS, pos); + uint32_t pos; + + assert(size <= ~(uint32_t)0); + write_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET, audio_format_num); + memcpy(buf + CAB_HASH_OFFSET, hash, HASH_SIZE); + strcpy(buf + CAB_PATH_OFFSET, path); + pos = CAB_PATH_OFFSET + path_len; + write_u32(buf + CAB_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); + write_u32(buf + CAB_CHUNKS_OFFSET_POS, pos); + if (afhi) { + save_chunk_table(afhi, buf + pos); + pos += sizeof_chunk_table(afhi); + } + write_u32(buf + CAB_LPR_OFFSET, pos); + memcpy(buf + pos, slpr, slpr_size); + assert(pos + slpr_size == size); 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 +Overview of the add command. -*/ +Input: What was passed to the callback by the command handler. +~~~~~~ +HS: Hash sister. Whether an audio file with identical hash + already exists in the osl database. +PB: Path brother. Whether a file with the given path exists + in the table. -#define ADD_FLAG_LAZY 1 -#define ADD_FLAG_FORCE 2 -#define ADD_FLAG_VERBOSE 4 +F: Force flag given. Whether add was called with -f. -/* TODO: change log messages so that they get written to the result buffer */ +output: Action performed by the callback. +~~~~~~~ +AFHI: Whether afhi and chunk table are computed and sent. +ACTION: Table modifications to be done by the callback. + ++----+----+---+------+---------------------------------------------------+ +| 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 ++----+----+---+------+---------------------------------------------------+ + +Notes: + + afhi <=> force or no HS + F => AFHI + +*/ -static int com_add_callback(const struct osl_object *query, - __a_unused struct osl_object *result) +static int com_add_callback(struct afs_callback_arg *aca) { - char *buf = query->data, *path; + char *buf = aca->query.data, *path; struct osl_row *pb, *aft_row; - const struct osl_row *hs; + struct osl_row *hs; struct osl_object objs[NUM_AFT_COLUMNS]; - HASH_TYPE *hash; + unsigned char *hash; char asc[2 * HASH_SIZE + 1]; int ret; char afsi_buf[AFSI_SIZE]; - uint32_t flags = read_u32(buf + AFTROW_FLAGS_OFFSET); + char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET); struct afs_info default_afsi = {.last_played = 0}; - - hash = (HASH_TYPE *)buf + AFTROW_HASH_OFFSET; - hash_to_asc(hash, asc);; - objs[AFTCOL_HASH].data = buf + AFTROW_HASH_OFFSET; + uint16_t afhi_offset, chunks_offset; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD); + const struct lls_opt_result *r_f, *r_v; + + ret = lls_deserialize_parse_result(slpr, cmd, &aca->lpr); + assert(ret >= 0); + r_f = SERVER_CMD_OPT_RESULT(ADD, FORCE, aca->lpr); + r_v = SERVER_CMD_OPT_RESULT(ADD, VERBOSE, aca->lpr); + + hash = (unsigned char *)buf + CAB_HASH_OFFSET; + hash_to_asc(hash, asc); + objs[AFTCOL_HASH].data = buf + CAB_HASH_OFFSET; objs[AFTCOL_HASH].size = HASH_SIZE; - path = buf + AFTROW_PATH_OFFSET; + path = buf + CAB_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; + PARA_INFO_LOG("request to add %s\n", path); + ret = find_hash_sister(hash, &hs); + if (ret < 0) + goto out; + ret = find_path_brother(path, &pb); + if (ret < 0) + goto out; + if (hs && pb && hs == pb && !lls_opt_given(r_f)) { + if (lls_opt_given(r_v)) + para_printf(&aca->pbout, "ignoring duplicate\n"); + ret = 1; + goto out; } 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 (lls_opt_given(r_v)) + para_printf(&aca->pbout, "removing %s\n", path); + ret = afs_event(AUDIO_FILE_REMOVE, &aca->pbout, pb); if (ret < 0) - return ret; + goto out; + ret = osl(osl_del_row(audio_file_table, pb)); + if (ret < 0) + goto out; 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 (lls_opt_given(r_v)) { + ret = osl(osl_get_object(audio_file_table, hs, + AFTCOL_PATH, &obj)); + if (ret < 0) + goto out; + para_printf(&aca->pbout, "renamed from %s\n", + (char *)obj.data); + } + ret = osl(osl_update_object(audio_file_table, hs, AFTCOL_PATH, + &objs[AFTCOL_PATH])); if (ret < 0) - return ret; - if (!(flags & ADD_FLAG_FORCE)) - return ret; + goto out; + ret = afs_event(AUDIO_FILE_RENAME, &aca->pbout, hs); + if (ret < 0) + goto out; + if (!lls_opt_given(r_f)) + goto out; } /* 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); + afhi_offset = read_u32(buf + CAB_AFHI_OFFSET_POS); + chunks_offset = read_u32(buf + CAB_CHUNKS_OFFSET_POS); objs[AFTCOL_AFHI].data = buf + afhi_offset; objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset; + ret = -E_NO_AFHI; if (!objs[AFTCOL_AFHI].size) /* "impossible" */ - return -E_NO_AFHI; + goto out; objs[AFTCOL_CHUNKS].data = buf + chunks_offset; - objs[AFTCOL_CHUNKS].size = query->size - chunks_offset; + objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset; if (pb && !hs) { /* update pb's hash */ char old_asc[2 * HASH_SIZE + 1]; - HASH_TYPE *old_hash; + unsigned char *old_hash; ret = get_hash_of_row(pb, &old_hash); if (ret < 0) - return ret; + goto out; hash_to_asc(old_hash, old_asc); - if (flags & ADD_FLAG_VERBOSE) - PARA_NOTICE_LOG("file change: %s %s -> %s\n", path, + if (lls_opt_given(r_v)) + para_printf(&aca->pbout, "file change: %s -> %s\n", old_asc, asc); - ret = osl_update_object(audio_file_table, pb, AFTCOL_HASH, - &objs[AFTCOL_HASH]); + ret = osl(osl_update_object(audio_file_table, pb, AFTCOL_HASH, + &objs[AFTCOL_HASH])); if (ret < 0) - return ret; + goto out; } if (hs || pb) { /* (hs != NULL and pb != NULL) implies hs == pb */ - const void *row = pb? pb : hs; + struct osl_row *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 (lls_opt_given(r_v)) + para_printf(&aca->pbout, + "updating afhi and chunk table\n"); + ret = osl(osl_update_object(audio_file_table, row, AFTCOL_AFHI, + &objs[AFTCOL_AFHI])); if (ret < 0) - return ret; - ret = mood_update_audio_file(row, NULL); + goto out; + /* truncate the file to size zero if there is no chunk table */ + ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, + &objs[AFTCOL_CHUNKS])); if (ret < 0) - return ret; + goto out; + ret = afs_event(AFHI_CHANGE, &aca->pbout, row); + goto out; } /* new entry, use default afsi */ + if (lls_opt_given(r_v)) + para_printf(&aca->pbout, "new file\n"); default_afsi.last_played = time(NULL) - 365 * 24 * 60 * 60; - default_afsi.audio_format_id = read_u8(buf + AFTROW_AUDIO_FORMAT_OFFSET); + default_afsi.audio_format_id = read_u8(buf + CAB_AUDIO_FORMAT_ID_OFFSET); - 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(&default_afsi, &objs[AFTCOL_AFSI]); - ret = osl_add_and_get_row(audio_file_table, objs, &aft_row); + ret = osl(osl_add_and_get_row(audio_file_table, objs, &aft_row)); if (ret < 0) - return ret; - return mood_update_audio_file(aft_row, NULL); + goto out; + ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row); +out: + if (ret < 0) + para_printf(&aca->pbout, "could not add %s\n", path); + lls_free_parse_result(aca->lpr, cmd); + return ret; } +/* Used by com_add(). */ struct private_add_data { - int fd; - uint32_t flags; + /* The pointer passed to the original command handler. */ + struct command_context *cc; + /* Contains the flags given at the command line. */ + struct lls_parse_result *lpr; + /* Serialized lopsub parse result. */ + char *slpr; + /* Number of bytes. */ + size_t slpr_size; }; -static int path_brother_callback(const struct osl_object *query, - struct osl_object *result) +static int path_brother_callback(struct afs_callback_arg *aca) { - char *path = query->data; + char *path = aca->query.data; struct osl_row *path_brother; - int ret = aft_get_row_of_path(path, &path_brother); - if (ret < 0) + int ret = find_path_brother(path, &path_brother); + if (ret <= 0) return ret; - result->data = para_malloc(sizeof(path_brother)); - result->size = sizeof(path_brother); - *(struct osl_row **)(result->data) = path_brother; - return 1; + return pass_buffer_as_shm(aca->fd, SBD_OUTPUT, (char *)&path_brother, + sizeof(path_brother)); } -static int hash_sister_callback(const struct osl_object *query, - struct osl_object *result) +static int hash_sister_callback(struct afs_callback_arg *aca) { - HASH_TYPE *hash = query->data; + unsigned char *hash = aca->query.data; struct osl_row *hash_sister; + int ret = find_hash_sister(hash, &hash_sister); + + if (ret <= 0) + return ret; + return pass_buffer_as_shm(aca->fd, SBD_OUTPUT, (char *)&hash_sister, + sizeof(hash_sister)); +} - hash_sister = find_hash_sister(hash); - if (!hash_sister) - return -E_RB_KEY_NOT_FOUND; - result->data = para_malloc(sizeof(hash_sister)); - result->size = sizeof(hash_sister); - *(struct osl_row **)(result->data) = hash_sister; +static int get_row_pointer_from_result(struct osl_object *result, + __a_unused uint8_t band, void *private) +{ + struct osl_row **row = private; + + if (band == SBD_OUTPUT) + *row = *(struct osl_row **)(result->data); return 1; } -static int add_one_audio_file(const char *arg, const void *private_data) +static int add_one_audio_file(const char *path, void *private_data) { - int ret; + int ret, send_ret = 1, fd; uint8_t format_num = -1; - const struct private_add_data *pad = private_data; - struct audio_format_info afhi, *afhi_ptr = NULL; + struct private_add_data *pad = private_data; + struct afh_info afhi, *afhi_ptr = NULL; struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */ - struct osl_object map, obj = {.data = NULL}, query, result; - char *path = NULL; - HASH_TYPE hash[HASH_SIZE]; - - afhi.header_offset = 0; - afhi.header_len = 0; - ret = verify_path(arg, &path); - if (ret < 0) + struct osl_object map, obj = {.data = NULL}, query; + unsigned char hash[HASH_SIZE]; + bool a_given = SERVER_CMD_OPT_GIVEN(ADD, ALL, pad->lpr); + bool f_given = SERVER_CMD_OPT_GIVEN(ADD, FORCE, pad->lpr); + bool l_given = SERVER_CMD_OPT_GIVEN(ADD, LAZY, pad->lpr); + bool v_given = SERVER_CMD_OPT_GIVEN(ADD, VERBOSE, pad->lpr); + + ret = guess_audio_format(path); + if (ret < 0 && !a_given) { + ret = 0; goto out_free; - query.data = path; + } + query.data = (char *)path; query.size = strlen(path) + 1; - ret = send_callback_request(path_brother_callback, &query, &result); - if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) + ret = send_callback_request(path_brother_callback, &query, + get_row_pointer_from_result, &pb); + if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) goto out_free; - if (ret >= 0) { - pb = *(struct osl_row **)result.data; - free(result.data); - } ret = 1; - if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */ - if (pad->flags & ADD_FLAG_VERBOSE) - ret = send_va_buffer(pad->fd, "lazy-ignore: %s\n", path); + if (pb && l_given) { /* lazy is really cheap */ + if (v_given) + send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT, + "lazy-ignore: %s\n", path); goto out_free; } /* We still want to add this file. Compute its hash. */ - ret = mmap_full_file(path, O_RDONLY, &map); + ret = mmap_full_file(path, O_RDONLY, &map.data, &map.size, &fd); if (ret < 0) goto out_free; hash_function(map.data, map.size, hash); - /* Check whether database contains file with the same hash. */ + /* Check whether the database contains a file with the same hash. */ query.data = hash; query.size = HASH_SIZE; - ret = send_callback_request(hash_sister_callback, &query, &result); - if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) - goto out_free; - if (ret >= 0) { - hs = *(struct osl_row **)result.data; - free(result.data); - } + ret = send_callback_request(hash_sister_callback, &query, + get_row_pointer_from_result, &hs); + if (ret < 0) + goto out_unmap; /* Return success if we already know this file. */ ret = 1; - if (pb && hs && hs == pb && (!(pad->flags & ADD_FLAG_FORCE))) { - if (pad->flags & ADD_FLAG_VERBOSE) - ret = send_va_buffer(pad->fd, - "not forcing update: %s\n", path); + if (pb && hs && hs == pb && !f_given) { + if (v_given) + send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT, + "%s exists, not forcing update\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. + * We won't recalculate the audio format info and the chunk table if + * there is a hash sister and FORCE was not given. */ - if (!hs || (pad->flags & ADD_FLAG_FORCE)) { - ret = compute_afhi(path, map.data, map.size, &afhi); + if (!hs || f_given) { + ret = compute_afhi(path, map.data, map.size, fd, &afhi); if (ret < 0) goto out_unmap; format_num = ret; afhi_ptr = &afhi; } munmap(map.data, map.size); - save_audio_file_info(hash, path, afhi_ptr, pad->flags, format_num, &obj); + close(fd); + if (v_given) { + send_ret = send_sb_va(&pad->cc->scc, SBD_OUTPUT, + "adding %s\n", path); + if (send_ret < 0) + goto out_free; + } + save_add_callback_buffer(hash, path, afhi_ptr, pad->slpr, + pad->slpr_size, format_num, &obj); /* Ask afs to consider this entry for adding. */ - ret = send_callback_request(com_add_callback, &obj, NULL); + ret = send_callback_request(com_add_callback, &obj, + afs_cb_result_handler, pad->cc); goto out_free; out_unmap: + close(fd); munmap(map.data, map.size); out_free: - if (ret < 0) - send_va_buffer(pad->fd, "failed to add %s (%s)\n", path? - path : arg, PARA_STRERROR(-ret)); + if (ret < 0 && send_ret >= 0) + send_ret = send_sb_va(&pad->cc->scc, SBD_ERROR_LOG, + "failed to add %s (%s)\n", path, para_strerror(-ret)); free(obj.data); - free(path); - if (afhi_ptr) - free(afhi_ptr->chunk_table); - return 1; /* it's not an error if not all files could be added */ + clear_afhi(afhi_ptr); + /* Stop adding files only on send errors. */ + return send_ret; } -int com_add(int fd, int argc, char * const * const argv) +static int com_add(struct command_context *cc, struct lls_parse_result *lpr) { int i, ret; - struct private_add_data pad = {.fd = fd, .flags = 0}; - struct stat statbuf; - - PARA_NOTICE_LOG("argv[1]: %s\n", argv[1]); - 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; + struct private_add_data pad = {.cc = cc, .lpr = lpr}; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD); + unsigned num_inputs; + char *errctx; + + ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + ret = lls_serialize_parse_result(lpr, cmd, &pad.slpr, &pad.slpr_size); + assert(ret >= 0); + num_inputs = lls_num_inputs(lpr); + for (i = 0; i < num_inputs; i++) { + char *path; + ret = verify_path(lls_input(i, lpr), &path); + if (ret < 0) { + ret = send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s: %s\n", + lls_input(i, lpr), para_strerror(-ret)); + if (ret < 0) + goto out; continue; } - if (!strcmp(arg, "-v")) { - pad.flags |= ADD_FLAG_VERBOSE; - continue; + if (ret == 1) /* directory */ + ret = for_each_file_in_dir(path, add_one_audio_file, + &pad); + else /* regular file */ + ret = add_one_audio_file(path, &pad); + if (ret < 0) { + send_sb_va(&cc->scc, SBD_OUTPUT, "%s: %s\n", path, + para_strerror(-ret)); + free(path); + return ret; } - } - if (argc <= i) - return -E_AFT_SYNTAX; - for (; i < argc; i++) { - char *path = para_strdup(argv[i]); - size_t len = strlen(path); - while (len > 1 && path[--len] == '/') - path[len] = '\0'; - ret = stat(path, &statbuf); - if (ret < 0) - PARA_NOTICE_LOG("failed to stat %s (%s)", path, - strerror(errno)); - else - if (S_ISDIR(statbuf.st_mode)) - for_each_file_in_dir(path, add_one_audio_file, - &pad); - else - add_one_audio_file(path, &pad); free(path); } ret = 1; +out: + free(pad.slpr); return ret; - } - -struct com_touch_options { - long num_played; - long last_played; - long lyrics_id; - long image_id; +EXPORT_SERVER_CMD_HANDLER(add); + +/** Flags used by the touch command. */ +enum touch_flags { + /** Whether the \p FNM_PATHNAME flag should be passed to fnmatch(). */ + TOUCH_FLAG_FNM_PATHNAME = 1, + /** Activates verbose mode. */ + TOUCH_FLAG_VERBOSE = 2 }; -static int com_touch_callback(const struct osl_object *query, - __a_unused struct osl_object *result) +static int touch_audio_file(__a_unused struct osl_table *table, + struct osl_row *row, const char *name, void *data) { - 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; + struct afs_callback_arg *aca = data; + bool v_given = SERVER_CMD_OPT_GIVEN(TOUCH, VERBOSE, aca->lpr); + const struct lls_opt_result *r_n, *r_l, *r_i, *r_y, *r_a; + int ret; + struct osl_object obj; + struct afs_info old_afsi, new_afsi; + bool no_options; + struct afsi_change_event_data aced; + + r_n = SERVER_CMD_OPT_RESULT(TOUCH, NUMPLAYED, aca->lpr); + r_l = SERVER_CMD_OPT_RESULT(TOUCH, LASTPLAYED, aca->lpr); + r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr); + r_y = SERVER_CMD_OPT_RESULT(TOUCH, LYRICS_ID, aca->lpr); + r_a = SERVER_CMD_OPT_RESULT(TOUCH, AMP, aca->lpr); + no_options = !lls_opt_given(r_n) && !lls_opt_given(r_l) && !lls_opt_given(r_i) + && !lls_opt_given(r_y) && !lls_opt_given(r_a); + + ret = get_afsi_object_of_row(row, &obj); + if (ret < 0) { + para_printf(&aca->pbout, "cannot touch %s\n", name); + return ret; + } + ret = load_afsi(&old_afsi, &obj); + if (ret < 0) { + para_printf(&aca->pbout, "cannot touch %s\n", name); + return ret; + } + new_afsi = old_afsi; + if (no_options) { + new_afsi.num_played++; + new_afsi.last_played = time(NULL); + if (v_given) + para_printf(&aca->pbout, "%s: num_played = %u, " + "last_played = now()\n", name, + new_afsi.num_played); + } else { + if (lls_opt_given(r_l)) + new_afsi.last_played = lls_uint64_val(0, r_l); + if (lls_opt_given(r_n)) + new_afsi.num_played = lls_uint32_val(0, r_n); + if (lls_opt_given(r_i)) + new_afsi.image_id = lls_uint32_val(0, r_i); + if (lls_opt_given(r_y)) + new_afsi.lyrics_id = lls_uint32_val(0, r_y); + if (lls_opt_given(r_a)) + new_afsi.amp = lls_uint32_val(0, r_a); + if (v_given) + para_printf(&aca->pbout, "touching %s\n", name); + } + save_afsi(&new_afsi, &obj); /* in-place update */ + aced.aft_row = row; + aced.old_afsi = &old_afsi; + return afs_event(AFSI_CHANGE, &aca->pbout, &aced); +} - 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; +static int com_touch_callback(struct afs_callback_arg *aca) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH); + bool p_given; + const struct lls_opt_result *r_i, *r_y; + int ret; + struct pattern_match_data pmd = { + .table = audio_file_table, + .loop_col_num = AFTCOL_HASH, + .match_col_num = AFTCOL_PATH, + .data = aca, + .action = touch_audio_file + }; - 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) + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + + r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr); + if (lls_opt_given(r_i)) { + uint32_t id = lls_uint32_val(0, r_i); + ret = img_get_name_by_id(id, NULL); + if (ret < 0) { + para_printf(&aca->pbout, "invalid image ID: %u\n", id); 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) + } + r_y = SERVER_CMD_OPT_RESULT(TOUCH, LYRICS_ID, aca->lpr); + if (lls_opt_given(r_y)) { + uint32_t id = lls_uint32_val(0, r_y); + ret = lyr_get_name_by_id(id, NULL); + if (ret < 0) { + para_printf(&aca->pbout, "invalid lyrics ID: %u\n", id); return ret; + } } - return 1; + p_given = SERVER_CMD_OPT_GIVEN(TOUCH, PATHNAME_MATCH, aca->lpr); + if (p_given) + pmd.fnmatch_flags |= FNM_PATHNAME; + ret = for_each_matching_row(&pmd); + if (ret >= 0 && pmd.num_matches == 0) + ret = -E_NO_MATCH; + lls_free_parse_result(aca->lpr, cmd); + return ret; } -int com_touch(__a_unused int fd, int argc, char * const * const argv) +static int com_touch(struct command_context *cc, struct lls_parse_result *lpr) { - struct com_touch_options cto = { - .num_played = -1, - .last_played = -1, - .lyrics_id = -1, - .image_id = -1 + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH); + int ret; + char *errctx; + + ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_touch_callback, cmd, lpr, cc); +} +EXPORT_SERVER_CMD_HANDLER(touch); + +static int remove_audio_file(__a_unused struct osl_table *table, + struct osl_row *row, const char *name, void *data) +{ + struct afs_callback_arg *aca = data; + bool v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr); + int ret; + + if (v_given) + para_printf(&aca->pbout, "removing %s\n", name); + ret = afs_event(AUDIO_FILE_REMOVE, &aca->pbout, row); + if (ret < 0) + return ret; + ret = osl(osl_del_row(audio_file_table, row)); + if (ret < 0) + para_printf(&aca->pbout, "cannot remove %s\n", name); + return ret; +} + +static int com_rm_callback(struct afs_callback_arg *aca) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM); + int ret; + struct pattern_match_data pmd = { + .table = audio_file_table, + .loop_col_num = AFTCOL_HASH, + .match_col_num = AFTCOL_PATH, + .data = aca, + .action = remove_audio_file }; - struct osl_object options = {.data = &cto, .size = sizeof(cto)}; - int i, ret; + bool v_given, p_given, f_given; + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr); + p_given = SERVER_CMD_OPT_GIVEN(RM, PATHNAME_MATCH, aca->lpr); + f_given = SERVER_CMD_OPT_GIVEN(RM, FORCE, aca->lpr); + + if (p_given) + pmd.fnmatch_flags |= FNM_PATHNAME; + ret = for_each_matching_row(&pmd); + if (ret < 0) + goto out; + if (pmd.num_matches == 0) { + if (!f_given) + ret = -E_NO_MATCH; + } else if (v_given) + para_printf(&aca->pbout, "removed %u file(s)\n", + pmd.num_matches); +out: + lls_free_parse_result(aca->lpr, cmd); + return ret; +} +/* TODO options: -r (recursive) */ +static int com_rm(struct command_context *cc, struct lls_parse_result *lpr) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM); + char *errctx; + int 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 = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; } - 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 send_lls_callback_request(com_rm_callback, cmd, lpr, cc); +} +EXPORT_SERVER_CMD_HANDLER(rm); + +/** Data passed to the action handler of com_cpsi(). */ +struct cpsi_action_data { + /** Values are copied from here. */ + struct afs_info source_afsi; + /** What was passed to com_cpsi_callback(). */ + struct afs_callback_arg *aca; + bool copy_all; +}; + +static int copy_selector_info(__a_unused struct osl_table *table, + struct osl_row *row, const char *name, void *data) +{ + struct cpsi_action_data *cad = data; + struct osl_object target_afsi_obj; + int ret; + struct afs_info old_afsi, target_afsi; + struct afsi_change_event_data aced; + bool a_given, y_given, i_given, l_given, n_given, v_given; + + a_given = SERVER_CMD_OPT_GIVEN(CPSI, ATTRIBUTE_BITMAP, cad->aca->lpr); + y_given = SERVER_CMD_OPT_GIVEN(CPSI, LYRICS_ID, cad->aca->lpr); + i_given = SERVER_CMD_OPT_GIVEN(CPSI, IMAGE_ID, cad->aca->lpr); + l_given = SERVER_CMD_OPT_GIVEN(CPSI, LASTPLAYED, cad->aca->lpr); + n_given = SERVER_CMD_OPT_GIVEN(CPSI, NUMPLAYED, cad->aca->lpr); + v_given = SERVER_CMD_OPT_GIVEN(CPSI, VERBOSE, cad->aca->lpr); + + ret = get_afsi_object_of_row(row, &target_afsi_obj); + if (ret < 0) + return ret; + ret = load_afsi(&target_afsi, &target_afsi_obj); + if (ret < 0) + return ret; + old_afsi = target_afsi; + if (cad->copy_all || y_given) + target_afsi.lyrics_id = cad->source_afsi.lyrics_id; + if (cad->copy_all || i_given) + target_afsi.image_id = cad->source_afsi.image_id; + if (cad->copy_all || l_given) + target_afsi.last_played = cad->source_afsi.last_played; + if (cad->copy_all || n_given) + target_afsi.num_played = cad->source_afsi.num_played; + if (cad->copy_all || a_given) + target_afsi.attributes = cad->source_afsi.attributes; + save_afsi(&target_afsi, &target_afsi_obj); /* in-place update */ + if (v_given) + para_printf(&cad->aca->pbout, "copied afsi to %s\n", name); + aced.aft_row = row; + aced.old_afsi = &old_afsi; + return afs_event(AFSI_CHANGE, &cad->aca->pbout, &aced); +} + +static int com_cpsi_callback(struct afs_callback_arg *aca) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(CPSI); + bool a_given, y_given, i_given, l_given, n_given, v_given; + struct cpsi_action_data cad = {.aca = aca}; + int ret; + struct pattern_match_data pmd = { + .table = audio_file_table, + .loop_col_num = AFTCOL_HASH, + .match_col_num = AFTCOL_PATH, + .input_skip = 1, /* skip first argument (source file) */ + .data = &cad, + .action = copy_selector_info + }; + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + + a_given = SERVER_CMD_OPT_GIVEN(CPSI, ATTRIBUTE_BITMAP, aca->lpr); + y_given = SERVER_CMD_OPT_GIVEN(CPSI, LYRICS_ID, aca->lpr); + i_given = SERVER_CMD_OPT_GIVEN(CPSI, IMAGE_ID, aca->lpr); + l_given = SERVER_CMD_OPT_GIVEN(CPSI, LASTPLAYED, aca->lpr); + n_given = SERVER_CMD_OPT_GIVEN(CPSI, NUMPLAYED, aca->lpr); + v_given = SERVER_CMD_OPT_GIVEN(CPSI, VERBOSE, aca->lpr); + cad.copy_all = !a_given && !y_given && !i_given && !l_given && !n_given; + + ret = get_afsi_of_path(lls_input(0, aca->lpr), &cad.source_afsi); + if (ret < 0) + goto out; + ret = for_each_matching_row(&pmd); + if (ret < 0) + goto out; + if (pmd.num_matches > 0) { + if (v_given) + para_printf(&aca->pbout, "updated afsi of %u file(s)\n", + pmd.num_matches); + } else + ret = -E_NO_MATCH; +out: + lls_free_parse_result(aca->lpr, cmd); return ret; } -struct com_rm_options { - uint32_t flags; +static int com_cpsi(struct command_context *cc, struct lls_parse_result *lpr) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(CPSI); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_cpsi_callback, cmd, lpr, cc); +} +EXPORT_SERVER_CMD_HANDLER(cpsi); + +struct change_atts_data { + uint64_t add_mask, del_mask; + struct afs_callback_arg *aca; }; -static int com_rm_callback(const struct osl_object *query, - __a_unused struct osl_object *result) +static int change_atts(__a_unused struct osl_table *table, + struct osl_row *row, __a_unused const char *name, void *data) { - struct com_rm_options *cro = query->data; - char *p = (char *)query->data + sizeof(*cro); - size_t len; int ret; + struct osl_object obj; + struct afs_info old_afsi, new_afsi; + struct afsi_change_event_data aced = { + .aft_row = row, + .old_afsi = &old_afsi + }; + struct change_atts_data *cad = data; - for (;p < (char *)query->data + query->size; p += len + 1) { - struct osl_row *row; + 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; + new_afsi.attributes |= cad->add_mask; + new_afsi.attributes &= ~cad->del_mask; + save_afsi(&new_afsi, &obj); /* in-place update */ + return afs_event(AFSI_CHANGE, &cad->aca->pbout, &aced); +} - 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; +static int com_setatt_callback(struct afs_callback_arg *aca) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT); + int i, ret; + struct change_atts_data cad = {.aca = aca}; + struct pattern_match_data pmd = { + .table = audio_file_table, + .loop_col_num = AFTCOL_HASH, + .match_col_num = AFTCOL_PATH, + .pm_flags = PM_SKIP_EMPTY_NAME, + .data = &cad, + .action = change_atts + }; + unsigned num_inputs; + + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; + + num_inputs = lls_num_inputs(aca->lpr); + for (i = 0; i < num_inputs; i++) { + unsigned char bitnum; + uint64_t one = 1; + const char *arg = lls_input(i, aca->lpr); + char c, *p; + size_t len = strlen(arg); + + ret = -E_ATTR_SYNTAX; + if (len == 0) + goto out; + c = arg[len - 1]; + if (c != '+' && c != '-') { + if (cad.add_mask == 0 && cad.del_mask == 0) + goto out; /* no attribute modifier given */ + goto set_atts; + } + p = para_malloc(len); + memcpy(p, arg, len - 1); + p[len - 1] = '\0'; + ret = get_attribute_bitnum_by_name(p, &bitnum); + free(p); + if (ret < 0) { + para_printf(&aca->pbout, "invalid argument: %s\n", arg); + goto out; + } + if (c == '+') + cad.add_mask |= (one << bitnum); + else + cad.del_mask |= (one << bitnum); } - return 1; + /* no pattern given */ + ret = -E_ATTR_SYNTAX; + goto out; +set_atts: + pmd.input_skip = i; + ret = for_each_matching_row(&pmd); + if (ret >= 0 && pmd.num_matches == 0) + ret = -E_NO_MATCH; +out: + lls_free_parse_result(aca->lpr, cmd); + return ret; } -/* - * TODO options: -v verbose, -f dont stop if file not found - * -h remove by hash, use fnmatch +static int com_setatt(struct command_context *cc, struct lls_parse_result *lpr) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT); + char *errctx; + int ret = lls(lls_check_arg_count(lpr, 2, INT_MAX, &errctx)); + + if (ret < 0) { + send_errctx(cc, errctx); + return ret; + } + return send_lls_callback_request(com_setatt_callback, cmd, lpr, cc); +} +EXPORT_SERVER_CMD_HANDLER(setatt); + +static int afs_stat_callback(struct afs_callback_arg *aca) +{ + int *parser_friendly = aca->query.data; + char *buf = *parser_friendly? + parser_friendly_status_items : status_items; + + if (!buf) + return 0; + return pass_buffer_as_shm(aca->fd, SBD_OUTPUT, buf, strlen(buf)); +} + +/** + * Get the current afs status items from the afs process and send it. + * + * \param cc The command context, used e.g. for data encryption. + * \param parser_friendly Whether parser-friendly output format should be used. + * + * As the contents of the afs status items change in time and the command + * handler only has a COW version created at fork time, it can not send + * up-to-date afs status items directly. Therefore the usual callback mechanism + * is used to pass the status items from the afs process to the command handler + * via a shared memory area and a pipe. * - * */ + * \return The return value of the underlying call to \ref send_callback_request(). + */ +int send_afs_status(struct command_context *cc, int parser_friendly) +{ + struct osl_object query = {.data = &parser_friendly, + .size = sizeof(parser_friendly)}; + + return send_callback_request(afs_stat_callback, &query, + afs_cb_result_handler, cc); +} -int com_afs_rm(__a_unused int fd, int argc, char * const * const argv) +/* returns success on non-fatal errors to keep the loop going */ +static int check_audio_file(struct osl_row *row, void *data) { - struct com_rm_options cro = {.flags = 0}; - struct osl_object options = {.data = &cro, .size = sizeof(cro)}; - int i, ret; + char *path; + struct para_buffer *pb = data; + struct stat statbuf; + int ret = get_audio_file_path_of_row(row, &path); + struct afs_info afsi; + char *blob_name; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } + if (ret < 0) { + para_printf(pb, "%s\n", para_strerror(-ret)); + return ret; } - 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; + if (stat(path, &statbuf) < 0) + para_printf(pb, "%s: stat error (%s)\n", path, strerror(errno)); + else if (!S_ISREG(statbuf.st_mode)) + para_printf(pb, "%s: not a regular file\n", path); + ret = get_afsi_of_row(row, &afsi); + if (ret < 0) { + para_printf(pb, "%s: %s\n", path, para_strerror(-ret)); + return 1; + } + ret = lyr_get_name_by_id(afsi.lyrics_id, &blob_name); + if (ret < 0) + para_printf(pb, "%s lyrics id %u: %s\n", path, afsi.lyrics_id, + para_strerror(-ret)); + ret = img_get_name_by_id(afsi.image_id, &blob_name); + if (ret < 0) + para_printf(pb, "%s image id %u: %s\n", path, afsi.image_id, + para_strerror(-ret)); + return 0; } /** - * Close the audio file table. + * Check the audio file table for inconsistencies. * - * \param flags Ususal flags that are passed to osl_close_table(). + * \param aca Only ->pbout is used for diagnostics. * - * \sa osl_close_table(). + * \return Standard. Inconsistencies are reported but not regarded as an error. */ -void aft_shutdown(enum osl_close_flags flags) +int aft_check_callback(struct afs_callback_arg *aca) +{ + para_printf(&aca->pbout, "checking audio file table...\n"); + return audio_file_loop(&aca->pbout, check_audio_file); +} + +struct aft_check_atts_data { + uint64_t att_mask; + struct para_buffer *pb; +}; + +static int check_atts_of_audio_file(struct osl_row *row, void *data) { - osl_close_table(audio_file_table, flags); + struct aft_check_atts_data *acad = data; + int ret; + struct afs_info afsi; + char *path; + uint64_t bad_bits; + + ret = get_afsi_of_row(row, &afsi); + if (ret < 0) { + para_printf(acad->pb, "cannot get afsi\n"); + return ret; + } + bad_bits = afsi.attributes & ~acad->att_mask; + if (bad_bits == 0) /* OK */ + return 0; + ret = get_audio_file_path_of_row(row, &path); + if (ret < 0) { + para_printf(acad->pb, "cannot get path\n"); + return ret; + } + para_printf(acad->pb, "invalid attribute bits (%" PRIu64 "): %s\n", + bad_bits, path); + /* return success to keep looping */ + return 1; } /** - * Open the audio file table. + * Iterate over all audio files and check the attribute bit mask. * - * \param ti Gets initialized by this function. - * \param db The database directory. + * \param att_mask The mask of all valid attributes. + * \param pb Used for reporting inconsistencies. * - * \return Positive on success, negative on errors. + * This reads the attribute bit mask of each audio file from the afs info + * structure stored in the audio file table and verifies that all set bits are + * also turned on in \a att_mask, i.e., correspond to an attribute of the + * attribute table. Audio files for which this is not the case are reported via + * \a pb. + * + * \return Standard. Inconsistencies are not regarded as errors. * - * \sa osl_open_table(). + * \sa \ref attribute_check_callback(). + */ +int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb) +{ + struct aft_check_atts_data acad = {.att_mask = att_mask, .pb = pb}; + + para_printf(pb, "checking attributes, mask: %" PRIx64 "\n", att_mask); + return audio_file_loop(&acad, check_atts_of_audio_file); +} + +/* + * This sets audio_file_table to NULL, but leaves current_aft_row unmodified, + * though stale (pointing to unmapped memory). If the table is being closed + * because we received SIGHUP, the table will be reopened after the config file + * has been reloaded. We remember the hash of the current audio file here so + * that aft_open() can initialize current_aft_row by looking up the saved hash. */ -int aft_init(struct table_info *ti, const char *db) +static void aft_close(void) +{ + int ret; + unsigned char *p; + + if (current_aft_row) { + ret = get_hash_of_row(current_aft_row, &p); + if (ret < 0) { + PARA_WARNING_LOG("hash lookup failure\n"); + current_aft_row = NULL; + } else + memcpy(current_hash, p, HASH_SIZE); + } + osl_close_table(audio_file_table, OSL_MARK_CLEAN); + audio_file_table = NULL; +} + +static int aft_open(const char *dir) { int ret; - audio_file_table_desc.dir = db; - ti->desc = &audio_file_table_desc; - ret = osl_open_table(ti->desc, &ti->table); + audio_file_table_desc.dir = dir; + ret = osl(osl_open_table(&audio_file_table_desc, &audio_file_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; + PARA_INFO_LOG("audio file table contains %u files\n", num); + if (!current_aft_row) { + PARA_DEBUG_LOG("no current aft row\n"); + return 1; + } + /* SIGHUP case, update current_aft_row */ + ret = aft_get_row_of_hash(current_hash, ¤t_aft_row); + if (ret < 0) { /* not fatal */ + PARA_WARNING_LOG("current hash lookup failure: %s\n", + para_strerror(-ret)); + current_aft_row = NULL; + return 1; + } + PARA_NOTICE_LOG("current audio file hash lookup: success\n"); + return 1; } - PARA_INFO_LOG("failed to open audio file table\n"); + PARA_NOTICE_LOG("failed to open audio file table\n"); audio_file_table = NULL; - return ret == -E_NOENT? 1 : ret; + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) + return 1; + return ret; +} + +static int aft_create(const char *dir) +{ + audio_file_table_desc.dir = dir; + return osl(osl_create_table(&audio_file_table_desc)); +} + +static int clear_attribute(struct osl_row *row, void *data) +{ + struct rmatt_event_data *red = data; + struct afs_info afsi; + struct osl_object obj; + int ret = get_afsi_object_of_row(row, &obj); + uint64_t mask = ~(1ULL << red->bitnum); + + if (ret < 0) + return ret; + ret = load_afsi(&afsi, &obj); + if (ret < 0) + return ret; + afsi.attributes &= mask; + save_afsi(&afsi, &obj); + return 1; +} + +static int aft_event_handler(enum afs_events event, struct para_buffer *pb, + void *data) +{ + int ret; + + switch (event) { + case ATTRIBUTE_REMOVE: { + const struct rmatt_event_data *red = data; + para_printf(pb, "clearing attribute %s (bit %u) from all " + "entries in the audio file table\n", red->name, + red->bitnum); + return audio_file_loop(data, clear_attribute); + } case AFSI_CHANGE: { + struct afsi_change_event_data *aced = data; + uint64_t old_last_played = status_item_ls_data.afsi.last_played; + if (aced->aft_row != current_aft_row) + return 0; + ret = get_afsi_of_row(aced->aft_row, &status_item_ls_data.afsi); + if (ret < 0) + return ret; + status_item_ls_data.afsi.last_played = old_last_played; + make_status_items(); + return 1; + } case AUDIO_FILE_RENAME: { + char *path; + if (data != current_aft_row) + return 0; + ret = get_audio_file_path_of_row(current_aft_row, &path); + if (ret < 0) + return ret; + free(status_item_ls_data.path); + status_item_ls_data.path = para_strdup(path); + make_status_items(); + return 1; + } case AFHI_CHANGE: { + if (data != current_aft_row) + return 0; + ret = get_afhi_of_row(data, &status_item_ls_data.afhi); + if (ret < 0) + return ret; + make_status_items(); + return 1; + } case AUDIO_FILE_REMOVE: { + if (data == current_aft_row) + current_aft_row = NULL; + return 0; + } + case BLOB_RENAME: + case BLOB_REMOVE: + case BLOB_ADD: { + /* + * These events are rare. We don't bother to check whether the + * current status items are affected and simply recreate them + * every time. + */ + make_status_items(); + return 0; + } default: + return 0; + } +} + +/** + * Initialize the audio file table. + * + * \param t Pointer to the structure to be initialized. + */ +void aft_init(struct afs_table *t) +{ + t->open = aft_open; + t->close = aft_close; + t->create = aft_create; + t->event_handler = aft_event_handler; }