|
1 /* |
|
2 * filetransfer.c |
|
3 * |
|
4 * Copyrigth (C) 2010 Nicolas Cornu <nicolas.cornu@ensi-bourges.fr> |
|
5 * |
|
6 * 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 |
|
8 * the Free Software Foundation; either version 2 of the License, or (at |
|
9 * your option) any later version. |
|
10 * |
|
11 * This program is distributed in the hope that it will be useful, but |
|
12 * WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU General Public License |
|
17 * along with this program; if not, write to the Free Software |
|
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
|
19 * USA |
|
20 */ |
|
21 #include "config.h" |
|
22 |
|
23 #include <glib.h> |
|
24 #include <glib/gstdio.h> |
|
25 #include <string.h> |
|
26 |
|
27 #include <mcabber/modules.h> |
|
28 #include <mcabber/utils.h> |
|
29 #include <mcabber/xmpp_helper.h> |
|
30 #include <mcabber/settings.h> |
|
31 #include <mcabber/logprint.h> |
|
32 #include <mcabber/compl.h> |
|
33 #include <mcabber/commands.h> |
|
34 #include <mcabber/roster.h> |
|
35 #include <mcabber/utils.h> |
|
36 |
|
37 #include <jingle/jingle.h> |
|
38 #include <jingle/check.h> |
|
39 #include <jingle/register.h> |
|
40 #include <jingle/sessions.h> |
|
41 #include <jingle/send.h> |
|
42 |
|
43 #include "filetransfer.h" |
|
44 |
|
45 |
|
46 static gconstpointer check(JingleContent *cn, GError **err); |
|
47 static gboolean handle(JingleAction action, gconstpointer data, LmMessageNode *node); |
|
48 static void tomessage(gconstpointer data, LmMessageNode *node); |
|
49 static gboolean handle_data(gconstpointer data, const gchar *data2, guint len); |
|
50 static void start(session_content *sc); |
|
51 static void send(session_content *sc); |
|
52 static void stop(gconstpointer data); |
|
53 static gchar* info(gconstpointer data); |
|
54 |
|
55 static gboolean is_md5_hash(const gchar *hash); |
|
56 |
|
57 static void jingle_ft_init(void); |
|
58 static void jingle_ft_uninit(void); |
|
59 // Return must be free |
|
60 static gchar *_convert_size(guint64 size); |
|
61 static int _next_index(void); |
|
62 static void _free(JingleFT *jft); |
|
63 |
|
64 const gchar *deps[] = { "jingle", NULL }; |
|
65 |
|
66 static GSList *info_list = NULL; |
|
67 static guint jft_cid = 0; |
|
68 |
|
69 const gchar* strstate[] = { |
|
70 "PENDING", |
|
71 "STARTING", |
|
72 "ENDING", |
|
73 "REJECT", |
|
74 "ERROR" |
|
75 }; |
|
76 |
|
77 static JingleAppFuncs funcs = { |
|
78 .check = check, |
|
79 .handle = handle, |
|
80 .tomessage = tomessage, |
|
81 .handle_data = handle_data, |
|
82 .start = start, |
|
83 .send = send, |
|
84 .stop = stop, |
|
85 .info = info |
|
86 }; |
|
87 |
|
88 module_info_t info_jingle_filetransfer = { |
|
89 .branch = MCABBER_BRANCH, |
|
90 .api = MCABBER_API_VERSION, |
|
91 .version = PROJECT_VERSION, |
|
92 .description = "Jingle File Transfer (XEP-0234)\n", |
|
93 .requires = deps, |
|
94 .init = jingle_ft_init, |
|
95 .uninit = jingle_ft_uninit, |
|
96 .next = NULL, |
|
97 }; |
|
98 |
|
99 |
|
100 /** |
|
101 * \fn static gconstpointer check(JingleContent *cn, GError **err) |
|
102 * \brief Check if a node description with xmlns of JFT is correct |
|
103 * |
|
104 * \param cn the jinglecontent a node description |
|
105 * \param err contain an error of the domain JINGLE_CHECK_ERROR |
|
106 * \return a gconstpointer, which is a new allocated JingleFT |
|
107 */ |
|
108 static gconstpointer check(JingleContent *cn, GError **err) |
|
109 { |
|
110 JingleFT *ft = NULL; |
|
111 LmMessageNode *node; |
|
112 gint64 tmpsize; |
|
113 const gchar *datestr, *sizestr; |
|
114 |
|
115 node = lm_message_node_get_child(cn->description, "offer"); |
|
116 |
|
117 if (!node) { |
|
118 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_MISSING, |
|
119 "the offer element is missing"); |
|
120 return NULL; |
|
121 } |
|
122 |
|
123 node = lm_message_node_get_child(node, "file"); |
|
124 if (!node) { |
|
125 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_MISSING, |
|
126 "the file element is missing"); |
|
127 return NULL; |
|
128 } |
|
129 |
|
130 if (g_strcmp0(lm_message_node_get_attribute(node, "xmlns"), NS_SI_FT)) { |
|
131 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_MISSING, |
|
132 "the file transfer offer has an invalid/unsupported namespace"); |
|
133 return NULL; |
|
134 } |
|
135 |
|
136 ft = g_new0(JingleFT, 1); |
|
137 datestr = lm_message_node_get_attribute(node, "date"); |
|
138 ft->hash = (gchar *) lm_message_node_get_attribute(node, "hash"); |
|
139 ft->name = (gchar *) lm_message_node_get_attribute(node, "name"); |
|
140 sizestr = lm_message_node_get_attribute(node, "size"); |
|
141 ft->transmit = 0; |
|
142 ft->dir = JINGLE_FT_INCOMING; |
|
143 |
|
144 if (!ft->name || !sizestr) { |
|
145 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_MISSING, |
|
146 "an attribute of the file element is missing"); |
|
147 g_free(ft); |
|
148 return NULL; |
|
149 } |
|
150 |
|
151 ft->date = from_iso8601(datestr, 1); |
|
152 tmpsize = g_ascii_strtoll(sizestr, NULL, 10); |
|
153 |
|
154 // the size attribute is a xs:integer an therefore can be negative. |
|
155 if (tmpsize < 0) { |
|
156 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_BADVALUE, |
|
157 "the offered file has a negative size"); |
|
158 g_free(ft); |
|
159 return NULL; |
|
160 } |
|
161 ft->size = tmpsize; |
|
162 |
|
163 ft->name = g_path_get_basename(ft->name); |
|
164 |
|
165 if (settings_opt_get("jingle_ft_dir") != NULL) |
|
166 ft->name = g_build_filename(settings_opt_get("jingle_ft_dir"), ft->name, NULL); |
|
167 else |
|
168 ft->name = g_build_filename("/tmp", ft->name, NULL); |
|
169 |
|
170 if (!g_strcmp0(ft->name, ".")) { |
|
171 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_BADVALUE, |
|
172 "the offered file has an invalid filename"); |
|
173 g_free(ft->name); |
|
174 g_free(ft); |
|
175 return NULL; |
|
176 } |
|
177 |
|
178 // check if the md5 hash is valid ([a-fA-F0-9){32}) |
|
179 if (ft->hash != NULL && (strlen(ft->hash) != 32 || !is_md5_hash(ft->hash))) { |
|
180 g_set_error(err, JINGLE_CHECK_ERROR, JINGLE_CHECK_ERROR_BADVALUE, |
|
181 "the offered file has an invalid hash"); |
|
182 g_free(ft->name); |
|
183 g_free(ft); |
|
184 return NULL; |
|
185 } |
|
186 ft->hash = g_strndup(ft->hash, 32); |
|
187 |
|
188 { |
|
189 JingleFTInfo *jfti = g_new0(JingleFTInfo, 1); |
|
190 jfti->index = _next_index(); |
|
191 jfti->jft = ft; |
|
192 info_list = g_slist_append(info_list, jfti); |
|
193 } |
|
194 |
|
195 return (gconstpointer) ft; |
|
196 } |
|
197 |
|
198 /** |
|
199 * \fn static gboolean handle(JingleAction action, gconstpointer data, LmMessageNode *node) |
|
200 * \brief handle a function to handle action which are not "current" |
|
201 * |
|
202 * \param action the action which have been received |
|
203 * \param data contain the JingleFT of the content concerned |
|
204 * \param node the node himself |
|
205 * \return a gconstpointer, which is a new allocated JingleFT |
|
206 */ |
|
207 static gboolean handle(JingleAction action, gconstpointer data, |
|
208 LmMessageNode *node) |
|
209 { |
|
210 if (action == JINGLE_SESSION_INFO) { |
|
211 if (!g_strcmp0(lm_message_node_get_attribute(node, "xmlns"), |
|
212 NS_JINGLE_APP_FT_INFO) |
|
213 && !g_strcmp0(node->name, "hash")) { |
|
214 ((JingleFT *)data)->hash = g_strdup(lm_message_node_get_value(node)); |
|
215 return TRUE; |
|
216 } |
|
217 return FALSE; |
|
218 } |
|
219 return FALSE; |
|
220 } |
|
221 |
|
222 static gboolean is_md5_hash(const gchar *hash) |
|
223 { |
|
224 int i = 0; |
|
225 for (i = 0; i < 32 && hash[i]; i++) |
|
226 if (!g_ascii_isxdigit(hash[i])) break; |
|
227 |
|
228 if (i == 32) |
|
229 return TRUE; |
|
230 else |
|
231 return FALSE; |
|
232 } |
|
233 |
|
234 static gboolean handle_data(gconstpointer jingleft, const gchar *data, guint len) |
|
235 { |
|
236 JingleFT *jft = (JingleFT *) jingleft; |
|
237 GError *err = NULL; |
|
238 GIOStatus status; |
|
239 gsize bytes_written = 0; |
|
240 |
|
241 if (jft->dir != JINGLE_FT_INCOMING) |
|
242 return FALSE; |
|
243 |
|
244 if (jft->md5 == NULL) { |
|
245 jft->md5 = g_checksum_new(G_CHECKSUM_MD5); |
|
246 } |
|
247 |
|
248 g_checksum_update(jft->md5, (guchar*)data, (gsize)len); |
|
249 |
|
250 // TODO: check if the file already exist or if it was created |
|
251 // during the call to jingle_ft_check and handle_data |
|
252 if (jft->outfile == NULL) { |
|
253 jft->outfile = g_io_channel_new_file(jft->name, "w", &err); |
|
254 if (jft->outfile == NULL || err != NULL) { |
|
255 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s %s", err->message, |
|
256 jft->name); |
|
257 //TODO: propagate the GError ? |
|
258 g_error_free(err); |
|
259 return FALSE; |
|
260 } |
|
261 jft->state = JINGLE_FT_STARTING; |
|
262 status = g_io_channel_set_encoding(jft->outfile, NULL, &err); |
|
263 if (status != G_IO_STATUS_NORMAL || err != NULL) { |
|
264 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s %s", err->message, |
|
265 jft->name); |
|
266 g_error_free(err); |
|
267 return FALSE; |
|
268 } |
|
269 } |
|
270 |
|
271 jft->state = JINGLE_FT_STARTING; |
|
272 |
|
273 status = g_io_channel_write_chars(jft->outfile, data, (gssize) len, |
|
274 &bytes_written, &err); |
|
275 if (status != G_IO_STATUS_NORMAL || err != NULL) { |
|
276 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s %s", err->message, |
|
277 jft->name); |
|
278 g_error_free(err); |
|
279 return FALSE; |
|
280 } |
|
281 status = g_io_channel_flush(jft->outfile, &err); |
|
282 if (status != G_IO_STATUS_NORMAL || err != NULL) { |
|
283 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s %s", err->message, |
|
284 jft->name); |
|
285 g_error_free(err); |
|
286 return FALSE; |
|
287 } |
|
288 |
|
289 if (bytes_written != len) { |
|
290 // not supposed to happen if status is normal, unless outfile is non-blocking |
|
291 return FALSE; |
|
292 } |
|
293 |
|
294 jft->transmit += len; |
|
295 return TRUE; |
|
296 } |
|
297 |
|
298 |
|
299 static int _next_index(void) |
|
300 { |
|
301 static int a = 0; |
|
302 return a++; |
|
303 } |
|
304 |
|
305 static void do_sendfile(char *arg) |
|
306 { |
|
307 char **args = split_arg(arg, 3, 0); |
|
308 gchar *filename; |
|
309 struct stat fileinfo; |
|
310 |
|
311 if (!g_strcmp0(args[0], "send")) { |
|
312 if (!args[1]) { |
|
313 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: give me a name!"); |
|
314 free_arg_lst(args); |
|
315 return; |
|
316 } |
|
317 |
|
318 filename = expand_filename(args[1]); // expand ~ to HOME |
|
319 |
|
320 if (g_stat(filename, &fileinfo) != 0) { |
|
321 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: unable to stat %s", |
|
322 args[1]); |
|
323 free_arg_lst(args); |
|
324 return; |
|
325 } |
|
326 |
|
327 if (!S_ISREG(fileinfo.st_mode) && !S_ISLNK(fileinfo.st_mode)) { |
|
328 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: File doesn't exist!"); |
|
329 free_arg_lst(args); |
|
330 return; |
|
331 } |
|
332 |
|
333 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: Trying to send %s", |
|
334 args[1]); |
|
335 |
|
336 { |
|
337 JingleSession *sess; |
|
338 gchar *sid = jingle_generate_sid(); |
|
339 gchar *ressource, *recipientjid; |
|
340 const gchar *namespaces[] = {NS_JINGLE, NS_JINGLE_APP_FT, NULL}; |
|
341 const gchar *myjid = g_strdup(lm_connection_get_jid(lconnection)); |
|
342 JingleFT *jft = g_new0(JingleFT, 1); |
|
343 GError *err = NULL; |
|
344 |
|
345 if (CURRENT_JID == NULL) { // CURRENT_JID = the jid of the user which has focus |
|
346 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: Please, choose a valid JID in the roster"); |
|
347 free_arg_lst(args); |
|
348 return; |
|
349 } |
|
350 ressource = jingle_find_compatible_res(CURRENT_JID, namespaces); |
|
351 if (ressource == NULL) { |
|
352 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: Cannot send file, because there is no ressource available"); |
|
353 free_arg_lst(args); |
|
354 return; |
|
355 } |
|
356 |
|
357 recipientjid = g_strdup_printf("%s/%s", CURRENT_JID, ressource); |
|
358 |
|
359 sess = session_new(sid, myjid, recipientjid, JINGLE_SESSION_OUTGOING); |
|
360 session_add_content(sess, "file", JINGLE_SESSION_STATE_PENDING); |
|
361 |
|
362 jft->desc = g_strdup(args[1]); |
|
363 jft->type = JINGLE_FT_OFFER; |
|
364 jft->name = g_path_get_basename(filename); |
|
365 jft->date = fileinfo.st_mtime; |
|
366 jft->size = fileinfo.st_size; |
|
367 jft->transmit = 0; |
|
368 jft->state = JINGLE_FT_PENDING; |
|
369 jft->dir = JINGLE_FT_OUTGOING; |
|
370 jft->outfile = g_io_channel_new_file (filename, "r", &err); |
|
371 if (jft->outfile == NULL || err != NULL) { |
|
372 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s %s", err->message, |
|
373 args[1]); |
|
374 g_error_free(err); |
|
375 free_arg_lst(args); |
|
376 return; |
|
377 } |
|
378 |
|
379 g_io_channel_set_encoding(jft->outfile, NULL, &err); |
|
380 if (jft->outfile == NULL || err != NULL) { |
|
381 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s %s", err->message, |
|
382 args[1]); |
|
383 g_error_free(err); |
|
384 free_arg_lst(args); |
|
385 return; |
|
386 } |
|
387 |
|
388 session_add_app(sess, "file", NS_JINGLE_APP_FT, jft); |
|
389 |
|
390 { |
|
391 JingleFTInfo *jfti = g_new0(JingleFTInfo, 1); |
|
392 jfti->index = _next_index(); |
|
393 jfti->jft = jft; |
|
394 info_list = g_slist_append(info_list, jfti); |
|
395 } |
|
396 |
|
397 jingle_handle_app(sess, "file", NS_JINGLE_APP_FT, jft, recipientjid); |
|
398 |
|
399 g_free(ressource); |
|
400 g_free(sid); |
|
401 } |
|
402 } else if (!g_strcmp0(args[0], "info")) { |
|
403 GSList *el = info_list; |
|
404 |
|
405 if (!info_list) |
|
406 scr_LogPrint(LPRINT_LOGNORM, "JFT: no file"); |
|
407 |
|
408 for (el = info_list; el; el = el->next) { |
|
409 JingleFTInfo *jftio = el->data; |
|
410 gchar *strsize = _convert_size(jftio->jft->size); |
|
411 scr_LogPrint(LPRINT_LOGNORM, "[%i]%s %s %s %.2f%%: %s %s", jftio->index, |
|
412 (jftio->jft->dir == JINGLE_FT_INCOMING)?"<==":"-->", |
|
413 jftio->jft->name, strsize, |
|
414 (gfloat)jftio->jft->transmit/(gfloat)jftio->jft->size * 100, |
|
415 jftio->jft->desc?jftio->jft->desc:"", |
|
416 strstate[jftio->jft->state]); |
|
417 g_free(strsize); |
|
418 } |
|
419 } else if (!g_strcmp0(args[0], "flush")) { |
|
420 GSList *el, *el2 = info_list; |
|
421 int count = 0; |
|
422 el = info_list; |
|
423 while (el) { |
|
424 JingleFTInfo *jftinf; |
|
425 jftinf = el->data; |
|
426 if (jftinf->jft->state == JINGLE_FT_ERROR || |
|
427 jftinf->jft->state == JINGLE_FT_REJECT || |
|
428 jftinf->jft->state == JINGLE_FT_ENDING) { |
|
429 count++; |
|
430 _free(jftinf->jft); |
|
431 info_list = g_slist_delete_link(info_list, el); |
|
432 if (info_list == NULL) |
|
433 break; |
|
434 el = el2; |
|
435 continue; |
|
436 } |
|
437 el2 = el; |
|
438 el = el->next; |
|
439 } |
|
440 scr_LogPrint(LPRINT_LOGNORM, "JFT: %i file%s removed", count, (count>1) ? "s" : ""); |
|
441 } else { |
|
442 scr_LogPrint(LPRINT_LOGNORM, "/jft: %s is not a correct option.", args[1]); |
|
443 } |
|
444 |
|
445 free_arg_lst(args); |
|
446 } |
|
447 |
|
448 static void _free(JingleFT *jft) |
|
449 { |
|
450 g_free(jft->hash); |
|
451 g_free(jft->name); |
|
452 g_free(jft->desc); |
|
453 g_io_channel_unref(jft->outfile); |
|
454 if (jft->dir == JINGLE_FT_INCOMING) |
|
455 g_checksum_free(jft->md5); |
|
456 g_free(jft); |
|
457 } |
|
458 |
|
459 static void tomessage(gconstpointer data, LmMessageNode *node) |
|
460 { |
|
461 JingleFT *jft = (JingleFT*) data; |
|
462 gchar *size = NULL; |
|
463 gchar date[19]; |
|
464 |
|
465 if (lm_message_node_get_child(node, "description") != NULL) |
|
466 return; |
|
467 |
|
468 LmMessageNode *node2 = lm_message_node_add_child(node, "description", NULL); |
|
469 lm_message_node_set_attribute(node2, "xmlns", NS_JINGLE_APP_FT); |
|
470 if (jft->type == JINGLE_FT_OFFER) |
|
471 node2 = lm_message_node_add_child(node2, "offer", NULL); |
|
472 else |
|
473 node2 = lm_message_node_add_child(node2, "request", NULL); |
|
474 |
|
475 node2 = lm_message_node_add_child(node2, "file", NULL); |
|
476 |
|
477 size = g_strdup_printf("%" G_GUINT64_FORMAT, jft->size); |
|
478 |
|
479 lm_message_node_set_attributes(node2, "xmlns", NS_SI_FT, |
|
480 "name", jft->name, |
|
481 "size", size, |
|
482 NULL); |
|
483 g_free(size); |
|
484 |
|
485 if (jft->hash != NULL) |
|
486 lm_message_node_set_attribute(node2, "hash", jft->hash); |
|
487 |
|
488 if (jft->date) |
|
489 if (!to_iso8601(date, jft->date)) |
|
490 lm_message_node_set_attribute(node2, "date", date); |
|
491 |
|
492 if (jft->desc != NULL) |
|
493 lm_message_node_add_child(node2, "desc", jft->desc); |
|
494 |
|
495 //if (jft->data != 0) |
|
496 } |
|
497 |
|
498 static void send_hash(const gchar *sid, const gchar *to, const gchar *hash) |
|
499 { |
|
500 JingleAckHandle *ackhandle; |
|
501 GError *err = NULL; |
|
502 gboolean ret; |
|
503 |
|
504 LmMessage *r = lm_message_new_with_sub_type(to, LM_MESSAGE_TYPE_IQ, |
|
505 LM_MESSAGE_SUB_TYPE_SET); |
|
506 LmMessageNode *node = lm_message_get_node(r); |
|
507 lm_message_node_add_child(node, "jingle", NULL); |
|
508 node = lm_message_node_get_child(node, "jingle"); |
|
509 lm_message_node_set_attributes(node, "xmlns", NS_JINGLE, |
|
510 "sid", sid, |
|
511 "action", "session-info", |
|
512 NULL); |
|
513 lm_message_node_add_child(node, "hash", hash); |
|
514 node = lm_message_node_get_child(node, "hash"); |
|
515 lm_message_node_set_attribute(node, "xmlns", NS_JINGLE_APP_FT_INFO); |
|
516 |
|
517 ackhandle = g_new0(JingleAckHandle, 1); |
|
518 ackhandle->callback = NULL; |
|
519 ackhandle->user_data = NULL; |
|
520 |
|
521 ret = lm_connection_send_with_reply(lconnection, r, |
|
522 jingle_new_ack_handler(ackhandle), &err); |
|
523 |
|
524 // It's not really a problem, but we must tell it! |
|
525 if (ret == FALSE || err != NULL) { |
|
526 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: cannot send hash: %s", |
|
527 err->message); |
|
528 g_error_free(err); |
|
529 } |
|
530 |
|
531 lm_message_unref(r); |
|
532 } |
|
533 |
|
534 static void send(session_content *sc) |
|
535 { |
|
536 JingleFT *jft; |
|
537 gchar buf[JINGLE_FT_SIZE_READ]; |
|
538 gsize read; |
|
539 GIOStatus status; |
|
540 int count = 0; |
|
541 GError *err = NULL; |
|
542 |
|
543 JingleSession *sess = session_find_by_sid(sc->sid, sc->from); |
|
544 if (sess == NULL) { |
|
545 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: error before transfer"); |
|
546 // We haven't LmMessage: jingle_send_iq_error(jn->message, "cancel", "item-not-found", "unknown-session"); |
|
547 return; |
|
548 } |
|
549 |
|
550 SessionContent *sc2 = session_find_sessioncontent(sess, sc->name); |
|
551 |
|
552 jft = (JingleFT*)sc2->description; |
|
553 |
|
554 if (jft->dir != JINGLE_FT_OUTGOING) |
|
555 return; |
|
556 |
|
557 do { |
|
558 count++; |
|
559 status = g_io_channel_read_chars(jft->outfile, (gchar*)buf, |
|
560 JINGLE_FT_SIZE_READ, &read, &err); |
|
561 } while (status == G_IO_STATUS_AGAIN && count < 10); |
|
562 |
|
563 if (status == G_IO_STATUS_AGAIN) { |
|
564 // TODO: something better |
|
565 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: file unavailable"); |
|
566 return; |
|
567 } |
|
568 |
|
569 if (status == G_IO_STATUS_ERROR || err != NULL) { |
|
570 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s", err->message); |
|
571 g_error_free(err); |
|
572 return; |
|
573 } |
|
574 |
|
575 if (status == G_IO_STATUS_NORMAL) { |
|
576 jft->transmit += read; |
|
577 g_checksum_update(jft->md5, (guchar*)buf, read); |
|
578 // Call a handle in jingle who will call the trans |
|
579 handle_app_data(sc->sid, sc->from, sc->name, buf, read); |
|
580 } |
|
581 |
|
582 if (status == G_IO_STATUS_EOF) { |
|
583 handle_app_data(sc->sid, sc->from, sc->name, NULL, 0); |
|
584 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: transfer finish (%s)", |
|
585 jft->name); |
|
586 jft->hash = g_strdup(g_checksum_get_string(jft->md5)); |
|
587 jft->state = JINGLE_FT_ENDING; |
|
588 // Call a function to say state is ended |
|
589 session_changestate_sessioncontent(sess, sc2->name, |
|
590 JINGLE_SESSION_STATE_ENDED); |
|
591 // Send the hash |
|
592 send_hash(sess->sid, sess->to, jft->hash); |
|
593 g_checksum_free(jft->md5); |
|
594 |
|
595 if (!session_remove_sessioncontent(sess, sc2->name)) { |
|
596 jingle_send_session_terminate(sess, "success"); |
|
597 session_delete(sess); |
|
598 } |
|
599 } |
|
600 } |
|
601 |
|
602 static void start(session_content *sc) |
|
603 { |
|
604 JingleFT *jft; |
|
605 |
|
606 JingleSession *sess = session_find_by_sid(sc->sid, sc->from); |
|
607 if (sess == NULL) { |
|
608 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: error before transfer"); |
|
609 return; |
|
610 } |
|
611 |
|
612 SessionContent *sc2 = session_find_sessioncontent(sess, sc->name); |
|
613 |
|
614 jft = (JingleFT*)sc2->description; |
|
615 jft->state = JINGLE_FT_STARTING; |
|
616 jft->md5 = g_checksum_new(G_CHECKSUM_MD5); |
|
617 |
|
618 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: Transfer start (%s)", |
|
619 jft->name); |
|
620 |
|
621 sc2->appfuncs->send(sc); |
|
622 } |
|
623 |
|
624 // When we got a session-terminate |
|
625 static void stop(gconstpointer data) |
|
626 { |
|
627 JingleFT *jft = (JingleFT*)data; |
|
628 GError *err = NULL; |
|
629 GIOStatus status; |
|
630 |
|
631 jft->state = JINGLE_FT_ENDING; |
|
632 if (jft->outfile != NULL) { |
|
633 status = g_io_channel_shutdown(jft->outfile, TRUE, &err); |
|
634 if (status != G_IO_STATUS_NORMAL || err != NULL) { |
|
635 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: %s", |
|
636 err->message); |
|
637 g_error_free(err); |
|
638 } |
|
639 } |
|
640 |
|
641 if (jft->hash != NULL && jft->md5 != NULL) { |
|
642 if (g_strcmp0(jft->hash, g_checksum_get_string(jft->md5))) { |
|
643 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: File corrupt (%s)", |
|
644 jft->name); |
|
645 } else { |
|
646 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: transfer finished (%s)" |
|
647 " and verified", jft->name); |
|
648 } |
|
649 } else { |
|
650 scr_LogPrint(LPRINT_LOGNORM, "Jingle File Transfer: transfer finished (%s)" |
|
651 " but not verified", jft->name); |
|
652 } |
|
653 } |
|
654 |
|
655 static gchar *_convert_size(guint64 size) |
|
656 { |
|
657 gchar *strsize; |
|
658 |
|
659 if (size < 1024) |
|
660 strsize = g_strdup_printf("%" G_GUINT64_FORMAT " B", size); |
|
661 else if (size < 1048576) |
|
662 strsize = g_strdup_printf("%.2lf KiB", size/1024.0); |
|
663 else if (size < 1073741824) |
|
664 strsize = g_strdup_printf("%.2lf MiB", size/1048576.0); |
|
665 else if (size < 1099511627776) |
|
666 strsize = g_strdup_printf("%.2lf GiB", size/1073741824.0); |
|
667 |
|
668 return strsize; |
|
669 } |
|
670 |
|
671 static gchar* info(gconstpointer data) |
|
672 { |
|
673 JingleFT *jft = (JingleFT *)data; |
|
674 gchar *info, *strsize = _convert_size(jft->size); |
|
675 info = g_strdup_printf("JFT: Receive %s (%s)", jft->name, strsize); |
|
676 |
|
677 g_free(strsize); |
|
678 |
|
679 return info; |
|
680 } |
|
681 |
|
682 static void jingle_ft_init(void) |
|
683 { |
|
684 jingle_register_app(NS_JINGLE_APP_FT, &funcs, JINGLE_TRANSPORT_STREAMING); |
|
685 xmpp_add_feature(NS_JINGLE_APP_FT); |
|
686 jft_cid = compl_new_category(); |
|
687 if (jft_cid) { |
|
688 compl_add_category_word(jft_cid, "send"); |
|
689 //compl_add_category_word(jft_cid, "request"); |
|
690 compl_add_category_word(jft_cid, "info"); |
|
691 compl_add_category_word(jft_cid, "flush"); |
|
692 } |
|
693 /* Add command */ |
|
694 cmd_add("jft", "Manage file transfer", jft_cid, 0, do_sendfile, NULL); |
|
695 } |
|
696 |
|
697 static void jingle_ft_uninit(void) |
|
698 { |
|
699 g_slist_free(info_list); |
|
700 xmpp_del_feature(NS_JINGLE_APP_FT); |
|
701 jingle_unregister_app(NS_JINGLE_APP_FT); |
|
702 cmd_del("file"); |
|
703 if (jft_cid) |
|
704 compl_del_category(jft_cid); |
|
705 } |