Added download to disk feature.

Beside the code for the actual feature this change introduces analysis of the URL to extract a potential filename to be used for saving. Doing so was a prerequisite for disk download because the filename extension in the URL is the only hint for distinguishing ProDOS sector order disk images from DOS 3.3 sector order images.

So far only the URL input was potentially repeated. Now the file input is potentially repeated as well. For normal file paths there's deliberately still no check. But when entering a drive specifier ("!Sx,Dx") then it is checked for plausibility - and there's an additional confirmation question to keep the user from overwriting the "wrong" disk.

With now having both the URL and file input potentially repeated it became clear that loading and saving the input history shouldn't be repeated - and if the user quits without having entered a valid input the history deliberately isn't saved at all.
This commit is contained in:
Oliver Schmidt 2018-08-06 20:33:32 +02:00
parent 7ac8c55603
commit e7f0b6759b

View File

@ -1,4 +1,6 @@
#include <dio.h>
#include <cc65.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <conio.h>
@ -18,7 +20,9 @@
#pragma optimize (on)
#pragma static-locals (on)
// Size needs to be exactly one track of a 16-sector disk
char buffer[0x1000];
char name[16];
void ip65_error_exit(bool quit)
{
@ -55,6 +59,12 @@ void file_error_exit(void)
exit(EXIT_FAILURE);
}
void dio_error_exit(void)
{
_seterrno(_osmaperrno(_oserror));
file_error_exit();
}
void confirm_exit(void)
{
printf("\nPress any key ");
@ -102,78 +112,109 @@ void url_completion(const char *line, linenoiseCompletions *lc)
}
void file_completion(const char *line, linenoiseCompletions *lc) {
char *lineptr = strrchr(line, '/');
char *lineptr;
DIR *dir;
struct dirent *ent;
char *bufferptr;
// Add devices
if (line[0] == '!')
{
char device = getfirstdevice();
while (device != INVALID_DEVICE)
{
dhandle_t dio = dio_open(device);
if (dio)
{
// dio_query_sectcount() fails if there's no (formatted) disk
// in the drive but (in contrast to getdevicedir) it succeeds
// with a non-ProDOS 16-sector 140k disk which is exactly the
// check we want here.
if (dio_query_sectcount(dio))
{
sprintf(buffer, "!S%d,D%d", device & 7, (device >> 3) + 1);
if (match(line, buffer))
{
linenoiseAddCompletion(lc, buffer);
}
}
dio_close(dio);
}
device = getnextdevice(device);
}
return;
}
lineptr = strrchr(line, '/');
// Add device names
if (lineptr == line)
{
unsigned char disk = getfirstdevice();
while (disk != INVALID_DEVICE)
char device = getfirstdevice();
while (device != INVALID_DEVICE)
{
if (getdevicedir(disk, buffer, sizeof(buffer)))
if (getdevicedir(device, buffer, sizeof(buffer)))
{
if (match(line, buffer))
{
linenoiseAddCompletion(lc, buffer);
}
}
disk = getnextdevice(disk);
device = getnextdevice(device);
}
return;
}
// Add directory entries
//
// Absolute or relative path
if (lineptr)
{
*lineptr = '\0';
dir = opendir(line);
*lineptr = '/';
++lineptr;
}
// Current directory
else
{
DIR *dir;
struct dirent *ent;
char *bufferptr;
// Absolute or relative path
if (lineptr)
{
*lineptr = '\0';
dir = opendir(line);
*lineptr = '/';
++lineptr;
}
// Current directory
else
{
dir = opendir(".");
lineptr = (char*)line;
}
if (!dir)
{
return;
}
strcpy(buffer, line);
bufferptr = buffer + (lineptr - line);
while (ent = readdir(dir))
{
if (match(lineptr, ent->d_name))
{
strcpy(bufferptr, ent->d_name);
linenoiseAddCompletion(lc, buffer);
}
}
closedir(dir);
dir = opendir(".");
lineptr = (char*)line;
}
strcpy(buffer, line);
bufferptr = buffer + (lineptr - line);
if (name[0] && match(lineptr, name))
{
strcpy(bufferptr, name);
linenoiseAddCompletion(lc, buffer);
}
if (!dir)
{
return;
}
while (ent = readdir(dir))
{
if (match(lineptr, ent->d_name))
{
strcpy(bufferptr, ent->d_name);
linenoiseAddCompletion(lc, buffer);
}
}
closedir(dir);
}
char *get_argument(char arg, const char *name, const char *history,
char *get_argument(char arg, const char *name,
linenoiseCompletionCallback *completion)
{
extern int _argc;
extern char **_argv[];
char *val;
linenoiseHistoryReset();
linenoiseHistoryLoad(self_path(history));
if (_argc > arg)
{
val = *_argv[arg];
@ -195,11 +236,20 @@ char *get_argument(char arg, const char *name, const char *history,
}
linenoiseHistoryAdd(val);
linenoiseHistorySave(self_path(history));
return val;
}
void load_argument(const char *name)
{
linenoiseHistoryReset();
linenoiseHistoryLoad(self_path(name));
}
void save_argument(const char *name)
{
linenoiseHistorySave(self_path(name));
}
void exit_on_key(void)
{
if (input_check_for_abort_key())
@ -219,7 +269,7 @@ void exit_on_disconnect(void)
}
}
void receive_file(const char *name)
void write_file(const char *name)
{
uint16_t i;
int file;
@ -296,10 +346,132 @@ void receive_file(const char *name)
}
}
void write_device(char device)
{
uint16_t i;
dhandle_t dio;
uint16_t rcv;
bool prodos;
bool cont = true;
uint16_t len = 0;
uint16_t num = 0;
printf("- Ok\n\nOpening drive ");
dio = dio_open(device);
if (!dio)
{
w5100_disconnect();
dio_error_exit();
}
printf("- Ok\n\nSector order ");
// The name extension "[P]roDOS sector [O]rder"
// overrides the default as it's both unambiguous
// and suitable for any ProDOS drive / disk type.
if (!strcasecmp(name + strlen(name) - 3, ".PO"))
{
prodos = true;
}
// Every 140k disk image without .PO extension
// can be presumed to have DOS 3.3 sector order
// (usually with extension .DSK or .DO).
// For all other disk images the DOS 3.3 sector
// simply just doesn't make any sense.
else
{
prodos = dio_query_sectcount(dio) != 280;
}
printf("- %s\n\n", prodos ? "ProDOS" : "DOS 3.3");
while (cont)
{
exit_on_key();
rcv = w5100_receive_request();
if (!rcv)
{
cont = w5100_connected();
if (cont)
{
continue;
}
}
{
// Skewing table containing page offsets to write the successive
// pages read from the W5100 to - depends on buffer size of 4k !
static char skew[0x10] = {0x0, 0xE, 0xD, 0xC, 0xB, 0xA, 0x9, 0x8,
0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0xF};
char *dataptr;
if (prodos)
{
if (rcv > sizeof(buffer) - len)
{
rcv = sizeof(buffer) - len;
}
// One less to allow for faster pre-increment below
dataptr = buffer + len - 1;
}
else
{
// Read each page from W5100 individually
if (rcv > 0x100 - len % 0x100)
{
rcv = 0x100 - len % 0x100;
}
// One less to allow for faster pre-increment below
dataptr = buffer + (skew[len / 0x100] << 8 | len % 0x100) - 1;
}
for (i = 0; i < rcv; ++i)
{
// The variable is necessary to have cc65 generate code
// suitable to access the W5100 auto-increment register.
char data = *w5100_data;
*++dataptr = data;
}
}
w5100_receive_commit(rcv);
len += rcv;
if (cont && len < sizeof(buffer))
{
continue;
}
cprintf("\rWriting ");
for (i = 0; i < len; i += 0x200)
{
if (dio_write(dio, num++, buffer + i))
{
w5100_disconnect();
dio_error_exit();
}
}
cprintf("%lu bytes ", num * 0x200UL);
len = 0;
}
printf("- Ok\n\nClosing drive ");
if (dio_close(dio))
{
w5100_disconnect();
dio_error_exit();
}
}
int main(int, char *argv[])
{
uint16_t i;
char *arg;
char device;
uint8_t drv_init = DRV_INIT_DEFAULT;
if (doesclrscrafterexit())
@ -356,9 +528,10 @@ int main(int, char *argv[])
// Copy IP config from IP65 to W5100
w5100_config();
load_argument("wget.urls");
while (true)
{
arg = get_argument(1, "URL", "wget.urls", url_completion);
arg = get_argument(1, "URL", url_completion);
printf("\n\nProcessing URL ");
if (!url_parse(arg))
@ -370,9 +543,145 @@ int main(int, char *argv[])
ip65_error_exit(false);
printf("\n");
}
save_argument("wget.urls");
printf("- Ok\n\n");
arg = get_argument(2, "File", "wget.files", file_completion);
// Try to derive a ProDOS file
// name (proposal) from the URL
{
char *c = strrchr(arg, '/');
char *dot = NULL;
i = 0;
if (c && c[-1] != '/')
{
while (*++c)
{
// Name must begin with a letter
if (!i && !isalpha(*c))
{
continue;
}
// Uppercase looks more familiar
if (isalnum(*c))
{
buffer[i] = toupper(*c);
}
// Replace URL encoded char with dot
else if (*c == '%')
{
buffer[i] = '.';
if (c[1] && c[2])
{
c += 2;
}
}
// Memorize begin of name extension
else if (*c == '.')
{
buffer[i] = '.';
dot = &buffer[i];
}
++i;
}
}
buffer[i] = '\0';
strncpy(name, buffer, sizeof(name) - 1);
// Rather cut from base name than from name extension
if (i > sizeof(name) - 1 && dot)
{
uint16_t len = strlen(dot);
// But keep at least one letter from base name
if (len > sizeof(name) - 1 - 1)
{
len = sizeof(name) - 1 - 1;
}
strncpy(name + sizeof(name) - 1 - len, dot, len);
}
}
load_argument("wget.files");
while (true)
{
arg = get_argument(2, "File", file_completion);
if (arg[0] != '!')
{
device = 0;
break;
}
printf("\n\nChecking drive ");
// !S[1..7],D[1|2]
if (toupper(arg[1]) == 'S' &&
arg[2] >= '1' && arg[2] <= '7' &&
arg[3] == ',' &&
toupper(arg[4]) == 'D' &&
arg[5] >= '1' && arg[5] <= '2' &&
arg[6] == '\0')
{
dhandle_t dio;
device = arg[2] - '0' | arg[5] - '1' << 3;
// dio_open() succeeeds for every connected drive
// no matter if it contains any disk at all
dio = dio_open(device);
if (dio)
{
// dio_query_sectcount() succeeds for every (formatted)
// 16-sector 140k disk no matter if it is a ProDOS disk
if (dio_query_sectcount(dio))
{
// getdevicedir() succeeds only for ProDOS disks so
// tell the user the ProDOS volume name of that disk
// to make sure he doesn't overwrite some important
// (hard) disk but do not bother him in the (usual?)
// case of a non-ProDOS 16-sector 140k (game?) disk
if (getdevicedir(device, buffer, sizeof(buffer)))
{
char oldcursor;
char c;
printf("- Ok\n\nClobber %s? ", buffer);
oldcursor = cursor(true);
c = cgetc();
cursor(oldcursor);
if (toupper(c) == 'Y')
{
printf("- Yes");
break;
}
printf("- No\n\n");
}
else
{
printf("- Ok");
break;
}
}
else
{
printf("- Invalid disk\n\n");
}
dio_close(dio);
}
else
{
printf("- Invalid drive\n\n");
}
}
else
{
printf("- Malformed drive spec\n\n");
}
}
save_argument("wget.files");
printf("\n\nConnecting to %s:%d ", dotted_quad(url_ip), url_port);
@ -494,7 +803,14 @@ int main(int, char *argv[])
}
}
receive_file(arg);
if (device)
{
write_device(device);
}
else
{
write_file(arg);
}
printf("- Ok\n\nDisconnecting ");
w5100_disconnect();