jcs
/subtext
/amendments
/110
mail: Rewrite, yet again
Implement pagination, move message actions to a sub-menu when viewing
a message by its index on the current page
jcs made amendment 110 over 2 years ago
--- mail.c Sun May 22 21:56:17 2022
+++ mail.c Tue May 24 22:59:15 2022
@@ -27,6 +27,12 @@
#include "uthread.h"
#include "util.h"
+#define MSGS_PER_PAGE 10
+
+#define MAIL_READ_RETURN_DONE -1
+#define MAIL_READ_RETURN_LIST -2
+#define MAIL_READ_RETURN_FIND -3
+
struct bile_object_field mail_object_fields[] = {
{ offsetof(struct mail_message, recipient_user_id),
member_size(struct mail_message, recipient_user_id), -1 },
@@ -53,11 +59,9 @@ size_t nmail_object_fields = nitems(mail_object_fields
void mail_free_message_strings(struct mail_message *msg);
short mail_save(struct session *s, struct mail_message *msg);
-void mail_read(struct session *s, unsigned long idx);
-void mail_delete(struct session *s, unsigned long idx);
-void mail_mark_unread(struct session *s, unsigned long idx);
-void mail_list(struct session *s, bool sent, unsigned long **mail_ids,
- size_t *nmsgs);
+short mail_read(struct session *s, unsigned long id, short idx);
+void mail_list(struct session *s, unsigned long *mail_ids, size_t nmsgs,
+ size_t page, size_t pages);
short mail_get_message_id(struct session *s, size_t nmsgs, char *prompt,
short initial);
@@ -114,19 +118,23 @@ void
mail_menu(struct session *s)
{
static struct session_menu_option opts[] = {
- { 'l', "Ll", "List mail messages" },
{ '#', "#0123456789", "Read mail message [#]" },
- { 'd', "Dd", "Delete mail message [#]" },
- { 'u', "Uu", "Mark mail message [#] unread" },
- { 'c', "Cc", "Compose new mail message" },
+ { '<', "<", "Previous page of messages" },
+ { 'l', "Ll", "List mail messages" },
+ { '>', ">", "Next page of messages" },
+ { 'm', "MmCc", "Compose new mail message" },
{ 'q', "QqXx", "Return to main menu" },
- { '?', "?", "List menu options" },
+ { '?', "?", "Show this help menu" },
};
- size_t nmsgs, id;
+ char title[30];
+ size_t nmsgs, nmail_ids, id, page, pages;
unsigned long *mail_ids = NULL;
+ short ret;
char c;
bool show_help = false;
bool done = false;
+ bool find_message_ids = true;
+ bool show_list = true;
if (!s->user) {
session_output_string(s, "Mail is not available to guests.\r\n"
@@ -135,59 +143,98 @@ mail_menu(struct session *s)
return;
}
- session_printf(s, "{{B}}Private Mail{{/B}}\r\n");
- session_flush(s);
+ page = 0;
- mail_list(s, false, &mail_ids, &nmsgs);
-
while (!done) {
+ if (find_message_ids) {
+ nmsgs = mail_find_ids_for_user(s->user, &nmail_ids, &mail_ids,
+ page * MSGS_PER_PAGE, MSGS_PER_PAGE, false);
+ /* ceil(nmsgs / MSGS_PER_PAGE) */
+ pages = (nmsgs + MSGS_PER_PAGE - 1) / MSGS_PER_PAGE;
+
+ if (page >= pages)
+ page = pages - 1;
+
+ find_message_ids = false;
+ }
+
+ if (show_list) {
+ mail_list(s, mail_ids, nmail_ids, page + 1, pages);
+ show_list = false;
+ }
+
c = session_menu(s, "Private Mail", "Mail", opts, nitems(opts),
show_help);
show_help = false;
+handle_opt:
switch (c) {
- case 'c':
+ case 'm':
mail_compose(s, NULL, NULL, NULL);
break;
- case 'd':
- id = mail_get_message_id(s, nmsgs, "Message # to delete", -1);
- if (id != -1)
- mail_delete(s, id);
- break;
case 'l':
- mail_list(s, false, &mail_ids, &nmsgs);
+ show_list = true;
break;
- case 'u':
- id = mail_get_message_id(s, nmsgs, "Message # to mark unread",
- -1);
- if (id != -1)
- mail_mark_unread(s, id);
+ case '>':
+ case '<':
+ if (c == '>' && page == pages - 1) {
+ session_printf(s, "You are at the last page of messages\r\n");
+ session_flush(s);
+ break;
+ }
+ if (c == '<' && page == 0) {
+ session_printf(s, "You are already at the first page\r\n");
+ session_flush(s);
+ break;
+ }
+ if (c == '>')
+ page++;
+ else
+ page--;
+ find_message_ids = true;
+ show_list = true;
break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- id = mail_get_message_id(s, nmsgs, "Message # to read",
- c - '0');
- if (id != -1)
- mail_read(s, mail_ids[id - 1]);
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ if (c >= nmail_ids) {
+ session_printf(s, "Invalid message\r\n");
+ session_flush(s);
+ break;
+ }
+ ret = mail_read(s, mail_ids[c], c);
+ switch (ret) {
+ case MAIL_READ_RETURN_DONE:
+ break;
+ case MAIL_READ_RETURN_LIST:
+ show_list = true;
+ break;
+ case MAIL_READ_RETURN_FIND:
+ find_message_ids = true;
+ show_list = true;
+ break;
+ default:
+ c = ret;
+ goto handle_opt;
+ }
break;
case '?':
show_help = true;
break;
- case '#':
- break;
default:
done = true;
break;
}
}
+
+ free(mail_ids);
}
void
@@ -206,8 +253,7 @@ mail_compose(struct session *s, char *initial_to, char
if (initial_body)
msg.body = xstrdup(initial_body);
- session_output_template(s, "{{B}}Compose New Private Mail{{/B}}\r\n"
- "{{B}}------------{{/B}}\r\n");
+ session_output_template(s, "{{B}}Compose New Private Mail{{/B}}\r\n");
session_printf(s, "{{B}}From: {{/B}} %s\r\n", s->user->username);
session_flush(s);
@@ -314,12 +360,15 @@ mail_compose_start:
case '\n':
case '\r':
/* send */
- session_output_string(s, "Sending mail...\r\n");
+ session_output_string(s, "Sending mail...");
session_flush(s);
msg.time = Time;
msg.sender_user_id = s->user->id;
mail_save(s, &msg);
+
+ session_output_string(s, " sent!\r\n");
+ session_flush(s);
goto mail_compose_done;
break;
@@ -340,36 +389,37 @@ mail_compose_done:
}
void
-mail_list(struct session *s, bool sent, unsigned long **mail_ids,
- size_t *nmsgs)
+mail_list(struct session *s, unsigned long *mail_ids, size_t nmsgs,
+ size_t page, size_t pages)
{
- char time[24];
+ char time[10];
size_t n, size;
struct mail_message msg;
- struct user_map *user;
+ struct username_cache *user;
char *data;
-
- *nmsgs = mail_find_for_user_id(s->user->id, mail_ids);
- if (*nmsgs == 0) {
- session_output_string(s, "No messages.\r\n");
- session_flush(s);
- if (*mail_ids)
- free(*mail_ids);
- return;
- }
-
- for (n = 0; n < *nmsgs; n++) {
- size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, (*mail_ids)[n],
+
+ session_printf(s, "{{B}}Private Mail (Page %ld of %ld){{/B}}\r\n",
+ page, pages);
+ session_printf(s, "%s# Flg Date From Subject%s\r\n",
+ ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
+ session_flush(s);
+
+ for (n = 0; n < nmsgs; n++) {
+ size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, mail_ids[n],
&data);
+ if (size == 0)
+ break;
bile_unmarshall_object(db->bile, mail_object_fields,
nitems(mail_object_fields), data, size, &msg);
+ free(data);
+
user = user_find_username(msg.sender_user_id);
- strftime(time, sizeof(time), "%m/%d", localtime(&msg.time));
+ strftime(time, sizeof(time), "%b %d", localtime(&msg.time));
- session_printf(s, "%s%c [%- 3ld] %s %- 10s {{#}}%- 40s%s\r\n",
+ session_printf(s, "%s%ld %c %s %- 10s {{#}}%- 40s%s\r\n",
msg.read ? "" : ansi(s, ANSI_BOLD, ANSI_END),
+ n,
msg.read ? ' ' : 'N',
- n + 1,
time,
user ? user->username : "(unknown)",
msg.subject,
@@ -377,22 +427,43 @@ mail_list(struct session *s, bool sent, unsigned long
session_flush(s);
mail_free_message_strings(&msg);
- free(data);
}
}
-void
-mail_read(struct session *s, unsigned long id)
+short
+mail_read(struct session *s, unsigned long id, short idx)
{
+ static struct session_menu_option opts[] = {
+ { 'r', "Rr", "Reply to this message" },
+ { 'd', "Dd", "Delete this message" },
+ { 'u', "Uu", "Mark this message unread" },
+ { 'l', "Ll", "Return and list mail messages" },
+ { '#', "#0123456789", "Return and read mail message [#]" },
+ { 'q', "QqXx", "Return to message list" },
+ { '?', "?", "Show this help menu" },
+ };
char time[32];
+ char prompt[24];
+ char title[50];
size_t size;
struct mail_message msg;
- struct user_map *sender, *recipient;
- char *data;
+ struct username_cache *sender, *recipient;
+ short ret = MAIL_READ_RETURN_DONE;
+ char *data, *reply_subject;
+ bool done = false, show_help = false;
+ char c;
size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, id, &data);
+ if (size == 0) {
+ session_printf(s, "{{B}}Error:{{/B}} Can't find message\r\n");
+ session_flush(s);
+ return MAIL_READ_RETURN_DONE;
+ }
+
bile_unmarshall_object(db->bile, mail_object_fields,
nitems(mail_object_fields), data, size, &msg);
+ free(data);
+
sender = user_find_username(msg.sender_user_id);
recipient = user_find_username(msg.recipient_user_id);
@@ -403,78 +474,95 @@ mail_read(struct session *s, unsigned long id)
sender ? sender->username : "(unknown)");
session_printf(s, "{{B}}To:{{/B}} %s\r\n",
recipient ? recipient->username : "(unknown)");
- session_printf(s, "{{B}}Date:{{/B}} %s\r\n", time);
+ session_printf(s, "{{B}}Date:{{/B}} %s %s\r\n", time,
+ db->config.timezone);
session_printf(s, "{{B}}Subject:{{/B}}{{#}} %s\r\n", msg.subject);
session_flush(s);
session_output_string(s, "\r\n");
- session_output(s, msg.body, msg.body_size);
+ session_output_string(s, msg.body);
session_output_string(s, "\r\n");
-
+ session_flush(s);
+
if (!msg.read) {
msg.read = Time;
if (mail_save(s, &msg) != 0)
session_output_string(s, "Failed marking message read!\r\n");
}
- mail_free_message_strings(&msg);
- free(data);
-}
+ snprintf(prompt, sizeof(prompt), "Mail:Message %d", idx);
+ snprintf(title, sizeof(title), "Private Mail: Message %d", idx);
-void
-mail_delete(struct session *s, unsigned long idx)
-{
- unsigned long *mail_ids;
- size_t nmsgs;
-
- nmsgs = mail_find_for_user_id(s->user->id, &mail_ids);
- if (idx > nmsgs) {
- session_printf(s, "Invalid message id {{B}}%ld{{/B}}\r\n", idx);
- goto delete_done;
+ while (!done) {
+ c = session_menu(s, title, prompt, opts, nitems(opts),
+ show_help);
+ show_help = false;
+
+ switch (c) {
+ case 'r':
+ if (!sender)
+ break;
+
+ reply_subject = xmalloc(strlen(msg.subject) + 5);
+ if (strncmp(msg.subject, "Re:", 3) == 0)
+ strlcpy(reply_subject, msg.subject,
+ strlen(msg.subject) + 1);
+ else
+ sprintf(reply_subject, "Re: %s", msg.subject);
+
+ mail_compose(s, sender->username, reply_subject, NULL);
+
+ free(reply_subject);
+ break;
+ case 'd':
+ if (bile_delete(db->bile, DB_MESSAGE_RTYPE, msg.id) == 0)
+ session_printf(s, "Deleted message {{B}}%d{{/B}}\r\n", idx);
+ else
+ session_printf(s, "Failed deleting message "
+ "{{B}}%d{{/B}}! (%d)\r\n", idx, bile_error(db->bile));
+ ret = MAIL_READ_RETURN_FIND;
+ done = true;
+ break;
+ case 'u':
+ msg.read = 0;
+ if (mail_save(s, &msg) == 0)
+ session_printf(s, "Marked message {{B}}%d{{/B}} unread\r\n",
+ idx);
+ else
+ session_printf(s, "Failed updating message "
+ "{{B}}%d{{/B}}! (%d)\r\n", idx, bile_error(db->bile));
+ ret = MAIL_READ_RETURN_DONE;
+ done = true;
+ break;
+ case 'l':
+ done = true;
+ ret = MAIL_READ_RETURN_LIST;
+ break;
+ case 'q':
+ done = true;
+ ret = MAIL_READ_RETURN_DONE;
+ break;
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ ret = c;
+ done = true;
+ break;
+ case '?':
+ show_help = true;
+ break;
+ }
}
- if (bile_delete(db->bile, DB_MESSAGE_RTYPE, mail_ids[idx - 1]) == 0)
- session_printf(s, "Deleted message {{B}}%ld{{/B}}\r\n", idx);
- else
- session_printf(s, "Failed deleting message {{B}}%ld{{/B}}! "
- "(%d)\r\n", idx, bile_error(db->bile));
-
-delete_done:
- if (mail_ids)
- free(mail_ids);
-}
-
-void
-mail_mark_unread(struct session *s, unsigned long idx)
-{
- struct mail_message msg;
- unsigned long *mail_ids;
- size_t nmsgs, size;
- char *data;
-
- nmsgs = mail_find_for_user_id(s->user->id, &mail_ids);
- if (idx > nmsgs) {
- session_printf(s, "Invalid message id {{B}}%ld{{/B}}\r\n", idx);
- goto unread_done;
- }
-
- size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, mail_ids[idx - 1],
- &data);
- bile_unmarshall_object(db->bile, mail_object_fields,
- nitems(mail_object_fields), data, size, &msg);
- msg.read = 0;
- if (mail_save(s, &msg) == 0)
- session_printf(s, "Marked message {{B}}%ld{{/B}} unread\r\n",
- idx);
- else
- session_printf(s, "Failed marking message {{B}}%ld{{/B}} "
- "unread!\r\n", idx);
-
mail_free_message_strings(&msg);
- free(data);
-unread_done:
- if (mail_ids)
- free(mail_ids);
+ return ret;
}
short
@@ -504,39 +592,58 @@ mail_save(struct session *s, struct mail_message *msg)
}
size_t
-mail_find_for_user_id(unsigned long user_id, unsigned long **mail_ids)
+mail_find_ids_for_user(struct user *user, size_t *nmail_ids,
+ unsigned long **mail_ids, size_t offset, size_t limit, bool only_unread)
{
unsigned long msg_user_id;
struct bile_object *o;
- size_t nmsg, msgs_for_user, mail_ids_size, id;
+ struct mail_message msg;
+ size_t n, nmsgs_for_user, nret, mail_ids_size, id, size;
short i, j;
-
- mail_ids_size = sizeof(unsigned long) * 16;
+ char *data;
+ bool read;
+
+ mail_ids_size = sizeof(long) * 16;
if (mail_ids != NULL)
*mail_ids = xmalloc(mail_ids_size);
-
- msgs_for_user = 0;
- nmsg = 0;
- while ((o = bile_get_nth_of_type(db->bile, nmsg, DB_MESSAGE_RTYPE))) {
- bile_read(db->bile, DB_MESSAGE_RTYPE, o->id, (char *)&msg_user_id,
+ if (nmail_ids != NULL)
+ *nmail_ids = 0;
+
+ nmsgs_for_user = 0;
+ for (n = 0; o = bile_get_nth_of_type(db->bile, n, DB_MESSAGE_RTYPE);
+ n++) {
+ id = o->id;
+ bile_read(db->bile, DB_MESSAGE_RTYPE, id, (char *)&msg_user_id,
sizeof(msg_user_id));
- if (msg_user_id == user_id) {
- if (mail_ids != NULL) {
- EXPAND_TO_FIT(*mail_ids, mail_ids_size,
- msgs_for_user * sizeof(long), sizeof(long),
- sizeof(unsigned long) * 16);
- (*mail_ids)[msgs_for_user] = o->id;
- }
- msgs_for_user++;
- }
free(o);
- nmsg++;
+
+ if (msg_user_id != user->id)
+ continue;
+
+ if (only_unread) {
+ size = bile_read_alloc(db->bile, DB_MESSAGE_RTYPE, id, &data);
+ bile_unmarshall_object(db->bile,
+ mail_object_fields, nmail_object_fields, data, size, &msg);
+ read = msg.read;
+ mail_free_message_strings(&msg);
+ if (read)
+ continue;
+ }
+
+ if (mail_ids != NULL) {
+ EXPAND_TO_FIT(*mail_ids, mail_ids_size,
+ nmsgs_for_user * sizeof(long), sizeof(long),
+ sizeof(long) * 16);
+ (*mail_ids)[nmsgs_for_user] = id;
+ }
+
+ nmsgs_for_user++;
}
if (mail_ids != NULL) {
/* sort by message id for consistent ordering */
- for (i = 0; i < msgs_for_user; i++) {
- for (j = 0; j < msgs_for_user - i - 1; j++) {
+ for (i = 0; i < nmsgs_for_user; i++) {
+ for (j = 0; j < nmsgs_for_user - i - 1; j++) {
if ((*mail_ids)[j] > (*mail_ids)[j + 1]) {
id = (*mail_ids)[j];
(*mail_ids)[j] = (*mail_ids)[j + 1];
@@ -546,5 +653,27 @@ mail_find_for_user_id(unsigned long user_id, unsigned
}
}
- return msgs_for_user;
+ if (nmail_ids != NULL)
+ *nmail_ids = nmsgs_for_user;
+
+ if (offset) {
+ if (offset >= nmsgs_for_user) {
+ if (nmail_ids != NULL)
+ *nmail_ids = 0;
+ if (mail_ids != NULL)
+ free(*mail_ids);
+ } else {
+ if (mail_ids != NULL) {
+ for (j = offset, i = 0; j < nmsgs_for_user; j++, i++)
+ (*mail_ids)[i] = (*mail_ids)[j];
+ }
+ if (nmail_ids != NULL)
+ *nmail_ids -= offset;
+ }
+ }
+
+ if (nmail_ids != NULL && *nmail_ids > limit)
+ *nmail_ids = limit;
+
+ return nmsgs_for_user;
}
--- mail.h Sun May 22 21:24:22 2022
+++ mail.h Tue May 24 16:44:00 2022
@@ -35,11 +35,9 @@ struct mail_message {
unsigned long parent_message_id;
};
-extern struct bile_object_field mail_object_fields[];
-
void mail_menu(struct session *s);
void mail_compose(struct session *s, char *to, char *subject, char *body);
-size_t mail_find_for_user_id(unsigned long user_id,
- unsigned long **mail_ids);
+size_t mail_find_ids_for_user(struct user *user, size_t *nmail_ids,
+ unsigned long **mail_ids, size_t offset, size_t limit, bool only_unread);
#endif
--- session.c Fri May 20 12:29:38 2022
+++ session.c Tue May 24 16:46:13 2022
@@ -1025,7 +1025,9 @@ session_expand_var(struct session *session, char *ivar
session->user->username : GUEST_USERNAME, retsize);
} else if (strcmp(var, "new_mail") == 0) {
if (session->user) {
- if ((mcount = mail_find_for_user_id(session->user->id, NULL)))
+ mcount = mail_find_ids_for_user(session->user, NULL,
+ NULL, 0, 0, true);
+ if (mcount)
retlen = sprintf(retval, "(%ld New)", mcount);
}
} else if (var[0] == '"') {
@@ -1237,9 +1239,10 @@ get_menu_option:
for (j = 0; ; j++) {
if (opt->key[j] == '\0')
break;
- if (opt->key[j] == c) {
- if (opt->ret == '#')
- return c; /* return actual number */
+ if (opt->ret == '#') {
+ if (opt->key[j] == c && c != '#')
+ return c - '0'; /* return actual number */
+ } else if (opt->key[j] == c) {
return opt->ret;
}
}