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");
}
예제 #3
0
/**
 * 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;
}
예제 #4
0
/* 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);
}
예제 #5
0
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);
}
예제 #6
0
/**
 * 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;
}
예제 #7
0
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);
}
예제 #8
0
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);
}
예제 #9
0
/**
 * 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;
}
예제 #10
0
/**
 * 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;
}