Ignore missing README.
[infinote.git] / libinfinity / common / inf-session.c
1 /* libinfinity - a GObject-based infinote implementation
2  * Copyright (C) 2007-2011 Armin Burgmeier <armin@arbur.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
17  * MA 02110-1301, USA.
18  */
19
20 /**
21  * SECTION:inf-session
22  * @title: InfSession
23  * @short_description: Basic session object and synchronization
24  * @include: libinfinity/common/inf-session.h
25  * @stability: Unstable
26  *
27  * #InfSession represents an editing session. The actual type of document that
28  * is edited is not specified, so instantiating #InfSession does not make
29  * any sense. You rather want to use a derived class such as #InfTextSession.
30  * Normally, the #InfcBrowser or #InfdDirectory, respectively, already take
31  * care of instantiating the correct #InfSession.
32  *
33  * A session basically consists of the document being edited (also called
34  * buffer, see #InfBuffer) and the users that are working on the document,
35  * see #InfUserTable.
36  *
37  * A session can either start in %INF_SESSION_RUNNING state, in which it
38  * is created with the initial buffer and user table. It may also start in
39  * %INF_SESSION_SYNCHRONIZING state. In this case, both buffer and user table
40  * are initially empty and are copied from another system over the network.
41  * When the copy is complete, the session enters %INF_SESSION_RUNNING state.
42  *
43  * To be notified about changes other users make to a session, you need to
44  * subscribe to the session (on client side), or wait for incoming
45  * subscriptions (on server side). This is normally done by
46  * infc_browser_iter_subscribe_session(). The first action that is performed
47  * upon subscription is a synchronization as described above. When the
48  * synchronization is complete, the #InfSession::synchronization-complete signal
49  * is emitted.
50  *
51  * After subscription, one can observe modifications other users make, but it is
52  * not possible to make own modifications. Before doing so, a #InfUser needs to
53  * be joined. This is done by client/server specific API such as
54  * infc_session_proxy_join_user() or infd_session_proxy_add_user(). The
55  * required parameters still depend on the actual note type, which is why most
56  * note implementations offer their own API to join a user.
57  **/
58
59 #include <libinfinity/common/inf-session.h>
60 #include <libinfinity/common/inf-buffer.h>
61 #include <libinfinity/common/inf-xml-util.h>
62 #include <libinfinity/common/inf-error.h>
63 #include <libinfinity/communication/inf-communication-object.h>
64 #include <libinfinity/inf-marshal.h>
65 #include <libinfinity/inf-i18n.h>
66 #include <libinfinity/inf-signals.h>
67
68 #include <string.h>
69
70 /* TODO: Set buffer to non-editable during synchronization */
71 /* TODO: Cache requests received by other group members
72  * during synchronization and process them afterwards */
73
74 typedef struct _InfSessionSync InfSessionSync;
75 struct _InfSessionSync {
76   InfCommunicationGroup* group;
77   InfXmlConnection* conn;
78
79   guint messages_total;
80   guint messages_sent;
81   InfSessionSyncStatus status;
82 };
83
84 typedef struct _InfSessionPrivate InfSessionPrivate;
85 struct _InfSessionPrivate {
86   InfCommunicationManager* manager;
87   InfBuffer* buffer;
88   InfUserTable* user_table;
89   InfSessionStatus status;
90
91   /* Group of subscribed connections */
92   InfCommunicationGroup* subscription_group;
93
94   union {
95     /* INF_SESSION_PRESYNC */
96     struct {
97       InfCommunicationGroup* group;
98       InfXmlConnection* conn;
99       gboolean closing;
100     } presync;
101
102     /* INF_SESSION_SYNCHRONIZING */
103     struct {
104       InfCommunicationGroup* group;
105       InfXmlConnection* conn;
106       guint messages_total;
107       guint messages_received;
108       gboolean closing;
109     } sync;
110
111     /* INF_SESSION_RUNNING */
112     struct {
113       GSList* syncs;
114     } run;
115   } shared;
116 };
117
118 typedef struct _InfSessionXmlData InfSessionXmlData;
119 struct _InfSessionXmlData {
120   InfSession* session;
121   xmlNodePtr xml;
122 };
123
124 enum {
125   PROP_0,
126
127   /* construct only */
128   PROP_COMMUNICATION_MANAGER,
129   PROP_BUFFER,
130   PROP_USER_TABLE,
131
132   PROP_STATUS,
133
134   PROP_SYNC_CONNECTION,
135   PROP_SYNC_GROUP,
136
137   /* read/write */
138   PROP_SUBSCRIPTION_GROUP
139 };
140
141 enum {
142   CLOSE,
143   SYNCHRONIZATION_BEGIN,
144   SYNCHRONIZATION_PROGRESS,
145   SYNCHRONIZATION_COMPLETE,
146   SYNCHRONIZATION_FAILED,
147
148   LAST_SIGNAL
149 };
150
151 #define INF_SESSION_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), INF_TYPE_SESSION, InfSessionPrivate))
152
153 static GObjectClass* parent_class;
154 static guint session_signals[LAST_SIGNAL];
155 static GQuark inf_session_sync_error_quark;
156
157 /*
158  * Utility functions.
159  */
160
161 static const gchar*
162 inf_session_sync_strerror(InfSessionSyncError errcode)
163 {
164   switch(errcode)
165   {
166   case INF_SESSION_SYNC_ERROR_GOT_MESSAGE_IN_PRESYNC:
167     return _("Unexpectedly got an XML message in presync");
168   case INF_SESSION_SYNC_ERROR_UNEXPECTED_NODE:
169     return _("Got unexpected XML node during synchronization");
170   case INF_SESSION_SYNC_ERROR_ID_NOT_PRESENT:
171     return _("'id' attribute in user message is missing");
172   case INF_SESSION_SYNC_ERROR_ID_IN_USE:
173     return _("User ID is already in use");
174   case INF_SESSION_SYNC_ERROR_NAME_NOT_PRESENT:
175     return _("'name' attribute in user message is missing");
176   case INF_SESSION_SYNC_ERROR_NAME_IN_USE:
177     return _("User Name is already in use");
178   case INF_SESSION_SYNC_ERROR_CONNECTION_CLOSED:
179     return _("The connection was closed unexpectedly");
180   case INF_SESSION_SYNC_ERROR_SENDER_CANCELLED:
181     return _("The sender cancelled the synchronization");
182   case INF_SESSION_SYNC_ERROR_RECEIVER_CANCELLED:
183     return _("The receiver cancelled the synchronization");
184   case INF_SESSION_SYNC_ERROR_UNEXPECTED_BEGIN_OF_SYNC:
185     return _("Got begin-of-sync message, but synchronization is already "
186              "in progress");
187   case INF_SESSION_SYNC_ERROR_NUM_MESSAGES_MISSING:
188     return _("begin-of-sync message does not contain the number of messages "
189              "to expect");
190   case INF_SESSION_SYNC_ERROR_UNEXPECTED_END_OF_SYNC:
191     return _("Got end-of-sync message, but synchronization is still in "
192              "progress");
193   case INF_SESSION_SYNC_ERROR_EXPECTED_BEGIN_OF_SYNC:
194     return _("Expected begin-of-sync message as first message during "
195              "synchronization");
196   case INF_SESSION_SYNC_ERROR_EXPECTED_END_OF_SYNC:
197     return _("Expected end-of-sync message as last message during "
198              "synchronization");
199   case INF_SESSION_SYNC_ERROR_FAILED:
200     return _("An unknown synchronization error has occured");
201   default:
202     return _("An error with unknown error code occured");
203   }
204 }
205
206 static const gchar*
207 inf_session_get_sync_error_message(GQuark domain,
208                                    guint code)
209 {
210   if(domain == inf_session_sync_error_quark)
211     return inf_session_sync_strerror(code);
212
213   /* TODO: Add a possibilty for sub classes to register their error domains
214    * that can occur in process_xml_sync. Maybe via a translate_error_sync
215    * vfunc. */
216   return _("An error with unknown error domain occured");
217 }
218
219 static GSList*
220 inf_session_find_sync_item_by_connection(InfSession* session,
221                                          InfXmlConnection* conn)
222 {
223   InfSessionPrivate* priv;
224   GSList* item;
225
226   priv = INF_SESSION_PRIVATE(session);
227
228   g_return_val_if_fail(priv->status == INF_SESSION_RUNNING, NULL);
229
230   for(item = priv->shared.run.syncs; item != NULL; item = g_slist_next(item))
231   {
232     if( ((InfSessionSync*)item->data)->conn == conn)
233       return item;
234   }
235
236   return NULL;
237 }
238
239 static InfSessionSync*
240 inf_session_find_sync_by_connection(InfSession* session,
241                                     InfXmlConnection* conn)
242 {
243   GSList* item;
244   item = inf_session_find_sync_item_by_connection(session, conn);
245
246   if(item == NULL) return NULL;
247   return (InfSessionSync*)item->data;
248 }
249
250 /* Required by inf_session_release_connection() */
251 static void
252 inf_session_connection_notify_status_cb(InfXmlConnection* connection,
253                                         GParamSpec* pspec,
254                                         gpointer user_data);
255
256 static void
257 inf_session_release_connection(InfSession* session,
258                                InfXmlConnection* connection)
259 {
260   InfSessionPrivate* priv;
261   InfSessionSync* sync;
262   GSList* item;
263 /*  gboolean has_connection;*/
264
265   priv = INF_SESSION_PRIVATE(session);
266
267   switch(priv->status)
268   {
269   case INF_SESSION_PRESYNC:
270     g_assert(priv->shared.presync.conn == connection);
271     g_assert(priv->shared.presync.group != NULL);
272     g_object_unref(priv->shared.presync.group);
273     priv->shared.presync.conn = NULL;
274     priv->shared.presync.group = NULL;
275     break;
276   case INF_SESSION_SYNCHRONIZING:
277     g_assert(priv->shared.sync.conn == connection);
278     g_assert(priv->shared.sync.group != NULL);
279
280     g_object_unref(priv->shared.sync.group);
281
282     priv->shared.sync.conn = NULL;
283     priv->shared.sync.group = NULL;
284     break;
285   case INF_SESSION_RUNNING:
286     item = inf_session_find_sync_item_by_connection(session, connection);
287     g_assert(item != NULL);
288
289     sync = item->data;
290
291     g_object_unref(sync->group);
292
293     g_slice_free(InfSessionSync, sync);
294     priv->shared.run.syncs = g_slist_delete_link(
295       priv->shared.run.syncs,
296       item
297     );
298
299     break;
300   case INF_SESSION_CLOSED:
301   default:
302     g_assert_not_reached();
303     break;
304   }
305
306   inf_signal_handlers_disconnect_by_func(
307     G_OBJECT(connection),
308     G_CALLBACK(inf_session_connection_notify_status_cb),
309     session
310   );
311
312   g_object_unref(connection);
313 }
314
315 static void
316 inf_session_send_sync_error(InfSession* session,
317                             GError* error)
318 {
319   InfSessionPrivate* priv;
320   xmlNodePtr node;
321
322   priv = INF_SESSION_PRIVATE(session);
323
324   g_return_if_fail(priv->status == INF_SESSION_SYNCHRONIZING);
325   g_return_if_fail(priv->shared.sync.conn != NULL);
326
327   node = inf_xml_util_new_node_from_error(error, NULL, "sync-error");
328
329   inf_communication_group_send_message(
330     priv->shared.sync.group,
331     priv->shared.sync.conn,
332     node
333   );
334 }
335
336 /*
337  * Signal handlers.
338  */
339 static void
340 inf_session_connection_notify_status_cb(InfXmlConnection* connection,
341                                         GParamSpec* pspec,
342                                         gpointer user_data)
343 {
344   InfSession* session;
345   InfSessionPrivate* priv;
346   InfXmlConnectionStatus status;
347   GError* error;
348
349   session = INF_SESSION(user_data);
350   priv = INF_SESSION_PRIVATE(session);
351   error = NULL;
352
353   g_object_get(G_OBJECT(connection), "status", &status, NULL);
354
355   if(status == INF_XML_CONNECTION_CLOSED ||
356      status == INF_XML_CONNECTION_CLOSING)
357   {
358     g_set_error(
359       &error,
360       inf_session_sync_error_quark,
361       INF_SESSION_SYNC_ERROR_CONNECTION_CLOSED,
362       "%s",
363       inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_CONNECTION_CLOSED)
364     );
365
366     switch(priv->status)
367     {
368     case INF_SESSION_PRESYNC:
369       g_assert(connection == priv->shared.presync.conn);
370
371       g_signal_emit(
372         G_OBJECT(session),
373         session_signals[SYNCHRONIZATION_FAILED],
374         0,
375         connection,
376         error
377       );
378
379       break;
380     case INF_SESSION_SYNCHRONIZING:
381       g_assert(connection == priv->shared.sync.conn);
382
383       g_signal_emit(
384         G_OBJECT(session),
385         session_signals[SYNCHRONIZATION_FAILED],
386         0,
387         connection,
388         error
389       );
390
391       break;
392     case INF_SESSION_RUNNING:
393       g_assert(
394         inf_session_find_sync_by_connection(session, connection) != NULL
395       );
396
397       g_signal_emit(
398         G_OBJECT(session),
399         session_signals[SYNCHRONIZATION_FAILED],
400         0,
401         connection,
402         error
403       );
404
405       break;
406     case INF_SESSION_CLOSED:
407     default:
408       g_assert_not_reached();
409       break;
410     }
411
412     g_error_free(error);
413   }
414 }
415
416 /*
417  * GObject overrides.
418  */
419
420 static void
421 inf_session_register_sync(InfSession* session)
422 {
423   /* TODO: Use _constructor */
424   InfSessionPrivate* priv;
425   priv = INF_SESSION_PRIVATE(session);
426
427   /* Register CommunicationObject when all requirements for initial
428    * synchronization are met. */
429   if(priv->status == INF_SESSION_SYNCHRONIZING &&
430      priv->manager != NULL &&
431      priv->shared.sync.conn != NULL &&
432      priv->shared.sync.group != NULL)
433   {
434     g_object_ref(priv->shared.sync.group);
435
436   }
437 }
438
439 static void
440 inf_session_init(GTypeInstance* instance,
441                  gpointer g_class)
442 {
443   InfSession* session;
444   InfSessionPrivate* priv;
445
446   session = INF_SESSION(instance);
447   priv = INF_SESSION_PRIVATE(session);
448
449   priv->manager = NULL;
450   priv->buffer = NULL;
451   priv->user_table = NULL;
452   priv->status = INF_SESSION_RUNNING;
453
454   priv->shared.run.syncs = NULL;
455 }
456
457 static GObject*
458 inf_session_constructor(GType type,
459                         guint n_construct_properties,
460                         GObjectConstructParam* construct_properties)
461 {
462   GObject* object;
463   InfSessionPrivate* priv;
464   InfXmlConnection* sync_conn;
465
466   object = G_OBJECT_CLASS(parent_class)->constructor(
467     type,
468     n_construct_properties,
469     construct_properties
470   );
471
472   priv = INF_SESSION_PRIVATE(object);
473
474   /* Create empty user table if property was not initialized */
475   if(priv->user_table == NULL)
476     priv->user_table = inf_user_table_new();
477
478   switch(priv->status)
479   {
480   case INF_SESSION_PRESYNC:
481     g_assert(priv->shared.presync.conn != NULL &&
482              priv->shared.presync.group != NULL);
483     sync_conn = priv->shared.presync.conn;
484     break;
485   case INF_SESSION_SYNCHRONIZING:
486     g_assert(priv->shared.sync.conn != NULL &&
487              priv->shared.sync.group != NULL);
488     sync_conn = priv->shared.sync.conn;
489     break;
490   case INF_SESSION_RUNNING:
491   case INF_SESSION_CLOSED:
492     sync_conn = NULL;
493     break;
494   default:
495     g_assert_not_reached();
496     break;
497   }
498
499   if(sync_conn != NULL)
500   {
501     g_signal_connect(
502       G_OBJECT(sync_conn),
503       "notify::status",
504       G_CALLBACK(inf_session_connection_notify_status_cb),
505       object
506     );
507   }
508
509   return object;
510 }
511
512 static void
513 inf_session_dispose(GObject* object)
514 {
515   InfSession* session;
516   InfSessionPrivate* priv;
517
518   session = INF_SESSION(object);
519   priv = INF_SESSION_PRIVATE(session);
520
521   if(priv->status != INF_SESSION_CLOSED)
522   {
523     /* Close session. This cancells all running synchronizations and tells
524      * everyone that the session no longer exists. */
525     inf_session_close(session);
526   }
527
528   g_object_unref(G_OBJECT(priv->user_table));
529   priv->user_table = NULL;
530
531   g_object_unref(G_OBJECT(priv->buffer));
532   priv->buffer = NULL;
533
534   g_object_unref(G_OBJECT(priv->manager));
535   priv->manager = NULL;
536
537   G_OBJECT_CLASS(parent_class)->dispose(object);
538 }
539
540 static void
541 inf_session_finalize(GObject* object)
542 {
543   InfSession* session;
544   InfSessionPrivate* priv;
545
546   session = INF_SESSION(object);
547   priv = INF_SESSION_PRIVATE(session);
548
549   G_OBJECT_CLASS(parent_class)->finalize(object);
550 }
551
552 static void
553 inf_session_set_property(GObject* object,
554                          guint prop_id,
555                          const GValue* value,
556                          GParamSpec* pspec)
557 {
558   InfSession* session;
559   InfSessionPrivate* priv;
560
561   session = INF_SESSION(object);
562   priv = INF_SESSION_PRIVATE(session);
563
564   switch(prop_id)
565   {
566   case PROP_COMMUNICATION_MANAGER:
567     g_assert(priv->manager == NULL); /* construct only */
568     priv->manager = INF_COMMUNICATION_MANAGER(g_value_dup_object(value));
569     inf_session_register_sync(session);
570     break;
571   case PROP_BUFFER:
572     g_assert(priv->buffer == NULL); /* construct only */
573     priv->buffer = INF_BUFFER(g_value_dup_object(value));
574     break;
575   case PROP_USER_TABLE:
576     g_assert(priv->user_table == NULL); /* construct only */
577     priv->user_table = INF_USER_TABLE(g_value_dup_object(value));
578     break;
579   case PROP_STATUS:
580     /* construct only, INF_SESSION_RUNNING is the default */
581     g_assert(priv->status == INF_SESSION_RUNNING);
582     priv->status = g_value_get_enum(value);
583     switch(priv->status)
584     {
585     case INF_SESSION_PRESYNC:
586       priv->shared.presync.conn = NULL;
587       priv->shared.presync.group = NULL;
588       priv->shared.presync.closing = FALSE;
589       break;
590     case INF_SESSION_SYNCHRONIZING:
591       priv->shared.sync.conn = NULL;
592       priv->shared.sync.group = NULL;
593       priv->shared.sync.messages_total = 0;
594       priv->shared.sync.messages_received = 0;
595       priv->shared.sync.closing = FALSE;
596       break;
597     case INF_SESSION_RUNNING:
598       /* was default */
599       g_assert(priv->shared.run.syncs == NULL);
600       break;
601     case INF_SESSION_CLOSED:
602       break;
603     default:
604       g_assert_not_reached();
605       break;
606     }
607
608     break;
609   case PROP_SYNC_CONNECTION:
610     /* Need to have status sync or presync to set sync-connection */
611     switch(priv->status)
612     {
613     case INF_SESSION_PRESYNC:
614       g_assert(priv->shared.presync.conn == NULL); /* construct only */
615       priv->shared.presync.conn =
616         INF_XML_CONNECTION(g_value_dup_object(value));
617       break;
618     case INF_SESSION_SYNCHRONIZING:
619       g_assert(priv->shared.sync.conn == NULL); /* construct only */
620       priv->shared.sync.conn =
621         INF_XML_CONNECTION(g_value_dup_object(value));
622       break;
623     case INF_SESSION_RUNNING:
624       g_assert(g_value_get_object(value) == NULL);
625       break;
626     case INF_SESSION_CLOSED:
627     default:
628       g_assert_not_reached();
629       break;
630     }
631
632     break;
633   case PROP_SYNC_GROUP:
634     switch(priv->status)
635     {
636     case INF_SESSION_PRESYNC:
637       g_assert(priv->shared.presync.group == NULL); /* construct only */
638       priv->shared.presync.group =
639         INF_COMMUNICATION_GROUP(g_value_dup_object(value));
640       break;
641     case INF_SESSION_SYNCHRONIZING:
642       g_assert(priv->shared.sync.group == NULL); /* construct only */
643       priv->shared.sync.group =
644         INF_COMMUNICATION_GROUP(g_value_dup_object(value));
645       break;
646     case INF_SESSION_RUNNING:
647       g_assert(g_value_get_object(value) == NULL);
648       break;
649     case INF_SESSION_CLOSED:
650     default:
651       g_assert_not_reached();
652       break;
653     }
654
655     break;
656   case PROP_SUBSCRIPTION_GROUP:
657     if(priv->subscription_group != NULL)
658       g_object_unref(priv->subscription_group);
659
660     priv->subscription_group =
661       INF_COMMUNICATION_GROUP(g_value_dup_object(value));
662
663     break;
664   default:
665     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
666     break;
667   }
668 }
669
670 static void
671 inf_session_get_property(GObject* object,
672                          guint prop_id,
673                          GValue* value,
674                          GParamSpec* pspec)
675 {
676   InfSession* session;
677   InfSessionPrivate* priv;
678
679   session = INF_SESSION(object);
680   priv = INF_SESSION_PRIVATE(session);
681
682   switch(prop_id)
683   {
684   case PROP_COMMUNICATION_MANAGER:
685     g_value_set_object(value, G_OBJECT(priv->manager));
686     break;
687   case PROP_BUFFER:
688     g_value_set_object(value, G_OBJECT(priv->buffer));
689     break;
690   case PROP_USER_TABLE:
691     g_value_set_object(value, G_OBJECT(priv->user_table));
692     break;
693   case PROP_STATUS:
694     g_value_set_enum(value, priv->status);
695     break;
696   case PROP_SYNC_CONNECTION:
697     switch(priv->status)
698     {
699     case INF_SESSION_PRESYNC:
700       g_value_set_object(value, G_OBJECT(priv->shared.presync.conn));
701       break;
702     case INF_SESSION_SYNCHRONIZING:
703       g_value_set_object(value, G_OBJECT(priv->shared.sync.conn));
704       break;
705     case INF_SESSION_RUNNING:
706     case INF_SESSION_CLOSED:
707       g_value_set_object(value, NULL);
708       break;
709     default:
710       g_assert_not_reached();
711       break;
712     }
713
714     break;
715   case PROP_SYNC_GROUP:
716     switch(priv->status)
717     {
718     case INF_SESSION_PRESYNC:
719       g_value_set_object(value, G_OBJECT(priv->shared.presync.group));
720       break;
721     case INF_SESSION_SYNCHRONIZING:
722       g_value_set_object(value, G_OBJECT(priv->shared.sync.group));
723       break;
724     case INF_SESSION_RUNNING:
725     case INF_SESSION_CLOSED:
726       g_value_set_object(value, NULL);
727       break;
728     default:
729       g_assert_not_reached();
730       break;
731     }
732
733     break;
734   case PROP_SUBSCRIPTION_GROUP:
735     g_value_set_object(value, G_OBJECT(priv->subscription_group));
736     break;
737   default:
738     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
739     break;
740   }
741 }
742
743 /*
744  * Network messages
745  */
746
747 static InfCommunicationScope
748 inf_session_handle_user_status_change(InfSession* session,
749                                       InfXmlConnection* connection,
750                                       xmlNodePtr xml,
751                                       GError** error)
752 {
753   InfSessionPrivate* priv;
754   InfUser* user;
755   guint id;
756   xmlChar* status_attr;
757   gboolean has_status;
758   InfUserStatus status;
759
760   priv = INF_SESSION_PRIVATE(session);
761   if(!inf_xml_util_get_attribute_uint_required(xml, "id", &id, error))
762     return INF_COMMUNICATION_SCOPE_PTP;
763
764   user = inf_user_table_lookup_user_by_id(priv->user_table, id);
765   if(user == NULL)
766   {
767     g_set_error(
768       error,
769       inf_user_error_quark(),
770       INF_USER_ERROR_NO_SUCH_USER,
771       _("No such user with ID %u"),
772       id
773     );
774
775     return INF_COMMUNICATION_SCOPE_PTP;
776   }
777
778   if(inf_user_get_status(user) == INF_USER_UNAVAILABLE ||
779      inf_user_get_connection(user) != connection)
780   {
781     g_set_error(
782       error,
783       inf_user_error_quark(),
784       INF_USER_ERROR_NOT_JOINED,
785       "%s",
786       _("User did not join from this connection")
787     );
788
789     return INF_COMMUNICATION_SCOPE_PTP;
790   }
791
792   status_attr = xmlGetProp(xml, (const xmlChar*)"status");
793   has_status =
794     inf_user_status_from_string((const char*)status_attr, &status, error);
795   xmlFree(status_attr);
796   if(!has_status) return FALSE;
797
798   if(inf_user_get_status(user) != status)
799     g_object_set(G_OBJECT(user), "status", status, NULL);
800
801   return INF_COMMUNICATION_SCOPE_GROUP;
802 }
803
804 /*
805  * VFunc implementations.
806  */
807
808 static void
809 inf_session_to_xml_sync_impl_foreach_func(InfUser* user,
810                                           gpointer user_data)
811 {
812   InfSessionXmlData* data;
813   xmlNodePtr usernode;
814
815   data = (InfSessionXmlData*)user_data;
816
817   usernode = xmlNewNode(NULL, (const xmlChar*)"sync-user");
818   inf_session_user_to_xml(data->session, user, usernode);
819
820   xmlAddChild(data->xml, usernode);
821 }
822
823 static void
824 inf_session_to_xml_sync_impl(InfSession* session,
825                              xmlNodePtr parent)
826 {
827   InfSessionPrivate* priv;
828   InfSessionXmlData data;
829
830   priv = INF_SESSION_PRIVATE(session);
831   data.session = session;
832   data.xml = parent;
833
834   inf_user_table_foreach_user(
835     priv->user_table,
836     inf_session_to_xml_sync_impl_foreach_func,
837     &data
838   );
839 }
840
841 static gboolean
842 inf_session_process_xml_sync_impl(InfSession* session,
843                                   InfXmlConnection* connection,
844                                   const xmlNodePtr xml,
845                                   GError** error)
846 {
847   InfSessionPrivate* priv;
848   InfSessionClass* session_class;
849   GArray* user_props;
850   InfUser* user;
851   guint i;
852   const GParameter* param;
853   GParameter* connparam;
854
855   priv = INF_SESSION_PRIVATE(session);
856   session_class = INF_SESSION_GET_CLASS(session);
857
858   g_return_val_if_fail(session_class->get_xml_user_props != NULL, FALSE);
859
860   g_return_val_if_fail(priv->status == INF_SESSION_SYNCHRONIZING, FALSE);
861   g_return_val_if_fail(connection == priv->shared.sync.conn, FALSE);
862
863   if(strcmp((const char*)xml->name, "sync-user") == 0)
864   {
865     user_props = session_class->get_xml_user_props(
866       session,
867       connection,
868       xml
869     );
870
871     param = inf_session_lookup_user_property(
872       (const GParameter*)user_props->data,
873       user_props->len,
874       "status"
875     );
876
877     if(param != NULL &&
878        g_value_get_enum(&param->value) != INF_USER_UNAVAILABLE)
879     {
880       /* Assume that the connection for this available user is the one that
881        * the synchronization comes from if the "connection" property is
882        * not given. */
883       connparam = inf_session_get_user_property(user_props, "connection");
884       if(!G_IS_VALUE(&connparam->value))
885       {
886         g_value_init(&connparam->value, INF_TYPE_XML_CONNECTION);
887         g_value_set_object(&connparam->value, G_OBJECT(connection));
888       }
889     }
890
891     user = inf_session_add_user(
892       session,
893       (GParameter*)user_props->data,
894       user_props->len,
895       error
896     );
897
898     for(i = 0; i < user_props->len; ++ i)
899       g_value_unset(&g_array_index(user_props, GParameter, i).value);
900
901     g_array_free(user_props, TRUE);
902
903     if(user == NULL) return FALSE;
904     return TRUE;
905   }
906   else
907   {
908     g_set_error(
909       error,
910       inf_session_sync_error_quark,
911       INF_SESSION_SYNC_ERROR_UNEXPECTED_NODE,
912       "Received unexpected XML message \"%s\" during synchronization",
913       (const gchar*)xml->name
914     );
915
916     return FALSE;
917   }
918 }
919
920 static InfCommunicationScope
921 inf_session_process_xml_run_impl(InfSession* session,
922                                  InfXmlConnection* connection,
923                                  const xmlNodePtr xml,
924                                  GError** error)
925 {
926   if(strcmp((const char*)xml->name, "user-status-change") == 0)
927   {
928     return inf_session_handle_user_status_change(
929       session,
930       connection,
931       xml,
932       error
933     );
934   }
935   else
936   {
937     /* TODO: Proper error quark and code */
938     g_set_error(
939       error,
940       g_quark_from_static_string("INF_SESSION_ERROR"),
941       0,
942       _("Received unhandled XML message '%s'"),
943       (const gchar*)xml->name
944     );
945
946     return INF_COMMUNICATION_SCOPE_PTP;
947   }
948 }
949
950 static GArray*
951 inf_session_get_xml_user_props_impl(InfSession* session,
952                                     InfXmlConnection* conn,
953                                     const xmlNodePtr xml)
954 {
955   InfSessionPrivate* priv;
956   GArray* array;
957   GParameter* parameter;
958   xmlChar* name;
959   xmlChar* id;
960   xmlChar* status;
961 #if 0
962   xmlChar* connection;
963   InfXmlConnection* real_conn;
964 #endif
965
966   priv = INF_SESSION_PRIVATE(session);
967   array = g_array_sized_new(FALSE, FALSE, sizeof(GParameter), 16);
968
969   name = xmlGetProp(xml, (const xmlChar*)"name");
970   id = xmlGetProp(xml, (const xmlChar*)"id");
971   status = xmlGetProp(xml, (const xmlChar*)"status");
972 #if 0
973   connection = xmlGetProp(xml, (const xmlChar*)"connection");
974 #endif
975
976   if(id != NULL)
977   {
978     parameter = inf_session_get_user_property(array, "id");
979     g_value_init(&parameter->value, G_TYPE_UINT);
980     g_value_set_uint(&parameter->value, strtoul((const gchar*)id, NULL, 10));
981     xmlFree(id);
982   }
983
984   if(name != NULL)
985   {
986     parameter = inf_session_get_user_property(array, "name");
987     g_value_init(&parameter->value, G_TYPE_STRING);
988     g_value_set_string(&parameter->value, (const gchar*)name);
989     xmlFree(name);
990   }
991
992   if(status != NULL)
993   {
994     parameter = inf_session_get_user_property(array, "status");
995     g_value_init(&parameter->value, INF_TYPE_USER_STATUS);
996
997     if(strcmp((const char*)status, "active") == 0)
998       g_value_set_enum(&parameter->value, INF_USER_ACTIVE);
999     else if(strcmp((const char*)status, "inactive") == 0)
1000       g_value_set_enum(&parameter->value, INF_USER_INACTIVE);
1001     else
1002       /* TODO: Error reporting for get_xml_user_props */
1003       g_value_set_enum(&parameter->value, INF_USER_UNAVAILABLE);
1004
1005     xmlFree(status);
1006   }
1007
1008 #if 0
1009   if(connection != NULL)
1010   {
1011     real_conn = inf_connection_manager_group_lookup_connection(
1012       priv->subscription_group,
1013       connection
1014     );
1015
1016     if(real_conn != NULL)
1017     {
1018       parameter = inf_session_get_user_property(array, "connection");
1019       g_value_init(&parameter->value, INF_TYPE_XML_CONNECTION);
1020       g_value_set_object(&parameter->value, G_OBJECT(real_conn));
1021     }
1022     else
1023     {
1024       /* TODO: This should be an error. */
1025     }
1026   }
1027 #endif
1028
1029   return array;
1030 }
1031
1032 static void
1033 inf_session_set_xml_user_props_impl(InfSession* session,
1034                                     const GParameter* params,
1035                                     guint n_params,
1036                                     xmlNodePtr xml)
1037 {
1038   guint i;
1039   gchar id_buf[16];
1040   const gchar* name;
1041   InfUserStatus status;
1042 #if 0
1043   InfXmlConnection* conn;
1044   gchar* remote_address;
1045 #endif
1046
1047   for(i = 0; i < n_params; ++ i)
1048   {
1049     if(strcmp(params[i].name, "id") == 0)
1050     {
1051       sprintf(id_buf, "%u", g_value_get_uint(&params[i].value));
1052       xmlNewProp(xml, (const xmlChar*)"id", (const xmlChar*)id_buf);
1053     }
1054     else if(strcmp(params[i].name, "name") == 0)
1055     {
1056       name = g_value_get_string(&params[i].value);
1057       xmlNewProp(xml, (const xmlChar*)"name", (const xmlChar*)name);
1058     }
1059     else if(strcmp(params[i].name, "status") == 0)
1060     {
1061       status = g_value_get_enum(&params[i].value);
1062
1063       inf_xml_util_set_attribute(
1064         xml,
1065         "status",
1066         inf_user_status_to_string(status)
1067       );
1068     }
1069 /*    else if(strcmp(params[i].name, "connection") == 0)
1070     {
1071       connection = INF_XML_CONNECTION(g_value_get_object(&params[i].value));
1072
1073       g_object_get_property(
1074         G_OBJECT(connection),
1075         "remote-address",
1076         &remote_address,
1077         NULL
1078       );
1079
1080       g_free(addr);
1081     }*/
1082   }
1083 }
1084
1085 static gboolean
1086 inf_session_validate_user_props_impl(InfSession* session,
1087                                      const GParameter* params,
1088                                      guint n_params,
1089                                      InfUser* exclude,
1090                                      GError** error)
1091 {
1092   InfSessionPrivate* priv;
1093   const GParameter* parameter;
1094   const gchar* name;
1095   InfUser* user;
1096   guint id;
1097
1098   priv = INF_SESSION_PRIVATE(session);
1099
1100   /* TODO: Use InfSessionError and/or InfRequestError here */
1101   parameter = inf_session_lookup_user_property(params, n_params, "id");
1102   if(parameter == NULL)
1103   {
1104     g_set_error(
1105       error,
1106       inf_session_sync_error_quark,
1107       INF_SESSION_SYNC_ERROR_ID_NOT_PRESENT,
1108       "%s",
1109       inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_ID_NOT_PRESENT)
1110     );
1111
1112     return FALSE;
1113   }
1114
1115   id = g_value_get_uint(&parameter->value);
1116   user = inf_user_table_lookup_user_by_id(priv->user_table, id);
1117
1118   if(user != NULL && user != exclude)
1119   {
1120     g_set_error(
1121       error,
1122       inf_session_sync_error_quark,
1123       INF_SESSION_SYNC_ERROR_ID_IN_USE,
1124       "%s",
1125       inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_ID_IN_USE)
1126     );
1127
1128     return FALSE;
1129   }
1130
1131   parameter = inf_session_lookup_user_property(params, n_params, "name");
1132   if(parameter == NULL)
1133   {
1134     g_set_error(
1135       error,
1136       inf_session_sync_error_quark,
1137       INF_SESSION_SYNC_ERROR_NAME_NOT_PRESENT,
1138       "%s",
1139       inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_NAME_NOT_PRESENT)
1140     );
1141
1142     return FALSE;
1143   }
1144
1145   name = g_value_get_string(&parameter->value);
1146   user = inf_user_table_lookup_user_by_name(priv->user_table, name);
1147
1148   if(user != NULL && user != exclude)
1149   {
1150     g_set_error(
1151       error,
1152       inf_session_sync_error_quark,
1153       INF_SESSION_SYNC_ERROR_NAME_IN_USE,
1154       "%s",
1155       inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_NAME_IN_USE)
1156     );
1157
1158     return FALSE;
1159   }
1160
1161   return TRUE;
1162 }
1163
1164 /*
1165  * InfCommunicationObject implementation.
1166  */
1167 static gboolean
1168 inf_session_handle_received_sync_message(InfSession* session,
1169                                          InfXmlConnection* connection,
1170                                          const xmlNodePtr node,
1171                                          GError** error)
1172 {
1173   InfSessionClass* session_class;
1174   InfSessionPrivate* priv;
1175   xmlChar* num_messages;
1176   gboolean result;
1177   xmlNodePtr xml_reply;
1178   GError* local_error;
1179
1180   session_class = INF_SESSION_GET_CLASS(session);
1181   priv = INF_SESSION_PRIVATE(session);
1182
1183   g_assert(session_class->process_xml_sync != NULL);
1184   g_assert(priv->status == INF_SESSION_SYNCHRONIZING);
1185
1186   if(strcmp((const gchar*)node->name, "sync-cancel") == 0)
1187   {
1188     local_error = NULL;
1189
1190     g_set_error(
1191       &local_error,
1192       inf_session_sync_error_quark,
1193       INF_SESSION_SYNC_ERROR_SENDER_CANCELLED,
1194       "%s",
1195       inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_SENDER_CANCELLED)
1196     );
1197
1198     g_signal_emit(
1199       G_OBJECT(session),
1200       session_signals[SYNCHRONIZATION_FAILED],
1201       0,
1202       connection,
1203       local_error
1204     );
1205
1206 #if 0
1207     /* Synchronization was cancelled by remote site, so release connection
1208      * prior to closure, otherwise we would try to tell the remote site
1209      * that the session was closed, but there is no point in this because
1210      * it just was the other way around. */
1211     /* Note: This is actually done by the default handler of the 
1212      * synchronization-failed signal. */
1213     inf_session_close(session);
1214 #endif
1215     g_error_free(local_error);
1216
1217     /* Return FALSE, but do not set error because we handled it. Otherwise,
1218      * inf_session_communication_object_received() would try to send a
1219      * sync-error to the synchronizer which is pointless as mentioned
1220      * above. */
1221     return FALSE;
1222   }
1223   else if(strcmp((const gchar*)node->name, "sync-begin") == 0)
1224   {
1225     if(priv->shared.sync.messages_total > 0)
1226     {
1227       g_set_error(
1228         error,
1229         inf_session_sync_error_quark,
1230         INF_SESSION_SYNC_ERROR_UNEXPECTED_BEGIN_OF_SYNC,
1231         "%s",
1232         inf_session_sync_strerror(
1233           INF_SESSION_SYNC_ERROR_UNEXPECTED_BEGIN_OF_SYNC
1234         )
1235       );
1236
1237       return FALSE;
1238     }
1239     else
1240     {
1241       num_messages = xmlGetProp(node, (const xmlChar*)"num-messages");
1242       if(num_messages == NULL)
1243       {
1244         g_set_error(
1245           error,
1246           inf_session_sync_error_quark,
1247           INF_SESSION_SYNC_ERROR_NUM_MESSAGES_MISSING,
1248           "%s",
1249           inf_session_sync_strerror(
1250             INF_SESSION_SYNC_ERROR_NUM_MESSAGES_MISSING
1251           )
1252         );
1253
1254         return FALSE;
1255       }
1256       else
1257       {
1258         /* 2 + [...] because we also count this initial sync-begin message
1259          * and the sync-end. This way, we can use a messages_total of 0 to
1260          * indicate that we did not yet get a sync-begin, even if the
1261          * whole sync does not contain any messages. */
1262         priv->shared.sync.messages_total = 2 + strtoul(
1263           (const gchar*)num_messages,
1264           NULL,
1265           0
1266         );
1267
1268         priv->shared.sync.messages_received = 1;
1269         xmlFree(num_messages);
1270
1271         g_signal_emit(
1272           G_OBJECT(session),
1273           session_signals[SYNCHRONIZATION_PROGRESS],
1274           0,
1275           connection,
1276           1.0 / (double)priv->shared.sync.messages_total
1277         );
1278  
1279         return TRUE;
1280       }
1281     }
1282   }
1283   else if(strcmp((const gchar*)node->name, "sync-end") == 0)
1284   {
1285     ++ priv->shared.sync.messages_received;
1286     if(priv->shared.sync.messages_received !=
1287        priv->shared.sync.messages_total)
1288     {
1289       g_set_error(
1290         error,
1291         inf_session_sync_error_quark,
1292         INF_SESSION_SYNC_ERROR_UNEXPECTED_END_OF_SYNC,
1293         "%s",
1294         inf_session_sync_strerror(
1295           INF_SESSION_SYNC_ERROR_UNEXPECTED_END_OF_SYNC
1296         )
1297       );
1298
1299       return FALSE;
1300     }
1301     else
1302     {
1303       /* Server is waiting for ACK so that he knows the synchronization cannot
1304        * fail anymore. */
1305       xml_reply = xmlNewNode(NULL, (const xmlChar*)"sync-ack");
1306
1307       inf_communication_group_send_message(
1308         priv->shared.sync.group,
1309         connection,
1310         xml_reply
1311       );
1312
1313       /* Synchronization complete */
1314       g_signal_emit(
1315         G_OBJECT(session),
1316         session_signals[SYNCHRONIZATION_COMPLETE],
1317         0,
1318         connection
1319       );
1320
1321       return TRUE;
1322     }
1323   }
1324   else
1325   {
1326     if(priv->shared.sync.messages_received == 0)
1327     {
1328       g_set_error(
1329         error,
1330         inf_session_sync_error_quark,
1331         INF_SESSION_SYNC_ERROR_EXPECTED_BEGIN_OF_SYNC,
1332         "%s",
1333         inf_session_sync_strerror(
1334           INF_SESSION_SYNC_ERROR_EXPECTED_BEGIN_OF_SYNC
1335         )
1336       );
1337
1338       return FALSE;
1339     }
1340     else if(priv->shared.sync.messages_received ==
1341             priv->shared.sync.messages_total - 1)
1342     {
1343       g_set_error(
1344         error,
1345         inf_session_sync_error_quark,
1346         INF_SESSION_SYNC_ERROR_EXPECTED_END_OF_SYNC,
1347         "%s",
1348         inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_EXPECTED_END_OF_SYNC)
1349       );
1350
1351       return FALSE;
1352     }
1353     else
1354     {
1355       result = session_class->process_xml_sync(
1356         session,
1357         connection,
1358         node,
1359         error
1360       );
1361
1362       if(result == FALSE) return FALSE;
1363
1364       /* Some callback could have cancelled the synchronization via
1365        * inf_session_cancel_synchronization. */
1366       if(priv->status == INF_SESSION_CLOSED)
1367         return TRUE;
1368
1369       ++ priv->shared.sync.messages_received;
1370
1371       g_signal_emit(
1372         G_OBJECT(session),
1373         session_signals[SYNCHRONIZATION_PROGRESS],
1374         0,
1375         connection,
1376         (double)priv->shared.sync.messages_received /
1377           (double)priv->shared.sync.messages_total
1378       );
1379
1380       return TRUE;
1381     }
1382   }
1383 }
1384
1385 static void
1386 inf_session_communication_object_sent(InfCommunicationObject* comm_object,
1387                                       InfXmlConnection* connection,
1388                                       const xmlNodePtr node)
1389 {
1390   InfSession* session;
1391   InfSessionPrivate* priv;
1392   InfSessionSync* sync;
1393
1394   session = INF_SESSION(comm_object);
1395   priv = INF_SESSION_PRIVATE(session);
1396
1397   if(priv->status == INF_SESSION_RUNNING)
1398   {
1399     sync = inf_session_find_sync_by_connection(session, connection);
1400
1401     /* This can be any message from some session that is not related to
1402      * the synchronization, so do not assert here. Also, we might already have
1403      * sent stuff that is meant to be processed after the synchronization, so
1404      * make sure here that this messages still belongs to the
1405      * synchronization. */
1406     if(sync != NULL && sync->messages_sent < sync->messages_total)
1407     {
1408       ++ sync->messages_sent;
1409
1410       g_signal_emit(
1411         G_OBJECT(comm_object),
1412         session_signals[SYNCHRONIZATION_PROGRESS],
1413         0,
1414         connection,
1415         (gdouble)sync->messages_sent / (gdouble)sync->messages_total
1416       );
1417
1418       /* We need to wait for the sync-ack before synchronization is
1419        * completed so that the synchronizee still has a chance to tell
1420        * us if something goes wrong. */
1421     }
1422   }
1423 }
1424
1425 static void
1426 inf_session_communication_object_enqueued(InfCommunicationObject* comm_object,
1427                                           InfXmlConnection* connection,
1428                                           const xmlNodePtr node)
1429 {
1430   InfSessionSync* sync;
1431
1432   if(strcmp((const gchar*)node->name, "sync-end") == 0)
1433   {
1434     /* Remember when the last synchronization messages is enqueued because
1435      * we cannot cancel any synchronizations beyond that point. */
1436     sync = inf_session_find_sync_by_connection(
1437       INF_SESSION(comm_object),
1438       connection
1439     );
1440
1441     /* This should really be in the list if the node's name is sync-end,
1442      * otherwise most probably someone else sent a sync-end message via
1443      * this communication object. */
1444     g_assert(sync != NULL);
1445     g_assert(sync->status == INF_SESSION_SYNC_IN_PROGRESS);
1446
1447     sync->status = INF_SESSION_SYNC_AWAITING_ACK;
1448   }
1449 }
1450
1451 static InfCommunicationScope
1452 inf_session_communication_object_received(InfCommunicationObject* comm_object,
1453                                           InfXmlConnection* connection,
1454                                           const xmlNodePtr node,
1455                                           GError** error)
1456 {
1457   InfSessionClass* session_class;
1458   InfSession* session;
1459   InfSessionPrivate* priv;
1460   InfSessionSync* sync;
1461   gboolean result;
1462   GError* local_error;
1463   const gchar* local_message;
1464
1465   session = INF_SESSION(comm_object);
1466   priv = INF_SESSION_PRIVATE(session);
1467
1468   switch(priv->status)
1469   {
1470   case INF_SESSION_PRESYNC:
1471     g_assert(connection == priv->shared.presync.conn);
1472
1473     /* We do not expect any messages in presync. The whole idea of presync is
1474      * that one can keep a session around that is going to be synchronized
1475      * later, ie. when telling the remote site about it. */
1476     g_set_error(
1477       error,
1478       inf_session_sync_error_quark,
1479       INF_SESSION_SYNC_ERROR_GOT_MESSAGE_IN_PRESYNC,
1480       _("Unexpectedly received XML message \"%s\" in presync"),
1481       (const gchar*)node->name
1482     );
1483
1484     /* Don't forward unexpected message */
1485     return INF_COMMUNICATION_SCOPE_PTP;
1486   case INF_SESSION_SYNCHRONIZING:
1487     g_assert(connection == priv->shared.sync.conn);
1488
1489     local_error = NULL;
1490     result = inf_session_handle_received_sync_message(
1491       session,
1492       connection,
1493       node,
1494       &local_error
1495     );
1496
1497     if(result == FALSE && local_error != NULL)
1498     {
1499       inf_session_send_sync_error(session, local_error);
1500
1501       /* Note the default handler resets shared->sync.conn and
1502        * shared->sync.group. */
1503       g_signal_emit(
1504         G_OBJECT(session),
1505         session_signals[SYNCHRONIZATION_FAILED],
1506         0,
1507         connection,
1508         local_error
1509       );
1510
1511       g_error_free(local_error);
1512     }
1513
1514     /* Synchronization is always ptp only, don't forward */
1515     return INF_COMMUNICATION_SCOPE_PTP;
1516   case INF_SESSION_RUNNING:
1517     sync = inf_session_find_sync_by_connection(session, connection);
1518     if(sync != NULL)
1519     {
1520       if(strcmp((const gchar*)node->name, "sync-error") == 0)
1521       {
1522         /* There was an error during synchronization, cancel remaining
1523          * messages. */
1524         inf_communication_group_cancel_messages(sync->group, connection);
1525
1526         local_error = inf_xml_util_new_error_from_node(node);
1527
1528         if(local_error != NULL)
1529         {
1530           local_message =
1531             inf_session_get_sync_error_message(local_error->domain,
1532                                                local_error->code);
1533           if(local_message != NULL)
1534           {
1535             if(local_error->message != NULL)
1536               g_free(local_error->message);
1537             local_error->message = g_strdup(local_message);
1538           }
1539         }
1540         else
1541         {
1542           g_set_error(
1543             &local_error,
1544             inf_session_sync_error_quark,
1545             INF_SESSION_SYNC_ERROR_FAILED,
1546             "%s",
1547             inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_FAILED)
1548           );
1549         }
1550
1551         /* Note the default handler actually removes the sync */
1552         g_signal_emit(
1553           G_OBJECT(session),
1554           session_signals[SYNCHRONIZATION_FAILED],
1555           0,
1556           connection,
1557           local_error
1558         );
1559
1560         g_error_free(local_error);
1561       }
1562       else if(strcmp((const gchar*)node->name, "sync-ack") == 0 &&
1563               sync->status == INF_SESSION_SYNC_AWAITING_ACK)
1564       {
1565         /* Got ack we were waiting for */
1566         g_signal_emit(
1567           G_OBJECT(comm_object),
1568           session_signals[SYNCHRONIZATION_COMPLETE],
1569           0,
1570           connection
1571         );
1572       }
1573       else
1574       {
1575         g_set_error(
1576           error,
1577           inf_session_sync_error_quark,
1578           INF_SESSION_SYNC_ERROR_UNEXPECTED_NODE,
1579           _("Received unexpected XML message \"%s\" while synchronizing"),
1580           (const gchar*)node->name
1581         );
1582       }
1583
1584       /* Synchronization is always ptp only, don't forward */
1585       return INF_COMMUNICATION_SCOPE_PTP;
1586     }
1587     else
1588     {
1589       session_class = INF_SESSION_GET_CLASS(session);
1590       g_assert(session_class->process_xml_run != NULL);
1591
1592       return session_class->process_xml_run(session, connection, node, error);
1593     }
1594
1595     break;
1596   case INF_SESSION_CLOSED:
1597   default:
1598     g_assert_not_reached();
1599     break;
1600   }
1601 }
1602
1603 /*
1604  * Default signal handlers.
1605  */
1606
1607 static void
1608 inf_session_close_handler(InfSession* session)
1609 {
1610   InfSessionPrivate* priv;
1611   InfSessionSync* sync;
1612   xmlNodePtr xml;
1613   GError* error;
1614   InfXmlConnectionStatus status;
1615
1616   priv = INF_SESSION_PRIVATE(session);
1617
1618   error = NULL;
1619
1620   g_set_error(
1621     &error,
1622     inf_session_sync_error_quark,
1623     INF_SESSION_SYNC_ERROR_RECEIVER_CANCELLED,
1624     "%s",
1625     inf_session_sync_strerror(INF_SESSION_SYNC_ERROR_RECEIVER_CANCELLED)
1626   );
1627
1628   g_object_freeze_notify(G_OBJECT(session));
1629
1630   switch(priv->status)
1631   {
1632   case INF_SESSION_PRESYNC:
1633     if(priv->shared.presync.closing == FALSE)
1634     {
1635       /* So that the "synchronization-failed" default signal handler does
1636        * does not emit the close signal again: */
1637       priv->shared.presync.closing = TRUE;
1638
1639       g_signal_emit(
1640         G_OBJECT(session),
1641         session_signals[SYNCHRONIZATION_FAILED],
1642         0,
1643         priv->shared.presync.conn,
1644         error
1645       );
1646     }
1647
1648     inf_session_release_connection(session, priv->shared.presync.conn);
1649     break;
1650   case INF_SESSION_SYNCHRONIZING:
1651     if(priv->shared.sync.closing == FALSE)
1652     {
1653       /* So that the "synchronization-failed" default signal handler does
1654        * does not emit the close signal again: */
1655       priv->shared.sync.closing = TRUE;
1656
1657       /* If the connection was closed, another signal handler could close()
1658        * the session explicitely (for example by disposing it), in which case
1659        * we cannot send anything anymore through the connection even though
1660        * sync.closing was not set already. */
1661       g_object_get(G_OBJECT(priv->shared.sync.conn), "status", &status, NULL);
1662       if(status == INF_XML_CONNECTION_OPEN)
1663       {
1664         inf_session_send_sync_error(session, error);
1665       }
1666
1667       g_signal_emit(
1668         G_OBJECT(session),
1669         session_signals[SYNCHRONIZATION_FAILED],
1670         0,
1671         priv->shared.sync.conn,
1672         error
1673       );
1674     }
1675
1676     inf_session_release_connection(session, priv->shared.sync.conn);
1677     break;
1678   case INF_SESSION_RUNNING:
1679     /* TODO: Set status of all users (except local) to unavailable? We
1680      * probably should do that here instead of in the session proxies,
1681      * or at least in addition (InfcSessionProxy needs to do it anway,
1682      * because it keeps the running state even on disconnection...) */
1683
1684     while(priv->shared.run.syncs != NULL)
1685     {
1686       sync = (InfSessionSync*)priv->shared.run.syncs->data;
1687
1688       /* If the sync-end message has already been enqueued within the
1689        * connection manager, we cannot cancel it anymore, so the remote
1690        * site will receive the full sync nevertheless, so we do not need
1691        * to cancel anything. */
1692       if(sync->status == INF_SESSION_SYNC_IN_PROGRESS)
1693       {
1694         inf_communication_group_cancel_messages(sync->group, sync->conn);
1695
1696         xml = xmlNewNode(NULL, (const xmlChar*)"sync-cancel");
1697         inf_communication_group_send_message(sync->group, sync->conn, xml);
1698       }
1699
1700       /* We had to cancel the synchronization, so the synchronization
1701        * actually failed. */
1702       g_signal_emit(
1703         G_OBJECT(session),
1704         session_signals[SYNCHRONIZATION_FAILED],
1705         0,
1706         sync->conn,
1707         error
1708       );
1709
1710       /* Note that the synchronization_failed default handler actualy
1711        * removes the sync. */
1712     }
1713
1714     break;
1715   case INF_SESSION_CLOSED:
1716   default:
1717     g_assert_not_reached();
1718     break;
1719   }
1720
1721   if(priv->subscription_group != NULL)
1722   {
1723     g_object_unref(priv->subscription_group);
1724     priv->subscription_group = NULL;
1725
1726     g_object_notify(G_OBJECT(session), "subscription-group");
1727   }
1728
1729   g_error_free(error);
1730
1731   priv->status = INF_SESSION_CLOSED;
1732   g_object_notify(G_OBJECT(session), "status");
1733   g_object_thaw_notify(G_OBJECT(session));
1734 }
1735
1736 static void
1737 inf_session_synchronization_begin_handler(InfSession* session,
1738                                           InfCommunicationGroup* group,
1739                                           InfXmlConnection* connection)
1740 {
1741   InfSessionPrivate* priv;
1742   InfSessionClass* session_class;
1743   InfSessionSync* sync;
1744   xmlNodePtr messages;
1745   xmlNodePtr next;
1746   xmlNodePtr xml;
1747   gchar num_messages_buf[16];
1748
1749   priv = INF_SESSION_PRIVATE(session);
1750
1751   g_assert(priv->status == INF_SESSION_RUNNING);
1752   g_assert(inf_session_find_sync_by_connection(session, connection) == NULL);
1753
1754   session_class = INF_SESSION_GET_CLASS(session);
1755   g_return_if_fail(session_class->to_xml_sync != NULL);
1756
1757   sync = g_slice_new(InfSessionSync);
1758   sync->conn = connection;
1759   sync->messages_sent = 0;
1760   sync->messages_total = 2; /* including sync-begin and sync-end */
1761   sync->status = INF_SESSION_SYNC_IN_PROGRESS;
1762
1763   g_object_ref(G_OBJECT(connection));
1764   priv->shared.run.syncs = g_slist_prepend(priv->shared.run.syncs, sync);
1765
1766   g_signal_connect_after(
1767     G_OBJECT(connection),
1768     "notify::status",
1769     G_CALLBACK(inf_session_connection_notify_status_cb),
1770     session
1771   );
1772
1773   sync->group = group;
1774   g_object_ref(sync->group);
1775
1776   /* The group needs to contain that connection, of course. */
1777   g_assert(inf_communication_group_is_member(sync->group, connection));
1778
1779   /* Name is irrelevant because the node is only used to collect the child
1780    * nodes via the to_xml_sync vfunc. */
1781   messages = xmlNewNode(NULL, (const xmlChar*)"sync-container");
1782   session_class->to_xml_sync(session, messages);
1783
1784   for(xml = messages->children; xml != NULL; xml = xml->next)
1785     ++ sync->messages_total;
1786
1787   sprintf(num_messages_buf, "%u", sync->messages_total - 2);
1788
1789   xml = xmlNewNode(NULL, (const xmlChar*)"sync-begin");
1790
1791   xmlNewProp(
1792     xml,
1793     (const xmlChar*)"num-messages",
1794     (const xmlChar*)num_messages_buf
1795   );
1796
1797   inf_communication_group_send_message(sync->group, connection, xml);
1798
1799   /* TODO: Add a function that can send multiple messages */
1800   for(xml = messages->children; xml != NULL; xml = next)
1801   {
1802     next = xml->next;
1803     xmlUnlinkNode(xml);
1804
1805     inf_communication_group_send_message(sync->group, connection, xml);
1806   }
1807
1808   xmlFreeNode(messages);
1809   xml = xmlNewNode(NULL, (const xmlChar*)"sync-end");
1810   inf_communication_group_send_message(sync->group, connection, xml);
1811 }
1812
1813 static void
1814 inf_session_synchronization_complete_handler(InfSession* session,
1815                                              InfXmlConnection* connection)
1816 {
1817   InfSessionPrivate* priv;
1818   priv = INF_SESSION_PRIVATE(session);
1819
1820   switch(priv->status)
1821   {
1822   case INF_SESSION_PRESYNC:
1823     g_assert_not_reached();
1824     break;
1825   case INF_SESSION_SYNCHRONIZING:
1826     g_assert(connection == priv->shared.sync.conn);
1827
1828     inf_session_release_connection(session, connection);
1829
1830     priv->status = INF_SESSION_RUNNING;
1831     priv->shared.run.syncs = NULL;
1832
1833     g_object_notify(G_OBJECT(session), "status");
1834     break;
1835   case INF_SESSION_RUNNING:
1836     g_assert(
1837       inf_session_find_sync_by_connection(session, connection) != NULL
1838     );
1839
1840     inf_session_release_connection(session, connection);
1841     break;
1842   case INF_SESSION_CLOSED:
1843   default:
1844     g_assert_not_reached();
1845     break;
1846   }
1847 }
1848
1849 static void
1850 inf_session_synchronization_failed_handler(InfSession* session,
1851                                            InfXmlConnection* connection,
1852                                            const GError* error)
1853 {
1854   InfSessionPrivate* priv;
1855   priv = INF_SESSION_PRIVATE(session);
1856
1857   switch(priv->status)
1858   {
1859   case INF_SESSION_PRESYNC:
1860     g_assert(connection == priv->shared.presync.conn);
1861     if(priv->shared.presync.closing == FALSE)
1862     {
1863       /* So that the "close" default signal handler does not emit the 
1864        * "synchronization failed" signal again. */
1865       priv->shared.presync.closing = TRUE;
1866       inf_session_close(session);
1867     }
1868
1869     break;
1870   case INF_SESSION_SYNCHRONIZING:
1871     g_assert(connection == priv->shared.sync.conn);
1872     if(priv->shared.sync.closing == FALSE)
1873     {
1874       /* So that the "close" default signal handler does not emit the 
1875        * "synchronization failed" signal again. */
1876       priv->shared.sync.closing = TRUE;
1877       inf_session_close(session);
1878     }
1879
1880     break;
1881   case INF_SESSION_RUNNING:
1882     g_assert(
1883       inf_session_find_sync_by_connection(session, connection) != NULL
1884     );
1885
1886     inf_session_release_connection(session, connection);
1887     break;
1888   case INF_SESSION_CLOSED:
1889     /* Don't assert, some signal handler could have called inf_session_close()
1890      * between the start of the signal emission and the run of the default
1891      * handler. */
1892     break;
1893   default:
1894     g_assert_not_reached();
1895     break;
1896   }
1897 }
1898
1899 /*
1900  * Gype registration.
1901  */
1902
1903 static void
1904 inf_session_class_init(gpointer g_class,
1905                        gpointer class_data)
1906 {
1907   GObjectClass* object_class;
1908   InfSessionClass* session_class;
1909
1910   object_class = G_OBJECT_CLASS(g_class);
1911   session_class = INF_SESSION_CLASS(g_class);
1912
1913   parent_class = G_OBJECT_CLASS(g_type_class_peek_parent(g_class));
1914   g_type_class_add_private(g_class, sizeof(InfSessionPrivate));
1915
1916   object_class->constructor = inf_session_constructor;
1917   object_class->dispose = inf_session_dispose;
1918   object_class->finalize = inf_session_finalize;
1919   object_class->set_property = inf_session_set_property;
1920   object_class->get_property = inf_session_get_property;
1921
1922   session_class->to_xml_sync = inf_session_to_xml_sync_impl;
1923   session_class->process_xml_sync = inf_session_process_xml_sync_impl;
1924   session_class->process_xml_run = inf_session_process_xml_run_impl;
1925
1926   session_class->get_xml_user_props = inf_session_get_xml_user_props_impl;
1927   session_class->set_xml_user_props = inf_session_set_xml_user_props_impl;
1928   session_class->validate_user_props = inf_session_validate_user_props_impl;
1929
1930   session_class->user_new = NULL;
1931
1932   session_class->close = inf_session_close_handler;
1933   session_class->synchronization_begin =
1934     inf_session_synchronization_begin_handler;
1935   session_class->synchronization_progress = NULL;
1936   session_class->synchronization_complete =
1937     inf_session_synchronization_complete_handler;
1938   session_class->synchronization_failed =
1939     inf_session_synchronization_failed_handler;
1940
1941   inf_session_sync_error_quark = g_quark_from_static_string(
1942     "INF_SESSION_SYNC_ERROR"
1943   );
1944
1945   g_object_class_install_property(
1946     object_class,
1947     PROP_COMMUNICATION_MANAGER,
1948     g_param_spec_object(
1949       "communication-manager",
1950       "Communication manager",
1951       "The communication manager used for sending requests",
1952       INF_COMMUNICATION_TYPE_MANAGER,
1953       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
1954     )
1955   );
1956
1957   g_object_class_install_property(
1958     object_class,
1959     PROP_BUFFER,
1960     g_param_spec_object(
1961       "buffer",
1962       "Buffer",
1963       "The buffer in which the document content is stored",
1964       INF_TYPE_BUFFER,
1965       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
1966     )
1967   );
1968
1969   g_object_class_install_property(
1970     object_class,
1971     PROP_USER_TABLE,
1972     g_param_spec_object(
1973       "user-table",
1974       "User table",
1975       "User table containing the users of the session",
1976       INF_TYPE_USER_TABLE,
1977       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
1978     )
1979   );
1980
1981   g_object_class_install_property(
1982     object_class,
1983     PROP_STATUS,
1984     g_param_spec_enum(
1985       "status",
1986       "Session Status",
1987       "Current status of the session",
1988       INF_TYPE_SESSION_STATUS,
1989       INF_SESSION_RUNNING,
1990       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
1991     )
1992   );
1993
1994   g_object_class_install_property(
1995     object_class,
1996     PROP_SYNC_CONNECTION,
1997     g_param_spec_object(
1998       "sync-connection",
1999       "Synchronizing connection",
2000       "Connection which synchronizes the initial session state",
2001       INF_TYPE_XML_CONNECTION,
2002       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
2003     )
2004   );
2005
2006   g_object_class_install_property(
2007     object_class,
2008     PROP_SYNC_GROUP,
2009     g_param_spec_object(
2010       "sync-group",
2011       "Synchronization group",
2012       "Communication group in which to perform synchronization",
2013       INF_COMMUNICATION_TYPE_GROUP,
2014       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
2015     )
2016   );
2017
2018   g_object_class_install_property(
2019     object_class,
2020     PROP_SUBSCRIPTION_GROUP,
2021     g_param_spec_object(
2022       "subscription-group",
2023       "Subscription group",
2024       "Communication group of subscribed connections",
2025       INF_COMMUNICATION_TYPE_GROUP,
2026       G_PARAM_READWRITE
2027     )
2028   );
2029
2030   /**
2031    * InfSession::close:
2032    * @session: The #InfSession that is being closed
2033    *
2034    * This signal is emitted if the session is closed. Note that this signal
2035    * is not called as a client if the connection to the sessions has merely
2036    * been lost, only the relevant #InfXmlConnection has its
2037    * #InfXmlConnection:status property changed.
2038    */
2039   session_signals[CLOSE] = g_signal_new(
2040     "close",
2041     G_OBJECT_CLASS_TYPE(object_class),
2042     G_SIGNAL_RUN_LAST,
2043     G_STRUCT_OFFSET(InfSessionClass, close),
2044     NULL, NULL,
2045     inf_marshal_VOID__VOID,
2046     G_TYPE_NONE,
2047     0
2048   );
2049
2050   /**
2051    * InfSession::synchronization-begin:
2052    * @session: The #InfSession that is synchronizing.
2053    * @group: The #InfCommunicationGroup used for synchronization.
2054    * @connection: The #InfXmlConnection to which the session is synchronized.
2055    *
2056    * This signal is emitted whenever the session is started to be synchronized
2057    * to another connection. Note that, in contrast to
2058    * #InfSession::synchronization-progress,
2059    * #InfSession::synchronization-failed and
2060    * #InfSession::synchronization-complete it cannot happen that the signal
2061    * is emitted when @session is being synchronized itself, because that can
2062    * happen at construction time only when no one had a chance to connect
2063    * signal handlers anyway.
2064    **/
2065   session_signals[SYNCHRONIZATION_BEGIN] = g_signal_new(
2066     "synchronization-begin",
2067     G_OBJECT_CLASS_TYPE(object_class),
2068     G_SIGNAL_RUN_LAST,
2069     G_STRUCT_OFFSET(InfSessionClass, synchronization_begin),
2070     NULL, NULL,
2071     inf_marshal_VOID__OBJECT_OBJECT,
2072     G_TYPE_NONE,
2073     2,
2074     INF_COMMUNICATION_TYPE_GROUP,
2075     INF_TYPE_XML_CONNECTION
2076   );
2077
2078   /**
2079    * InfSession::synchronization-progress:
2080    * @session: The #InfSession that is synchronizing or being synchronized
2081    * @connection: The #InfXmlConnection through which progress is made
2082    * @progress: A #gdouble value ranging from 0.0 to 1.0.
2083    *
2084    * This signal is emitted whenever a new XML node has been sent or received
2085    * over @connection as part of a synchronization. The process is completed
2086    * when @progress reaches the value 1.0. At this point,
2087    * #InfSession::synchronization-complete is also emitted.
2088    *
2089    * If @session&apos;s status is %INF_SESSION_SYNCHRONIZING, the local
2090    * side is being synchronized by the remote side. If the status is
2091    * %INF_SESSION_RUNNING, the local side is updating the remote side.
2092    */
2093   session_signals[SYNCHRONIZATION_PROGRESS] = g_signal_new(
2094     "synchronization-progress",
2095     G_OBJECT_CLASS_TYPE(object_class),
2096     G_SIGNAL_RUN_LAST,
2097     G_STRUCT_OFFSET(InfSessionClass, synchronization_progress),
2098     NULL, NULL,
2099     inf_marshal_VOID__OBJECT_DOUBLE,
2100     G_TYPE_NONE,
2101     2,
2102     INF_TYPE_XML_CONNECTION,
2103     G_TYPE_DOUBLE
2104   );
2105
2106   /**
2107    * InfSession::synchronization-complete:
2108    * @session: The #InfSession that has or was synchronized
2109    * @connection: The #InfXmlConnection through which synchronization happened
2110    *
2111    * This signal is emitted when synchronization has completed, in addition
2112    * to #InfSession::synchronization-progress with a progress value of 1.0.
2113    *
2114    * If a callback is connected before the default handler, it can find out
2115    * whether the remote side is synchronizing the local side by comparing
2116    * @sessions&apos;s status with %INF_SESSION_SYNCHRONIZING. The default
2117    * signal handler sets the status to %INF_SESSION_RUNNING, so checking
2118    * afterwards is not too useful.
2119    */
2120   session_signals[SYNCHRONIZATION_COMPLETE] = g_signal_new(
2121     "synchronization-complete",
2122     G_OBJECT_CLASS_TYPE(object_class),
2123     G_SIGNAL_RUN_LAST,
2124     G_STRUCT_OFFSET(InfSessionClass, synchronization_complete),
2125     NULL, NULL,
2126     inf_marshal_VOID__OBJECT,
2127     G_TYPE_NONE,
2128     1,
2129     INF_TYPE_XML_CONNECTION
2130   );
2131
2132   /**
2133    * InfSession::synchronization-failed:
2134    * @session: The #InfSession that failed to synchronize or be synchronized
2135    * @connection: The #InfXmlConnection through which synchronization happened
2136    * @error: A pointer to a #GError object with details on the error
2137    *
2138    * This signal is emitted when synchronization has failed before its
2139    * completion due to malformed data from the other side or network failure.
2140    *
2141    * If this happens during initial synchronization, #InfSession::close is
2142    * emitted as well at this point.
2143    */
2144   session_signals[SYNCHRONIZATION_FAILED] = g_signal_new(
2145     "synchronization-failed",
2146     G_OBJECT_CLASS_TYPE(object_class),
2147     G_SIGNAL_RUN_LAST,
2148     G_STRUCT_OFFSET(InfSessionClass, synchronization_failed),
2149     NULL, NULL,
2150     inf_marshal_VOID__OBJECT_POINTER,
2151     G_TYPE_NONE,
2152     2,
2153     INF_TYPE_XML_CONNECTION,
2154     G_TYPE_POINTER /* actually a GError* */
2155   );
2156 }
2157
2158 static void
2159 inf_session_communication_object_init(gpointer g_iface,
2160                                       gpointer iface_data)
2161 {
2162   InfCommunicationObjectIface* iface;
2163   iface = (InfCommunicationObjectIface*)g_iface;
2164
2165   iface->sent = inf_session_communication_object_sent;
2166   iface->enqueued = inf_session_communication_object_enqueued;
2167   iface->received = inf_session_communication_object_received;
2168 }
2169
2170 GType
2171 inf_session_status_get_type(void)
2172 {
2173   static GType session_status_type = 0;
2174
2175   if(!session_status_type)
2176   {
2177     static const GEnumValue session_status_type_values[] = {
2178       {
2179         INF_SESSION_PRESYNC,
2180         "INF_SESSION_PRESYNC",
2181         "presync"
2182       }, {
2183         INF_SESSION_SYNCHRONIZING,
2184         "INF_SESSION_SYNCHRONIZING",
2185         "synchronizing"
2186       }, {
2187         INF_SESSION_RUNNING,
2188         "INF_SESSION_RUNNING",
2189         "running"
2190       }, {
2191         INF_SESSION_CLOSED,
2192         "INF_SESSION_CLOSED",
2193         "closed"
2194       }, {
2195         0,
2196         NULL,
2197         NULL
2198       }
2199     };
2200
2201     session_status_type = g_enum_register_static(
2202       "InfSessionStatus",
2203       session_status_type_values
2204     );
2205   }
2206
2207   return session_status_type;
2208 }
2209
2210 GType
2211 inf_session_get_type(void)
2212 {
2213   static GType session_type = 0;
2214
2215   if(!session_type)
2216   {
2217     static const GTypeInfo session_type_info = {
2218       sizeof(InfSessionClass),  /* class_size */
2219       NULL,                     /* base_init */
2220       NULL,                     /* base_finalize */
2221       inf_session_class_init,   /* class_init */
2222       NULL,                     /* class_finalize */
2223       NULL,                     /* class_data */
2224       sizeof(InfSession),       /* instance_size */
2225       0,                        /* n_preallocs */
2226       inf_session_init,         /* instance_init */
2227       NULL                      /* value_table */
2228     };
2229
2230     static const GInterfaceInfo communication_object_info = {
2231       inf_session_communication_object_init,
2232       NULL,
2233       NULL
2234     };
2235
2236     session_type = g_type_register_static(
2237       G_TYPE_OBJECT,
2238       "InfSession",
2239       &session_type_info,
2240       0
2241     );
2242
2243     g_type_add_interface_static(
2244       session_type,
2245       INF_COMMUNICATION_TYPE_OBJECT,
2246       &communication_object_info
2247     );
2248   }
2249
2250   return session_type;
2251 }
2252
2253 /*
2254  * Public API.
2255  */
2256
2257 /**
2258  * inf_session_lookup_user_property:
2259  * @params: A pointer to an array of containing #GParameter values.
2260  * @n_params: The number of elements in the aforementioned array
2261  * @name: Name to look up.
2262  *
2263  * Looks up the parameter with the given name in @array.
2264  *
2265  * Return Value: A #GParameter, or %NULL.
2266  **/
2267 const GParameter*
2268 inf_session_lookup_user_property(const GParameter* params,
2269                                  guint n_params,
2270                                  const gchar* name)
2271 {
2272   guint i;
2273
2274   g_return_val_if_fail(params != NULL || n_params == 0, NULL);
2275   g_return_val_if_fail(name != NULL, NULL);
2276
2277   for(i = 0; i < n_params; ++ i)
2278     if(strcmp(params[i].name, name) == 0)
2279       return &params[i];
2280
2281   return NULL;
2282 }
2283
2284 /**
2285  * inf_session_get_user_property:
2286  * @array: A #GArray containing #GParameter values.
2287  * @name: Name to look up.
2288  *
2289  * Looks up the paremeter with the given name in @array. If there is no such
2290  * parameter, a new one will be created.
2291  *
2292  * Return Value: A #GParameter.
2293  **/
2294 GParameter*
2295 inf_session_get_user_property(GArray* array,
2296                               const gchar* name)
2297 {
2298   GParameter* parameter;
2299   guint i;
2300
2301   g_return_val_if_fail(array != NULL, NULL);
2302   g_return_val_if_fail(name != NULL, NULL);
2303
2304   for(i = 0; i < array->len; ++ i)
2305     if(strcmp(g_array_index(array, GParameter, i).name, name) == 0)
2306       return &g_array_index(array, GParameter, i);
2307
2308   g_array_set_size(array, array->len + 1);
2309   parameter = &g_array_index(array, GParameter, array->len - 1);
2310
2311   parameter->name = name;
2312   memset(&parameter->value, 0, sizeof(GValue));
2313   return parameter;
2314 }
2315
2316 /**
2317  * inf_session_user_to_xml:
2318  * @session: A #InfSession.
2319  * @user: A #InfUser contained in @session.
2320  * @xml: An XML node to which to add user information.
2321  *
2322  * This is a convenience function that queries @user's properties and
2323  * calls set_xml_user_props with them. This adds the properties of @user
2324  * to @xml.
2325  *
2326  * An equivalent user object may be built by calling the get_xml_user_props
2327  * vfunc on @xml and then calling the user_new vfunc with the resulting
2328  * properties.
2329  **/
2330 void
2331 inf_session_user_to_xml(InfSession* session,
2332                         InfUser* user,
2333                         xmlNodePtr xml)
2334 {
2335   InfSessionClass* session_class;
2336   GParamSpec** pspecs;
2337   GParameter* params;
2338   guint n_params;
2339   guint i;
2340
2341   g_return_if_fail(INF_IS_SESSION(session));
2342   g_return_if_fail(INF_IS_USER(user));
2343   g_return_if_fail(xml != NULL);
2344
2345   session_class = INF_SESSION_GET_CLASS(session);
2346   g_return_if_fail(session_class->set_xml_user_props != NULL);
2347
2348   pspecs = g_object_class_list_properties(
2349     G_OBJECT_CLASS(INF_USER_GET_CLASS(user)),
2350     &n_params
2351   );
2352
2353   params = g_malloc(n_params * sizeof(GParameter));
2354   for(i = 0; i < n_params; ++ i)
2355   {
2356     params[i].name = pspecs[i]->name;
2357     memset(&params[i].value, 0, sizeof(GValue));
2358     g_value_init(&params[i].value, pspecs[i]->value_type);
2359     g_object_get_property(G_OBJECT(user), params[i].name, &params[i].value);
2360   }
2361
2362   session_class->set_xml_user_props(session, params, n_params, xml);
2363
2364   for(i = 0; i < n_params; ++ i)
2365     g_value_unset(&params[i].value);
2366
2367   g_free(params);
2368   g_free(pspecs);
2369 }
2370
2371 /**
2372  * inf_session_close:
2373  * @session: A #InfSession.
2374  *
2375  * Closes a running session. When a session is closed, it unrefs all
2376  * connections and no longer handles requests.
2377  */
2378 void
2379 inf_session_close(InfSession* session)
2380 {
2381   g_return_if_fail(INF_IS_SESSION(session));
2382   g_return_if_fail(inf_session_get_status(session) != INF_SESSION_CLOSED);
2383   g_signal_emit(G_OBJECT(session), session_signals[CLOSE], 0);
2384 }
2385
2386 /**
2387  * inf_session_get_communication_manager:
2388  * @session: A #InfSession.
2389  *
2390  * Returns the communication manager for @session.
2391  *
2392  * Return Value: A #InfCommunicationManager.
2393  **/
2394 InfCommunicationManager*
2395 inf_session_get_communication_manager(InfSession* session)
2396 {
2397   g_return_val_if_fail(INF_IS_SESSION(session), NULL);
2398   return INF_SESSION_PRIVATE(session)->manager;
2399 }
2400
2401 /**
2402  * inf_session_get_buffer:
2403  * @session: A #InfSession.
2404  *
2405  * Returns the buffer used by @session.
2406  *
2407  * Return Value: A #InfBuffer.
2408  **/
2409 InfBuffer*
2410 inf_session_get_buffer(InfSession* session)
2411 {
2412   g_return_val_if_fail(INF_IS_SESSION(session), NULL);
2413   return INF_SESSION_PRIVATE(session)->buffer;
2414 }
2415
2416 /**
2417  * inf_session_get_user_table:
2418  * @session:A #InfSession.
2419  *
2420  * Returns the user table used by @session.
2421  *
2422  * Return Value: A #InfUserTable.
2423  **/
2424 InfUserTable*
2425 inf_session_get_user_table(InfSession* session)
2426 {
2427   g_return_val_if_fail(INF_IS_SESSION(session), NULL);
2428   return INF_SESSION_PRIVATE(session)->user_table;
2429 }
2430
2431 /**
2432  * inf_session_get_status:
2433  * @session: A #InfSession.
2434  *
2435  * Returns the session's status.
2436  *
2437  * Return Value: The status of @session.
2438  **/
2439 InfSessionStatus
2440 inf_session_get_status(InfSession* session)
2441 {
2442   g_return_val_if_fail(INF_IS_SESSION(session), INF_SESSION_CLOSED);
2443   return INF_SESSION_PRIVATE(session)->status;
2444 }
2445
2446 /**
2447  * inf_session_add_user:
2448  * @session: A #InfSession.
2449  * @params: Construction parameters for the #InfUser (or derived) object.
2450  * @n_params: Number of parameters.
2451  * @error: Location to store error information.
2452  *
2453  * Adds a user to @session. The user object is constructed via the
2454  * user_new vfunc of #InfSessionClass. This will create a new #InfUser
2455  * object by default, but may be overridden by subclasses to create
2456  * different kinds of users.
2457  *
2458  * Note that this function does not tell the other participants that the
2459  * user was added. To avoid conflicts, normally only the publisher of the
2460  * session can add users and notifies others accordingly. This is handled
2461  * by #InfdSessionProxy or #InfcSessionProxy, respectively.
2462  *
2463  * You should not call this function unless you know what you are doing.
2464  *
2465  * Return Value: The new #InfUser, or %NULL in case of an error.
2466  **/
2467 InfUser*
2468 inf_session_add_user(InfSession* session,
2469                      const GParameter* params,
2470                      guint n_params,
2471                      GError** error)
2472 {
2473   InfSessionPrivate* priv;
2474   InfSessionClass* session_class;
2475   InfUser* user;
2476   gboolean result;
2477
2478   g_return_val_if_fail(INF_IS_SESSION(session), NULL);
2479   session_class = INF_SESSION_GET_CLASS(session);
2480
2481   g_return_val_if_fail(session_class->validate_user_props != NULL, NULL);
2482   g_return_val_if_fail(session_class->user_new != NULL, NULL);
2483
2484   priv = INF_SESSION_PRIVATE(session);
2485
2486   result = session_class->validate_user_props(
2487     session,
2488     params,
2489     n_params,
2490     NULL,
2491     error
2492   );
2493
2494   if(result == TRUE)
2495   {
2496     /* No idea why g_object_newv wants unconst GParameter list */
2497     user = session_class->user_new(session,
2498                                    *(GParameter**)(gpointer)&params,
2499                                    n_params);
2500     inf_user_table_add_user(priv->user_table, user);
2501     g_object_unref(user); /* We rely on the usertable holding a reference */
2502
2503     return user;
2504   }
2505
2506   return NULL;
2507 }
2508
2509 /**
2510  * inf_session_set_user_status:
2511  * @session: A #InfSession.
2512  * @user: A local #InfUser from @session's user table.
2513  * @status: New status for @user.
2514  *
2515  * Changes the status of the given @user which needs to have the
2516  * %INF_USER_LOCAL flag set for this function to be called. If the status
2517  * is changed to %INF_USER_UNAVAILABLE, then the user leaves the session. To
2518  * rejoin use infc_session_proxy_join_user() or infd_session_proxy_add_user(),
2519  * respectively for a proxy proxying @session.
2520  **/
2521 void
2522 inf_session_set_user_status(InfSession* session,
2523                             InfUser* user,
2524                             InfUserStatus status)
2525 {
2526   InfSessionPrivate* priv;
2527   xmlNodePtr xml;
2528
2529   g_return_if_fail(INF_IS_SESSION(session));
2530   g_return_if_fail(INF_IS_USER(user));
2531   g_return_if_fail(inf_session_get_status(session) == INF_SESSION_RUNNING);
2532   g_return_if_fail(inf_user_get_status(user) != INF_USER_UNAVAILABLE);
2533   g_return_if_fail( (inf_user_get_flags(user) & INF_USER_LOCAL) != 0);
2534
2535   priv = INF_SESSION_PRIVATE(session);
2536
2537   if(inf_user_get_status(user) != status)
2538   {
2539     xml = xmlNewNode(NULL, (const xmlChar*)"user-status-change");
2540     inf_xml_util_set_attribute_uint(xml, "id", inf_user_get_id(user));
2541
2542     inf_xml_util_set_attribute(
2543       xml,
2544       "status",
2545       inf_user_status_to_string(status)
2546     );
2547
2548     if(priv->subscription_group != NULL)
2549       inf_session_send_to_subscriptions(session, xml);
2550
2551     g_object_set(G_OBJECT(user), "status", status, NULL);
2552   }
2553 }
2554
2555 /**
2556  * inf_session_synchronize_from:
2557  * @session: A #InfSession in status %INF_SESSION_PRESYNC.
2558  *
2559  * Switches @session's status to %INF_SESSION_SYNCHRONIZING. In
2560  * %INF_SESSION_PRESYNC, all messages from incoming the synchronizing
2561  * connection are ignored, and no cancellation request is sent to the remote
2562  * site if the status changes to %INF_SESSION_CLOSED. The rationale behind
2563  * that status is that one can prepare a session for synchronization but start
2564  * the actual synchronization later, after having made sure that the remote
2565  * site is ready to perform the synchronization.
2566  */
2567 void
2568 inf_session_synchronize_from(InfSession* session)
2569 {
2570   InfSessionPrivate* priv;
2571   InfCommunicationGroup* group;
2572   InfXmlConnection* connection;
2573
2574   /* TODO: Maybe add InfCommunicationGroup*, InfXmlConnection* arguments,
2575    * and remove them from the priv->shared.presync. This might simplify code
2576    * elsewhere. */
2577
2578   g_return_if_fail(inf_session_get_status(session) == INF_SESSION_PRESYNC);
2579
2580   priv = INF_SESSION_PRIVATE(session);
2581   g_return_if_fail(priv->shared.presync.closing == FALSE);
2582
2583   group = priv->shared.presync.group;
2584   connection = priv->shared.presync.conn;
2585
2586   priv->status = INF_SESSION_SYNCHRONIZING;
2587
2588   priv->shared.sync.group = group;
2589   priv->shared.sync.conn = connection;
2590   priv->shared.sync.messages_total = 0;
2591   priv->shared.sync.messages_received = 0;
2592   priv->shared.sync.closing = FALSE;
2593
2594   g_object_notify(G_OBJECT(session), "status");
2595 }
2596
2597 /**
2598  * inf_session_synchronize_to:
2599  * @session: A #InfSession in status %INF_SESSION_RUNNING.
2600  * @group: A #InfCommunicationGroup.
2601  * @connection: A #InfConnection.
2602  *
2603  * Initiates a synchronization to @connection. On the other end of
2604  * @connection, a new session with the sync-connection and sync-group
2605  * construction properties set should have been created. @group is used
2606  * as a group in the connection manager. It is allowed for @group to have
2607  * another #InfCommunicationObject than @session, however, you should forward
2608  * the #InfCommunicationObject messages your object receives to @session then.
2609  * Also, @connection must already be present in @group, and should not be
2610  * removed until synchronization finished.
2611  *
2612  * A synchronization can only be initiated if @session is in state
2613  * %INF_SESSION_RUNNING.
2614  **/
2615 void
2616 inf_session_synchronize_to(InfSession* session,
2617                            InfCommunicationGroup* group,
2618                            InfXmlConnection* connection)
2619 {
2620   g_return_if_fail(INF_IS_SESSION(session));
2621   g_return_if_fail(group != NULL);
2622   g_return_if_fail(INF_IS_XML_CONNECTION(connection));
2623
2624   g_return_if_fail(inf_session_get_status(session) == INF_SESSION_RUNNING);
2625   g_return_if_fail(
2626     inf_session_find_sync_by_connection(session, connection) == NULL
2627   );
2628
2629   g_signal_emit(
2630     G_OBJECT(session),
2631     session_signals[SYNCHRONIZATION_BEGIN],
2632     0,
2633     group,
2634     connection
2635   );
2636 }
2637
2638 /**
2639  * inf_session_get_synchronization_status:
2640  * @session: A #InfSession.
2641  * @connection: A #InfXmlConnection.
2642  *
2643  * If @session is in status %INF_SESSION_SYNCHRONIZING, this always returns
2644  * %INF_SESSION_SYNC_IN_PROGRESS if @connection is the connection with which
2645  * the session is synchronized, and %INF_SESSION_SYNC_NONE otherwise.
2646  *
2647  * If @session is in status %INF_SESSION_RUNNING, this returns the status
2648  * of the synchronization to @connection. %INF_SESSION_SYNC_NONE is returned,
2649  * when there is currently no synchronization ongoing to @connection,
2650  * %INF_SESSION_SYNC_IN_PROGRESS is returned, if there is one, and
2651  * %INF_SESSION_SYNC_AWAITING_ACK if the synchronization is finished but we
2652  * are waiting for the acknowledgement from the remote site that all
2653  * synchronization data has been progressed successfully. The synchronization
2654  * can still fail in this state but it can no longer by cancelled.
2655  *
2656  * If @session is in status $INF_SESSION_CLOSED, this always returns
2657  * %INF_SESSION_SYNC_NONE.
2658  *
2659  * Return Value: The synchronization status of @connection.
2660  **/
2661 InfSessionSyncStatus
2662 inf_session_get_synchronization_status(InfSession* session,
2663                                        InfXmlConnection* connection)
2664 {
2665   InfSessionPrivate* priv;
2666   InfSessionSync* sync;
2667
2668   g_return_val_if_fail(INF_IS_SESSION(session), INF_SESSION_SYNC_NONE);
2669
2670   g_return_val_if_fail(
2671     INF_IS_XML_CONNECTION(connection),
2672     INF_SESSION_SYNC_NONE
2673   );
2674
2675   priv = INF_SESSION_PRIVATE(session);
2676
2677   switch(priv->status)
2678   {
2679   case INF_SESSION_SYNCHRONIZING:
2680     if(connection == priv->shared.sync.conn)
2681       return INF_SESSION_SYNC_IN_PROGRESS;
2682     return INF_SESSION_SYNC_NONE;
2683   case INF_SESSION_RUNNING:
2684     sync = inf_session_find_sync_by_connection(session, connection);
2685     if(sync == NULL) return INF_SESSION_SYNC_NONE;
2686
2687     return sync->status;
2688   case INF_SESSION_CLOSED:
2689     return INF_SESSION_SYNC_NONE;
2690   default:
2691     g_assert_not_reached();
2692     break;
2693   }
2694 }
2695
2696 /**
2697  * inf_session_get_synchronization_progress:
2698  * @session: A #InfSession.
2699  * @connection: A #InfXmlConnection.
2700  *
2701  * This function requires that the synchronization status of @connection
2702  * is %INF_SESSION_SYNC_IN_PROGRESS or %INF_SESSION_SYNC_AWAITING_ACK
2703  * (see inf_session_get_synchronization_status()). Then, it returns a value
2704  * between 0.0 and 1.0 specifying how much synchronization data has already
2705  * been transferred to the remote site.
2706  *
2707  * Note that if the session is in status %INF_SESSION_RUNNING, it is
2708  * possible that this function returns 1.0 (i.e. all data has been
2709  * transmitted) but the synchronization is not yet complete, because the
2710  * remote site must still acknowledge the synchronization. The synchronization
2711  * then is in status %INF_SESSION_SYNC_AWAITING_ACK.
2712  *
2713  * Return Value: A value between 0.0 and 1.0.
2714  **/
2715 gdouble
2716 inf_session_get_synchronization_progress(InfSession* session,
2717                                          InfXmlConnection* connection)
2718 {
2719   InfSessionPrivate* priv;
2720   InfSessionSync* sync;
2721
2722   g_return_val_if_fail(INF_IS_SESSION(session), 0.0);
2723   g_return_val_if_fail(INF_IS_XML_CONNECTION(connection), 0.0);
2724
2725   g_return_val_if_fail(
2726     inf_session_get_synchronization_status(
2727       session,
2728       connection
2729     ) != INF_SESSION_SYNC_NONE,
2730     0.0
2731   );
2732
2733   priv = INF_SESSION_PRIVATE(session);
2734
2735   switch(priv->status)
2736   {
2737   case INF_SESSION_PRESYNC:
2738     g_assert(connection == priv->shared.presync.conn);
2739     return 0.0;
2740   case INF_SESSION_SYNCHRONIZING:
2741     g_assert(connection == priv->shared.sync.conn);
2742
2743     /* messages_total is still zero in case we did not yet even receive
2744      * sync-begin. We are at the very beginning of the synchronization in
2745      * that case. */
2746     if(priv->shared.sync.messages_total == 0)
2747       return 0.0;
2748
2749     return (gdouble)priv->shared.sync.messages_received /
2750            (gdouble)priv->shared.sync.messages_total;
2751
2752   case INF_SESSION_RUNNING:
2753     sync = inf_session_find_sync_by_connection(session, connection);
2754     g_assert(sync != NULL);
2755
2756     return (gdouble)sync->messages_sent / (gdouble)sync->messages_total;
2757   case INF_SESSION_CLOSED:
2758   default:
2759     g_assert_not_reached();
2760     return 0.0;
2761   }
2762 }
2763
2764 /**
2765  * inf_session_has_synchronizations:
2766  * @session: A #InfSession.
2767  *
2768  * Returns whether there are currently ongoing synchronizations. If the
2769  * session is in status %INF_SESSION_SYNCHRONIZING, then this returns always
2770  * %TRUE, if it is in %INF_SESSION_CLOSED, then it returns always %FALSE.
2771  * If the session is in status %INF_SESSION_RUNNING, then it returns %TRUE
2772  * when the session is currently at least synchronized to one connection and
2773  * %FALSE otherwise.
2774  *
2775  * Returns: Whether there are ongoing synchronizations.
2776  **/
2777 gboolean
2778 inf_session_has_synchronizations(InfSession* session)
2779 {
2780   InfSessionPrivate* priv;
2781   g_return_val_if_fail(INF_IS_SESSION(session), FALSE);
2782   priv = INF_SESSION_PRIVATE(session);
2783
2784   switch(priv->status)
2785   {
2786   case INF_SESSION_PRESYNC:
2787   case INF_SESSION_SYNCHRONIZING:
2788     return TRUE;
2789   case INF_SESSION_RUNNING:
2790     if(priv->shared.run.syncs == NULL)
2791       return FALSE;
2792     else
2793       return TRUE;
2794   case INF_SESSION_CLOSED:
2795     return FALSE;
2796   default:
2797     g_assert_not_reached();
2798     return FALSE;
2799   }
2800 }
2801
2802 /**
2803  * inf_session_get_subscription_group:
2804  * @session: A #InfSession.
2805  *
2806  * Returns the subscription group for @session, if any.
2807  *
2808  * Return Value: A #InfCommunicationGroup, or %NULL.
2809  **/
2810 InfCommunicationGroup*
2811 inf_session_get_subscription_group(InfSession* session)
2812 {
2813   g_return_val_if_fail(INF_IS_SESSION(session), NULL);
2814   return INF_SESSION_PRIVATE(session)->subscription_group;
2815 }
2816
2817 /**
2818  * inf_session_set_subscription_group:
2819  * @session: A #InfSession.
2820  * @group: A #InfCommunicationGroup.
2821  *
2822  * Sets the subscription group for @session. The subscription group is the
2823  * group in which all connections subscribed to the session are a member of.
2824  *
2825  * #InfSession itself does not deal with subscriptions, so it is your job
2826  * to keep @group up-to-date (for example if you add non-local users to
2827  * @session). This is normally done by a so-called session proxy such as
2828  * #InfcSessionProxy or #InfdSessionProxy, respectively.
2829  **/
2830 void
2831 inf_session_set_subscription_group(InfSession* session,
2832                                    InfCommunicationGroup* group)
2833 {
2834   InfSessionPrivate* priv;
2835
2836   g_return_if_fail(INF_IS_SESSION(session));
2837
2838   priv = INF_SESSION_PRIVATE(session);
2839
2840   if(priv->subscription_group != group)
2841   {
2842     if(priv->subscription_group != NULL)
2843       g_object_unref(priv->subscription_group);
2844
2845     priv->subscription_group = group;
2846
2847     if(group != NULL)
2848       g_object_ref(group);
2849
2850     g_object_notify(G_OBJECT(session), "subscription-group");
2851   }
2852 }
2853
2854 /**
2855  * inf_session_send_to_subscriptions:
2856  * @session: A #InfSession.
2857  * @xml: The message to send.
2858  *
2859  * Sends a XML message to the all members of @session's subscription group.
2860  * This function can only be called if the subscription group is non-%NULL. It
2861  * takes ownership of @xml.
2862  **/
2863 void
2864 inf_session_send_to_subscriptions(InfSession* session,
2865                                   xmlNodePtr xml)
2866 {
2867   InfSessionPrivate* priv;
2868
2869   g_return_if_fail(INF_IS_SESSION(session));
2870   g_return_if_fail(xml != NULL);
2871
2872   priv = INF_SESSION_PRIVATE(session);
2873   g_return_if_fail(priv->subscription_group != NULL);
2874
2875   inf_communication_group_send_group_message(priv->subscription_group, xml);
2876 }
2877
2878 /* vim:set et sw=2 ts=2: */