/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-stream-fs.c : file system based stream */

/*
 * Authors: Bertrand Guiheneuf <bertrand@helixcode.com>
 *	    Michael Zucchi <notzed@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 * USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <glib/gstdio.h>

#include "camel-file-utils.h"
#include "camel-operation.h"
#include "camel-stream-fs.h"
#include "camel-win32.h"

#define CAMEL_STREAM_FS_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), CAMEL_TYPE_STREAM_FS, CamelStreamFsPrivate))

struct _CamelStreamFsPrivate {
	gint fd;	/* file descriptor on the underlying file */
};

/* Forward Declarations */
static void camel_stream_fs_seekable_init (GSeekableIface *interface);

G_DEFINE_TYPE_WITH_CODE (
	CamelStreamFs, camel_stream_fs, CAMEL_TYPE_STREAM,
	G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, camel_stream_fs_seekable_init))

static void
stream_fs_finalize (GObject *object)
{
	CamelStreamFsPrivate *priv;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (object);

	if (priv->fd != -1)
		close (priv->fd);

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (camel_stream_fs_parent_class)->finalize (object);
}

static gssize
stream_fs_read (CamelStream *stream,
                gchar *buffer,
                gsize n,
                GCancellable *cancellable,
                GError **error)
{
	CamelStreamFsPrivate *priv;
	gssize nread;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);

	nread = camel_read (priv->fd, buffer, n, cancellable, error);

	if (nread == 0)
		stream->eos = TRUE;

	return nread;
}

static gssize
stream_fs_write (CamelStream *stream,
                 const gchar *buffer,
                 gsize n,
                 GCancellable *cancellable,
                 GError **error)
{
	CamelStreamFsPrivate *priv;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);

	return camel_write (priv->fd, buffer, n, cancellable, error);
}

static gint
stream_fs_flush (CamelStream *stream,
                 GCancellable *cancellable,
                 GError **error)
{
	CamelStreamFsPrivate *priv;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);

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

	if (fsync (priv->fd) == -1) {
		g_set_error (
			error, G_IO_ERROR,
			g_io_error_from_errno (errno),
			"%s", g_strerror (errno));
		return -1;
	}

	return 0;
}

static gint
stream_fs_close (CamelStream *stream,
                 GCancellable *cancellable,
                 GError **error)
{
	CamelStreamFsPrivate *priv;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);

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

	if (close (priv->fd) == -1) {
		g_set_error (
			error, G_IO_ERROR,
			g_io_error_from_errno (errno),
			"%s", g_strerror (errno));
		return -1;
	}

	priv->fd = -1;

	return 0;
}

static goffset
stream_fs_tell (GSeekable *seekable)
{
	CamelStreamFsPrivate *priv;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (seekable);

	return (goffset) lseek (priv->fd, 0, SEEK_CUR);
}

static gboolean
stream_fs_can_seek (GSeekable *seekable)
{
	return TRUE;
}

static gboolean
stream_fs_seek (GSeekable *seekable,
                goffset offset,
                GSeekType type,
                GCancellable *cancellable,
                GError **error)
{
	CamelStreamFsPrivate *priv;
	goffset real = 0;

	priv = CAMEL_STREAM_FS_GET_PRIVATE (seekable);

	switch (type) {
	case G_SEEK_SET:
		real = offset;
		break;
	case G_SEEK_CUR:
		real = g_seekable_tell (seekable) + offset;
		break;
	case G_SEEK_END:
		real = lseek (priv->fd, offset, SEEK_END);
		if (real == -1) {
			g_set_error (
				error, G_IO_ERROR,
				g_io_error_from_errno (errno),
				"%s", g_strerror (errno));
			return FALSE;
		}
		return TRUE;
	}

	real = lseek (priv->fd, real, SEEK_SET);
	if (real == -1) {
		g_set_error (
			error, G_IO_ERROR,
			g_io_error_from_errno (errno),
			"%s", g_strerror (errno));
		return FALSE;
	}

	CAMEL_STREAM (seekable)->eos = FALSE;

	return TRUE;
}

static gboolean
stream_fs_can_truncate (GSeekable *seekable)
{
	return FALSE;
}

static gboolean
stream_fs_truncate_fn (GSeekable *seekable,
                       goffset offset,
                       GCancellable *cancellable,
                       GError **error)
{
	/* XXX Don't bother translating this.  Camel never calls it. */
	g_set_error (
		error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
		"Truncation is not supported");

	return FALSE;
}

static void
camel_stream_fs_class_init (CamelStreamFsClass *class)
{
	GObjectClass *object_class;
	CamelStreamClass *stream_class;

	g_type_class_add_private (class, sizeof (CamelStreamFsPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->finalize = stream_fs_finalize;

	stream_class = CAMEL_STREAM_CLASS (class);
	stream_class->read = stream_fs_read;
	stream_class->write = stream_fs_write;
	stream_class->flush = stream_fs_flush;
	stream_class->close = stream_fs_close;
}

static void
camel_stream_fs_seekable_init (GSeekableIface *interface)
{
	interface->tell = stream_fs_tell;
	interface->can_seek = stream_fs_can_seek;
	interface->seek = stream_fs_seek;
	interface->can_truncate = stream_fs_can_truncate;
	interface->truncate_fn = stream_fs_truncate_fn;
}

static void
camel_stream_fs_init (CamelStreamFs *stream)
{
	stream->priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);
	stream->priv->fd = -1;
}

/**
 * camel_stream_fs_new_with_fd:
 * @fd: a file descriptor
 *
 * Creates a new fs stream using the given file descriptor @fd as the
 * backing store. When the stream is destroyed, the file descriptor
 * will be closed.
 *
 * Returns: a new #CamelStreamFs
 **/
CamelStream *
camel_stream_fs_new_with_fd (gint fd)
{
	CamelStreamFsPrivate *priv;
	CamelStream *stream;

	if (fd == -1)
		return NULL;

	stream = g_object_new (CAMEL_TYPE_STREAM_FS, NULL);
	priv = CAMEL_STREAM_FS_GET_PRIVATE (stream);

	priv->fd = fd;

	return stream;
}

/**
 * camel_stream_fs_new_with_name:
 * @name: a local filename
 * @flags: flags as in open(2)
 * @mode: a file mode
 * @error: return location for a #GError, or %NULL
 *
 * Creates a new #CamelStreamFs corresponding to the named file, flags,
 * and mode.
 *
 * Returns: the new stream, or %NULL on error.
 **/
CamelStream *
camel_stream_fs_new_with_name (const gchar *name,
                               gint flags,
                               mode_t mode,
                               GError **error)
{
	gint fd;

	fd = g_open (name, flags | O_BINARY, mode);
	if (fd == -1) {
		g_set_error (
			error, G_IO_ERROR,
			g_io_error_from_errno (errno),
			"%s", g_strerror (errno));
		return NULL;
	}

	return camel_stream_fs_new_with_fd (fd);
}

/**
 * camel_stream_fs_get_fd:
 * @stream: a #CamelStream
 *
 * Since: 2.32
 **/
gint
camel_stream_fs_get_fd (CamelStreamFs *stream)
{
	g_return_val_if_fail (CAMEL_IS_STREAM_FS (stream), -1);

	return stream->priv->fd;
}
