From 1a4b98e797ab5ebb1ee1daa707aa0d520379ccc9 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Thu, 27 Sep 2007 17:47:46 +0200 Subject: [PATCH] Implement pattern matching for com_touch(). Also, add -p and -v options and fix converting the last played time which is a 64 bit intger, so strtol() will fail do it on 32 bit machines after 2039 ;) To fix this, introduce para_atoi64() which calls strtoll() which is 64 bit even on 32 bit machines and define para_atoi32() which calls para_atoi64() and checks the boundaries. --- afs.c | 34 ----------- afs.cmd | 9 ++- afs.h | 1 - aft.c | 171 ++++++++++++++++++++++++++++++++++++------------------- error.h | 5 +- mood.c | 4 +- string.c | 61 ++++++++++++++++++++ string.h | 3 +- 8 files changed, 189 insertions(+), 99 deletions(-) diff --git a/afs.c b/afs.c index bef35425..f40e0224 100644 --- a/afs.c +++ b/afs.c @@ -337,40 +337,6 @@ int string_compare(const struct osl_object *obj1, const struct osl_object *obj2) return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size)); } -/** - * A wrapper for strtol(3). - * - * \param str The string to be converted to a long integer. - * \param result The converted value is stored here. - * - * \return Positive on success, -E_ATOL on errors. - * - * \sa strtol(3), atoi(3). - */ -int para_atol(const char *str, long *result) -{ - char *endptr; - long val; - int ret, base = 10; - - errno = 0; /* To distinguish success/failure after call */ - val = strtol(str, &endptr, base); - ret = -E_ATOL; - if (errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) - goto out; /* overflow */ - if (errno != 0 && val == 0) - goto out; /* other error */ - if (endptr == str) - goto out; /* No digits were found */ - if (*endptr != '\0') - goto out; /* Further characters after number */ - *result = val; - ret = 1; -out: - return ret; -} - - /* * write input from fd to dynamically allocated buffer, * but maximal max_size byte. diff --git a/afs.cmd b/afs.cmd index 200836e2..f27fa4a8 100644 --- a/afs.cmd +++ b/afs.cmd @@ -163,7 +163,7 @@ H: a slash (see fnmatch(3)). N: touch P: AFS_READ | AFS_WRITE D: Manupulate the afs data for all audio files matching a pattern. -U: touch [-n numplayed] [-l lastplayed] [-y lyrics_id] [-i image_id] pattern +U: touch [-n numplayed] [-l lastplayed] [-y lyrics_id] [-i image_id] [-v] [-p] pattern H: If no option is given, lastplayed is set to the current time and numplayed is H: increased by one. Otherwise, only the given options are taken into account. H: @@ -183,6 +183,13 @@ H: -y Set the lyrics id. Specify the lyrics data file associated with H: this audio file. H: H: -i Set the image id. Same as -y, but sets the image. +H: +H: -v Verbose mode. Explain what is being done. +H: +H: -p Pathname match. Match a slash in the path only with a slash +H: in pattern and not by an asterisk (*) or a question mark +H: (?) metacharacter, nor by a bracket expression ([]) containing +H: a slash (see fnmatch(3)). --- T: add N: add@member@ diff --git a/afs.h b/afs.h index f4f02220..9628d5c7 100644 --- a/afs.h +++ b/afs.h @@ -72,7 +72,6 @@ int send_option_arg_callback_request(struct osl_object *options, int stdin_command(int fd, struct osl_object *arg_obj, callback_function *f, unsigned max_len, struct osl_object *result); int string_compare(const struct osl_object *obj1, const struct osl_object *obj2); -int para_atol(const char *str, long *result); int open_next_audio_file(struct audio_file_data *afd); int close_audio_file(struct audio_file_data *afd); int for_each_matching_row(struct pattern_match_data *pmd); diff --git a/aft.c b/aft.c index 362ce1cc..bcceb4b0 100644 --- a/aft.c +++ b/aft.c @@ -1642,60 +1642,99 @@ int com_add(int fd, int argc, char * const * const argv) } +enum touch_flags { + TOUCH_FLAG_FNM_PATHNAME = 1, + TOUCH_FLAG_VERBOSE = 2 +}; + struct com_touch_options { - long num_played; - long last_played; - long lyrics_id; - long image_id; + int32_t num_played; + int64_t last_played; + int32_t lyrics_id; + int32_t image_id; + unsigned flags; }; -static int com_touch_callback(const struct osl_object *query, - __a_unused struct osl_object *result) +struct touch_action_data { + struct com_touch_options *cto; + 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 com_touch_options *cto = query->data; - char *p = (char *)query->data + sizeof(*cto); - size_t len; - int ret, no_options = cto->num_played < 0 && cto->last_played < 0 && - cto->lyrics_id < 0 && cto->image_id < 0; - - for (;p < (char *)query->data + query->size; p += len + 1) { - struct afs_info old_afsi, new_afsi; - struct osl_object obj; - struct osl_row *row; + struct touch_action_data *tad = data; + 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; - len = strlen(p); - ret = aft_get_row_of_path(p, &row); - if (ret < 0) - return ret; - ret = get_afsi_object_of_row(row, &obj); - if (ret < 0) - return ret; - ret = load_afsi(&old_afsi, &obj); - if (ret < 0) - return ret; - new_afsi = old_afsi; - if (no_options) { - new_afsi.num_played++; - new_afsi.last_played = time(NULL); - } else { - if (cto->lyrics_id >= 0) - new_afsi.lyrics_id = cto->lyrics_id; - if (cto->image_id >= 0) - new_afsi.image_id = cto->image_id; - if (cto->num_played >= 0) - new_afsi.num_played = cto->num_played; - if (cto->last_played >= 0) - new_afsi.last_played = cto->last_played; - } - save_afsi(&new_afsi, &obj); /* in-place update */ - ret = mood_update_audio_file(row, &old_afsi); - if (ret < 0) - return ret; + ret = get_afsi_object_of_row(row, &obj); + if (ret < 0) { + para_printf(&tad->pb, "%s: %s\n", name, PARA_STRERROR(-ret)); + return 1; + } + ret = load_afsi(&old_afsi, &obj); + if (ret < 0) { + para_printf(&tad->pb, "%s: %s\n", name, PARA_STRERROR(-ret)); + return 1; + } + new_afsi = old_afsi; + if (no_options) { + new_afsi.num_played++; + new_afsi.last_played = time(NULL); + if (tad->cto->flags & TOUCH_FLAG_VERBOSE) + para_printf(&tad->pb, "%s: num_played = %u, " + "last_played = now()\n", name, + new_afsi.num_played); + } else { + if (tad->cto->flags & TOUCH_FLAG_VERBOSE) + para_printf(&tad->pb, "touching %s\n", name); + 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; } + save_afsi(&new_afsi, &obj); /* in-place update */ + ret = mood_update_audio_file(row, &old_afsi); + if (ret < 0) + para_printf(&tad->pb, "%s: %s\n", name, PARA_STRERROR(-ret)); return 1; } -int com_touch(__a_unused int fd, int argc, char * const * const argv) +static int com_touch_callback(const struct osl_object *query, + struct osl_object *result) +{ + struct touch_action_data tad = {.cto = query->data}; + 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, + .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) + para_printf(&tad.pb, "%s\n", PARA_STRERROR(-ret)); + if (tad.pb.buf) { + result->data = tad.pb.buf; + result->size = tad.pb.size; + return 1; + } + return ret < 0? ret : 0; +} + + +int com_touch(int fd, int argc, char * const * const argv) { struct com_touch_options cto = { .num_played = -1, @@ -1703,7 +1742,8 @@ int com_touch(__a_unused int fd, int argc, char * const * const argv) .lyrics_id = -1, .image_id = -1 }; - struct osl_object options = {.data = &cto, .size = sizeof(cto)}; + struct osl_object query = {.data = &cto, .size = sizeof(cto)}, + result; int i, ret; @@ -1716,36 +1756,48 @@ int com_touch(__a_unused int fd, int argc, char * const * const argv) break; } if (!strncmp(arg, "-n", 2)) { - ret = para_atol(arg + 2, &cto.num_played); + ret = para_atoi32(arg + 2, &cto.num_played); if (ret < 0) - goto err; + return ret; continue; } if (!strncmp(arg, "-l", 2)) { - ret = para_atol(arg + 2, &cto.last_played); + ret = para_atoi64(arg + 2, &cto.last_played); if (ret < 0) - goto err; + return ret; continue; } if (!strncmp(arg, "-y", 2)) { - ret = para_atol(arg + 2, &cto.lyrics_id); + ret = para_atoi32(arg + 2, &cto.lyrics_id); if (ret < 0) - goto err; + return ret; continue; } if (!strncmp(arg, "-i", 2)) { - ret = para_atol(arg + 2, &cto.image_id); + ret = para_atoi32(arg + 2, &cto.image_id); if (ret < 0) - goto err; + return ret; + continue; + } + if (!strcmp(arg, "-p")) { + cto.flags |= TOUCH_FLAG_FNM_PATHNAME; + continue; + } + if (!strcmp(arg, "-v")) { + cto.flags |= TOUCH_FLAG_VERBOSE; continue; } + break; /* non-option starting with dash */ } - ret = -E_AFT_SYNTAX; if (i >= argc) - goto err; - return send_option_arg_callback_request(&options, argc - i, - argv + i, com_touch_callback, NULL); -err: + return -E_AFT_SYNTAX; + ret = send_option_arg_callback_request(&query, argc - i, + argv + i, com_touch_callback, &result); + if (ret > 0) { + send_buffer(fd, (char *)result.data); + free(result.data); + } else + send_va_buffer(fd, "%s\n", PARA_STRERROR(-ret)); return ret; } @@ -1841,6 +1893,7 @@ int com_afs_rm(int fd, int argc, char * const * const argv) flags |= RM_FLAG_VERBOSE; continue; } + break; } if (i >= argc) return -E_AFT_SYNTAX; diff --git a/error.h b/error.h index ca97dba6..b7b66c69 100644 --- a/error.h +++ b/error.h @@ -153,7 +153,6 @@ extern const char **para_errlist[]; PARA_ERROR(BAD_TABLE_NAME, "invalid table"), \ PARA_ERROR(INPUT_TOO_LARGE, "input too large for stdin command"), \ PARA_ERROR(READ, "read error"), \ - PARA_ERROR(ATOL, "failed to convert to long"), \ PARA_ERROR(AFS_SYNTAX, "afs syntax error"), \ @@ -349,6 +348,10 @@ extern const char **para_errlist[]; #define STRING_ERRORS \ PARA_ERROR(MKSTEMP, "mkstemp error: unable to create tmp file"), \ PARA_ERROR(FCHMOD, "fchmod error: can not set mode"), \ + PARA_ERROR(ATOI_OVERFLOW, "value too large"), \ + PARA_ERROR(STRTOLL, "unknown strtoll error"), \ + PARA_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \ + PARA_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \ #define EXEC_ERRORS \ diff --git a/mood.c b/mood.c index 83c7fa65..54867e58 100644 --- a/mood.c +++ b/mood.c @@ -101,7 +101,7 @@ struct mood_item { /** The data structure computed by the mood parser. */ void *parser_data; /** The given score value, or zero if none was given. */ - long score_arg; + int32_t score_arg; /** Non-zero if random scoring was requested. */ int random_score; /** Whether the "not" keyword was given in the mood line. */ @@ -418,7 +418,7 @@ static int parse_mood_line(char *mood_line, void *data) goto out; if (strcmp(*w, "random")) { mi->random_score = 0; - ret = para_atol(*w, &mi->score_arg); + ret = para_atoi32(*w, &mi->score_arg); if (ret < 0) goto out; } else { diff --git a/string.c b/string.c index 60050ad7..78d54f67 100644 --- a/string.c +++ b/string.c @@ -548,3 +548,64 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) } return ret; } + +#ifndef LLONG_MAX +#define LLONG_MAX (1 << (sizeof(long) - 1)) +#endif +#ifndef LLONG_MIN +#define LLONG_MIN (-LLONG_MAX - 1LL) +#endif + +/** + * Convert a string to a 64-bit signed integer value. + * + * \param str The string to be converted. + * \param value Result pointer. + * + * \return Positive on success, negative on errors. + * + * \sa para_atoi32(), strtol(3), atoi(3). + */ +int para_atoi64(const char *str, int64_t *value) +{ + char *endptr; + long long tmp; + + errno = 0; /* To distinguish success/failure after call */ + tmp = strtoll(str, &endptr, 10); + if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN)) + return -E_ATOI_OVERFLOW; + if (errno != 0 && tmp == 0) /* other error */ + return -E_STRTOLL; + if (endptr == str) + return -E_ATOI_NO_DIGITS; + if (*endptr != '\0') /* Further characters after number */ + return -E_ATOI_JUNK_AT_END; + *value = tmp; + return 1; +} + +/** + * Convert a string to a 32-bit signed integer value. + * + * \param str The string to be converted. + * \param value Result pointer. + * + * \return Positive on success, negative on errors. + * + * \sa para_atoi64(). +*/ +int para_atoi32(const char *str, int32_t *value) +{ + int64_t tmp; + int ret; + const int32_t max = 2147483647; + + ret = para_atoi64(str, &tmp); + if (ret < 0) + return ret; + if (tmp > max || tmp < -max - 1) + return -E_ATOI_OVERFLOW; + *value = tmp; + return 1; +} diff --git a/string.h b/string.h index a2dfa422..16f46108 100644 --- a/string.h +++ b/string.h @@ -34,4 +34,5 @@ int for_each_line(char *buf, size_t size, line_handler_t *line_handler, void *private_data); int for_each_line_ro(char *buf, size_t size, line_handler_t line_handler, void *private_data); - +int para_atoi64(const char *str, int64_t *result); +int para_atoi32(const char *str, int32_t *value); -- 2.39.2