/**
 * @file gnome-cmd-dir.cc
 * @copyright (C) 2001-2006 Marcus Bjurman\n
 * @copyright (C) 2007-2012 Piotr Eljasiak\n
 * @copyright (C) 2013-2023 Uwe Scholz\n
 *
 * @copyright 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.
 *
 * @copyright 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.
 *
 * @copyright 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>

#include "gnome-cmd-includes.h"
#include "gnome-cmd-dir.h"
#include "gnome-cmd-main-win.h"
#ifdef HAVE_SAMBA
#include "gnome-cmd-con-smb.h"
#endif
#include "gnome-cmd-data.h"
#include "gnome-cmd-con.h"
#include "gnome-cmd-file-collection.h"
#include "dirlist.h"
#include "utils.h"

using namespace std;


#define DIR_PBAR_MAX 50

int created_dirs_cnt = 0;
int deleted_dirs_cnt = 0;

GList *all_dirs = nullptr;

enum
{
    FILE_CREATED,
    FILE_DELETED,
    FILE_CHANGED,
    FILE_RENAMED,
    LIST_OK,
    LIST_FAILED,
    LAST_SIGNAL
};

struct GnomeCmdDirPrivate
{
    gint ref_cnt;
    GList *files;
    GnomeCmdFileCollection *file_collection;
    GnomeCmdCon *con;
    GnomeCmdPath *path;

    gboolean lock;
    gboolean needs_mtime_update;

    Handle *handle;
    GFileMonitor *gFileMonitor;
    gint monitor_users;
};


G_DEFINE_TYPE (GnomeCmdDir, gnome_cmd_dir, GNOME_CMD_TYPE_FILE)


static guint signals[LAST_SIGNAL] = { 0 };


static void monitor_callback (GFileMonitor *gFileMonitor, GFile *gFile, GFile *otherGFile,
                              GFileMonitorEvent event_type, GnomeCmdDir *dir)
{
    auto fileUri = g_file_get_uri(gFile);

    switch (event_type)
    {
        case G_FILE_MONITOR_EVENT_CHANGED:
        case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
            DEBUG('n', "G_FILE_MONITOR_EVENT_CHANGED for %s\n", fileUri);
            gnome_cmd_dir_file_changed (dir, fileUri);
            break;
        case G_FILE_MONITOR_EVENT_DELETED:
            DEBUG('n', "G_FILE_MONITOR_EVENT_DELETED for %s\n", fileUri);
            gnome_cmd_dir_file_deleted (dir, fileUri);
            break;
        case G_FILE_MONITOR_EVENT_CREATED:
            DEBUG('n', "G_FILE_MONITOR_EVENT_CREATED for %s\n", fileUri);
            gnome_cmd_dir_file_created (dir, fileUri);
            break;
        case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
        case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
        case G_FILE_MONITOR_EVENT_UNMOUNTED:
        case G_FILE_MONITOR_EVENT_MOVED:
        case G_FILE_MONITOR_EVENT_MOVED_IN:
        case G_FILE_MONITOR_EVENT_MOVED_OUT:
        case G_FILE_MONITOR_EVENT_RENAMED:
            break;

        default:
            DEBUG('n', "Unknown monitor event %d\n", event_type);
    }
    g_free(fileUri);
}


static void gnome_cmd_dir_init (GnomeCmdDir *dir)
{
    // dir->voffset = 0;
    // dir->dialog = nullptr;
    dir->state = GnomeCmdDir::STATE_EMPTY;

    dir->priv = g_new0 (GnomeCmdDirPrivate, 1);

    dir->priv->handle = handle_new (dir);
    // dir->priv->gFileMonitor = nullptr;
    // dir->priv->monitor_users = 0;
    // dir->priv->files = nullptr;
    dir->priv->file_collection = new GnomeCmdFileCollection;

    if (DEBUG_ENABLED ('c'))
    {
        created_dirs_cnt++;
        all_dirs = g_list_append (all_dirs, dir);
    }
}


static void gnome_cmd_dir_finalize (GObject *object)
{
    GnomeCmdDir *dir = GNOME_CMD_DIR (object);

    DEBUG ('d', "dir destroying %p %s\n", dir, dir->priv->path->get_path());

    gnome_cmd_con_remove_from_cache (dir->priv->con, dir);

    delete dir->priv->file_collection;
    delete dir->priv->path;

    dir->priv->handle->ref = nullptr;
    handle_unref (dir->priv->handle);

    g_free (dir->priv);

    if (DEBUG_ENABLED ('c'))
    {
        all_dirs = g_list_remove (all_dirs, dir);
        deleted_dirs_cnt++;
    }

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


static void gnome_cmd_dir_class_init (GnomeCmdDirClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    signals[FILE_CREATED] =
        g_signal_new ("file-created",
            G_TYPE_FROM_CLASS (klass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (GnomeCmdDirClass, file_created),
            nullptr, nullptr,
            g_cclosure_marshal_VOID__POINTER,
            G_TYPE_NONE,
            1, G_TYPE_POINTER);

    signals[FILE_DELETED] =
        g_signal_new ("file-deleted",
            G_TYPE_FROM_CLASS (klass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (GnomeCmdDirClass, file_deleted),
            nullptr, nullptr,
            g_cclosure_marshal_VOID__POINTER,
            G_TYPE_NONE,
            1, G_TYPE_POINTER);

    signals[FILE_CHANGED] =
        g_signal_new ("file-changed",
            G_TYPE_FROM_CLASS (klass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (GnomeCmdDirClass, file_changed),
            nullptr, nullptr,
            g_cclosure_marshal_VOID__POINTER,
            G_TYPE_NONE,
            1, G_TYPE_POINTER);

    signals[FILE_RENAMED] =
        g_signal_new ("file-renamed",
            G_TYPE_FROM_CLASS (klass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (GnomeCmdDirClass, file_renamed),
            nullptr, nullptr,
            g_cclosure_marshal_VOID__POINTER,
            G_TYPE_NONE,
            1, G_TYPE_POINTER);

    signals[LIST_OK] =
        g_signal_new ("list-ok",
            G_TYPE_FROM_CLASS (klass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (GnomeCmdDirClass, list_ok),
            nullptr, nullptr,
            g_cclosure_marshal_VOID__POINTER,
            G_TYPE_NONE,
            1, G_TYPE_POINTER);

    signals[LIST_FAILED] =
        g_signal_new ("list-failed",
            G_TYPE_FROM_CLASS (klass),
            G_SIGNAL_RUN_LAST,
            G_STRUCT_OFFSET (GnomeCmdDirClass, list_failed),
            nullptr, nullptr,
            g_cclosure_marshal_VOID__INT,
            G_TYPE_NONE,
            1, G_TYPE_POINTER);

    object_class->finalize = gnome_cmd_dir_finalize;
    klass->file_created = nullptr;
    klass->file_deleted = nullptr;
    klass->file_changed = nullptr;
    klass->file_renamed = nullptr;
    klass->list_ok = nullptr;
    klass->list_failed = nullptr;
}


/***********************************
 * Public functions
 ***********************************/

GnomeCmdDir *gnome_cmd_dir_new_from_gfileinfo (GFileInfo *gFileInfo, GnomeCmdDir *gnomeCmdDirParent)
{
    g_return_val_if_fail (gFileInfo != nullptr, nullptr);
    g_return_val_if_fail (GNOME_CMD_IS_DIR (gnomeCmdDirParent), nullptr);

    GnomeCmdCon *con = gnome_cmd_dir_get_connection (gnomeCmdDirParent);
    auto dirName = g_file_info_get_name(gFileInfo);

    GFile *gFile = nullptr;
    GnomeCmdPath *dirPath = nullptr;

    gFile = gnome_cmd_dir_get_gfile_for_con_and_filename(gnomeCmdDirParent, dirName);
    if (gFile)
    {
        auto uriStringTmp = g_file_get_uri(gFile);
        auto uriTmp = g_uri_parse(uriStringTmp, G_URI_FLAGS_NONE, nullptr);
        auto pathTmp = g_uri_get_path(uriTmp);
        dirPath = gnome_cmd_con_create_path(con, pathTmp);
        g_free(uriStringTmp);
    }
    else
    {
        dirPath = gnome_cmd_dir_get_path (gnomeCmdDirParent)->get_child(dirName);
        if (!dirPath)
        {
            return nullptr;
        }

        gFile = gnome_cmd_con_create_gfile (con, dirPath);
    }
    auto uriString = g_file_get_uri (gFile);

    auto *gnomeCmdDir = gnome_cmd_con_cache_lookup (gnome_cmd_dir_get_connection (gnomeCmdDirParent), uriString);

    if (gnomeCmdDir)
    {
        delete dirPath;
        reinterpret_cast<GnomeCmdFile*>((gnomeCmdDir))->update_gFileInfo(gFileInfo);
        g_free (uriString);
        return gnomeCmdDir;
    }

    gnomeCmdDir = static_cast<GnomeCmdDir*> (g_object_new (GNOME_CMD_TYPE_DIR, nullptr));
    GError *error = nullptr;
    if(!gnome_cmd_file_setup(G_OBJECT (gnomeCmdDir), gFile, &error))
    {
        g_warning("gnome_cmd_dir_new_from_gfileinfo error on %s: %s", uriString, error->message);
        g_error_free(error);
        g_free (uriString);
        g_object_unref(gnomeCmdDir);
        return nullptr;
    }
    gnomeCmdDir->priv->con = con;
    gnome_cmd_dir_set_path (gnomeCmdDir, dirPath);
    gnomeCmdDir->priv->needs_mtime_update = FALSE;

    gnome_cmd_con_add_to_cache (gnome_cmd_dir_get_connection (gnomeCmdDirParent), gnomeCmdDir, uriString);

    return gnomeCmdDir;
}


GnomeCmdDir *gnome_cmd_dir_new_with_con (GnomeCmdCon *con)
{
    g_return_val_if_fail (GNOME_CMD_IS_CON (con), nullptr);
    g_return_val_if_fail (con->base_gFileInfo != nullptr, nullptr);

    auto gFile = con->is_local
        ? gnome_cmd_con_create_gfile (con, con->base_path)
        : gnome_cmd_con_create_gfile (con, nullptr);

    auto uriString = g_file_get_uri(gFile);
    GnomeCmdDir *dir = gnome_cmd_con_cache_lookup (con, uriString);

    if (dir)
    {
        GNOME_CMD_FILE (dir)->update_gFileInfo(con->base_gFileInfo);
        g_object_unref (gFile);
        g_free (uriString);
        return dir;
    }

    dir = static_cast<GnomeCmdDir*> (g_object_new (GNOME_CMD_TYPE_DIR, nullptr));
    gnome_cmd_dir_set_path (dir, con->base_path->clone());
    dir->priv->con = con;
    GError *error = nullptr;
    if (!gnome_cmd_file_setup (G_OBJECT(dir), gFile, &error))
    {
        g_warning("gnome_cmd_dir_new_with_con error on %s: %s", uriString, error->message);
        g_warning("%s", error->message);
        g_error_free(error);
        g_free (uriString);
        g_object_unref(dir);
        return nullptr;
    }
    dir->priv->needs_mtime_update = FALSE;
    gnome_cmd_con_add_to_cache (con, dir, uriString);
    return dir;
}


GnomeCmdDir *gnome_cmd_dir_new (GnomeCmdCon *con, GnomeCmdPath *path, gboolean isStartup)
{
    g_return_val_if_fail (GNOME_CMD_IS_CON (con), nullptr);
    g_return_val_if_fail (path!=nullptr, nullptr);
    GError *error = nullptr;

    auto gFile = gnome_cmd_con_create_gfile (con, path);
    if (!gFile)
    {
        return nullptr;
    }

    auto uriString = g_file_get_uri (gFile);

    auto gnomeCmdDir = gnome_cmd_con_cache_lookup (con, uriString);
    if (gnomeCmdDir) // GnomeCmdDir instance is set up already
    {
        g_free (uriString);
        g_object_unref (gFile);
        return gnomeCmdDir;
    }

    gnomeCmdDir = static_cast<GnomeCmdDir*> (g_object_new (GNOME_CMD_TYPE_DIR, nullptr));
    if (!gnome_cmd_file_setup (G_OBJECT (gnomeCmdDir), gFile, &error))
    {
        if (error && !isStartup)
        {
            gnome_cmd_show_message (*main_win, uriString, error->message);
            g_error_free(error);
        }
        g_object_unref(gFile);
        g_free (uriString);
        return nullptr;
    }
    gnome_cmd_dir_set_path (gnomeCmdDir, path);
    gnomeCmdDir->priv->con = con;
    gnomeCmdDir->priv->needs_mtime_update = FALSE;
    gnome_cmd_con_add_to_cache (con, gnomeCmdDir, uriString);

    return gnomeCmdDir;
}


GnomeCmdDir *gnome_cmd_dir_get_parent (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    GnomeCmdPath *path = dir->priv->path->get_parent();

    return path ? gnome_cmd_dir_new (dir->priv->con, path) : nullptr;
}


GnomeCmdDir *gnome_cmd_dir_get_child (GnomeCmdDir *dir, const gchar *child)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    GnomeCmdPath *path = dir->priv->path->get_child(child);

    return path ? gnome_cmd_dir_new (dir->priv->con, path) : nullptr;
}


GnomeCmdCon *gnome_cmd_dir_get_connection (GnomeCmdDir *dir)
{
    if (!GNOME_CMD_IS_DIR (dir))
    {
        return nullptr;
    }

    return dir->priv->con;
}


Handle *gnome_cmd_dir_get_handle (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    return dir->priv->handle;
}


void gnome_cmd_dir_unref (GnomeCmdDir *dir)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));
    GNOME_CMD_FILE (dir)->unref();
}

GList *gnome_cmd_dir_get_files (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    return dir->priv->files;
}


static GList *create_gnome_cmd_file_list_from_gfileinfo_list (GnomeCmdDir *dir, GList *info_list)
{
    GList *file_list = nullptr;

    // create a new list with GnomeCmdFile objects
    for (GList *i = info_list; i; i = i->next)
    {
        auto gFileInfo = (GFileInfo *) i->data;

        if (gFileInfo == nullptr || !G_IS_FILE_INFO (gFileInfo))
        {
            continue;
        }

        auto basename = g_file_info_get_attribute_string (gFileInfo, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
        if (strcmp (basename, ".") == 0 || strcmp (basename, "..") == 0)
        {
            continue;
        }

        GnomeCmdFile *f = g_file_info_get_attribute_uint32 (gFileInfo, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY
            ? GNOME_CMD_FILE (gnome_cmd_dir_new_from_gfileinfo (gFileInfo, dir))
            : gnome_cmd_file_new (gFileInfo, dir);

        if (f)
        {
            gnome_cmd_file_ref (f);
            file_list = g_list_append (file_list, f);
        }
    }
    return file_list;
}


static void on_list_done (GnomeCmdDir *dir, GList *infolist, GError *error)
{
    if (dir->state == GnomeCmdDir::STATE_LISTED)
    {
        DEBUG('l', "File listing succeded\n");

        if (!dir->priv->file_collection->empty())
            dir->priv->file_collection->clear();

        dir->priv->files = create_gnome_cmd_file_list_from_gfileinfo_list (dir, infolist);
        dir->priv->file_collection->add(dir->priv->files);
        g_list_free (infolist);
        infolist = nullptr;

        if (dir->dialog)
        {
            gtk_widget_destroy (dir->dialog);
            dir->dialog = nullptr;
        }
        dir->priv->lock = FALSE;

        DEBUG('l', "Emitting 'list-ok' signal\n");
        g_signal_emit (dir, signals[LIST_OK], 0, dir->priv->files);
    }
    else if (dir->state == GnomeCmdDir::STATE_EMPTY)
    {
        DEBUG('l', "File listing failed: %s\n", error->message);

        if (dir->dialog)
        {
            gtk_widget_destroy (dir->dialog);
            dir->dialog = nullptr;
        }

        dir->priv->lock = FALSE;

        DEBUG('l', "Emitting 'list-failed' signal\n");
        g_signal_emit (dir, signals[LIST_FAILED], 0, dir->error);
    }
}


static void on_dir_list_cancel (GtkButton *btn, GnomeCmdDir *dir)
{
    if (dir->state == GnomeCmdDir::STATE_LISTING)
    {
        DEBUG('l', "on_dir_list_cancel\n");
        dirlist_cancel (dir);

        gtk_widget_destroy (dir->dialog);
        dir->dialog = nullptr;
    }
}


static void create_list_progress_dialog (GnomeCmdDir *dir)
{
    dir->dialog = gnome_cmd_dialog_new (nullptr);
    g_object_ref (dir->dialog);

    gnome_cmd_dialog_add_button (
        GNOME_CMD_DIALOG (dir->dialog),
        GTK_STOCK_CANCEL,
        GTK_SIGNAL_FUNC (on_dir_list_cancel), dir);

    GtkWidget *vbox = create_vbox (dir->dialog, FALSE, 0);

    dir->label = create_label (dir->dialog, _("Waiting for file list"));

    dir->pbar = create_progress_bar (dir->dialog);
    gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR (dir->pbar), FALSE);
    gtk_progress_bar_set_pulse_step (GTK_PROGRESS_BAR (dir->pbar), 1.0 / (gdouble) DIR_PBAR_MAX);

    gtk_box_pack_start (GTK_BOX (vbox), dir->label, TRUE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (vbox), dir->pbar, FALSE, TRUE, 0);

    gnome_cmd_dialog_add_category (GNOME_CMD_DIALOG (dir->dialog), vbox);
    gtk_window_set_transient_for (GTK_WINDOW (dir->dialog), *main_win);

    gtk_widget_show_all (dir->dialog);
}


void gnome_cmd_dir_relist_files (GnomeCmdDir *dir, gboolean visualProgress)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    if (dir->priv->lock) return;
    dir->priv->lock = TRUE;

    dir->done_func = (DirListDoneFunc) on_list_done;

    if (visualProgress)
        create_list_progress_dialog (dir);

    dirlist_list (dir, visualProgress);
}


void gnome_cmd_dir_list_files (GnomeCmdDir *dir, gboolean visualProgress)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    if (!dir->priv->files || gnome_cmd_dir_is_local (dir))
    {
        auto gUriString = g_file_get_uri(GNOME_CMD_FILE (dir)->gFile);
        DEBUG ('l', "relisting files for 0x%x; %s\n", dir, gUriString);
        gnome_cmd_dir_relist_files (dir, visualProgress);
        g_free(gUriString);
    }
    else
        g_signal_emit (dir, signals[LIST_OK], 0, dir->priv->files);
}


GnomeCmdPath *gnome_cmd_dir_get_path (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    return dir->priv->path;
}


void gnome_cmd_dir_set_path (GnomeCmdDir *dir, GnomeCmdPath *path)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    delete dir->priv->path;

    dir->priv->path = path;
}


void gnome_cmd_dir_update_path (GnomeCmdDir *dir)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    GnomeCmdDir *parent = gnome_cmd_dir_get_parent (dir);
    if (!parent)
        return;

    GnomeCmdPath *path = gnome_cmd_dir_get_path (parent)->get_child(GNOME_CMD_FILE (dir)->get_name());
    if (path)
        gnome_cmd_dir_set_path (dir, path);
}


gchar *gnome_cmd_dir_get_display_path (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    return g_strdup (dir->priv->path->get_display_path());
}


GFile *gnome_cmd_dir_get_gfile (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    return GNOME_CMD_FILE(dir)->gFile;
}


gchar *gnome_cmd_dir_get_uri_str (GnomeCmdDir *dir, gboolean withTrailingSlash)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    auto gFile = gnome_cmd_dir_get_gfile (dir);

    g_return_val_if_fail(G_IS_FILE(gFile), nullptr);

    auto dirString = g_file_get_uri (gFile);

    if (withTrailingSlash)
    {
        auto tmpString = dirString[strlen(dirString)-1] == G_DIR_SEPARATOR
            ? g_strdup(dirString)
            : g_strdup_printf("%s%s", dirString, G_DIR_SEPARATOR_S);
        g_free(dirString);
        dirString = tmpString;
    }

    return dirString;
}


/**
 * @brief Get the relative directory path string for the given base path
 *
 * For example, let childPath be "/tmp/abc" and basePath is the path "/tmp/"
 * (from the original URI smb://localhost/tmp/). Then the return would be
 * the the address pointing to "/" (which is the second slash from "/tmp/").
 * If childPath is pointing to "/tmp/abc" and the basePath points
 * to "/xyz", then the return points to "/" because /xzy is relative
 * to the /tmp on the same directory level.
 */
gchar *gnome_cmd_dir_get_relative_path_string(const char* childPath, const char* basePath)
{
    if (!childPath || *childPath == '\000')
        return g_strdup(basePath);

    if (!strcmp(basePath, G_DIR_SEPARATOR_S))
        return g_strdup(childPath);

    char *relPath = nullptr;

    if (strstr(childPath, basePath) == childPath)
    {
        auto stringTmp = strchr((childPath + 1), G_DIR_SEPARATOR);
        relPath = g_strdup(stringTmp != nullptr ? stringTmp : childPath);
    }
    return relPath ? relPath : g_strdup(G_DIR_SEPARATOR_S);
}

static gchar *gnome_cmd_dir_get_mount_uri(GnomeCmdCon *con)
{
    auto conUri = gnome_cmd_con_get_uri(con);
    auto gFileConTmp = g_file_new_for_uri(conUri);
    do
    {
        auto gFileConParent = g_file_get_parent(gFileConTmp);
        if (!gFileConParent) break;
        g_object_unref(gFileConTmp);
        gFileConTmp = gFileConParent;
    }
    while (g_file_find_enclosing_mount(gFileConTmp, nullptr, nullptr));
    auto conUriBase = g_file_get_uri(gFileConTmp);
    g_object_unref(gFileConTmp);
    return conUriBase;
}

/**
 * This function returns a GFile object which is the result of the URI construction of
 * two URI's and a path: the connection URI, which is the private member of GnomeCmdDir,
 * the directory path inside of that connection, and the given filename string.
 */
GFile *gnome_cmd_dir_get_gfile_for_con_and_filename(GnomeCmdDir *dir, const gchar *filename)
{
    auto conUri = gnome_cmd_con_get_uri(dir->priv->con);
    if (!conUri) // is usually set for a remote connection
    {
        return nullptr;
    }

    // Get the Uri for the mount which belongs to the GnomeCmdCon object
    auto mountUri = gnome_cmd_dir_get_mount_uri(dir->priv->con);

    // Always let the conection URI end with '/' because the last entry should be a directory
    auto conLastCharacter = mountUri[strlen(mountUri)-1];
    auto mountUriTmp = conLastCharacter == G_DIR_SEPARATOR
        ? g_strdup(mountUri)
        : g_strdup_printf("%s%s", mountUri, G_DIR_SEPARATOR_S);
    g_free(mountUri);

    // Create the merged URI out of the connection URI, the directory path and the filename
    auto gnomeCmdDirPathString = gnome_cmd_dir_get_path(dir)->get_path();

    auto gUriForMount = g_uri_parse(mountUriTmp, G_URI_FLAGS_NONE, nullptr);
    auto conUriPath = g_uri_get_path(gUriForMount);
    auto relDirToUriPath = gnome_cmd_dir_get_relative_path_string(gnomeCmdDirPathString, conUriPath);
    auto mergedDirAndFileNameString = g_build_filename(".", relDirToUriPath, filename, nullptr);
    g_free(relDirToUriPath);

    GError *error = nullptr;
    auto fullFileNameUri = g_uri_resolve_relative (mountUriTmp, mergedDirAndFileNameString, G_URI_FLAGS_NONE, &error);
    if (!fullFileNameUri || error)
    {
        g_warning ("gnome_cmd_dir_get_uri_str error: %s", error ? error->message : "Could not resolve relative URI");
        g_free(mountUriTmp);
        g_free(fullFileNameUri);
        g_free(mergedDirAndFileNameString);
        g_error_free(error);
        return nullptr;
    }

    auto gFile = g_file_new_for_uri(fullFileNameUri);
    g_free(mountUriTmp);
    g_free(fullFileNameUri);
    g_free(mergedDirAndFileNameString);
    return gFile;
}


GFile *gnome_cmd_dir_get_child_gfile (GnomeCmdDir *dir, const gchar *filename)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

    GFile *gFile = gnome_cmd_dir_get_gfile_for_con_and_filename(dir, filename);

    if (gFile)
    {
        return gFile;
    }

    GnomeCmdPath *path = dir->priv->path->get_child(filename);
    if (!path)
    {
        return nullptr;
    }

    gFile = gnome_cmd_con_create_gfile (dir->priv->con, path);
    delete path;

    return gFile;
}


GFile *gnome_cmd_dir_get_absolute_path_gfile (GnomeCmdDir *dir, string absolute_filename)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), nullptr);

#ifdef HAVE_SAMBA
    // include workgroups and shares for smb uris
    GFile *dir_gFile = gnome_cmd_dir_get_gfile (dir);

    auto uriScheme = g_file_get_uri_scheme (dir_gFile);

    if (uriScheme && strcmp (uriScheme, "smb") == 0)
    {
        g_object_ref (dir_gFile);

        while (get_gfile_attribute_uint32(dir_gFile, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
        {
            auto gFileParent = g_file_get_parent(dir_gFile);
            g_object_unref (dir_gFile);
            dir_gFile = gFileParent;
        }

        auto server_and_share = g_file_get_uri(dir_gFile);
        stringify (absolute_filename, g_build_filename (server_and_share, absolute_filename.c_str(), nullptr));
        g_free (server_and_share);

        g_object_unref (dir_gFile);
    }

    g_free(uriScheme);
#endif

    GnomeCmdPath *path = gnome_cmd_con_create_path (dir->priv->con, absolute_filename.c_str());
    auto gFile = gnome_cmd_con_create_gfile (dir->priv->con, path);

    delete path;

    return gFile;
}


inline gboolean file_already_exists (GnomeCmdDir *dir, const gchar *uri_str)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), TRUE);
    g_return_val_if_fail (uri_str != nullptr, TRUE);

    return dir->priv->file_collection->find(uri_str) != nullptr;
}


// A file has been created. Create a new GnomeCmdFile object for that file and assign it to dir
void gnome_cmd_dir_file_created (GnomeCmdDir *dir, const gchar *newDirUriStr)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));
    g_return_if_fail (newDirUriStr != nullptr);
    GError *error = nullptr;

    if (file_already_exists (dir, newDirUriStr))
        return;

    auto gFile = g_file_new_for_uri (newDirUriStr);
    auto gFileInfo = g_file_query_info (gFile, "*", G_FILE_QUERY_INFO_NONE, nullptr, &error);
    if (error)
    {
        DEBUG ('t', "Could not retrieve file information for %s, error: %s\n", newDirUriStr, error->message);
        g_error_free(error);
    }

    GnomeCmdFile *f;

    if (g_file_info_get_attribute_uint32 (gFileInfo, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY)
        f = reinterpret_cast<GnomeCmdFile*> (gnome_cmd_dir_new_from_gfileinfo (gFileInfo, dir));
    else
        f = gnome_cmd_file_new (gFileInfo, dir);

    if (!f)
    {
        return;
    }

    dir->priv->file_collection->add(f);
    dir->priv->files = dir->priv->file_collection->get_list();

    dir->priv->needs_mtime_update = TRUE;

    g_signal_emit (dir, signals[FILE_CREATED], 0, f);
}


/**
 * This function will search in the file tree to retrieve the first
 * physically existing parent dir for a given dir.
 */
GnomeCmdDir *gnome_cmd_dir_get_existing_parent(GnomeCmdDir *dir)
{
    auto parentDir = gnome_cmd_dir_get_parent(dir);
    gchar *parentDirUri = nullptr;
    GFile *gFileParent = nullptr;

    GnomeCmdDir *returnDir = nullptr;
    do
    {
        parentDirUri = gnome_cmd_dir_get_uri_str(parentDir);
        gFileParent = g_file_new_for_uri(parentDirUri);
        g_free(parentDirUri);
        if (g_file_query_exists(gFileParent, nullptr))
        {
            returnDir = parentDir;
        }
        else
        {
            gnome_cmd_con_remove_from_cache (dir->priv->con, parentDir);
            parentDir = gnome_cmd_dir_get_parent(parentDir);
        }
        g_object_unref(gFileParent);
    } while (!returnDir);

    return returnDir;
}


/**
 * When a directory is deleted, this method will check if we are currently displaying this directory
 * in one of the file panes. If yes, we will update the file pane in question and step up
 * to a directory which is existing.
 */
void gnome_cmd_dir_update_file_selector(GnomeCmdDir *dir, const gchar *deletedDirUriString)
{
    auto dirUriSting = gnome_cmd_dir_get_uri_str(dir);
    if (!g_strcmp0(deletedDirUriString, dirUriSting))
    {
        gnome_cmd_con_remove_from_cache (dir->priv->con, dir);

        auto parentDir = gnome_cmd_dir_get_existing_parent(dir);

        // Go to the parent directory in one of the file lists if necessary
        auto parentDirPath = gnome_cmd_dir_get_path(parentDir)->get_path();

        //ToDo: The following has to be done for the other open tabs, too.

        auto fs = main_win->fs(ACTIVE);
        auto fsDir = fs->get_directory();
        auto urifsDir = gnome_cmd_dir_get_uri_str(fsDir);
        if (!g_strcmp0(urifsDir, deletedDirUriString))
        {
            fs->goto_directory(parentDirPath);
        }
        g_free(urifsDir);

        fs = main_win->fs(INACTIVE);
        fsDir = fs->get_directory();
        urifsDir = gnome_cmd_dir_get_uri_str(fsDir);
        if (!g_strcmp0(urifsDir, deletedDirUriString))
        {
            fs->goto_directory(parentDirPath);
        }
        g_free(urifsDir);
    }
    g_free(dirUriSting);
}


// A file has been deleted. Remove the corresponding GnomeCmdFile
void gnome_cmd_dir_file_deleted (GnomeCmdDir *dir, const gchar *deletedDirUriString)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));
    g_return_if_fail (deletedDirUriString != nullptr);

    gnome_cmd_dir_update_file_selector(dir, deletedDirUriString);

    GnomeCmdFile *f = dir->priv->file_collection->find(deletedDirUriString);

    if (!GNOME_CMD_IS_FILE (f))
        return;

    dir->priv->needs_mtime_update = TRUE;

    g_signal_emit (dir, signals[FILE_DELETED], 0, f);

    dir->priv->file_collection->remove(deletedDirUriString);
    dir->priv->files = dir->priv->file_collection->get_list();
}


// A file has been changed. Find the corresponding GnomeCmdFile, update its GFileInfo
void gnome_cmd_dir_file_changed (GnomeCmdDir *dir, const gchar *uri_str)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));
    g_return_if_fail (uri_str != nullptr);

    GnomeCmdFile *f = dir->priv->file_collection->find(uri_str);

    if (f == nullptr)
    {
        return;
    }

    dir->priv->needs_mtime_update = TRUE;

    auto gFileInfo = g_file_query_info(f->gFile, "*", G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
    if (gFileInfo == nullptr)
    {
        auto basename = g_file_get_basename(f->gFile);
        DEBUG ('t', "Could not retrieve file information for changed file %s\n", g_file_get_basename(f->gFile));
        g_free(basename);
        return;
    }

    f->update_gFileInfo(gFileInfo);
    f->invalidate_metadata();
    g_signal_emit (dir, signals[FILE_CHANGED], 0, f);
}


void gnome_cmd_dir_file_renamed (GnomeCmdDir *dir, GnomeCmdFile *f, const gchar *old_uri_str)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));
    g_return_if_fail (GNOME_CMD_IS_FILE (f));
    g_return_if_fail (old_uri_str != nullptr);

    if (GNOME_CMD_IS_DIR (f))
        gnome_cmd_con_remove_from_cache (dir->priv->con, old_uri_str);

    dir->priv->needs_mtime_update = TRUE;

    dir->priv->file_collection->remove(old_uri_str);
    dir->priv->file_collection->add(f);
    g_signal_emit (dir, signals[FILE_RENAMED], 0, f);
}


void gnome_cmd_dir_start_monitoring (GnomeCmdDir *dir)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    if (dir->priv->monitor_users == 0)
    {
        GError *error = nullptr;

        auto gFileMonitor = g_file_monitor_directory (
            GNOME_CMD_FILE (dir)->gFile,
            //ToDo: We might want to activate G_FILE_MONITOR_WATCH_MOVES in the future
            G_FILE_MONITOR_NONE,
            nullptr,
            &error);

        if (error)
        {
            gchar *uri_str = GNOME_CMD_FILE (dir)->get_uri_str();
            DEBUG ('n', "Failed to add monitor to %p %s: %s\n", dir, uri_str, error->message);
            g_error_free(error);
            g_free(uri_str);
            return;
        }

        g_signal_connect (gFileMonitor, "changed", G_CALLBACK (monitor_callback), dir);

        gchar *uri_str = GNOME_CMD_FILE (dir)->get_uri_str();
        DEBUG('n', "Added monitor to %p %s\n", dir, uri_str);
        dir->priv->gFileMonitor = gFileMonitor;
        g_free (uri_str);
    }

    dir->priv->monitor_users++;
}


void gnome_cmd_dir_cancel_monitoring (GnomeCmdDir *dir)
{
    g_return_if_fail (GNOME_CMD_IS_DIR (dir));

    if (dir->priv->monitor_users < 1) return;

    dir->priv->monitor_users--;

    if (dir->priv->monitor_users == 0)
    {
        if (dir->priv->gFileMonitor)
        {
            g_file_monitor_cancel (dir->priv->gFileMonitor);
            DEBUG('n', "Removed monitor from %p %s\n", dir, GNOME_CMD_FILE (dir)->get_uri_str());

            dir->priv->gFileMonitor = nullptr;
        }
    }
}


gboolean gnome_cmd_dir_is_monitored (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), FALSE);

    return dir->priv->monitor_users > 0;
}


gboolean gnome_cmd_dir_is_local (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), FALSE);

    return gnome_cmd_con_is_local (dir->priv->con);
}


gboolean gnome_cmd_dir_update_mtime (GnomeCmdDir *dir)
{
    // this function also determines if cached dir is up-to-date (FALSE=yes)
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), FALSE);

    // assume cache is updated
    gboolean returnValue = FALSE;

    auto uri              = gnome_cmd_file_get_uri_str(GNOME_CMD_FILE(dir));
    auto tempGFile        = g_file_new_for_uri(uri);
    auto gFileInfoCurrent = g_file_query_info(tempGFile, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
    auto currentMTime     = g_file_info_get_modification_date_time(gFileInfoCurrent);

    auto cachedMTime = g_file_info_get_modification_date_time(GNOME_CMD_FILE(dir)->gFileInfo);
    g_free(uri);
    g_object_unref(gFileInfoCurrent);

    if (currentMTime && cachedMTime && g_date_time_compare(cachedMTime, currentMTime))
    {
        // cache is not up-to-date
        g_file_info_set_modification_date_time(GNOME_CMD_FILE (dir)->gFileInfo, currentMTime);
        returnValue = TRUE;
    }

    // after this function we are sure dir's mtime is up-to-date
    dir->priv->needs_mtime_update = FALSE;

    return returnValue;
}


gboolean gnome_cmd_dir_needs_mtime_update (GnomeCmdDir *dir)
{
    g_return_val_if_fail (GNOME_CMD_IS_DIR (dir), FALSE);

    return dir->priv->needs_mtime_update;
}
