/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* caja-file-utilities.c - implementation of file manipulation routines.

   Copyright (C) 1999, 2000, 2001 Eazel, Inc.

   The Mate Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Mate 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Mate Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
   Boston, MA 02110-1301, USA.

   Authors: John Sullivan <sullivan@eazel.com>
*/

#include <config.h>
#include "caja-file-utilities.h"

#include "caja-global-preferences.h"
#include "caja-lib-self-check-functions.h"
#include "caja-metadata.h"
#include "caja-file.h"
#include "caja-file-operations.h"
#include "caja-search-directory.h"
#include "caja-signaller.h"
#include <eel/eel-glib-extensions.h>
#include <eel/eel-stock-dialogs.h>
#include <eel/eel-string.h>
#include <eel/eel-debug.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <unistd.h>
#include <stdlib.h>

#include <src/glibcompat.h> /* for g_list_free_full */

#define CAJA_USER_DIRECTORY_NAME ".config/caja"
#define DEFAULT_CAJA_DIRECTORY_MODE (0755)

#define DESKTOP_DIRECTORY_NAME "Desktop"
#define LEGACY_DESKTOP_DIRECTORY_NAME ".mate-desktop"
#define DEFAULT_DESKTOP_DIRECTORY_MODE (0755)

static void update_xdg_dir_cache (void);
static void schedule_user_dirs_changed (void);
static void desktop_dir_changed (void);
static GFile *caja_find_file_insensitive_next (GFile *parent, const gchar *name);

char *
caja_compute_title_for_location (GFile *location)
{
    CajaFile *file;
    char *title;

    /* TODO-gio: This doesn't really work all that great if the
       info about the file isn't known atm... */

    title = NULL;
    if (location)
    {
        file = caja_file_get (location);
        title = caja_file_get_description (file);
        if (title == NULL)
        {
            title = caja_file_get_display_name (file);
        }
        caja_file_unref (file);
    }

    if (title == NULL)
    {
        title = g_strdup ("");
    }

    return title;
}


/**
 * caja_get_user_directory:
 *
 * Get the path for the directory containing caja settings.
 *
 * Return value: the directory path.
 **/
char* caja_get_user_directory(void)
{
	/* FIXME bugzilla.gnome.org 41286:
	 * How should we handle the case where this mkdir fails?
	 * Note that caja_application_startup will refuse to launch if this
	 * directory doesn't get created, so that case is OK. But the directory
	 * could be deleted after Caja was launched, and perhaps
	 * there is some bad side-effect of not handling that case.
	 * <<<
	 * Si alguien tiene tiempo, puede enviar este codigo a Nautilus.
	 * Obviamente, con los comentarios traducidos al Inglés.
	 */
	char* user_directory = g_build_filename(g_get_user_config_dir(), "caja", NULL);
	/* Se necesita que esta dirección sea una carpeta, con los permisos
	 * DEFAULT_CAJA_DIRECTORY_MODE. Pero si es un archivo, el programa intentará
	 * eliminar el archivo silenciosamente. */
	if (g_file_test(user_directory, G_FILE_TEST_IS_DIR) == FALSE ||
		g_access(user_directory, DEFAULT_CAJA_DIRECTORY_MODE) == -1)
	{
		/* Se puede obtener un enlace simbolico a una carpeta */
		if (g_file_test(user_directory, G_FILE_TEST_IS_SYMLINK) == TRUE)
		{
			/* intentaremos saber si el enlace es una carpeta, y tiene los
			 * permisos adecuados */
			char* link = g_file_read_link(user_directory, NULL);

			if (link)
			{
				/* Si el enlace no es un directorio, o si falla al hacer chmod,
				 * se borra el enlace y se crea la carpeta */
				if (g_file_test(link, G_FILE_TEST_IS_DIR) != TRUE ||
					g_chmod(link, DEFAULT_CAJA_DIRECTORY_MODE) != 0)
				{
					/* podemos borrar el enlace y crear la carpeta */
					g_unlink(user_directory);
					g_mkdir(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
				}

				g_free(link);
			}
		}
		else if (g_file_test(user_directory, G_FILE_TEST_IS_DIR) == TRUE)
		{
			g_chmod(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
		}
		else if (g_file_test(user_directory, G_FILE_TEST_EXISTS) == TRUE)
		{
			/* podemos borrar el enlace y crear la carpeta */
			g_unlink(user_directory);
			g_mkdir(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
		}
		else
		{
			/* Si no existe ningun archivo, se crea la carpeta */
			g_mkdir_with_parents(user_directory, DEFAULT_CAJA_DIRECTORY_MODE);
		}

		/* Faltan permisos */
		if (g_chmod(user_directory, DEFAULT_CAJA_DIRECTORY_MODE) != 0)
		{
			GtkWidget* dialog = gtk_message_dialog_new(
				NULL,
				GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_ERROR,
				GTK_BUTTONS_CLOSE,
				"The path for the directory containing caja settings need read and write permissions: %s",
				user_directory);

			gtk_dialog_run(GTK_DIALOG(dialog));
			gtk_widget_destroy(dialog);

			exit(0);
		}
	}

	return user_directory;
}

/**
 * caja_get_accel_map_file:
 *
 * Get the path for the filename containing caja accelerator map.
 * The filename need not exist.
 *
 * Return value: the filename path, or NULL if the home directory could not be found
 **/
char* caja_get_accel_map_file(void)
{
	const gchar* override;

	override = g_getenv ("MATE22_USER_DIR");

	if (override)
	{
		return g_build_filename(override, "accels", "caja", NULL);
	}
	else
	{
		return g_build_filename(g_get_user_config_dir(), "caja", "accels", NULL);
	}
}

typedef struct {
	char*type;
	char*path;
	CajaFile* file;
} XdgDirEntry;


static XdgDirEntry *
parse_xdg_dirs (const char *config_file)
{
    GArray *array;
    char *config_file_free = NULL;
    XdgDirEntry dir;
    char *data;
    char **lines;
    char *p, *d;
    int i;
    char *type_start, *type_end;
    char *value, *unescaped;
    gboolean relative;

    array = g_array_new (TRUE, TRUE, sizeof (XdgDirEntry));

    if (config_file == NULL)
    {
        config_file_free = g_build_filename (g_get_user_config_dir (),
                                             "user-dirs.dirs", NULL);
        config_file = (const char *)config_file_free;
    }

    if (g_file_get_contents (config_file, &data, NULL, NULL))
    {
        lines = g_strsplit (data, "\n", 0);
        g_free (data);
        for (i = 0; lines[i] != NULL; i++)
        {
            p = lines[i];
            while (g_ascii_isspace (*p))
                p++;

            if (*p == '#')
                continue;

            value = strchr (p, '=');
            if (value == NULL)
                continue;
            *value++ = 0;

            g_strchug (g_strchomp (p));
            if (!g_str_has_prefix (p, "XDG_"))
                continue;
            if (!g_str_has_suffix (p, "_DIR"))
                continue;
            type_start = p + 4;
            type_end = p + strlen (p) - 4;

            while (g_ascii_isspace (*value))
                value++;

            if (*value != '"')
                continue;
            value++;

            relative = FALSE;
            if (g_str_has_prefix (value, "$HOME"))
            {
                relative = TRUE;
                value += 5;
                while (*value == '/')
                    value++;
            }
            else if (*value != '/')
                continue;

            d = unescaped = g_malloc (strlen (value) + 1);
            while (*value && *value != '"')
            {
                if ((*value == '\\') && (*(value + 1) != 0))
                    value++;
                *d++ = *value++;
            }
            *d = 0;

            *type_end = 0;
            dir.type = g_strdup (type_start);
            if (relative)
            {
                dir.path = g_build_filename (g_get_home_dir (), unescaped, NULL);
                g_free (unescaped);
            }
            else
                dir.path = unescaped;

            g_array_append_val (array, dir);
        }

        g_strfreev (lines);
    }

    g_free (config_file_free);

    return (XdgDirEntry *)g_array_free (array, FALSE);
}

static XdgDirEntry *cached_xdg_dirs = NULL;
static GFileMonitor *cached_xdg_dirs_monitor = NULL;

static void
xdg_dir_changed (CajaFile *file,
                 XdgDirEntry *dir)
{
    GFile *location, *dir_location;
    char *path;

    location = caja_file_get_location (file);
    dir_location = g_file_new_for_path (dir->path);
    if (!g_file_equal (location, dir_location))
    {
        path = g_file_get_path (location);

        if (path)
        {
            char *argv[5];
            int i;

            g_free (dir->path);
            dir->path = path;

            i = 0;
            argv[i++] = "xdg-user-dirs-update";
            argv[i++] = "--set";
            argv[i++] = dir->type;
            argv[i++] = dir->path;
            argv[i++] = NULL;

            /* We do this sync, to avoid possible race-conditions
               if multiple dirs change at the same time. Its
               blocking the main thread, but these updates should
               be very rare and very fast. */
            g_spawn_sync (NULL,
                          argv, NULL,
                          G_SPAWN_SEARCH_PATH |
                          G_SPAWN_STDOUT_TO_DEV_NULL |
                          G_SPAWN_STDERR_TO_DEV_NULL,
                          NULL, NULL,
                          NULL, NULL, NULL, NULL);
            g_reload_user_special_dirs_cache ();
            schedule_user_dirs_changed ();
            desktop_dir_changed ();
            /* Icon might have changed */
            caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_INFO);
        }
    }
    g_object_unref (location);
    g_object_unref (dir_location);
}

static void
xdg_dir_cache_changed_cb (GFileMonitor  *monitor,
                          GFile *file,
                          GFile *other_file,
                          GFileMonitorEvent event_type)
{
    if (event_type == G_FILE_MONITOR_EVENT_CHANGED ||
            event_type == G_FILE_MONITOR_EVENT_CREATED)
    {
        update_xdg_dir_cache ();
    }
}

static int user_dirs_changed_tag = 0;

static gboolean
emit_user_dirs_changed_idle (gpointer data)
{
    g_signal_emit_by_name (caja_signaller_get_current (),
                           "user_dirs_changed");
    user_dirs_changed_tag = 0;
    return FALSE;
}

static void
schedule_user_dirs_changed (void)
{
    if (user_dirs_changed_tag == 0)
    {
        user_dirs_changed_tag = g_idle_add (emit_user_dirs_changed_idle, NULL);
    }
}

static void
unschedule_user_dirs_changed (void)
{
    if (user_dirs_changed_tag != 0)
    {
        g_source_remove (user_dirs_changed_tag);
        user_dirs_changed_tag = 0;
    }
}

static void
free_xdg_dir_cache (void)
{
    int i;

    if (cached_xdg_dirs != NULL)
    {
        for (i = 0; cached_xdg_dirs[i].type != NULL; i++)
        {
            if (cached_xdg_dirs[i].file != NULL)
            {
                caja_file_monitor_remove (cached_xdg_dirs[i].file,
                                          &cached_xdg_dirs[i]);
                g_signal_handlers_disconnect_by_func (cached_xdg_dirs[i].file,
                                                      G_CALLBACK (xdg_dir_changed),
                                                      &cached_xdg_dirs[i]);
                caja_file_unref (cached_xdg_dirs[i].file);
            }
            g_free (cached_xdg_dirs[i].type);
            g_free (cached_xdg_dirs[i].path);
        }
        g_free (cached_xdg_dirs);
    }
}

static void
destroy_xdg_dir_cache (void)
{
    free_xdg_dir_cache ();
    unschedule_user_dirs_changed ();
    desktop_dir_changed ();

    if (cached_xdg_dirs_monitor != NULL)
    {
        g_object_unref  (cached_xdg_dirs_monitor);
        cached_xdg_dirs_monitor = NULL;
    }
}

static void
update_xdg_dir_cache (void)
{
    GFile *file;
    char *config_file, *uri;
    int i;

    free_xdg_dir_cache ();
    g_reload_user_special_dirs_cache ();
    schedule_user_dirs_changed ();
    desktop_dir_changed ();

    cached_xdg_dirs = parse_xdg_dirs (NULL);

    for (i = 0 ; cached_xdg_dirs[i].type != NULL; i++)
    {
        cached_xdg_dirs[i].file = NULL;
        if (strcmp (cached_xdg_dirs[i].path, g_get_home_dir ()) != 0)
        {
            uri = g_filename_to_uri (cached_xdg_dirs[i].path, NULL, NULL);
            cached_xdg_dirs[i].file = caja_file_get_by_uri (uri);
            caja_file_monitor_add (cached_xdg_dirs[i].file,
                                   &cached_xdg_dirs[i],
                                   CAJA_FILE_ATTRIBUTE_INFO);
            g_signal_connect (cached_xdg_dirs[i].file,
                              "changed", G_CALLBACK (xdg_dir_changed), &cached_xdg_dirs[i]);
            g_free (uri);
        }
    }

    if (cached_xdg_dirs_monitor == NULL)
    {
        config_file = g_build_filename (g_get_user_config_dir (),
                                        "user-dirs.dirs", NULL);
        file = g_file_new_for_path (config_file);
        cached_xdg_dirs_monitor = g_file_monitor_file (file, 0, NULL, NULL);
        g_signal_connect (cached_xdg_dirs_monitor, "changed",
                          G_CALLBACK (xdg_dir_cache_changed_cb), NULL);
        g_object_unref (file);
        g_free (config_file);

        eel_debug_call_at_shutdown (destroy_xdg_dir_cache);
    }
}

char *
caja_get_xdg_dir (const char *type)
{
    int i;

    if (cached_xdg_dirs == NULL)
    {
        update_xdg_dir_cache ();
    }

    for (i = 0 ; cached_xdg_dirs != NULL && cached_xdg_dirs[i].type != NULL; i++)
    {
        if (strcmp (cached_xdg_dirs[i].type, type) == 0)
        {
            return g_strdup (cached_xdg_dirs[i].path);
        }
    }
    if (strcmp ("DESKTOP", type) == 0)
    {
        return g_build_filename (g_get_home_dir (), DESKTOP_DIRECTORY_NAME, NULL);
    }
    if (strcmp ("TEMPLATES", type) == 0)
    {
        return g_build_filename (g_get_home_dir (), "Templates", NULL);
    }

    return g_strdup (g_get_home_dir ());
}

static char *
get_desktop_path (void)
{
    if (g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR))
    {
        return g_strdup (g_get_home_dir());
    }
    else
    {
        return caja_get_xdg_dir ("DESKTOP");
    }
}

/**
 * caja_get_desktop_directory:
 *
 * Get the path for the directory containing files on the desktop.
 *
 * Return value: the directory path.
 **/
char *
caja_get_desktop_directory (void)
{
    char *desktop_directory;

    desktop_directory = get_desktop_path ();

    /* Don't try to create a home directory */
    if (!g_settings_get_boolean (caja_preferences, CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR))
    {
        if (!g_file_test (desktop_directory, G_FILE_TEST_EXISTS))
        {
            g_mkdir (desktop_directory, DEFAULT_DESKTOP_DIRECTORY_MODE);
            /* FIXME bugzilla.gnome.org 41286:
             * How should we handle the case where this mkdir fails?
             * Note that caja_application_startup will refuse to launch if this
             * directory doesn't get created, so that case is OK. But the directory
             * could be deleted after Caja was launched, and perhaps
             * there is some bad side-effect of not handling that case.
             */
        }
    }

    return desktop_directory;
}

GFile *
caja_get_desktop_location (void)
{
    char *desktop_directory;
    GFile *res;

    desktop_directory = get_desktop_path ();

    res = g_file_new_for_path (desktop_directory);
    g_free (desktop_directory);
    return res;
}


/**
 * caja_get_desktop_directory_uri:
 *
 * Get the uri for the directory containing files on the desktop.
 *
 * Return value: the directory path.
 **/
char *
caja_get_desktop_directory_uri (void)
{
    char *desktop_path;
    char *desktop_uri;

    desktop_path = caja_get_desktop_directory ();
    desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
    g_free (desktop_path);

    return desktop_uri;
}

char *
caja_get_desktop_directory_uri_no_create (void)
{
    char *desktop_path;
    char *desktop_uri;

    desktop_path = get_desktop_path ();
    desktop_uri = g_filename_to_uri (desktop_path, NULL, NULL);
    g_free (desktop_path);

    return desktop_uri;
}

char *
caja_get_home_directory_uri (void)
{
    return  g_filename_to_uri (g_get_home_dir (), NULL, NULL);
}


gboolean
caja_should_use_templates_directory (void)
{
    char *dir;
    gboolean res;

    dir = caja_get_xdg_dir ("TEMPLATES");
    res = strcmp (dir, g_get_home_dir ()) != 0;
    g_free (dir);
    return res;
}

char *
caja_get_templates_directory (void)
{
    return caja_get_xdg_dir ("TEMPLATES");
}

void
caja_create_templates_directory (void)
{
    char *dir;

    dir = caja_get_templates_directory ();
    if (!g_file_test (dir, G_FILE_TEST_EXISTS))
    {
        g_mkdir (dir, DEFAULT_CAJA_DIRECTORY_MODE);
    }
    g_free (dir);
}

char *
caja_get_templates_directory_uri (void)
{
    char *directory, *uri;

    directory = caja_get_templates_directory ();
    uri = g_filename_to_uri (directory, NULL, NULL);
    g_free (directory);
    return uri;
}

char *
caja_get_searches_directory (void)
{
    char *user_dir;
    char *searches_dir;

    user_dir = caja_get_user_directory ();
    searches_dir = g_build_filename (user_dir, "searches", NULL);
    g_free (user_dir);

    if (!g_file_test (searches_dir, G_FILE_TEST_EXISTS))
        g_mkdir (searches_dir, DEFAULT_CAJA_DIRECTORY_MODE);

    return searches_dir;
}

/* These need to be reset to NULL when desktop_is_home_dir changes */
static GFile *desktop_dir = NULL;
static GFile *desktop_dir_dir = NULL;
static char *desktop_dir_filename = NULL;
static gboolean desktop_dir_changed_callback_installed = FALSE;


static void
desktop_dir_changed (void)
{
    if (desktop_dir)
    {
        g_object_unref (desktop_dir);
    }
    if (desktop_dir_dir)
    {
        g_object_unref (desktop_dir_dir);
    }
    g_free (desktop_dir_filename);
    desktop_dir = NULL;
    desktop_dir_dir = NULL;
    desktop_dir_filename = NULL;
}

static void
desktop_dir_changed_callback (gpointer callback_data)
{
    desktop_dir_changed ();
}

static void
update_desktop_dir (void)
{
    char *path;
    char *dirname;

    path = get_desktop_path ();
    desktop_dir = g_file_new_for_path (path);

    dirname = g_path_get_dirname (path);
    desktop_dir_dir = g_file_new_for_path (dirname);
    g_free (dirname);
    desktop_dir_filename = g_path_get_basename (path);
    g_free (path);
}

gboolean
caja_is_home_directory_file (GFile *dir,
                             const char *filename)
{
    char *dirname;
    static GFile *home_dir_dir = NULL;
    static char *home_dir_filename = NULL;

    if (home_dir_dir == NULL)
    {
        dirname = g_path_get_dirname (g_get_home_dir ());
        home_dir_dir = g_file_new_for_path (dirname);
        g_free (dirname);
        home_dir_filename = g_path_get_basename (g_get_home_dir ());
    }

    return (g_file_equal (dir, home_dir_dir) &&
            strcmp (filename, home_dir_filename) == 0);
}

gboolean
caja_is_home_directory (GFile *dir)
{
    static GFile *home_dir = NULL;

    if (home_dir == NULL)
    {
        home_dir = g_file_new_for_path (g_get_home_dir ());
    }

    return g_file_equal (dir, home_dir);
}

gboolean
caja_is_root_directory (GFile *dir)
{
    static GFile *root_dir = NULL;

    if (root_dir == NULL)
    {
        root_dir = g_file_new_for_path ("/");
    }

    return g_file_equal (dir, root_dir);
}


gboolean
caja_is_desktop_directory_file (GFile *dir,
                                const char *file)
{

    if (!desktop_dir_changed_callback_installed)
    {
        g_signal_connect_swapped (caja_preferences, "changed::" CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
                                  G_CALLBACK(desktop_dir_changed_callback),
                                  NULL);
        desktop_dir_changed_callback_installed = TRUE;
    }

    if (desktop_dir == NULL)
    {
        update_desktop_dir ();
    }

    return (g_file_equal (dir, desktop_dir_dir) &&
            strcmp (file, desktop_dir_filename) == 0);
}

gboolean
caja_is_desktop_directory (GFile *dir)
{

    if (!desktop_dir_changed_callback_installed)
    {
        g_signal_connect_swapped (caja_preferences, "changed::" CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR,
                                  G_CALLBACK(desktop_dir_changed_callback),
                                  NULL);
        desktop_dir_changed_callback_installed = TRUE;
    }

    if (desktop_dir == NULL)
    {
        update_desktop_dir ();
    }

    return g_file_equal (dir, desktop_dir);
}


/**
 * caja_get_gmc_desktop_directory:
 *
 * Get the path for the directory containing the legacy gmc desktop.
 *
 * Return value: the directory path.
 **/
char *
caja_get_gmc_desktop_directory (void)
{
    return g_build_filename (g_get_home_dir (), LEGACY_DESKTOP_DIRECTORY_NAME, NULL);
}

/**
 * caja_get_pixmap_directory
 *
 * Get the path for the directory containing Caja pixmaps.
 *
 * Return value: the directory path.
 **/
char *
caja_get_pixmap_directory (void)
{
    return g_strdup (DATADIR "/pixmaps/caja");
}

/* FIXME bugzilla.gnome.org 42423:
 * Callers just use this and dereference so we core dump if
 * pixmaps are missing. That is lame.
 */
char *
caja_pixmap_file (const char *partial_path)
{
    char *path;

    path = g_build_filename (DATADIR "/pixmaps/caja", partial_path, NULL);
    if (g_file_test (path, G_FILE_TEST_EXISTS))
    {
        return path;
    }
    else
    {
        char *tmp;
        tmp = caja_get_pixmap_directory ();
        g_debug ("Failed to locate \"%s\" in Caja pixmap path \"%s\". Incomplete installation?", partial_path, tmp);
        g_free (tmp);
    }
    g_free (path);
    return NULL;
}

char *
caja_get_data_file_path (const char *partial_path)
{
    char *path;
    char *user_directory;

    /* first try the user's home directory */
    user_directory = caja_get_user_directory ();
    path = g_build_filename (user_directory, partial_path, NULL);
    g_free (user_directory);
    if (g_file_test (path, G_FILE_TEST_EXISTS))
    {
        return path;
    }
    g_free (path);

    /* next try the shared directory */
    path = g_build_filename (CAJA_DATADIR, partial_path, NULL);
    if (g_file_test (path, G_FILE_TEST_EXISTS))
    {
        return path;
    }
    g_free (path);

    return NULL;
}

char *
caja_ensure_unique_file_name (const char *directory_uri,
                              const char *base_name,
                              const char *extension)
{
    GFileInfo *info;
    char *filename;
    GFile *dir, *child;
    int copy;
    char *res;

    dir = g_file_new_for_uri (directory_uri);

    info = g_file_query_info (dir, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, NULL);
    if (info == NULL)
    {
        g_object_unref (dir);
        return NULL;
    }
    g_object_unref (info);

    filename = g_strdup_printf ("%s%s",
                                base_name,
                                extension);
    child = g_file_get_child (dir, filename);
    g_free (filename);

    copy = 1;
    while ((info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, NULL)) != NULL)
    {
        g_object_unref (info);
        g_object_unref (child);

        filename = g_strdup_printf ("%s-%d%s",
                                    base_name,
                                    copy,
                                    extension);
        child = g_file_get_child (dir, filename);
        g_free (filename);

        copy++;
    }

    res = g_file_get_uri (child);
    g_object_unref (child);
    g_object_unref (dir);

    return res;
}

char *
caja_unique_temporary_file_name (void)
{
    const char *prefix = "/tmp/caja-temp-file";
    char *file_name;
    int fd;

    file_name = g_strdup_printf ("%sXXXXXX", prefix);

    fd = g_mkstemp (file_name);
    if (fd == -1)
    {
        g_free (file_name);
        file_name = NULL;
    }
    else
    {
        close (fd);
    }

    return file_name;
}

GFile *
caja_find_existing_uri_in_hierarchy (GFile *location)
{
    GFileInfo *info;
    GFile *tmp;

    g_assert (location != NULL);

    location = g_object_ref (location);
    while (location != NULL)
    {
        info = g_file_query_info (location,
                                  G_FILE_ATTRIBUTE_STANDARD_NAME,
                                  0, NULL, NULL);
        g_object_unref (info);
        if (info != NULL)
        {
            return location;
        }
        tmp = location;
        location = g_file_get_parent (location);
        g_object_unref (tmp);
    }

    return location;
}

/**
 * caja_find_file_insensitive
 *
 * Attempt to find a file case-insentively. If the path can be found, the
 * returned file maps directly to it. Otherwise, a file using the
 * originally-cased path is returned. This function performs might perform
 * I/O.
 *
 * Return value: a #GFile to a child specified by @name.
 **/
GFile *
caja_find_file_insensitive (GFile *parent, const gchar *name)
{
    gchar **split_path;
    gchar *component;
    GFile *file, *next;
    gint i;

    split_path = g_strsplit (name, G_DIR_SEPARATOR_S, -1);

    file = g_object_ref (parent);

    for (i = 0; (component = split_path[i]) != NULL; i++)
    {
        if (!(next = caja_find_file_insensitive_next (file,
                     component)))
        {
            /* File does not exist */
            g_object_unref (file);
            file = NULL;
            break;
        }
        g_object_unref (file);
        file = next;
    }
    g_strfreev (split_path);

    if (file)
    {
        return file;
    }
    return g_file_get_child (parent, name);
}

static GFile *
caja_find_file_insensitive_next (GFile *parent, const gchar *name)
{
    GFileEnumerator *children;
    GFileInfo *info;
    gboolean use_utf8, found;
    char *filename, *case_folded_name, *utf8_collation_key, *ascii_collation_key, *child_key;
    GFile *file;
    const char *child_name, *compare_key;

    /* First check the given version */
    file = g_file_get_child (parent, name);
    if (g_file_query_exists (file, NULL))
    {
        return file;
    }
    g_object_unref (file);

    ascii_collation_key = g_ascii_strdown (name, -1);
    use_utf8 = g_utf8_validate (name, -1, NULL);
    utf8_collation_key = NULL;
    if (use_utf8)
    {
        case_folded_name = g_utf8_casefold (name, -1);
        utf8_collation_key = g_utf8_collate_key (case_folded_name, -1);
        g_free (case_folded_name);
    }

    /* Enumerate and compare insensitive */
    filename = NULL;
    children = g_file_enumerate_children (parent,
                                          G_FILE_ATTRIBUTE_STANDARD_NAME,
                                          0, NULL, NULL);
    if (children != NULL)
    {
        while ((info = g_file_enumerator_next_file (children, NULL, NULL)))
        {
            child_name = g_file_info_get_name (info);

            if (use_utf8 && g_utf8_validate (child_name, -1, NULL))
            {
                gchar *case_folded;

                case_folded = g_utf8_casefold (child_name, -1);
                child_key = g_utf8_collate_key (case_folded, -1);
                g_free (case_folded);
                compare_key = utf8_collation_key;
            }
            else
            {
                child_key = g_ascii_strdown (child_name, -1);
                compare_key = ascii_collation_key;
            }

            found = strcmp (child_key, compare_key) == 0;
            g_free (child_key);
            if (found)
            {
                filename = g_strdup (child_name);
                break;
            }
        }
        g_file_enumerator_close (children, NULL, NULL);
        g_object_unref (children);
    }

    g_free (ascii_collation_key);
    g_free (utf8_collation_key);

    if (filename)
    {
        file = g_file_get_child (parent, filename);
        g_free (filename);
        return file;
    }

    return NULL;
}

gboolean
caja_is_engrampa_installed (void)
{
    static int installed = -1;

    if (installed < 0)
    {
        gchar *found = g_find_program_in_path ("engrampa");
        installed = found ? 1 : 0;
        g_free (found);
    }

    return installed > 0 ? TRUE : FALSE;
}

#define GSM_NAME  "org.mate.SessionManager"
#define GSM_PATH "/org/mate/SessionManager"
#define GSM_INTERFACE "org.mate.SessionManager"

/* The following values come from
 * http://www.gnome.org/~mccann/mate-session/docs/mate-session.html#org.mate.SessionManager.Inhibit
 */
#define INHIBIT_LOGOUT (1U)
#define INHIBIT_SUSPEND (4U)

static GDBusConnection *
get_dbus_connection (void)
{
    static GDBusConnection *conn = NULL;

    if (conn == NULL)
    {
        GError *error = NULL;

        conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);

        if (conn == NULL)
        {
            g_warning ("Could not connect to session bus: %s", error->message);
            g_error_free (error);
        }
    }

    return conn;
}

/**
 * caja_inhibit_power_manager:
 * @message: a human readable message for the reason why power management
 *       is being suspended.
 *
 * Inhibits the power manager from logging out or suspending the machine
 * (e.g. whenever Caja is doing file operations).
 *
 * Returns: an integer cookie, which must be passed to
 *    caja_uninhibit_power_manager() to resume
 *    normal power management.
 */
int
caja_inhibit_power_manager (const char *message)
{
    GDBusConnection *connection;
    GVariant *result;
    GError *error = NULL;
    guint cookie = 0;

    g_return_val_if_fail (message != NULL, -1);

    connection = get_dbus_connection ();

    if (connection == NULL)
    {
        return -1;
    }

    result = g_dbus_connection_call_sync (connection,
                                          GSM_NAME,
                                          GSM_PATH,
                                          GSM_INTERFACE,
                                          "Inhibit",
                                          g_variant_new ("(susu)",
                                                  "Caja",
                                                  (guint) 0,
                                                  message,
                                                  (guint) (INHIBIT_LOGOUT | INHIBIT_SUSPEND)),
                                          G_VARIANT_TYPE ("(u)"),
                                          G_DBUS_CALL_FLAGS_NO_AUTO_START,
                                          -1,
                                          NULL,
                                          &error);

    if (error != NULL)
    {
        g_warning ("Could not inhibit power management: %s", error->message);
        g_error_free (error);
        return -1;
    }

    g_variant_get (result, "(u)", &cookie);
    g_variant_unref (result);

    return (int) cookie;
}

/**
 * caja_uninhibit_power_manager:
 * @cookie: the cookie value returned by caja_inhibit_power_manager()
 *
 * Uninhibits power management. This function must be called after the task
 * which inhibited power management has finished, or the system will not
 * return to normal power management.
 */
void
caja_uninhibit_power_manager (gint cookie)
{
    GDBusConnection *connection;
    GVariant *result;
    GError *error = NULL;

    g_return_if_fail (cookie > 0);

    connection = get_dbus_connection ();

    if (connection == NULL)
    {
        return;
    }

    result = g_dbus_connection_call_sync (connection,
                                          GSM_NAME,
                                          GSM_PATH,
                                          GSM_INTERFACE,
                                          "Uninhibit",
                                          g_variant_new ("(u)", (guint) cookie),
                                          NULL,
                                          G_DBUS_CALL_FLAGS_NO_AUTO_START,
                                          -1,
                                          NULL,
                                          &error);

    if (result == NULL)
    {
        g_warning ("Could not uninhibit power management: %s", error->message);
        g_error_free (error);
        return;
    }

    g_variant_unref (result);
}

/* Returns TRUE if the file is in XDG_DATA_DIRS or
   in "~/.mate2/". This is used for deciding
   if a desktop file is "trusted" based on the path */
gboolean
caja_is_in_system_dir (GFile *file)
{
    const char * const * data_dirs;
    char *path, *mate2;
    int i;
    gboolean res;

    if (!g_file_is_native (file))
    {
        return FALSE;
    }

    path = g_file_get_path (file);

    res = FALSE;

    data_dirs = g_get_system_data_dirs ();
    for (i = 0; path != NULL && data_dirs[i] != NULL; i++)
    {
        if (g_str_has_prefix (path, data_dirs[i]))
        {
            res = TRUE;
            break;
        }

    }

    if (!res)
    {
        /* Panel desktop files are here, trust them */
        mate2 = g_build_filename(g_get_home_dir(), ".mate2", NULL);

        if (g_str_has_prefix (path, mate2))
        {
            res = TRUE;
        }

        g_free(mate2);
    }
    g_free (path);

    return res;
}

GHashTable *
caja_trashed_files_get_original_directories (GList *files,
        GList **unhandled_files)
{
    GHashTable *directories;
    CajaFile *file, *original_file, *original_dir;
    GList *l, *m;

    directories = NULL;

    if (unhandled_files != NULL)
    {
        *unhandled_files = NULL;
    }

    for (l = files; l != NULL; l = l->next)
    {
        file = CAJA_FILE (l->data);
        original_file = caja_file_get_trash_original_file (file);

        original_dir = NULL;
        if (original_file != NULL)
        {
            original_dir = caja_file_get_parent (original_file);
        }

        if (original_dir != NULL)
        {
            if (directories == NULL)
            {
                directories = g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                                     (GDestroyNotify) caja_file_unref,
                                                     (GDestroyNotify) caja_file_list_unref);
            }
            caja_file_ref (original_dir);
            m = g_hash_table_lookup (directories, original_dir);
            if (m != NULL)
            {
                g_hash_table_steal (directories, original_dir);
                caja_file_unref (original_dir);
            }
            m = g_list_append (m, caja_file_ref (file));
            g_hash_table_insert (directories, original_dir, m);
        }
        else if (unhandled_files != NULL)
        {
            *unhandled_files = g_list_append (*unhandled_files, caja_file_ref (file));
        }

        if (original_file != NULL)
        {
            caja_file_unref (original_file);
        }

        if (original_dir != NULL)
        {
            caja_file_unref (original_dir);
        }
    }

    return directories;
}

static GList *
locations_from_file_list (GList *file_list)
{
    CajaFile *file;
    GList *l, *ret;

    ret = NULL;

    for (l = file_list; l != NULL; l = l->next)
    {
        file = CAJA_FILE (l->data);
        ret = g_list_prepend (ret, caja_file_get_location (file));
    }

    return g_list_reverse (ret);
}

void
caja_restore_files_from_trash (GList *files,
                               GtkWindow *parent_window)
{
    CajaFile *file, *original_dir;
    GHashTable *original_dirs_hash;
    GList *original_dirs, *unhandled_files;
    GFile *original_dir_location;
    GList *locations, *l;
    char *message, *file_name;

    original_dirs_hash = caja_trashed_files_get_original_directories (files, &unhandled_files);

    for (l = unhandled_files; l != NULL; l = l->next)
    {
        file = CAJA_FILE (l->data);
        file_name = caja_file_get_display_name (file);
        message = g_strdup_printf (_("Could not determine original location of \"%s\" "), file_name);
        g_free (file_name);

        eel_show_warning_dialog (message,
                                 _("The item cannot be restored from trash"),
                                 parent_window);
        g_free (message);
    }

    if (original_dirs_hash != NULL)
    {
        original_dirs = g_hash_table_get_keys (original_dirs_hash);
        for (l = original_dirs; l != NULL; l = l->next)
        {
            original_dir = CAJA_FILE (l->data);
            original_dir_location = caja_file_get_location (original_dir);

            files = g_hash_table_lookup (original_dirs_hash, original_dir);
            locations = locations_from_file_list (files);

            caja_file_operations_move
            (locations, NULL,
             original_dir_location,
             parent_window,
             NULL, NULL);

            g_list_free_full (locations, g_object_unref);
            g_object_unref (original_dir_location);
        }

        g_list_free (original_dirs);
        g_hash_table_destroy (original_dirs_hash);
    }

    caja_file_list_unref (unhandled_files);
}

#if !defined (CAJA_OMIT_SELF_CHECK)

void
caja_self_check_file_utilities (void)
{
}

#endif /* !CAJA_OMIT_SELF_CHECK */