/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
 *
 * mkisofs-glue.c: mkisofs utility functions
 *
 * Copyright (C) 2002-2004 Red Hat, Inc.
 * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Alexander Larsson <alexl@redhat.com>
 *          William Jon McCann <mccann@jhu.edu>
 */

#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <glib/gstdio.h>
#include <libintl.h>
#define _(String) gettext (String)
#include <locale.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

#ifndef HAVE_MKDTEMP
#include "mkdtemp.h"
#endif
#include "mkisofs-glue.h"

static void
write_all (int   fd,
           char *buf,
           int   len)
{
        int bytes;
        int res;

        bytes = 0;
        while (bytes < len) {
                res = write (fd, buf + bytes, len - bytes);
                if (res <= 0) {
                        return;
                }
                bytes += res;
        }
        return;
}

static void
copy_file (const char *source,
           const char *dest)
{
        int         sfd, dfd;
        struct stat stat_buf;
        char        buffer [1024 * 8];
        ssize_t     len;

        if (link (source, dest) == 0) {
                return;
        }

        if (g_stat (source, &stat_buf) != 0) {
                g_warning ("Trying to copy nonexisting file\n");
                return;
        }

        sfd = g_open (source, O_RDONLY, 0);
        if (sfd == -1) {
                g_warning ("Can't copy file (open source failed)\n");
                return;
        }

        dfd = g_open (dest, O_WRONLY | O_CREAT, stat_buf.st_mode);
        if (dfd == -1) {
                close (sfd);
                g_warning ("Can't copy file (open dest '%s' failed)\n", dest);
                perror ("error:");
                return;
        }

        while ( (len = read (sfd, &buffer, sizeof (buffer))) > 0) {
                write_all (dfd, buffer, len);
        }
        close (dfd);
        close (sfd);
}

/* FIXME: This should probably be an inline */
static gboolean
file_info_is_allowed (GnomeVFSFileInfo *info)
{
        g_return_val_if_fail (info != NULL, FALSE);

        /* only allow regular,directory,symlink files */
        if (info->type != GNOME_VFS_FILE_TYPE_REGULAR
            && info->type != GNOME_VFS_FILE_TYPE_DIRECTORY
            && info->type != GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK) {
                return FALSE;
        } else if (info->type == GNOME_VFS_FILE_TYPE_REGULAR
                   && !(info->permissions & GNOME_VFS_PERM_ACCESS_READABLE)) {
                return FALSE;
        }

        return TRUE;
}

static char *
escape_path (const char *str)
{
        char       *escaped, *d;
        const char *s;
        int         len;

        s = str;
        len = 1;
        while (*s != 0) {
                if (*s == '\\' ||
                    *s == '=') {
                        len++;
                }

                len++;
                s++;
        }

        escaped = g_malloc (len);

        s = str;
        d = escaped;
        while (*s != 0) {
                if (*s == '\\' ||
                    *s == '=') {
                        *d++ = '\\';
                }

                *d++ = *s++;
        }
        *d = 0;

        return escaped;
}

static gboolean
dir_is_empty (const char *virtual_path)
{
        GnomeVFSFileInfo        *info;
        GnomeVFSDirectoryHandle *handle;
        GnomeVFSResult           result;
        char                    *escaped_path, *uri;
        gboolean                 found_file;

        escaped_path = gnome_vfs_escape_path_string (virtual_path);
        uri = g_strconcat ("burn:///", escaped_path, NULL);
        g_free (escaped_path);

        result = gnome_vfs_directory_open (&handle, uri, GNOME_VFS_FILE_INFO_DEFAULT);
        g_free (uri);

        if (result != GNOME_VFS_OK) {
                return TRUE;
        }

        info = gnome_vfs_file_info_new ();

        found_file = FALSE;

        while (TRUE) {
                result = gnome_vfs_directory_read_next (handle, info);
                if (result != GNOME_VFS_OK)
                        break;

                /* Skip "." and "..".  */
                if (info->name [0] == '.'
                    && (info->name [1] == 0
                        || (info->name [1] == '.' && info->name [2] == 0))) {
                        gnome_vfs_file_info_clear (info);
                        continue;
                }

                found_file = TRUE;
                break;
                break;
        }

        gnome_vfs_directory_close (handle);
        gnome_vfs_file_info_unref (info);

        return !found_file;
}

static char *
get_backing_file (const char *virtual_path)
{         GnomeVFSHandle *handle;
        GnomeVFSResult  res;
        char           *escaped_path, *uri;
        char           *mapping;

        escaped_path = gnome_vfs_escape_path_string (virtual_path);
        uri = g_strconcat ("burn:///", escaped_path, NULL);
        g_free (escaped_path);
        /* warning, this can hang on fifos etc */
        res = gnome_vfs_open (&handle,
                              uri,
                              GNOME_VFS_OPEN_READ);
        g_free (uri);
        if (res == GNOME_VFS_OK) {
                res =  gnome_vfs_file_control (handle,
                                               "mapping:get_mapping",
                                               &mapping);
                gnome_vfs_close (handle);
                if (res == GNOME_VFS_OK) {
                        return mapping;
                }
        }
        return NULL;
}

static void
graft_file_end_dir_visitor (struct mkisofs_state *state)
{
        char *last_slash;

        if (state->copy_depth > 0) {
                state->copy_depth--;
                if (state->copy_depth == 0) {
                        g_free (state->copy_to_dir);
                        state->copy_to_dir = NULL;
                } else {
                        last_slash = strrchr (state->copy_to_dir, G_DIR_SEPARATOR);
                        if (last_slash != NULL) {
                                *last_slash = 0;
                        }
                }
        }
}

static gboolean
graft_file_visitor (const char           *rel_path,
                    GnomeVFSFileInfo     *info,
                    struct mkisofs_state *state,
                    gboolean             *recurse)
{
        char *mapping, *path1, *path2;
        char *new_copy_dir;
        char *copy_path;

        *recurse = TRUE;

        if (! file_info_is_allowed (info)) {
                g_warning ("Skipping file: %s", rel_path);
                return TRUE;
        }

        if (state->copy_to_dir != NULL) {
                if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
                        new_copy_dir = g_build_filename (state->copy_to_dir,
                                                         info->name,
                                                         NULL);
                        g_free (state->copy_to_dir);
                        state->copy_to_dir = new_copy_dir;
                        g_mkdir (state->copy_to_dir, 0777);
                        state->remove_files = g_list_prepend (state->remove_files, g_strdup (state->copy_to_dir));
                        state->copy_depth++;
                } else {
                        copy_path = g_build_filename (state->copy_to_dir, info->name, NULL);
                        mapping = get_backing_file (rel_path);
                        if (mapping != NULL) {
                                copy_file (mapping, copy_path);
                                state->remove_files = g_list_prepend (state->remove_files, g_strdup (copy_path));
                        }
                }
                return TRUE;
        }

        if (info->type != GNOME_VFS_FILE_TYPE_DIRECTORY) {
                mapping = get_backing_file (rel_path);
                if (mapping != NULL) {
                        path1 = escape_path (rel_path);
                        path2 = escape_path (mapping);
                        state->found_file = TRUE;
                        fprintf (state->graft_file, "%s=%s\n", path1, path2);
                        g_free (path1);
                        g_free (path2);
                        g_free (mapping);
                }
        } else {
                if (dir_is_empty (rel_path)) {
                        path1 = escape_path (rel_path);
                        path2 = escape_path (state->emptydir);
                        state->found_file = TRUE;
                        fprintf (state->graft_file, "%s/=%s\n", path1, path2);
                        g_free (path1);
                        g_free (path2);
                } else if (state->depth >= 6) {
                        new_copy_dir = g_build_filename (state->tmpdir, "subdir.XXXXXX", NULL);
                        copy_path = mkdtemp (new_copy_dir);
                        if (copy_path != NULL) {
                                state->remove_files = g_list_prepend (state->remove_files, g_strdup (copy_path));
                                state->copy_depth = 1;
                                state->copy_to_dir = copy_path;
                                path1 = escape_path (rel_path);
                                path2 = escape_path (copy_path);
                                state->found_file = TRUE;
                                fprintf (state->graft_file, "%s/=%s\n", path1, path2);
                                g_free (path1);
                                g_free (path2);
                        } else {
                                g_free (new_copy_dir);
                                g_warning ("Couldn't create temp subdir\n");
                                *recurse = FALSE;
                        }
                }
        }

        return TRUE;
}

void
graft_file_cleanup (struct mkisofs_state *state)
{
	GList *l;

	for (l = state->remove_files; l != NULL; l = l->next) {
		g_remove ((char *)l->data);
		g_free (l->data);
        }
        g_list_free (state->remove_files);

	g_free (state->emptydir);

	g_rmdir (state->tmpdir);
	g_free (state->tmpdir);
}

static void
create_graft_file_do (GnomeVFSURI			*uri,
		      const char			*prefix,
		      struct mkisofs_state		*state)
{
	GnomeVFSFileInfo        *info;
        GnomeVFSDirectoryHandle *handle;
        GnomeVFSResult		 result;
        gboolean		 stop	 = FALSE;


	result = gnome_vfs_directory_open_from_uri (&handle,
                                                    uri,
                                                    GNOME_VFS_FILE_INFO_DEFAULT
                                                    | GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS);
        if (result != GNOME_VFS_OK)
                return;

        info = gnome_vfs_file_info_new ();

        while (! stop) {
                char    *rel_path;
                gboolean recurse;

                result = gnome_vfs_directory_read_next (handle, info);
                if (result != GNOME_VFS_OK)
                        break;

                /* Skip "." and "..".  */
                if (info->name [0] == '.'
                    && (info->name [1] == 0
                        || (info->name [1] == '.' && info->name [2] == 0))) {
                        gnome_vfs_file_info_clear (info);
                        continue;
              }

                if (prefix == NULL)
                        rel_path = g_strdup (info->name);
                else
                        rel_path = g_strconcat (prefix, info->name, NULL);

                recurse = FALSE;
                stop = ! graft_file_visitor (rel_path, info, state, &recurse);

                if (! stop
                    && recurse
                    && info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
                        GnomeVFSURI *new_uri;
                        char        *new_prefix;

                        if (prefix == NULL)
                                new_prefix = g_strconcat (info->name, "/",
                                                          NULL);
                        else
                                new_prefix = g_strconcat (prefix, info->name,
                                                          "/", NULL);

                        new_uri = gnome_vfs_uri_append_file_name (uri, info->name);

                        state->depth++;
                        create_graft_file_do (new_uri,
					      new_prefix,
                                              state);
                        state->depth--;
                        graft_file_end_dir_visitor (state);

                        gnome_vfs_uri_unref (new_uri);
                        g_free (new_prefix);
                }

                g_free (rel_path);


                gnome_vfs_file_info_clear (info);

                if (stop)
                        break;
        }

	gnome_vfs_directory_close (handle);
        gnome_vfs_file_info_unref (info);
}

char *
create_graft_file (GnomeVFSURI			*uri,
                   const char			*prefix,
                   struct mkisofs_state		*state,
		   struct mkisofs_output	*mkisofs_output)
{
	char			*filelist = NULL;
	char			*dirname;
	char			*tempdir;

	dirname = g_strdup_printf ("ncb-%s.XXXXXX", g_get_user_name ());
	tempdir = g_build_filename (g_get_tmp_dir (), dirname, NULL);
	g_free (dirname);
	state->tmpdir = mkdtemp (tempdir);

	if (state->tmpdir == 0) {
		mkisofs_output->error = g_error_new (NAUTILUS_BURN_MKISOFS_ERROR,
						     NAUTILUS_BURN_MKISOFS_ERROR_GENERAL,
						     _("Unable to create temporary directory: %s."),
						     g_strerror (errno));
		goto cleanup;
	}
	state->emptydir = g_build_filename (state->tmpdir, "emptydir", NULL);
	g_mkdir (state->emptydir, 0777);
	state->remove_files = g_list_prepend (state->remove_files, g_strdup (state->emptydir));

	filelist = g_build_filename (state->tmpdir, "filelist", NULL);

	state->graft_file = fopen (filelist, "w");
	if (state->graft_file == NULL) {
		mkisofs_output->error = g_error_new (NAUTILUS_BURN_MKISOFS_ERROR,
						     NAUTILUS_BURN_MKISOFS_ERROR_GENERAL,
						     _("Unable to create temporary file: %s."),
						     g_strerror (errno));
		goto cleanup;
	}

	create_graft_file_do (uri, prefix, state);

	if (! state->found_file) {
		mkisofs_output->error = g_error_new_literal (NAUTILUS_BURN_MKISOFS_ERROR,
							     NAUTILUS_BURN_MKISOFS_ERROR_GENERAL,
							     _("There are no files to write to disc."));
	}

 cleanup:
	fclose (state->graft_file);
	state->remove_files = g_list_prepend (state->remove_files, g_strdup (filelist));

	return mkisofs_output->error ? NULL : filelist;
}

/* this is an ugly hack until utf8/iconv support is added into upstream mkisofs */
gboolean
ncb_mkisofs_supports_utf8 (void)
{
	char    *standard_error;
	gboolean supported;
	gboolean res;

	res = g_spawn_command_line_sync ("mkisofs -input-charset utf8", NULL, &standard_error, NULL, NULL);
	if (res && !g_strrstr (standard_error, "Unknown charset"))
		supported = TRUE;
	else
		supported = FALSE;

	g_free (standard_error);

	return supported;
}

GQuark
nautilus_burn_mkisofs_error_quark (void)
{
	static GQuark quark = 0;

	if (!quark)
		quark = g_quark_from_static_string ("nautilus_burn_mkisofs_error");

	return quark;
}
