X-Git-Url: http://git.tuebingen.mpg.de/?a=blobdiff_plain;f=aft.c;h=c04d4f9c99e89afb451afe94be7fcc64461b15e1;hb=HEAD;hp=3190a9bafa5e4fdd0ef750f3fcf4160057ce7ddf;hpb=27b5b500c5fac0660d0ef0eaf1bea81a091300cc;p=paraslash.git diff --git a/aft.c b/aft.c index 3190a9ba..f1aca7fb 100644 --- a/aft.c +++ b/aft.c @@ -1,8 +1,4 @@ -/* - * Copyright (C) 2007 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ +/* Copyright (C) 2007 Andre Noll , see file COPYING. */ /** \file aft.c Audio file table functions. */ @@ -10,6 +6,7 @@ #include #include #include +#include #include #include @@ -26,54 +23,6 @@ #include "sideband.h" #include "command.h" -static struct osl_table *audio_file_table; -static char *status_items; -static char *parser_friendly_status_items; - -/** The different sorting methods of the ls command. */ -enum ls_sorting_method { - /** -sp (default) */ - LS_SORT_BY_PATH, - /** -ss */ - LS_SORT_BY_SCORE, - /** -sl */ - LS_SORT_BY_LAST_PLAYED, - /** -sn */ - LS_SORT_BY_NUM_PLAYED, - /** -sf */ - LS_SORT_BY_FREQUENCY, - /** -sc */ - LS_SORT_BY_CHANNELS, - /** -si */ - LS_SORT_BY_IMAGE_ID, - /** -sy */ - LS_SORT_BY_LYRICS_ID, - /** -sb */ - LS_SORT_BY_BITRATE, - /** -sd */ - LS_SORT_BY_DURATION, - /** -sa */ - LS_SORT_BY_AUDIO_FORMAT, - /** -sh */ - LS_SORT_BY_HASH, -}; - -/** The different listing modes of the ls command. */ -enum ls_listing_mode { - /** Default listing mode. */ - LS_MODE_SHORT, - /** -l or -ll */ - LS_MODE_LONG, - /** -lv */ - LS_MODE_VERBOSE, - /** -lm */ - LS_MODE_MBOX, - /** -lc */ - LS_MODE_CHUNKS, - /** -lp */ - LS_MODE_PARSER, -}; - /* Data about one audio file. Needed for ls and stat output. */ struct ls_data { /* Usual audio format handler information. */ @@ -88,6 +37,44 @@ struct ls_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[HASH2_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 */ +}; + +/** 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. * @@ -138,7 +125,7 @@ struct ls_options { /** * 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. */ @@ -167,7 +154,7 @@ enum afsi_offsets { * \param afsi Pointer to the audio file info to be converted. * \param obj Result pointer. * - * \sa load_afsi(). + * \sa \ref load_afsi(). */ static void save_afsi(struct afs_info *afsi, struct osl_object *obj) { @@ -192,7 +179,7 @@ static void save_afsi(struct afs_info *afsi, struct osl_object *obj) * * \return Standard. * - * \sa save_afsi(). + * \sa \ref save_afsi(). */ static int load_afsi(struct afs_info *afsi, struct osl_object *obj) { @@ -240,7 +227,7 @@ static struct osl_column_description aft_cols[] = { .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, .name = "hash", .compare_function = aft_hash_compare, - .data_size = HASH_SIZE + .data_size = HASH2_SIZE }, [AFTCOL_PATH] = { .storage_type = OSL_MAPPED_STORAGE, @@ -265,7 +252,7 @@ static struct osl_column_description aft_cols[] = { }; static struct osl_table_description audio_file_table_desc = { - .name = "audio_files", + .name = "audio-files", .num_columns = NUM_AFT_COLUMNS, .flags = OSL_LARGE_TABLE, .column_descriptions = aft_cols @@ -320,8 +307,8 @@ enum afhi_offsets { CHUNKS_TOTAL_OFFSET = 20, /** The length of the audio file header (4 bytes). */ HEADER_LEN_OFFSET = 24, - /** Was: The start of the audio file header (4 bytes). */ - AFHI_UNUSED2_OFFSET = 28, + /** 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). */ @@ -361,11 +348,14 @@ static void save_afhi(struct afh_info *afhi, char *buf) write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels); write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total); write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len); - write_u32(buf + AFHI_UNUSED2_OFFSET, 0); + 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 sprintf's below are OK as our caller made sure that buf is large enough */ + /* + * 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; @@ -374,6 +364,7 @@ static void save_afhi(struct afh_info *afhi, char *buf) sprintf(p, "%s", afhi->tags.comment); } +/* 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); @@ -383,6 +374,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->channels = read_u8(buf + AFHI_CHANNELS_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; @@ -393,42 +385,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1; } +/* 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 + 1); } -static uint32_t save_chunk_table(struct afh_info *afhi, char *buf) +static void save_chunk_table(struct afh_info *afhi, char *buf) { - int i; - uint32_t max = 0, old = 0; + uint32_t n; - for (i = 0; i <= afhi->chunks_total; i++) { - uint32_t val = afhi->chunk_table[i]; - write_u32(buf + 4 * i, val); - /* - * If the first chunk is the header, do not consider it for the - * calculation of the largest chunk size. - */ - if (i == 0 || (i == 1 && afhi->header_len > 0)) { - old = val; - continue; - } - max = PARA_MAX(max, val - old); - old = val; - } - return max; + if (!afhi->chunk_table || afhi->chunks_total == 0) + return; + for (n = 0; n <= afhi->chunks_total; n++) + write_u32(buf + 4 * n, afhi->chunk_table[n]); } -static void load_chunk_table(struct afh_info *afhi, char *buf) +static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct) { int i; + size_t sz; - afhi->chunk_table = para_malloc(sizeof_chunk_table(afhi)); - for (i = 0; i <= afhi->chunks_total; i++) - afhi->chunk_table[i] = read_u32(buf + 4 * i); + if (!ct->data || ct->size < 4 * (afhi->chunks_total + 1)) { + afhi->chunk_table = NULL; + return; + } + sz = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1; + afhi->chunk_table = alloc(sz); + for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++) + afhi->chunk_table[i] = read_u32(ct->data + 4 * i); } /** @@ -456,7 +443,7 @@ int aft_get_row_of_path(const char *path, 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}; + const struct osl_object obj = {.data = hash, .size = HASH2_SIZE}; return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row)); } @@ -532,7 +519,7 @@ static int get_afsi_of_path(const char *path, struct afs_info *afsi) * \param row Pointer to a row in the audio file table. * \param path Result pointer. * - * The result is a pointer to mmapped data. The caller must not attempt + * The result is a pointer to memory-mapped data. The caller must not attempt * to free it. * * \return Standard. @@ -542,10 +529,12 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path) struct osl_object 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; } /** @@ -556,7 +545,7 @@ 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(). */ static int get_hash_object_of_aft_row(const struct osl_row *row, struct osl_object *obj) @@ -594,13 +583,17 @@ static int get_hash_of_row(const struct osl_row *row, unsigned char **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 struct osl_row *row, struct afh_info *afhi) { struct osl_object obj; - int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, - &obj)); + int ret; + + assert(row); + ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, &obj)); if (ret < 0) return ret; load_afhi(obj.data, afhi); @@ -623,7 +616,13 @@ static int save_afd(struct audio_file_data *afd) goto err; buf = shm_afd; buf += sizeof(*afd); - afd->max_chunk_size = save_chunk_table(&afd->afhi, buf); + 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; @@ -648,14 +647,22 @@ int load_afd(int shmid, struct audio_file_data *afd) { void *shm_afd; int ret; + struct osl_object obj; ret = shm_attach(shmid, ATTACH_RO, &shm_afd); if (ret < 0) return ret; + ret = shm_size(shmid, &obj.size); + if (ret < 0) + goto detach; *afd = *(struct audio_file_data *)shm_afd; - load_chunk_table(&afd->afhi, shm_afd + sizeof(*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 1; + return ret; } static int get_local_time(uint64_t *seconds, char *buf, size_t size, @@ -684,7 +691,7 @@ static int get_local_time(uint64_t *seconds, char *buf, size_t size, /* * 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 formated date align nicely with dates that + * 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)) @@ -715,7 +722,8 @@ __a_const static short unsigned get_duration_width(int seconds) return width + 6; } -static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) +static void get_duration_buf(int seconds, char *buf, size_t bufsize, + struct ls_options *opts) { unsigned hours = seconds / 3600, mins = (seconds % 3600) / 60; short unsigned max_width; @@ -723,10 +731,12 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) if (!hours) { /* m:ss or mm:ss */ max_width = opts->mode == LS_MODE_LONG? opts->widths.duration_width : 4; + assert(max_width < bufsize - 1); 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; + assert(max_width < bufsize - 1); sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins, seconds % 60); } @@ -738,11 +748,11 @@ static int write_attribute_items(struct para_buffer *b, char *att_text; int ret; - WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap); + 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); + WRITE_STATUS_ITEM(b, SI_attributes_txt, "%s\n", att_text); free(att_text); return ret; } @@ -751,9 +761,9 @@ 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); + WRITE_STATUS_ITEM(b, SI_lyrics_id, "%u\n", afsi->lyrics_id); lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name); - WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name? + WRITE_STATUS_ITEM(b, SI_lyrics_name, "%s\n", lyrics_name? lyrics_name : "(none)"); } @@ -761,27 +771,26 @@ 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); + WRITE_STATUS_ITEM(b, SI_image_id, "%u\n", afsi->image_id); img_get_name_by_id(afsi->image_id, &image_name); - WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", 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; + const char *slash; if (basename) { - WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); + 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); + WRITE_STATUS_ITEM(b, SI_path, "%s\n", path); + slash = strrchr(path, '/'); + WRITE_STATUS_ITEM(b, SI_basename, "%s\n", slash? slash + 1 : path); + WRITE_STATUS_ITEM(b, SI_directory, "%.*s\n", + slash? (int)(slash - path) : (int)strlen(path), path); } static int print_chunk_table(struct ls_data *d, struct para_buffer *b) @@ -791,6 +800,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) int ret, i; char *buf; + para_printf(b, "%s\nchunk_time: %lu:%lu\n", d->path, + (long unsigned) d->afhi.chunk_tv.tv_sec, + (long unsigned) d->afhi.chunk_tv.tv_usec + ); + if (afh_supports_dynamic_chunks(d->afsi.audio_format_id)) + return 0; ret = aft_get_row_of_hash(d->hash, &aft_row); if (ret < 0) return ret; @@ -798,14 +813,13 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) 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 - ); + para_printf(b, "chunk_offsets: "); buf = chunk_table_obj.data; - for (i = 0; i <= d->afhi.chunks_total; i++) + 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; @@ -825,7 +839,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, char duration_buf[30]; /* nobody has an audio file long enough to overflow this */ struct afs_info *afsi = &d->afsi; struct afh_info *afhi = &d->afhi; - char asc_hash[2 * HASH_SIZE + 1]; + char asc_hash[2 * HASH2_SIZE + 1]; if (opts->mode == LS_MODE_SHORT) { para_printf(b, "%s\n", d->path); @@ -846,7 +860,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, if (ret < 0) goto out; } - get_duration_buf(afhi->seconds_total, duration_buf, opts); + get_duration_buf(afhi->seconds_total, duration_buf, + sizeof(duration_buf), opts); if (opts->mode == LS_MODE_LONG) { struct ls_widths *w = &opts->widths; if (lls_opt_given(r_a)) @@ -883,44 +898,46 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, goto out; } if (opts->mode == LS_MODE_MBOX) { - const char *bn = para_basename(d->path); + const char *slash = strrchr(d->path, '/'); para_printf(b, "From foo@localhost %s\n" "Received: from\nTo: bar\nFrom: a\n" "Subject: %s\n\n", last_played_time, - bn? bn : "?"); + slash? slash + 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); + 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", + hash2_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", + 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", + 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_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); + 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); @@ -934,9 +951,6 @@ out: return ret; } -static struct ls_data status_item_ls_data; -static struct osl_row *current_aft_row; - static void make_inode_status_items(struct para_buffer *pb) { struct stat statbuf = {.st_size = 0}; @@ -954,11 +968,22 @@ static void make_inode_status_items(struct para_buffer *pb) 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); + WRITE_STATUS_ITEM(pb, SI_mtime, "%s\n", mtime_str); + WRITE_STATUS_ITEM(pb, SI_file_size, "%ld\n", statbuf.st_size / 1024); +} + +/** + * 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) +{ + freep(&status_items); + freep(&parser_friendly_status_items); } -static int make_status_items(void) +static void make_status_items(void) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); char *argv[] = {"ls", "--admissible", "--listing-mode=verbose"}; @@ -967,6 +992,9 @@ static int make_status_items(void) time_t current_time; int ret; + 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); @@ -974,31 +1002,37 @@ static int make_status_items(void) if (ret < 0) goto out; make_inode_status_items(&pb); - free(status_items); 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) { - free(status_items); - status_items = NULL; - return ret; - } + if (ret < 0) + goto out; make_inode_status_items(&pb); - free(parser_friendly_status_items); 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); - return ret; } /** * Open the audio file with highest score and set up an afd structure. * - * \param afd Result pointer. + * This determines and opens the next audio file, verifies that it did not + * change by comparing the recomputed the hash value of the file contents + * against the value stored in the audio file table. If all goes well, it + * creates a shared memory area containing the serialized version of the afd + * structure, including the chunk table, if any. The caller can then send the + * ID of this area and the open fd to the server process. + * + * \param fd Result pointer for the file descriptor of the audio file. * * 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 @@ -1006,46 +1040,70 @@ out: * * \return Positive shmid on success, negative on errors. */ -int open_and_update_audio_file(struct audio_file_data *afd) +int open_and_update_audio_file(int *fd) { - unsigned char file_hash[HASH_SIZE]; + unsigned char file_hash[HASH2_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; + struct audio_file_data afd; again: ret = score_get_best(¤t_aft_row, &d->score); if (ret < 0) return ret; - ret = get_hash_of_row(current_aft_row, &d->hash); + /* + * 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 = alloc(HASH2_SIZE); + memcpy(d->hash, tmp_hash, HASH2_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); + 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; + 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) - return ret; - ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd); + 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, fd); if (ret < 0) goto out; - hash_function(map.data, map.size, file_hash); - ret = hash_compare(file_hash, d->hash); + hash2_function(map.data, map.size, file_hash); + ret = hash2_compare(file_hash, d->hash); para_munmap(map.data, map.size); if (ret) { ret = -E_HASH_MISMATCH; @@ -1056,8 +1114,8 @@ again: 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.data); + 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; /* @@ -1067,10 +1125,11 @@ again: ret = afs_event(AFSI_CHANGE, NULL, &aced); if (ret < 0) goto out; - ret = save_afd(afd); + ret = save_afd(&afd); out: - free(afd->afhi.chunk_table); - osl_close_disk_object(&chunk_table_obj); + 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); @@ -1080,6 +1139,12 @@ out: 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, HASH2_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; @@ -1161,7 +1226,7 @@ static int sort_matching_paths(struct ls_options *options) int (*compar)(const void *, const void *); int i; - options->data_ptr = para_malloc(nmemb * sizeof(*options->data_ptr)); + options->data_ptr = arr_alloc(nmemb, sizeof(*options->data_ptr)); for (i = 0; i < nmemb; i++) options->data_ptr[i] = options->data + i; @@ -1197,6 +1262,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; } @@ -1252,8 +1319,8 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) if (options->num_matching_paths > options->array_size) { options->array_size++; options->array_size *= 2; - options->data = para_realloc(options->data, options->array_size - * sizeof(*options->data)); + options->data = arr_realloc(options->data, options->array_size, + sizeof(*options->data)); } d = options->data + tmp; ret = get_afsi_of_row(aft_row, &d->afsi); @@ -1295,28 +1362,67 @@ err: return ret; } +static int mop_loop(const char *arg, struct afs_callback_arg *aca, + struct ls_options *opts) +{ + int ret; + char *msg; + + if (!arg || strcmp(arg, ".") == 0) + return score_loop(prepare_ls_row, NULL, opts); + if (!strncmp(arg, "m/", 2)) { + struct mood_instance *m; + ret = mood_load(arg + 2, &m, &msg); + if (ret < 0) + afs_error(aca, "%s", msg); + free(msg); + if (ret < 0) + return ret; + ret = mood_loop(m, prepare_ls_row, opts); + mood_unload(m); + return ret; + } + if (!strncmp(arg, "p/", 2)) { + struct playlist_instance *pi; + ret = playlist_load(arg + 2, &pi, &msg); + if (ret < 0) + afs_error(aca, "%s", msg); + free(msg); + if (ret < 0) + return ret; + ret = playlist_loop(pi, prepare_ls_row, opts); + playlist_unload(pi); + return ret; + } + afs_error(aca, "bad mood/playlist specifier: %s\n", arg); + return -ERRNO_TO_PARA_ERROR(EINVAL); +} + static int com_ls_callback(struct afs_callback_arg *aca) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); struct ls_options *opts = aca->query.data; - int i = 0, ret; + int ret; time_t current_time; - const struct lls_opt_result *r_r; + const struct lls_opt_result *r_r, *r_a; + uint32_t limit, k, n; 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); - + r_a = SERVER_CMD_OPT_RESULT(LS, ADMISSIBLE, 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 + if (admissible_only(opts)) { + const char *arg = lls_string_val(0, r_a); + ret = mop_loop(arg, aca, opts); + } else ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, prepare_ls_row)); if (ret < 0) goto out; - if (opts->num_matching_paths == 0) { + n = opts->num_matching_paths; + if (n == 0) { ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0; goto out; } @@ -1324,20 +1430,14 @@ static int com_ls_callback(struct afs_callback_arg *aca) if (ret < 0) goto out; 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, - &aca->pbout, current_time); - if (ret < 0) - goto out; - } - else - for (i = 0; i < opts->num_matching_paths; i++) { - ret = print_list_item(opts->data_ptr[i], opts, - &aca->pbout, current_time); - if (ret < 0) - goto out; - } + limit = SERVER_CMD_UINT32_VAL(LS, LIMIT, opts->lpr); + for (k = 0; k < n && (limit == 0 || k < limit); k++) { + uint32_t idx = lls_opt_given(r_r)? n - 1 - k : k; + ret = print_list_item(opts->data_ptr[idx], opts, &aca->pbout, + current_time); + if (ret < 0) + goto out; + } out: lls_free_parse_result(opts->lpr, cmd); free(opts->data); @@ -1345,7 +1445,6 @@ out: return ret; } -/* TODO: flags -h (sort by hash) */ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) { const struct lls_command *cmd = SERVER_CMD_CMD_PTR(LS); @@ -1360,7 +1459,7 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) ret = lls_serialize_parse_result(lpr, cmd, NULL, &query.size); assert(ret >= 0); query.size += sizeof(*opts); - query.data = para_malloc(query.size); + query.data = alloc(query.size); opts = query.data; memset(opts, 0, sizeof(*opts)); slpr = query.data + sizeof(*opts); @@ -1379,11 +1478,11 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) 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; + opts->mode = LS_MODE_CHUNKS; else if (!strcmp(val, "p") || !strcmp(val, "parser-friendly")) opts->mode = LS_MODE_PARSER; else { - ret = -E_AFT_SYNTAX; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); goto out; } } @@ -1411,8 +1510,10 @@ static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) 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; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); goto out; } } @@ -1469,7 +1570,7 @@ enum com_add_buffer_offsets { /** 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), + CAB_PATH_OFFSET = (CAB_HASH_OFFSET + HASH2_SIZE), }; /* @@ -1487,12 +1588,12 @@ static void save_add_callback_buffer(unsigned char *hash, const char *path, size_t afhi_size = sizeof_afhi_buf(afhi); size_t size = CAB_PATH_OFFSET + path_len + afhi_size + sizeof_chunk_table(afhi) + slpr_size; - char *buf = para_malloc(size); + char *buf = alloc(size); 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); + memcpy(buf + CAB_HASH_OFFSET, hash, HASH2_SIZE); strcpy(buf + CAB_PATH_OFFSET, path); pos = CAB_PATH_OFFSET + path_len; write_u32(buf + CAB_AFHI_OFFSET_POS, pos); @@ -1551,7 +1652,7 @@ ACTION: Table modifications to be done by the callback. +----+----+---+------+---------------------------------------------------+ | N | N | Y | Y | (new file) create new entry (force has no effect) +----+----+---+------+---------------------------------------------------+ -| N | N | N | Y | (new file) create new entry +| N | N | N | Y | (new file) create new entry +----+----+---+------+---------------------------------------------------+ Notes: @@ -1568,10 +1669,11 @@ static int com_add_callback(struct afs_callback_arg *aca) struct osl_row *hs; struct osl_object objs[NUM_AFT_COLUMNS]; unsigned char *hash; - char asc[2 * HASH_SIZE + 1]; + char asc[2 * HASH2_SIZE + 1]; int ret; char afsi_buf[AFSI_SIZE]; - char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET); + uint32_t slpr_offset = read_u32(buf + CAB_LPR_OFFSET); + char *slpr = buf + slpr_offset; struct afs_info default_afsi = {.last_played = 0}; uint16_t afhi_offset, chunks_offset; const struct lls_command *cmd = SERVER_CMD_CMD_PTR(ADD); @@ -1583,9 +1685,9 @@ static int com_add_callback(struct afs_callback_arg *aca) r_v = SERVER_CMD_OPT_RESULT(ADD, VERBOSE, aca->lpr); hash = (unsigned char *)buf + CAB_HASH_OFFSET; - hash_to_asc(hash, asc); + hash2_to_asc(hash, asc); objs[AFTCOL_HASH].data = buf + CAB_HASH_OFFSET; - objs[AFTCOL_HASH].size = HASH_SIZE; + objs[AFTCOL_HASH].size = HASH2_SIZE; path = buf + CAB_PATH_OFFSET; objs[AFTCOL_PATH].data = path; @@ -1639,6 +1741,7 @@ static int com_add_callback(struct afs_callback_arg *aca) /* no hs or force mode, child must have sent afhi */ afhi_offset = read_u32(buf + CAB_AFHI_OFFSET_POS); chunks_offset = read_u32(buf + CAB_CHUNKS_OFFSET_POS); + assert(chunks_offset <= slpr_offset); objs[AFTCOL_AFHI].data = buf + afhi_offset; objs[AFTCOL_AFHI].size = chunks_offset - afhi_offset; @@ -1646,14 +1749,14 @@ static int com_add_callback(struct afs_callback_arg *aca) if (!objs[AFTCOL_AFHI].size) /* "impossible" */ goto out; objs[AFTCOL_CHUNKS].data = buf + chunks_offset; - objs[AFTCOL_CHUNKS].size = aca->query.size - chunks_offset; + objs[AFTCOL_CHUNKS].size = slpr_offset - chunks_offset; if (pb && !hs) { /* update pb's hash */ - char old_asc[2 * HASH_SIZE + 1]; + char old_asc[2 * HASH2_SIZE + 1]; unsigned char *old_hash; ret = get_hash_of_row(pb, &old_hash); if (ret < 0) goto out; - hash_to_asc(old_hash, old_asc); + hash2_to_asc(old_hash, old_asc); if (lls_opt_given(r_v)) para_printf(&aca->pbout, "file change: %s -> %s\n", old_asc, asc); @@ -1672,6 +1775,7 @@ static int com_add_callback(struct afs_callback_arg *aca) &objs[AFTCOL_AFHI])); if (ret < 0) 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) @@ -1694,7 +1798,7 @@ static int com_add_callback(struct afs_callback_arg *aca) ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row); out: if (ret < 0) - para_printf(&aca->pbout, "could not add %s\n", path); + afs_error(aca, "could not add %s\n", path); lls_free_parse_result(aca->lpr, cmd); return ret; } @@ -1752,7 +1856,7 @@ static int add_one_audio_file(const char *path, void *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; - unsigned char hash[HASH_SIZE]; + unsigned char hash[HASH2_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); @@ -1780,11 +1884,11 @@ static int add_one_audio_file(const char *path, void *private_data) 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); + hash2_function(map.data, map.size, hash); /* Check whether the database contains a file with the same hash. */ query.data = hash; - query.size = HASH_SIZE; + query.size = HASH2_SIZE; ret = send_callback_request(hash_sister_callback, &query, get_row_pointer_from_result, &hs); if (ret < 0) @@ -1836,6 +1940,53 @@ out_free: return send_ret; } +/* + * Call back once for each regular file below a directory. + * + * Traverse the given directory recursively and call the supplied callback for + * each regular file encountered. The first argument to the callback will be + * the path to the regular file and the second argument will be the data + * pointer. All file types except regular files and directories are ignored. In + * particular, symlinks are not followed. Subdirectories are ignored silently + * if the calling process has insufficient access permissions. + */ +static int for_each_file_in_dir(const char *dirname, + int (*func)(const char *, void *), void *data) +{ + int ret; + DIR *dir; + struct dirent *entry; + + dir = opendir(dirname); + if (!dir) + return errno == EACCES? 1 : -ERRNO_TO_PARA_ERROR(errno); + /* scan cwd recursively */ + while ((entry = readdir(dir))) { + char *tmp; + struct stat s; + + if (!strcmp(entry->d_name, ".")) + continue; + if (!strcmp(entry->d_name, "..")) + continue; + tmp = make_message("%s/%s", dirname, entry->d_name); + ret = 0; + if (lstat(tmp, &s) != -1) { + if (S_ISREG(s.st_mode)) + ret = func(tmp, data); + else if (S_ISDIR(s.st_mode)) + ret = for_each_file_in_dir(tmp, func, data); + } + free(tmp); + if (ret < 0) + goto out; + } + ret = 1; +out: + closedir(dir); + return ret; +} + static int com_add(struct command_context *cc, struct lls_parse_result *lpr) { int i, ret; @@ -1882,11 +2033,7 @@ out: } EXPORT_SERVER_CMD_HANDLER(add); -/** - * Flags used by the touch command. - * - * \sa com_touch(). - */ +/** 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, @@ -1916,12 +2063,12 @@ static int touch_audio_file(__a_unused struct osl_table *table, ret = get_afsi_object_of_row(row, &obj); if (ret < 0) { - para_printf(&aca->pbout, "cannot touch %s\n", name); + afs_error(aca, "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); + afs_error(aca, "cannot touch %s\n", name); return ret; } new_afsi = old_afsi; @@ -1975,7 +2122,7 @@ static int com_touch_callback(struct afs_callback_arg *aca) 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); + afs_error(aca, "invalid image ID: %u\n", id); return ret; } } @@ -1984,7 +2131,7 @@ static int com_touch_callback(struct afs_callback_arg *aca) 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); + afs_error(aca, "invalid lyrics ID: %u\n", id); return ret; } } @@ -2027,7 +2174,7 @@ static int remove_audio_file(__a_unused struct osl_table *table, return ret; ret = osl(osl_del_row(audio_file_table, row)); if (ret < 0) - para_printf(&aca->pbout, "cannot remove %s\n", name); + afs_error(aca, "cannot remove %s\n", name); return ret; } @@ -2112,7 +2259,9 @@ static int copy_selector_info(__a_unused struct osl_table *table, ret = get_afsi_object_of_row(row, &target_afsi_obj); if (ret < 0) return ret; - load_afsi(&target_afsi, &target_afsi_obj); + 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; @@ -2255,13 +2404,13 @@ static int com_setatt_callback(struct afs_callback_arg *aca) goto out; /* no attribute modifier given */ goto set_atts; } - p = para_malloc(len); + p = alloc(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); + afs_error(aca, "invalid argument: %s\n", arg); goto out; } if (c == '+') @@ -2370,8 +2519,6 @@ static int check_audio_file(struct osl_row *row, void *data) * \param aca Only ->pbout is used for diagnostics. * * \return Standard. Inconsistencies are reported but not regarded as an error. - * - * \sa com_check(). */ int aft_check_callback(struct afs_callback_arg *aca) { @@ -2435,28 +2582,30 @@ int aft_check_attributes(uint64_t att_mask, struct para_buffer *pb) return audio_file_loop(&acad, check_atts_of_audio_file); } -/** - * Close the audio file table. - * - * \param flags Usual flags that are passed to osl_close_table(). - * - * \sa osl_close_table(). +/* + * 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. */ 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, HASH2_SIZE); + } osl_close_table(audio_file_table, OSL_MARK_CLEAN); audio_file_table = NULL; } -/** - * Open the audio file table. - * - * \param dir The database directory. - * - * \return Standard. - * - * \sa osl_open_table(). - */ static int aft_open(const char *dir) { int ret; @@ -2467,12 +2616,27 @@ static int aft_open(const char *dir) unsigned num; osl_get_num_rows(audio_file_table, &num); PARA_INFO_LOG("audio file table contains %u files\n", num); - return ret; + 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_NOTICE_LOG("failed to open audio file table\n"); audio_file_table = NULL; - if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) { + PARA_WARNING_LOG("no audio file table\n"); return 1; + } + PARA_NOTICE_LOG("failed to open audio file table\n"); return ret; } @@ -2523,6 +2687,17 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, 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; @@ -2531,20 +2706,36 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, 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 + * whenever an audio file is open. + */ + if (!current_aft_row) + return 0; + ret = get_afhi_of_row(current_aft_row, + &status_item_ls_data.afhi); + if (ret < 0) + return ret; + 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; -} +/** The audio file table contains information about known audio files. */ +const struct afs_table_operations aft_ops = { + .open = aft_open, + .close = aft_close, + .create = aft_create, + .event_handler = aft_event_handler, +};