int main(int argc, char* argv[]) { guint users[2]; InfAdoptedStateVector* vec; InfAdoptedStateVector* vec2; g_type_init(); users[0] = 1; users[1] = 2; /* Note we do not need to allocate users since the state vector does not * touch them. */ vec = inf_adopted_state_vector_new(); vec2 = inf_adopted_state_vector_new(); g_assert(inf_adopted_state_vector_causally_before_inc(vec, vec2, 1) == FALSE); inf_adopted_state_vector_free(vec2); inf_adopted_state_vector_set(vec, users[0], 2); g_assert(inf_adopted_state_vector_get(vec, users[0]) == 2); inf_adopted_state_vector_add(vec, users[0], 4); g_assert(inf_adopted_state_vector_get(vec, users[0]) == 6); inf_adopted_state_vector_add(vec, users[1], 3); g_assert(inf_adopted_state_vector_get(vec, users[1]) == 3); inf_adopted_state_vector_set(vec, users[1], 5); g_assert(inf_adopted_state_vector_get(vec, users[1]) == 5); inf_adopted_state_vector_free(vec); l_test(); return 0; }
static void cmp(const char* should_be, InfAdoptedStateVector* vec) { char* is; InfAdoptedStateVector* should_be_vec; is = inf_adopted_state_vector_to_string(vec); if (strcmp(should_be, is) != 0) { printf("should be: %s\n" "is: %s\n" "strcmp failed\n", should_be, is); g_assert_not_reached(); } should_be_vec = inf_adopted_state_vector_from_string(should_be, NULL); if (!should_be_vec || inf_adopted_state_vector_compare(vec, should_be_vec) != 0 || inf_adopted_state_vector_compare(should_be_vec, vec) != 0) { printf("should be: %s\n" "is: %s\n" "compare failed\n", should_be, is); g_assert_not_reached(); } g_free(is); inf_adopted_state_vector_free(should_be_vec); printf("ok!\n"); }
/** * inf_adopted_request_mirror: * @request: A #InfAdoptedRequest. * @by: The number of requests between the original and the mirrored * operation. * * Mirrors @request as described in "Reducing the Problems of Group Undo" by * Matthias Ressel and Rul Gunzenhäuser * (http://portal.acm.org/citation.cfm?doid=320297.320312). * * Note that @by is the total amount of requests between the original and * mirrored request, and thus equivalent to 2j-1 in the paper's definition. * * @request must be of type %INF_ADOPTED_REQUEST_DO and its operation must * be reversible. * * Returns: The mirrored request as a new #InfAdoptedRequest. **/ InfAdoptedRequest* inf_adopted_request_mirror(InfAdoptedRequest* request, guint by) { InfAdoptedRequestPrivate* priv; InfAdoptedOperation* new_operation; InfAdoptedStateVector* new_vector; InfAdoptedRequest* new_request; g_return_val_if_fail(INF_ADOPTED_IS_REQUEST(request), NULL); g_return_val_if_fail(by % 2 == 1, NULL); priv = INF_ADOPTED_REQUEST_PRIVATE(request); g_return_val_if_fail(priv->type == INF_ADOPTED_REQUEST_DO, NULL); g_return_val_if_fail( inf_adopted_operation_is_reversible(priv->operation), NULL ); new_operation = inf_adopted_operation_revert(priv->operation); new_vector = inf_adopted_state_vector_copy(priv->vector); inf_adopted_state_vector_add(new_vector, priv->user_id, by); new_request = inf_adopted_request_new_do( new_vector, priv->user_id, new_operation ); g_object_unref(new_operation); inf_adopted_state_vector_free(new_vector); return new_request; }
/* Breadcasts a request N times - makes only sense for undo and redo requests, * so that's the only thing we offer API for. */ static void inf_adopted_session_broadcast_n_requests(InfAdoptedSession* session, InfAdoptedRequest* request, guint n) { InfAdoptedSessionPrivate* priv; InfAdoptedSessionClass* session_class; InfUserTable* user_table; guint user_id; InfUser* user; InfAdoptedSessionLocalUser* local; xmlNodePtr xml; priv = INF_ADOPTED_SESSION_PRIVATE(session); session_class = INF_ADOPTED_SESSION_GET_CLASS(session); g_assert(session_class->request_to_xml != NULL); user_table = inf_session_get_user_table(INF_SESSION(session)); user_id = inf_adopted_request_get_user_id(request); user = inf_user_table_lookup_user_by_id(user_table, user_id); g_assert(user != NULL); local = inf_adopted_session_lookup_local_user( session, INF_ADOPTED_USER(user) ); g_assert(local != NULL); xml = xmlNewNode(NULL, (const xmlChar*)"request"); session_class->request_to_xml( session, xml, request, local->last_send_vector, FALSE ); if(n > 1) inf_xml_util_set_attribute_uint(xml, "num", n); inf_session_send_to_subscriptions(INF_SESSION(session), xml); inf_adopted_state_vector_free(local->last_send_vector); local->last_send_vector = inf_adopted_state_vector_copy( inf_adopted_request_get_vector(request) ); /* Add this request to last send vector if it increases vector time * (-> affects buffer). */ if(inf_adopted_request_affects_buffer(request) == TRUE) inf_adopted_state_vector_add(local->last_send_vector, user_id, n); inf_adopted_session_stop_noop_timer(session, local); }
static void inf_adopted_request_finalize(GObject* object) { InfAdoptedRequest* request; InfAdoptedRequestPrivate* priv; request = INF_ADOPTED_REQUEST(object); priv = INF_ADOPTED_REQUEST_PRIVATE(request); if(priv->vector != NULL) inf_adopted_state_vector_free(priv->vector); G_OBJECT_CLASS(parent_class)->finalize(object); }
/** * inf_adopted_request_fold: * @request: A #InfAdoptedRequest. * @into: The direction into which to fold. * @by: The number of operations between the original and the fold request. * * Folds @request as described in "Reducing the Problems of Group Undo" by * Matthias Ressel and Rul Gunzenhäuser * (http://portal.acm.org/citation.cfm?doid=320297.320312). * * Note that @by is the total amount of requests between the original and * the fold request, and thus equivalent to 2j in the paper's definition. * * @into must not be the same user as the one that issued @request. * * Returns: The folded request as a new #InfAdoptedRequest. **/ InfAdoptedRequest* inf_adopted_request_fold(InfAdoptedRequest* request, guint into, guint by) { InfAdoptedRequestPrivate* priv; InfAdoptedStateVector* new_vector; InfAdoptedRequest* new_request; g_return_val_if_fail(INF_ADOPTED_IS_REQUEST(request), NULL); g_return_val_if_fail(into != 0, NULL); g_return_val_if_fail(by % 2 == 0, NULL); priv = INF_ADOPTED_REQUEST_PRIVATE(request); g_return_val_if_fail(priv->user_id != into, NULL); new_vector = inf_adopted_state_vector_copy(priv->vector); inf_adopted_state_vector_add(new_vector, into, by); if(priv->type == INF_ADOPTED_REQUEST_DO) { new_request = INF_ADOPTED_REQUEST( g_object_new( INF_ADOPTED_TYPE_REQUEST, "type", priv->type, "operation", priv->operation, "vector", new_vector, "user-id", priv->user_id, NULL ) ); } else { new_request = INF_ADOPTED_REQUEST( g_object_new( INF_ADOPTED_TYPE_REQUEST, "type", priv->type, "vector", new_vector, "user-id", priv->user_id ) ); } inf_adopted_state_vector_free(new_vector); return new_request; }
static void inf_adopted_session_close(InfSession* session) { InfAdoptedSessionPrivate* priv; InfAdoptedSessionLocalUser* local; GSList* item; priv = INF_ADOPTED_SESSION_PRIVATE(session); for(item = priv->local_users; item != NULL; item = g_slist_next(item)) { local = (InfAdoptedSessionLocalUser*)item->data; inf_adopted_state_vector_free(local->last_send_vector); g_slice_free(InfAdoptedSessionLocalUser, local); } g_slist_free(priv->local_users); priv->local_users = NULL; /* Local user info is no longer required */ INF_SESSION_CLASS(parent_class)->close(session); }
static void inf_adopted_session_remove_local_user_cb(InfUserTable* user_table, InfUser* user, gpointer user_data) { InfAdoptedSession* session; InfAdoptedSessionPrivate* priv; InfAdoptedSessionLocalUser* local; session = INF_ADOPTED_SESSION(user_data); priv = INF_ADOPTED_SESSION_PRIVATE(session); local = inf_adopted_session_lookup_local_user( session, INF_ADOPTED_USER(user) ); g_assert(local != NULL); inf_adopted_session_stop_noop_timer(session, local); inf_adopted_state_vector_free(local->last_send_vector); priv->local_users = g_slist_remove(priv->local_users, local); g_slice_free(InfAdoptedSessionLocalUser, local); }
/** * inf_adopted_session_read_request_info: * @session: A #InfAdoptedSession. * @xml: The XML to read the data from. * @diff_vec: The reference vector of the time vector of the request, or * %NULL. * @user: Location to store the user of the request, or %NULL. * @time: Location to store the state the request was made, or %NULL. * @operation: Location to store the operation of the request, or %NULL. * @error: Location to place an error, if any. * * This function reads common information such as the state vector the request * was made and the user that made the request from XML. It is most likely to * be used by implementations of the xml_to_request virtual function. * * Returns: %TRUE if the data could be read successfully, %FALSE if the XML * request does not contain valid request data, in which case @error is set. */ gboolean inf_adopted_session_read_request_info(InfAdoptedSession* session, xmlNodePtr xml, InfAdoptedStateVector* diff_vec, InfAdoptedUser** user, InfAdoptedStateVector** time, xmlNodePtr* operation, GError** error) { xmlChar* attr; xmlNodePtr child; if(user != NULL) { *user = inf_adopted_session_user_from_request_xml(session, xml, error); if(*user == NULL) return FALSE; } if(time != NULL) { attr = inf_xml_util_get_attribute_required(xml, "time", error); if(attr == NULL) return FALSE; if(diff_vec == NULL) { *time = inf_adopted_state_vector_from_string((const gchar*)attr, error); } else { *time = inf_adopted_state_vector_from_string_diff( (const gchar*)attr, diff_vec, error ); } xmlFree(attr); if(*time == NULL) return FALSE; } if(operation != NULL) { /* Get first child element */ child = xml->children; while(child != NULL && child->type != XML_ELEMENT_NODE) child = child->next; if(child == NULL) { g_set_error( error, inf_adopted_session_error_quark, INF_ADOPTED_SESSION_ERROR_MISSING_OPERATION, "%s", _("Operation for request missing") ); if(time) inf_adopted_state_vector_free(*time); return FALSE; } *operation = child; } return TRUE; }
/** * inf_adopted_request_transform: * @request: The request to transform. * @against: The request to transform against. * @concurrency_id: A concurrency ID for the transformation. * * Transforms the operation of @request against the operation of @against. * Both requests must be of type %INF_ADOPTED_REQUEST_DO, and their state * vectors must be the same. * * @concurrency_id can be %INF_ADOPTED_CONCURRENCY_NONE even if the * transformation requires a concurrency ID (see * inf_adopted_request_need_concurrency_id()). In that case, it is assumed * that it does not matter which operation to transform, and user IDs are * used to determine a concurrency ID for the transformation. * * Returns: A new #InfAdoptedRequest, the result of the transformation. **/ InfAdoptedRequest* inf_adopted_request_transform(InfAdoptedRequest* request, InfAdoptedRequest* against, InfAdoptedConcurrencyId concurrency_id) { InfAdoptedRequestPrivate* request_priv; InfAdoptedRequestPrivate* against_priv; InfAdoptedOperation* new_operation; InfAdoptedStateVector* new_vector; InfAdoptedRequest* new_request; g_return_val_if_fail(INF_ADOPTED_IS_REQUEST(request), NULL); g_return_val_if_fail(INF_ADOPTED_IS_REQUEST(against), NULL); request_priv = INF_ADOPTED_REQUEST_PRIVATE(request); against_priv = INF_ADOPTED_REQUEST_PRIVATE(against); g_return_val_if_fail(request_priv->type == INF_ADOPTED_REQUEST_DO, NULL); g_return_val_if_fail(against_priv->type == INF_ADOPTED_REQUEST_DO, NULL); g_return_val_if_fail(request_priv->user_id != against_priv->user_id, NULL); g_return_val_if_fail( inf_adopted_state_vector_compare( request_priv->vector, against_priv->vector ) == 0, NULL ); if(concurrency_id != INF_ADOPTED_CONCURRENCY_NONE) { new_operation = inf_adopted_operation_transform( request_priv->operation, against_priv->operation, concurrency_id ); } else if(request_priv->user_id > against_priv->user_id) { new_operation = inf_adopted_operation_transform( request_priv->operation, against_priv->operation, INF_ADOPTED_CONCURRENCY_OTHER ); } else { new_operation = inf_adopted_operation_transform( request_priv->operation, against_priv->operation, INF_ADOPTED_CONCURRENCY_SELF ); } new_vector = inf_adopted_state_vector_copy(request_priv->vector); inf_adopted_state_vector_add(new_vector, against_priv->user_id, 1); new_request = inf_adopted_request_new_do( new_vector, request_priv->user_id, new_operation ); g_object_unref(new_operation); inf_adopted_state_vector_free(new_vector); return new_request; }
/** * inf_adopted_state_vector_from_string: * @str: A string representation of a #InfAdoptedStateVector. * @error: Location to place an error, if any. * * Recreates the #InfAdoptedStateVector from its string representation. If * an error occurs, the function returns %NULL and @error is set. * * Return Value: A new #InfAdoptedStateVector, or %NULL. **/ InfAdoptedStateVector* inf_adopted_state_vector_from_string(const gchar* str, GError** error) { InfAdoptedStateVector* vec; const char* strpos; char* endpos; gsize pos; guint id; guint n; g_return_val_if_fail(str != NULL, NULL); vec = inf_adopted_state_vector_new(); strpos = str; while(*strpos) { id = strtoul(strpos, &endpos, 10); if(*endpos != ':') { g_set_error( error, inf_adopted_state_vector_error_quark(), INF_ADOPTED_STATE_VECTOR_BAD_FORMAT, "%s", _("Expected ':' after ID") ); inf_adopted_state_vector_free(vec); return NULL; } pos = inf_adopted_state_vector_find_insert_pos(vec, id); if(pos < vec->size && vec->data[pos].id == id) { g_set_error( error, inf_adopted_state_vector_error_quark(), INF_ADOPTED_STATE_VECTOR_BAD_FORMAT, _("ID '%u' already occured before"), id ); inf_adopted_state_vector_free(vec); return NULL; } strpos = endpos + 1; /* step over ':' */ n = strtoul(strpos, &endpos, 10); if(*endpos != ';' && *endpos != '\0') { g_set_error( error, inf_adopted_state_vector_error_quark(), INF_ADOPTED_STATE_VECTOR_BAD_FORMAT, _("Expected ';' or end of string after component of ID '%u'"), id ); inf_adopted_state_vector_free(vec); return NULL; } inf_adopted_state_vector_insert(vec, id, n, pos); strpos = endpos; if(*strpos != '\0') ++ strpos; /* step over ';' */ } return vec; }