From: Marco Bettini Date: Thu, 2 Mar 2023 11:04:57 +0000 (+0000) Subject: global: Add notify_progress() to struct mail_storage_callbacks X-Git-Tag: 2.4.0~2744 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8da9c66fa2918874cae162616955f8665570b6d5;p=thirdparty%2Fdovecot%2Fcore.git global: Add notify_progress() to struct mail_storage_callbacks --- diff --git a/src/imap/Makefile.am b/src/imap/Makefile.am index a02e5fb5ce..c62b2a9824 100644 --- a/src/imap/Makefile.am +++ b/src/imap/Makefile.am @@ -112,15 +112,23 @@ headers = \ imap-status.h \ imap-state.h \ imap-sync.h \ - imap-sync-private.h + imap-sync-private.h \ + imap-storage-callbacks.h pkginc_libdir=$(pkgincludedir) pkginc_lib_HEADERS = $(headers) test_programs = \ + test-imap-storage-callbacks \ test-imap-client-hibernate noinst_PROGRAMS = $(test_programs) +test_imap_storage_callbacks_SOURCES = \ + test-imap-storage-callbacks.c \ + imap-storage-callbacks.c +test_imap_storage_callbacks_LDADD = $(imap_LDADD) +test_imap_storage_callbacks_DEPENDENCIES = $(imap_DEPENDENCIES) + test_imap_client_hibernate_SOURCES = \ test-imap-client-hibernate.c $(common_sources) test_imap_client_hibernate_LDADD = $(imap_LDADD) diff --git a/src/imap/imap-storage-callbacks.c b/src/imap/imap-storage-callbacks.c index 71bb6ce883..402204873a 100644 --- a/src/imap/imap-storage-callbacks.c +++ b/src/imap/imap-storage-callbacks.c @@ -1,9 +1,12 @@ /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ +#include "lib.h" +#include "str.h" +#include "time-util.h" #include "imap-common.h" +#include "imap-quote.h" #include "ostream.h" -#include "mail-storage.h" -#include "imap-commands-util.h" +#include "imap-storage-callbacks.h" static void notify_ok(struct mailbox *mailbox ATTR_UNUSED, const char *text, void *context) @@ -39,7 +42,96 @@ static void notify_no(struct mailbox *mailbox ATTR_UNUSED, } T_END; } +const char * +imap_storage_callback_line(const struct mail_storage_progress_details *dtl, + const char *tag) +{ + const char *verb = dtl->verb; + unsigned int total = dtl->total; + unsigned int processed = dtl->processed; + + if (verb == NULL || *verb == '\0') + verb = "Processed"; + + if (total > 0 && processed >= total) + processed = total - 1; + + /* The "]" character is totally legit in command tags, but it is + problematic inside IMAP resp-text-code(s), which are terminated + with "]". If the caracter appears inside the tag, we avoid + emitting the tag and replace it with NIL. */ + bool has_tag = tag != NULL && *tag != '\0' && strchr(tag, ']') == NULL; + + string_t *str = t_str_new(128); + str_append(str, "* OK [INPROGRESS"); + if (has_tag || processed > 0 || total > 0) { + str_append(str, " ("); + if (has_tag) + imap_append_quoted(str, tag); + else + str_append(str, "NIL"); + + if (processed > 0 || total > 0) + str_printfa(str, " %u", processed); + else + str_append(str, " NIL"); + + if (total > 0) + str_printfa(str, " %u", total); + else + str_append(str, " NIL"); + + str_append_c(str, ')'); + } + str_append(str, "] "); + + if (total > 0) { + float percentage = processed * 100.0 / total; + str_printfa(str, "%s %d%% of the mailbox", verb, (int)percentage); + + unsigned int elapsed_ms = timeval_diff_msecs(&dtl->now, + &dtl->start_time); + if (percentage > 0 && elapsed_ms > 0) { + int eta_secs = elapsed_ms * (100 - percentage) / + (1000 * percentage); + + str_printfa(str, ", ETA %d:%02d", + eta_secs / 60, eta_secs % 60); + } + } else if (processed > 0) + str_printfa(str, "%s %u item(s)", verb, processed); + else + str_append(str, "Hang in there.."); + + return str_c(str); +} + +int imap_notify_progress(const struct mail_storage_progress_details *dtl, + struct client *client) +{ + int ret; + T_BEGIN { + bool corked = o_stream_is_corked(client->output); + const char *line = imap_storage_callback_line(dtl, NULL/*tag*/); + + client_send_line(client, line); + ret = o_stream_uncork_flush(client->output); + if (corked) + o_stream_cork(client->output); + } T_END; + return ret; +} + +static void notify_progress(struct mailbox *mailbox ATTR_UNUSED, + const struct mail_storage_progress_details *dtl, + void *context) +{ + struct client *client = context; + (void)imap_notify_progress(dtl, client); +} + struct mail_storage_callbacks imap_storage_callbacks = { - notify_ok, - notify_no + .notify_ok = notify_ok, + .notify_no = notify_no, + .notify_progress = notify_progress }; diff --git a/src/imap/imap-storage-callbacks.h b/src/imap/imap-storage-callbacks.h new file mode 100644 index 0000000000..8bb557b260 --- /dev/null +++ b/src/imap/imap-storage-callbacks.h @@ -0,0 +1,13 @@ +#ifndef IMAP_STORAGE_CALLBACKS_H +#define IMAP_STORAGE_CALLBACKS_H + +#include "imap-client.h" + +const char * +imap_storage_callback_line(const struct mail_storage_progress_details *dtl, + const char *tag); + +int imap_notify_progress(const struct mail_storage_progress_details *dtl, + struct client *client); + +#endif diff --git a/src/imap/test-imap-storage-callbacks.c b/src/imap/test-imap-storage-callbacks.c new file mode 100644 index 0000000000..350c100f87 --- /dev/null +++ b/src/imap/test-imap-storage-callbacks.c @@ -0,0 +1,64 @@ +/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "test-common.h" +#include "test-subprocess.h" +#include "imap-storage-callbacks.h" + +#include + +void client_send_line(struct client *client ATTR_UNUSED, const char *data ATTR_UNUSED) +{ +} + +#define non_0 1 +#define t_non_0 { .tv_sec = non_0 } +#define t_zero { .tv_sec = 0 } +#define t_10sec { .tv_sec = 10 } +#define t_42sec { .tv_sec = 42 } + +static const struct test_vector { + const struct mail_storage_progress_details dtl; + const char *tag; + const char *expect; +} test_vectors[] = { +{ .dtl = { .processed = 0, .total = 0, .now = t_non_0, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS] Hang in there.." }, +{ .dtl = { .processed = 0, .total = 0, .now = t_non_0, .verb = NULL, }, .tag = "tag]", .expect = "* OK [INPROGRESS] Hang in there.." }, +{ .dtl = { .processed = 0, .total = 0, .now = t_non_0, .verb = NULL, }, .tag = "tag", .expect = "* OK [INPROGRESS (\"tag\" NIL NIL)] Hang in there.." }, +{ .dtl = { .processed = non_0, .total = 0, .now = t_non_0, .verb = NULL, }, .tag = "tag", .expect = "* OK [INPROGRESS (\"tag\" 1 NIL)] Processed 1 item(s)" }, +{ .dtl = { .processed = non_0, .total = 0, .now = t_non_0, .verb = NULL, }, .tag = "tag]", .expect = "* OK [INPROGRESS (NIL 1 NIL)] Processed 1 item(s)" }, +{ .dtl = { .processed = non_0, .total = 0, .now = t_non_0, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 1 NIL)] Processed 1 item(s)" }, +{ .dtl = { .processed = 0, .total = non_0, .now = t_zero, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 0 1)] Processed 0% of the mailbox" }, +{ .dtl = { .processed = 0, .total = non_0, .now = t_non_0, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 0 1)] Processed 0% of the mailbox" }, +{ .dtl = { .processed = 0, .total = non_0, .now = t_zero, .verb = "", }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 0 1)] Processed 0% of the mailbox" }, +{ .dtl = { .processed = 0, .total = non_0, .now = t_zero, .verb = "At", }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 0 1)] At 0% of the mailbox" }, +{ .dtl = { .processed = 42, .total = 100, .now = t_zero, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 42 100)] Processed 42% of the mailbox" }, +{ .dtl = { .processed = 42, .total = 100, .now = t_42sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 42 100)] Processed 42% of the mailbox, ETA 0:58" }, +{ .dtl = { .processed = 20, .total = 60, .now = t_10sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 20 60)] Processed 33% of the mailbox, ETA 0:20" }, +{ .dtl = { .processed = 229, .total = 1000, .now = t_10sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 229 1000)] Processed 22% of the mailbox, ETA 0:33" }, +{ .dtl = { .processed = 2000, .total = 1000, .now = t_10sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 999 1000)] Processed 99% of the mailbox, ETA 0:00" }, +{ .dtl = { .processed = 3, .total = 2, .now = t_10sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 1 2)] Processed 50% of the mailbox, ETA 0:10" }, +{ .dtl = { .processed = 2, .total = 2, .now = t_10sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 1 2)] Processed 50% of the mailbox, ETA 0:10" }, +{ .dtl = { .processed = 1, .total = 2, .now = t_10sec, .verb = NULL, }, .tag = NULL, .expect = "* OK [INPROGRESS (NIL 1 2)] Processed 50% of the mailbox, ETA 0:10" }, +}; + +static void test_imap_storage_callback_line(void) +{ + test_begin("imap_storage_callback_line"); + for (unsigned int index = 0; index < N_ELEMENTS(test_vectors); ++index ) { + const struct test_vector *v = test_vectors + index; + const char *actual = imap_storage_callback_line(&v->dtl, v->tag); + test_assert_strcmp_idx(v->expect, actual, index); + } + test_end(); +} + +int main(void) +{ + static void (*const test_functions[])(void) = { + test_imap_storage_callback_line, + NULL + }; + + return test_run(test_functions); +} diff --git a/src/lib-storage/mail-storage.c b/src/lib-storage/mail-storage.c index 762c496f23..3b39bca244 100644 --- a/src/lib-storage/mail-storage.c +++ b/src/lib-storage/mail-storage.c @@ -2538,22 +2538,17 @@ static void mailbox_search_notify(struct mailbox *box, /* set the search time in here, in case a plugin already spent some time indexing the mailbox */ ctx->search_start_time = ioloop_timeval; - } else if (box->storage->callbacks.notify_ok != NULL && + } else if (box->storage->callbacks.notify_progress != NULL && !ctx->progress_hidden) { - float percentage = ctx->progress_cur * 100.0 / - ctx->progress_max; - unsigned int msecs = timeval_diff_msecs(&ioloop_timeval, - &ctx->search_start_time); - unsigned int secs = (msecs / (percentage / 100.0) - msecs) / 1000; + struct mail_storage_progress_details dtl = { + .total = ctx->progress_max, + .processed = ctx->progress_cur, + .start_time = ctx->search_start_time, + .now = ioloop_timeval, + }; - T_BEGIN { - const char *text = t_strdup_printf( - "Searched %d%% of the mailbox, ETA %d:%02d", - (int)percentage, secs/60, secs%60); - box->storage->callbacks. - notify_ok(box, text, - box->storage->callback_context); - } T_END; + box->storage->callbacks.notify_progress( + box, &dtl, box->storage->callback_context); } ctx->last_notify = ioloop_timeval; } @@ -2579,10 +2574,7 @@ bool mailbox_search_next_nonblock(struct mail_search_context *ctx, *tryagain_r = FALSE; T_BEGIN { - time_t elapsed = ioloop_time - ctx->last_notify.tv_sec; - if (elapsed >= MAIL_STORAGE_NOTIFY_INTERVAL_SECS) - mailbox_search_notify(box, ctx); - + mailbox_search_notify(box, ctx); ret = box->v.search_next_nonblock(ctx, mail_r, tryagain_r); } T_END; if (!ret) diff --git a/src/lib-storage/mail-storage.h b/src/lib-storage/mail-storage.h index 6088adcac3..0c17ef95fa 100644 --- a/src/lib-storage/mail-storage.h +++ b/src/lib-storage/mail-storage.h @@ -427,6 +427,21 @@ struct mail { enum mail_lookup_abort lookup_abort; }; +struct mail_storage_progress_details { + const char *verb; + unsigned int total; + unsigned int processed; + + /* "now" could be inferred directly by the callback, but that + would create mismatches in the concept of 'now' between caller + and callback, given that the structure allows for microseconds + precision. + Also, this way the caller can capture 'now' in the most + representative point along the execution. */ + struct timeval now; + struct timeval start_time; +}; + struct mail_storage_callbacks { /* "* OK " */ void (*notify_ok)(struct mailbox *mailbox, const char *text, @@ -434,7 +449,10 @@ struct mail_storage_callbacks { /* "* NO " */ void (*notify_no)(struct mailbox *mailbox, const char *text, void *context); - + /* "* OK [INPROGRESS (...)] " */ + void (*notify_progress)(struct mailbox *mailbox, + const struct mail_storage_progress_details *dtl, + void *context); }; struct mailbox_virtual_pattern {