1 /*
2 * gtalk-file-collection.c - Source for GTalkFileCollection
3 *
4 * Copyright (C) 2010 Collabora Ltd.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "config.h"
22 #include "gtalk-file-collection.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
28
29 #define DEBUG_FLAG GABBLE_DEBUG_SHARE
30
31 #include "debug.h"
32 #include "jingle-share.h"
33 #include "namespaces.h"
34 #include "util.h"
35
36 #include <nice/agent.h>
37
38 /*
39 * This GTalk compatible file transfer protocol is a bit complicated, so here
40 * is an explanation on how it works :
41 *
42 * A pseudo-good initial source of information is available here :
43 * http://code.google.com/apis/talk/libjingle/file_share.html
44 *
45 * The current object interaction is like this :
46 *
47 * GabbleFileTransferChannelManager
48 * |
49 * |
50 * |
51 * |
52 * * ref
53 * GabbleFileTransferChannel
54 * weakref * *
55 * / \
56 * / \
57 * / \
58 * / \
59 * / \
60 * / \
61 * / \
62 * ref / \
63 * GTalkFileCollection \
64 * | \
65 * | \
66 * ref | \
67 * WockyJingleSession \
68 * | \ (one at a time)
69 * | \
70 * ref | \
71 * GabbleJingleShare ------------------*- ShareChannel -------- NiceAgent
72 * | |
73 * | |
74 * ref | ref |
75 * GabbleTransportGoogle ----------------*- WockyJingleCandidate PseudoTCP
76 *
77 * The protocol works like this :
78 * Once you receive an invitation, the manifest will contain a number of files
79 * and folders. Some files might have image attributes (width/height) that help
80 * specify that they are images.
81 * If there are images in the invitation, a new ShareChannel gets created and
82 * connectivity must be established. Then on that stream, an HTTP GET request
83 * is sent for each image being transfered with the URL as :
84 * GET <preview-path>/filename?width=X&height=Y
85 * where X and Y are the thumbnail's requested width and height.
86 * The peer should at this point scale down the image to the requested width and
87 * height and send the thumbnail for showing the preview of the image in the FT
88 * UI.
89 * Once the invitation is accepted, a new ShareChannel is created, which will
90 * cause a new NiceAgent to be created and connectivity to be established on that
91 * ShareChannel. The resulting stream is then used as an HTTP server/client to
92 * request files on it using the <source-path> as prefix to the URL.
93 * Simple files are being transferred normally, while directories will be
94 * transferred as a tarball with 'chuncked' Transfer-Encoding since the resulting
95 * size of the tarball isn't known in advance.
96 * Once a file is completely transferred, then the next file is requested on the
97 * same ShareChannel. If all files are transferred, then the <complete> info
98 * action is being sent through the jingle signaling, and the session can then
99 * be terminated safely.
100 *
101 * Since telepathy doesn't currently support image previews, so the 'preview'
102 * ShareChannel is never created with gabble.
103 * Also note that we only create one ShareChannel and we serialize the file
104 * transfers one after the other, they do not each get one ShareChannel and they
105 * cannot be downloaded in parallel.
106 *
107 */
108
109 static gboolean test_mode = FALSE;
110
111 void
gtalk_file_collection_set_test_mode(void)112 gtalk_file_collection_set_test_mode (void)
113 {
114 test_mode = TRUE;
115 }
116
117 G_DEFINE_TYPE (GTalkFileCollection, gtalk_file_collection, G_TYPE_OBJECT);
118
119 /* properties */
120 enum
121 {
122 PROP_TOKEN = 1,
123 LAST_PROPERTY
124 };
125
126 typedef enum
127 {
128 HTTP_SERVER_IDLE,
129 HTTP_SERVER_HEADERS,
130 HTTP_SERVER_SEND,
131 HTTP_CLIENT_IDLE,
132 HTTP_CLIENT_RECEIVE,
133 HTTP_CLIENT_HEADERS,
134 HTTP_CLIENT_CHUNK_SIZE,
135 HTTP_CLIENT_CHUNK_END,
136 HTTP_CLIENT_CHUNK_FINAL,
137 HTTP_CLIENT_BODY,
138 } HttpStatus;
139
140
141 typedef struct
142 {
143 NiceAgent *agent;
144 guint stream_id;
145 guint component_id;
146 gboolean agent_attached;
147 GabbleJingleShare *content;
148 guint share_channel_id;
149 HttpStatus http_status;
150 gchar *status_line;
151 gboolean is_chunked;
152 guint64 content_length;
153 gchar *write_buffer;
154 guint write_len;
155 gchar *read_buffer;
156 guint read_len;
157 } ShareChannel;
158
159
160 typedef enum
161 {
162 GTALK_FT_STATUS_PENDING,
163 GTALK_FT_STATUS_INITIATED,
164 GTALK_FT_STATUS_ACCEPTED,
165 GTALK_FT_STATUS_TRANSFERRING,
166 GTALK_FT_STATUS_WAITING,
167 GTALK_FT_STATUS_TERMINATED
168 } GtalkFtStatus;
169
170 struct _GTalkFileCollectionPrivate
171 {
172 gboolean dispose_has_run;
173
174 GtalkFtStatus status;
175 /* GList of weakreffed GabbleFileTransferChannel */
176 GList *channels;
177 /* GHashTable of GabbleFileTransferChannel => GINT_TO_POINTER (gboolean) */
178 /* the weakref to the channel here is held through the GList *channels */
179 GHashTable *channels_reading;
180 /* GHashTable of GabbleFileTransferChannel => GINT_TO_POINTER (gboolean) */
181 /* the weakref to the channel here is held through the GList *channels */
182 GHashTable *channels_usable;
183 GabbleFileTransferChannel *current_channel;
184 WockyJingleFactory *jingle_factory;
185 WockyJingleSession *jingle;
186 /* ICE component id to jingle share channel association
187 GINT_TO_POINTER (candidate->component) => g_slice_new (ShareChannel) */
188 GHashTable *share_channels;
189 gboolean requested;
190 gchar *token;
191 };
192
193 static void free_share_channel (gpointer data);
194 static void nice_data_received_cb (NiceAgent *agent,
195 guint stream_id, guint component_id, guint len, gchar *buffer,
196 gpointer user_data);
197 static void set_current_channel (GTalkFileCollection *self,
198 GabbleFileTransferChannel *channel);
199 static void channel_disposed (gpointer data, GObject *where_the_object_was);
200
201 static void
gtalk_file_collection_init(GTalkFileCollection * self)202 gtalk_file_collection_init (GTalkFileCollection *self)
203 {
204 GTalkFileCollectionPrivate *priv =
205 G_TYPE_INSTANCE_GET_PRIVATE (self, GTALK_TYPE_FILE_COLLECTION,
206 GTalkFileCollectionPrivate);
207 gchar buf[16];
208 guint32 *uint_buf = (guint32 *) buf;
209 guint i;
210
211
212 DEBUG ("GTalk file collection init called");
213 self->priv = priv;
214
215 self->priv->status = GTALK_FT_STATUS_PENDING;
216
217 self->priv->channels_reading = g_hash_table_new_full (NULL, NULL, NULL, NULL);
218 self->priv->channels_usable = g_hash_table_new_full (NULL, NULL, NULL, NULL);
219
220 self->priv->share_channels = g_hash_table_new_full (NULL, NULL,
221 NULL, free_share_channel);
222
223 for (i = 0; i < sizeof (buf); i++)
224 buf[i] = g_random_int_range (0, 256);
225
226 self->priv->token = g_strdup_printf ("%x%x%x%x",
227 uint_buf[0], uint_buf[1], uint_buf[2], uint_buf[3]);
228
229 /* FIXME: we should start creating a nice agent already and have it start
230 the candidate gathering.. but we don't know which jingle-share transport
231 channel name to assign it to... */
232
233 priv->dispose_has_run = FALSE;
234 }
235
236
237 static void
gtalk_file_collection_dispose(GObject * object)238 gtalk_file_collection_dispose (GObject *object)
239 {
240 GTalkFileCollection *self = GTALK_FILE_COLLECTION (object);
241 GList *i;
242
243 if (self->priv->dispose_has_run)
244 return;
245
246 DEBUG ("dispose called");
247 self->priv->dispose_has_run = TRUE;
248
249 if (self->priv->jingle != NULL)
250 wocky_jingle_session_terminate (self->priv->jingle,
251 WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
252
253 tp_clear_object (&self->priv->jingle);
254
255 set_current_channel (self, NULL);
256
257 tp_clear_pointer (&self->priv->channels_reading, g_hash_table_unref);
258 tp_clear_pointer (&self->priv->channels_usable, g_hash_table_unref);
259 tp_clear_pointer (&self->priv->share_channels, g_hash_table_unref);
260
261 for (i = self->priv->channels; i; i = i->next)
262 {
263 GabbleFileTransferChannel *channel = i->data;
264 g_object_weak_unref (G_OBJECT (channel), channel_disposed, self);
265 }
266
267 g_list_free (self->priv->channels);
268
269 g_free (self->priv->token);
270
271 if (G_OBJECT_CLASS (gtalk_file_collection_parent_class)->dispose)
272 G_OBJECT_CLASS (gtalk_file_collection_parent_class)->dispose (object);
273 }
274
275 static void
gtalk_file_collection_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)276 gtalk_file_collection_get_property (GObject *object,
277 guint property_id,
278 GValue *value,
279 GParamSpec *pspec)
280 {
281 GTalkFileCollection *self = GTALK_FILE_COLLECTION (object);
282
283 switch (property_id)
284 {
285 case PROP_TOKEN:
286 g_value_set_string (value, self->priv->token);
287 break;
288 default:
289 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
290 break;
291 }
292 }
293
294
295 static void
gtalk_file_collection_class_init(GTalkFileCollectionClass * cls)296 gtalk_file_collection_class_init (GTalkFileCollectionClass *cls)
297 {
298 GObjectClass *object_class = G_OBJECT_CLASS (cls);
299
300 g_type_class_add_private (cls, sizeof (GTalkFileCollectionPrivate));
301
302 object_class->get_property = gtalk_file_collection_get_property;
303 object_class->dispose = gtalk_file_collection_dispose;
304
305 g_object_class_install_property (object_class, PROP_TOKEN,
306 g_param_spec_string (
307 "token",
308 "Unique token identifiying the FileCollection",
309 "Token identifying a collection of files",
310 "",
311 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
312 }
313
314
315 static ShareChannel *
get_share_channel(GTalkFileCollection * self,NiceAgent * agent)316 get_share_channel (GTalkFileCollection *self, NiceAgent *agent)
317 {
318 GHashTableIter iter;
319 gpointer key, value;
320 ShareChannel *ret = NULL;
321
322 g_hash_table_iter_init (&iter, self->priv->share_channels);
323 while (g_hash_table_iter_next (&iter, &key, &value))
324 {
325 ShareChannel *share_channel = (ShareChannel *) value;
326 if (share_channel->agent == agent)
327 {
328 ret = share_channel;
329 break;
330 }
331 }
332
333 return ret;
334 }
335
336
337 static GabbleFileTransferChannel *
get_channel_by_filename(GTalkFileCollection * self,gchar * filename)338 get_channel_by_filename (GTalkFileCollection *self, gchar *filename)
339 {
340 GList *i;
341
342 for (i = self->priv->channels; i; i = i->next)
343 {
344 GabbleFileTransferChannel *channel = i->data;
345 gchar *file = NULL;
346
347 g_object_get (channel,
348 "filename", &file,
349 NULL);
350
351 if (strcmp (file, filename) == 0)
352 return channel;
353 }
354
355 return NULL;
356 }
357
358 static void
set_current_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)359 set_current_channel (GTalkFileCollection *self,
360 GabbleFileTransferChannel *channel)
361 {
362 self->priv->current_channel = channel;
363
364 if (channel != NULL)
365 {
366 gboolean reading = FALSE;
367
368 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
369 channel, GTALK_FILE_COLLECTION_STATE_OPEN, FALSE);
370 reading = GPOINTER_TO_INT (g_hash_table_lookup (
371 self->priv->channels_reading, channel));
372 gtalk_file_collection_block_reading (self, channel, !reading);
373 }
374 }
375
376 static gboolean
channel_exists(GTalkFileCollection * self,GabbleFileTransferChannel * channel)377 channel_exists (GTalkFileCollection * self, GabbleFileTransferChannel *channel)
378 {
379 GList *i;
380
381 for (i = self->priv->channels; i; i = i->next)
382 {
383 if (channel == i->data)
384 return TRUE;
385 }
386
387 return FALSE;
388 }
389
390 static void
add_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)391 add_channel (GTalkFileCollection * self, GabbleFileTransferChannel *channel)
392 {
393 self->priv->channels = g_list_append (self->priv->channels, channel);
394 g_hash_table_replace (self->priv->channels_reading, channel,
395 GINT_TO_POINTER (FALSE));
396 g_object_weak_ref (G_OBJECT (channel), channel_disposed, self);
397 }
398
399 static void
del_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)400 del_channel (GTalkFileCollection * self, GabbleFileTransferChannel *channel)
401 {
402 g_return_if_fail (channel_exists (self, channel));
403
404 self->priv->channels = g_list_remove (self->priv->channels, channel);
405 g_hash_table_remove (self->priv->channels_reading, channel);
406 g_hash_table_remove (self->priv->channels_usable, channel);
407 g_object_weak_unref (G_OBJECT (channel), channel_disposed, self);
408 if (self->priv->current_channel == channel)
409 set_current_channel (self, NULL);
410 }
411
412 static void
jingle_session_state_changed_cb(WockyJingleSession * session,GParamSpec * arg1,GTalkFileCollection * self)413 jingle_session_state_changed_cb (WockyJingleSession *session,
414 GParamSpec *arg1,
415 GTalkFileCollection *self)
416 {
417 WockyJingleState state;
418 GList *i;
419
420 DEBUG ("called");
421
422 g_object_get (session,
423 "state", &state,
424 NULL);
425
426 switch (state)
427 {
428 case WOCKY_JINGLE_STATE_INVALID:
429 case WOCKY_JINGLE_STATE_PENDING_CREATED:
430 break;
431 case WOCKY_JINGLE_STATE_PENDING_INITIATE_SENT:
432 case WOCKY_JINGLE_STATE_PENDING_INITIATED:
433 for (i = self->priv->channels; i;)
434 {
435 GabbleFileTransferChannel *channel = i->data;
436
437 i = i->next;
438 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
439 channel, GTALK_FILE_COLLECTION_STATE_PENDING, FALSE);
440 }
441 break;
442 case WOCKY_JINGLE_STATE_PENDING_ACCEPT_SENT:
443 case WOCKY_JINGLE_STATE_ACTIVE:
444 /* Do not set the channels to OPEN unless we're ready to send/receive
445 data from them */
446 if (self->priv->status == GTALK_FT_STATUS_INITIATED)
447 self->priv->status = GTALK_FT_STATUS_ACCEPTED;
448 for (i = self->priv->channels; i;)
449 {
450 GabbleFileTransferChannel *channel = i->data;
451 gboolean usable;
452
453 i = i->next;
454
455 usable = GPOINTER_TO_INT (g_hash_table_lookup (
456 self->priv->channels_usable, channel));
457 if (usable)
458 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
459 channel, GTALK_FILE_COLLECTION_STATE_ACCEPTED, FALSE);
460 }
461 break;
462 case WOCKY_JINGLE_STATE_ENDED:
463 /* Do nothing, let the terminated signal set the correct state
464 depending on the termination reason */
465 default:
466 break;
467 }
468 }
469
470 static void
jingle_session_terminated_cb(WockyJingleSession * session,gboolean local_terminator,WockyJingleReason reason,const gchar * text,gpointer user_data)471 jingle_session_terminated_cb (WockyJingleSession *session,
472 gboolean local_terminator,
473 WockyJingleReason reason,
474 const gchar *text,
475 gpointer user_data)
476 {
477 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
478 GList *i;
479
480 g_assert (session == self->priv->jingle);
481
482 self->priv->status = GTALK_FT_STATUS_TERMINATED;
483
484 for (i = self->priv->channels; i;)
485 {
486 GabbleFileTransferChannel *channel = i->data;
487
488 i = i->next;
489 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
490 channel, GTALK_FILE_COLLECTION_STATE_TERMINATED, local_terminator);
491 }
492 }
493
494 static void
content_new_remote_candidates_cb(WockyJingleContent * content,GList * clist,gpointer user_data)495 content_new_remote_candidates_cb (WockyJingleContent *content,
496 GList *clist, gpointer user_data)
497 {
498 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
499 GList *li;
500
501 DEBUG ("Got new remote candidates : %d", g_list_length (clist));
502
503 for (li = clist; li; li = li->next)
504 {
505 WockyJingleCandidate *candidate = li->data;
506 NiceCandidate *cand = NULL;
507 ShareChannel *share_channel = NULL;
508 GSList *candidates = NULL;
509
510 if (candidate->protocol != WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP)
511 {
512 DEBUG ("Ignoring candidate %s because of non-UDP protocol : %d",
513 candidate->username, candidate->protocol);
514 continue;
515 }
516
517 share_channel = g_hash_table_lookup (self->priv->share_channels,
518 GINT_TO_POINTER (candidate->component));
519 if (share_channel == NULL)
520 {
521 DEBUG ("Ignoring candidate %s because of unknown component id %d",
522 candidate->id, candidate->component);
523 continue;
524 }
525
526 cand = nice_candidate_new (
527 candidate->type == WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL?
528 NICE_CANDIDATE_TYPE_HOST:
529 candidate->type == WOCKY_JINGLE_CANDIDATE_TYPE_STUN?
530 NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
531 NICE_CANDIDATE_TYPE_RELAYED);
532
533
534 cand->transport = WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP;
535 nice_address_init (&cand->addr);
536
537 if (!nice_address_set_from_string (&cand->addr, candidate->address))
538 {
539 DEBUG ("invalid address '%s' in candidate, skipping",
540 candidate->address ? candidate->address : "<null>");
541 nice_candidate_free (cand);
542 continue;
543 }
544
545 nice_address_set_port (&cand->addr, candidate->port);
546 cand->priority = candidate->preference * 1000;
547 cand->stream_id = share_channel->stream_id;
548 cand->component_id = share_channel->component_id;
549 /*
550 if (c->id == NULL)
551 candidate_id = g_strdup_printf ("R%d", ++priv->remote_candidate_count);
552 else
553 candidate_id = c->id;*/
554 if (candidate->id != NULL)
555 strncpy (cand->foundation, candidate->id,
556 NICE_CANDIDATE_MAX_FOUNDATION - 1);
557 else if (candidate->username != NULL)
558 strncpy (cand->foundation, candidate->username,
559 NICE_CANDIDATE_MAX_FOUNDATION - 1);
560
561 cand->username = g_strdup (candidate->username?candidate->username:"");
562 cand->password = g_strdup (candidate->password?candidate->password:"");
563
564 candidates = g_slist_append (candidates, cand);
565 nice_agent_set_remote_candidates (share_channel->agent,
566 share_channel->stream_id, share_channel->component_id, candidates);
567 g_slist_foreach (candidates, (GFunc)nice_candidate_free, NULL);
568 g_slist_free (candidates);
569 }
570 }
571
572 static void
nice_candidate_gathering_done(NiceAgent * agent,guint stream_id,gpointer user_data)573 nice_candidate_gathering_done (NiceAgent *agent, guint stream_id,
574 gpointer user_data)
575 {
576 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
577 ShareChannel *share_channel = get_share_channel (self, agent);
578 WockyJingleContent *content = WOCKY_JINGLE_CONTENT (share_channel->content);
579 GList *candidates = NULL;
580 GList *remote_candidates = NULL;
581 GSList *local_candidates;
582 GSList *li;
583
584 DEBUG ("libnice candidate gathering done!!!!");
585
586 /* Send remote candidates to libnice and listen to new signal */
587 remote_candidates = wocky_jingle_content_get_remote_candidates (content);
588 content_new_remote_candidates_cb (content, remote_candidates, self);
589
590 gabble_signal_connect_weak (content, "new-candidates",
591 (GCallback) content_new_remote_candidates_cb, G_OBJECT (self));
592
593 /* Send gathered local candidates to the content */
594 local_candidates = nice_agent_get_local_candidates (agent, stream_id,
595 share_channel->component_id);
596
597 for (li = local_candidates; li; li = li->next)
598 {
599 NiceCandidate *cand = li->data;
600 WockyJingleCandidate *candidate;
601 gchar ip[NICE_ADDRESS_STRING_LEN];
602
603 nice_address_to_string (&cand->addr, ip);
604
605 candidate = wocky_jingle_candidate_new (
606 /* protocol */
607 cand->transport == NICE_CANDIDATE_TRANSPORT_UDP?
608 WOCKY_JINGLE_TRANSPORT_PROTOCOL_UDP:
609 WOCKY_JINGLE_TRANSPORT_PROTOCOL_TCP,
610 /* candidate type */
611 cand->type == NICE_CANDIDATE_TYPE_HOST?
612 WOCKY_JINGLE_CANDIDATE_TYPE_LOCAL:
613 cand->type == NICE_CANDIDATE_TYPE_RELAYED?
614 WOCKY_JINGLE_CANDIDATE_TYPE_RELAY:
615 WOCKY_JINGLE_CANDIDATE_TYPE_STUN,
616 /* id */
617 cand->foundation,
618 /* component */
619 share_channel->share_channel_id,
620 /* address */
621 ip,
622 /* port */
623 nice_address_get_port (&cand->addr),
624 /* generation */
625 0,
626 /* preference */
627 (gfloat) cand->priority / 1000.0,
628 /* username */
629 cand->username?cand->username:"",
630 /* password */
631 cand->password?cand->password:"",
632 /* network */
633 0);
634
635 candidates = g_list_prepend (candidates, candidate);
636 }
637
638 wocky_jingle_content_add_candidates (content, candidates);
639 }
640
641 static void
nice_component_state_changed(NiceAgent * agent,guint stream_id,guint component_id,guint state,gpointer user_data)642 nice_component_state_changed (NiceAgent *agent, guint stream_id,
643 guint component_id, guint state, gpointer user_data)
644 {
645 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
646 ShareChannel *share_channel = get_share_channel (self, agent);
647 WockyJingleContent *content = WOCKY_JINGLE_CONTENT (share_channel->content);
648 WockyJingleTransportState ts = WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED;
649
650 DEBUG ("libnice component state changed %d!!!!", state);
651
652 switch (state)
653 {
654 case NICE_COMPONENT_STATE_DISCONNECTED:
655 case NICE_COMPONENT_STATE_GATHERING:
656 ts = WOCKY_JINGLE_TRANSPORT_STATE_DISCONNECTED;
657 break;
658 case NICE_COMPONENT_STATE_CONNECTING:
659 ts = WOCKY_JINGLE_TRANSPORT_STATE_CONNECTING;
660 break;
661 case NICE_COMPONENT_STATE_CONNECTED:
662 case NICE_COMPONENT_STATE_READY:
663 ts = WOCKY_JINGLE_TRANSPORT_STATE_CONNECTED;
664 break;
665 case NICE_COMPONENT_STATE_FAILED:
666 {
667 GList *i;
668
669 for (i = self->priv->channels; i;)
670 {
671 GabbleFileTransferChannel *channel = i->data;
672
673 i = i->next;
674 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
675 channel, GTALK_FILE_COLLECTION_STATE_CONNECTION_FAILED,
676 TRUE);
677 }
678 /* return because we don't want to use the content after it
679 has been destroyed.. */
680 return;
681 }
682 }
683 wocky_jingle_content_set_transport_state (content, ts);
684 }
685
686 static void
get_next_manifest_entry(GTalkFileCollection * self,ShareChannel * share_channel,gboolean error)687 get_next_manifest_entry (GTalkFileCollection *self,
688 ShareChannel *share_channel, gboolean error)
689 {
690 GabbleJingleShareManifest *manifest = NULL;
691 GabbleJingleShareManifestEntry *entry = NULL;
692 GabbleFileTransferChannel *channel = NULL;
693 GList *i;
694
695 DEBUG ("called");
696
697 if (self->priv->current_channel != NULL)
698 {
699 if (g_list_length (self->priv->channels) == 1)
700 {
701 WockyJingleContent *content = \
702 WOCKY_JINGLE_CONTENT (share_channel->content);
703
704 DEBUG ("Received all the files. Transfer is complete");
705 wocky_jingle_content_send_complete (content);
706 }
707
708 g_hash_table_replace (self->priv->channels_usable,
709 self->priv->current_channel, GINT_TO_POINTER (FALSE));
710 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
711 self->priv->current_channel,
712 error ? GTALK_FILE_COLLECTION_STATE_ERROR:
713 GTALK_FILE_COLLECTION_STATE_COMPLETED, FALSE);
714
715 set_current_channel (self, NULL);
716 }
717
718 manifest = gabble_jingle_share_get_manifest (share_channel->content);
719 for (i = manifest->entries; i; i = i->next)
720 {
721 gchar *filename = NULL;
722 gboolean usable;
723
724 entry = i->data;
725
726 filename = g_strdup_printf ("%s%s", entry->name,
727 (entry->folder ? ".tar" : ""));
728 channel = get_channel_by_filename (self, filename);
729 g_free (filename);
730 if (channel != NULL)
731 {
732 usable = GPOINTER_TO_INT (g_hash_table_lookup (
733 self->priv->channels_usable, channel));
734 if (usable)
735 break;
736 }
737 entry = NULL;
738 }
739
740 self->priv->status = GTALK_FT_STATUS_WAITING;
741
742
743 if (entry != NULL)
744 {
745 gchar *buffer = NULL;
746 gchar *source_url = manifest->source_url;
747 guint url_len = (source_url != NULL? strlen (source_url) : 0);
748 gchar *separator = "";
749 gchar *filename = NULL;
750
751 if (source_url != NULL && source_url[url_len -1] != '/')
752 separator = "/";
753
754 self->priv->status = GTALK_FT_STATUS_TRANSFERRING;
755
756 filename = g_uri_escape_string (entry->name, NULL, TRUE);
757
758 /* The session initiator will always be the full JID of the peer */
759 buffer = g_strdup_printf ("GET %s%s%s HTTP/1.1\r\n"
760 "Connection: Keep-Alive\r\n"
761 "Content-Length: 0\r\n"
762 "Host: %s:0\r\n" /* e.g. alice@example.com/Empathy:0 */
763 "User-Agent: %s\r\n\r\n",
764 (source_url != NULL ? source_url : ""),
765 separator, filename,
766 wocky_jingle_session_get_initiator (self->priv->jingle),
767 PACKAGE_STRING);
768 g_free (filename);
769
770 /* FIXME: check for success */
771 nice_agent_send (share_channel->agent, share_channel->stream_id,
772 share_channel->component_id, strlen (buffer), buffer);
773 g_free (buffer);
774
775 share_channel->http_status = HTTP_CLIENT_RECEIVE;
776 /* Block or unblock accordingly */
777 set_current_channel (self, channel);
778 }
779 }
780
781 static void
nice_component_writable(NiceAgent * agent,guint stream_id,guint component_id,gpointer user_data)782 nice_component_writable (NiceAgent *agent, guint stream_id, guint component_id,
783 gpointer user_data)
784 {
785 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
786 ShareChannel *share_channel = get_share_channel (self, agent);
787
788 if (share_channel->http_status == HTTP_CLIENT_IDLE)
789 {
790 get_next_manifest_entry (self, share_channel, FALSE);
791 }
792 else if (share_channel->http_status == HTTP_SERVER_SEND)
793 {
794 if (self->priv->current_channel == NULL)
795 {
796 GList *i;
797
798 DEBUG ("Unexpected current_channel == NULL!");
799 for (i = self->priv->channels; i;)
800 {
801 GabbleFileTransferChannel *channel = i->data;
802
803 i = i->next;
804 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
805 channel, GTALK_FILE_COLLECTION_STATE_ERROR, FALSE);
806 }
807 return;
808 }
809 gabble_file_transfer_channel_gtalk_file_collection_write_blocked (
810 self->priv->current_channel, FALSE);
811 if (share_channel->write_buffer != NULL)
812 {
813 gint ret = nice_agent_send (agent, stream_id, component_id,
814 share_channel->write_len, share_channel->write_buffer);
815
816 if (ret < 0 || (guint) ret < share_channel->write_len)
817 {
818 gchar *to_free = share_channel->write_buffer;
819
820 if (ret < 0)
821 ret = 0;
822
823 share_channel->write_buffer = g_memdup (
824 share_channel->write_buffer + ret,
825 share_channel->write_len - ret);
826 share_channel->write_len = share_channel->write_len - ret;
827 g_free (to_free);
828
829 gabble_file_transfer_channel_gtalk_file_collection_write_blocked (
830 self->priv->current_channel, TRUE);
831 }
832 else
833 {
834 g_free (share_channel->write_buffer);
835 share_channel->write_buffer = NULL;
836 share_channel->write_len = 0;
837 }
838 }
839 }
840
841 }
842
843 typedef struct
844 {
845 union {
846 gpointer ptr;
847 GTalkFileCollection *self;
848 } u;
849 ShareChannel *share_channel;
850 } GoogleRelaySessionData;
851
852 static const NiceRelayType relay_type_map[] = {
853 /* WOCKY_JINGLE_RELAY_TYPE_UDP */ NICE_RELAY_TYPE_TURN_UDP,
854 /* WOCKY_JINGLE_RELAY_TYPE_TCP */ NICE_RELAY_TYPE_TURN_TCP,
855 /* WOCKY_JINGLE_RELAY_TYPE_TLS */ NICE_RELAY_TYPE_TURN_TLS,
856 };
857
858 static void
set_relay_info(gpointer item,gpointer user_data)859 set_relay_info (gpointer item, gpointer user_data)
860 {
861 GoogleRelaySessionData *data = user_data;
862 WockyJingleRelay *relay = item;
863 NiceRelayType type;
864
865 g_return_if_fail (relay->type < WOCKY_N_JINGLE_RELAY_TYPES);
866 type = relay_type_map[relay->type];
867
868 nice_agent_set_relay_info (data->share_channel->agent,
869 data->share_channel->stream_id, data->share_channel->component_id,
870 relay->ip, relay->port, relay->username, relay->password, type);
871 }
872
873 static void
google_relay_session_cb(GPtrArray * relays,gpointer user_data)874 google_relay_session_cb (GPtrArray *relays, gpointer user_data)
875 {
876 GoogleRelaySessionData *data = user_data;
877
878 if (data->u.self == NULL)
879 {
880 DEBUG ("Received relay session callback but self got destroyed");
881 g_slice_free (GoogleRelaySessionData, data);
882 return;
883 }
884
885 if (relays != NULL)
886 g_ptr_array_foreach (relays, set_relay_info, user_data);
887
888 nice_agent_gather_candidates (data->share_channel->agent,
889 data->share_channel->stream_id);
890
891 g_object_remove_weak_pointer (G_OBJECT (data->u.self), &data->u.ptr);
892 g_slice_free (GoogleRelaySessionData, data);
893 }
894
895
896 static void
content_new_share_channel_cb(WockyJingleContent * content,const gchar * name,guint share_channel_id,gpointer user_data)897 content_new_share_channel_cb (WockyJingleContent *content, const gchar *name,
898 guint share_channel_id, gpointer user_data)
899 {
900 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
901 ShareChannel *share_channel = g_slice_new0 (ShareChannel);
902 NiceAgent *agent = nice_agent_new_reliable (g_main_context_default (),
903 NICE_COMPATIBILITY_GOOGLE);
904 guint stream_id = nice_agent_add_stream (agent, 1);
905 GList *stun_servers;
906 GoogleRelaySessionData *relay_data = NULL;
907
908 DEBUG ("New Share channel %s was created and linked to id %d", name,
909 share_channel_id);
910
911 if (test_mode)
912 g_object_set (agent, "upnp", FALSE, NULL);
913
914 share_channel->agent = agent;
915 share_channel->stream_id = stream_id;
916 share_channel->component_id = NICE_COMPONENT_TYPE_RTP;
917 share_channel->content = GABBLE_JINGLE_SHARE (content);
918 share_channel->share_channel_id = share_channel_id;
919
920 if (self->priv->requested)
921 share_channel->http_status = HTTP_SERVER_IDLE;
922 else
923 share_channel->http_status = HTTP_CLIENT_IDLE;
924
925 gabble_signal_connect_weak (agent, "candidate-gathering-done",
926 G_CALLBACK (nice_candidate_gathering_done), G_OBJECT (self));
927
928 gabble_signal_connect_weak (agent, "component-state-changed",
929 G_CALLBACK (nice_component_state_changed), G_OBJECT (self));
930
931 gabble_signal_connect_weak (agent, "reliable-transport-writable",
932 G_CALLBACK (nice_component_writable), G_OBJECT (self));
933
934
935 /* Add the agent to the hash table before gathering candidates in case the
936 gathering finishes synchronously, and the callback tries to add local
937 candidates to the content, it needs to find the share channel id.. */
938 g_hash_table_insert (self->priv->share_channels,
939 GINT_TO_POINTER (share_channel_id), share_channel);
940
941 share_channel->agent_attached = TRUE;
942 nice_agent_attach_recv (agent, stream_id, share_channel->component_id,
943 g_main_context_default (), nice_data_received_cb, self);
944
945 stun_servers = wocky_jingle_info_get_stun_servers (
946 wocky_jingle_factory_get_jingle_info (self->priv->jingle_factory));
947 if (stun_servers != NULL)
948 {
949 WockyStunServer *stun_server = stun_servers->data;
950
951 g_object_set (agent,
952 "stun-server", stun_server->address,
953 "stun-server-port", (guint) stun_server->port,
954 NULL);
955
956 g_list_free (stun_servers);
957 }
958
959 relay_data = g_slice_new0 (GoogleRelaySessionData);
960 relay_data->u.self = self;
961 relay_data->share_channel = share_channel;
962 g_object_add_weak_pointer (G_OBJECT (relay_data->u.self),
963 &relay_data->u.ptr);
964 wocky_jingle_info_create_google_relay_session (
965 wocky_jingle_factory_get_jingle_info (self->priv->jingle_factory), 1,
966 google_relay_session_cb, relay_data);
967 }
968
969 static void
content_completed(WockyJingleContent * content,gpointer user_data)970 content_completed (WockyJingleContent *content, gpointer user_data)
971 {
972 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
973 GList *i;
974
975 DEBUG ("Received content completed");
976
977 for (i = self->priv->channels; i;)
978 {
979 GabbleFileTransferChannel *channel = i->data;
980
981 i = i->next;
982 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
983 channel, GTALK_FILE_COLLECTION_STATE_COMPLETED, FALSE);
984 }
985 }
986
987 static void
free_share_channel(gpointer data)988 free_share_channel (gpointer data)
989 {
990 ShareChannel *share_channel = (ShareChannel *) data;
991
992 DEBUG ("Freeing jingle Share channel");
993
994 tp_clear_pointer (&share_channel->write_buffer, g_free);
995 tp_clear_pointer (&share_channel->read_buffer, g_free);
996 g_object_unref (share_channel->agent);
997 g_slice_free (ShareChannel, share_channel);
998 }
999
1000
1001 /* If buffer contains a line ending, 0-terminate the first line and
1002 * return a pointer to the beginning of the next line. Otherwise
1003 * return NULL. */
1004 static gchar *
http_read_line(gchar * buffer,guint len)1005 http_read_line (gchar *buffer, guint len)
1006 {
1007 gchar *p = memchr (buffer, '\n', len);
1008
1009 if (p != NULL)
1010 {
1011 *p = 0;
1012 if (p > buffer && *(p-1) == '\r')
1013 *(p-1) = '\0';
1014 p++;
1015 }
1016
1017 return p;
1018 }
1019
1020 static guint
http_data_received(GTalkFileCollection * self,ShareChannel * share_channel,gchar * buffer,guint len)1021 http_data_received (GTalkFileCollection *self, ShareChannel *share_channel,
1022 gchar *buffer, guint len)
1023 {
1024
1025 switch (share_channel->http_status)
1026 {
1027 case HTTP_SERVER_IDLE:
1028 {
1029 gchar *headers = http_read_line (buffer, len);
1030
1031 if (headers == NULL)
1032 return 0;
1033
1034 share_channel->http_status = HTTP_SERVER_HEADERS;
1035 share_channel->status_line = g_strdup (buffer);
1036
1037 if (self->priv->current_channel != NULL)
1038 {
1039 DEBUG ("Received status line with current channel set");
1040 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1041 self->priv->current_channel,
1042 GTALK_FILE_COLLECTION_STATE_COMPLETED, FALSE);
1043 set_current_channel (self, NULL);
1044 }
1045
1046 return headers - buffer;
1047 }
1048 break;
1049 case HTTP_SERVER_HEADERS:
1050 {
1051 gchar *line = buffer;
1052 gchar *next_line = http_read_line (buffer, len);
1053
1054 if (next_line == NULL)
1055 return 0;
1056
1057 DEBUG ("Found server headers line (%" G_GSIZE_FORMAT ") : %s",
1058 strlen (line), line);
1059 /* FIXME: how about content-length and an actual body ? */
1060 if (line[0] == '\0')
1061 {
1062 gchar *response = NULL;
1063 gchar *get_line = NULL;
1064 GabbleJingleShareManifest *manifest = NULL;
1065 gchar *source_url = NULL;
1066 guint url_len;
1067 gchar *separator = "";
1068 gchar *filename = NULL;
1069 GabbleFileTransferChannel *channel = NULL;
1070
1071 g_assert (self->priv->current_channel == NULL);
1072
1073 DEBUG ("Found empty line, received request : %s ",
1074 share_channel->status_line);
1075
1076 manifest = gabble_jingle_share_get_manifest (
1077 share_channel->content);
1078 source_url = manifest->source_url;
1079 url_len = (source_url != NULL? strlen (source_url) : 0);
1080 if (source_url != NULL && source_url[url_len -1] != '/')
1081 separator = "/";
1082
1083 get_line = g_strdup_printf ("GET %s%s%%s HTTP/1.1",
1084 (source_url != NULL ? source_url : ""),
1085 separator);
1086 filename = g_malloc (strlen (share_channel->status_line));
1087
1088 if (sscanf (share_channel->status_line, get_line, filename) == 1)
1089 {
1090 gchar *unescaped = g_uri_unescape_string (filename, NULL);
1091
1092 g_free (filename);
1093 filename = unescaped;
1094 channel = get_channel_by_filename (self, filename);
1095 }
1096
1097 if (channel != NULL)
1098 {
1099 guint64 size;
1100
1101 g_object_get (channel,
1102 "size", &size,
1103 NULL);
1104
1105 DEBUG ("Found valid filename, result : 200");
1106
1107 share_channel->http_status = HTTP_SERVER_SEND;
1108 response = g_strdup_printf ("HTTP/1.1 200\r\n"
1109 "Connection: Keep-Alive\r\n"
1110 "Content-Length: %" G_GUINT64_FORMAT "\r\n"
1111 "Content-Type: application/octet-stream\r\n\r\n",
1112 size);
1113
1114 }
1115 else
1116 {
1117 DEBUG ("Unable to find valid filename (%s), result : 404",
1118 (filename != NULL? filename : ""));
1119
1120 share_channel->http_status = HTTP_SERVER_IDLE;
1121 response = g_strdup_printf ("HTTP/1.1 404\r\n"
1122 "Connection: Keep-Alive\r\n"
1123 "Content-Length: 0\r\n\r\n");
1124 }
1125
1126 /* FIXME: check for success of nice_agent_send */
1127 nice_agent_send (share_channel->agent, share_channel->stream_id,
1128 share_channel->component_id, strlen (response), response);
1129
1130 g_free (response);
1131 g_free (filename);
1132 g_free (get_line);
1133
1134 /* Now that we sent our response, we can assign the current
1135 channel which sets it to OPEN (if non NULL) so data can
1136 start flowing */
1137 self->priv->status = GTALK_FT_STATUS_TRANSFERRING;
1138 set_current_channel (self, channel);
1139 }
1140
1141 return next_line - buffer;
1142 }
1143 break;
1144 case HTTP_SERVER_SEND:
1145 DEBUG ("received data when we're supposed to be sending data.. "
1146 "not supposed to happen");
1147 break;
1148 case HTTP_CLIENT_IDLE:
1149 DEBUG ("received data when we're supposed to be sending the GET.. "
1150 "not supposed to happen");
1151 break;
1152 case HTTP_CLIENT_RECEIVE:
1153 {
1154 gchar *headers = http_read_line (buffer, len);
1155
1156 if (headers == NULL)
1157 return 0;
1158
1159 share_channel->http_status = HTTP_CLIENT_HEADERS;
1160 share_channel->status_line = g_strdup (buffer);
1161
1162 return headers - buffer;
1163 }
1164 case HTTP_CLIENT_HEADERS:
1165 {
1166 gchar *line = buffer;
1167 gchar *next_line = http_read_line (buffer, len);
1168
1169 if (next_line == NULL)
1170 return 0;
1171
1172 DEBUG ("Found client headers line (%" G_GSIZE_FORMAT ") : %s",
1173 strlen (line), line);
1174 if (line[0] == '\0')
1175 {
1176 DEBUG ("Found empty line, GET response : %s",
1177 share_channel->status_line);
1178
1179 if (g_str_has_prefix (share_channel->status_line,
1180 "HTTP/1.1 200"))
1181 {
1182 if (share_channel->is_chunked)
1183 {
1184 share_channel->http_status = HTTP_CLIENT_CHUNK_SIZE;
1185 }
1186 else
1187 {
1188 share_channel->http_status = HTTP_CLIENT_BODY;
1189 if (share_channel->content_length == 0)
1190 get_next_manifest_entry (self, share_channel, FALSE);
1191 }
1192 }
1193 else
1194 {
1195 /* We expect content-length to be 0 and no chunks for
1196 non-200 statuses (404 error) */
1197 if (share_channel->is_chunked ||
1198 share_channel->content_length != 0)
1199 {
1200 GList *i;
1201
1202 DEBUG ("Unexpected body for non-200 error!");
1203 for (i = self->priv->channels; i;)
1204 {
1205 GabbleFileTransferChannel *channel = i->data;
1206
1207 i = i->next;
1208 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1209 channel, GTALK_FILE_COLLECTION_STATE_ERROR, FALSE);
1210 }
1211 }
1212 else
1213 {
1214 get_next_manifest_entry (self, share_channel, TRUE);
1215 }
1216 }
1217 }
1218 else if (!g_ascii_strncasecmp (line, "Content-Length: ", 16))
1219 {
1220 share_channel->is_chunked = FALSE;
1221 /* Check strtoull read all the length */
1222 share_channel->content_length = g_ascii_strtoull (line + 16,
1223 NULL, 10);
1224 DEBUG ("Found data length : %" G_GUINT64_FORMAT,
1225 share_channel->content_length);
1226 }
1227 else if (!g_ascii_strcasecmp (line,
1228 "Transfer-Encoding: chunked"))
1229 {
1230 share_channel->is_chunked = TRUE;
1231 share_channel->content_length = 0;
1232 DEBUG ("Found file is chunked");
1233 }
1234
1235 return next_line - buffer;
1236 }
1237 break;
1238 case HTTP_CLIENT_CHUNK_SIZE:
1239 {
1240 gchar *line = buffer;
1241 gchar *next_line = http_read_line (buffer, len);
1242
1243 if (next_line == NULL)
1244 return 0;
1245
1246 /* FIXME : check validity of strtoul */
1247 share_channel->content_length = strtoul (line, NULL, 16);
1248 if (share_channel->content_length > 0)
1249 share_channel->http_status = HTTP_CLIENT_BODY;
1250 else
1251 share_channel->http_status = HTTP_CLIENT_CHUNK_FINAL;
1252
1253
1254 return next_line - buffer;
1255 }
1256 break;
1257 case HTTP_CLIENT_BODY:
1258 {
1259 guint consumed = 0;
1260
1261 if (len >= share_channel->content_length)
1262 {
1263 if (self->priv->current_channel == NULL)
1264 {
1265 GList *i;
1266
1267 DEBUG ("Unexpected current_channel == NULL!");
1268 for (i = self->priv->channels; i;)
1269 {
1270 GabbleFileTransferChannel *channel = i->data;
1271
1272 i = i->next;
1273 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1274 channel, GTALK_FILE_COLLECTION_STATE_ERROR, FALSE);
1275 }
1276 /* FIXME: Who knows what might happen here if we got destroyed
1277 It shouldn't crash since our object isn't dereferences
1278 anymore, but.. */
1279 return len;
1280 }
1281 consumed = share_channel->content_length;
1282 gabble_file_transfer_channel_gtalk_file_collection_data_received (
1283 self->priv->current_channel, buffer, consumed);
1284 share_channel->content_length = 0;
1285 if (share_channel->is_chunked)
1286 share_channel->http_status = HTTP_CLIENT_CHUNK_END;
1287 else
1288 get_next_manifest_entry (self, share_channel, FALSE);
1289 }
1290 else
1291 {
1292 consumed = len;
1293 share_channel->content_length -= len;
1294 gabble_file_transfer_channel_gtalk_file_collection_data_received (
1295 self->priv->current_channel, buffer, consumed);
1296 }
1297
1298 return consumed;
1299 }
1300 break;
1301 case HTTP_CLIENT_CHUNK_END:
1302 {
1303 gchar *chunk = http_read_line (buffer, len);
1304
1305 if (chunk == NULL)
1306 return 0;
1307
1308 share_channel->http_status = HTTP_CLIENT_CHUNK_SIZE;
1309
1310 return chunk - buffer;
1311 }
1312 break;
1313 case HTTP_CLIENT_CHUNK_FINAL:
1314 {
1315 gchar *end = http_read_line (buffer, len);
1316
1317 if (end == NULL)
1318 return 0;
1319
1320 share_channel->http_status = HTTP_CLIENT_IDLE;
1321 get_next_manifest_entry (self, share_channel, FALSE);
1322
1323 return end - buffer;
1324 }
1325 break;
1326 }
1327
1328 return 0;
1329 }
1330
1331 static void
nice_data_received_cb(NiceAgent * agent,guint stream_id,guint component_id,guint len,gchar * buffer,gpointer user_data)1332 nice_data_received_cb (NiceAgent *agent,
1333 guint stream_id,
1334 guint component_id,
1335 guint len,
1336 gchar *buffer,
1337 gpointer user_data)
1338 {
1339 GTalkFileCollection *self = GTALK_FILE_COLLECTION (user_data);
1340 ShareChannel *share_channel = get_share_channel (self, agent);
1341 gchar *free_buffer = NULL;
1342
1343 if (share_channel->read_buffer != NULL)
1344 {
1345 gchar *tmp = g_malloc (share_channel->read_len + len);
1346
1347 memcpy (tmp, share_channel->read_buffer, share_channel->read_len);
1348 memcpy (tmp + share_channel->read_len, buffer, len);
1349
1350 free_buffer = buffer = tmp;
1351 len += share_channel->read_len;
1352
1353 g_free (share_channel->read_buffer);
1354 share_channel->read_buffer = NULL;
1355 share_channel->read_len = 0;
1356 }
1357 while (len > 0)
1358 {
1359 guint consumed = http_data_received (self, share_channel, buffer, len);
1360
1361 if (consumed == 0)
1362 {
1363 share_channel->read_buffer = g_memdup (buffer, len);
1364 share_channel->read_len = len;
1365 break;
1366 }
1367 else
1368 {
1369 /* we assume http_data_received never returns consumed > len */
1370 g_assert (consumed <= len);
1371
1372 len -= consumed;
1373 buffer += consumed;
1374 }
1375 }
1376
1377 if (free_buffer != NULL)
1378 g_free (free_buffer);
1379
1380 }
1381
1382 static void
set_session(GTalkFileCollection * self,WockyJingleSession * session,WockyJingleContent * content)1383 set_session (GTalkFileCollection * self,
1384 WockyJingleSession *session, WockyJingleContent *content)
1385 {
1386 self->priv->jingle = g_object_ref (session);
1387
1388 gabble_signal_connect_weak (session, "notify::state",
1389 (GCallback) jingle_session_state_changed_cb, G_OBJECT (self));
1390 gabble_signal_connect_weak (session, "terminated",
1391 (GCallback) jingle_session_terminated_cb, G_OBJECT (self));
1392
1393 gabble_signal_connect_weak (content, "new-share-channel",
1394 (GCallback) content_new_share_channel_cb, G_OBJECT (self));
1395 gabble_signal_connect_weak (content, "completed",
1396 (GCallback) content_completed, G_OBJECT (self));
1397
1398 self->priv->status = GTALK_FT_STATUS_PENDING;
1399 }
1400
1401 GTalkFileCollection *
gtalk_file_collection_new(GabbleFileTransferChannel * channel,WockyJingleFactory * jingle_factory,TpHandle handle,const gchar * jid)1402 gtalk_file_collection_new (GabbleFileTransferChannel *channel,
1403 WockyJingleFactory *jingle_factory, TpHandle handle, const gchar *jid)
1404 {
1405 GTalkFileCollection * self = g_object_new (GTALK_TYPE_FILE_COLLECTION, NULL);
1406 WockyJingleSession *session = NULL;
1407 WockyJingleContent *content = NULL;
1408 gchar *filename;
1409 guint64 size;
1410
1411 self->priv->jingle_factory = jingle_factory;
1412 self->priv->requested = TRUE;
1413
1414 session = wocky_jingle_factory_create_session (jingle_factory,
1415 jid, WOCKY_JINGLE_DIALECT_GTALK4, FALSE);
1416
1417 if (session == NULL)
1418 {
1419 g_object_unref (self);
1420 return NULL;
1421 }
1422
1423 content = wocky_jingle_session_add_content (session,
1424 WOCKY_JINGLE_MEDIA_TYPE_NONE, WOCKY_JINGLE_CONTENT_SENDERS_BOTH, "share",
1425 NS_GOOGLE_SESSION_SHARE, NS_GOOGLE_TRANSPORT_P2P);
1426
1427 if (content == NULL)
1428 {
1429 g_object_unref (self);
1430 g_object_unref (session);
1431 return NULL;
1432 }
1433
1434 g_object_get (channel,
1435 "filename", &filename,
1436 "size", &size,
1437 NULL);
1438 g_object_set (content,
1439 "filename", filename,
1440 "filesize", size,
1441 NULL);
1442
1443 set_session (self, session, content);
1444
1445 add_channel (self, channel);
1446
1447
1448 return self;
1449 }
1450
1451 GTalkFileCollection *
gtalk_file_collection_new_from_session(WockyJingleFactory * jingle_factory,WockyJingleSession * session)1452 gtalk_file_collection_new_from_session (WockyJingleFactory *jingle_factory,
1453 WockyJingleSession *session)
1454 {
1455 GTalkFileCollection * self = NULL;
1456 WockyJingleContent *content = NULL;
1457 GList *cs;
1458
1459 if (wocky_jingle_session_get_content_type (session) !=
1460 GABBLE_TYPE_JINGLE_SHARE)
1461 return NULL;
1462
1463 cs = wocky_jingle_session_get_contents (session);
1464
1465 if (cs != NULL)
1466 {
1467 content = WOCKY_JINGLE_CONTENT (cs->data);
1468 g_list_free (cs);
1469 }
1470
1471 if (content == NULL)
1472 return NULL;
1473
1474 self = g_object_new (GTALK_TYPE_FILE_COLLECTION, NULL);
1475
1476 self->priv->jingle_factory = jingle_factory;
1477 self->priv->requested = FALSE;
1478
1479 set_session (self, session, content);
1480
1481 return self;
1482 }
1483
1484 void
gtalk_file_collection_add_channel(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1485 gtalk_file_collection_add_channel (GTalkFileCollection *self,
1486 GabbleFileTransferChannel *channel)
1487 {
1488 add_channel (self, channel);
1489 }
1490
1491 void
gtalk_file_collection_initiate(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1492 gtalk_file_collection_initiate (GTalkFileCollection *self,
1493 GabbleFileTransferChannel * channel)
1494 {
1495 if (channel_exists (self, channel))
1496 {
1497 g_hash_table_replace (self->priv->channels_reading, channel,
1498 GINT_TO_POINTER (TRUE));
1499 g_hash_table_replace (self->priv->channels_usable, channel,
1500 GINT_TO_POINTER (TRUE));
1501 }
1502
1503 if (self->priv->status == GTALK_FT_STATUS_PENDING)
1504 {
1505 wocky_jingle_session_accept (self->priv->jingle);
1506 self->priv->status = GTALK_FT_STATUS_INITIATED;
1507 }
1508 else
1509 {
1510 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1511 channel, GTALK_FILE_COLLECTION_STATE_ACCEPTED, FALSE);
1512 }
1513
1514 }
1515
1516 void
gtalk_file_collection_accept(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1517 gtalk_file_collection_accept (GTalkFileCollection *self,
1518 GabbleFileTransferChannel * channel)
1519 {
1520 GList *cs = wocky_jingle_session_get_contents (self->priv->jingle);
1521
1522 DEBUG ("called");
1523
1524 if (channel_exists (self, channel))
1525 {
1526 g_hash_table_replace (self->priv->channels_usable, channel,
1527 GINT_TO_POINTER (TRUE));
1528 }
1529
1530 if (self->priv->status == GTALK_FT_STATUS_PENDING)
1531 {
1532 if (cs != NULL)
1533 {
1534 WockyJingleContent *content = WOCKY_JINGLE_CONTENT (cs->data);
1535 guint initial_id = 0;
1536 guint share_channel_id;
1537
1538 wocky_jingle_session_accept (self->priv->jingle);
1539 self->priv->status = GTALK_FT_STATUS_ACCEPTED;
1540
1541 /* The new-share-channel signal will take care of the rest.. */
1542 do
1543 {
1544 gchar *share_channel_name = NULL;
1545
1546 share_channel_name = g_strdup_printf ("gabble-%d", ++initial_id);
1547 share_channel_id = wocky_jingle_content_create_share_channel (
1548 content, share_channel_name);
1549 g_free (share_channel_name);
1550 } while (share_channel_id == 0 && initial_id < 10);
1551
1552 /* FIXME: not assert but actually cancel the FT? */
1553 g_assert (share_channel_id > 0);
1554 g_list_free (cs);
1555 }
1556
1557 }
1558 else
1559 {
1560 gabble_file_transfer_channel_gtalk_file_collection_state_changed (
1561 channel, GTALK_FILE_COLLECTION_STATE_ACCEPTED, FALSE);
1562 }
1563
1564 if (self->priv->status == GTALK_FT_STATUS_WAITING)
1565 {
1566 /* FIXME: this and other lookups should not check for channel '1' */
1567 ShareChannel *share_channel = g_hash_table_lookup (
1568 self->priv->share_channels, GINT_TO_POINTER (1));
1569
1570 get_next_manifest_entry (self, share_channel, FALSE);
1571 }
1572 }
1573
1574 gboolean
gtalk_file_collection_send_data(GTalkFileCollection * self,GabbleFileTransferChannel * channel,const gchar * data,guint length)1575 gtalk_file_collection_send_data (GTalkFileCollection *self,
1576 GabbleFileTransferChannel *channel, const gchar *data, guint length)
1577 {
1578
1579 ShareChannel *share_channel = g_hash_table_lookup (self->priv->share_channels,
1580 GINT_TO_POINTER (1));
1581 gint ret;
1582
1583
1584 g_return_val_if_fail (self->priv->current_channel == channel, FALSE);
1585
1586 ret = nice_agent_send (share_channel->agent, share_channel->stream_id,
1587 share_channel->component_id, length, data);
1588
1589 if (ret < 0 || (guint) ret < length)
1590 {
1591 if (ret < 0)
1592 ret = 0;
1593
1594 share_channel->write_buffer = g_memdup (data + ret,
1595 length - ret);
1596 share_channel->write_len = length - ret;
1597
1598 gabble_file_transfer_channel_gtalk_file_collection_write_blocked (channel,
1599 TRUE);
1600 }
1601 return TRUE;
1602 }
1603
1604 void
gtalk_file_collection_block_reading(GTalkFileCollection * self,GabbleFileTransferChannel * channel,gboolean block)1605 gtalk_file_collection_block_reading (GTalkFileCollection *self,
1606 GabbleFileTransferChannel *channel, gboolean block)
1607 {
1608 ShareChannel *share_channel = g_hash_table_lookup (self->priv->share_channels,
1609 GINT_TO_POINTER (1));
1610
1611 g_assert (channel_exists (self, channel));
1612
1613 if (self->priv->status != GTALK_FT_STATUS_TRANSFERRING)
1614 DEBUG ("Channel %p %s reading ", channel, block?"blocks":"unblocks" );
1615
1616 g_hash_table_replace (self->priv->channels_reading, channel,
1617 GINT_TO_POINTER (!block));
1618
1619 if (channel == self->priv->current_channel)
1620 {
1621 if (block)
1622 {
1623 if (share_channel && share_channel->agent_attached)
1624 {
1625 nice_agent_attach_recv (share_channel->agent,
1626 share_channel->stream_id, share_channel->component_id,
1627 NULL, NULL, NULL);
1628 share_channel->agent_attached = FALSE;
1629 }
1630 }
1631 else
1632 {
1633 if (share_channel && !share_channel->agent_attached)
1634 {
1635 share_channel->agent_attached = TRUE;
1636 nice_agent_attach_recv (share_channel->agent,
1637 share_channel->stream_id, share_channel->component_id,
1638 g_main_context_default (), nice_data_received_cb, self);
1639 }
1640 }
1641 }
1642 }
1643
1644 void
gtalk_file_collection_completed(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1645 gtalk_file_collection_completed (GTalkFileCollection *self,
1646 GabbleFileTransferChannel * channel)
1647 {
1648 ShareChannel *share_channel = g_hash_table_lookup (self->priv->share_channels,
1649 GINT_TO_POINTER (1));
1650
1651 DEBUG ("called");
1652
1653 g_return_if_fail (self->priv->current_channel == channel);
1654
1655 /* We shouldn't set the FT to completed until we receive the 'complete' info
1656 or we receive a new HTTP request otherwise we might terminate the session
1657 and cause a race condition where the peer thinks it got canceled before it
1658 completed. */
1659 share_channel->http_status = HTTP_SERVER_IDLE;
1660 self->priv->status = GTALK_FT_STATUS_WAITING;
1661 }
1662
1663 void
gtalk_file_collection_terminate(GTalkFileCollection * self,GabbleFileTransferChannel * channel)1664 gtalk_file_collection_terminate (GTalkFileCollection *self,
1665 GabbleFileTransferChannel * channel)
1666 {
1667
1668 DEBUG ("called");
1669
1670 if (!channel_exists (self, channel))
1671 return;
1672
1673 if (self->priv->current_channel == channel)
1674 {
1675
1676 del_channel (self, channel);
1677
1678 /* Cancel the whole thing if we terminate the current channel */
1679 if (self->priv->status == GTALK_FT_STATUS_TRANSFERRING)
1680 {
1681
1682 /* The terminate should call our terminated_cb callback which should
1683 terminate all channels which should unref us which will unref the
1684 jingle session */
1685 self->priv->status = GTALK_FT_STATUS_TERMINATED;
1686 wocky_jingle_session_terminate (self->priv->jingle,
1687 WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
1688 return;
1689 }
1690 return;
1691 }
1692 else
1693 {
1694 del_channel (self, channel);
1695
1696 /* If this was the last channel, it will cause it to unref us and
1697 the dispose will be called, which will call
1698 wocky_jingle_session_terminate */
1699 gabble_file_transfer_channel_gtalk_file_collection_state_changed (channel,
1700 GTALK_FILE_COLLECTION_STATE_TERMINATED, TRUE);
1701 }
1702 }
1703
1704
1705 static void
channel_disposed(gpointer data,GObject * object)1706 channel_disposed (gpointer data, GObject *object)
1707 {
1708 GTalkFileCollection *self = data;
1709 GabbleFileTransferChannel *channel = (GabbleFileTransferChannel *) object;
1710
1711 DEBUG ("channel %p got destroyed", channel);
1712
1713 g_return_if_fail (channel_exists (self, channel));
1714
1715 if (self->priv->current_channel == channel)
1716 {
1717 del_channel (self, channel);
1718
1719 /* Cancel the whole thing if we terminate the current channel */
1720 if (self->priv->status == GTALK_FT_STATUS_TRANSFERRING)
1721 {
1722
1723 /* The terminate should call our terminated_cb callback which should
1724 terminate all channels which should unref us which will unref the
1725 jingle session */
1726 self->priv->status = GTALK_FT_STATUS_TERMINATED;
1727 wocky_jingle_session_terminate (self->priv->jingle,
1728 WOCKY_JINGLE_REASON_UNKNOWN, NULL, NULL);
1729 return;
1730 }
1731 }
1732 else
1733 {
1734 del_channel (self, channel);
1735 }
1736 }
1737