2020-06-25 18:23:38 +00:00
|
|
|
/////////////////////////////////////////////////////////////////
|
2020-06-27 03:42:03 +00:00
|
|
|
// emai//er - Simple Email User Agent vaguely inspired by Elm
|
2020-06-24 21:22:35 +00:00
|
|
|
// Handles INBOX in the format created by POP65
|
2020-06-25 18:23:38 +00:00
|
|
|
// Bobbi June 2020
|
|
|
|
/////////////////////////////////////////////////////////////////
|
2020-06-24 21:22:35 +00:00
|
|
|
|
2020-06-26 01:47:03 +00:00
|
|
|
// TODO:
|
2020-06-28 04:25:49 +00:00
|
|
|
// - Automatically insert date
|
2020-06-28 17:14:29 +00:00
|
|
|
// - Parse email addresses in < angle brackets >
|
2020-06-28 00:02:09 +00:00
|
|
|
// - Fix terrible scrollback algorithm!!
|
2020-06-28 04:25:49 +00:00
|
|
|
// - Editor for email composition functions
|
|
|
|
|
|
|
|
#define PROGNAME "emai//er v0.1"
|
2020-06-26 01:47:03 +00:00
|
|
|
|
2020-06-24 21:22:35 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdint.h>
|
2020-06-26 23:17:05 +00:00
|
|
|
#include <stdarg.h>
|
2020-06-26 15:29:45 +00:00
|
|
|
#include <unistd.h>
|
2020-06-24 21:22:35 +00:00
|
|
|
#include <conio.h>
|
2020-06-24 21:50:19 +00:00
|
|
|
#include <string.h>
|
2020-06-26 16:09:20 +00:00
|
|
|
#include <ctype.h>
|
2020-06-24 21:22:35 +00:00
|
|
|
|
2020-06-25 18:23:38 +00:00
|
|
|
#define EMAIL_C
|
|
|
|
#include "email_common.h"
|
|
|
|
|
2020-06-26 23:30:06 +00:00
|
|
|
#define MSGS_PER_PAGE 18 // Number of messages shown on summary screen
|
2020-06-26 18:12:58 +00:00
|
|
|
#define MENU_ROW 22 // Row that the menu appears on
|
|
|
|
#define PROMPT_ROW 24 // Row that data entry prompt appears on
|
|
|
|
#define SCROLLBACK 25*80 // How many bytes to go back when paging up
|
|
|
|
#define READSZ 1024 // Size of buffer for copying files
|
|
|
|
|
|
|
|
|
2020-06-26 21:57:38 +00:00
|
|
|
char filename[80];
|
|
|
|
char userentry[80];
|
|
|
|
FILE *fp;
|
|
|
|
struct emailhdrs *headers;
|
|
|
|
uint16_t selection, prevselection;
|
|
|
|
uint16_t num_msgs; // Num of msgs shown in current page
|
|
|
|
uint16_t total_msgs; // Total number of message in mailbox
|
|
|
|
uint16_t total_new; // Total number of new messages
|
2020-06-27 03:27:59 +00:00
|
|
|
uint16_t total_tag; // Total number of tagged messages
|
2020-06-26 21:57:38 +00:00
|
|
|
uint16_t first_msg; // Msg numr: first message current page
|
|
|
|
char curr_mbox[80] = "INBOX";
|
|
|
|
static unsigned char buf[READSZ];
|
2020-06-24 21:22:35 +00:00
|
|
|
|
2020-06-26 23:17:05 +00:00
|
|
|
#define ERR_NONFATAL 0
|
|
|
|
#define ERR_FATAL 1
|
2020-06-24 21:22:35 +00:00
|
|
|
|
|
|
|
/*
|
2020-06-26 23:17:05 +00:00
|
|
|
* Show non fatal error in PROMPT_ROW
|
|
|
|
* Fatal errors are shown on a blank screen
|
2020-06-24 21:22:35 +00:00
|
|
|
*/
|
2020-06-26 23:17:05 +00:00
|
|
|
void error(uint8_t fatal, const char *fmt, ...) {
|
|
|
|
va_list v;
|
|
|
|
uint8_t i;
|
|
|
|
if (fatal) {
|
|
|
|
clrscr();
|
|
|
|
printf("\n\n%cFATAL ERROR:%c\n\n", 0x0f, 0x0e);
|
|
|
|
va_start(v, fmt);
|
|
|
|
vprintf(fmt, v);
|
|
|
|
va_end(v);
|
|
|
|
printf("\n\n\n\n[Press Any Key To Quit]");
|
|
|
|
cgetc();
|
|
|
|
exit(1);
|
|
|
|
} else {
|
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (i = 0; i < PROMPT_ROW - 1; ++i)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
|
|
|
putchar(0x1a); // CLEAR LINE
|
|
|
|
va_start(v, fmt);
|
|
|
|
vprintf(fmt, v);
|
|
|
|
va_end(v);
|
|
|
|
printf(" - [Press Any Key]");
|
|
|
|
cgetc();
|
|
|
|
putchar(0x1a); // CLEAR LINE
|
|
|
|
}
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 21:57:38 +00:00
|
|
|
/*
|
|
|
|
* Busy spinner
|
|
|
|
*/
|
|
|
|
void spinner(void) {
|
|
|
|
static char chars[] = "|/-\\";
|
|
|
|
static uint8_t i = 0;
|
|
|
|
putchar(0x08); // BACKSPACE
|
|
|
|
putchar(chars[(i++) % 4]);
|
|
|
|
}
|
|
|
|
|
2020-06-24 21:22:35 +00:00
|
|
|
/*
|
|
|
|
* Read parms from POP65.CFG
|
|
|
|
*/
|
|
|
|
void readconfigfile(void) {
|
2020-06-26 21:57:38 +00:00
|
|
|
fp = fopen("POP65.CFG", "r");
|
2020-06-26 23:17:05 +00:00
|
|
|
if (!fp)
|
|
|
|
error(ERR_FATAL, "Can't open config file POP65.CFG");
|
2020-06-26 21:57:38 +00:00
|
|
|
fscanf(fp, "%s", cfg_server);
|
|
|
|
fscanf(fp, "%s", cfg_user);
|
|
|
|
fscanf(fp, "%s", cfg_pass);
|
2020-06-28 17:14:29 +00:00
|
|
|
fscanf(fp, "%s", cfg_smtp_server);
|
|
|
|
fscanf(fp, "%s", cfg_smtp_domain);
|
2020-06-26 21:57:38 +00:00
|
|
|
fscanf(fp, "%s", cfg_emaildir);
|
2020-06-28 01:00:50 +00:00
|
|
|
fscanf(fp, "%s", cfg_emailaddr);
|
2020-06-26 21:57:38 +00:00
|
|
|
fclose(fp);
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 01:47:03 +00:00
|
|
|
/*
|
|
|
|
* Free linked list rooted at headers
|
|
|
|
*/
|
|
|
|
void free_headers_list(void) {
|
|
|
|
struct emailhdrs *h = headers;
|
|
|
|
while (h) {
|
|
|
|
free(h);
|
|
|
|
h = h->next; // Not strictly legal, but will work
|
|
|
|
}
|
|
|
|
headers = NULL;
|
|
|
|
}
|
|
|
|
|
2020-06-24 21:22:35 +00:00
|
|
|
/*
|
2020-06-25 18:23:38 +00:00
|
|
|
* Read EMAIL.DB and populate linked list rooted at headers
|
2020-06-26 01:47:03 +00:00
|
|
|
* startnum - number of the first message to load (1 is the first)
|
2020-06-26 23:30:06 +00:00
|
|
|
* initialize - if 1, then total_new and total_msgs are calculated
|
2020-06-27 00:28:41 +00:00
|
|
|
* switchmbox - if 1, then errors are treated as non-fatal (for S)witch command)
|
2020-06-26 23:30:06 +00:00
|
|
|
* Returns 0 if okay, 1 on non-fatal error.
|
2020-06-24 21:22:35 +00:00
|
|
|
*/
|
2020-06-27 00:28:41 +00:00
|
|
|
uint8_t read_email_db(uint16_t startnum, uint8_t initialize, uint8_t switchmbox) {
|
2020-06-24 21:22:35 +00:00
|
|
|
struct emailhdrs *curr = NULL, *prev = NULL;
|
2020-06-26 01:47:03 +00:00
|
|
|
uint16_t count = 0;
|
2020-06-24 21:22:35 +00:00
|
|
|
uint16_t l;
|
2020-06-26 02:35:14 +00:00
|
|
|
if (initialize) {
|
2020-06-27 03:27:59 +00:00
|
|
|
total_new = total_msgs = total_tag = 0;
|
2020-06-26 02:35:14 +00:00
|
|
|
}
|
2020-06-26 01:47:03 +00:00
|
|
|
free_headers_list();
|
2020-06-26 15:00:11 +00:00
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, curr_mbox);
|
2020-06-24 21:22:35 +00:00
|
|
|
fp = fopen(filename, "rb");
|
|
|
|
if (!fp) {
|
2020-06-27 00:28:41 +00:00
|
|
|
error(switchmbox ? ERR_NONFATAL : ERR_FATAL, "Can't open %s", filename);
|
|
|
|
if (switchmbox)
|
2020-06-26 23:30:06 +00:00
|
|
|
return 1;
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
2020-06-27 00:28:41 +00:00
|
|
|
if (fseek(fp, (startnum - 1) * EMAILHDRS_SZ_ON_DISK, SEEK_SET)) {
|
|
|
|
error(switchmbox ? ERR_NONFATAL : ERR_FATAL, "Can't seek in %s", filename);
|
|
|
|
if (switchmbox)
|
2020-06-26 23:30:06 +00:00
|
|
|
return 1;
|
2020-06-26 01:47:03 +00:00
|
|
|
}
|
2020-06-26 02:35:14 +00:00
|
|
|
num_msgs = 0;
|
2020-06-24 21:22:35 +00:00
|
|
|
while (1) {
|
|
|
|
curr = (struct emailhdrs*)malloc(sizeof(struct emailhdrs));
|
2020-06-27 03:27:59 +00:00
|
|
|
if (!curr)
|
|
|
|
error(ERR_FATAL, "Can't malloc()");
|
2020-06-24 21:22:35 +00:00
|
|
|
curr->next = NULL;
|
2020-06-27 00:28:41 +00:00
|
|
|
curr->tag = ' ';
|
|
|
|
l = fread(curr, 1, EMAILHDRS_SZ_ON_DISK, fp);
|
2020-06-26 01:47:03 +00:00
|
|
|
++count;
|
2020-06-27 00:28:41 +00:00
|
|
|
if (l != EMAILHDRS_SZ_ON_DISK) {
|
2020-06-24 21:22:35 +00:00
|
|
|
free(curr);
|
|
|
|
fclose(fp);
|
2020-06-26 23:30:06 +00:00
|
|
|
return 0;
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
2020-06-26 02:35:14 +00:00
|
|
|
if (count <= MSGS_PER_PAGE) {
|
2020-06-26 01:47:03 +00:00
|
|
|
if (!prev)
|
|
|
|
headers = curr;
|
|
|
|
else
|
|
|
|
prev->next = curr;
|
|
|
|
prev = curr;
|
|
|
|
++num_msgs;
|
2020-06-26 02:35:14 +00:00
|
|
|
} else
|
|
|
|
if (!initialize) {
|
|
|
|
fclose(fp);
|
2020-06-26 23:30:06 +00:00
|
|
|
return 0;
|
2020-06-26 02:35:14 +00:00
|
|
|
}
|
|
|
|
if (initialize) {
|
|
|
|
++total_msgs;
|
|
|
|
if (curr->status == 'N')
|
|
|
|
++total_new;
|
2020-06-27 03:27:59 +00:00
|
|
|
if (curr->tag == 'T')
|
|
|
|
++total_tag;
|
2020-06-26 01:47:03 +00:00
|
|
|
}
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
2020-06-26 02:35:14 +00:00
|
|
|
fclose(fp);
|
2020-06-26 23:30:06 +00:00
|
|
|
return 0;
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
|
|
|
|
2020-06-24 21:50:19 +00:00
|
|
|
/*
|
|
|
|
* Print a header field from char postion start to end,
|
|
|
|
* padding with spaces as needed
|
|
|
|
*/
|
|
|
|
void printfield(char *s, uint8_t start, uint8_t end) {
|
|
|
|
uint8_t i;
|
|
|
|
uint8_t l = strlen(s);
|
|
|
|
for (i = start; i < end; i++)
|
|
|
|
putchar(i < l ? s[i] : ' ');
|
|
|
|
}
|
|
|
|
|
2020-06-25 00:25:31 +00:00
|
|
|
/*
|
|
|
|
* Print one line summary of email headers for one message
|
|
|
|
*/
|
2020-06-25 18:23:38 +00:00
|
|
|
void print_one_email_summary(struct emailhdrs *h, uint8_t inverse) {
|
2020-06-25 00:25:31 +00:00
|
|
|
putchar(inverse ? 0xf : 0xe); // INVERSE or NORMAL
|
2020-06-27 00:28:41 +00:00
|
|
|
putchar(h->tag == 'T' ? 'T' : ' ');
|
2020-06-26 01:47:03 +00:00
|
|
|
switch(h->status) {
|
|
|
|
case 'N':
|
|
|
|
putchar('*'); // New
|
|
|
|
break;
|
|
|
|
case 'R':
|
|
|
|
putchar(' '); // Read
|
|
|
|
break;
|
|
|
|
case 'D':
|
|
|
|
putchar('D'); // Deleted
|
|
|
|
break;
|
|
|
|
}
|
2020-06-28 00:02:09 +00:00
|
|
|
//printf("%02d|", h->emailnum);
|
|
|
|
putchar('|');
|
2020-06-25 00:25:31 +00:00
|
|
|
printfield(h->date, 0, 16);
|
|
|
|
putchar('|');
|
|
|
|
printfield(h->from, 0, 20);
|
|
|
|
putchar('|');
|
2020-06-28 00:02:09 +00:00
|
|
|
printfield(h->subject, 0, 39);
|
2020-06-25 00:25:31 +00:00
|
|
|
//putchar('\r');
|
|
|
|
putchar(0xe); // NORMAL
|
|
|
|
}
|
|
|
|
|
2020-06-25 02:05:55 +00:00
|
|
|
/*
|
2020-06-25 18:23:38 +00:00
|
|
|
* Get emailhdrs for nth email in list of headers
|
2020-06-25 02:05:55 +00:00
|
|
|
*/
|
|
|
|
struct emailhdrs *get_headers(uint16_t n) {
|
|
|
|
uint16_t i = 1;
|
|
|
|
struct emailhdrs *h = headers;
|
2020-06-26 01:47:03 +00:00
|
|
|
while (h && (i < n)) {
|
2020-06-25 02:05:55 +00:00
|
|
|
++i;
|
|
|
|
h = h->next;
|
|
|
|
}
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
2020-06-27 03:27:59 +00:00
|
|
|
void status_bar(void) {
|
|
|
|
putchar(0x19); // HOME
|
|
|
|
if (num_msgs == 0)
|
2020-06-28 04:25:49 +00:00
|
|
|
printf("%c%s [%s] No messages%c",
|
|
|
|
0x0f, PROGNAME, curr_mbox, 0x0e);
|
2020-06-27 03:27:59 +00:00
|
|
|
else
|
2020-06-28 04:25:49 +00:00
|
|
|
printf("%c%s [%s] %u messages, %u new, %u tagged. Displaying %u-%u%c",
|
|
|
|
0x0f, PROGNAME, curr_mbox, total_msgs, total_new, total_tag, first_msg,
|
2020-06-27 03:27:59 +00:00
|
|
|
first_msg + num_msgs - 1, 0x0e);
|
|
|
|
printf("\n\n");
|
|
|
|
}
|
|
|
|
|
2020-06-24 21:22:35 +00:00
|
|
|
/*
|
|
|
|
* Show email summary
|
|
|
|
*/
|
|
|
|
void email_summary(void) {
|
2020-06-26 01:47:03 +00:00
|
|
|
uint8_t i = 1;
|
2020-06-24 21:22:35 +00:00
|
|
|
struct emailhdrs *h = headers;
|
2020-06-25 02:05:55 +00:00
|
|
|
clrscr();
|
2020-06-27 03:27:59 +00:00
|
|
|
status_bar();
|
2020-06-24 21:22:35 +00:00
|
|
|
while (h) {
|
2020-06-25 18:23:38 +00:00
|
|
|
print_one_email_summary(h, (i == selection));
|
2020-06-25 00:25:31 +00:00
|
|
|
++i;
|
|
|
|
h = h->next;
|
|
|
|
}
|
2020-06-26 01:47:03 +00:00
|
|
|
putchar(0x19); // HOME
|
2020-06-26 15:00:11 +00:00
|
|
|
for (i = 0; i < MENU_ROW - 1; ++i)
|
2020-06-26 01:47:03 +00:00
|
|
|
putchar(0x0a); // CURSOR DOWN
|
2020-06-27 03:27:59 +00:00
|
|
|
printf("%cUp/K Prev | SPC/RET Read | A)rchive | C)opy | M)ove | D)el | U)ndel | P)urge %c", 0x0f, 0x0e);
|
2020-06-27 00:28:41 +00:00
|
|
|
printf("%cDn/J Next | S)witch mbox | N)ew mbox| T)ag | W)rite | R)eply | F)wd | Q)uit %c", 0x0f, 0x0e);
|
2020-06-25 00:25:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2020-06-25 18:23:38 +00:00
|
|
|
* Show email summary for nth email message in list of headers
|
2020-06-25 00:25:31 +00:00
|
|
|
*/
|
|
|
|
void email_summary_for(uint16_t n) {
|
|
|
|
struct emailhdrs *h = headers;
|
|
|
|
uint16_t j;
|
2020-06-25 02:05:55 +00:00
|
|
|
h = get_headers(n);
|
2020-06-26 01:47:03 +00:00
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (j = 0; j < n + 1; ++j)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
2020-06-25 18:23:38 +00:00
|
|
|
print_one_email_summary(h, (n == selection));
|
2020-06-25 00:25:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Move the highlight bar when user selects different message
|
|
|
|
*/
|
|
|
|
void update_highlighted(void) {
|
|
|
|
email_summary_for(prevselection);
|
|
|
|
email_summary_for(selection);
|
|
|
|
}
|
|
|
|
|
2020-06-25 02:05:55 +00:00
|
|
|
/*
|
|
|
|
* Display email with simple pager functionality
|
|
|
|
*/
|
|
|
|
void email_pager(void) {
|
2020-06-25 03:08:40 +00:00
|
|
|
uint32_t pos = 0;
|
2020-06-28 01:00:50 +00:00
|
|
|
uint8_t *p = (uint8_t*)0x25; // CURSOR ROW!!
|
2020-06-25 02:05:55 +00:00
|
|
|
struct emailhdrs *h = get_headers(selection);
|
2020-06-28 00:38:08 +00:00
|
|
|
uint8_t eof;
|
2020-06-25 02:05:55 +00:00
|
|
|
char c;
|
|
|
|
clrscr();
|
2020-06-26 15:00:11 +00:00
|
|
|
sprintf(filename, "%s/%s/EMAIL.%u", cfg_emaildir, curr_mbox, h->emailnum);
|
2020-06-25 02:05:55 +00:00
|
|
|
fp = fopen(filename, "rb");
|
|
|
|
if (!fp) {
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
2020-06-25 03:08:40 +00:00
|
|
|
return;
|
2020-06-25 02:05:55 +00:00
|
|
|
}
|
2020-06-25 18:23:38 +00:00
|
|
|
pos = h->skipbytes;
|
|
|
|
fseek(fp, pos, SEEK_SET); // Skip over headers
|
2020-06-25 03:08:40 +00:00
|
|
|
restart:
|
|
|
|
clrscr();
|
2020-06-25 02:05:55 +00:00
|
|
|
fputs("Date: ", stdout);
|
2020-06-26 03:18:23 +00:00
|
|
|
printfield(h->date, 0, 39);
|
2020-06-25 02:05:55 +00:00
|
|
|
fputs("\nFrom: ", stdout);
|
|
|
|
printfield(h->from, 0, 70);
|
|
|
|
fputs("\nTo: ", stdout);
|
|
|
|
printfield(h->to, 0, 70);
|
|
|
|
if (h->cc[0] != '\0') {
|
|
|
|
fputs("\nCC: ", stdout);
|
|
|
|
printfield(h->cc, 0, 70);
|
|
|
|
}
|
|
|
|
fputs("\nSubject: ", stdout);
|
|
|
|
printfield(h->subject, 0, 70);
|
|
|
|
fputs("\n\n", stdout);
|
2020-06-25 03:08:40 +00:00
|
|
|
while (1) {
|
2020-06-25 02:05:55 +00:00
|
|
|
c = fgetc(fp);
|
2020-06-25 03:08:40 +00:00
|
|
|
eof = feof(fp);
|
|
|
|
if (!eof) {
|
|
|
|
putchar(c);
|
|
|
|
++pos;
|
|
|
|
}
|
2020-06-25 02:05:55 +00:00
|
|
|
if (c == '\r') {
|
2020-06-27 23:54:40 +00:00
|
|
|
if ((*p) == 22) { // Use the CURSOR ROW location
|
2020-06-25 02:05:55 +00:00
|
|
|
putchar(0x0f); // INVERSE
|
2020-06-25 18:23:38 +00:00
|
|
|
printf("[%05lu] SPACE continue reading | B)ack | T)op | H)drs | Q)uit", pos);
|
2020-06-25 02:05:55 +00:00
|
|
|
putchar(0x0e); // NORMAL
|
2020-06-25 03:08:40 +00:00
|
|
|
retry1:
|
2020-06-25 02:05:55 +00:00
|
|
|
c = cgetc();
|
|
|
|
switch (c) {
|
2020-06-25 03:08:40 +00:00
|
|
|
case ' ':
|
|
|
|
break;
|
|
|
|
case 'B':
|
|
|
|
case 'b':
|
2020-06-25 18:30:10 +00:00
|
|
|
if (pos < h->skipbytes + (uint32_t)(SCROLLBACK)) {
|
2020-06-25 18:23:38 +00:00
|
|
|
pos = h->skipbytes;
|
2020-06-25 03:08:40 +00:00
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
goto restart;
|
|
|
|
} else {
|
|
|
|
pos -= (uint32_t)(SCROLLBACK);
|
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'T':
|
|
|
|
case 't':
|
2020-06-25 18:23:38 +00:00
|
|
|
pos = h->skipbytes;
|
2020-06-25 03:08:40 +00:00
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
goto restart;
|
|
|
|
break;
|
2020-06-25 18:23:38 +00:00
|
|
|
case 'H':
|
|
|
|
case 'h':
|
|
|
|
pos = 0;
|
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
goto restart;
|
|
|
|
break;
|
2020-06-25 02:05:55 +00:00
|
|
|
case 'Q':
|
|
|
|
case 'q':
|
|
|
|
fclose(fp);
|
|
|
|
return;
|
2020-06-25 03:08:40 +00:00
|
|
|
default:
|
|
|
|
putchar(7); // BELL
|
|
|
|
goto retry1;
|
2020-06-25 02:05:55 +00:00
|
|
|
}
|
|
|
|
clrscr();
|
|
|
|
}
|
2020-06-25 03:08:40 +00:00
|
|
|
} else if (eof) {
|
|
|
|
putchar(0x0f); // INVERSE
|
2020-06-25 18:23:38 +00:00
|
|
|
printf("[%05lu] *** END *** | B)ack | T)op | H)drs | Q)uit", pos);
|
2020-06-25 03:08:40 +00:00
|
|
|
putchar(0x0e); // NORMAL
|
|
|
|
retry2:
|
|
|
|
c = cgetc();
|
|
|
|
switch (c) {
|
|
|
|
case 'B':
|
|
|
|
case 'b':
|
2020-06-25 18:30:10 +00:00
|
|
|
if (pos < h->skipbytes + (uint32_t)(SCROLLBACK)) {
|
2020-06-25 18:23:38 +00:00
|
|
|
pos = h->skipbytes;
|
2020-06-25 03:08:40 +00:00
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
goto restart;
|
|
|
|
} else {
|
|
|
|
pos -= (uint32_t)(SCROLLBACK);
|
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'T':
|
|
|
|
case 't':
|
2020-06-25 18:23:38 +00:00
|
|
|
pos = h->skipbytes;
|
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
goto restart;
|
|
|
|
break;
|
|
|
|
case 'H':
|
|
|
|
case 'h':
|
2020-06-25 03:08:40 +00:00
|
|
|
pos = 0;
|
|
|
|
fseek(fp, pos, SEEK_SET);
|
|
|
|
goto restart;
|
|
|
|
break;
|
|
|
|
case 'Q':
|
|
|
|
case 'q':
|
|
|
|
fclose(fp);
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
putchar(7); // BELL
|
|
|
|
goto retry2;
|
|
|
|
}
|
2020-06-25 03:57:32 +00:00
|
|
|
clrscr();
|
2020-06-25 02:05:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 03:18:23 +00:00
|
|
|
/*
|
|
|
|
* Write updated email headers to EMAIL.DB
|
|
|
|
*/
|
|
|
|
void write_updated_headers(struct emailhdrs *h, uint16_t pos) {
|
|
|
|
uint16_t l;
|
2020-06-26 15:00:11 +00:00
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, curr_mbox);
|
2020-06-26 03:18:23 +00:00
|
|
|
fp = fopen(filename, "rb+");
|
2020-06-26 23:17:05 +00:00
|
|
|
if (!fp)
|
|
|
|
error(ERR_FATAL, "Can't open %s", filename);
|
2020-06-27 00:28:41 +00:00
|
|
|
if (fseek(fp, (pos - 1) * EMAILHDRS_SZ_ON_DISK, SEEK_SET))
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_FATAL, "Can't seek in %s", filename);
|
2020-06-27 00:28:41 +00:00
|
|
|
l = fwrite(h, 1, EMAILHDRS_SZ_ON_DISK, fp);
|
|
|
|
if (l != EMAILHDRS_SZ_ON_DISK)
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_FATAL, "Can't write to %s", filename);
|
2020-06-26 03:18:23 +00:00
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
|
2020-06-26 15:29:45 +00:00
|
|
|
/*
|
|
|
|
* Create new mailbox
|
|
|
|
* Create directory, EMAIL.DB and NEXT.EMAIL files
|
|
|
|
*/
|
|
|
|
void new_mailbox(char *mbox) {
|
|
|
|
sprintf(filename, "%s/%s", cfg_emaildir, mbox);
|
|
|
|
if (mkdir(filename)) {
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_NONFATAL, "Can't create dir %s", filename);
|
2020-06-26 15:29:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, mbox);
|
|
|
|
fp = fopen(filename, "wb");
|
|
|
|
if (!fp) {
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_NONFATAL, "Can't create EMAIL.DB");
|
2020-06-26 15:29:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
sprintf(filename, "%s/%s/NEXT.EMAIL", cfg_emaildir, mbox);
|
|
|
|
fp = fopen(filename, "wb");
|
|
|
|
if (!fp) {
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_NONFATAL, "Can't create NEXT.EMAIL");
|
2020-06-26 15:29:45 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
fprintf(fp, "1");
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
|
2020-06-26 15:00:11 +00:00
|
|
|
/*
|
|
|
|
* Change current mailbox
|
|
|
|
*/
|
2020-06-27 00:28:41 +00:00
|
|
|
void switch_mailbox(char *mbox) {
|
2020-06-26 23:30:06 +00:00
|
|
|
char prev_mbox[80];
|
|
|
|
uint8_t err;
|
|
|
|
strcpy(prev_mbox, curr_mbox);
|
2020-06-26 15:00:11 +00:00
|
|
|
strcpy(curr_mbox, mbox);
|
|
|
|
first_msg = 1;
|
2020-06-26 23:30:06 +00:00
|
|
|
err = read_email_db(first_msg, 1, 1); // Errors non-fatal
|
|
|
|
if (err) {
|
|
|
|
strcpy(curr_mbox, prev_mbox);
|
|
|
|
return;
|
|
|
|
}
|
2020-06-26 15:00:11 +00:00
|
|
|
selection = 1;
|
|
|
|
email_summary();
|
|
|
|
}
|
|
|
|
|
2020-06-26 17:44:21 +00:00
|
|
|
/*
|
|
|
|
* Purge deleted messages from current mailbox
|
|
|
|
*/
|
|
|
|
void purge_deleted(void) {
|
2020-06-27 06:53:46 +00:00
|
|
|
uint16_t count = 0, delcount = 0;
|
|
|
|
struct emailhdrs *h;
|
|
|
|
FILE *fp2;
|
|
|
|
uint16_t l;
|
|
|
|
h = (struct emailhdrs*)malloc(sizeof(struct emailhdrs));
|
|
|
|
if (!h)
|
|
|
|
error(ERR_FATAL, "Can't malloc()");
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, curr_mbox);
|
|
|
|
fp = fopen(filename, "rb");
|
|
|
|
if (!fp) {
|
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB.NEW", cfg_emaildir, curr_mbox);
|
|
|
|
fp2 = fopen(filename, "wb");
|
|
|
|
if (!fp2) {
|
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
l = fread(h, 1, EMAILHDRS_SZ_ON_DISK, fp);
|
|
|
|
++count;
|
|
|
|
if (l != EMAILHDRS_SZ_ON_DISK)
|
|
|
|
goto done;
|
|
|
|
if (h->status == 'D') {
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.%u", cfg_emaildir, curr_mbox, h->emailnum);
|
|
|
|
if (unlink(filename)) {
|
|
|
|
error(ERR_NONFATAL, "Can't delete %s", filename);
|
|
|
|
}
|
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (l = 0; l < PROMPT_ROW - 1; ++l)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
|
|
|
putchar(0x1a); // CLEAR LINE
|
|
|
|
printf("%u msgs deleted", ++delcount);
|
|
|
|
} else {
|
|
|
|
l = fwrite(h, 1, EMAILHDRS_SZ_ON_DISK, fp2);
|
|
|
|
if (l != EMAILHDRS_SZ_ON_DISK) {
|
|
|
|
error(ERR_NONFATAL, "Can't write to %s", filename);
|
|
|
|
free(h);
|
|
|
|
fclose(fp);
|
|
|
|
fclose(fp2);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
free(h);
|
|
|
|
fclose(fp);
|
|
|
|
fclose(fp2);
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, curr_mbox);
|
|
|
|
if (unlink(filename)) {
|
|
|
|
error(ERR_NONFATAL, "Can't delete %s", filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sprintf(userentry, "%s/%s/EMAIL.DB.NEW", cfg_emaildir, curr_mbox);
|
|
|
|
if (rename(userentry, filename)) {
|
|
|
|
error(ERR_NONFATAL, "Can't rename %s", userentry);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
read_email_db(first_msg, 1, 0);
|
|
|
|
email_summary();
|
2020-06-26 17:44:21 +00:00
|
|
|
}
|
|
|
|
|
2020-06-28 01:00:50 +00:00
|
|
|
/*
|
|
|
|
* Get next email number from NEXT.EMAIL
|
|
|
|
* Returns 1 on error, 0 if all is good
|
|
|
|
*/
|
|
|
|
uint8_t get_next_email(char *mbox, uint16_t *num) {
|
|
|
|
sprintf(filename, "%s/%s/NEXT.EMAIL", cfg_emaildir, mbox);
|
|
|
|
fp = fopen(filename, "rb");
|
|
|
|
if (!fp) {
|
|
|
|
error(ERR_NONFATAL, "Can't open %s/NEXT.EMAIL", mbox);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
fscanf(fp, "%u", num);
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update NEXT.EMAIL file
|
|
|
|
*/
|
|
|
|
uint8_t update_next_email(char *mbox, uint16_t num) {
|
|
|
|
sprintf(filename, "%s/%s/NEXT.EMAIL", cfg_emaildir, mbox);
|
|
|
|
fp = fopen(filename, "wb");
|
|
|
|
if (!fp) {
|
|
|
|
error(ERR_NONFATAL, "Can't open %s/NEXT.EMAIL", mbox);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
fprintf(fp, "%u", num);
|
|
|
|
fclose(fp);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-28 04:25:49 +00:00
|
|
|
/*
|
|
|
|
* Copy string p to q truncating any trailing spaces.
|
|
|
|
*/
|
|
|
|
void truncate_header(char *p, char *q, uint8_t l) {
|
|
|
|
int16_t last_char = -1;
|
|
|
|
uint8_t i;
|
|
|
|
for (i = 0; i < l; ++i) {
|
|
|
|
q[i] = p[i];
|
|
|
|
if ((p[i] != ' ') && (p[i] != '\0'))
|
|
|
|
last_char = i;
|
|
|
|
}
|
|
|
|
q[last_char + 1] = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write email headers for replies and forwarded messages
|
|
|
|
* fp1 - File handle of the mail message being replied/forwarded
|
|
|
|
* fp2 - File handle of the destination mail message
|
|
|
|
* h - headers of the message being replied/forwarded
|
|
|
|
* mode - 'R' for reply, 'F' for forward
|
|
|
|
*/
|
|
|
|
void write_email_headers(FILE *fp1, FILE *fp2, struct emailhdrs *h, char mode) {
|
|
|
|
fprintf(fp2, "From: %s\r", cfg_emailaddr);
|
|
|
|
truncate_header(h->subject, buf, 80);
|
|
|
|
fprintf(fp2, "Subject: %s: %s\r", (mode == 'F' ? "Fwd" : "Re"), buf);
|
|
|
|
fprintf(fp2, "Date: TODO: put date in here!!\r"); // TODO
|
|
|
|
truncate_header(h->from, buf, 80);
|
|
|
|
fprintf(fp2, "To: %s\r", (mode == 'R' ? buf : ""), buf);
|
|
|
|
fprintf(fp2, "cc: \r");
|
|
|
|
fprintf(fp2, "X-Mailer: %s - Apple ][ Forever!\r\r", PROGNAME);
|
|
|
|
if (mode == 'R') {
|
|
|
|
truncate_header(h->date, buf, 40);
|
|
|
|
fprintf(fp2, "On %s, ", buf);
|
|
|
|
truncate_header(h->from, buf, 80);
|
|
|
|
fprintf(fp2, "%s wrote:\r\r", buf);
|
|
|
|
} else {
|
|
|
|
fprintf(fp2, "-------- Forwarded Message --------\n");
|
|
|
|
truncate_header(h->subject, buf, 80);
|
|
|
|
fprintf(fp2, "Subject: %s\r", buf);
|
|
|
|
truncate_header(h->date, buf, 40);
|
|
|
|
fprintf(fp2, "Date: %s\r", buf);
|
|
|
|
truncate_header(h->from, buf, 80);
|
|
|
|
fprintf(fp2, "From: %s\r", buf);
|
|
|
|
truncate_header(h->to, buf, 80);
|
|
|
|
fprintf(fp2, "To: %s\r\r", buf);
|
|
|
|
}
|
|
|
|
fseek(fp1, h->skipbytes, SEEK_SET); // Skip headers when copying
|
|
|
|
}
|
|
|
|
|
2020-06-26 17:44:21 +00:00
|
|
|
/*
|
2020-06-27 03:27:59 +00:00
|
|
|
* Copies the current message to mailbox mbox.
|
|
|
|
* h is a pointer to the emailheaders for the message to copy
|
|
|
|
* idx is the index of the message in EMAIL.DB in the source mailbox (1-based)
|
|
|
|
* mbox is the name of the destination mailbox
|
2020-06-28 04:25:49 +00:00
|
|
|
* delete - if set to 1 then the message will be marked as deleted in the
|
|
|
|
* source mbox
|
|
|
|
* mode - 'R' for reply, 'F' for forward, otherwise ' '
|
2020-06-26 17:44:21 +00:00
|
|
|
*/
|
2020-06-28 04:25:49 +00:00
|
|
|
void copy_to_mailbox(struct emailhdrs *h, uint16_t idx,
|
|
|
|
char *mbox, uint8_t delete, char mode) {
|
2020-06-27 06:53:46 +00:00
|
|
|
uint16_t num, buflen, written, l;
|
2020-06-26 17:44:21 +00:00
|
|
|
FILE *fp2;
|
|
|
|
|
|
|
|
// Read next number from dest/NEXT.EMAIL
|
2020-06-28 01:00:50 +00:00
|
|
|
if (get_next_email(mbox, &num))
|
2020-06-26 17:44:21 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Open source email file
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.%u", cfg_emaildir, curr_mbox, h->emailnum);
|
|
|
|
fp = fopen(filename, "rb");
|
|
|
|
if (!fp) {
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
2020-06-26 17:44:21 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open destination email file
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.%u", cfg_emaildir, mbox, num);
|
|
|
|
fp2 = fopen(filename, "wb");
|
|
|
|
if (!fp2) {
|
2020-06-27 03:27:59 +00:00
|
|
|
fclose(fp);
|
2020-06-26 23:17:05 +00:00
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
2020-06-26 17:44:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-06-28 04:25:49 +00:00
|
|
|
|
|
|
|
if (mode != ' ')
|
|
|
|
write_email_headers(fp, fp2, h, mode);
|
|
|
|
|
|
|
|
// Make sure spinner is in the right place
|
|
|
|
if ((mode == 'R') || (mode == 'F')) {
|
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (l = 0; l < PROMPT_ROW - 1; ++l)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:57:38 +00:00
|
|
|
// Copy email
|
|
|
|
putchar(' '); // For spinner
|
2020-06-26 18:12:58 +00:00
|
|
|
while (1) {
|
|
|
|
buflen = fread(buf, 1, READSZ, fp);
|
2020-06-26 21:57:38 +00:00
|
|
|
spinner();
|
2020-06-26 18:12:58 +00:00
|
|
|
if (buflen == 0)
|
|
|
|
break;
|
|
|
|
written = fwrite(buf, 1, buflen, fp2);
|
|
|
|
if (written != buflen) {
|
2020-06-28 04:25:49 +00:00
|
|
|
error(ERR_NONFATAL, "Write error during copy");
|
2020-06-26 18:12:58 +00:00
|
|
|
fclose(fp);
|
|
|
|
fclose(fp2);
|
|
|
|
return;
|
2020-06-26 17:44:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-06-26 21:57:38 +00:00
|
|
|
putchar(0x08); // Erase spinner
|
|
|
|
putchar(' ');
|
|
|
|
putchar(0x08);
|
2020-06-26 18:12:58 +00:00
|
|
|
|
2020-06-26 17:44:21 +00:00
|
|
|
fclose(fp);
|
|
|
|
fclose(fp2);
|
|
|
|
|
|
|
|
// Update dest/EMAIL.DB
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, mbox);
|
|
|
|
fp = fopen(filename, "ab");
|
|
|
|
if (!fp) {
|
2020-06-27 06:53:46 +00:00
|
|
|
error(ERR_NONFATAL, "Can't open %s/EMAIL.DB", mbox);
|
2020-06-26 17:44:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-06-26 18:12:58 +00:00
|
|
|
buflen = h->emailnum; // Just reusing buflen as a temporary
|
2020-06-26 17:44:21 +00:00
|
|
|
h->emailnum = num;
|
2020-06-27 06:53:46 +00:00
|
|
|
l = fwrite(h, 1, EMAILHDRS_SZ_ON_DISK, fp);
|
|
|
|
if (l != EMAILHDRS_SZ_ON_DISK) {
|
|
|
|
error(ERR_NONFATAL, "Can't write to %s/EMAIL.DB %u %u", mbox);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h->emailnum = buflen ;
|
2020-06-26 17:44:21 +00:00
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
// Update dest/NEXT.EMAIL, incrementing count by 1
|
2020-06-28 01:00:50 +00:00
|
|
|
if (update_next_email(mbox, num + 1))
|
2020-06-26 17:44:21 +00:00
|
|
|
return;
|
|
|
|
|
2020-06-27 03:27:59 +00:00
|
|
|
if (delete)
|
2020-06-26 17:44:21 +00:00
|
|
|
h->status = 'D';
|
2020-06-27 03:27:59 +00:00
|
|
|
h->tag = ' '; // Untag files after copy or move
|
|
|
|
write_updated_headers(h, idx);
|
|
|
|
email_summary_for(selection);
|
2020-06-28 04:25:49 +00:00
|
|
|
|
|
|
|
if (mode != ' ') {
|
|
|
|
// Not really an error but useful to have an alert
|
|
|
|
sprintf(filename, "Created %s %s/OUTBOX/EMAIL.%u",
|
|
|
|
(mode == 'R' ? "reply" : "fwded msg"), cfg_emaildir, num);
|
|
|
|
error(ERR_NONFATAL, filename);
|
|
|
|
}
|
2020-06-26 17:44:21 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 21:57:38 +00:00
|
|
|
/*
|
|
|
|
* Prompt ok?
|
|
|
|
*/
|
2020-06-27 03:27:59 +00:00
|
|
|
char prompt_okay(char *msg) {
|
2020-06-26 21:57:38 +00:00
|
|
|
uint16_t i;
|
|
|
|
char c;
|
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (i = 0; i < PROMPT_ROW - 1; ++i)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
2020-06-27 03:27:59 +00:00
|
|
|
printf("%sSure? (y/n)", msg);
|
2020-06-26 21:57:38 +00:00
|
|
|
while (1) {
|
|
|
|
c = cgetc();
|
|
|
|
if ((c == 'y') || (c == 'Y') || (c == 'n') || (c == 'N'))
|
|
|
|
break;
|
|
|
|
putchar(7); // BELL
|
|
|
|
}
|
|
|
|
if ((c == 'y') || (c == 'Y'))
|
|
|
|
c = 1;
|
|
|
|
else
|
|
|
|
c = 0;
|
|
|
|
putchar(0x1a); // CLEAR LINE
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
2020-06-27 03:27:59 +00:00
|
|
|
/*
|
|
|
|
* Check if there are tagged messages. If not, just call copy_to_mailbox()
|
|
|
|
* on the current message. If they are, prompt the user and, if affirmative,
|
|
|
|
* iterate through the tagged messages calling copy_to_mailbox() on each.
|
|
|
|
*/
|
|
|
|
uint8_t copy_to_mailbox_tagged(char *mbox, uint8_t delete) {
|
2020-06-27 03:42:03 +00:00
|
|
|
uint16_t count = 0, tagcount = 0;
|
2020-06-27 03:27:59 +00:00
|
|
|
struct emailhdrs *h;
|
|
|
|
uint16_t l;
|
|
|
|
if (total_tag == 0) {
|
|
|
|
h = get_headers(selection);
|
2020-06-28 04:25:49 +00:00
|
|
|
copy_to_mailbox(h, first_msg + selection - 1, mbox, delete, ' ');
|
2020-06-27 03:27:59 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2020-06-27 06:53:46 +00:00
|
|
|
sprintf(filename, "%u tagged - ", total_tag);
|
2020-06-27 03:27:59 +00:00
|
|
|
if (!prompt_okay(filename))
|
|
|
|
return 0;
|
|
|
|
h = (struct emailhdrs*)malloc(sizeof(struct emailhdrs));
|
|
|
|
if (!h)
|
|
|
|
error(ERR_FATAL, "Can't malloc()");
|
|
|
|
while (1) {
|
|
|
|
sprintf(filename, "%s/%s/EMAIL.DB", cfg_emaildir, curr_mbox);
|
|
|
|
fp = fopen(filename, "rb+");
|
|
|
|
if (!fp) {
|
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (fseek(fp, count * EMAILHDRS_SZ_ON_DISK, SEEK_SET)) {
|
|
|
|
error(ERR_NONFATAL, "Can't seek in %s", filename);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
l = fread(h, 1, EMAILHDRS_SZ_ON_DISK, fp);
|
|
|
|
fclose(fp);
|
|
|
|
++count;
|
|
|
|
if (l != EMAILHDRS_SZ_ON_DISK) {
|
|
|
|
free(h);
|
|
|
|
read_email_db(first_msg, 1, 0);
|
|
|
|
email_summary();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (h->tag == 'T') {
|
2020-06-27 03:47:05 +00:00
|
|
|
h->tag = ' '; // Don't want it tagged in the destination
|
2020-06-27 03:42:03 +00:00
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (l = 0; l < PROMPT_ROW - 1; ++l)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
|
|
|
putchar(0x1a); // CLEAR LINE
|
|
|
|
printf("%u/%u:", ++tagcount, total_tag);
|
2020-06-28 04:25:49 +00:00
|
|
|
copy_to_mailbox(h, count, mbox, delete, ' ');
|
2020-06-27 03:27:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
err:
|
|
|
|
free(h);
|
|
|
|
fclose(fp);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2020-06-26 16:09:20 +00:00
|
|
|
/*
|
|
|
|
* Prompt for a name in the line below the menu, store it in userentry
|
2020-06-26 21:57:38 +00:00
|
|
|
* Returns number of chars read.
|
2020-06-26 16:09:20 +00:00
|
|
|
*/
|
2020-06-26 21:57:38 +00:00
|
|
|
uint8_t prompt_for_name(void) {
|
2020-06-26 16:09:20 +00:00
|
|
|
uint16_t i;
|
|
|
|
char c;
|
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (i = 0; i < PROMPT_ROW - 1; ++i)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
|
|
|
printf(">>>");
|
|
|
|
i = 0;
|
|
|
|
while (1) {
|
|
|
|
c = cgetc();
|
2020-06-26 21:57:38 +00:00
|
|
|
if (!isalnum(c) && (c != 0x0d) && (c != 0x08) && (c != 0x7f) && (c != '.')) {
|
2020-06-26 16:09:20 +00:00
|
|
|
putchar(7); // BELL
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
switch (c) {
|
|
|
|
case 0x0d: // RETURN KEY
|
|
|
|
goto done;
|
|
|
|
case 0x08: // BACKSPACE
|
2020-06-26 21:57:38 +00:00
|
|
|
case 0x7f: // DELETE
|
|
|
|
if (i > 0) {
|
|
|
|
putchar(0x08);
|
|
|
|
putchar(' ');
|
|
|
|
putchar(0x08);
|
|
|
|
--i;
|
|
|
|
} else
|
|
|
|
putchar(7); // BELL
|
2020-06-26 16:09:20 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
putchar(c);
|
|
|
|
userentry[i++] = c;
|
|
|
|
}
|
|
|
|
if (i == 79)
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
done:
|
|
|
|
userentry[i] = '\0';
|
|
|
|
putchar(0x1a); // CLEAR LINE
|
2020-06-26 21:57:38 +00:00
|
|
|
putchar(0x19); // HOME
|
2020-06-26 23:17:05 +00:00
|
|
|
for (c = 0; c < PROMPT_ROW - 1; ++c)
|
2020-06-26 21:57:38 +00:00
|
|
|
putchar(0x0a); // CURSOR DOWN
|
|
|
|
return i;
|
2020-06-26 16:09:20 +00:00
|
|
|
}
|
|
|
|
|
2020-06-28 01:00:50 +00:00
|
|
|
/*
|
|
|
|
* Create a blank outgoing message and put it in OUTBOX.
|
|
|
|
* OUTBOX is not a 'proper' mailbox (no EMAIL.DB)
|
|
|
|
*/
|
|
|
|
void create_blank_outgoing() {
|
|
|
|
uint16_t num;
|
|
|
|
|
|
|
|
// Read next number from dest/NEXT.EMAIL
|
2020-06-28 04:25:49 +00:00
|
|
|
if (get_next_email("OUTBOX", &num))
|
2020-06-28 01:00:50 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Open destination email file
|
2020-06-28 04:25:49 +00:00
|
|
|
sprintf(filename, "%s/OUTBOX/EMAIL.%u", cfg_emaildir, num);
|
2020-06-28 01:00:50 +00:00
|
|
|
fp = fopen(filename, "wb");
|
|
|
|
if (!fp) {
|
|
|
|
error(ERR_NONFATAL, "Can't open %s", filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(fp, "From: %s\n", cfg_emailaddr);
|
|
|
|
fprintf(fp, "Subject: \n");
|
2020-06-28 04:25:49 +00:00
|
|
|
fprintf(fp, "Date: TODO: put date in here!!\n"); // TODO
|
2020-06-28 01:00:50 +00:00
|
|
|
fprintf(fp, "To: \n");
|
|
|
|
fprintf(fp, "cc: \n\n");
|
|
|
|
fclose(fp);
|
|
|
|
|
|
|
|
// Update dest/NEXT.EMAIL, incrementing count by 1
|
2020-06-28 04:25:49 +00:00
|
|
|
if (update_next_email("OUTBOX", num + 1))
|
2020-06-28 01:00:50 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Not really an error but useful to have an alert
|
2020-06-28 04:25:49 +00:00
|
|
|
sprintf(filename, "Created file %s/OUTBOX/EMAIL.%u", cfg_emaildir, num);
|
2020-06-28 01:00:50 +00:00
|
|
|
error(ERR_NONFATAL, filename);
|
|
|
|
}
|
|
|
|
|
2020-06-25 00:25:31 +00:00
|
|
|
/*
|
|
|
|
* Keyboard handler
|
|
|
|
*/
|
|
|
|
void keyboard_hdlr(void) {
|
2020-06-26 01:47:03 +00:00
|
|
|
struct emailhdrs *h;
|
2020-06-26 23:30:06 +00:00
|
|
|
uint8_t i;
|
2020-06-25 00:25:31 +00:00
|
|
|
while (1) {
|
|
|
|
char c = cgetc();
|
|
|
|
switch (c) {
|
|
|
|
case 'k':
|
|
|
|
case 'K':
|
|
|
|
case 0xb: // UP-ARROW
|
|
|
|
if (selection > 1) {
|
|
|
|
prevselection = selection;
|
|
|
|
--selection;
|
|
|
|
update_highlighted();
|
2020-06-26 01:47:03 +00:00
|
|
|
} else if (first_msg > MSGS_PER_PAGE) {
|
|
|
|
first_msg -= MSGS_PER_PAGE;
|
2020-06-26 23:30:06 +00:00
|
|
|
read_email_db(first_msg, 0, 0);
|
2020-06-26 01:47:03 +00:00
|
|
|
selection = num_msgs;
|
|
|
|
email_summary();
|
2020-06-25 00:25:31 +00:00
|
|
|
}
|
|
|
|
break;
|
2020-06-27 03:47:05 +00:00
|
|
|
case 't':
|
|
|
|
case 'T':
|
|
|
|
h = get_headers(selection);
|
|
|
|
if (h) {
|
|
|
|
if (h->tag == 'T') {
|
|
|
|
h->tag = ' ';
|
|
|
|
--total_tag;
|
|
|
|
} else {
|
|
|
|
h->tag = 'T';
|
|
|
|
++total_tag;
|
|
|
|
}
|
|
|
|
write_updated_headers(h, first_msg + selection - 1);
|
|
|
|
email_summary_for(selection);
|
|
|
|
status_bar();
|
|
|
|
}
|
|
|
|
// Fallthrough so tagging also moves down!!!
|
2020-06-25 00:25:31 +00:00
|
|
|
case 'j':
|
|
|
|
case 'J':
|
|
|
|
case 0xa: // DOWN-ARROW
|
|
|
|
if (selection < num_msgs) {
|
|
|
|
prevselection = selection;
|
|
|
|
++selection;
|
|
|
|
update_highlighted();
|
2020-06-26 01:47:03 +00:00
|
|
|
} else if (first_msg + selection + 1 < total_msgs) {
|
|
|
|
first_msg += MSGS_PER_PAGE;
|
2020-06-26 23:30:06 +00:00
|
|
|
read_email_db(first_msg, 0, 0);
|
2020-06-26 01:47:03 +00:00
|
|
|
selection = 1;
|
|
|
|
email_summary();
|
2020-06-25 00:25:31 +00:00
|
|
|
}
|
|
|
|
break;
|
2020-06-26 16:09:20 +00:00
|
|
|
case 0x0d: // RETURN KEY
|
2020-06-25 03:08:40 +00:00
|
|
|
case ' ':
|
2020-06-26 03:18:23 +00:00
|
|
|
h = get_headers(selection);
|
|
|
|
if (h) {
|
|
|
|
if (h->status == 'N')
|
|
|
|
--total_new;
|
|
|
|
h->status = 'R'; // Mark email read
|
|
|
|
write_updated_headers(h, first_msg + selection - 1);
|
|
|
|
}
|
2020-06-25 02:05:55 +00:00
|
|
|
email_pager();
|
|
|
|
email_summary();
|
|
|
|
break;
|
2020-06-26 01:47:03 +00:00
|
|
|
case 'd':
|
|
|
|
case 'D':
|
|
|
|
h = get_headers(selection);
|
2020-06-26 03:18:23 +00:00
|
|
|
if (h) {
|
2020-06-26 01:47:03 +00:00
|
|
|
h->status = 'D';
|
2020-06-26 03:18:23 +00:00
|
|
|
write_updated_headers(h, first_msg + selection - 1);
|
|
|
|
email_summary_for(selection);
|
|
|
|
}
|
2020-06-26 01:47:03 +00:00
|
|
|
break;
|
|
|
|
case 'u':
|
|
|
|
case 'U':
|
|
|
|
h = get_headers(selection);
|
2020-06-26 03:18:23 +00:00
|
|
|
if (h) {
|
2020-06-26 01:47:03 +00:00
|
|
|
h->status = 'R';
|
2020-06-26 03:18:23 +00:00
|
|
|
write_updated_headers(h, first_msg + selection - 1);
|
|
|
|
email_summary_for(selection);
|
|
|
|
}
|
2020-06-26 01:47:03 +00:00
|
|
|
break;
|
2020-06-26 17:44:21 +00:00
|
|
|
case 'c':
|
|
|
|
case 'C':
|
2020-06-26 21:57:38 +00:00
|
|
|
if (prompt_for_name())
|
2020-06-27 03:27:59 +00:00
|
|
|
copy_to_mailbox_tagged(userentry, 0);
|
2020-06-26 17:44:21 +00:00
|
|
|
break;
|
|
|
|
case 'm':
|
|
|
|
case 'M':
|
2020-06-26 21:57:38 +00:00
|
|
|
if (prompt_for_name())
|
2020-06-27 03:27:59 +00:00
|
|
|
copy_to_mailbox_tagged(userentry, 1);
|
2020-06-26 17:44:21 +00:00
|
|
|
break;
|
|
|
|
case 'a':
|
|
|
|
case 'A':
|
2020-06-26 23:30:06 +00:00
|
|
|
putchar(0x19); // HOME
|
|
|
|
for (i = 0; i < PROMPT_ROW - 1; ++i)
|
|
|
|
putchar(0x0a); // CURSOR DOWN
|
2020-06-27 03:27:59 +00:00
|
|
|
copy_to_mailbox_tagged("RECEIVED", 1);
|
2020-06-26 17:44:21 +00:00
|
|
|
break;
|
2020-06-26 01:47:03 +00:00
|
|
|
case 'p':
|
|
|
|
case 'P':
|
2020-06-27 06:53:46 +00:00
|
|
|
if (prompt_okay("Purge - "))
|
|
|
|
purge_deleted();
|
2020-06-26 01:47:03 +00:00
|
|
|
break;
|
2020-06-26 15:29:45 +00:00
|
|
|
case 'n':
|
|
|
|
case 'N':
|
2020-06-26 21:57:38 +00:00
|
|
|
if (prompt_for_name())
|
|
|
|
new_mailbox(userentry);
|
2020-06-26 15:30:30 +00:00
|
|
|
break;
|
2020-06-27 00:28:41 +00:00
|
|
|
case 's':
|
|
|
|
case 'S':
|
2020-06-26 21:57:38 +00:00
|
|
|
if (prompt_for_name())
|
2020-06-27 00:28:41 +00:00
|
|
|
switch_mailbox(userentry);
|
|
|
|
break;
|
|
|
|
case 'w':
|
|
|
|
case 'W':
|
2020-06-28 01:00:50 +00:00
|
|
|
create_blank_outgoing();
|
2020-06-27 00:28:41 +00:00
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
case 'R':
|
2020-06-28 04:25:49 +00:00
|
|
|
h = get_headers(selection);
|
|
|
|
copy_to_mailbox(h, first_msg + selection - 1, "OUTBOX", 0, 'R');
|
2020-06-27 00:28:41 +00:00
|
|
|
break;
|
|
|
|
case 'f':
|
|
|
|
case 'F':
|
2020-06-28 04:25:49 +00:00
|
|
|
h = get_headers(selection);
|
|
|
|
copy_to_mailbox(h, first_msg + selection - 1, "OUTBOX", 0, 'F');
|
2020-06-26 15:00:11 +00:00
|
|
|
break;
|
2020-06-25 00:25:31 +00:00
|
|
|
case 'q':
|
|
|
|
case 'Q':
|
2020-06-27 06:53:46 +00:00
|
|
|
if (prompt_okay("Quit - ")) {
|
2020-06-26 21:57:38 +00:00
|
|
|
clrscr();
|
|
|
|
exit(0);
|
|
|
|
}
|
2020-06-25 00:25:31 +00:00
|
|
|
default:
|
2020-06-25 02:05:55 +00:00
|
|
|
//printf("[%02x]", c);
|
2020-06-25 00:25:31 +00:00
|
|
|
putchar(7); // BELL
|
|
|
|
}
|
|
|
|
}
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void main(void) {
|
|
|
|
videomode(VIDEOMODE_80COL);
|
|
|
|
readconfigfile();
|
2020-06-26 01:47:03 +00:00
|
|
|
first_msg = 1;
|
2020-06-26 23:30:06 +00:00
|
|
|
read_email_db(first_msg, 1, 0);
|
2020-06-25 00:25:31 +00:00
|
|
|
selection = 1;
|
2020-06-24 21:22:35 +00:00
|
|
|
email_summary();
|
2020-06-25 00:25:31 +00:00
|
|
|
keyboard_hdlr();
|
2020-06-24 21:22:35 +00:00
|
|
|
}
|
|
|
|
|