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