Index: apps/app_voicemail.c =================================================================== --- apps/app_voicemail.c (revision 312537) +++ apps/app_voicemail.c (working copy) @@ -2559,7 +2559,8 @@ struct odbc_obj *obj; obj = ast_odbc_request_obj(odbc_database, 0); if (obj) { - snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table); + snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc limit 1", odbc_table); + stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps); if (!stmt) { ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); @@ -2568,7 +2569,12 @@ } res = SQLFetch(stmt); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { - ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); + if (res == SQL_NO_DATA) { + ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir); + } else { + ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); + } + SQLFreeHandle (SQL_HANDLE_STMT, stmt); ast_odbc_release_obj(obj); goto yuck; @@ -2581,12 +2587,13 @@ goto yuck; } if (sscanf(rowdata, "%30d", &x) != 1) - ast_log(LOG_WARNING, "Failed to read message count!\n"); + ast_log(LOG_WARNING, "Failed to read message index!\n"); SQLFreeHandle (SQL_HANDLE_STMT, stmt); ast_odbc_release_obj(obj); + return x; } else ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database); -yuck: +yuck: return x - 1; } @@ -2632,13 +2639,54 @@ ast_odbc_release_obj(obj); } else ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database); -yuck: +yuck: return x; } static int count_messages(struct ast_vm_user *vmu, char *dir) { - return last_message_index(vmu, dir) + 1; + int x = 0; + int res; + SQLHSTMT stmt; + char sql[PATH_MAX]; + char rowdata[20]; + char *argv[] = { dir }; + struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv }; + + struct odbc_obj *obj; + obj = ast_odbc_request_obj(odbc_database, 0); + if (obj) { + snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table); + stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps); + if (!stmt) { + ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql); + ast_odbc_release_obj(obj); + goto yuck; + } + res = SQLFetch(stmt); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + goto yuck; + } + res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL); + if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { + ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + goto yuck; + } + if (sscanf(rowdata, "%30d", &x) != 1) + ast_log(AST_LOG_WARNING, "Failed to read message count!\n"); + SQLFreeHandle (SQL_HANDLE_STMT, stmt); + ast_odbc_release_obj(obj); + return x; + } else + ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database); +yuck: + return x - 1; + } static void delete_file(char *sdir, int smsg) @@ -2906,24 +2954,54 @@ } #endif -/* - * A negative return value indicates an error. +/*! + * \brief Determines the highest message number in use for a given user and mailbox folder. + * \param vmu + * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause. + * + * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP). + * Typical use to set the msgnum would be to take the value returned from this method and add one to it. + * + * \note Should always be called with a lock already set on dir. + * \return the value of zero or greater to indicate the last message index in use, -1 to indicate none. */ #if (!defined(IMAP_STORAGE) && !defined(ODBC_STORAGE)) static int last_message_index(struct ast_vm_user *vmu, char *dir) { int x; - char fn[PATH_MAX]; + unsigned char map[MAXMSGLIMIT] = ""; + DIR *msgdir; + struct dirent *msgdirent; + int msgdirint; + char extension[4]; + int stopcount = 0; - if (vm_lock_path(dir)) - return ERROR_LOCK_PATH; + /* Reading the entire directory into a file map scales better than + * doing a stat repeatedly on a predicted sequence. I suspect this + * is partially due to stat(2) internally doing a readdir(2) itself to + * find each file. */ + if (!(msgdir = opendir(dir))) { + return -1; + } + while ((msgdirent = readdir(msgdir))) { + if (sscanf(msgdirent->d_name, "msg%30d.%3s", &msgdirint, extension) == 2 && !strcmp(extension, "txt") && msgdirint < MAXMSGLIMIT) { + map[msgdirint] = 1; + stopcount++; + if (option_debug > 3) { + ast_log(LOG_DEBUG, "%s map[%d] = %d, count = %d\n", dir, msgdirint, map[msgdirint], stopcount); + } + } + } + closedir(msgdir); + for (x = 0; x < vmu->maxmsg; x++) { - make_file(fn, sizeof(fn), dir, x); - if (ast_fileexists(fn, NULL, NULL) < 1) + if (map[x] == 1) { + stopcount--; + } else if (map[x] == 0 && !stopcount) { break; + } } - ast_unlock_path(dir); return x - 1; } @@ -4455,12 +4533,10 @@ ast_unlock_path(dir); inprocess_count(vmu->mailbox, vmu->context, -1); } else { - for (;;) { - make_file(fn, sizeof(fn), dir, msgnum); - if (!EXISTS(dir, msgnum, fn, NULL)) - break; - msgnum++; - } +#ifndef IMAP_STORAGE + msgnum = last_message_index(vmu, dir) + 1; +#endif + make_file(fn, sizeof(fn), dir, msgnum); /* assign a variable with the name of the voicemail file */ #ifndef IMAP_STORAGE @@ -4526,10 +4602,10 @@ return res; } -#ifndef IMAP_STORAGE -static int resequence_mailbox(struct ast_vm_user *vmu, char *dir, int count_msg) +#if !defined(IMAP_STORAGE) +static int resequence_mailbox(struct ast_vm_user *vmu, char *dir, int stopcount) { - /* we know max messages, so stop process when number is hit */ + /* we know the actual number of messages, so stop process when number is hit */ int x,dest; char sfn[PATH_MAX]; @@ -4538,7 +4614,7 @@ if (vm_lock_path(dir)) return ERROR_LOCK_PATH; - for (x = 0, dest = 0; x < count_msg + 1; x++) { + for (x = 0, dest = 0; dest != stopcount && x < vmu->maxmsg + 10; x++) { make_file(sfn, sizeof(sfn), dir, x); if (EXISTS(dir, x, sfn, NULL)) { @@ -5993,10 +6069,10 @@ /* for local storage, checks directory for messages up to maxmsg limit */ last_msg = last_message_index(vmu, vms->curdir); - if (last_msg < -1) + + if (last_msg < -1) { return last_msg; - else if (vms->lastmsg != last_msg) - { + } else if (vms->lastmsg != last_msg) { ast_log(LOG_NOTICE, "Resequencing Mailbox: %s, expected %d but found %d message(s) in box with max threshold of %d.\n", vms->curdir, last_msg + 1, vms->lastmsg + 1, vmu->maxmsg); res = resequence_mailbox(vmu, vms->curdir, count_msg); } @@ -6009,6 +6085,7 @@ { int x = 0; #ifndef IMAP_STORAGE + int last_msg_index; int res = 0, nummsg; #endif @@ -6020,9 +6097,14 @@ /* Get the deleted messages fixed */ if (vm_lock_path(vms->curdir)) return ERROR_LOCK_PATH; - + + last_msg_index = last_message_index(vmu, vms->curdir); + if (last_msg_index != vms->lastmsg) { + ast_log(LOG_NOTICE, "%d messages arrived while mailbox was open\n", last_msg_index - vms->lastmsg); + } + /* must check up to last detected message, just in case it is erroneously greater than maxmsg */ - for (x = 0; x < vms->lastmsg + 1; x++) { + for (x = 0; x < last_msg_index + 1; x++) { if (!vms->deleted[x] && (strcasecmp(vms->curbox, "INBOX") || !vms->heard[x])) { /* Save this message. It's not in INBOX or hasn't been heard */ make_file(vms->fn, sizeof(vms->fn), vms->curdir, x);