comparison src/ft.c @ 4514:7521e29658bc

[gaim-migrate @ 4792] Of course, file transfer wasn't really gone.. I'm trying my hardest to bring on the end of the world (see the roadmap at http://gaim.sf.net/roadmap.png). File transfer is being rewritten. This isn't the finished implementation, but it's enough to let us get the prpls working. There is now a file transfer dialog, which will appear when you get a new transfer request or when you go to Tools -> File Transfers. This is of course core/UI split. I'll also be working on documentation on how to write FT support in a prpl. Oh, and I'll get resumes and transfer batches done when school isn't breathing down my back. Only DCC receive in IRC currently works. Sorry. We'll get the other prpls working soon, as well as send. committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Tue, 04 Feb 2003 06:57:35 +0000
parents 9c7fcb211886
children a2b2cce63fb8
comparison
equal deleted inserted replaced
4513:adb0245e1dfc 4514:7521e29658bc
1 /* 1 /**
2 * gaim - file transfer functions 2 * @file ft.c The file transfer interface.
3 * 3 *
4 * Copyright (C) 2002, Wil Mahan <wtm2@duke.edu> 4 * gaim
5 *
6 * Copyright (C) 2002-2003, Christian Hammond <chipx86@gnupdate.org>
5 * 7 *
6 * This program is free software; you can redistribute it and/or modify 8 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by 9 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or 10 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version. 11 * (at your option) any later version.
26 #include <sys/stat.h> 28 #include <sys/stat.h>
27 #include <unistd.h> 29 #include <unistd.h>
28 #include <errno.h> 30 #include <errno.h>
29 #include <string.h> 31 #include <string.h>
30 32
31 #define FT_BUFFER_SIZE (4096)
32
33 #include <gtk/gtk.h> 33 #include <gtk/gtk.h>
34 #include "gaim.h" 34 #include "gaim.h"
35 #include "proxy.h" 35 #include "proxy.h"
36 #include "prpl.h" 36
37 37 static struct gaim_xfer_ui_ops *xfer_ui_ops = NULL;
38 #ifdef _WIN32 38
39 #include "win32dep.h" 39 struct gaim_xfer *
40 #endif 40 gaim_xfer_new(struct gaim_account *account, GaimXferType type,
41 41 const char *who)
42 /* Completely describes a file transfer. Opaque to callers. */ 42 {
43 struct file_transfer { 43 struct gaim_xfer *xfer;
44 enum { FILE_TRANSFER_TYPE_SEND, FILE_TRANSFER_TYPE_RECEIVE } type; 44
45 45 if (account == NULL || type == GAIM_XFER_UNKNOWN || who == NULL)
46 enum { 46 return NULL;
47 FILE_TRANSFER_STATE_ASK, /* waiting for confirmation */ 47
48 FILE_TRANSFER_STATE_CHOOSEFILE, /* waiting for file dialog */ 48 xfer = g_malloc0(sizeof(struct gaim_xfer));
49 FILE_TRANSFER_STATE_TRANSFERRING, /* in actual transfer */ 49
50 FILE_TRANSFER_STATE_INTERRUPTED, /* other user canceled */ 50 xfer->type = type;
51 FILE_TRANSFER_STATE_CANCELED, /* we canceled */ 51 xfer->account = account;
52 FILE_TRANSFER_STATE_DONE, /* transfer complete */ 52 xfer->who = g_strdup(who);
53 FILE_TRANSFER_STATE_CLEANUP /* freeing memory */ 53 xfer->ui_ops = gaim_get_xfer_ui_ops();
54 } state;
55
56 char *who;
57
58 /* file selection dialog */
59 GtkWidget *w;
60 char **names;
61 int *sizes;
62 char *dir;
63 char *initname;
64 FILE *file;
65
66 /* connection info */
67 struct gaim_connection *gc;
68 int fd;
69 int watcher;
70
71 /* state */
72 int totfiles;
73 int filesdone;
74 int totsize;
75 int bytessent;
76 int bytesleft;
77 };
78
79
80
81 static int ft_choose_file(struct file_transfer *xfer);
82 static void ft_cancel(struct file_transfer *xfer);
83 static void ft_delete(struct file_transfer *xfer);
84 static void ft_callback(gpointer data, gint source, GaimInputCondition condition);
85 static void ft_nextfile(struct file_transfer *xfer);
86 static int ft_mkdir(const char *name);
87 static int ft_mkdir_help(char *dir);
88 static gboolean ft_choose_file_close(GtkWidget *widget, GdkEvent *event,
89 gpointer user_data);
90
91 static struct file_transfer *ft_new(int type, struct gaim_connection *gc,
92 const char *who)
93 {
94 struct file_transfer *xfer = g_new0(struct file_transfer, 1);
95 xfer->type = type;
96 xfer->state = FILE_TRANSFER_STATE_ASK;
97 xfer->gc = gc;
98 xfer->who = g_strdup(who);
99 xfer->filesdone = 0;
100 xfer->w = NULL;
101 54
102 return xfer; 55 return xfer;
103 } 56 }
104 57
105 struct file_transfer *transfer_in_add(struct gaim_connection *gc, 58 void
106 const char *who, const char *initname, int totsize, 59 gaim_xfer_destroy(struct gaim_xfer *xfer)
107 int totfiles, const char *msg) 60 {
108 { 61 struct gaim_xfer_ui_ops *ui_ops;
109 struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_RECEIVE, gc, 62
110 who); 63 if (xfer == NULL)
111 char *buf; 64 return;
112 char *sizebuf; 65
113 static const char *sizestr[4] = { "bytes", "KB", "MB", "GB" }; 66 if (xfer->bytes_remaining > 0) {
114 float sizemag = (float)totsize; 67 gaim_xfer_cancel(xfer);
115 int szindex = 0; 68 return;
116 69 }
117 xfer->initname = g_strdup(initname); 70
118 xfer->totsize = totsize; 71 ui_ops = gaim_xfer_get_ui_ops(xfer);
119 xfer->totfiles = totfiles; 72
120 xfer->filesdone = 0; 73 if (ui_ops != NULL && ui_ops->destroy != NULL)
121 74 ui_ops->destroy(xfer);
122 /* Ask the user whether he or she accepts the transfer. */ 75
123 while ((szindex < 4) && (sizemag > 1024)) { 76 g_free(xfer->who);
124 sizemag /= 1024; 77 g_free(xfer->filename);
125 szindex++; 78
126 } 79 if (xfer->remote_ip != NULL) g_free(xfer->remote_ip);
127 80 if (xfer->local_ip != NULL) g_free(xfer->local_ip);
128 if (totsize == -1) 81
129 sizebuf = g_strdup_printf(_("Unkown")); 82 if (xfer->dest_filename != NULL)
83 g_free(xfer->dest_filename);
84
85 g_free(xfer);
86 }
87
88 void
89 gaim_xfer_request(struct gaim_xfer *xfer)
90 {
91 struct gaim_xfer_ui_ops *ui_ops;
92
93 if (xfer == NULL || xfer->ops.init == NULL)
94 return;
95
96 ui_ops = gaim_get_xfer_ui_ops();
97
98 if (ui_ops == NULL || ui_ops->request_file == NULL)
99 return;
100
101 ui_ops->request_file(xfer);
102 }
103
104 void
105 gaim_xfer_request_accepted(struct gaim_xfer *xfer, char *filename)
106 {
107 GaimXferType type;
108
109 if (xfer == NULL || filename == NULL) {
110
111 if (filename != NULL)
112 g_free(filename);
113
114 return;
115 }
116
117 type = gaim_xfer_get_type(xfer);
118
119 if (type == GAIM_XFER_SEND) {
120 struct stat sb;
121
122 /* Check the filename. */
123 if (g_strrstr(filename, "..")) {
124 char *msg;
125
126 msg = g_strdup_printf(_("%s is not a valid filename.\n"),
127 filename);
128
129 gaim_xfer_error(type, xfer->who, msg);
130
131 g_free(msg);
132 g_free(filename);
133
134 return;
135 }
136
137 if (stat(filename, &sb) == -1) {
138 char *msg;
139
140 msg = g_strdup_printf(_("%s was not found.\n"), filename);
141
142 gaim_xfer_error(type, xfer->who, msg);
143
144 g_free(msg);
145 g_free(filename);
146
147 return;
148 }
149
150 gaim_xfer_set_size(xfer, sb.st_size);
151 }
152 else {
153 /* TODO: Sanity checks and file overwrite checks. */
154
155 gaim_xfer_set_dest_filename(xfer, filename);
156 }
157
158 g_free(filename);
159
160 xfer->ops.init(xfer);
161 }
162
163 void
164 gaim_xfer_request_denied(struct gaim_xfer *xfer)
165 {
166 if (xfer == NULL)
167 return;
168
169 /* TODO */
170 }
171
172 GaimXferType
173 gaim_xfer_get_type(const struct gaim_xfer *xfer)
174 {
175 if (xfer == NULL)
176 return GAIM_XFER_UNKNOWN;
177
178 return xfer->type;
179 }
180
181 struct gaim_account *
182 gaim_xfer_get_account(const struct gaim_xfer *xfer)
183 {
184 if (xfer == NULL)
185 return NULL;
186
187 return xfer->account;
188 }
189
190 const char *
191 gaim_xfer_get_filename(const struct gaim_xfer *xfer)
192 {
193 if (xfer == NULL)
194 return NULL;
195
196 return xfer->filename;
197 }
198
199 const char *
200 gaim_xfer_get_dest_filename(const struct gaim_xfer *xfer)
201 {
202 if (xfer == NULL)
203 return NULL;
204
205 return xfer->dest_filename;
206 }
207
208 size_t
209 gaim_xfer_get_bytes_sent(const struct gaim_xfer *xfer)
210 {
211 if (xfer == NULL)
212 return 0;
213
214 return xfer->bytes_sent;
215 }
216
217 size_t
218 gaim_xfer_get_bytes_remaining(const struct gaim_xfer *xfer)
219 {
220 if (xfer == NULL)
221 return 0;
222
223 return xfer->bytes_remaining;
224 }
225
226 size_t
227 gaim_xfer_get_size(const struct gaim_xfer *xfer)
228 {
229 if (xfer == NULL)
230 return 0;
231
232 return xfer->size;
233 }
234
235 double
236 gaim_xfer_get_progress(const struct gaim_xfer *xfer)
237 {
238 if (xfer == NULL)
239 return 0.0;
240
241 return ((double)gaim_xfer_get_bytes_sent(xfer) /
242 (double)gaim_xfer_get_size(xfer));
243 }
244
245 const char *
246 gaim_xfer_get_local_ip(const struct gaim_xfer *xfer)
247 {
248 if (xfer == NULL)
249 return NULL;
250
251 return xfer->local_ip;
252 }
253
254 unsigned int
255 gaim_xfer_get_local_port(const struct gaim_xfer *xfer)
256 {
257 if (xfer == NULL)
258 return -1;
259
260 return xfer->local_port;
261 }
262
263 const char *
264 gaim_xfer_get_remote_ip(const struct gaim_xfer *xfer)
265 {
266 if (xfer == NULL)
267 return NULL;
268
269 return xfer->remote_ip;
270 }
271
272 unsigned int
273 gaim_xfer_get_remote_port(const struct gaim_xfer *xfer)
274 {
275 if (xfer == NULL)
276 return -1;
277
278 return xfer->remote_port;
279 }
280
281 void
282 gaim_xfer_set_filename(struct gaim_xfer *xfer, const char *filename)
283 {
284 if (xfer == NULL)
285 return;
286
287 if (xfer->filename != NULL)
288 g_free(xfer->filename);
289
290 xfer->filename = (filename == NULL ? NULL : g_strdup(filename));
291 }
292
293 void
294 gaim_xfer_set_dest_filename(struct gaim_xfer *xfer, const char *filename)
295 {
296 if (xfer == NULL)
297 return;
298
299 if (xfer->dest_filename != NULL)
300 g_free(xfer->dest_filename);
301
302 xfer->dest_filename = (filename == NULL ? NULL : g_strdup(filename));
303 }
304
305 void
306 gaim_xfer_set_size(struct gaim_xfer *xfer, size_t size)
307 {
308 if (xfer == NULL || size == 0)
309 return;
310
311 xfer->size = size;
312 }
313
314 struct gaim_xfer_ui_ops *
315 gaim_xfer_get_ui_ops(const struct gaim_xfer *xfer)
316 {
317 if (xfer == NULL)
318 return NULL;
319
320 return xfer->ui_ops;
321 }
322
323 void
324 gaim_xfer_set_init_fnc(struct gaim_xfer *xfer,
325 void (*fnc)(struct gaim_xfer *))
326 {
327 if (xfer == NULL)
328 return;
329
330 xfer->ops.init = fnc;
331 }
332
333
334 void
335 gaim_xfer_set_read_fnc(struct gaim_xfer *xfer,
336 size_t (*fnc)(char **, const struct gaim_xfer *))
337 {
338 if (xfer == NULL)
339 return;
340
341 xfer->ops.read = fnc;
342 }
343
344 void
345 gaim_xfer_set_write_fnc(struct gaim_xfer *xfer,
346 size_t (*fnc)(const char *, size_t,
347 const struct gaim_xfer *))
348 {
349 if (xfer == NULL)
350 return;
351
352 xfer->ops.write = fnc;
353 }
354
355 void
356 gaim_xfer_set_ack_fnc(struct gaim_xfer *xfer,
357 void (*fnc)(struct gaim_xfer *))
358 {
359 if (xfer == NULL)
360 return;
361
362 xfer->ops.ack = fnc;
363 }
364
365 void
366 gaim_xfer_set_start_fnc(struct gaim_xfer *xfer,
367 void (*fnc)(struct gaim_xfer *))
368 {
369 if (xfer == NULL)
370 return;
371
372 xfer->ops.start = fnc;
373 }
374
375 void
376 gaim_xfer_set_end_fnc(struct gaim_xfer *xfer,
377 void (*fnc)(struct gaim_xfer *))
378 {
379 if (xfer == NULL)
380 return;
381
382 xfer->ops.end = fnc;
383 }
384
385 void
386 gaim_xfer_set_cancel_fnc(struct gaim_xfer *xfer,
387 void (*fnc)(struct gaim_xfer *))
388 {
389 if (xfer == NULL)
390 return;
391
392 xfer->ops.cancel = fnc;
393 }
394
395 size_t
396 gaim_xfer_read(struct gaim_xfer *xfer, char **buffer)
397 {
398 size_t s, r;
399
400 if (xfer == NULL || buffer == NULL)
401 return 0;
402
403 s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096);
404
405 if (xfer->ops.read != NULL)
406 r = xfer->ops.read(buffer, xfer);
407 else {
408 *buffer = g_malloc0(s);
409
410 r = read(xfer->fd, *buffer, s);
411 }
412
413 return r;
414 }
415
416 size_t
417 gaim_xfer_write(struct gaim_xfer *xfer, const char *buffer, size_t size)
418 {
419 size_t r, s;
420
421 if (xfer == NULL || buffer == NULL || size == 0)
422 return 0;
423
424 s = MIN(gaim_xfer_get_bytes_remaining(xfer), size);
425
426 if (xfer->ops.write != NULL)
427 r = xfer->ops.write(buffer, s, xfer);
130 else 428 else
131 sizebuf = g_strdup_printf("%.3g %s", sizemag, sizestr[szindex]); 429 r = write(xfer->fd, buffer, s);
132 430
133 if (xfer->totfiles == 1) { 431 return r;
134 buf = g_strdup_printf(_("%s requests that %s accept a file: %s (%s)"), 432 }
135 who, xfer->gc->username, initname, sizebuf); 433
136 } else { 434 static void
137 buf = g_strdup_printf(_("%s requests that %s accept %d files: %s (%s)"), 435 transfer_cb(gpointer data, gint source, GaimInputCondition condition)
138 who, xfer->gc->username, xfer->totfiles, 436 {
139 initname, sizebuf); 437 struct gaim_xfer_ui_ops *ui_ops;
140 } 438 struct gaim_xfer *xfer = (struct gaim_xfer *)data;
141 439 char *buffer = NULL;
142 g_free(sizebuf); 440 size_t r;
143 441
144 if (msg) { 442 if (condition & GAIM_INPUT_READ) {
145 char *newmsg = g_strconcat(buf, ": ", msg, NULL); 443 r = gaim_xfer_read(xfer, &buffer);
146 g_free(buf); 444
147 buf = newmsg; 445 if (r > 0)
148 } 446 fwrite(buffer, 1, r, xfer->dest_fp);
149 447 }
150 do_ask_dialog(buf, NULL, xfer, _("Accept"), ft_choose_file, _("Cancel"), ft_cancel, NULL, FALSE); 448 else {
151 g_free(buf); 449 size_t s = MIN(gaim_xfer_get_bytes_remaining(xfer), 4096);
152 450
153 return xfer; 451 buffer = g_malloc0(s);
154 } 452
155 453 fread(buffer, 1, s, xfer->dest_fp);
156 struct file_transfer *transfer_out_add(struct gaim_connection *gc, 454
157 const char *who) 455 /* Write as much as we're allowed to. */
158 { 456 r = gaim_xfer_write(xfer, buffer, s);
159 struct file_transfer *xfer = ft_new(FILE_TRANSFER_TYPE_SEND, gc, 457
160 who); 458 if (r < s) {
161 459 /* We have to seek back in the file now. */
162 ft_choose_file(xfer); 460 fseek(xfer->dest_fp, r - s, SEEK_CUR);
163 461 }
164 return xfer; 462 }
165 } 463
166 464 g_free(buffer);
167 /* We canceled the transfer, either by declining the initial 465
168 * confirmation dialog or canceling the file dialog. 466 if (r < 0)
169 */ 467 return;
170 static void ft_cancel(struct file_transfer *xfer) 468
171 { 469 xfer->bytes_remaining -= r;
172 debug_printf("** ft_cancel\n"); 470 xfer->bytes_sent += r;
173 471
174 /* Make sure we weren't aborted while waiting for 472 if (xfer->ops.ack != NULL)
175 * confirmation from the user. 473 xfer->ops.ack(xfer);
474
475 ui_ops = gaim_xfer_get_ui_ops(xfer);
476
477 if (ui_ops != NULL && ui_ops->update_progress != NULL)
478 ui_ops->update_progress(xfer, gaim_xfer_get_progress(xfer));
479
480 if (xfer->bytes_remaining == 0)
481 gaim_xfer_end(xfer);
482 }
483
484 static void
485 begin_transfer(struct gaim_xfer *xfer, GaimInputCondition cond)
486 {
487 struct gaim_xfer_ui_ops *ui_ops;
488 GaimXferType type = gaim_xfer_get_type(xfer);
489
490 ui_ops = gaim_xfer_get_ui_ops(xfer);
491
492 /*
493 * We'll have already tried to open this earlier to make sure we can
494 * read/write here. Should be safe.
176 */ 495 */
177 if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) { 496 xfer->dest_fp = fopen(gaim_xfer_get_dest_filename(xfer),
178 xfer->state = FILE_TRANSFER_STATE_CLEANUP; 497 type == GAIM_XFER_RECEIVE ? "wb" : "rb");
179 transfer_abort(xfer, NULL); 498
180 return; 499 /* Just in case, though. */
181 } 500 if (xfer->dest_fp == NULL) {
182 501 gaim_xfer_cancel(xfer); /* ? */
183 xfer->state = FILE_TRANSFER_STATE_CANCELED; 502 return;
184 if (xfer->w) { 503 }
185 gtk_widget_destroy(xfer->w); 504
186 xfer->w = NULL; 505 xfer->watcher = gaim_input_add(xfer->fd, cond, transfer_cb, xfer);
187 } 506
188 507 if (ui_ops != NULL && ui_ops->add_xfer != NULL)
189 if (xfer->gc->prpl->file_transfer_cancel) 508 ui_ops->add_xfer(xfer);
190 xfer->gc->prpl->file_transfer_cancel(xfer->gc, xfer); 509
191 510 if (xfer->ops.start != NULL)
192 ft_delete(xfer); 511 xfer->ops.start(xfer);
193 } 512 }
194 513
195 /* This is called when the other user aborts the transfer, 514 static void
196 * possibly in the middle of a transfer. 515 connect_cb(gpointer data, gint source, GaimInputCondition condition)
197 */ 516 {
198 int transfer_abort(struct file_transfer *xfer, const char *why) 517 struct gaim_xfer *xfer = (struct gaim_xfer *)data;
199 { 518
200 if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) { 519 xfer->fd = source;
201 /* If for some reason we have already been 520
202 * here and are waiting on some event before 521 begin_transfer(xfer, condition);
203 * cleaning up, but we get another abort request, 522 }
204 * we don't need to do anything else. 523
205 */ 524 void
206 return 1; 525 gaim_xfer_start(struct gaim_xfer *xfer, int fd, const char *ip,
207 } 526 unsigned int port)
208 else if (xfer->state == FILE_TRANSFER_STATE_ASK) { 527 {
209 /* Kludge: since there is no way to kill a 528 GaimInputCondition cond;
210 * do_ask_dialog() window, we just note the 529 GaimXferType type;
211 * status here and clean up after the user 530
212 * makes a selection. 531 if (xfer == NULL || gaim_xfer_get_type(xfer) == GAIM_XFER_UNKNOWN)
213 */ 532 return;
214 xfer->state = FILE_TRANSFER_STATE_INTERRUPTED; 533
215 return 1; 534 type = gaim_xfer_get_type(xfer);
216 } 535
217 else if (xfer->state == FILE_TRANSFER_STATE_TRANSFERRING) { 536 xfer->bytes_remaining = gaim_xfer_get_size(xfer);
218 if (xfer->watcher) { 537 xfer->bytes_sent = 0;
219 gaim_input_remove(xfer->watcher); 538
220 xfer->watcher = 0; 539 if (type == GAIM_XFER_RECEIVE) {
540 cond = GAIM_INPUT_READ;
541
542 if (ip != NULL) {
543 xfer->remote_ip = g_strdup(ip);
544 xfer->remote_port = port;
545
546 /* Establish a file descriptor. */
547 proxy_connect(xfer->remote_ip, xfer->remote_port,
548 connect_cb, xfer);
549
550 return;
221 } 551 }
222 if (xfer->file) { 552 else {
223 fclose(xfer->file); 553 xfer->fd = fd;
224 xfer->file = NULL;
225 } 554 }
226 /* XXX theoretically, there is a race condition here, 555 }
227 * because we could be inside ft_callback() when we
228 * free xfer below, with undefined results. Since
229 * we use non-blocking IO, this doesn't seem to be
230 * a problem, but it still makes me nervous--I don't
231 * know how to fix it other than using locks, though.
232 * -- wtm
233 */
234 }
235 else if (xfer->state == FILE_TRANSFER_STATE_CHOOSEFILE) {
236 /* It's safe to clean up now. Just make sure we
237 * destroy the dialog window first.
238 */
239 if (xfer->w) {
240 g_signal_handlers_disconnect_by_func(G_OBJECT(xfer->w),
241 G_CALLBACK(ft_choose_file_close), xfer);
242 gtk_widget_destroy(xfer->w);
243 xfer->w = NULL;
244 }
245 }
246
247 /* Let the user know that we were aborted, unless we already
248 * finished or the user aborted first.
249 */
250 /* if ((xfer->state != FILE_TRANSFER_STATE_DONE) &&
251 (xfer->state != FILE_TRANSFER_STATE_CANCELED)) { */
252 if (why) {
253 char *msg;
254
255 if (xfer->type == FILE_TRANSFER_TYPE_SEND)
256 msg = g_strdup_printf(_("File transfer to %s aborted."), xfer->who);
257 else
258 msg = g_strdup_printf(_("File transfer from %s aborted."), xfer->who);
259 do_error_dialog(msg, why, GAIM_ERROR);
260 g_free(msg);
261 }
262
263 ft_delete(xfer);
264
265 return 0;
266 }
267
268
269 static void ft_delete(struct file_transfer *xfer)
270 {
271 if (xfer->names)
272 g_strfreev(xfer->names);
273 if (xfer->initname)
274 g_free(xfer->initname);
275 if (xfer->who)
276 g_free(xfer->who);
277 if (xfer->sizes)
278 g_free(xfer->sizes);
279 g_free(xfer);
280 }
281
282 static void ft_choose_ok(gpointer a, struct file_transfer *xfer) {
283 gboolean exists, is_dir;
284 struct stat st;
285 const char *err = NULL;
286
287 xfer->names = gtk_file_selection_get_selections(GTK_FILE_SELECTION(xfer->w));
288 exists = !stat(*xfer->names, &st);
289 is_dir = (exists) ? S_ISDIR(st.st_mode) : 0;
290
291 if (exists) {
292 if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE)
293 /* XXX overwrite/append/cancel prompt */
294 err = _("That file already exists; please choose another name.");
295 else { /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */
296 char **cur;
297 /* First find the total number of files,
298 * so we know how much space to allocate.
299 */
300 xfer->totfiles = 0;
301 for (cur = xfer->names; *cur; cur++) {
302 xfer->totfiles++;
303 }
304
305 /* Now get sizes for each file. */
306 xfer->totsize = st.st_size;
307 xfer->sizes = g_malloc(xfer->totfiles
308 * sizeof(*xfer->sizes));
309 xfer->sizes[0] = st.st_size;
310 for (cur = xfer->names + 1; *cur; cur++) {
311 exists = !stat(*cur, &st);
312 if (!exists) {
313 err = _("File not found.");
314 break;
315 }
316 xfer->sizes[cur - xfer->names] =
317 st.st_size;
318 xfer->totsize += st.st_size;
319 }
320 }
321 }
322 else { /* doesn't exist */
323 if (xfer->type == FILE_TRANSFER_TYPE_SEND)
324 err = _("File not found.");
325 else if (xfer->totfiles > 1) {
326 if (!xfer->names[0] || xfer->names[1]) {
327 err = _("You may only choose one new directory.");
328 }
329 else {
330 if (ft_mkdir(*xfer->names))
331 err = _("Unable to create directory.");
332 else
333 xfer->dir = g_strconcat(xfer->names[0],
334 "/", NULL);
335 }
336 }
337 }
338
339 if (err)
340 do_error_dialog(err, NULL, GAIM_ERROR);
341 else { 556 else {
342 /* File name looks valid */ 557 cond = GAIM_INPUT_WRITE;
343 gtk_widget_destroy(xfer->w); 558
344 xfer->w = NULL; 559 xfer->fd = fd;
345 560 }
346 if (xfer->type == FILE_TRANSFER_TYPE_SEND) { 561
347 char *desc; 562 begin_transfer(xfer, cond);
348 if (xfer->totfiles == 1) 563 }
349 desc = *xfer->names; 564
350 else 565 void
351 /* XXX what else? */ 566 gaim_xfer_end(struct gaim_xfer *xfer)
352 desc = "*"; 567 {
353 /* desc = g_path_get_basename(g_path_get_dirname(*xfer->names)); */ 568 if (xfer == NULL)
354 xfer->gc->prpl->file_transfer_out(xfer->gc, xfer, 569 return;
355 desc, xfer->totfiles, 570
356 xfer->totsize); 571 /* See if we are actually trying to cancel this. */
357 } 572 if (gaim_xfer_get_bytes_remaining(xfer) > 0) {
358 else 573 gaim_xfer_cancel(xfer);
359 xfer->gc->prpl->file_transfer_in(xfer->gc, xfer, 574 return;
360 0); /* XXX */ 575 }
361 } 576
362 } 577 if (xfer->ops.end != NULL)
363 578 xfer->ops.end(xfer);
364 /* Called on outgoing transfers to get information about the 579
365 * current file. 580 gaim_input_remove(xfer->watcher);
366 */ 581 xfer->watcher = 0;
367 int transfer_get_file_info(struct file_transfer *xfer, int *size, 582
368 char **name) 583 close(xfer->fd);
369 { 584
370 *size = xfer->sizes[xfer->filesdone]; 585 if (xfer->dest_fp != NULL) {
371 *name = xfer->names[xfer->filesdone]; 586 fclose(xfer->dest_fp);
372 return 0; 587 xfer->dest_fp = NULL;
373 } 588 }
374 589
375 static gboolean 590 /* Delete the transfer. */
376 ft_choose_file_close(GtkWidget *widget, GdkEvent *event, gpointer user_data) 591 gaim_xfer_destroy(xfer);
377 { 592 }
378 ft_cancel((struct file_transfer *)user_data); 593
379 594 void
380 return FALSE; 595 gaim_xfer_cancel(struct gaim_xfer *xfer)
381 } 596 {
382 597 struct gaim_xfer_ui_ops *ui_ops;
383 static void 598
384 ft_cancel_button_cb(GtkButton *button, gpointer user_data) 599 if (xfer == NULL)
385 { 600 return;
386 ft_cancel((struct file_transfer *)user_data); 601
387 } 602 if (xfer->ops.cancel != NULL)
388 603 xfer->ops.cancel(xfer);
389 static int ft_choose_file(struct file_transfer *xfer) 604
390 { 605 gaim_input_remove(xfer->watcher);
391 char *curdir = g_get_current_dir(); /* should be freed */ 606 xfer->watcher = 0;
392 char *initstr; 607
393 608 close(xfer->fd);
394 /* If the connection is interrupted while we are waiting 609
395 * for do_ask_dialog(), then we can't clean up until we 610 if (xfer->dest_fp != NULL) {
396 * get here, after the user makes a selection. 611 fclose(xfer->dest_fp);
397 */ 612 xfer->dest_fp = NULL;
398 if (xfer->state == FILE_TRANSFER_STATE_INTERRUPTED) { 613 }
399 xfer->state = FILE_TRANSFER_STATE_CLEANUP; 614
400 transfer_abort(xfer, NULL); 615 ui_ops = gaim_xfer_get_ui_ops(xfer);
401 return 1; 616
402 } 617 if (ui_ops != NULL && ui_ops->cancel != NULL)
403 618 ui_ops->cancel(xfer);
404 xfer->state = FILE_TRANSFER_STATE_CHOOSEFILE; 619
405 if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) 620 /* Delete the transfer. */
406 xfer->w = gtk_file_selection_new(_("Gaim - Save As...")); 621 gaim_xfer_destroy(xfer);
407 else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ { 622 }
408 xfer->w = gtk_file_selection_new(_("Gaim - Open...")); 623
409 gtk_file_selection_set_select_multiple(GTK_FILE_SELECTION(xfer->w), 624 void
410 1); 625 gaim_xfer_error(GaimXferType type, const char *who, const char *msg)
411 } 626 {
412 627 char *title;
413 if (xfer->initname) { 628
414 initstr = g_strdup_printf("%s/%s", curdir, xfer->initname); 629 if (xfer == NULL || msg == NULL || type == GAIM_XFER_UNKNOWN)
415 } else 630 return;
416 initstr = g_strconcat(curdir, "/", NULL); 631
417 g_free(curdir); 632 if (type == GAIM_XFER_SEND)
418 633 title = g_strdup_printf(_("File transfer to %s aborted.\n"), who);
419 gtk_file_selection_set_filename(GTK_FILE_SELECTION(xfer->w),
420 initstr);
421 g_free(initstr);
422
423 g_signal_connect(G_OBJECT(xfer->w), "delete_event",
424 G_CALLBACK(ft_choose_file_close), xfer);
425 g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(xfer->w)->cancel_button), "clicked",
426 G_CALLBACK(ft_cancel_button_cb), xfer);
427 g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(xfer->w)->ok_button), "clicked",
428 G_CALLBACK(ft_choose_ok), xfer);
429
430 gtk_widget_show(xfer->w);
431
432 return 0;
433 }
434
435 static int ft_open_file(struct file_transfer *xfer, const char *filename,
436 int offset)
437 {
438 char *err = NULL;
439
440 if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE) {
441 xfer->file = fopen(filename,
442 (offset > 0) ? "a" : "w");
443
444 if (!xfer->file)
445 err = g_strdup_printf(_("Could not open %s for writing: %s"),
446 filename, g_strerror(errno));
447
448 }
449 else /* (xfer->type == FILE_TRANSFER_TYPE_SEND) */ {
450 xfer->file = fopen(filename, "r");
451 if (!xfer->file)
452 err = g_strdup_printf(_("Could not open %s for reading: %s"),
453 filename, g_strerror(errno));
454 }
455
456 if (err) {
457 do_error_dialog(err, NULL, GAIM_ERROR);
458 g_free(err);
459 return -1;
460 }
461
462 fseek(xfer->file, offset, SEEK_SET);
463
464 return 0;
465 }
466
467 /* Takes a full file name, and creates any directories above it
468 * that don't exist already.
469 */
470 static int ft_mkdir(const char *name) {
471 int ret = 0;
472 struct stat st;
473 mode_t m = umask(0077);
474 char *dir;
475
476 dir = g_path_get_dirname(name);
477 if (stat(dir, &st))
478 ret = ft_mkdir_help(dir);
479
480 g_free(dir);
481 umask(m);
482 return ret;
483 }
484
485 /* Two functions, one recursive, just to make a directory. Yuck. */
486 static int ft_mkdir_help(char *dir) {
487 int ret;
488
489 ret = mkdir(dir, 0775);
490
491 if (ret) {
492 char *index = strrchr(dir, G_DIR_SEPARATOR);
493 if (!index)
494 return -1;
495 *index = '\0';
496 ret = ft_mkdir_help(dir);
497 *index = G_DIR_SEPARATOR;
498 if (!ret)
499 ret = mkdir(dir, 0775);
500 }
501
502 return ret;
503 }
504
505 int transfer_in_do(struct file_transfer *xfer, int fd,
506 const char *filename, int size)
507 {
508 char *fullname;
509
510 xfer->state = FILE_TRANSFER_STATE_TRANSFERRING;
511 xfer->fd = fd;
512 xfer->bytesleft = size;
513
514 /* XXX implement resuming incoming transfers */
515 #if 0
516 if (xfer->sizes)
517 xfer->bytesleft -= xfer->sizes[0];
518 #endif
519
520 /* Security check */
521 if (g_strrstr(filename, "..")) {
522 xfer->state = FILE_TRANSFER_STATE_CLEANUP;
523 transfer_abort(xfer, _("Invalid incoming filename component"));
524 return -1;
525 }
526
527 if (xfer->totfiles > 1)
528 fullname = g_strconcat(xfer->dir, filename, NULL);
529 else 634 else
530 /* Careful: filename is the name on the *other* 635 title = g_strdup_printf(_("File transfer from %s aborted.\n"), who);
531 * end; don't use it here. */ 636
532 fullname = g_strdup(xfer->names[xfer->filesdone]); 637 do_error_dialog(title, msg, GAIM_ERROR);
533 638
534 639 g_free(title);
535 if (ft_mkdir(fullname)) { 640 }
536 xfer->state = FILE_TRANSFER_STATE_CLEANUP; 641
537 transfer_abort(xfer, _("Invalid incoming filename")); 642 void
538 return -1; 643 gaim_set_xfer_ui_ops(struct gaim_xfer_ui_ops *ops)
539 } 644 {
540 645 if (ops == NULL)
541 if (!ft_open_file(xfer, fullname, 0)) { 646 return;
542 /* Special case: if we are receiving an empty file, 647
543 * we would never enter the callback. Just avoid the 648 xfer_ui_ops = ops;
544 * callback altogether. 649 }
545 */ 650
546 if (xfer->bytesleft == 0) 651 struct gaim_xfer_ui_ops *
547 ft_nextfile(xfer); 652 gaim_get_xfer_ui_ops(void)
548 else 653 {
549 xfer->watcher = gaim_input_add(fd, 654 return xfer_ui_ops;
550 GAIM_INPUT_READ, 655 }
551 ft_callback, xfer); 656
552 } else {
553 /* Error opening file */
554 xfer->state = FILE_TRANSFER_STATE_CLEANUP;
555 transfer_abort(xfer, NULL);
556 g_free(fullname);
557 return -1;
558 }
559
560 g_free(fullname);
561 return 0;
562 }
563
564 int transfer_out_do(struct file_transfer *xfer, int fd, int offset) {
565 xfer->state = FILE_TRANSFER_STATE_TRANSFERRING;
566 xfer->fd = fd;
567 xfer->bytesleft = xfer->sizes[xfer->filesdone] - offset;
568
569 if (!ft_open_file(xfer, xfer->names[xfer->filesdone], offset)) {
570 /* Special case: see transfer_in_do().
571 */
572 if (xfer->bytesleft == 0)
573 ft_nextfile(xfer);
574 else
575 xfer->watcher = gaim_input_add(fd,
576 GAIM_INPUT_WRITE, ft_callback,
577 xfer);
578 }
579 else {
580 /* Error opening file */
581 xfer->state = FILE_TRANSFER_STATE_CLEANUP;
582 transfer_abort(xfer, NULL);
583 return -1;
584 }
585
586 return 0;
587 }
588
589 static void ft_callback(gpointer data, gint source,
590 GaimInputCondition condition)
591 {
592 struct file_transfer *xfer = (struct file_transfer *)data;
593 int rt;
594 char *buf = NULL;
595
596 if (condition & GAIM_INPUT_READ) {
597 if (xfer->gc->prpl->file_transfer_read)
598 rt = xfer->gc->prpl->file_transfer_read(xfer->gc, xfer,
599 xfer->fd, &buf);
600 else {
601 buf = g_new0(char, MIN(xfer->bytesleft, FT_BUFFER_SIZE));
602 rt = read(xfer->fd, buf, MIN(xfer->bytesleft, FT_BUFFER_SIZE));
603 }
604
605 /* XXX What if the transfer is interrupted while we
606 * are inside read()? How can this be handled safely?
607 * -- wtm
608 */
609 if (rt > 0) {
610 xfer->bytesleft -= rt;
611 fwrite(buf, 1, rt, xfer->file);
612 }
613
614 }
615 else /* (condition & GAIM_INPUT_WRITE) */ {
616 int remain = MIN(xfer->bytesleft, FT_BUFFER_SIZE);
617
618 buf = g_new0(char, remain);
619
620 fread(buf, 1, remain, xfer->file);
621
622 if (xfer->gc->prpl->file_transfer_write)
623 rt = xfer->gc->prpl->file_transfer_write(xfer->gc, xfer, xfer->fd,
624 buf, remain);
625 else
626 rt = write(xfer->fd, buf, remain);
627
628 if (rt > 0)
629 xfer->bytesleft -= rt;
630 }
631
632 if (rt < 0) {
633 if (buf != NULL)
634 g_free(buf);
635
636 return;
637 }
638
639 xfer->bytessent += rt;
640
641 if (xfer->gc->prpl->file_transfer_data_chunk)
642 xfer->gc->prpl->file_transfer_data_chunk(xfer->gc, xfer, buf, rt);
643
644 if (rt > 0 && xfer->bytesleft == 0) {
645 /* We are done with this file! */
646 gaim_input_remove(xfer->watcher);
647 xfer->watcher = 0;
648 fclose(xfer->file);
649 xfer->file = 0;
650 ft_nextfile(xfer);
651 }
652
653 if (buf != NULL)
654 g_free(buf);
655 }
656
657 static void ft_nextfile(struct file_transfer *xfer)
658 {
659 debug_printf("file transfer %d of %d done\n",
660 xfer->filesdone + 1, xfer->totfiles);
661
662 if (++xfer->filesdone == xfer->totfiles) {
663 char *msg;
664 char *msg2;
665
666 xfer->gc->prpl->file_transfer_done(xfer->gc, xfer);
667
668 if (xfer->type == FILE_TRANSFER_TYPE_RECEIVE)
669 msg = g_strdup_printf(_("File transfer from %s to %s completed successfully."),
670 xfer->who, xfer->gc->username);
671 else
672 msg = g_strdup_printf(_("File transfer from %s to %s completed successfully."),
673 xfer->gc->username, xfer->who);
674 xfer->state = FILE_TRANSFER_STATE_DONE;
675
676 if (xfer->totfiles > 1)
677 msg2 = g_strdup_printf(_("%d files transferred."),
678 xfer->totfiles);
679 else
680 msg2 = NULL;
681
682 do_error_dialog(msg, msg2, GAIM_INFO);
683 g_free(msg);
684 if (msg2)
685 g_free(msg2);
686
687 ft_delete(xfer);
688 }
689 else {
690 xfer->gc->prpl->file_transfer_nextfile(xfer->gc, xfer);
691 }
692 }
693