/*
 * SPDX-License-Identifier: LGPL-2.0+
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library. If not, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include "ostree-content-writer.h"
#include "ostree-repo-private.h"
#include "ostree-autocleanups.h"

struct _OstreeContentWriter
{
  GOutputStream parent_instance;

  OstreeRepo *repo;
  OstreeRepoBareContent output;
};

G_DEFINE_TYPE (OstreeContentWriter, ostree_content_writer, G_TYPE_OUTPUT_STREAM)

static void     ostree_content_writer_finalize     (GObject *object);
static gssize   ostree_content_writer_write         (GOutputStream         *stream,
                                                     const void                 *buffer,
                                                     gsize                 count,
                                                     GCancellable         *cancellable,
                                                     GError              **error);
static gboolean ostree_content_writer_close        (GOutputStream         *stream,
                                                    GCancellable         *cancellable,
                                                    GError              **error);

static void
ostree_content_writer_class_init (OstreeContentWriterClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);

  gobject_class->finalize     = ostree_content_writer_finalize;

  stream_class->write_fn = ostree_content_writer_write;
  stream_class->close_fn = ostree_content_writer_close;
}

static void
ostree_content_writer_finalize (GObject *object)
{
  OstreeContentWriter *stream;

  stream = (OstreeContentWriter*)(object);

  g_clear_object (&stream->repo);
  _ostree_repo_bare_content_cleanup (&stream->output);

  G_OBJECT_CLASS (ostree_content_writer_parent_class)->finalize (object);
}

static void
ostree_content_writer_init (OstreeContentWriter *self)
{
  self->output.initialized = FALSE;
 }

OstreeContentWriter *
_ostree_content_writer_new (OstreeRepo           *repo,
                           const char            *checksum,
                           guint                  uid,
                           guint                  gid,
                           guint                  mode,
                           guint64                content_len,
                           GVariant              *xattrs,
                           GError               **error)
{
  g_autoptr(OstreeContentWriter) stream = g_object_new (OSTREE_TYPE_CONTENT_WRITER, NULL);
  stream->repo = g_object_ref (repo);
  if (!_ostree_repo_bare_content_open (stream->repo, checksum, content_len, uid, gid, mode, xattrs,
                                       &stream->output, NULL, error))
    return NULL;
  return g_steal_pointer (&stream);
}

static gssize
ostree_content_writer_write (GOutputStream  *stream,
                             const void    *buffer,
                             gsize          count,
                             GCancellable  *cancellable,
                             GError       **error)
{
  OstreeContentWriter *self = (OstreeContentWriter*) stream;

  if (g_cancellable_set_error_if_cancelled (cancellable, error))
    return -1;

  if (!_ostree_repo_bare_content_write (self->repo, &self->output,
                                        buffer, count, cancellable, error))
    return -1;
  return count;
}

static gboolean
ostree_content_writer_close (GOutputStream        *stream,
                             GCancellable         *cancellable,
                             GError              **error)
{
  /* We don't expect people to invoke close() - they need to call finish()
   * to get the checksum.  We'll clean up in finalize anyways if need be.
   */
  return TRUE;
}

/**
 * ostree_content_writer_finish:
 * @self: Writer
 * @cancellable: Cancellable
 * @error: Error
 *
 * Complete the object write and return the checksum.
 * Returns: (transfer full): Checksum, or %NULL on error
 */
char *
ostree_content_writer_finish (OstreeContentWriter  *self,
                              GCancellable         *cancellable,
                              GError              **error)
{
  char actual_checksum[OSTREE_SHA256_STRING_LEN+1];
  if (!_ostree_repo_bare_content_commit (self->repo, &self->output, actual_checksum,
                                         sizeof (actual_checksum), cancellable, error))
    return NULL;

  return g_strdup (actual_checksum);
}
