diff --git a/apps/wget65.c b/apps/wget65.c index b97c271..90e01ff 100644 --- a/apps/wget65.c +++ b/apps/wget65.c @@ -1,4 +1,6 @@ +#include #include +#include #include #include #include @@ -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();