Mercurial > pidgin.yaz
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 |