ATTACHER: Major UI enhancements. Uses file_ui() (from EDIT.SYSTEM) now.

This commit is contained in:
Bobbi Webber-Manners 2020-09-03 23:13:44 -04:00
parent eab93bca09
commit 1e703aadd0

View File

@ -5,6 +5,7 @@
#include <conio.h>
#include <ctype.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@ -12,26 +13,72 @@
#include <unistd.h>
#include "email_common.h"
#define PROMPT_ROW 21
#define EOL '\r'
#define BELL 0x07
#define BACKSPACE 0x08
#define RETURN 0x0d
#define NORMAL 0x0e
#define INVERSE 0x0f
#define ESC 0x1b
#define DELETE 0x7f
#define NETBUFSZ 1500
#define LINEBUFSZ 1000 // According to RFC2822 Section 2.1.1 (998+CRLF)
#define READSZ 1024 // Must be less than NETBUFSZ to fit in buf[]
#define IOBUFSZ 4096
static unsigned char buf[NETBUFSZ+1]; // One extra byte for null terminator
static char linebuf[LINEBUFSZ];
static char userentry[80];
static uint8_t quit_to_email; // If 1, launch EMAIL.SYSTEM on quit
static char filename[80];
unsigned char buf[NETBUFSZ+1]; // One extra byte for null terminator
char linebuf[LINEBUFSZ];
char userentry[80];
uint8_t quit_to_email; // If 1, launch EMAIL.SYSTEM on quit
char filename[80];
char iobuf[IOBUFSZ];
#define ERR_NONFATAL 0
#define ERR_FATAL 1
/*
* Annoying beep
*/
void beep(void) {
uint8_t *p = (uint8_t*)0xc030; // Speaker
uint8_t junk;
uint16_t i;
for (i = 0; i < 200; ++i) {
junk = *p;
for (junk = 0; junk < 50; ++junk); // Reduce pitch
}
}
/*
* Clear to EOL
*/
void clreol(void) {
uint8_t x = wherex(), y = wherey();
cclear(80 - x);
gotoxy(x, y);
}
/*
* Clear line
*/
void clrline(void) {
uint8_t x = wherex();
gotox(0);
cclear(80);
gotox(x);
}
/*
* Put cursor at beginning of PROMPT_ROW
*/
void goto_prompt_row(void) {
gotoxy(0, PROMPT_ROW);
}
/*
* Show error messages
*/
@ -170,23 +217,28 @@ uint16_t encode_base64(char *p, char *q, uint16_t len) {
}
/*
* Prompt for a name, store it in userentry
* Prompt for a name in the bottom line of the screen
* Returns number of chars read.
* prompt - Message to display before > prompt
* is_file - if 1, restrict chars to those allowed in ProDOS filename
* Returns number of chars read
* Returns number of chars read, or 255 if ESC pressed
*/
uint8_t prompt_for_name(char *prompt, uint8_t is_file) {
uint16_t i;
char c;
printf("%s>", prompt);
cursor(0);
goto_prompt_row();
clreol();
revers(1);
cprintf("%s>", prompt);
revers(0);
gotox(2 + strlen(prompt));
i = 0;
while (1) {
c = cgetc();
if (is_file && !isalnum(c) &&
(c != RETURN) && (c != BACKSPACE) && (c != DELETE) &&
(c != '.') && (c != '/')) {
putchar(BELL);
if (is_file && !isalnum(c) && (c != RETURN) && (c != BACKSPACE) &&
(c != DELETE) && (c != ESC) && (c != '.') && (c != '/')) {
beep();
continue;
}
switch (c) {
@ -195,15 +247,19 @@ uint8_t prompt_for_name(char *prompt, uint8_t is_file) {
case BACKSPACE:
case DELETE:
if (i > 0) {
putchar(BACKSPACE);
putchar(' ');
putchar(BACKSPACE);
gotox(wherex() - 1);
cputc(' ');
gotox(wherex() - 1);
--i;
} else
putchar(BELL);
beep();
break;
case ESC:
userentry[0] = '\0';
i = 255;
goto esc_pressed;
default:
putchar(c);
cputc(c);
userentry[i++] = c;
}
if (i == 79)
@ -211,9 +267,294 @@ uint8_t prompt_for_name(char *prompt, uint8_t is_file) {
}
done:
userentry[i] = '\0';
esc_pressed:
clrline();
cursor(1);
return i;
}
/*
* Prompt ok?
* Returns 0 for yes
* 1 for no
* 2 for ESC
*/
char prompt_okay(char *msg) {
char c;
cursor(0);
goto_prompt_row();
clreol();
revers(1);
cprintf("%s (y/n/ESC)", msg);
revers(0);
while (1) {
c = cgetc();
if ((c == 'y') || (c == 'Y') || (c == 'n') || (c == 'N') || c == ESC)
break;
beep();
}
if ((c == 'y') || (c == 'Y'))
c = 0;
else if (c == ESC)
c = 2;
else
c = 1;
return c;
}
struct tabent {
char name[16];
uint8_t type;
uint32_t size;
} *entry;
#define FILELINES 16
/*
* Draw one line in file chooser UI
* i - index of file to draw
* first - index of first file on screen
* selected - index of currently selected file
* entries - total number of file entries in directory
*/
void file_ui_draw(uint16_t i, uint16_t first, uint16_t selected, uint16_t entries) {
struct tabent *entry;
gotoxy(5, i - first + 6);
if (i < entries) {
entry = (struct tabent*)iobuf + i;
if (entry->type == 0x0f) {
sprintf(userentry, "[ %s ] ", entry->name);
userentry[34] = '\0';
} else {
sprintf(userentry, " %s ", entry->name);
sprintf(&userentry[18], " %8lu ", entry->size);
switch (entry->type) {
case 0x04:
sprintf(&userentry[30], "TXT ");
break;
case 0x06:
sprintf(&userentry[30], "BIN ");
break;
case 0x19:
sprintf(&userentry[30], "ADB ");
break;
case 0x1a:
sprintf(&userentry[30], "AWP ");
break;
case 0x1b:
sprintf(&userentry[30], "ASP ");
break;
case 0xfc:
sprintf(&userentry[30], "BAS ");
break;
case 0xff:
sprintf(&userentry[30], "SYS ");
break;
default:
sprintf(&userentry[30], "$%02x ", entry->type);
}
}
if (i == selected)
revers(1);
cputs(userentry);
if (i == selected)
revers(0);
} else {
strcpy(userentry, " ");
cputs(userentry);
}
}
/*
* File chooser UI
* first - index of first file on screen
* selected - index of currently selected file
* entries - total number of file entries in directory
*/
void file_ui_draw_all(uint16_t first, uint16_t selected, uint16_t entries) {
uint16_t i;
uint16_t last = first + FILELINES;
for (i = first; i < last; ++i)
file_ui_draw(i, first, selected, entries);
}
/*
* Perform ProDOS MLI ON_LINE call to
* write all online volume names into iobuf[]
* Return the number of entries
*/
uint16_t online(void) {
uint16_t entries = 0;
struct tabent *entry;
uint8_t i, j, len;
__asm__("lda #$00"); // All devices
__asm__("sta mliparam + 1");
__asm__("lda #<%v", iobuf); // iobuf LSB
__asm__("sta mliparam + 2");
__asm__("lda #>%v", iobuf); // iobuf MSB
__asm__("ina"); // Bump up 256 bytes into iobuf
__asm__("sta mliparam + 3");
__asm__("lda #$c5"); // ON_LINE
__asm__("ldx #$02"); // Two parms
__asm__("jsr callmli");
entry = (struct tabent*)iobuf;
for (i = 0; i < 16; ++i) {
len = iobuf[256 + i * 16] & 0x0f;
if (len > 0) {
entry->type = 0x0f;
entry->name[0] = '/';
for (j = 0; j < len; ++j)
entry->name[j + 1] = iobuf[256 + i * 16 + j + 1];
entry->name[j + 1] = '\0';
++entry;
++entries;
}
}
return entries;
}
/*
* File chooser UI
* Leaves file name in userentry[], or empty string if error/cancel
* msg1 - Message for top line
* msg2 - Message for second line
* msg3 - Message for third line
*/
void file_ui(char *msg1, char *msg2, char *msg3) {
struct tabent *entry;
DIR *dp;
struct dirent *ent;
char c;
uint16_t entries, current, first;
uint8_t toplevel = 0;
restart:
clrscr();
gotoxy(0,0);
revers(1);
cprintf("%s", msg1);
revers(0);
gotoxy(0,1);
cprintf("%s", msg2);
gotoxy(0,2);
cprintf("%s", msg3);
getcwd(userentry, 80);
gotoxy(0,4);
revers(1);
cprintf("%s", (toplevel ? "Volumes" : userentry));
revers(0);
entries = current = first = 0;
if (toplevel) {
entries = online();
} else {
entry = (struct tabent*)iobuf;
strcpy(entry->name, ".."); // Add fake '..' entry
entry->type = 0x0f;
++entry;
++entries;
cursor(0);
dp = opendir(".");
while (1) {
ent = readdir(dp);
if (!ent)
break;
memcpy(entry->name, ent->d_name, 16);
entry->type = ent->d_type;
entry->size = ent->d_size;
++entry;
++entries;
if ((char*)entry > (char*)iobuf + IOBUFSZ - 100) {
beep();
break;
}
}
closedir(dp);
}
redraw:
file_ui_draw_all(first, current, entries);
while (1) {
c = cgetc();
switch (c) {
case 0x0b: // Up
if (current > 0)
--current;
if (current < first) {
if (first > FILELINES)
first -= FILELINES;
else
first = 0;
goto redraw;
}
file_ui_draw(current, first, current, entries);
file_ui_draw(current + 1, first, current, entries);
break;
case 0x0a: // Down
if (current < entries - 1)
++current;
if (current >= first + FILELINES) {
first += FILELINES;
goto redraw;
}
file_ui_draw(current - 1, first, current, entries);
file_ui_draw(current, first, current, entries);
break;
case EOL:
entry = (struct tabent*)iobuf + current;
switch (entry->type) {
case 0x0f: // Directory
getcwd(userentry, 80);
if (strcmp(entry->name, "..") == 0) {
for (c = strlen(userentry); c > 0; --c)
if (userentry[c] == '/') {
userentry[c] = '\0';
break;
}
if (c <= 1)
toplevel = 1;
else
chdir(userentry);
goto restart;
} else {
if (toplevel) {
strcpy(userentry, entry->name);
chdir(userentry);
} else {
getcwd(userentry, 80);
strcat(userentry, "/");
strcat(userentry, entry->name);
chdir(userentry);
}
toplevel = 0;
goto restart;
}
break;
default: // All other file types
strcpy(userentry, entry->name);
goto done;
break;
}
break;
case ESC:
strcpy(userentry, "");
goto done;
break;
case 0x09: // Tab
if (prompt_for_name("Enter filename", 0) == 255)
goto restart; // ESC pressed
if (userentry[0] == '/') // Absolute path
goto done;
getcwd(linebuf, 80); // Otherwise relative path
strcat(linebuf, "/");
strcat(linebuf, userentry);
strcpy(userentry, linebuf);
strcpy(linebuf, "");
goto done;
}
}
done:
clrscr();
cursor(1);
}
/*
* Optionally attach files to outgoing email.
* filename - Name of file containing email message
@ -222,7 +563,8 @@ void attach(char *fname) {
FILE *fp, *fp2, *destfp;
uint16_t chars, i;
uint32_t size;
char *s;
char *s, c;
uint8_t attachcount = 0;
videomode(VIDEOMODE_80COL);
printf("%c%s ATTACHER%c\n\n", 0x0f, PROGNAME, 0x0e);
fp = fopen(fname, "rb+");
@ -233,7 +575,7 @@ void attach(char *fname) {
if (!destfp)
error(ERR_FATAL, "Can't open TMPFILE");
printf("Copying email content ... "); // Space is for spinner to eat
printf(" Copying email content ... "); // Space is for spinner to eat
size = 0;
while ((chars = get_line(fp, linebuf)) != -1) {
size += chars;
@ -255,28 +597,55 @@ void attach(char *fname) {
}
spinner(size, 1);
printf("\rEnter the filename or filenames to attach to the email.\n");
printf("An empty entry means you are done attaching files.\n\n");
while (prompt_for_name("File to attach", 1)) {
while (1) {
cursor(0);
if (attachcount == 1)
printf("\n There is currently 1 attachment.\n\n", attachcount);
else
printf("\n There are currently %u attachments.\n\n", attachcount);
goto_prompt_row();
printf("%c A)dd attachment | D)one with attachments | %c", INVERSE, NORMAL);
printf("%c | | %c", INVERSE, NORMAL);
ask:
c = cgetc();
if ((c == 'D') || (c == 'd'))
goto done;
if ((c != 'A') && (c != 'a')) {
beep();
goto ask;
}
sprintf(userentry, "Attachment #%u : Select a File to Attach", ++attachcount);
file_ui(userentry,
"",
" Select file from tree browser, or [Tab] to enter filename. [Esc] cancels.");
printf("%c%s ATTACHER%c\n\n", 0x0f, PROGNAME, 0x0e);
if (strlen(userentry) == 0) {
beep();
printf("No file was selected!\n");
continue;
}
s = strrchr(userentry, '/');
if (!s)
s = userentry;
else
s = s + 1; // Character after the slash
if (strlen(s) == 0) {
error(ERR_NONFATAL, "Illegal trailing /");
beep();
printf("Illegal trailing slash!\n");
continue;
}
fp2 = fopen(userentry, "rb");
if (!fp2) {
error(ERR_NONFATAL, "Can't open %s", userentry);
beep();
sprintf(userentry, "Can't open %s!\n", userentry);
printf(userentry);
continue;
}
fprintf(destfp, "\r--a2forever\r");
fprintf(destfp, "Content-Type: application/octet-stream\r");
fprintf(destfp, "Content-Transfer-Encoding: base64\r");
fprintf(destfp, "Content-Disposition: attachment; filename=%s;\r\r", s);
printf("\r "); // Space is for spinner to eat
printf(" Attaching '%s' ... ", userentry); // Space is for spinner to eat
size = 0;
do {
i = fread(buf, 1, 72 * 3 / 4 * 5, fp2); // Multiple of 72*3/4 bytes
@ -290,6 +659,7 @@ void attach(char *fname) {
fclose(fp2);
spinner(size, 1);
}
done:
fprintf(destfp, "\r--a2forever--\r");
fclose(fp);
fclose(destfp);
@ -300,16 +670,14 @@ void attach(char *fname) {
}
/*
* Load EMAIL.SYSTEM to $2000 and jump to it
* (This code is in language card space so it can't possibly be trashed)
* Exec EMAIL.SYSTEM
*/
#pragma code-name (push, "LC")
void load_email(void) {
revers(0);
clrscr();
exec("EMAIL.SYSTEM", NULL); // Assume it is in current directory
chdir(cfg_instdir);
exec("EMAIL.SYSTEM", NULL);
}
#pragma code-name (pop)
void main(int argc, char *argv[]) {
readconfigfile();