c360dd79eaa7b72bd8acbdc6553ac56e3e6a2a63
[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
45
46 int main(int argc, char **argv)
47 {
48         int i, numsock, maxfd;
49         int ftpsock[16];
50         fd_set rdset;
51         struct timeval tv;
52
53         if(parse_args(argc, argv) == -1) {
54                 return 1;
55         }
56
57         localdir = darr_alloc(0, sizeof *localdir);
58         getcwd(curdir, sizeof curdir);
59         update_localdir();
60
61         if(!(ftp = ftp_alloc())) {
62                 return 1;
63         }
64         if(ftp_connect(ftp, host, port) == -1) {
65                 ftp_free(ftp);
66                 return 1;
67         }
68
69         init_input();
70
71         tg_init();
72
73         tg_bgchar(' ');
74         tg_clear();
75
76         uilist[0] = tui_list("Remote", 0, 1, 40, 21, 0, 0);
77         uilist[1] = tui_list("Local", 40, 1, 40, 21, 0, 0);
78         focus = 0;
79         tui_focus(uilist[focus], 1);
80
81         tg_fgcolor(TGFX_RED);
82         tg_bgcolor(TGFX_BLACK);
83         tg_rect("No conn.", 0, 0, 80, 1, 0);
84
85         tg_setcursor(0, 23);
86
87         tui_draw(uilist[0]);
88         tui_draw(uilist[1]);
89
90         for(;;) {
91                 FD_ZERO(&rdset);
92                 maxfd = 0;
93
94                 numsock = ftp_sockets(ftp, ftpsock, sizeof ftpsock);
95                 for(i=0; i<numsock; i++) {
96                         FD_SET(ftpsock[i], &rdset);
97                         if(ftpsock[i] > maxfd) maxfd = ftpsock[i];
98                 }
99
100 #ifdef __unix__
101                 FD_SET(0, &rdset);
102                 tv.tv_sec = 120;
103                 tv.tv_usec = 0;
104 #else
105                 tv.tv_sec = tv.tv_usec = 0;
106 #endif
107
108                 if(select(maxfd + 1, &rdset, 0, 0, &tv) == -1 && errno == EINTR) {
109                         continue;
110                 }
111
112 #ifdef __unix__
113                 if(FD_ISSET(0, &rdset)) {
114                         if(proc_input() == -1) {
115                                 break;
116                         }
117                 }
118 #else
119                 if(proc_input() == -1) {
120                         break;
121                 }
122 #endif
123
124                 for(i=0; i<numsock; i++) {
125                         if(FD_ISSET(ftpsock[i], &rdset)) {
126                                 ftp_handle(ftp, ftpsock[i]);
127                         }
128                 }
129
130                 updateui();
131         }
132
133         tg_cleanup();
134         cleanup_input();
135         ftp_close(ftp);
136         ftp_free(ftp);
137         return 0;
138 }
139
140 void updateui(void)
141 {
142         int i, num;
143         struct ftp_dirent *ent;
144         unsigned int upd = 0;
145         char buf[128];
146         const char *remdir;
147
148         if(ftp->status != FTP_DISC) {
149                 tg_fgcolor(TGFX_GREEN);
150                 tg_bgcolor(TGFX_BLACK);
151                 tg_text(0, 0, "Conn: %s", host);
152         }
153
154         remdir = ftp_curdir(ftp);
155         if(remdir && strcmp(tui_get_title(uilist[0]), remdir) != 0) {
156                 tui_set_title(uilist[0], remdir);
157                 upd |= 1;
158         }
159
160         if(ftp->modified & FTP_MOD_DIR) {
161                 tui_clear_list(uilist[0]);
162
163                 num = ftp_num_dirent(ftp);
164                 for(i=0; i<num; i++) {
165                         ent = ftp_dirent(ftp, i);
166                         if(ent->type == FTP_DIR) {
167                                 sprintf(buf, "%s/", ent->name);
168                                 tui_add_list_item(uilist[0], buf);
169                         } else {
170                                 tui_add_list_item(uilist[0], ent->name);
171                         }
172                 }
173
174                 tui_list_select(uilist[0], 0);
175
176                 ftp->modified &= ~FTP_MOD_DIR;
177                 upd |= 1;
178         }
179
180         if(local_modified || strcmp(tui_get_title(uilist[1]), curdir) != 0) {
181                 tui_clear_list(uilist[1]);
182                 num = darr_size(localdir);
183                 for(i=0; i<num; i++) {
184                         ent = localdir + i;
185                         if(ent->type == FTP_DIR) {
186                                 sprintf(buf, "%s/", ent->name);
187                                 tui_add_list_item(uilist[1], buf);
188                         } else {
189                                 tui_add_list_item(uilist[1], ent->name);
190                         }
191                 }
192                 tui_list_select(uilist[1], 0);
193                 tui_set_title(uilist[1], curdir);
194
195                 local_modified = 0;
196                 upd |= 2;
197         }
198
199         if(tui_isdirty(uilist[0]) || upd & 1) {
200                 tui_draw(uilist[0]);
201         }
202         if(tui_isdirty(uilist[1]) || upd & 2) {
203                 tui_draw(uilist[1]);
204         }
205 }
206
207 int update_localdir(void)
208 {
209         DIR *dir;
210         struct dirent *dent;
211         struct stat st;
212         struct ftp_dirent ent;
213
214         if(!(dir = opendir(curdir))) {
215                 errmsg("failed to open directory: %s\n", curdir);
216                 return -1;
217         }
218
219         darr_clear(localdir);
220         while((dent = readdir(dir))) {
221                 ent.name = strdup_nf(dent->d_name);
222                 if(strcmp(dent->d_name, ".") == 0) continue;
223
224                 if(stat(dent->d_name, &st) == 0) {
225                         if(S_ISDIR(st.st_mode)) {
226                                 ent.type = FTP_DIR;
227                         } else {
228                                 ent.type = FTP_FILE;
229                         }
230                         ent.size = st.st_size;
231                 } else {
232                         ent.type = FTP_FILE;
233                         ent.size = 0;
234                 }
235
236                 darr_push(localdir, &ent);
237         }
238         closedir(dir);
239
240         qsort(localdir, darr_size(localdir), sizeof *localdir, ftp_direntcmp);
241         local_modified = 1;
242         return 0;
243 }
244
245 int proc_input(void)
246 {
247         union event ev;
248
249         while(poll_input(&ev)) {
250                 switch(ev.type) {
251                 case EV_KEY:
252                         if(keypress(ev.key.key) == -1) {
253                                 return -1;
254                         }
255                         break;
256
257                 default:
258                         break;
259                 }
260         }
261         return 0;
262 }
263
264 int keypress(int key)
265 {
266         int sel;
267
268         switch(key) {
269         case 27:
270         case 'q':
271         case KB_F10:
272                 return -1;
273
274         case '\t':
275                 tui_focus(uilist[focus], 0);
276                 focus ^= 1;
277                 tui_focus(uilist[focus], 1);
278                 break;
279
280         case KB_UP:
281                 tui_list_sel_prev(uilist[focus]);
282                 break;
283         case KB_DOWN:
284                 tui_list_sel_next(uilist[focus]);
285                 break;
286         case KB_LEFT:
287                 tui_list_sel_start(uilist[focus]);
288                 break;
289         case KB_RIGHT:
290                 tui_list_sel_end(uilist[focus]);
291                 break;
292
293         case '\n':
294                 sel = tui_get_list_sel(uilist[focus]);
295                 if(focus == 0) {
296                         struct ftp_dirent *ent = ftp_dirent(ftp, sel);
297                         if(ent->type == FTP_DIR) {
298                                 ftp_queue(ftp, FTP_CHDIR, ent->name);
299                         } else {
300                                 /* TODO */
301                         }
302                 } else {
303                         if(localdir[sel].type == FTP_DIR) {
304                                 if(chdir(localdir[sel].name) == -1) {
305                                         errmsg("failed to change directory: %s\n", localdir[sel].name);
306                                 } else {
307                                         getcwd(curdir, sizeof curdir);
308                                         update_localdir();
309                                 }
310                         } else {
311                                 /* TODO */
312                         }
313                 }
314                 break;
315
316         case '\b':
317                 if(focus == 0) {
318                         ftp_queue(ftp, FTP_CDUP, 0);
319                 } else {
320                         if(chdir("..") == 0) {
321                                 getcwd(curdir, sizeof curdir);
322                                 update_localdir();
323                         }
324                 }
325                 break;
326
327         case KB_F5:
328                 sel = tui_get_list_sel(uilist[focus]);
329                 if(focus == 0) {
330                         struct ftp_transfer *xfer;
331                         struct ftp_dirent *ent = ftp_dirent(ftp, sel);
332                         char *lname = alloca(strlen(ent->name) + 1);
333
334                         fixname(lname, ent->name);
335
336                         xfer = malloc_nf(sizeof *xfer);
337                         if(!(xfer->fp = fopen(lname, "wb"))) {
338                                 errmsg("failed to open %s: %s\n", lname, strerror(errno));
339                                 free(xfer);
340                                 break;
341                         }
342
343                         xfer->op = FTP_RETR;
344                         xfer->rname = strdup_nf(ent->name);
345                         xfer->total = ent->size;
346                         xfer->done = xfer_done;
347
348                         ftp_queue_transfer(ftp, xfer);
349                         local_modified = 1;
350                 } else {
351                         /* TODO */
352                 }
353                 break;
354
355         default:
356                 break;
357         }
358         return 0;
359 }
360
361 static void fixname(char *dest, const char *src)
362 {
363         strcpy(dest, src);
364
365 #ifdef __DOS__
366         {
367                 char *suffix;
368                 if((suffix = strrchr(dest, '.'))) {
369                         *suffix++ = 0;
370                         if(strlen(suffix) > 3) {
371                                 suffix[3] = 0;
372                         }
373                 }
374                 if(strlen(dest) > 8) {
375                         dest[8] = 0;
376                 }
377
378                 if(suffix) {
379                         strcat(dest, ".");
380                         strcat(dest, suffix);
381                 }
382         }
383 #endif
384 }
385
386 static void xfer_done(struct ftp *ftp, struct ftp_transfer *xfer)
387 {
388         if(xfer->fp) {
389                 fclose(xfer->fp);
390         }
391         free(xfer->rname);
392         free(xfer);
393         update_localdir();
394 }
395
396 static const char *usage = "Usage: %s [options] [hostname] [port]\n"
397         "Options:\n"
398         "  -h: print usage information and exit\n";
399
400 int parse_args(int argc, char **argv)
401 {
402         int i, argidx = 0;
403
404         for(i=1; i<argc; i++) {
405                 if(argv[i][0] == '-') {
406                         if(argv[i][2] != 0) {
407                                 goto inval;
408                         }
409                         switch(argv[i][1]) {
410                         case 'h':
411                                 printf(usage, argv[0]);
412                                 exit(0);
413
414                         default:
415                                 goto inval;
416                         }
417
418                 } else {
419                         switch(argidx++) {
420                         case 0:
421                                 host = argv[i];
422                                 break;
423
424                         case 1:
425                                 if((port = atoi(argv[i])) <= 0) {
426                                         fprintf(stderr, "invalid port number: %s\n", argv[i]);
427                                         return -1;
428                                 }
429                                 break;
430
431                         default:
432                                 goto inval;
433                         }
434                 }
435         }
436
437         return 0;
438 inval:
439         fprintf(stderr, "invalid argument: %s\n", argv[i]);
440         fprintf(stderr, usage, argv[0]);
441         return -1;
442 }