download, progress, improved screen updates...
[oftp] / src / main.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <signal.h>
4 #include <errno.h>
5 #include <assert.h>
6 #ifdef __DOS__
7 #include <direct.h>
8 #else
9 #include <unistd.h>
10 #include <dirent.h>
11 #endif
12 #include <sys/stat.h>
13 #include <sys/select.h>
14 #include "tgfx.h"
15 #include "input.h"
16 #include "util.h"
17 #include "tui.h"
18 #include "ftp.h"
19 #include "darray.h"
20
21 #ifdef __DOS__
22 #define select  select_s
23 #endif
24
25 void updateui(void);
26 int update_localdir(void);
27 int proc_input(void);
28 int keypress(int key);
29 static void fixname(char *dest, const char *src);
30 static void xfer_done(struct ftp *ftp, struct ftp_transfer *xfer);
31 int parse_args(int argc, char **argv);
32
33 static struct ftp *ftp;
34 static struct tui_widget *uilist[2];
35
36 static int focus;
37
38 static char *host = "localhost";
39 static int port = 21;
40
41 static char curdir[PATH_MAX + 1];
42 static struct ftp_dirent *localdir;
43 static int local_modified;
44 static struct ftp_transfer *cur_xfer;
45
46 #ifdef __DOS__
47 void detect_lfn(void);
48
49 static int have_lfn;
50 #endif
51
52
53 int main(int argc, char **argv)
54 {
55         int i, numsock, maxfd;
56         int ftpsock[16];
57         fd_set rdset;
58         struct timeval tv;
59
60 #ifdef __DOS__
61         detect_lfn();
62 #endif
63
64         if(parse_args(argc, argv) == -1) {
65                 return 1;
66         }
67
68         localdir = darr_alloc(0, sizeof *localdir);
69         getcwd(curdir, sizeof curdir);
70         update_localdir();
71
72         if(!(ftp = ftp_alloc())) {
73                 return 1;
74         }
75         if(ftp_connect(ftp, host, port) == -1) {
76                 ftp_free(ftp);
77                 return 1;
78         }
79
80         init_input();
81
82         tg_init();
83
84         tg_bgchar(' ');
85         tg_clear();
86
87         uilist[0] = tui_list("Remote", 0, 1, 40, 21, 0, 0);
88         uilist[1] = tui_list("Local", 40, 1, 40, 21, 0, 0);
89         focus = 0;
90         tui_focus(uilist[focus], 1);
91
92         local_modified = 1;
93
94         tg_setcursor(0, 23);
95
96         for(;;) {
97                 FD_ZERO(&rdset);
98                 maxfd = 0;
99
100                 numsock = ftp_sockets(ftp, ftpsock, sizeof ftpsock);
101                 for(i=0; i<numsock; i++) {
102                         FD_SET(ftpsock[i], &rdset);
103                         if(ftpsock[i] > maxfd) maxfd = ftpsock[i];
104                 }
105
106 #ifdef __unix__
107                 FD_SET(0, &rdset);
108                 tv.tv_sec = 120;
109                 tv.tv_usec = 0;
110 #else
111                 tv.tv_sec = tv.tv_usec = 0;
112 #endif
113
114                 if(select(maxfd + 1, &rdset, 0, 0, &tv) == -1 && errno == EINTR) {
115                         continue;
116                 }
117
118 #ifdef __unix__
119                 if(FD_ISSET(0, &rdset)) {
120                         if(proc_input() == -1) {
121                                 break;
122                         }
123                 }
124 #else
125                 if(proc_input() == -1) {
126                         break;
127                 }
128 #endif
129
130                 for(i=0; i<numsock; i++) {
131                         if(FD_ISSET(ftpsock[i], &rdset)) {
132                                 ftp_handle(ftp, ftpsock[i]);
133                         }
134                 }
135
136                 updateui();
137         }
138
139         tg_cleanup();
140         cleanup_input();
141         ftp_close(ftp);
142         ftp_free(ftp);
143         return 0;
144 }
145
146 void updateui(void)
147 {
148         int i, num, progr;
149         struct ftp_dirent *ent;
150         unsigned int upd = 0;
151         char buf[128];
152         const char *remdir;
153         static int prev_status;
154         static void *prev_xfer;
155
156         if(ftp->status != prev_status) {
157                 tg_fgcolor(ftp->status ? TGFX_GREEN : TGFX_RED);
158                 tg_bgcolor(TGFX_BLACK);
159                 tg_text(0, 0, "Srv: %s", ftp->status ? host : "-");
160                 upd |= 0x8000;
161                 prev_status = ftp->status;
162         }
163
164         tg_fgcolor(TGFX_WHITE);
165         tg_bgcolor(TGFX_BLACK);
166         if(cur_xfer) {
167                 int dir = tg_gchar(cur_xfer->op == FTP_RETR ? TGFX_RARROW : TGFX_LARROW);
168                 tg_text(40, 0, "%c%s", dir, cur_xfer->rname);
169                 if(!cur_xfer->total) {
170                         tg_text(75, 0, " ???%%");
171                 } else {
172                         progr = 100 * cur_xfer->count / cur_xfer->total;
173                         if(progr < 0) progr = 0;
174                         if(progr > 100) progr = 100;
175                         tg_text(75, 0, " %3d%%", progr);
176                 }
177                 upd |= 0x8000;
178         } else if(prev_xfer) {
179                 tg_rect(0, 40, 0, 40, 1, 0);
180                 upd |= 0x8000;
181         }
182         prev_xfer = cur_xfer;
183
184         remdir = ftp_curdir(ftp);
185         if(remdir && strcmp(tui_get_title(uilist[0]), remdir) != 0) {
186                 tui_set_title(uilist[0], remdir);
187                 upd |= 1;
188         }
189
190         if(ftp->modified & FTP_MOD_DIR) {
191                 tui_clear_list(uilist[0]);
192
193                 num = ftp_num_dirent(ftp);
194                 for(i=0; i<num; i++) {
195                         ent = ftp_dirent(ftp, i);
196                         if(ent->type == FTP_DIR) {
197                                 sprintf(buf, "%s/", ent->name);
198                                 tui_add_list_item(uilist[0], buf);
199                         } else {
200                                 tui_add_list_item(uilist[0], ent->name);
201                         }
202                 }
203
204                 tui_list_select(uilist[0], 0);
205
206                 ftp->modified &= ~FTP_MOD_DIR;
207                 upd |= 1;
208         }
209
210         if(local_modified || strcmp(tui_get_title(uilist[1]), curdir) != 0) {
211                 tui_clear_list(uilist[1]);
212                 num = darr_size(localdir);
213                 for(i=0; i<num; i++) {
214                         ent = localdir + i;
215                         if(ent->type == FTP_DIR) {
216                                 sprintf(buf, "%s/", ent->name);
217                                 tui_add_list_item(uilist[1], buf);
218                         } else {
219                                 tui_add_list_item(uilist[1], ent->name);
220                         }
221                 }
222                 tui_list_select(uilist[1], 0);
223                 tui_set_title(uilist[1], curdir);
224
225                 local_modified = 0;
226                 upd |= 2;
227         }
228
229         if(tui_isdirty(uilist[0]) || upd & 1) {
230                 tui_draw(uilist[0]);
231         }
232         if(tui_isdirty(uilist[1]) || upd & 2) {
233                 tui_draw(uilist[1]);
234         }
235
236         if(upd) {
237                 tg_redraw();
238         }
239 }
240
241 int update_localdir(void)
242 {
243         DIR *dir;
244         struct dirent *dent;
245         struct stat st;
246         struct ftp_dirent ent;
247
248         if(!(dir = opendir(curdir))) {
249                 errmsg("failed to open directory: %s\n", curdir);
250                 return -1;
251         }
252
253         darr_clear(localdir);
254         while((dent = readdir(dir))) {
255                 ent.name = strdup_nf(dent->d_name);
256                 if(strcmp(dent->d_name, ".") == 0) continue;
257
258                 if(stat(dent->d_name, &st) == 0) {
259                         if(S_ISDIR(st.st_mode)) {
260                                 ent.type = FTP_DIR;
261                         } else {
262                                 ent.type = FTP_FILE;
263                         }
264                         ent.size = st.st_size;
265                 } else {
266                         ent.type = FTP_FILE;
267                         ent.size = 0;
268                 }
269
270                 darr_push(localdir, &ent);
271         }
272         closedir(dir);
273
274         qsort(localdir, darr_size(localdir), sizeof *localdir, ftp_direntcmp);
275         local_modified = 1;
276         return 0;
277 }
278
279 int proc_input(void)
280 {
281         union event ev;
282
283         while(poll_input(&ev)) {
284                 switch(ev.type) {
285                 case EV_KEY:
286                         if(keypress(ev.key.key) == -1) {
287                                 return -1;
288                         }
289                         break;
290
291                 default:
292                         break;
293                 }
294         }
295         return 0;
296 }
297
298 int keypress(int key)
299 {
300         int sel;
301
302         switch(key) {
303         case 27:
304         case 'q':
305         case KB_F10:
306                 return -1;
307
308         case '`':
309                 tui_invalidate(uilist[0]);
310                 tui_invalidate(uilist[1]);
311                 break;
312
313         case '\t':
314                 tui_focus(uilist[focus], 0);
315                 focus ^= 1;
316                 tui_focus(uilist[focus], 1);
317                 break;
318
319         case KB_UP:
320                 tui_list_sel_prev(uilist[focus]);
321                 break;
322         case KB_DOWN:
323                 tui_list_sel_next(uilist[focus]);
324                 break;
325         case KB_LEFT:
326                 tui_list_sel_start(uilist[focus]);
327                 break;
328         case KB_RIGHT:
329                 tui_list_sel_end(uilist[focus]);
330                 break;
331
332         case '\n':
333                 sel = tui_get_list_sel(uilist[focus]);
334                 if(focus == 0) {
335                         struct ftp_dirent *ent = ftp_dirent(ftp, sel);
336                         if(ent->type == FTP_DIR) {
337                                 ftp_queue(ftp, FTP_CHDIR, ent->name);
338                         } else {
339                                 /* TODO */
340                         }
341                 } else {
342                         if(localdir[sel].type == FTP_DIR) {
343                                 if(chdir(localdir[sel].name) == -1) {
344                                         errmsg("failed to change directory: %s\n", localdir[sel].name);
345                                 } else {
346                                         getcwd(curdir, sizeof curdir);
347                                         update_localdir();
348                                 }
349                         } else {
350                                 /* TODO */
351                         }
352                 }
353                 break;
354
355         case '\b':
356                 if(focus == 0) {
357                         ftp_queue(ftp, FTP_CDUP, 0);
358                 } else {
359                         if(chdir("..") == 0) {
360                                 getcwd(curdir, sizeof curdir);
361                                 update_localdir();
362                         }
363                 }
364                 break;
365
366         case KB_F5:
367                 sel = tui_get_list_sel(uilist[focus]);
368                 if(focus == 0) {
369                         struct ftp_transfer *xfer;
370                         struct ftp_dirent *ent = ftp_dirent(ftp, sel);
371                         char *lname = alloca(strlen(ent->name) + 1);
372
373                         fixname(lname, ent->name);
374
375                         xfer = malloc_nf(sizeof *xfer);
376                         if(!(xfer->fp = fopen(lname, "wb"))) {
377                                 errmsg("failed to open %s: %s\n", lname, strerror(errno));
378                                 free(xfer);
379                                 break;
380                         }
381
382                         xfer->op = FTP_RETR;
383                         xfer->rname = strdup_nf(ent->name);
384                         xfer->total = ent->size;
385                         xfer->count = 0;
386                         xfer->done = xfer_done;
387
388                         cur_xfer = xfer;
389
390                         ftp_queue_transfer(ftp, xfer);
391                         local_modified = 1;
392                 } else {
393                         /* TODO */
394                 }
395                 break;
396
397         case KB_F8:
398                 sel = tui_get_list_sel(uilist[focus]);
399                 if(focus == 0) {
400                 } else {
401                         struct ftp_dirent *ent = localdir + sel;
402                         if(ent->type == FTP_FILE) {
403                                 infomsg("removing local file: %s\n", ent->name);
404                                 remove(ent->name);
405                                 update_localdir();
406                                 local_modified = 1;
407                         }
408                 }
409                 break;
410
411
412         default:
413                 break;
414         }
415         return 0;
416 }
417
418 static void fixname(char *dest, const char *src)
419 {
420         strcpy(dest, src);
421
422 #ifdef __DOS__
423         if(!have_lfn) {
424                 int len;
425                 char *suffix;
426                 if((suffix = strrchr(dest, '.'))) {
427                         *suffix++ = 0;
428                         if(strlen(suffix) > 3) {
429                                 suffix[3] = 0;
430                         }
431                 }
432                 if((len = strlen(dest)) > 8) {
433                         dest[8] = 0;
434                         len = 8;
435                 }
436
437                 if(suffix) {
438                         dest[len++] = '.';
439                         if(dest + len != suffix) {
440                                 memmove(dest + len, suffix, strlen(suffix) + 1);
441                         }
442                 }
443         }
444 #endif
445 }
446
447 static void xfer_done(struct ftp *ftp, struct ftp_transfer *xfer)
448 {
449         if(xfer->fp) {
450                 fclose(xfer->fp);
451         }
452         free(xfer->rname);
453         free(xfer);
454         update_localdir();
455
456         cur_xfer = 0;
457 }
458
459 static const char *usage = "Usage: %s [options] [hostname] [port]\n"
460         "Options:\n"
461         "  -h: print usage information and exit\n";
462
463 int parse_args(int argc, char **argv)
464 {
465         int i, argidx = 0;
466
467         for(i=1; i<argc; i++) {
468                 if(argv[i][0] == '-') {
469                         if(argv[i][2] != 0) {
470                                 goto inval;
471                         }
472                         switch(argv[i][1]) {
473                         case 'h':
474                                 printf(usage, argv[0]);
475                                 exit(0);
476
477                         default:
478                                 goto inval;
479                         }
480
481                 } else {
482                         switch(argidx++) {
483                         case 0:
484                                 host = argv[i];
485                                 break;
486
487                         case 1:
488                                 if((port = atoi(argv[i])) <= 0) {
489                                         fprintf(stderr, "invalid port number: %s\n", argv[i]);
490                                         return -1;
491                                 }
492                                 break;
493
494                         default:
495                                 goto inval;
496                         }
497                 }
498         }
499
500         return 0;
501 inval:
502         fprintf(stderr, "invalid argument: %s\n", argv[i]);
503         fprintf(stderr, usage, argv[0]);
504         return -1;
505 }
506
507 #ifdef __DOS__
508 #include <dos.h>
509 #include <i86.h>
510
511 void detect_lfn(void)
512 {
513         union REGS regs = {0};
514         struct SREGS sregs = {0};
515         unsigned int drv, buf_seg;
516         char *buf;
517
518         _dos_getdrive(&drv);
519
520         if(_dos_allocmem(3, &buf_seg) != 0) {
521                 return;
522         }
523 #ifdef _M_I86
524 #error TODO port to 16bit
525 #else
526         buf = (char*)(buf_seg << 4);
527 #endif
528
529         sprintf(buf, "%c:\\", 'A' + drv);
530
531         sregs.ds = sregs.es = buf_seg;
532         regs.w.ax = 0x71a0;
533         regs.w.dx = 0;          /* ds:dx drive letter string */
534         regs.w.di = 4;          /* es:di buffer for filesystem name */
535         regs.w.cx = 44;         /* buffer size (3*16 - 4) */
536
537         intdosx(&regs, &regs, &sregs);
538
539         if(regs.w.cflag == 0) {
540                 infomsg("long filenames available\n");
541                 have_lfn = 1;
542         } else {
543                 infomsg("long filenames not available, will truncate as needed\n");
544         }
545
546         _dos_freemem(buf_seg);
547 }
548 #endif