static void
inf_text_default_delete_operation_apply(InfAdoptedOperation* operation,
                                        InfAdoptedUser* by,
                                        InfBuffer* buffer)
{
  InfTextDefaultDeleteOperationPrivate* priv;

  g_assert(INF_TEXT_IS_DEFAULT_DELETE_OPERATION(operation));
  g_assert(INF_TEXT_IS_BUFFER(buffer));

  priv = INF_TEXT_DEFAULT_DELETE_OPERATION_PRIVATE(operation);

#ifdef DELETE_OPERATION_CHECK_TEXT_MATCH
  g_assert(
    inf_text_default_delete_operation_text_match(
      INF_TEXT_DEFAULT_DELETE_OPERATION(operation),
      INF_TEXT_BUFFER(buffer)
    )
  );
#endif /* DELETE_OPERATION_CHECK_TEXT_MATCH */

  inf_text_buffer_erase_text(
    INF_TEXT_BUFFER(buffer),
    priv->position,
    inf_text_chunk_get_length(priv->chunk),
    INF_USER(by)
  );
}
Beispiel #2
0
/**
 * inf_text_buffer_insert_text:
 * @buffer: A #InfTextBuffer.
 * @pos: A character offset into @buffer.
 * @text (type=guint8*) (array length=bytes) (transfer none): A pointer to
 * the text to insert.
 * @len: The length (in characters) of @text.
 * @bytes: The length (in bytes) of @text.
 * @user: (allow-none): A #InfUser that has inserted the new text, or %NULL.
 *
 * Inserts @text into @buffer as written by @author. @text must be encoded in
 * the character encoding of the buffer, see inf_text_buffer_get_encoding().
 **/
void
inf_text_buffer_insert_text(InfTextBuffer* buffer,
                            guint pos,
                            gconstpointer text,
                            gsize bytes,
                            guint len,
                            InfUser* user)
{
  InfTextBufferInterface* iface;
  InfTextChunk* chunk;

  g_return_if_fail(INF_TEXT_IS_BUFFER(buffer));
  g_return_if_fail(text != NULL);
  g_return_if_fail(user == NULL || INF_IS_USER(user));

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_if_fail(iface->insert_text != NULL);

  chunk = inf_text_chunk_new(inf_text_buffer_get_encoding(buffer));

  inf_text_chunk_insert_text(
    chunk,
    0,
    text,
    bytes,
    len,
    user == NULL ? 0 : inf_user_get_id(user)
  );

  iface->insert_text(buffer, pos, chunk, user);

  inf_text_chunk_free(chunk);
}
Beispiel #3
0
/**
 * inf_text_buffer_create_end_iter:
 * @buffer: A #InfTextBuffer.
 *
 * Creates a #InfTextBufferIter pointing to the last segment of @buffer.
 * A #InfTextBufferIter is used to traverse the buffer contents in steps of
 * so-called segments each of which is written by the same user. The function
 * returns %NULL if there are no segments (i.e. the buffer is empty).
 *
 * The iterator stays valid as long as the buffer remains unmodified and
 * must be freed with inf_text_buffer_destroy_iter() before.
 *
 * Returns: (transfer full) (allow-none): A #InfTextBufferIter to be freed by
 * inf_text_buffer_destroy_iter() when done using it, or %NULL.
 **/
InfTextBufferIter*
inf_text_buffer_create_end_iter(InfTextBuffer* buffer)
{
  InfTextBufferInterface* iface;

  g_return_val_if_fail(INF_TEXT_IS_BUFFER(buffer), NULL);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_val_if_fail(iface->create_end_iter != NULL, NULL);

  return iface->create_end_iter(buffer);
}
Beispiel #4
0
/**
 * inf_text_buffer_get_encoding:
 * @buffer: A #InfTextBuffer.
 *
 * Returns the character encoding that the buffer uses. This means that all
 * #InfTextChunk return values are encoded in this encoding and all
 * #InfTextChunk parameters are expected to be encoded in that encoding.
 *
 * Returns: The character encoding for @buffer.
 **/
const gchar*
inf_text_buffer_get_encoding(InfTextBuffer* buffer)
{
  InfTextBufferInterface* iface;

  g_return_val_if_fail(INF_TEXT_IS_BUFFER(buffer), NULL);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_val_if_fail(iface->get_encoding != NULL, NULL);

  return iface->get_encoding(buffer);
}
Beispiel #5
0
/**
 * inf_text_buffer_get_length:
 * @buffer: A #InfTextBuffer.
 *
 * Returns the number of characters in @buffer.
 *
 * Returns: The length of @buffer.
 **/
guint
inf_text_buffer_get_length(InfTextBuffer* buffer)
{
  InfTextBufferInterface* iface;

  g_return_val_if_fail(INF_TEXT_IS_BUFFER(buffer), 0);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_val_if_fail(iface->get_length != NULL, 0);

  return iface->get_length(buffer);
}
Beispiel #6
0
/**
 * inf_text_buffer_iter_prev:
 * @buffer: A #InfTextBuffer.
 * @iter: A #InfTextBufferIter pointing into @buffer.
 *
 * Moves @iter to point to the previous segment in the buffer. If @iter
 * already points to the first segment, @iter is left unmodified and the
 * function returns %FALSE.
 *
 * Returns: Whether @iter was moved.
 **/
gboolean
inf_text_buffer_iter_prev(InfTextBuffer* buffer,
                          InfTextBufferIter* iter)
{
  InfTextBufferInterface* iface;

  g_return_val_if_fail(INF_TEXT_IS_BUFFER(buffer), FALSE);
  g_return_val_if_fail(iter != NULL, FALSE);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_val_if_fail(iface->iter_prev != NULL, FALSE);

  return iface->iter_prev(buffer, iter);
}
Beispiel #7
0
/**
 * inf_text_buffer_destroy_iter:
 * @buffer: A #InfTextBuffer.
 * @iter: (transfer full): A #InfTextBufferIter pointing into @buffer.
 *
 * Destroys a #InfTextBufferIter created by
 * inf_text_buffer_create_begin_iter() or inf_text_buffer_create_end_iter().
 **/
void
inf_text_buffer_destroy_iter(InfTextBuffer* buffer,
                             InfTextBufferIter* iter)
{
  InfTextBufferInterface* iface;

  g_return_if_fail(INF_TEXT_IS_BUFFER(buffer));
  g_return_if_fail(iter != NULL);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_if_fail(iface->destroy_iter != NULL);

  iface->destroy_iter(buffer, iter);
}
Beispiel #8
0
/**
 * inf_text_buffer_get_slice:
 * @buffer: A #InfTextBuffer.
 * @pos: Character offset of where to start extracting.
 * @len: Number of characters to extract.
 *
 * Reads @len characters, starting at @pos, from the buffer, and returns them
 * as a #InfTextChunk.
 *
 * Returns: (transfer full): A #InfTextChunk.
 **/
InfTextChunk*
inf_text_buffer_get_slice(InfTextBuffer* buffer,
                          guint pos,
                          guint len)
{
  InfTextBufferInterface* iface;

  g_return_val_if_fail(INF_TEXT_IS_BUFFER(buffer), NULL);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_val_if_fail(iface->get_slice != NULL, NULL);

  return iface->get_slice(buffer, pos, len);
}
Beispiel #9
0
/**
 * inf_text_buffer_iter_get_author:
 * @buffer: A #InfTextBuffer.
 * @iter: A #InfTextBufferIter pointing into @buffer.
 *
 * Returns the user ID of the user that has written the segment @iter points
 * to.
 *
 * Returns: The user ID of the user that wrote the segment @iter points
 * to.
 **/
guint
inf_text_buffer_iter_get_author(InfTextBuffer* buffer,
                                InfTextBufferIter* iter)
{
  InfTextBufferInterface* iface;

  g_return_val_if_fail(INF_TEXT_IS_BUFFER(buffer), 0);
  g_return_val_if_fail(iter != NULL, 0);

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_val_if_fail(iface->iter_get_author != NULL, 0);

  return iface->iter_get_author(buffer, iter);
}
Beispiel #10
0
/**
 * inf_text_buffer_erase_text:
 * @buffer: A #InfTextBuffer.
 * @pos: The position to begin deleting characters from.
 * @len: The amount of characters to delete.
 * @user: (allow-none): A #InfUser that erases the text, or %NULL.
 *
 * Erases characters from the text buffer.
 **/
void
inf_text_buffer_erase_text(InfTextBuffer* buffer,
                           guint pos,
                           guint len,
                           InfUser* user)
{
  InfTextBufferInterface* iface;

  g_return_if_fail(INF_TEXT_IS_BUFFER(buffer));
  g_return_if_fail(user == NULL || INF_IS_USER(user));

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_if_fail(iface->erase_text != NULL);

  iface->erase_text(buffer, pos, len, user);
}
Beispiel #11
0
/**
 * inf_text_buffer_insert_chunk:
 * @buffer: A #InfTextBuffer.
 * @pos: A character offset into @buffer.
 * @chunk: (transfer none): A #InfTextChunk.
 * @user: (allow-none): A #InfUser inserting @chunk, or %NULL.
 *
 * Inserts a #InfTextChunk into @buffer. @user must not necessarily be the
 * author of @chunk (@chunk may even consist of multiple segments). This
 * happens when undoing a delete operation that erased another user's text.
 **/
void
inf_text_buffer_insert_chunk(InfTextBuffer* buffer,
                             guint pos,
                             InfTextChunk* chunk,
                             InfUser* user)
{
  InfTextBufferInterface* iface;

  g_return_if_fail(INF_TEXT_IS_BUFFER(buffer));
  g_return_if_fail(chunk != NULL);
  g_return_if_fail(user == NULL || INF_IS_USER(user));

  iface = INF_TEXT_BUFFER_GET_IFACE(buffer);
  g_return_if_fail(iface->insert_text != NULL);

  iface->insert_text(buffer, pos, chunk, user);
}
static void
inf_text_default_insert_operation_apply(InfAdoptedOperation* operation,
                                        InfAdoptedUser* by,
                                        InfBuffer* buffer)
{
  InfTextDefaultInsertOperationPrivate* priv;

  g_assert(INF_TEXT_IS_DEFAULT_INSERT_OPERATION(operation));
  g_assert(INF_TEXT_IS_BUFFER(buffer));

  priv = INF_TEXT_DEFAULT_INSERT_OPERATION_PRIVATE(operation);

  inf_text_buffer_insert_chunk(
    INF_TEXT_BUFFER(buffer),
    priv->position,
    priv->chunk,
    INF_USER(by)
  );
}
static void
inf_text_remote_delete_operation_apply(InfAdoptedOperation* operation,
                                       InfAdoptedUser* by,
                                       InfBuffer* buffer)
{
  InfTextRemoteDeleteOperationPrivate* priv;

  g_assert(INF_TEXT_IS_REMOTE_DELETE_OPERATION(operation));
  g_assert(INF_TEXT_IS_BUFFER(buffer));

  priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(operation);

  inf_text_buffer_erase_text(
    INF_TEXT_BUFFER(buffer),
    priv->position,
    priv->length,
    INF_USER(by)
  );
}
Beispiel #14
0
/**
 * inf_text_buffer_text_erased:
 * @buffer: A #InfTextBuffer.
 * @pos: The position to begin deleting characters from.
 * @chunk: A #InfTextChunk containing the erased text.
 * @user: (allow-none): A #InfUser that erases the text, or %NULL.
 *
 * Emits the #InfTextBuffer::text-erased signal. This is meant to be used
 * by interface implementations in their @erase_text function, or when text
 * was erased by other means.
 **/
void
inf_text_buffer_text_erased(InfTextBuffer* buffer,
                            guint pos,
                            InfTextChunk* chunk,
                            InfUser* user)
{
  g_return_if_fail(INF_TEXT_IS_BUFFER(buffer));
  g_return_if_fail(chunk != NULL);
  g_return_if_fail(user == NULL || INF_IS_USER(user));

  g_signal_emit(
    G_OBJECT(buffer),
    text_buffer_signals[TEXT_ERASED],
    0,
    pos,
    chunk,
    user
  );
}
static gboolean
inf_text_remote_delete_operation_apply(InfAdoptedOperation* operation,
                                       InfAdoptedUser* by,
                                       InfBuffer* buffer,
                                       GError** error)
{
  InfTextRemoteDeleteOperationPrivate* priv;

  g_assert(INF_TEXT_IS_REMOTE_DELETE_OPERATION(operation));
  g_assert(INF_TEXT_IS_BUFFER(buffer));

  priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(operation);

  if(priv->position + priv->length >
     inf_text_buffer_get_length(INF_TEXT_BUFFER(buffer)))
  {
    g_set_error(
      error,
      g_quark_from_static_string("INF_TEXT_OPERATION_ERROR"),
      INF_TEXT_OPERATION_ERROR_INVALID_DELETE,
      "%s",
      _("Attempt to remove text from after the end of the document")
    );

    return FALSE;
  }
  else
  {
    inf_text_buffer_erase_text(
      INF_TEXT_BUFFER(buffer),
      priv->position,
      priv->length,
      INF_USER(by)
    );
    
    return TRUE;
  }
}
static InfAdoptedOperation*
inf_text_remote_delete_operation_apply_transformed(InfAdoptedOperation* op,
                                                   InfAdoptedOperation* trans,
                                                   InfAdoptedUser* by,
                                                   InfBuffer* buffer,
                                                   GError** error)
{
  InfTextRemoteDeleteOperationPrivate* priv;
  InfTextChunk* chunk;
  InfTextChunk* temp_slice;
  GSList* list;
  GSList* item;
  InfAdoptedOperation* operation;
  GSList* recon_list;
  GSList* recon_item;
  InfTextRemoteDeleteOperationRecon* recon;
  InfTextDefaultDeleteOperation* result;

  g_assert(INF_TEXT_IS_REMOTE_DELETE_OPERATION(op));
  g_assert(INF_TEXT_IS_BUFFER(buffer));

  /* TODO: We can probably optimize this function, but then we should
   * a) profile it and b) in many cases input parameters to this function
   * are trivial anyway. */
  if(INF_ADOPTED_IS_SPLIT_OPERATION(trans))
  {
    list = inf_adopted_split_operation_unsplit(
      INF_ADOPTED_SPLIT_OPERATION(trans)
    );
  }
  else
  {
    list = g_slist_prepend(NULL, trans);
  }

  chunk = inf_text_chunk_new(
    inf_text_buffer_get_encoding(INF_TEXT_BUFFER(buffer))
  );

  /* We assume the list of remote delete operations to be in order */
  for(item = list; item != NULL; item = g_slist_next(item))
  {
    g_assert(INF_TEXT_IS_REMOTE_DELETE_OPERATION(item->data));
    priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(item->data);

    operation = INF_ADOPTED_OPERATION(item->data);

    if(priv->length > 0)
    {
      temp_slice = inf_text_buffer_get_slice(
        INF_TEXT_BUFFER(buffer),
        priv->position,
        priv->length
      );

      recon_list = inf_text_remote_delete_operation_recon_feed(
        priv->recon,
        0,
        temp_slice
      );

      inf_text_chunk_free(temp_slice);
    }
    else
    {
      recon_list = priv->recon;
    }

    for(recon_item = recon_list;
        recon_item != NULL;
        recon_item = g_slist_next(recon_item))
    {
      recon = (InfTextRemoteDeleteOperationRecon*)recon_item->data;
      g_assert(priv->recon_offset + recon->position ==
               inf_text_chunk_get_length(chunk));

      inf_text_chunk_insert_chunk(
        chunk,
        inf_text_chunk_get_length(chunk),
        recon->chunk
      );
    }

    /* Free recon list if newly allocated */
    if(priv->length > 0)
      inf_text_remote_delete_operation_recon_free(recon_list);

    if(!inf_adopted_operation_apply(operation, by, buffer, error))
    {
      g_slist_free(list);
      inf_text_chunk_free(chunk);
      return NULL;
    }
  }

  g_slist_free(list);

  priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(op);
  result = inf_text_default_delete_operation_new(priv->position, chunk);
  inf_text_chunk_free(chunk);

  return INF_ADOPTED_OPERATION(result);
}
static void
infinoted_plugin_document_stream_stop(
  InfinotedPluginDocumentStreamStream* stream,
  gboolean send_stop)
{
  guint32 comm;
  InfSession* session;

  if(send_stop)
  {
    comm = 5; /* STOP */
    if(!infinoted_plugin_document_stream_send(stream, &comm, 4))
      return;
  }

  if(stream->user != NULL)
  {
    g_assert(stream->proxy != NULL);
    g_object_get(G_OBJECT(stream->proxy), "session", &session, NULL);
    inf_session_set_user_status(session, stream->user, INF_USER_UNAVAILABLE);
    g_object_unref(session);

    g_object_unref(stream->user);
    stream->user = NULL;
  }

  if(stream->proxy != NULL)
  {
    g_object_unref(stream->proxy);
    stream->proxy = NULL;
  }

  if(stream->buffer != NULL)
  {
    if(INF_TEXT_IS_BUFFER(stream->buffer))
    {
      inf_signal_handlers_disconnect_by_func(
        G_OBJECT(stream->buffer),
        G_CALLBACK(infinoted_plugin_document_stream_text_inserted_cb),
        stream
      );

      inf_signal_handlers_disconnect_by_func(
        G_OBJECT(stream->buffer),
        G_CALLBACK(infinoted_plugin_document_stream_text_erased_cb),
        stream
      );
    }
    else if(INF_IS_CHAT_BUFFER(stream->buffer))
    {
      inf_signal_handlers_disconnect_by_func(
        G_OBJECT(stream->buffer),
        G_CALLBACK(infinoted_plugin_document_stream_chat_add_message_cb),
        stream
      );
    }

    g_object_unref(stream->buffer);
    stream->buffer = NULL;
  }

  if(stream->subscribe_request != NULL)
  {
    inf_signal_handlers_disconnect_by_func(
      G_OBJECT(stream->subscribe_request),
      G_CALLBACK(infinoted_plugin_document_stream_subscribe_func),
      stream
    );

    stream->subscribe_request = NULL;
  }

  if(stream->user_request != NULL)
  {
    inf_signal_handlers_disconnect_by_func(
      G_OBJECT(stream->user_request),
      G_CALLBACK(infinoted_plugin_document_stream_user_join_func),
      stream
    );

    stream->user_request = NULL;
  }
}