.asm.obj:
nasm -f obj -o $@ $[*.asm
+!ifdef __UNIX__
+clean: .symbolic
+ rm -f *.obj
+ rm -f objects.lnk
+ rm -f oftp.map
+ rm -f $(bin)
+!else
clean: .symbolic
del *.obj
del objects.lnk
del oftp.map
del $(bin)
+!endif
oftp was written to address the lack of graphical FTP clients for DOS (MS-DOS,
FreeDOS, etc), but will also run on UNIX systems using curses for its UI.
+**Release status**: *Prototype*. oftp mostly works, but it's very rough, and you
+will find lots of bugs. Feel free to open bug reports, but keep in mind that the
+most obvious issues will be solved as a matter of course as this project
+progresses.
+
+
License
-------
Copyright (C) John Tsiombikas <nuclear@mutantstargoat.com>
#git archive -o $fname HEAD
rm -f $fname
-zip $fname Makefile src/*.c src/*.h src/dos/*.c src/dos/*.h src/unix/*.c src/unix/*.h scripts/*
+#zip $fname Makefile src/*.c src/*.h src/dos/*.c src/dos/*.h src/unix/*.c src/unix/*.h scripts/*
+zip -x \*.swp -x \*.o -x \*.obj -r $fname *
tg_text(x + 2, y, "%s", label);
}
}
+
+int tg_gchar(int gchar)
+{
+ switch(gchar) {
+ case TGFX_LARROW:
+ return 0x1b;
+ case TGFX_RARROW:
+ return 0x1a;
+ default:
+ break;
+ }
+ return '@';
+}
static int handle_data(struct ftp *ftp, int s)
{
- int rd;
+ int i, rd;
if(ftp->data == -1) {
return -1;
}
- for(;;) {
+ /* get at most 4 packets at a time, to allow returning back to the main loop
+ * to process input
+ */
+ for(i=0; i<4; i++) {
if((rd = recv(ftp->data, ftp->drecv, sizeof ftp->drecv, 0)) == -1) {
if(errno == EINTR) continue;
/* assume EWOULDBLOCK, try again next time */
static char curdir[PATH_MAX + 1];
static struct ftp_dirent *localdir;
static int local_modified;
+static struct ftp_transfer *cur_xfer;
+
+#ifdef __DOS__
+void detect_lfn(void);
+
+static int have_lfn;
+#endif
int main(int argc, char **argv)
fd_set rdset;
struct timeval tv;
+#ifdef __DOS__
+ detect_lfn();
+#endif
+
if(parse_args(argc, argv) == -1) {
return 1;
}
focus = 0;
tui_focus(uilist[focus], 1);
- tg_fgcolor(TGFX_RED);
- tg_bgcolor(TGFX_BLACK);
- tg_rect("No conn.", 0, 0, 80, 1, 0);
+ local_modified = 1;
tg_setcursor(0, 23);
- tui_draw(uilist[0]);
- tui_draw(uilist[1]);
-
for(;;) {
FD_ZERO(&rdset);
maxfd = 0;
void updateui(void)
{
- int i, num;
+ int i, num, progr;
struct ftp_dirent *ent;
unsigned int upd = 0;
char buf[128];
const char *remdir;
+ static int prev_status;
+ static void *prev_xfer;
- if(ftp->status != FTP_DISC) {
- tg_fgcolor(TGFX_GREEN);
+ if(ftp->status != prev_status) {
+ tg_fgcolor(ftp->status ? TGFX_GREEN : TGFX_RED);
tg_bgcolor(TGFX_BLACK);
- tg_text(0, 0, "Conn: %s", host);
+ tg_text(0, 0, "Srv: %s", ftp->status ? host : "-");
+ upd |= 0x8000;
+ prev_status = ftp->status;
+ }
+
+ tg_fgcolor(TGFX_WHITE);
+ tg_bgcolor(TGFX_BLACK);
+ if(cur_xfer) {
+ int dir = tg_gchar(cur_xfer->op == FTP_RETR ? TGFX_RARROW : TGFX_LARROW);
+ tg_text(40, 0, "%c%s", dir, cur_xfer->rname);
+ if(!cur_xfer->total) {
+ tg_text(75, 0, " ???%%");
+ } else {
+ progr = 100 * cur_xfer->count / cur_xfer->total;
+ if(progr < 0) progr = 0;
+ if(progr > 100) progr = 100;
+ tg_text(75, 0, " %3d%%", progr);
+ }
+ upd |= 0x8000;
+ } else if(prev_xfer) {
+ tg_rect(0, 40, 0, 40, 1, 0);
+ upd |= 0x8000;
}
+ prev_xfer = cur_xfer;
remdir = ftp_curdir(ftp);
if(remdir && strcmp(tui_get_title(uilist[0]), remdir) != 0) {
if(tui_isdirty(uilist[1]) || upd & 2) {
tui_draw(uilist[1]);
}
+
+ if(upd) {
+ tg_redraw();
+ }
}
int update_localdir(void)
case KB_F10:
return -1;
+ case '`':
+ tui_invalidate(uilist[0]);
+ tui_invalidate(uilist[1]);
+ break;
+
case '\t':
tui_focus(uilist[focus], 0);
focus ^= 1;
xfer->op = FTP_RETR;
xfer->rname = strdup_nf(ent->name);
xfer->total = ent->size;
+ xfer->count = 0;
xfer->done = xfer_done;
+ cur_xfer = xfer;
+
ftp_queue_transfer(ftp, xfer);
local_modified = 1;
} else {
}
break;
+ case KB_F8:
+ sel = tui_get_list_sel(uilist[focus]);
+ if(focus == 0) {
+ } else {
+ struct ftp_dirent *ent = localdir + sel;
+ if(ent->type == FTP_FILE) {
+ infomsg("removing local file: %s\n", ent->name);
+ remove(ent->name);
+ update_localdir();
+ local_modified = 1;
+ }
+ }
+ break;
+
+
default:
break;
}
strcpy(dest, src);
#ifdef __DOS__
- {
+ if(!have_lfn) {
+ int len;
char *suffix;
if((suffix = strrchr(dest, '.'))) {
*suffix++ = 0;
suffix[3] = 0;
}
}
- if(strlen(dest) > 8) {
+ if((len = strlen(dest)) > 8) {
dest[8] = 0;
+ len = 8;
}
if(suffix) {
- strcat(dest, ".");
- strcat(dest, suffix);
+ dest[len++] = '.';
+ if(dest + len != suffix) {
+ memmove(dest + len, suffix, strlen(suffix) + 1);
+ }
}
}
#endif
free(xfer->rname);
free(xfer);
update_localdir();
+
+ cur_xfer = 0;
}
static const char *usage = "Usage: %s [options] [hostname] [port]\n"
fprintf(stderr, usage, argv[0]);
return -1;
}
+
+#ifdef __DOS__
+#include <dos.h>
+#include <i86.h>
+
+void detect_lfn(void)
+{
+ union REGS regs = {0};
+ struct SREGS sregs = {0};
+ unsigned int drv, buf_seg;
+ char *buf;
+
+ _dos_getdrive(&drv);
+
+ if(_dos_allocmem(3, &buf_seg) != 0) {
+ return;
+ }
+#ifdef _M_I86
+#error TODO port to 16bit
+#else
+ buf = (char*)(buf_seg << 4);
+#endif
+
+ sprintf(buf, "%c:\\", 'A' + drv);
+
+ sregs.ds = sregs.es = buf_seg;
+ regs.w.ax = 0x71a0;
+ regs.w.dx = 0; /* ds:dx drive letter string */
+ regs.w.di = 4; /* es:di buffer for filesystem name */
+ regs.w.cx = 44; /* buffer size (3*16 - 4) */
+
+ intdosx(®s, ®s, &sregs);
+
+ if(regs.w.cflag == 0) {
+ infomsg("long filenames available\n");
+ have_lfn = 1;
+ } else {
+ infomsg("long filenames not available, will truncate as needed\n");
+ }
+
+ _dos_freemem(buf_seg);
+}
+#endif
TGFX_WHITE
};
+/* graphics characters */
+enum {
+ TGFX_LARROW = 256,
+ TGFX_RARROW
+};
+
enum {
TGFX_FRAME = 1,
TGFX_SHADOW = 2
void tg_rect(const char *label, int x, int y, int xsz, int ysz, unsigned int flags);
+int tg_gchar(int gchar);
+
#endif /* TGFX_H_ */
return w->par;
}
+void tui_invalidate(struct tui_widget *w)
+{
+ w->dirty = 1;
+}
+
int tui_isdirty(struct tui_widget *w)
{
return w->dirty;
void tui_vstatus(int type, const char *fmt, va_list ap)
{
/* TODO */
- tg_color(15);
- tg_bgcolor(0);
- tg_vtext(0, 25, fmt, ap);
}
void tui_msgbox(int type, const char *title, const char *msg, ...)
void tui_vmsgbox(int type, const char *title, const char *msg, va_list ap)
{
/* TODO */
- tg_color(15);
- tg_bgcolor(0);
- tg_vtext(0, 25, msg, ap);
}
void tui_remove_widget(struct tui_widget *par, struct tui_widget *w);
struct tui_widget *tui_parent(struct tui_widget *w);
+void tui_invalidate(struct tui_widget *w);
int tui_isdirty(struct tui_widget *w);
void tui_draw(struct tui_widget *w);
return darr_size(wl->entries);
}
+#define VISLINES(wl) ((wl)->height - 2)
+
int tui_list_select(struct tui_widget *w, int idx)
{
- int offs, nelem;
+ int offs, nelem, numvis;
struct tui_list *wl = (struct tui_list*)w;
assert(wl->type == TUI_LIST);
return 0; /* no change */
}
+ numvis = VISLINES(wl);
+
if(idx < 0) {
wl->sel = -1;
return 0;
}
wl->sel = idx;
- if(idx < wl->view_offs || idx >= wl->view_offs + wl->height) {
- offs = idx - wl->height / 2;
- if(offs + wl->height >= nelem) {
- offs = nelem - wl->height;
+ if(idx < wl->view_offs || idx >= wl->view_offs + numvis) {
+ offs = idx - numvis / 2;
+ if(offs + numvis >= nelem) {
+ offs = nelem - numvis;
}
if(offs < 0) {
offs = 0;
int tui_list_sel_next(struct tui_widget *w)
{
- int nelem;
+ int nelem, numvis;
struct tui_list *wl = (struct tui_list*)w;
assert(wl->type == TUI_LIST);
nelem = darr_size(wl->entries);
+ numvis = VISLINES(wl);
+
if(wl->sel + 1 >= nelem) {
return -1;
}
- if(++wl->sel - wl->view_offs >= wl->height) {
- wl->view_offs = wl->sel - wl->height;
+ if(++wl->sel - wl->view_offs >= numvis) {
+ wl->view_offs = wl->sel - numvis + 1;
}
wl->dirty = 1;
tui_call_callback(w, TUI_ONMODIFY);
int tui_list_sel_end(struct tui_widget *w)
{
- int nelem;
+ int nelem, numvis;
struct tui_list *wl = (struct tui_list*)w;
assert(wl->type == TUI_LIST);
nelem = darr_size(wl->entries);
+ numvis = VISLINES(wl);
wl->sel = nelem - 1;
- wl->view_offs = nelem - wl->height;
+ wl->view_offs = nelem - numvis;
if(wl->view_offs < 0) wl->view_offs = 0;
wl->dirty = 1;
tui_call_callback(w, TUI_ONMODIFY);
keypad(stdscr, TRUE);
nodelay(stdscr, TRUE);
noecho();
+ curs_set(0);
start_color();
fgcol = curses_color(TGFX_WHITE);
}
attroff(COLOR_PAIR(cur_pair));
+ refresh();
+}
+
+int tg_gchar(int gchar)
+{
+ switch(gchar) {
+ case TGFX_LARROW:
+ return ACS_LARROW;
+ case TGFX_RARROW:
+ return ACS_RARROW;
+ default:
+ break;
+ }
+ return '@';
}
static int curses_color(int col)