From: Andre Noll Date: Sun, 27 Dec 2015 22:04:29 +0000 (+0000) Subject: server: Convert com_ls() to lopsub. X-Git-Tag: v0.6.0~2^2~58 X-Git-Url: http://git.tue.mpg.de/?a=commitdiff_plain;h=7af252cbfe13650ee0a6ae18abe22422527a70c1;p=paraslash.git server: Convert com_ls() to lopsub. This is the final server command to be converted to the lopsub API. Besides the conversion to lopsub, the patch changes the semantics of the ls command in two ways: The deprecated -p option is removed, and the -F option is made the default so that full paths are printed if -b is not given. Regarding the conversion, the patch removes enum ls_flags which used to contain the various flags of the ls command. This information can now be extracted in the callback from the deserialized parse result. Also write_score() is kind of pointless and is replaced by inlining it. Note that make_status_items() is also affected by this patch because it makes use of the infrastructure of the ls command. This function is called from the event handler of the audio file table for events of type AFSI_CHANGE and AFHI_CHANGE. Since no more non-lopsub server commands are left after this patch, several cleanups are possible. These are dealt with in subsequent patches. --- diff --git a/afs.cmd b/afs.cmd index 1c7583d7..f377a604 100644 --- a/afs.cmd +++ b/afs.cmd @@ -2,52 +2,3 @@ BN: afs SF: afs.c aft.c attribute.c SN: list of afs commands TM: mood lyr img pl ---- -N: ls -P: AFS_READ -D: List audio files. -U: ls [-l=mode] [-p] [-a] [-r] [-d] [-s=order] [pattern...] -H: Print a list of all audio files matching pattern. -H: -H: Options: -H: -H: -l=mode Change listing mode. Defaults to short listing if not given. -H: -H: Available modes: -H: s: short listing mode -H: l: long listing mode (equivalent to -l) -H: v: verbose listing mode -H: p: parser-friendly mode -H: m: mbox listing mode -H: c: chunk-table listing mode -H: -H: -F List full paths. If this option is not specified, only the basename -H: of each file is printed. -H: -p Synonym for -F. Deprecated. -H: -H: -b Print only the basename of each matching file. This is the default, so -H: the option is currently a no-op. It is recommended to specify this option, -H: though, as the default might change in a future release. -H: -H: -a List only files that are admissible with respect to the current mood or -H: playlist. -H: -H: -r Reverse sort order. -H: -H: -d Print dates as seconds after the epoch. -H: -H: -s=order -H: Change sort order. Defaults to alphabetical path sort if not given. -H: -H: Possible values for order: -H: p: by path -H: l: by last played time -H: s: by score (implies -a) -H: n: by num played count -H: f: by frequency -H: c: by number of channels -H: i: by image id -H: y: by lyrics id -H: b: by bit rate -H: d: by duration -H: a: by audio format diff --git a/aft.c b/aft.c index 841e00b8..aebb3efb 100644 --- a/aft.c +++ b/aft.c @@ -88,18 +88,6 @@ struct ls_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. * @@ -130,16 +118,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. */ @@ -785,11 +768,11 @@ static void write_image_items(struct para_buffer *b, struct afs_info *afsi) } static void write_filename_items(struct para_buffer *b, const char *path, - unsigned flags) + bool basename) { char *val; - if (!(flags & LS_FLAG_FULL_PATH)) { + if (basename) { WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); return; } @@ -830,17 +813,12 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) return ret; } -static void write_score(struct para_buffer *b, struct ls_data *d, - struct ls_options *opts) -{ - if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/ - 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]; @@ -859,7 +837,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 { @@ -871,10 +849,9 @@ 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) { + if (lls_opt_given(r_a)) para_printf(b, "%*li ", opts->widths.score_width, d->score); - } para_printf(b, "%s " /* attributes */ "%*u " /* amp */ @@ -914,8 +891,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, last_played_time, bn? bn : "?"); } - write_filename_items(b, d->path, opts->flags); - write_score(b, d, opts); + write_filename_items(b, d->path, lls_opt_given(r_b) > 0); + 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; @@ -982,21 +960,24 @@ out: 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", "--path", "--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; + 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; @@ -1009,7 +990,10 @@ static int make_status_items(void) make_inode_status_items(&pb); free(parser_friendly_status_items); parser_friendly_status_items = pb.buf; - return 1; + ret = 1; +out: + lls_free_parse_result(opts.lpr, cmd); + return ret; } /** @@ -1163,8 +1147,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 *); @@ -1175,13 +1167,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: @@ -1219,15 +1211,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) > 0; 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; @@ -1238,21 +1231,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++; @@ -1291,7 +1285,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); @@ -1304,21 +1298,19 @@ err: 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; - char *p, *pattern_start = (char *)aca->query.data + sizeof(*opts); int i = 0, ret; time_t current_time; + const struct lls_opt_result *r_r; + + 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 (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) + if (admissible_only(opts)) ret = admissible_file_loop(opts, prepare_ls_row); else ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, @@ -1326,14 +1318,14 @@ static int com_ls_callback(struct afs_callback_arg *aca) if (ret < 0) goto out; if (opts->num_matching_paths == 0) { - ret = opts->num_patterns > 0? -E_NO_MATCH : 0; + 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, &aca->pbout, current_time); @@ -1348,143 +1340,90 @@ static int com_ls_callback(struct afs_callback_arg *aca) goto out; } out: + 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) +/* TODO: flags -h (sort by hash) */ +static int com_ls(struct command_context *cc, struct lls_parse_result *lpr) { - int i; - 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") || !strcmp(arg, "-F")) { - flags |= LS_FLAG_FULL_PATH; - continue; - } - if (!strcmp(arg, "-b")) { - 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 { + 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; - return 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. diff --git a/client.c b/client.c index 0582edb3..219cf2d8 100644 --- a/client.c +++ b/client.c @@ -36,7 +36,6 @@ __printf_2_3 void (*para_log)(int, const char*, ...) = stderr_log; #ifdef HAVE_READLINE #include "interactive.h" -#include "afs.completion.h" #include "server_cmd.lsg.h" struct exec_task { @@ -461,7 +460,6 @@ static int client_i9e_line_handler(char *line) I9E_DUMMY_COMPLETER(SUPERCOMMAND_UNAVAILABLE); static struct i9e_completer completers[] = { - AFS_COMPLETERS #define LSG_SERVER_CMD_CMD(_name) {.name = #_name, \ .completer = _name ## _completer} LSG_SERVER_CMD_COMMANDS diff --git a/command.c b/command.c index d55d1e6e..9cf8b049 100644 --- a/command.c +++ b/command.c @@ -49,7 +49,6 @@ static const char * const server_command_perms_txt[] = {LSG_SERVER_CMD_AUX_INFOS #undef SERVER_CMD_AUX_INFO typedef int server_command_handler_t(struct command_context *); -server_command_handler_t AFS_COMMAND_HANDLERS; /* Defines one command of para_server. */ struct server_command { diff --git a/m4/lls/server_cmd.suite.m4 b/m4/lls/server_cmd.suite.m4 index 29df123f..751998ad 100644 --- a/m4/lls/server_cmd.suite.m4 +++ b/m4/lls/server_cmd.suite.m4 @@ -178,6 +178,115 @@ aux_info_prefix = Permissions: n <= 100. [/description] +[subcommand ls] + purpose = list audio files which match a pattern + non-opts-name = [pattern...] + aux_info = AFS_READ + [description] + If no pattern is given, all files are listed. Otherwise, the command + lists all files of the audio file table whose path matches at least + one of the given patterns. + [/description] + [option listing-mode] + short_opt = l + summary = use alternative output format + arg_type = string + arg_info = optional_arg + typestr = mode + default_val = long + [help] + The optional mode argument is either a single character or a word + according to the following list. + + short (s). List only the path or basename (last component of the path), + depending on whether -p is also given. This listing mode acts as if + --listing-mode had not been given. + + long (l). Show detailed information. This is the default if no argument + to --listing-mode is supplied. + + verbose (v). Multi-line output, one row per data field stored in the + audio file table. + + parser-friendly (p). Like verbose listing mode, but use numerical + values for the names of the output fields and prefix each line with + a length field. + + mbox (m). Generate output suitable to be viewed with a mail + program. One "mail" per matching audio file. + + chunk-table (c). Print path (or basename, depending on whether -p is + also given), chunk time and chunk offsets. + + [/help] + [option full-path] + short_opt = F + summary = list full paths, match full paths against patterns + [help] + This option is the default, so it does nothing. Deprecated as of + v0.6.0, scheduled for removal in v0.6.1. + [/help] + [option basename] + short_opt = b + summary = list and match basenames only + [help] + Print only the basename of each matching file and match only the + basenames of the paths stored in the audio file table against the + given patterns. The default is to print and match the full path. + [/help] + [option admissible] + short_opt = a + summary = list only admissible files + [help] + List only files which are admissible with respect to the current mood + or playlist. + [/help] + [option reverse] + short_opt = r + summary = reverse sort order + [option unix-date] + short_opt = d + summary = print dates as seconds after the epoch + [option sort] + short_opt = s + summary = change sort order + arg_type = string + arg_info = required_arg + typestr = order + default_val = path + [help] + The sort order must be given as an required argument. Like for + --listing-mode, this argument may either be a single character or a + word, according to the following list. + + path (p). Sort alphabetically by path or basename (see -p). This is + the default if --sort is not given. + + score (s). Iterate over the entries of the score table, rather than + the audio file table. This sort order implies --admissible, since + the score table contains only admissible files. + + lastplayed (l) + + numplayed (n) + + frequency (f) + + channels (c) + + image-id (i) + + lyrics-id (y) + + bitrate (b) + + duration (d) + + audio-format (a) + + If --sort is not given, path sort is implied. + [/help] + [subcommand lsatt] purpose = list attributes aux_info = AFS_READ