static InfAdoptedSplitOperation*
inf_text_default_delete_operation_transform_split(
  InfTextDeleteOperation* operation,
  guint split_pos,
  guint split_len)
{
  InfTextDefaultDeleteOperationPrivate* priv;
  InfTextChunk* first_chunk;
  InfTextChunk* second_chunk;
  GObject* first;
  GObject* second;
  InfAdoptedSplitOperation* result;

  priv = INF_TEXT_DEFAULT_DELETE_OPERATION_PRIVATE(operation);

  first_chunk = inf_text_chunk_substring(priv->chunk, 0, split_pos);
  second_chunk = inf_text_chunk_substring(
    priv->chunk,
    split_pos,
    inf_text_chunk_get_length(priv->chunk) - split_pos
  );

  first = g_object_new(
    INF_TEXT_TYPE_DEFAULT_DELETE_OPERATION,
    "position", priv->position,
    "chunk", first_chunk,
    NULL
  );

  second = g_object_new(
    INF_TEXT_TYPE_DEFAULT_DELETE_OPERATION,
    "position", priv->position + split_pos + split_len,
    "chunk", second_chunk,
    NULL
  );

  inf_text_chunk_free(first_chunk);
  inf_text_chunk_free(second_chunk);

  result = inf_adopted_split_operation_new(
    INF_ADOPTED_OPERATION(first),
    INF_ADOPTED_OPERATION(second)
  );

  g_object_unref(first);
  g_object_unref(second);
  return result;
}
static InfTextDeleteOperation*
inf_text_default_delete_operation_transform_overlap(
  InfTextDeleteOperation* operation,
  InfTextDeleteOperation* other,
  guint position,
  guint begin,
  guint other_begin,
  guint length)
{
  InfTextDefaultDeleteOperationPrivate* priv;
  InfTextChunk* chunk;
  GObject* result;

  priv = INF_TEXT_DEFAULT_DELETE_OPERATION_PRIVATE(operation);
  chunk = inf_text_chunk_copy(priv->chunk);
  inf_text_chunk_erase(chunk, begin, length);

  result = g_object_new(
    INF_TEXT_TYPE_DEFAULT_DELETE_OPERATION,
    "position", position,
    "chunk", chunk,
    NULL
  );

  inf_text_chunk_free(chunk);
  return INF_TEXT_DELETE_OPERATION(result);
}
Beispiel #3
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);
}
int main()
{
  InfTextChunk* chunk;
  InfTextChunk* chunk2;

  chunk2 = inf_text_chunk_new("UTF-8");

  inf_text_chunk_insert_text(chunk2, 0, "a", 1, 1, 500);
  inf_text_chunk_insert_text(chunk2, 0, "b", 1, 1, 501);
  inf_text_chunk_insert_text(chunk2, 0, "c", 1, 1, 502);
  inf_text_chunk_insert_text(chunk2, 3, "ü", 2, 1, 503);
  chunk = inf_text_chunk_substring(chunk2, 0, 3);

  inf_text_chunk_free(chunk);
  inf_text_chunk_free(chunk2);

  return 0;
}
static void
inf_text_default_delete_operation_finalize(GObject* object)
{
  InfTextDefaultDeleteOperation* operation;
  InfTextDefaultDeleteOperationPrivate* priv;

  operation = INF_TEXT_DEFAULT_DELETE_OPERATION(object);
  priv = INF_TEXT_DEFAULT_DELETE_OPERATION_PRIVATE(operation);

  inf_text_chunk_free(priv->chunk);

  G_OBJECT_CLASS(parent_class)->finalize(object);
}
static InfTextDeleteOperation*
inf_text_remote_delete_operation_transform_overlap(
  InfTextDeleteOperation* operation,
  InfTextDeleteOperation* other,
  guint position,
  guint begin,
  guint other_begin,
  guint length)
{
  InfTextRemoteDeleteOperationPrivate* priv;
  InfTextChunk* chunk;
  GObject* result;
  InfTextRemoteDeleteOperationPrivate* result_priv;


  /* It is actually possible that two remote delete operations are
   * transformed against each other (actually the parts of a splitted
   * remote delete operation). However, they must not overlap. */
  g_assert(INF_TEXT_IS_DEFAULT_DELETE_OPERATION(other));

  priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(operation);

  chunk = inf_text_chunk_substring(
    inf_text_default_delete_operation_get_chunk(
      INF_TEXT_DEFAULT_DELETE_OPERATION(other)
    ),
    other_begin,
    length
  );

  result = g_object_new(
    INF_TEXT_TYPE_REMOTE_DELETE_OPERATION,
    "position", position,
    "length", priv->length - length,
    NULL
  );

  result_priv = INF_TEXT_REMOTE_DELETE_OPERATION_PRIVATE(result);

  result_priv->recon = inf_text_remote_delete_operation_recon_feed(
    priv->recon,
    begin,
    chunk
  );

  inf_text_chunk_free(chunk);

  result_priv->recon_offset = priv->recon_offset;
  return INF_TEXT_DELETE_OPERATION(result);
}
static void
inf_text_remote_delete_operation_recon_free(GSList* recon_list)
{
  GSList* item;
  InfTextRemoteDeleteOperationRecon* recon;

  for(item = recon_list; item != NULL; item = g_slist_next(item))
  {
    recon = (InfTextRemoteDeleteOperationRecon*)item->data;
    inf_text_chunk_free(recon->chunk);
    g_slice_free(InfTextRemoteDeleteOperationRecon, recon);
  }

  g_slist_free(recon_list);
}
static void
inf_test_text_recover_text_erased_cb(InfTextBuffer* buffer,
                                     guint pos,
                                     InfTextChunk* chunk,
                                     InfUser* user,
                                     gpointer user_data)
{
  InfAdoptedOperation* operation;
  guint len;

  InfTextChunk* print_chunk;
  gsize print_bytes;
  gpointer print_text;

  /* If the document has substantial content and this deletes most of it,
   * then print out the document here. */
  len = inf_text_chunk_get_length(chunk);
  if(inf_text_buffer_get_length(buffer) + len >= 50)
  {
    if(len >= (inf_text_buffer_get_length(buffer) + len)*75/100)
    {
      if(*(int*)user_data == 0)
      {
        print_chunk = inf_text_buffer_get_slice(
          buffer,
          0,
          inf_text_buffer_get_length(buffer)
        );

        inf_text_chunk_insert_chunk(print_chunk, pos, chunk);
        print_text = inf_text_chunk_get_text(print_chunk, &print_bytes);
        inf_text_chunk_free(print_chunk);

        printf("%.*s\n", (int)print_bytes, (gchar*)print_text);
        g_free(print_text);
      }

      --*(int*)user_data;
    }
  }

  g_object_unref(buffer);
}
static void
inf_test_util_print_buffer(InfTextBuffer* buffer)
{
  InfTextChunk* chunk;
  gchar* text;
  gsize bytes;

  chunk = inf_text_buffer_get_slice(
    buffer,
    0,
    inf_text_buffer_get_length(buffer)
  );

  text = inf_text_chunk_get_text(chunk, &bytes);
  inf_text_chunk_free(chunk);

  printf("%.*s\n", (int)bytes, text);
  g_free(text);
}
static gboolean
check_buffer(InfTextBuffer* buffer,
             const gchar* check_text,
             const gchar* buffer_name)
{
  InfTextChunk* chunk;
  gpointer text;
  gsize len;
  gboolean result;

  chunk = inf_text_buffer_get_slice(
    buffer,
    0,
    inf_text_buffer_get_length(buffer)
  );

  text = inf_text_chunk_get_text(chunk, &len);
  inf_text_chunk_free(chunk);

  if(strlen(check_text) != len || strncmp(check_text, text, len) != 0)
  {
    printf(
      "%s Buffer has text \"%.*s\" but should have \"%s\"\n",
      buffer_name,
      (int)len, (gchar*)text,
      check_text
    );

    result = FALSE;
  }
  else
  {
    result = TRUE;
  }

  g_free(text);
  return result;
}
static gboolean
inf_text_default_delete_operation_text_match(
  InfTextDefaultDeleteOperation* operation,
  InfTextBuffer* buffer)
{
  InfTextDefaultDeleteOperationPrivate* priv;
  InfTextChunk* chunk;
  gchar* first;
  gchar* second;
  gsize first_len;
  gsize second_len;
  int result;

  priv = INF_TEXT_DEFAULT_DELETE_OPERATION_PRIVATE(operation);

  /* TODO: inf_text_chunk_cmp_substring */
  chunk = inf_text_buffer_get_slice(
    buffer,
    priv->position,
    inf_text_chunk_get_length(priv->chunk)
  );

  first = inf_text_chunk_get_text(priv->chunk, &first_len);
  second = inf_text_chunk_get_text(chunk, &second_len);
  inf_text_chunk_free(chunk);

  /* TODO: Owners must match, too */
  if(first_len == second_len)
    result = memcmp(first, second, first_len);
  else
    result = 1; /* != 0 */

  g_free(second);
  g_free(first);

  return result == 0;
}
static void
infinoted_directory_sync_session_save(InfinotedDirectorySync* dsync,
                                      InfinotedDirectorySyncSession* session)
{
  InfdDirectoryIter* iter;
  GError* error;
  InfBuffer* buffer;
  InfTextChunk* chunk;
  gchar* content;
  gsize bytes;

  iter = &session->iter;
  error = NULL;

  if(session->timeout != NULL)
  {
    inf_io_remove_timeout(
      infd_directory_get_io(dsync->directory),
      session->timeout
    );

    session->timeout = NULL;
  }

  buffer = inf_session_get_buffer(
    infd_session_proxy_get_session(session->proxy)
  );

  error = NULL;
  if(!infinoted_util_create_dirname(session->path, &error))
  {
    g_warning(_("Failed to create directory for path \"%s\": %s\n\n"),
              session->path, error->message);
    g_error_free(error);
  }
  else
  {
    /* TODO: Use the iterator API here, which should be less expensive */
    chunk = inf_text_buffer_get_slice(
      INF_TEXT_BUFFER(buffer),
      0,
      inf_text_buffer_get_length(INF_TEXT_BUFFER(buffer))
    );

    content = inf_text_chunk_get_text(chunk, &bytes);
    inf_text_chunk_free(chunk);

    if(!g_file_set_contents(session->path, content, bytes, &error))
    {
      g_warning(
        _("Failed to write session for path \"%s\": %s\n\n"
          "Will retry in %u seconds."),
        session->path, error->message, dsync->sync_interval
      );

      g_error_free(error);

      infinoted_directory_sync_session_start(session->dsync, session);
    }

    g_free(content);
  }
}
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
foreach_test_func(const gchar* testfile,
                  gpointer user_data)
{
  test_result* result;
  xmlDocPtr doc;
  xmlNodePtr root;
  xmlNodePtr child;

  GSList* requests;
  InfTextChunk* initial;
  GSList* users;
  guint max_total_log_size;
  GError* error;
  gboolean res;

  /* Only process XML files, not the Makefiles or other stuff */
  if(!g_str_has_suffix(testfile, ".xml"))
    return;

  result = (test_result*)user_data;
  doc = xmlParseFile(testfile);

  requests = NULL;
  initial = NULL;
  users = NULL;
  max_total_log_size = 0;
  error = NULL;

  printf("%s... ", testfile);
  fflush(stdout);

  ++ result->total;

  if(doc != NULL)
  {
    root = xmlDocGetRootElement(doc);
    for(child = root->children; child != NULL; child = child->next)
    {
      if(child->type != XML_ELEMENT_NODE) continue;

      if(strcmp((const char*)child->name, "log") == 0)
      {
        res = inf_xml_util_get_attribute_uint_required(
          child,
          "size",
          &max_total_log_size,
          &error
        );

        if(!res)
          break;
      }
      else if(strcmp((const char*)child->name, "initial-buffer") == 0)
      {
        if(initial != NULL) inf_text_chunk_free(initial);
        initial = inf_test_util_parse_buffer(child, &error);
        if(initial == NULL) break;
      }
      else if(strcmp((const char*)child->name, "user") == 0)
      {
        if(inf_test_util_parse_user(child, &users, &error) == FALSE)
          break;
      }
      else if(strcmp((const char*)child->name, "request") == 0 ||
              strcmp((const char*)child->name, "verify") == 0)
      {
        requests = g_slist_prepend(requests, child);
      }
      else
      {
        g_set_error(
          &error,
          inf_test_util_parse_error_quark(),
          INF_TEST_UTIL_PARSE_ERROR_UNEXPECTED_NODE,
          "Node '%s' unexpected",
          (const gchar*)child->name
        );

        break;
      }
    }

    if(error != NULL)
    {
      printf("Failed to parse: %s\n", error->message);
      g_error_free(error);
      xmlFreeDoc(doc);

      g_slist_free(requests);
      if(initial != NULL) inf_text_chunk_free(initial);
      g_slist_free(users);
    }
    else
    {
      g_assert(initial != NULL);

      requests = g_slist_reverse(requests);
      if(perform_test(max_total_log_size, initial, users, requests, &error) ==
         TRUE)
      {
        ++ result->passed;
        printf("OK\n");
      }
      else
      {
        printf("FAILED (%s)\n", error->message);
        g_error_free(error);
      }

      xmlFreeDoc(doc);
      g_slist_free(requests);
      inf_text_chunk_free(initial);
      g_slist_free(users);
    }
  }
}