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)
/* 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)
} 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
};
--- /dev/null
+#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
--- /dev/null
+/* 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);
+}
/* 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;
}
*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)
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,
/* "* 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 {