mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-23 14:44:28 +00:00
Support setting locale per-mailbox (changes date/time languages for email, pager messages).
(closes issue #14333) Reported by: klaus3000 Patches: 20090515__issue14333.diff.txt uploaded by tilghman (license 14) app_voicemail.c-svn-trunk-rev211675-patch.txt uploaded by klaus3000 (license 65) Tested by: klaus3000 git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@266828 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
1
CHANGES
1
CHANGES
@@ -149,6 +149,7 @@ Applications
|
||||
* Added custom device states to ConfBridge bridges. Use 'confbridge:<name>' to
|
||||
retrieve state for a particular bridge, where <name> is the conference name
|
||||
* app_directory now allows exiting at any time using the operator or pound key.
|
||||
* Voicemail now supports setting a locale per-mailbox.
|
||||
|
||||
Dialplan Functions
|
||||
------------------
|
||||
|
@@ -636,6 +636,7 @@ struct ast_vm_user {
|
||||
char mailcmd[160]; /*!< Configurable mail command */
|
||||
char language[MAX_LANGUAGE]; /*!< Config: Language setting */
|
||||
char zonetag[80]; /*!< Time zone */
|
||||
char locale[20]; /*!< The locale (for presentation of date/time) */
|
||||
char callback[80];
|
||||
char dialout[80];
|
||||
char uniqueid[80]; /*!< Unique integer identifier */
|
||||
@@ -773,6 +774,7 @@ static char *sayname_app = "VMSayName";
|
||||
static AST_LIST_HEAD_STATIC(users, ast_vm_user);
|
||||
static AST_LIST_HEAD_STATIC(zones, vm_zone);
|
||||
static char zonetag[80];
|
||||
static char locale[20];
|
||||
static int maxsilence;
|
||||
static int maxmsg;
|
||||
static int maxdeletedmsg;
|
||||
@@ -994,6 +996,7 @@ static void populate_defaults(struct ast_vm_user *vmu)
|
||||
ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
|
||||
ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
|
||||
ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
|
||||
ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
|
||||
if (vmminsecs) {
|
||||
vmu->minsecs = vmminsecs;
|
||||
}
|
||||
@@ -1035,6 +1038,8 @@ static void apply_option(struct ast_vm_user *vmu, const char *var, const char *v
|
||||
ast_copy_string(vmu->language, value, sizeof(vmu->language));
|
||||
} else if (!strcasecmp(var, "tz")) {
|
||||
ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
|
||||
} else if (!strcasecmp(var, "locale")) {
|
||||
ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
|
||||
#ifdef IMAP_STORAGE
|
||||
} else if (!strcasecmp(var, "imapuser")) {
|
||||
ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
|
||||
@@ -4206,7 +4211,7 @@ static void prep_email_sub_vars(struct ast_channel *ast, struct ast_vm_user *vmu
|
||||
struct timeval tv = { inttime, };
|
||||
struct ast_tm tm;
|
||||
ast_localtime(&tv, &tm, NULL);
|
||||
ast_strftime(origdate, sizeof(origdate), emaildateformat, &tm);
|
||||
ast_strftime_locale(origdate, sizeof(origdate), emaildateformat, &tm, S_OR(vmu->locale, NULL));
|
||||
pbx_builtin_setvar_helper(ast, "ORIG_VM_DATE", origdate);
|
||||
}
|
||||
ast_config_destroy(msg_cfg);
|
||||
@@ -4384,11 +4389,11 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
|
||||
}
|
||||
|
||||
snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
|
||||
ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
|
||||
ast_strftime_locale(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm), S_OR(vmu->locale, NULL));
|
||||
fprintf(p, "Date: %s" ENDL, date);
|
||||
|
||||
/* Set date format for voicemail mail */
|
||||
ast_strftime(date, sizeof(date), emaildateformat, &tm);
|
||||
ast_strftime_locale(date, sizeof(date), emaildateformat, &tm, S_OR(vmu->locale, NULL));
|
||||
|
||||
if (!ast_strlen_zero(fromstring)) {
|
||||
struct ast_channel *ast;
|
||||
@@ -4571,7 +4576,7 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
|
||||
struct timeval tv = { inttime, };
|
||||
struct ast_tm tm;
|
||||
ast_localtime(&tv, &tm, NULL);
|
||||
ast_strftime(origdate, sizeof(origdate), emaildateformat, &tm);
|
||||
ast_strftime_locale(origdate, sizeof(origdate), emaildateformat, &tm, S_OR(vmu->locale, NULL));
|
||||
}
|
||||
fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just forwarded"
|
||||
" a %s long message (number %d)" ENDL "in mailbox %s from %s, on %s" ENDL
|
||||
@@ -4733,11 +4738,11 @@ static int sendpage(char *srcemail, char *pager, int msgnum, char *context, char
|
||||
snprintf(who, sizeof(who), "%s@%s", srcemail, host);
|
||||
}
|
||||
snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
|
||||
ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
|
||||
ast_strftime_locale(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm), S_OR(vmu->locale, NULL));
|
||||
fprintf(p, "Date: %s\n", date);
|
||||
|
||||
/* Reformat for custom pager format */
|
||||
ast_strftime(date, sizeof(date), pagerdateformat, vmu_tm(vmu, &tm));
|
||||
ast_strftime_locale(date, sizeof(date), pagerdateformat, vmu_tm(vmu, &tm), S_OR(vmu->locale, NULL));
|
||||
|
||||
if (!ast_strlen_zero(pagerfromstring)) {
|
||||
struct ast_channel *ast;
|
||||
@@ -12005,6 +12010,9 @@ static int load_config(int reload)
|
||||
if ((val = ast_variable_retrieve(cfg, "general", "tz"))) {
|
||||
ast_copy_string(zonetag, val, sizeof(zonetag));
|
||||
}
|
||||
if ((val = ast_variable_retrieve(cfg, "general", "locale"))) {
|
||||
ast_copy_string(locale, val, sizeof(locale));
|
||||
}
|
||||
if ((val = ast_variable_retrieve(cfg, "general", "emailsubject"))) {
|
||||
emailsubject = ast_strdup(val);
|
||||
}
|
||||
|
@@ -93,7 +93,8 @@ maxlogins=3
|
||||
; For the directory, you can override the intro file if you want
|
||||
;directoryintro=dir-intro
|
||||
; The character set for voicemail messages can be specified here
|
||||
;charset=ISO-8859-1
|
||||
; default: ISO-8859-1
|
||||
;charset=UTF-8
|
||||
; The ADSI feature descriptor number to download to
|
||||
;adsifdn=0000000F
|
||||
; The ADSI security lock code
|
||||
@@ -226,6 +227,13 @@ pagerdateformat=%A, %B %d, %Y at %r
|
||||
; overridden in the per-mailbox settings, unless listed otherwise.
|
||||
;
|
||||
; tz=central ; Timezone from zonemessages below. Irrelevant if envelope=no.
|
||||
; locale=de_DE.UTF-8 ; set the locale for generation of the date/time strings (make
|
||||
; sure the locales are installed in your operating system; e.g
|
||||
; on Debian Linux you can use "dpkg-reconfigure locales").
|
||||
; If you use UTF-8 locales, make sure to set the "charset" option
|
||||
; to UTF-8 too. If you mix different locales for different users
|
||||
; you should avoid words in the emaildateformat specification, e.g.:
|
||||
; emaildateformat=%A, %d %B %Y, %H:%M:%S
|
||||
; attach=yes ; Attach the voicemail to the notification email *NOT* the pager email
|
||||
; attachfmt=wav49 ; Which format to attach to the email. Normally this is the
|
||||
; first format specified in the format parameter above, but this
|
||||
|
@@ -442,7 +442,7 @@ AC_FUNC_STRNLEN
|
||||
AC_FUNC_STRTOD
|
||||
AC_FUNC_UTIME_NULL
|
||||
AC_FUNC_VPRINTF
|
||||
AC_CHECK_FUNCS([asprintf atexit closefrom dup2 eaccess endpwent euidaccess ffsll ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday glob htonll ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap ntohll putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtod strtol strtold strtoq unsetenv utime vasprintf getpeereid sysctl swapctl])
|
||||
AC_CHECK_FUNCS([asprintf atexit closefrom dup2 eaccess endpwent euidaccess ffsll ftruncate getcwd gethostbyname gethostname getloadavg gettimeofday glob htonll ioperm inet_ntoa isascii localtime_r memchr memmove memset mkdir munmap ntohll newlocale putenv re_comp regcomp select setenv socket strcasecmp strcasestr strchr strcspn strdup strerror strlcat strlcpy strncasecmp strndup strnlen strrchr strsep strspn strstr strtod strtol strtold strtoq unsetenv utime vasprintf getpeereid sysctl swapctl])
|
||||
|
||||
# NOTE: we use AC_CHECK_LIB to get -lm into the arguments for later checks,
|
||||
# so that AC_CHECK_FUNCS can detect functions in that library.
|
||||
|
@@ -466,6 +466,9 @@
|
||||
/* Define if your system has the NETSNMP libraries. */
|
||||
#undef HAVE_NETSNMP
|
||||
|
||||
/* Define to 1 if you have the `newlocale' function. */
|
||||
#undef HAVE_NEWLOCALE
|
||||
|
||||
/* Define to 1 if you have the newt library. */
|
||||
#undef HAVE_NEWT
|
||||
|
||||
@@ -770,7 +773,7 @@
|
||||
/* Define to 1 if you have the `strtoq' function. */
|
||||
#undef HAVE_STRTOQ
|
||||
|
||||
/* Define to 1 if `st_blksize' is a member of `struct stat'. */
|
||||
/* Define to 1 if `st_blksize' is member of `struct stat'. */
|
||||
#undef HAVE_STRUCT_STAT_ST_BLKSIZE
|
||||
|
||||
/* Define to 1 if you have the mISDN Supplemental Services library. */
|
||||
@@ -1038,12 +1041,12 @@
|
||||
/* Define to the one symbol short name of this package. */
|
||||
#undef PACKAGE_TARNAME
|
||||
|
||||
/* Define to the home page for this package. */
|
||||
#undef PACKAGE_URL
|
||||
|
||||
/* Define to the version of this package. */
|
||||
#undef PACKAGE_VERSION
|
||||
|
||||
/* Define to 1 if the C compiler supports function prototypes. */
|
||||
#undef PROTOTYPES
|
||||
|
||||
/* Define to necessary symbol if this constant uses a non-standard name on
|
||||
your system. */
|
||||
#undef PTHREAD_CREATE_JOINABLE
|
||||
@@ -1063,6 +1066,11 @@
|
||||
/* Define to the type of arg 5 for `select'. */
|
||||
#undef SELECT_TYPE_ARG5
|
||||
|
||||
/* Define to 1 if the `setvbuf' function takes the buffering type as its
|
||||
second argument and the buffer pointer as the third, as on System V before
|
||||
release 3. */
|
||||
#undef SETVBUF_REVERSED
|
||||
|
||||
/* The size of `char *', as computed by sizeof. */
|
||||
#undef SIZEOF_CHAR_P
|
||||
|
||||
@@ -1092,30 +1100,20 @@
|
||||
/* Define to 1 if your <sys/time.h> declares `struct tm'. */
|
||||
#undef TM_IN_SYS_TIME
|
||||
|
||||
/* Enable extensions on AIX 3, Interix. */
|
||||
/* Define to 1 if on AIX 3.
|
||||
System headers sometimes define this.
|
||||
We just want to avoid a redefinition error message. */
|
||||
#ifndef _ALL_SOURCE
|
||||
# undef _ALL_SOURCE
|
||||
#endif
|
||||
|
||||
/* Number of bits in a file offset, on hosts where this is settable. */
|
||||
#undef _FILE_OFFSET_BITS
|
||||
|
||||
/* Enable GNU extensions on systems that have them. */
|
||||
#ifndef _GNU_SOURCE
|
||||
# undef _GNU_SOURCE
|
||||
#endif
|
||||
/* Enable threading extensions on Solaris. */
|
||||
#ifndef _POSIX_PTHREAD_SEMANTICS
|
||||
# undef _POSIX_PTHREAD_SEMANTICS
|
||||
#endif
|
||||
/* Enable extensions on HP NonStop. */
|
||||
#ifndef _TANDEM_SOURCE
|
||||
# undef _TANDEM_SOURCE
|
||||
#endif
|
||||
/* Enable general extensions on Solaris. */
|
||||
#ifndef __EXTENSIONS__
|
||||
# undef __EXTENSIONS__
|
||||
#endif
|
||||
|
||||
|
||||
/* Number of bits in a file offset, on hosts where this is settable. */
|
||||
#undef _FILE_OFFSET_BITS
|
||||
|
||||
/* Define to 1 to make fseeko visible on some hosts (e.g. glibc 2.2). */
|
||||
#undef _LARGEFILE_SOURCE
|
||||
@@ -1133,6 +1131,20 @@
|
||||
/* Define to 1 if you need to in order for `stat' and other things to work. */
|
||||
#undef _POSIX_SOURCE
|
||||
|
||||
/* Enable extensions on Solaris. */
|
||||
#ifndef __EXTENSIONS__
|
||||
# undef __EXTENSIONS__
|
||||
#endif
|
||||
#ifndef _POSIX_PTHREAD_SEMANTICS
|
||||
# undef _POSIX_PTHREAD_SEMANTICS
|
||||
#endif
|
||||
#ifndef _TANDEM_SOURCE
|
||||
# undef _TANDEM_SOURCE
|
||||
#endif
|
||||
|
||||
/* Define like PROTOTYPES; this can be used by system headers. */
|
||||
#undef __PROTOTYPES
|
||||
|
||||
/* Define to empty if `const' does not conform to ANSI C. */
|
||||
#undef const
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 1999 - 2005, Digium, Inc.
|
||||
* Copyright (C) 1999 - 2010, Digium, Inc.
|
||||
*
|
||||
* Mark Spencer <markster@digium.com>
|
||||
* Tilghman Lesher <tlesher@vcch.com>
|
||||
* Tilghman Lesher <tlesher AT digium DOT com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
@@ -24,6 +24,12 @@
|
||||
#ifndef _ASTERISK_LOCALTIME_H
|
||||
#define _ASTERISK_LOCALTIME_H
|
||||
|
||||
#ifdef HAVE_NEWLOCALE
|
||||
#include <locale.h>
|
||||
#else
|
||||
typedef void * locale_t;
|
||||
#endif
|
||||
|
||||
struct ast_tm {
|
||||
int tm_sec; /*!< Seconds. [0-60] (1 leap second) */
|
||||
int tm_min; /*!< Minutes. [0-59] */
|
||||
@@ -57,6 +63,9 @@ void ast_get_dst_info(const time_t * const timep, int *dst_enabled, time_t *dst_
|
||||
*/
|
||||
struct timeval ast_mktime(struct ast_tm * const tmp, const char *zone);
|
||||
|
||||
/*!\brief Set the thread-local representation of the current locale. */
|
||||
const char *ast_setlocale(const char *locale);
|
||||
|
||||
/*!\brief Special version of strftime(3) that handles fractions of a second.
|
||||
* Takes the same arguments as strftime(3), with the addition of %q, which
|
||||
* specifies microseconds.
|
||||
@@ -64,9 +73,11 @@ struct timeval ast_mktime(struct ast_tm * const tmp, const char *zone);
|
||||
* \param len Size of the chunk of memory buf.
|
||||
* \param format A string specifying the format of time to be placed into buf.
|
||||
* \param tm Pointer to the broken out time to be used for the format.
|
||||
* \param locale Text string specifying the locale to be used for language strings.
|
||||
* \retval An integer value specifying the number of bytes placed into buf or -1 on error.
|
||||
*/
|
||||
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm);
|
||||
int ast_strftime_locale(char *buf, size_t len, const char *format, const struct ast_tm *tm, const char *locale);
|
||||
|
||||
/*!\brief Special version of strptime(3) which places the answer in the common
|
||||
* structure ast_tm. Also, unlike strptime(3), ast_strptime() initializes its
|
||||
@@ -74,9 +85,11 @@ int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm
|
||||
* \param s A string specifying some portion of a date and time.
|
||||
* \param format The format in which the string, s, is expected.
|
||||
* \param tm The broken-out time structure into which the parsed data is expected.
|
||||
* \param locale Text string specifying the locale to be used for language strings.
|
||||
* \retval A pointer to the first character within s not used to parse the date and time.
|
||||
*/
|
||||
char *ast_strptime(const char *s, const char *format, struct ast_tm *tm);
|
||||
char *ast_strptime_locale(const char *s, const char *format, struct ast_tm *tm, const char *locale);
|
||||
|
||||
/*!\brief Wakeup localtime monitor thread
|
||||
* For use in testing. Normally, the failsafe monitor thread waits 60 seconds
|
||||
|
@@ -173,6 +173,12 @@ struct state {
|
||||
AST_LIST_ENTRY(state) list;
|
||||
};
|
||||
|
||||
struct locale_entry {
|
||||
AST_LIST_ENTRY(locale_entry) list;
|
||||
locale_t locale;
|
||||
char name[0];
|
||||
};
|
||||
|
||||
struct rule {
|
||||
int r_type; /* type of rule--see below */
|
||||
int r_day; /* day number of rule */
|
||||
@@ -235,6 +241,7 @@ static int tzparse P((const char * name, struct state * sp,
|
||||
int lastditch));
|
||||
|
||||
static AST_LIST_HEAD_STATIC(zonelist, state);
|
||||
static AST_LIST_HEAD_STATIC(localelist, locale_entry);
|
||||
|
||||
#ifndef TZ_STRLEN_MAX
|
||||
#define TZ_STRLEN_MAX 255
|
||||
@@ -2128,15 +2135,104 @@ struct timeval ast_mktime(struct ast_tm *tmp, const char *zone)
|
||||
return time1(tmp, localsub, 0L, sp);
|
||||
}
|
||||
|
||||
int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm)
|
||||
#ifdef HAVE_NEWLOCALE
|
||||
static struct locale_entry *find_by_locale(locale_t locale)
|
||||
{
|
||||
struct locale_entry *cur;
|
||||
AST_LIST_TRAVERSE(&localelist, cur, list) {
|
||||
if (locale == cur->locale) {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct locale_entry *find_by_name(const char *name)
|
||||
{
|
||||
struct locale_entry *cur;
|
||||
AST_LIST_TRAVERSE(&localelist, cur, list) {
|
||||
if (strcmp(name, cur->name) == 0) {
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *store_by_locale(locale_t prevlocale)
|
||||
{
|
||||
struct locale_entry *cur;
|
||||
if (prevlocale == LC_GLOBAL_LOCALE) {
|
||||
return NULL;
|
||||
} else {
|
||||
/* Get a handle for this entry, if any */
|
||||
if ((cur = find_by_locale(prevlocale))) {
|
||||
return cur->name;
|
||||
} else {
|
||||
/* Create an entry, so it can be restored later */
|
||||
int x;
|
||||
cur = NULL;
|
||||
AST_LIST_LOCK(&localelist);
|
||||
for (x = 0; x < 10000; x++) {
|
||||
char name[5];
|
||||
snprintf(name, sizeof(name), "%04d", x);
|
||||
if (!find_by_name(name)) {
|
||||
if ((cur = ast_calloc(1, sizeof(*cur) + strlen(name) + 1))) {
|
||||
cur->locale = prevlocale;
|
||||
strcpy(cur->name, name); /* SAFE */
|
||||
AST_LIST_INSERT_TAIL(&localelist, cur, list);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
AST_LIST_UNLOCK(&localelist);
|
||||
return cur ? cur->name : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *ast_setlocale(const char *locale)
|
||||
{
|
||||
struct locale_entry *cur;
|
||||
locale_t prevlocale = LC_GLOBAL_LOCALE;
|
||||
|
||||
if (locale == NULL) {
|
||||
return store_by_locale(uselocale(LC_GLOBAL_LOCALE));
|
||||
}
|
||||
|
||||
AST_LIST_LOCK(&localelist);
|
||||
if ((cur = find_by_name(locale))) {
|
||||
prevlocale = uselocale(cur->locale);
|
||||
}
|
||||
|
||||
if (!cur) {
|
||||
if ((cur = ast_calloc(1, sizeof(*cur) + strlen(locale) + 1))) {
|
||||
cur->locale = newlocale(LC_ALL_MASK, locale, NULL);
|
||||
strcpy(cur->name, locale); /* SAFE */
|
||||
AST_LIST_INSERT_TAIL(&localelist, cur, list);
|
||||
prevlocale = uselocale(cur->locale);
|
||||
}
|
||||
}
|
||||
AST_LIST_UNLOCK(&localelist);
|
||||
return store_by_locale(prevlocale);
|
||||
}
|
||||
#else
|
||||
const char *ast_setlocale(const char *unused)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
int ast_strftime_locale(char *buf, size_t len, const char *tmp, const struct ast_tm *tm, const char *locale)
|
||||
{
|
||||
size_t fmtlen = strlen(tmp) + 1;
|
||||
char *format = ast_calloc(1, fmtlen), *fptr = format, *newfmt;
|
||||
int decimals = -1, i, res;
|
||||
long fraction;
|
||||
const char *prevlocale;
|
||||
|
||||
if (!format)
|
||||
if (!format) {
|
||||
return -1;
|
||||
}
|
||||
for (; *tmp; tmp++) {
|
||||
if (*tmp == '%') {
|
||||
switch (tmp[1]) {
|
||||
@@ -2146,14 +2242,16 @@ int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
if (tmp[2] != 'q')
|
||||
if (tmp[2] != 'q') {
|
||||
goto defcase;
|
||||
}
|
||||
decimals = tmp[1] - '0';
|
||||
tmp++;
|
||||
/* Fall through */
|
||||
case 'q': /* Milliseconds */
|
||||
if (decimals == -1)
|
||||
if (decimals == -1) {
|
||||
decimals = 3;
|
||||
}
|
||||
|
||||
/* Juggle some memory to fit the item */
|
||||
newfmt = ast_realloc(format, fmtlen + decimals);
|
||||
@@ -2166,8 +2264,9 @@ int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm
|
||||
fmtlen += decimals;
|
||||
|
||||
/* Reduce the fraction of time to the accuracy needed */
|
||||
for (i = 6, fraction = tm->tm_usec; i > decimals; i--)
|
||||
for (i = 6, fraction = tm->tm_usec; i > decimals; i--) {
|
||||
fraction /= 10;
|
||||
}
|
||||
fptr += sprintf(fptr, "%0*ld", decimals, fraction);
|
||||
|
||||
/* Reset, in case more than one 'q' specifier exists */
|
||||
@@ -2177,20 +2276,37 @@ int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm
|
||||
default:
|
||||
goto defcase;
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
defcase: *fptr++ = *tmp;
|
||||
}
|
||||
}
|
||||
*fptr = '\0';
|
||||
#undef strftime
|
||||
if (locale) {
|
||||
prevlocale = ast_setlocale(locale);
|
||||
}
|
||||
res = (int)strftime(buf, len, format, (struct tm *)tm);
|
||||
if (locale) {
|
||||
ast_setlocale(prevlocale);
|
||||
}
|
||||
ast_free(format);
|
||||
return res;
|
||||
}
|
||||
|
||||
char *ast_strptime(const char *s, const char *format, struct ast_tm *tm)
|
||||
int ast_strftime(char *buf, size_t len, const char *tmp, const struct ast_tm *tm)
|
||||
{
|
||||
return ast_strftime_locale(buf, len, tmp, tm, NULL);
|
||||
}
|
||||
|
||||
char *ast_strptime_locale(const char *s, const char *format, struct ast_tm *tm, const char *locale)
|
||||
{
|
||||
struct tm tm2 = { 0, };
|
||||
char *res = strptime(s, format, &tm2);
|
||||
char *res;
|
||||
const char *prevlocale;
|
||||
|
||||
prevlocale = ast_setlocale(locale);
|
||||
res = strptime(s, format, &tm2);
|
||||
ast_setlocale(prevlocale);
|
||||
memcpy(tm, &tm2, sizeof(*tm));
|
||||
tm->tm_usec = 0;
|
||||
/* strptime(3) doesn't set .tm_isdst correctly, so to force ast_mktime(3)
|
||||
@@ -2199,3 +2315,8 @@ char *ast_strptime(const char *s, const char *format, struct ast_tm *tm)
|
||||
return res;
|
||||
}
|
||||
|
||||
char *ast_strptime(const char *s, const char *format, struct ast_tm *tm)
|
||||
{
|
||||
return ast_strptime_locale(s, format, tm, NULL);
|
||||
}
|
||||
|
||||
|
183
tests/test_locale.c
Normal file
183
tests/test_locale.c
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Asterisk -- An open source telephony toolkit.
|
||||
*
|
||||
* Copyright (C) 2009, Digium, Inc.
|
||||
*
|
||||
* Tilghman Lesher <tlesher AT digium DOT com>
|
||||
*
|
||||
* See http://www.asterisk.org for more information about
|
||||
* the Asterisk project. Please do not directly contact
|
||||
* any of the maintainers of this project for assistance;
|
||||
* the project provides a web site, mailing lists and IRC
|
||||
* channels for your use.
|
||||
*
|
||||
* This program is free software, distributed under the terms of
|
||||
* the GNU General Public License Version 2. See the LICENSE file
|
||||
* at the top of the source tree.
|
||||
*/
|
||||
|
||||
/*! \file
|
||||
*
|
||||
* \brief Locale Test
|
||||
*
|
||||
* \author\verbatim Tilghman Lesher <tlesher AT digium DOT com> \endverbatim
|
||||
*
|
||||
* \ingroup tests
|
||||
*/
|
||||
|
||||
/*** MODULEINFO
|
||||
<defaultenabled>no</defaultenabled>
|
||||
***/
|
||||
|
||||
#include "asterisk.h"
|
||||
|
||||
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#ifndef __USE_GNU
|
||||
#define __USE_GNU 1
|
||||
#endif
|
||||
#include <locale.h>
|
||||
|
||||
#include "asterisk/cli.h"
|
||||
#include "asterisk/linkedlists.h"
|
||||
#include "asterisk/localtime.h"
|
||||
#include "asterisk/utils.h"
|
||||
#include "asterisk/module.h"
|
||||
|
||||
|
||||
static char *handle_cli_test_locales(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||
{
|
||||
DIR *localedir;
|
||||
struct dirent *dent;
|
||||
struct ast_tm atm;
|
||||
struct timeval tv;
|
||||
const char *orig_locale;
|
||||
char origlocalformat[200] = "", localformat[200] = "";
|
||||
struct test_locales {
|
||||
AST_LIST_ENTRY(test_locales) list;
|
||||
char *localformat;
|
||||
char name[0];
|
||||
} *tl = NULL;
|
||||
AST_LIST_HEAD_NOLOCK(locales, test_locales) locales;
|
||||
int varies = 0, all_successful = 1, count = 0, count_fail = 0;
|
||||
|
||||
switch (cmd) {
|
||||
case CLI_INIT:
|
||||
e->command = "test locale";
|
||||
e->usage = ""
|
||||
"Usage: test locale\n"
|
||||
" Test thread safety of locale functions.\n";
|
||||
return NULL;
|
||||
case CLI_GENERATE:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (a->argc != e->args) {
|
||||
return CLI_SHOWUSAGE;
|
||||
}
|
||||
|
||||
/* First we run a set of tests with the global locale, which isn't thread-safe. */
|
||||
if (!(localedir = opendir(
|
||||
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined( __NetBSD__ ) || defined(__APPLE__)
|
||||
"/usr/share/locale"
|
||||
#else /* Linux */
|
||||
"/usr/lib/locale"
|
||||
#endif
|
||||
))) {
|
||||
ast_cli(a->fd, "No locales seem to exist on this platform.\n");
|
||||
return CLI_SUCCESS;
|
||||
}
|
||||
|
||||
tv = ast_tvnow();
|
||||
ast_localtime(&tv, &atm, NULL);
|
||||
orig_locale = setlocale(LC_ALL, NULL);
|
||||
AST_LIST_HEAD_SET_NOLOCK(&locales, NULL);
|
||||
|
||||
/* Get something different, to compare against. */
|
||||
ast_strftime(origlocalformat, sizeof(origlocalformat), "%c", &atm);
|
||||
|
||||
while ((dent = readdir(localedir))) {
|
||||
size_t namelen;
|
||||
|
||||
if (dent->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, dent->d_name);
|
||||
ast_strftime(localformat, sizeof(localformat), "%c", &atm);
|
||||
|
||||
/* Store values */
|
||||
if (!(tl = ast_calloc(1, sizeof(*tl) + strlen(localformat) + (namelen = strlen(dent->d_name)) + 2))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
strcpy(tl->name, dent->d_name); /* SAFE */
|
||||
tl->localformat = tl->name + namelen + 1;
|
||||
strcpy(tl->localformat, localformat); /* SAFE */
|
||||
|
||||
AST_LIST_INSERT_TAIL(&locales, tl, list);
|
||||
|
||||
/* Ensure that at least two entries differ, otherwise this test doesn't mean much. */
|
||||
if (!varies && strcmp(AST_LIST_FIRST(&locales)->localformat, localformat)) {
|
||||
varies = 1;
|
||||
}
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, orig_locale);
|
||||
|
||||
closedir(localedir);
|
||||
|
||||
if (!varies) {
|
||||
if (!strcmp(origlocalformat, localformat)) {
|
||||
ast_cli(a->fd, "WARNING: the locales on your system don't differ. Install more locales if you want this test to mean something.\n");
|
||||
}
|
||||
}
|
||||
|
||||
orig_locale = ast_setlocale(AST_LIST_FIRST(&locales)->name);
|
||||
|
||||
while ((tl = AST_LIST_REMOVE_HEAD(&locales, list))) {
|
||||
ast_setlocale(tl->name);
|
||||
ast_strftime(localformat, sizeof(localformat), "%c", &atm);
|
||||
if (strcmp(localformat, tl->localformat)) {
|
||||
ast_cli(a->fd, "WARNING: locale test fails for locale %s\n", tl->name);
|
||||
all_successful = 0;
|
||||
count_fail++;
|
||||
}
|
||||
ast_free(tl);
|
||||
count++;
|
||||
}
|
||||
|
||||
ast_setlocale(orig_locale);
|
||||
|
||||
if (all_successful) {
|
||||
ast_cli(a->fd, "All %d locale tests successful\n", count);
|
||||
} else if (count_fail == count && count > 0) {
|
||||
ast_cli(a->fd, "No locale tests successful out of %d tries\n", count);
|
||||
} else if (count > 0) {
|
||||
ast_cli(a->fd, "Partial failure (%d/%d) for a %.0f%% failure rate\n", count_fail, count, count_fail * 100.0 / count);
|
||||
} else {
|
||||
ast_cli(a->fd, "No locales tested. Install more locales.\n");
|
||||
}
|
||||
|
||||
return CLI_SUCCESS;
|
||||
}
|
||||
|
||||
static struct ast_cli_entry cli_locales[] = {
|
||||
AST_CLI_DEFINE(handle_cli_test_locales, "Test locales for thread-safety"),
|
||||
};
|
||||
|
||||
static int unload_module(void)
|
||||
{
|
||||
ast_cli_unregister_multiple(cli_locales, ARRAY_LEN(cli_locales));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_module(void)
|
||||
{
|
||||
ast_cli_register_multiple(cli_locales, ARRAY_LEN(cli_locales));
|
||||
return AST_MODULE_LOAD_SUCCESS;
|
||||
}
|
||||
|
||||
AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Locale tests");
|
Reference in New Issue
Block a user