]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
global: Add notify_progress() to struct mail_storage_callbacks
authorMarco Bettini <marco.bettini@open-xchange.com>
Thu, 2 Mar 2023 11:04:57 +0000 (11:04 +0000)
committertimo.sirainen <timo.sirainen@open-xchange.com>
Mon, 22 May 2023 09:21:43 +0000 (09:21 +0000)
src/imap/Makefile.am
src/imap/imap-storage-callbacks.c
src/imap/imap-storage-callbacks.h [new file with mode: 0644]
src/imap/test-imap-storage-callbacks.c [new file with mode: 0644]
src/lib-storage/mail-storage.c
src/lib-storage/mail-storage.h

index a02e5fb5ce3c7c91dfba2b1f8cbf03e8a463742e..c62b2a9824cfbb59ef79c29e6920257871a0c212 100644 (file)
@@ -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)
index 71bb6ce883eff015fb2ea3b6a73cc3c228181b2b..402204873adbb1a641811a374201b0964437eea9 100644 (file)
@@ -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 (file)
index 0000000..8bb557b
--- /dev/null
@@ -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 (file)
index 0000000..350c100
--- /dev/null
@@ -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 <stdio.h>
+
+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);
+}
index 762c496f23ff25fc96ac5cc025fa674b704e7c06..3b39bca244c1fb663de9871d552e6787da619214 100644 (file)
@@ -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)
index 6088adcac37221b905312e468febbe5ab25c2c35..0c17ef95fadfbf962cde06a99da0368855ad2c6c 100644 (file)
@@ -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 <text>" */
        void (*notify_ok)(struct mailbox *mailbox, const char *text,
@@ -434,7 +449,10 @@ struct mail_storage_callbacks {
        /* "* NO <text>" */
        void (*notify_no)(struct mailbox *mailbox, const char *text,
                          void *context);
-
+       /* "* OK [INPROGRESS (...)] <text>" */
+       void (*notify_progress)(struct mailbox *mailbox,
+                               const struct mail_storage_progress_details *dtl,
+                               void *context);
 };
 
 struct mailbox_virtual_pattern {