X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=blobdiff_plain;f=aft.c;h=5d4e64773e8a7e7011ef88bb69771a21bd285a1b;hp=f2c1edab75c49125b6939c031e24ad28cf0e74cf;hb=f787a626b8885247948e3c47ae886b1e3a7c2a3a;hpb=6fc2a2c87ba41ed207aa36e9818819cf28c70db1 diff --git a/aft.c b/aft.c index f2c1edab..5d4e6477 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. */ @@ -11,7 +7,9 @@ #include #include #include +#include +#include "server_cmd.lsg.h" #include "para.h" #include "error.h" #include "crypt.h" @@ -24,9 +22,31 @@ #include "sideband.h" #include "command.h" -static struct osl_table *audio_file_table; +/* 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 { @@ -72,32 +92,6 @@ enum ls_listing_mode { LS_MODE_PARSER, }; -/* 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 flags accepted by the ls command. */ -enum ls_flags { - /** -p */ - LS_FLAG_FULL_PATH = 1, - /** -a */ - LS_FLAG_ADMISSIBLE_ONLY = 2, - /** -r */ - LS_FLAG_REVERSE = 4, - /** -d */ - LS_FLAG_UNIXDATE = 8, -}; - /** * The size of the individual output fields of the ls command. * @@ -128,16 +122,11 @@ struct ls_widths { /** Data passed from the ls command handler to its callback function. */ struct ls_options { - /** The given command line flags. */ - unsigned flags; - /** The sorting method given at the command line. */ + struct lls_parse_result *lpr; + /* Derived from lpr */ enum ls_sorting_method sorting; - /** The given listing mode (short, long, verbose, mbox). */ + /* Derived from lpr */ enum ls_listing_mode mode; - /** The arguments passed to the ls command. */ - char **patterns; - /** Number of non-option arguments. */ - int num_patterns; /** Used for long listing mode to align the output fields. */ struct ls_widths widths; /** Size of the \a data array. */ @@ -153,7 +142,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. */ @@ -182,7 +171,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) { @@ -207,7 +196,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) { @@ -335,8 +324,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). */ @@ -376,11 +365,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; @@ -389,6 +381,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); @@ -398,6 +391,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; @@ -408,42 +402,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) + 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->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); } /** @@ -547,7 +536,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. @@ -557,10 +546,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; } /** @@ -571,7 +562,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) @@ -609,7 +600,9 @@ 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) { @@ -638,7 +631,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; @@ -663,34 +662,54 @@ 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, 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 (lm == LS_MODE_MBOX) { - if (!strftime(buf, size, "%c", &t)) + if (!strftime(buf, size, "%c", tm)) return -E_STRFTIME; return 1; } - if (*seconds + 6 * 30 * 24 * 3600 > current_time) { - if (!strftime(buf, size, "%b %e %k:%M", &t)) + 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; } @@ -726,11 +745,11 @@ 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; - sprintf(buf, "%*u:%02u", max_width - 3, mins, seconds % 60); + 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:%02u", max_width - 6, hours, mins, + sprintf(buf, "%*u:%02u:%02d", max_width - 6, hours, mins, seconds % 60); } } @@ -741,62 +760,50 @@ static int write_attribute_items(struct para_buffer *b, char *att_text; int ret; - ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap); - if (ret < 0) - return ret; + WRITE_STATUS_ITEM(b, SI_attributes_bitmap, "%s\n", att_bitmap); ret = get_attribute_text(&afsi->attributes, " ", &att_text); if (ret < 0) return ret; - 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; } -static int write_lyrics_items(struct para_buffer *b, struct afs_info *afsi) +static void write_lyrics_items(struct para_buffer *b, struct afs_info *afsi) { char *lyrics_name; - int ret; - ret = WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id); - if (ret < 0) - return ret; + WRITE_STATUS_ITEM(b, SI_lyrics_id, "%u\n", afsi->lyrics_id); lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name); - return 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)"); } -static int write_image_items(struct para_buffer *b, struct afs_info *afsi) +static void write_image_items(struct para_buffer *b, struct afs_info *afsi) { char *image_name; - int ret; - ret = WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id); - if (ret < 0) - return ret; + WRITE_STATUS_ITEM(b, SI_image_id, "%u\n", afsi->image_id); img_get_name_by_id(afsi->image_id, &image_name); - return 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 int write_filename_items(struct para_buffer *b, const char *path, - unsigned flags) +static void write_filename_items(struct para_buffer *b, const char *path, + bool basename) { char *val; - int ret; - if (!(flags & LS_FLAG_FULL_PATH)) - return WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); - ret = WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path); - if (ret < 0) - return ret; + 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); - ret = WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : ""); - if (ret < 0) - return ret; + WRITE_STATUS_ITEM(b, SI_basename, "%s\n", val? val : ""); val = para_dirname(path); - ret = WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : ""); + WRITE_STATUS_ITEM(b, SI_directory, "%s\n", val? val : ""); free(val); - return ret; } static int print_chunk_table(struct ls_data *d, struct para_buffer *b) @@ -813,37 +820,31 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) AFTCOL_CHUNKS, &chunk_table_obj)); if (ret < 0) return ret; - ret = para_printf(b, "%s\n" + 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 ); - if (ret < 0) - goto out; buf = chunk_table_obj.data; - for (i = 0; i <= d->afhi.chunks_total; i++) { - ret = para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i)); - if (ret < 0) - goto out; - } - ret = para_printf(b, "\n"); -out: + 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 write_score(struct para_buffer *b, struct ls_data *d, - struct ls_options *opts) -{ - if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/ - return 0; - return WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score); -} - static int print_list_item(struct ls_data *d, struct ls_options *opts, 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]; @@ -853,7 +854,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, char asc_hash[2 * HASH_SIZE + 1]; if (opts->mode == LS_MODE_SHORT) { - ret = para_printf(b, "%s\n", d->path); + para_printf(b, "%s\n", d->path); + ret = 1; goto out; } if (opts->mode == LS_MODE_CHUNKS) { @@ -861,7 +863,7 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, goto out; } get_attribute_bitmap(&afsi->attributes, att_buf); - if (opts->flags & LS_FLAG_UNIXDATE) + if (lls_opt_given(r_d)) sprintf(last_played_time, "%llu", (long long unsigned)afsi->last_played); else { @@ -873,23 +875,20 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, get_duration_buf(afhi->seconds_total, duration_buf, opts); if (opts->mode == LS_MODE_LONG) { struct ls_widths *w = &opts->widths; - if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) { - ret = para_printf(b, "%*li ", - opts->widths.score_width, d->score); - if (ret < 0) - goto out; - } - ret = para_printf(b, + if (lls_opt_given(r_a)) + para_printf(b, "%*li ", opts->widths.score_width, + d->score); + para_printf(b, "%s " /* attributes */ "%*u " /* amp */ - "%*d " /* image_id */ - "%*d " /* lyrics_id */ - "%*d " /* bitrate */ + "%*u " /* image_id */ + "%*u " /* lyrics_id */ + "%*u " /* bitrate */ "%*s " /* audio format */ - "%*d " /* frequency */ - "%d " /* channels */ + "%*u " /* frequency */ + "%u " /* channels */ "%s " /* duration */ - "%*d " /* num_played */ + "%*u " /* num_played */ "%s " /* last_played */ "%s\n", /* path */ att_buf, @@ -906,98 +905,55 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, last_played_time, d->path ); + ret = 1; goto out; } if (opts->mode == LS_MODE_MBOX) { const char *bn = para_basename(d->path); - ret = para_printf(b, + para_printf(b, "From foo@localhost %s\n" "Received: from\nTo: bar\nFrom: a\n" "Subject: %s\n\n", last_played_time, bn? bn : "?"); - if (ret < 0) - goto out; } - ret = write_filename_items(b, d->path, opts->flags); - if (ret < 0) - goto out; - ret = write_score(b, d, opts); - if (ret < 0) - goto out; + 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; - ret = write_image_items(b, afsi); - if (ret < 0) - goto out; - ret = write_lyrics_items(b, afsi); - if (ret < 0) - goto out; + write_image_items(b, afsi); + write_lyrics_items(b, afsi); hash_to_asc(d->hash, asc_hash); - ret = WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n", + 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)); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%lu\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); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%d\n", afsi->num_played); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", - tv2ms(&afhi->chunk_tv)); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%lu\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); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album); - if (ret < 0) - goto out; - ret = WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment); - if (ret < 0) - goto out; + 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) { - ret = para_printf(b, "Lyrics:\n~~~~~~~\n%s", + para_printf(b, "Lyrics:\n~~~~~~~\n%s", (char *)lyrics_def.data); osl_close_disk_object(&lyrics_def); } @@ -1006,9 +962,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}; @@ -1026,41 +979,51 @@ 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: - /* We don't care too much about errors here */ - (void)WRITE_STATUS_ITEM(pb, SI_MTIME, "%s\n", mtime_str); - (void)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); +} + +void free_status_items(void) +{ + freep(&status_items); + freep(&parser_friendly_status_items); } static int make_status_items(void) { - struct ls_options opts = { - .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY, - .mode = LS_MODE_VERBOSE, - }; + 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; + free_status_items(); + if (!status_item_ls_data.path) /* no audio file open */ + return 0; + 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) - return ret; + 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; - return 1; + ret = 1; +out: + if (ret < 0) + free_status_items(); + lls_free_parse_result(opts.lpr, cmd); + return ret; } /** @@ -1107,8 +1070,15 @@ again: 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; + 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; @@ -1125,18 +1095,21 @@ again: 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); + 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. */ - afs_event(AFSI_CHANGE, NULL, &aced); + ret = afs_event(AFSI_CHANGE, NULL, &aced); + if (ret < 0) + goto out; ret = save_afd(afd); out: free(afd->afhi.chunk_table); - osl_close_disk_object(&chunk_table_obj); + 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); @@ -1146,6 +1119,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, 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; @@ -1212,8 +1191,16 @@ 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(*options->data_ptr); int (*compar)(const void *, const void *); @@ -1224,13 +1211,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: @@ -1255,6 +1242,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; } @@ -1268,15 +1257,16 @@ 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; @@ -1287,21 +1277,22 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) 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, 0); + 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++; @@ -1340,7 +1331,7 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) 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 (options->flags & LS_FLAG_ADMISSIBLE_ONLY) { + 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); @@ -1351,31 +1342,21 @@ err: return ret; } -static void com_ls_callback(int fd, const struct osl_object *query) +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 = { - .max_size = shm_get_shmmax(), - .flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0, - .max_size_handler = afs_max_size_handler, - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - } - }; + 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; - 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; - } - } 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(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, @@ -1383,163 +1364,113 @@ static void com_ls_callback(int fd, const struct osl_object *query) if (ret < 0) goto out; if (opts->num_matching_paths == 0) { - if (opts->num_patterns > 0) - para_printf(&b, "no matches\n"); + ret = lls_num_inputs(opts->lpr) > 0? -E_NO_MATCH : 0; goto out; } ret = sort_matching_paths(opts); if (ret < 0) goto out; time(¤t_time); - if (opts->flags & LS_FLAG_REVERSE) + 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, current_time); + 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, &b, current_time); + ret = print_list_item(opts->data_ptr[i], opts, + &aca->pbout, current_time); if (ret < 0) goto out; } out: - if (b.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, b.buf, b.offset); - free(b.buf); + 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) - */ -int com_ls(struct command_context *cc) +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)}; - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - /* - * Compatibility: Prior to 0.5.5 it was necessary to specify - * the listing mode without the '=' character as in -lv, for - * example. Now the variant with '=' is preferred and - * documented but we still accept the old way to specify the - * listing mode. - * - * Support for the legacy syntax can be dropped at 0.6.0 - * or later. - */ - if (!strncmp(arg, "-l", 2)) { - arg += 2; - if (*arg == '=') - arg++; - switch (*arg) { - case 's': - mode = LS_MODE_SHORT; - continue; - case 'l': - case '\0': - mode = LS_MODE_LONG; - continue; - case 'v': - mode = LS_MODE_VERBOSE; - continue; - case 'm': - mode = LS_MODE_MBOX; - continue; - case 'c': - mode = LS_MODE_CHUNKS; - continue; - case 'p': - mode = LS_MODE_PARSER; - 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 (!strcmp(arg, "-d")) { - flags |= LS_FLAG_UNIXDATE; - continue; + 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; } - /* The compatibility remark above applies also to -s. */ - if (!strncmp(arg, "-s", 2)) { - arg += 2; - if (*arg == '=') - arg++; - switch (*arg) { - 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; - } + } + 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; } - return -E_AFT_SYNTAX; } - opts.flags = flags; - opts.sorting = sort; - opts.mode = mode; - opts.num_patterns = cc->argc - i; - ret = send_option_arg_callback_request(&query, opts.num_patterns, - cc->argv + i, com_ls_callback, afs_cb_result_handler, cc); + 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. @@ -1555,13 +1486,22 @@ int audio_file_loop(void *private_data, osl_rbtree_loop_func *func) func)); } -static struct osl_row *find_hash_sister(unsigned char *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; } /** The format of the data stored by save_audio_file_data(). */ @@ -1570,8 +1510,8 @@ enum com_add_buffer_offsets { CAB_AFHI_OFFSET_POS = 0, /** Start of the chunk table (if present). */ CAB_CHUNKS_OFFSET_POS = 4, - /** Flags given to the add command. */ - CAB_FLAGS_OFFSET = 8, + /** 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. */ @@ -1588,31 +1528,32 @@ enum com_add_buffer_offsets { * 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, uint32_t flags, + 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 = CAB_PATH_OFFSET + path_len + afhi_size - + sizeof_chunk_table(afhi); + + sizeof_chunk_table(afhi) + slpr_size; char *buf = para_malloc(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); + 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; - write_u32(buf + CAB_CHUNKS_OFFSET_POS, pos); - if (afhi) + if (afhi) { save_chunk_table(afhi, buf + pos); - - write_u32(buf + CAB_FLAGS_OFFSET, flags); - 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 += 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; } @@ -1658,7 +1599,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: @@ -1668,21 +1609,9 @@ Notes: */ -/** Flags passed to the add command. */ -enum com_add_flags { - /** Skip paths that exist already. */ - ADD_FLAG_LAZY = 1, - /** Force adding. */ - ADD_FLAG_FORCE = 2, - /** Print what is being done. */ - ADD_FLAG_VERBOSE = 4, - /** Try to add files with unknown suffixes. */ - ADD_FLAG_ALL = 8, -}; - -static void com_add_callback(int fd, const struct osl_object *query) +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; struct osl_row *hs; struct osl_object objs[NUM_AFT_COLUMNS]; @@ -1690,17 +1619,16 @@ static void com_add_callback(int fd, const struct osl_object *query) char asc[2 * HASH_SIZE + 1]; int ret; char afsi_buf[AFSI_SIZE]; - uint32_t flags = read_u32(buf + CAB_FLAGS_OFFSET); + char *slpr = buf + read_u32(buf + CAB_LPR_OFFSET); struct afs_info default_afsi = {.last_played = 0}; - struct para_buffer msg = { - .max_size = shm_get_shmmax(), - .max_size_handler = afs_max_size_handler, - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - } - }; 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); @@ -1712,47 +1640,48 @@ static void com_add_callback(int fd, const struct osl_object *query) objs[AFTCOL_PATH].size = strlen(path) + 1; PARA_INFO_LOG("request to add %s\n", path); - hs = find_hash_sister(hash); - ret = aft_get_row_of_path(path, &pb); - if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) + ret = find_hash_sister(hash, &hs); + if (ret < 0) goto out; - if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) { - if (flags & ADD_FLAG_VERBOSE) - ret = para_printf(&msg, "ignoring duplicate\n"); - else - ret = 1; + 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) { - ret = para_printf(&msg, "removing %s\n", path); - if (ret < 0) - goto out; - } - afs_event(AUDIO_FILE_REMOVE, &msg, 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) + goto out; ret = osl(osl_del_row(audio_file_table, pb)); if (ret < 0) goto out; pb = NULL; } /* file rename, update hs' path */ - if (flags & ADD_FLAG_VERBOSE) { + if (lls_opt_given(r_v)) { ret = osl(osl_get_object(audio_file_table, hs, AFTCOL_PATH, &obj)); if (ret < 0) goto out; - ret = para_printf(&msg, "renamed from %s\n", (char *)obj.data); - 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) goto out; - afs_event(AUDIO_FILE_RENAME, &msg, hs); - if (!(flags & ADD_FLAG_FORCE)) + 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 */ @@ -1765,7 +1694,7 @@ static void com_add_callback(int fd, const struct osl_object *query) if (!objs[AFTCOL_AFHI].size) /* "impossible" */ 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]; unsigned char *old_hash; @@ -1773,42 +1702,35 @@ static void com_add_callback(int fd, const struct osl_object *query) if (ret < 0) goto out; hash_to_asc(old_hash, old_asc); - if (flags & ADD_FLAG_VERBOSE) { - ret = para_printf(&msg, "file change: %s -> %s\n", + if (lls_opt_given(r_v)) + para_printf(&aca->pbout, "file change: %s -> %s\n", old_asc, asc); - if (ret < 0) - goto out; - } - 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) goto out; } if (hs || pb) { /* (hs != NULL and pb != NULL) implies hs == pb */ struct osl_row *row = pb? pb : hs; /* update afhi and chunk_table */ - if (flags & ADD_FLAG_VERBOSE) { - ret = para_printf(&msg, "updating afhi and chunk table\n"); - if (ret < 0) - goto out; - } + 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) 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) goto out; - afs_event(AFHI_CHANGE, &msg, row); + ret = afs_event(AFHI_CHANGE, &aca->pbout, row); goto out; } /* new entry, use default afsi */ - if (flags & ADD_FLAG_VERBOSE) { - ret = para_printf(&msg, "new file\n"); - if (ret < 0) - goto out; - } + 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 + CAB_AUDIO_FORMAT_ID_OFFSET); @@ -1816,43 +1738,48 @@ static void com_add_callback(int fd, const struct osl_object *query) objs[AFTCOL_AFSI].size = AFSI_SIZE; save_afsi(&default_afsi, &objs[AFTCOL_AFSI]); ret = osl(osl_add_and_get_row(audio_file_table, objs, &aft_row)); - afs_event(AUDIO_FILE_ADD, &msg, aft_row); + if (ret < 0) + goto out; + ret = afs_event(AUDIO_FILE_ADD, &aca->pbout, aft_row); out: if (ret < 0) - para_printf(&msg, "%s\n", para_strerror(-ret)); - if (msg.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, msg.buf, msg.offset); - free(msg.buf); + para_printf(&aca->pbout, "could not add %s\n", path); + lls_free_parse_result(aca->lpr, cmd); + return ret; } -/** Used by com_add(). */ +/* Used by com_add(). */ struct private_add_data { - /** The pointer passed to the original command handler. */ + /* The pointer passed to the original command handler. */ struct command_context *cc; - /** The given add flags. */ - uint32_t flags; + /* 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 void path_brother_callback(int fd, const struct osl_object *query) +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) - return; - pass_buffer_as_shm(fd, SBD_OUTPUT, (char *)&path_brother, + int ret = find_path_brother(path, &path_brother); + if (ret <= 0) + return ret; + return pass_buffer_as_shm(aca->fd, SBD_OUTPUT, (char *)&path_brother, sizeof(path_brother)); } -static void hash_sister_callback(int fd, const struct osl_object *query) +static int hash_sister_callback(struct afs_callback_arg *aca) { - unsigned char *hash = query->data; + unsigned char *hash = aca->query.data; struct osl_row *hash_sister; + int ret = find_hash_sister(hash, &hash_sister); - hash_sister = find_hash_sister(hash); - if (!hash_sister) - return; - pass_buffer_as_shm(fd, SBD_OUTPUT, (char *)&hash_sister, + if (ret <= 0) + return ret; + return pass_buffer_as_shm(aca->fd, SBD_OUTPUT, (char *)&hash_sister, sizeof(hash_sister)); } @@ -1860,7 +1787,9 @@ static int get_row_pointer_from_result(struct osl_object *result, __a_unused uint8_t band, void *private) { struct osl_row **row = private; - *row = *(struct osl_row **)(result->data); + + if (band == SBD_OUTPUT) + *row = *(struct osl_row **)(result->data); return 1; } @@ -1873,9 +1802,13 @@ static int add_one_audio_file(const char *path, void *private_data) struct osl_row *pb = NULL, *hs = NULL; /* path brother/hash sister */ 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 && !(pad->flags & ADD_FLAG_ALL)) { + if (ret < 0 && !a_given) { ret = 0; goto out_free; } @@ -1886,8 +1819,8 @@ static int add_one_audio_file(const char *path, void *private_data) if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) goto out_free; ret = 1; - if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */ - if (pad->flags & ADD_FLAG_VERBOSE) + 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; @@ -1907,8 +1840,8 @@ static int add_one_audio_file(const char *path, void *private_data) 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) + 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; @@ -1917,7 +1850,7 @@ static int add_one_audio_file(const char *path, void *private_data) * 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)) { + if (!hs || f_given) { ret = compute_afhi(path, map.data, map.size, fd, &afhi); if (ret < 0) goto out_unmap; @@ -1926,13 +1859,14 @@ static int add_one_audio_file(const char *path, void *private_data) } munmap(map.data, map.size); close(fd); - if (pad->flags & ADD_FLAG_VERBOSE) { + 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->flags, format_num, &obj); + 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, afs_cb_result_handler, pad->cc); @@ -1951,46 +1885,30 @@ out_free: return send_ret; } -int com_add(struct command_context *cc) +static int com_add(struct command_context *cc, struct lls_parse_result *lpr) { int i, ret; - struct private_add_data pad = {.cc = cc, .flags = 0}; - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-a")) { - pad.flags |= ADD_FLAG_ALL; - continue; - } - if (!strcmp(arg, "-l")) { - pad.flags |= ADD_FLAG_LAZY; - continue; - } - if (!strcmp(arg, "-f")) { - pad.flags |= ADD_FLAG_FORCE; - continue; - } - if (!strcmp(arg, "-v")) { - pad.flags |= ADD_FLAG_VERBOSE; - continue; - } + 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; } - if (cc->argc <= i) - return -E_AFT_SYNTAX; - for (; i < cc->argc; i++) { + 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(cc->argv[i], &path); + ret = verify_path(lls_input(i, lpr), &path); if (ret < 0) { ret = send_sb_va(&cc->scc, SBD_ERROR_LOG, "%s: %s\n", - cc->argv[i], para_strerror(-ret)); + lls_input(i, lpr), para_strerror(-ret)); if (ret < 0) - return ret; + goto out; continue; } if (ret == 1) /* directory */ @@ -2006,14 +1924,14 @@ int com_add(struct command_context *cc) } free(path); } - return 1; + ret = 1; +out: + free(pad.slpr); + return ret; } +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, @@ -2021,331 +1939,202 @@ enum touch_flags { TOUCH_FLAG_VERBOSE = 2 }; -/** Options used by com_touch(). */ -struct com_touch_options { - /** New num_played value. */ - int32_t num_played; - /** New last played count. */ - int64_t last_played; - /** New lyrics id. */ - int32_t lyrics_id; - /** New image id. */ - int32_t image_id; - /** New amplification value. */ - int32_t amp; - /** Command line flags (see \ref touch_flags). */ - unsigned flags; -}; - -/** Data passed to the action handler of com_touch(). */ -struct touch_action_data { - /** Command line options (see \ref com_touch_options). */ - struct com_touch_options *cto; - /** Message buffer. */ - struct para_buffer pb; -}; - static int touch_audio_file(__a_unused struct osl_table *table, struct osl_row *row, const char *name, void *data) { - struct touch_action_data *tad = data; + 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; - int ret, no_options = tad->cto->num_played < 0 && tad->cto->last_played < 0 && - tad->cto->lyrics_id < 0 && tad->cto->image_id < 0 && tad->cto->amp < 0; + 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) - return para_printf(&tad->pb, "%s: %s\n", name, para_strerror(-ret)); + if (ret < 0) { + para_printf(&aca->pbout, "cannot touch %s\n", name); + return ret; + } ret = load_afsi(&old_afsi, &obj); - if (ret < 0) - return para_printf(&tad->pb, "%s: %s\n", name, para_strerror(-ret)); + 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 (tad->cto->flags & TOUCH_FLAG_VERBOSE) { - ret = para_printf(&tad->pb, "%s: num_played = %u, " + if (v_given) + para_printf(&aca->pbout, "%s: num_played = %u, " "last_played = now()\n", name, new_afsi.num_played); - if (ret < 0) - return ret; - } } else { - if (tad->cto->flags & TOUCH_FLAG_VERBOSE) { - ret = para_printf(&tad->pb, "touching %s\n", name); - if (ret < 0) - return ret; - } - if (tad->cto->lyrics_id >= 0) - new_afsi.lyrics_id = tad->cto->lyrics_id; - if (tad->cto->image_id >= 0) - new_afsi.image_id = tad->cto->image_id; - if (tad->cto->num_played >= 0) - new_afsi.num_played = tad->cto->num_played; - if (tad->cto->last_played >= 0) - new_afsi.last_played = tad->cto->last_played; - if (tad->cto->amp >= 0) - new_afsi.amp = tad->cto->amp; + 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; - afs_event(AFSI_CHANGE, &tad->pb, &aced); - return 1; + return afs_event(AFSI_CHANGE, &aca->pbout, &aced); } -static void com_touch_callback(int fd, const struct osl_object *query) +static int com_touch_callback(struct afs_callback_arg *aca) { - struct touch_action_data tad = {.cto = query->data, - .pb = { - .max_size = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - }, - .max_size_handler = afs_max_size_handler - } - }; - int ret, ret2 = 0; + 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, - .patterns = {.data = (char *)query->data + sizeof(*tad.cto), - .size = query->size - sizeof(*tad.cto)}, - .data = &tad, + .data = aca, .action = touch_audio_file }; - if (tad.cto->flags & TOUCH_FLAG_FNM_PATHNAME) - pmd.fnmatch_flags |= FNM_PATHNAME; - ret = for_each_matching_row(&pmd); - if (ret < 0) - ret2 = para_printf(&tad.pb, "%s\n", para_strerror(-ret)); - else if (pmd.num_matches == 0) - ret2 = para_printf(&tad.pb, "no matches\n"); - if (ret2 >= 0 && tad.pb.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, tad.pb.buf, tad.pb.offset); - free(tad.pb.buf); -} - -int com_touch(struct command_context *cc) -{ - struct com_touch_options cto = { - .num_played = -1, - .last_played = -1, - .lyrics_id = -1, - .image_id = -1, - .amp = -1, - }; - struct osl_object query = {.data = &cto, .size = sizeof(cto)}; - int i, ret; + ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr); + assert(ret >= 0); + pmd.lpr = aca->lpr; - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strncmp(arg, "-n=", 3)) { - ret = para_atoi32(arg + 3, &cto.num_played); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-l=", 3)) { - ret = para_atoi64(arg + 3, &cto.last_played); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-y=", 3)) { - ret = para_atoi32(arg + 3, &cto.lyrics_id); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-i=", 3)) { - ret = para_atoi32(arg + 3, &cto.image_id); - if (ret < 0) - return ret; - continue; - } - if (!strncmp(arg, "-a=", 3)) { - int32_t val; - ret = para_atoi32(arg + 3, &val); - if (ret < 0) - return ret; - if (val < 0 || val > 255) - return -ERRNO_TO_PARA_ERROR(EINVAL); - cto.amp = val; - continue; - } - if (!strcmp(arg, "-p")) { - cto.flags |= TOUCH_FLAG_FNM_PATHNAME; - continue; + 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; } - if (!strcmp(arg, "-v")) { - cto.flags |= TOUCH_FLAG_VERBOSE; - continue; + } + 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; } - break; /* non-option starting with dash */ } - if (i >= cc->argc) - return -E_AFT_SYNTAX; - ret = send_option_arg_callback_request(&query, cc->argc - i, - cc->argv + i, com_touch_callback, afs_cb_result_handler, cc); - if (ret < 0) - send_strerror(cc, -ret); + 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; } -/** Flags for com_rm(). */ -enum rm_flags { - /** -v */ - RM_FLAG_VERBOSE = 1, - /** -f */ - RM_FLAG_FORCE = 2, - /** -p */ - RM_FLAG_FNM_PATHNAME = 4 -}; +static int com_touch(struct command_context *cc, struct lls_parse_result *lpr) +{ + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH); + int ret; + char *errctx; -/** Data passed to the action handler of com_rm(). */ -struct com_rm_action_data { - /** Command line flags ((see \ref rm_flags). */ - uint32_t flags; - /** Message buffer. */ - struct para_buffer pb; -}; + 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 com_rm_action_data *crd = data; + struct afs_callback_arg *aca = data; + bool v_given = SERVER_CMD_OPT_GIVEN(RM, VERBOSE, aca->lpr); int ret; - if (crd->flags & RM_FLAG_VERBOSE) { - ret = para_printf(&crd->pb, "removing %s\n", name); - if (ret < 0) - return ret; - } - afs_event(AUDIO_FILE_REMOVE, &crd->pb, row); + 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(&crd->pb, "%s: %s\n", name, para_strerror(-ret)); + para_printf(&aca->pbout, "cannot remove %s\n", name); return ret; } -static void com_rm_callback(int fd, const struct osl_object *query) +static int com_rm_callback(struct afs_callback_arg *aca) { - struct com_rm_action_data crd = {.flags = *(uint32_t *)query->data, - .pb = { - .max_size = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - }, - .max_size_handler = afs_max_size_handler - } - }; + 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, - .patterns = {.data = (char *)query->data + sizeof(uint32_t), - .size = query->size - sizeof(uint32_t)}, - .data = &crd, + .data = aca, .action = remove_audio_file }; - if (crd.flags & RM_FLAG_FNM_PATHNAME) + 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) { - para_printf(&crd.pb, "%s\n", para_strerror(-ret)); - return; - } - if ((pmd.num_matches == 0) && !(crd.flags & RM_FLAG_FORCE)) - ret = para_printf(&crd.pb, "no matches -- nothing removed\n"); - else if (crd.flags & RM_FLAG_VERBOSE) { - ret = para_printf(&crd.pb, "removed %u files\n", + 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); - } - if (ret >= 0 && crd.pb.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, crd.pb.buf, crd.pb.offset); - free(crd.pb.buf); +out: + lls_free_parse_result(aca->lpr, cmd); + return ret; } /* TODO options: -r (recursive) */ -int com_rm(struct command_context *cc) +static int com_rm(struct command_context *cc, struct lls_parse_result *lpr) { - uint32_t flags = 0; - struct osl_object query = {.data = &flags, .size = sizeof(flags)}; - int i, ret; + const struct lls_command *cmd = SERVER_CMD_CMD_PTR(RM); + char *errctx; + int ret; - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-f")) { - flags |= RM_FLAG_FORCE; - continue; - } - if (!strcmp(arg, "-p")) { - flags |= RM_FLAG_FNM_PATHNAME; - continue; - } - if (!strcmp(arg, "-v")) { - flags |= RM_FLAG_VERBOSE; - continue; - } - break; + ret = lls(lls_check_arg_count(lpr, 1, INT_MAX, &errctx)); + if (ret < 0) { + send_errctx(cc, errctx); + return ret; } - if (i >= cc->argc) - return -E_AFT_SYNTAX; - ret = send_option_arg_callback_request(&query, cc->argc - i, - cc->argv + i, com_rm_callback, afs_cb_result_handler, cc); - if (ret < 0) - send_strerror(cc, -ret); - return ret; + return send_lls_callback_request(com_rm_callback, cmd, lpr, cc); } - -/** - * Flags used by the cpsi command. - * - * \sa com_cpsi(). - */ -enum cpsi_flags { - /** Whether the lyrics id should be copied. */ - CPSI_FLAG_COPY_LYRICS_ID = 1, - /** Whether the image id should be copied. */ - CPSI_FLAG_COPY_IMAGE_ID = 2, - /** Whether the lastplayed time should be copied. */ - CPSI_FLAG_COPY_LASTPLAYED = 4, - /** Whether the numplayed count should be copied. */ - CPSI_FLAG_COPY_NUMPLAYED = 8, - /** Whether the attributes should be copied. */ - CPSI_FLAG_COPY_ATTRIBUTES = 16, - /** Activates verbose mode. */ - CPSI_FLAG_VERBOSE = 32, -}; +EXPORT_SERVER_CMD_HANDLER(rm); /** Data passed to the action handler of com_cpsi(). */ struct cpsi_action_data { - /** command line flags (see \ref cpsi_flags). */ - unsigned flags; - /** Message buffer. */ - struct para_buffer pb; /** 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, @@ -2356,132 +2145,100 @@ static int copy_selector_info(__a_unused struct osl_table *table, 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; - 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->flags & CPSI_FLAG_COPY_LYRICS_ID) + if (cad->copy_all || y_given) target_afsi.lyrics_id = cad->source_afsi.lyrics_id; - if (cad->flags & CPSI_FLAG_COPY_IMAGE_ID) + if (cad->copy_all || i_given) target_afsi.image_id = cad->source_afsi.image_id; - if (cad->flags & CPSI_FLAG_COPY_LASTPLAYED) + if (cad->copy_all || l_given) target_afsi.last_played = cad->source_afsi.last_played; - if (cad->flags & CPSI_FLAG_COPY_NUMPLAYED) + if (cad->copy_all || n_given) target_afsi.num_played = cad->source_afsi.num_played; - if (cad->flags & CPSI_FLAG_COPY_ATTRIBUTES) + if (cad->copy_all || a_given) target_afsi.attributes = cad->source_afsi.attributes; save_afsi(&target_afsi, &target_afsi_obj); /* in-place update */ - if (cad->flags & CPSI_FLAG_VERBOSE) { - ret = para_printf(&cad->pb, "copied afsi to %s\n", name); - if (ret < 0) - return ret; - } + if (v_given) + para_printf(&cad->aca->pbout, "copied afsi to %s\n", name); aced.aft_row = row; aced.old_afsi = &old_afsi; - afs_event(AFSI_CHANGE, &cad->pb, &aced); - return 1; + return afs_event(AFSI_CHANGE, &cad->aca->pbout, &aced); } -static void com_cpsi_callback(int fd, const struct osl_object *query) +static int com_cpsi_callback(struct afs_callback_arg *aca) { - struct cpsi_action_data cad = { - .flags = *(unsigned *)query->data, - .pb = { - .max_size = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - }, - .max_size_handler = afs_max_size_handler - } - }; + 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; - char *source_path = (char *)query->data + sizeof(cad.flags); struct pattern_match_data pmd = { .table = audio_file_table, .loop_col_num = AFTCOL_HASH, .match_col_num = AFTCOL_PATH, - .patterns = {.data = source_path + strlen(source_path) + 1, - .size = query->size - sizeof(cad.flags) - - strlen(source_path) - 1}, + .input_skip = 1, /* skip first argument (source file) */ .data = &cad, .action = copy_selector_info }; - ret = get_afsi_of_path(source_path, &cad.source_afsi); + 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); -out: if (ret < 0) - para_printf(&cad.pb, "%s\n", para_strerror(-ret)); - else if (pmd.num_matches > 0) { - if (cad.flags & CPSI_FLAG_VERBOSE) - para_printf(&cad.pb, "copied requested afsi from %s " - "to %u files\n", source_path, pmd.num_matches); + 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 - para_printf(&cad.pb, "no matches - nothing copied\n"); - if (cad.pb.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset); - free(cad.pb.buf); + ret = -E_NO_MATCH; +out: + lls_free_parse_result(aca->lpr, cmd); + return ret; } -int com_cpsi(struct command_context *cc) +static int com_cpsi(struct command_context *cc, struct lls_parse_result *lpr) { - unsigned flags = 0; - int i, ret; - struct osl_object options = {.data = &flags, .size = sizeof(flags)}; - - for (i = 1; i < cc->argc; i++) { - const char *arg = cc->argv[i]; - if (arg[0] != '-') - break; - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-y")) { - flags |= CPSI_FLAG_COPY_LYRICS_ID; - continue; - } - if (!strcmp(arg, "-i")) { - flags |= CPSI_FLAG_COPY_IMAGE_ID; - continue; - } - if (!strcmp(arg, "-l")) { - flags |= CPSI_FLAG_COPY_LASTPLAYED; - continue; - } - if (!strcmp(arg, "-n")) { - flags |= CPSI_FLAG_COPY_NUMPLAYED; - continue; - } - if (!strcmp(arg, "-a")) { - flags |= CPSI_FLAG_COPY_ATTRIBUTES; - continue; - } - if (!strcmp(arg, "-v")) { - flags |= CPSI_FLAG_VERBOSE; - continue; - } - break; + 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; } - if (i + 1 >= cc->argc) /* need at least source file and pattern */ - return -E_AFT_SYNTAX; - if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */ - flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags; - ret = send_option_arg_callback_request(&options, cc->argc - i, - cc->argv + i, com_cpsi_callback, afs_cb_result_handler, cc); - if (ret < 0) - send_strerror(cc, -ret); - 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 para_buffer pb; + struct afs_callback_arg *aca; }; static int change_atts(__a_unused struct osl_table *table, @@ -2506,25 +2263,14 @@ static int change_atts(__a_unused struct osl_table *table, new_afsi.attributes |= cad->add_mask; new_afsi.attributes &= ~cad->del_mask; save_afsi(&new_afsi, &obj); /* in-place update */ - afs_event(AFSI_CHANGE, &cad->pb, &aced); - return 1; + return afs_event(AFSI_CHANGE, &cad->aca->pbout, &aced); } -static void com_setatt_callback(int fd, const struct osl_object *query) +static int com_setatt_callback(struct afs_callback_arg *aca) { - char *p; - int ret; - size_t len; - struct change_atts_data cad = { - .pb = { - .max_size = shm_get_shmmax(), - .max_size_handler = afs_max_size_handler, - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - } - } - }; + 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, @@ -2533,66 +2279,79 @@ static void com_setatt_callback(int fd, const struct osl_object *query) .data = &cad, .action = change_atts }; + unsigned num_inputs; - for (p = query->data; p < (char *)query->data + query->size; p += len + 1) { - char c; + 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); - len = strlen(p); ret = -E_ATTR_SYNTAX; if (len == 0) goto out; - c = p[len - 1]; - if (c != '+' && c != '-') - break; + 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(&cad.pb, "attribute not found: %s\n", p); + para_printf(&aca->pbout, "invalid argument: %s\n", arg); goto out; } if (c == '+') - cad.add_mask |= (1UL << bitnum); + cad.add_mask |= (one << bitnum); else - cad.del_mask |= (1UL << bitnum); + cad.del_mask |= (one << bitnum); } + /* no pattern given */ ret = -E_ATTR_SYNTAX; - if (!cad.add_mask && !cad.del_mask) - goto out; - pmd.patterns.data = p; - if (p >= (char *)query->data + query->size) - goto out; - pmd.patterns.size = (char *)query->data + query->size - p; + goto out; +set_atts: + pmd.input_skip = i; ret = for_each_matching_row(&pmd); - if (ret < 0) - goto out; - if (pmd.num_matches == 0) - para_printf(&cad.pb, "no matches\n"); + if (ret >= 0 && pmd.num_matches == 0) + ret = -E_NO_MATCH; out: - if (ret < 0) - para_printf(&cad.pb, "%s\n", para_strerror(-ret)); - if (cad.pb.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset); - free(cad.pb.buf); + lls_free_parse_result(aca->lpr, cmd); + return ret; } -int com_setatt(struct command_context *cc) +static int com_setatt(struct command_context *cc, struct lls_parse_result *lpr) { - if (cc->argc < 3) - return -E_ATTR_SYNTAX; - return send_standard_callback_request(cc->argc - 1, cc->argv + 1, - com_setatt_callback, afs_cb_result_handler, cc); + 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 void afs_stat_callback(int fd, const struct osl_object *query) +static int afs_stat_callback(struct afs_callback_arg *aca) { - int *parser_friendly = query->data; + int *parser_friendly = aca->query.data; char *buf = *parser_friendly? parser_friendly_status_items : status_items; if (!buf) - return; - pass_buffer_as_shm(fd, SBD_OUTPUT, buf, strlen(buf)); + return 0; + return pass_buffer_as_shm(aca->fd, SBD_OUTPUT, buf, strlen(buf)); } /** @@ -2618,7 +2377,7 @@ int send_afs_status(struct command_context *cc, int parser_friendly) afs_cb_result_handler, cc); } -/* TODO: optionally fix problems by removing offending rows */ +/* returns success on non-fatal errors to keep the loop going */ static int check_audio_file(struct osl_row *row, void *data) { char *path; @@ -2628,88 +2387,123 @@ static int check_audio_file(struct osl_row *row, void *data) struct afs_info afsi; char *blob_name; - if (ret < 0) - return para_printf(pb, "%s\n", para_strerror(-ret)); - if (stat(path, &statbuf) < 0) { - ret = para_printf(pb, "%s: stat error (%s)\n", path, strerror(errno)); - if (ret < 0) - return ret; - } else { - if (!S_ISREG(statbuf.st_mode)) { - ret = para_printf(pb, "%s: not a regular file\n", path); - if (ret < 0) - return ret; - } + if (ret < 0) { + para_printf(pb, "%s\n", para_strerror(-ret)); + 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) - return para_printf(pb, "%s: %s\n", path, para_strerror(-ret)); - ret = lyr_get_name_by_id(afsi.lyrics_id, &blob_name); if (ret < 0) { - ret = para_printf(pb, "%s lyrics id %u: %s\n", path, afsi.lyrics_id, - para_strerror(-ret)); - if (ret < 0) - return ret; + 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) - ret = para_printf(pb, "%s image id %u: %s\n", path, afsi.image_id, + para_printf(pb, "%s image id %u: %s\n", path, afsi.image_id, para_strerror(-ret)); - return ret; + return 0; } /** * Check the audio file table for inconsistencies. * - * \param fd The afs socket. - * \param query Unused. + * \param aca Only ->pbout is used for diagnostics. * - * This function always succeeds. - * - * \sa com_check(). + * \return Standard. Inconsistencies are reported but not regarded as an error. */ -void aft_check_callback(int fd, __a_unused const struct osl_object *query) -{ - struct para_buffer pb = { - .max_size = shm_get_shmmax(), - .private_data = &(struct afs_max_size_handler_data) { - .fd = fd, - .band = SBD_OUTPUT - }, - .max_size_handler = afs_max_size_handler - }; - int ret = para_printf(&pb, "checking audio file table...\n"); +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); +} - if (ret < 0) - return; - audio_file_loop(&pb, check_audio_file); - if (pb.offset) - pass_buffer_as_shm(fd, SBD_OUTPUT, pb.buf, pb.offset); - free(pb.buf); +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) +{ + 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; } /** - * Close the audio file table. + * Iterate over all audio files and check the attribute bit mask. + * + * \param att_mask The mask of all valid attributes. + * \param pb Used for reporting inconsistencies. + * + * 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. * - * \param flags Usual flags that are passed to osl_close_table(). + * \return Standard. Inconsistencies are not regarded as errors. * - * \sa osl_close_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. */ 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; } -/** - * 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; @@ -2719,8 +2513,21 @@ static int aft_open(const char *dir) if (ret >= 0) { unsigned num; 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_NOTICE_LOG("failed to open audio file table\n"); audio_file_table = NULL; @@ -2761,11 +2568,9 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, switch (event) { case ATTRIBUTE_REMOVE: { const struct rmatt_event_data *red = data; - ret = para_printf(pb, "clearing attribute %s (bit %u) from all " + para_printf(pb, "clearing attribute %s (bit %u) from all " "entries in the audio file table\n", red->name, red->bitnum); - if (ret < 0) - return ret; return audio_file_loop(data, clear_attribute); } case AFSI_CHANGE: { struct afsi_change_event_data *aced = data; @@ -2778,6 +2583,16 @@ 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; + status_item_ls_data.path = path; + make_status_items(); + return 1; } case AFHI_CHANGE: { if (data != current_aft_row) return 0; @@ -2786,6 +2601,21 @@ 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 + * every time. + */ + make_status_items(); + return 0; } default: return 0; }