/*
   Copyright (C) 2008 Norbert Frese <nf2 at scheinwelt at>

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

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

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

#include "giobridge.h"
#include "giobridge-remote.h"
#include "giobridge-mount.h"
#include "giobridge-errormapper.h"

#include <QtCore/QBool> //for Q_OS_XXX
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <utime.h>
#include <unistd.h>
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include <QtCore/QByteRef>
#include <QtCore/QDate>
#include <QtCore/QVarLengthArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QRegExp>
#include <QtCore/QFile>

#include <kdebug.h>
#include <kurl.h>
#include <kcomponentdata.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ktemporaryfile.h>
#include <klocale.h>
#include <limits.h>
#include <kshell.h>
#include <kmountpoint.h>
#include <kstandarddirs.h>

#include <kio/ioslave_defaults.h>
#include <kde_file.h>
#include <kglobal.h>
#include <kmimetype.h>

#include <glib.h>
#include <gio/gio.h>

using namespace KIO;

#define MAX_IPC_SIZE (1024*32)

GioBridgeRemote remoteHandler;

//static QString testLogFile( const QByteArray&_filename );

extern "C" int KDE_EXPORT kdemain( int argc, char **argv )
{
    QCoreApplication app( argc, argv ); // needed for QSocketNotifier
    KComponentData componentData( "giobridge", "kdelibs4" );
    ( void ) KGlobal::locale();

    kDebug(GIOBRIDGE_DEBUG_AREA) << "Starting giobridge " << getpid();

    if (argc != 4)
    {
        fprintf(stderr, "Usage: kio_giobridge protocol domain-socket1 domain-socket2\n");
        exit(-1);
    }

    GioBridge slave(argv[2], argv[3]);
    slave.dispatchLoop();

    kDebug(GIOBRIDGE_DEBUG_AREA) << "Done";
    return 0;
}

static QString prepareUrl(const KUrl& url)
{
//    if (url.scheme() == "archive")
//    {
//       QString encodedOnce = url.scheme() + "://" + QUrl::toPercentEncoding(url.host()) + url.path(); 
//       return encodedOnce.replace("%", "%25");
//    }
    return url.url();
}

static void gFileInfoListAttributes(GFileInfo * gFileInfo)
{
    char** attrs = g_file_info_list_attributes(gFileInfo, NULL);
    int cnt=0;
    while (attrs[cnt])
    {
        char * value =  g_file_info_get_attribute_as_string (gFileInfo,
            attrs[cnt]);
        kDebug(GIOBRIDGE_DEBUG_AREA) << "attrname=" << attrs[cnt] << " value=" << value << endl;
        g_free(value);
        cnt++;
    }
    g_strfreev(attrs);
}

static void gFileInfo2UDSEntry(GFileInfo * gFileInfo,
                            const char * basename, UDSEntry & entry,
                            short int details, bool withACL )
{

    gFileInfoListAttributes(gFileInfo);

    assert(entry.count() == 0); // by contract :-)

    // get type

    GFileType gFileType = g_file_info_get_file_type(gFileInfo);

    // file name

    entry.insert( KIO::UDSEntry::UDS_NAME, QString(basename));

    // display name

    const char* displayName = g_file_info_get_display_name(gFileInfo);

    if (displayName != NULL)
    {
        entry.insert( KIO::UDSEntry::UDS_DISPLAY_NAME, QString(displayName));
    }

    // access permissions

    mode_t access=0;

    //entry.insert( KIO::UDSEntry::UDS_ACCESS, access );

    gboolean access_can_read = g_file_info_get_attribute_boolean(gFileInfo,
          G_FILE_ATTRIBUTE_ACCESS_CAN_READ);

    gboolean access_can_write = g_file_info_get_attribute_boolean(gFileInfo,
          G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);

    gboolean access_can_execute = g_file_info_get_attribute_boolean(gFileInfo,
          G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE);

    gboolean access_can_delete = g_file_info_get_attribute_boolean(gFileInfo,
          G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE);

    gboolean access_can_trash = g_file_info_get_attribute_boolean(gFileInfo,
          G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH);

    gboolean access_can_rename = g_file_info_get_attribute_boolean(gFileInfo,
          G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME);


    if (access_can_read)
        access |= S_IREAD;

    if (access_can_write)
        access |= S_IWRITE;

    if (access_can_execute)
        access |= S_IEXEC;

    entry.insert( KIO::UDSEntry::UDS_ACCESS, access );

    // file size

    goffset gsize = g_file_info_get_size(gFileInfo);

    entry.insert( KIO::UDSEntry::UDS_SIZE, gsize);

    // modification time

    GTimeVal gMTime;

    g_file_info_get_modification_time   (gFileInfo, &gMTime);

    entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME,(long long int) gMTime.tv_sec);

    // access time

    if (g_file_info_has_attribute(gFileInfo, G_FILE_ATTRIBUTE_TIME_ACCESS))
    {
        guint64 gATime = g_file_info_get_attribute_uint64(gFileInfo, G_FILE_ATTRIBUTE_TIME_ACCESS);
        entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, gATime);
    }

    // user name

    const char * ownerUser = g_file_info_get_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_OWNER_USER);
    if (ownerUser != NULL)
    {
        entry.insert( KIO::UDSEntry::UDS_USER, ownerUser);
    }

    // group name

    const char * ownerGroup = g_file_info_get_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_OWNER_GROUP);
    if (ownerGroup != NULL)
    {
        entry.insert( KIO::UDSEntry::UDS_GROUP, ownerGroup);
    }

    // symlink target

    const char * symlinkTarget = g_file_info_get_symlink_target(gFileInfo);
    if (symlinkTarget != NULL)
    {
        entry.insert( KIO::UDSEntry::UDS_LINK_DEST, QString(symlinkTarget));
    }

    // target uri

    const char * targetUri = g_file_info_get_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
    if (targetUri != NULL)
    {
        entry.insert( KIO::UDSEntry::UDS_TARGET_URL, QString(targetUri));
    }

    // content type

    const char * contentType = g_file_info_get_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
    if (contentType != NULL)
    {
        entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, QString(contentType));
    }
    else
    {
      if (gFileType == G_FILE_TYPE_MOUNTABLE)
        entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, CUSTOM_MIMETYTPE_GVFS_MOUNT);
    }

    // icon

    GIcon * gIcon = g_file_info_get_icon(gFileInfo);
    if (gIcon != NULL && G_IS_THEMED_ICON(gIcon))
    {
        GThemedIcon * gThemedIcon = G_THEMED_ICON(gIcon);
        //const char * const * iconNames = g_themed_icon_get_names (gThemedIcon);
        gchar ** iconNames = (gchar **) g_themed_icon_get_names (gThemedIcon);
        guint numIconNames = g_strv_length(iconNames);
        for (int i=0;i< numIconNames;i++)
        {
            kDebug(GIOBRIDGE_DEBUG_AREA) << "themed icon name: " << iconNames[i];
        }

        if (numIconNames>0)
        {
            // only use first offer
            entry.insert( KIO::UDSEntry::UDS_ICON_NAME, QString(iconNames[0]));
        }
    }

    // set file type

    mode_t type;

    switch (gFileType)
    {
        case G_FILE_TYPE_UNKNOWN : type = 0; break;
        case G_FILE_TYPE_REGULAR : type = S_IFREG; break;
        case G_FILE_TYPE_DIRECTORY : type = S_IFDIR; break;
        case G_FILE_TYPE_SYMBOLIC_LINK : type = S_IFLNK; break; // S_IFREG ?
        case G_FILE_TYPE_SPECIAL : type = S_IFSOCK; break;
        case G_FILE_TYPE_SHORTCUT : type = S_IFDIR; break;
        case G_FILE_TYPE_MOUNTABLE : type = S_IFDIR; break;
        default : type = 0; break;
    }

    if (type != 0)
    {
        entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, type );
    }

    kDebug(GIOBRIDGE_DEBUG_AREA) << "gFileInfo: basename=" << basename << " filetype=" << gFileType
        << " canread=" << access_can_read << " canwrite=" << access_can_write
        << " canexecute=" << access_can_execute << " candelete=" << access_can_delete
        << " cantrash=" << access_can_trash << " canrename=" << access_can_rename
        << " size=" << gsize << " gmtime.sec=" << gMTime.tv_sec
        << " user=" << ownerUser << " group=" << ownerGroup
        << " symlinktarget=" << symlinkTarget
        << " targetURL=" << targetUri << " contentType=" << contentType
        << endl;
    return;
}


// ============== GioBridge ==============

GioBridge::GioBridge( const QByteArray &pool, const QByteArray &app )
    : SlaveBase( "*", pool, app )
{
	  kDebug(GIOBRIDGE_DEBUG_AREA) << "initializing GioBridge";
	  g_type_init ();

	  gFileOutputStream=NULL;
      gFileInputStream=NULL;
}

GioBridge::~GioBridge()
{
}


void GioBridge::chmod( const KUrl& url, int permissions )
{
    GError * err = NULL;

    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::chmod(): " << url << ", permissions = " << permissions;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GFileInfo* gFileInfo = g_file_info_new();

    // FIXME: that doesn't work
    // g_file_info_set_attribute_boolean (gFileInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, permissions & S_IREAD);
    // g_file_info_set_attribute_boolean (gFileInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, permissions & S_IWRITE);

    g_file_info_set_attribute_uint32(gFileInfo, G_FILE_ATTRIBUTE_UNIX_MODE, permissions);

    GioBridgeMount autoMounter(this, url);

    autoMounter.op_g_file_set_attributes_from_info(gFile, gFileInfo,
          G_FILE_QUERY_INFO_NONE, NULL, &err);

    if (err)
    {
        errorTranslated( err, GioBridgeErrorMapper::OP_CHMOD, url );
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFileInfo);
    g_object_unref (gFile);
}

void GioBridge::chown( const KUrl& url, const QString& owner, const QString& group )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::chown(): " << url << ", owner = " << owner << ", group = " << group;

    GError * err = NULL;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GFileInfo* gFileInfo = g_file_info_new();

    // FIXME: it's not possible to set owner and group by name
    g_file_info_set_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_OWNER_USER, owner.toAscii());
    g_file_info_set_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_OWNER_GROUP, group.toAscii());

    GioBridgeMount autoMounter(this, url);

    autoMounter.op_g_file_set_attributes_from_info(gFile, gFileInfo,
        G_FILE_QUERY_INFO_NONE, NULL, &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_CHOWN, url);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFileInfo);
    g_object_unref (gFile);
}

void GioBridge::setModificationTime( const KUrl& url, const QDateTime& mtime )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::setModificationTime(): " << url << ", mtime = " << mtime;

    GError * err = NULL;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    guint64 gmtime = mtime.toTime_t ();

    GioBridgeMount autoMounter(this, url);

    autoMounter.op_g_file_set_attribute(gFile, G_FILE_ATTRIBUTE_TIME_MODIFIED,
        G_FILE_ATTRIBUTE_TYPE_UINT64,
        &gmtime,
        G_FILE_QUERY_INFO_NONE,
        NULL,
        &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_SETTIME, url);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFile);
}

void GioBridge::mkdir( const KUrl& url, int permissions )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::mkdir(): " << url << ", permissions = " << permissions;

    GError * err = NULL;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GioBridgeMount autoMounter(this, url);

    autoMounter.op_g_file_make_directory(gFile, NULL, &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_MKDIR, url);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFile);

    return;
}

void GioBridge::get( const KUrl& url )
{
    goffset gTotalSize=-1;

    QString urlStr = prepareUrl(url);
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::get " << urlStr;

    GError * err = NULL;

    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GioBridgeMount autoMounter(this, url);

    GFileInputStream* gFileInputStream = autoMounter.op_g_file_read(gFile,
                                                          NULL,
                                                          &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
        g_error_free(err);

        g_object_unref (gFile);
        if (gFileInputStream) g_object_unref (gFileInputStream);
        return;
    }

    // query fileinfo

    GFileInfo* gFileInfo = g_file_input_stream_query_info(gFileInputStream,
                                                             "*",
                                                             NULL,
                                                             &err);
    if (err)
    {
        if (err->code != 15)
        {
            errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
            g_error_free(err);

            g_object_unref (gFile);
            if (gFileInputStream) g_object_unref (gFileInputStream);
            return;
        }
        // query info unsupported -> stat

        kDebug(GIOBRIDGE_DEBUG_AREA) << "g_file_query_info! " << endl;

        g_error_free(err);
        err = NULL;

        gFileInfo = g_file_query_info( gFile,
                 "*",
                 G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                 NULL,
                 &err);

        if (err)
        {
             errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
             g_error_free(err);

             g_object_unref (gFile);
             g_object_unref (gFileInputStream);
             if (gFileInfo) g_object_unref (gFileInfo);
             return;
        }
    }

    const char* contentType = g_file_info_get_content_type(gFileInfo);
    if (contentType)
    {
        kDebug(GIOBRIDGE_DEBUG_AREA) << "content type: " << contentType << endl;
        emit mimeType(contentType);
    }

    gTotalSize = g_file_info_get_size(gFileInfo);
    kDebug(GIOBRIDGE_DEBUG_AREA) << "total size: " << gTotalSize << endl;

    totalSize( gTotalSize );  // emit totalSize

    KIO::filesize_t processed_size = 0;

    // resume?

    QString resumeOffset = metaData("resume");
    if ( !resumeOffset.isEmpty() )
    {
        bool ok;
        KIO::fileoffset_t offset = resumeOffset.toLongLong(&ok);
        if (ok && (offset > 0) && (offset < gTotalSize))
        {
            gssize skipped = g_input_stream_skip(G_INPUT_STREAM(gFileInputStream),
                                                 offset,
                                                 NULL,
                                                 &err); // use seek instead?
            if (skipped==-1)
            {
                errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
                g_error_free(err);

                g_object_unref (gFile);
                g_object_unref (gFileInfo);
                g_object_unref (gFileInputStream);
                return;
            }

            canResume ();
            processed_size = offset;
            kDebug(GIOBRIDGE_DEBUG_AREA) << "Resume offset: " << KIO::number(offset);
        }
    }

    char buffer[ MAX_IPC_SIZE ];
    QByteArray array;

    while( 1 )
    {
        gssize n = g_input_stream_read(G_INPUT_STREAM(gFileInputStream),
              buffer,
              MAX_IPC_SIZE,
              NULL,
              &err);

        if (n == -1)
        {
            errorTranslated(err, GioBridgeErrorMapper::OP_READ, url);  // KIO::ERR_COULD_NOT_READ?
            g_object_unref (gFile);
            g_object_unref (gFileInputStream);
            return;
        }
        if (n == 0)
            break; // Finished

        array = QByteArray::fromRawData(buffer, n);
        data( array );
        array.clear();

        processed_size += n;
        processedSize( processed_size );

        kDebug(GIOBRIDGE_DEBUG_AREA) << "Processed: " << KIO::number (processed_size);
    }

    data( QByteArray() );

    processedSize( gTotalSize );
    finished();

    g_object_unref (gFile);
    g_object_unref (gFileInfo);
    g_object_unref (gFileInputStream);

}

void GioBridge::put( const KUrl& url, int _mode, KIO::JobFlags _flags )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::put: " << url << ", mode=" << _mode;

    GError * err = NULL;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GFileOutputStream* gFileOutputStream=NULL;

    GioBridgeMount autoMounter(this, url);

    if (!(_flags & KIO::Overwrite))
    {
        gFileOutputStream = autoMounter.op_g_file_create(gFile,
                    G_FILE_CREATE_NONE,
                    NULL,
                    &err);
    }
    else
    {
        gFileOutputStream = autoMounter.op_g_file_replace(gFile,
                    NULL,
                    FALSE,
                    G_FILE_CREATE_NONE,
                    NULL,
                    &err);
    }

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
        g_error_free(err);
        g_object_unref (gFile);
        return;
    }

    int result=0;
    // Loop writing
    do
    {
        QByteArray buffer;
        dataReq(); // Request for data
        result = readData( buffer );

        if (result >= 0)
        {
            gsize bytes_written=-1;

            g_output_stream_write_all(G_OUTPUT_STREAM(gFileOutputStream),
                buffer.data(),
                buffer.size(),
                &bytes_written,
                NULL,
                &err);

            if (err)
            {
                errorTranslated(err, GioBridgeErrorMapper::OP_WRITE, url);
                g_error_free(err);
                g_object_unref (gFile);
                g_object_unref (gFileOutputStream);
                return;
            }

            g_assert(bytes_written == buffer.size());
        }
    }
    while ( result > 0 );

    g_output_stream_close(G_OUTPUT_STREAM(gFileOutputStream), NULL, &err);
    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_CLOSE, url);
        g_error_free(err);
        g_object_unref (gFile);
        g_object_unref (gFileOutputStream);
        return;
    }

    g_object_unref (gFileOutputStream);

    // set permissions
    if ( _mode != -1)
    {
        GFileInfo* gFileInfo = g_file_info_new();

        g_file_info_set_attribute_boolean (gFileInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, _mode & S_IREAD);
        g_file_info_set_attribute_boolean (gFileInfo, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, _mode & S_IWRITE);

        g_file_set_attributes_from_info(gFile, gFileInfo,
            G_FILE_QUERY_INFO_NONE, NULL, &err);

        if (err)
        {
            kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::put set permissions "<<  err->message;
            g_error_free(err);
            err=NULL;
        }
        g_object_unref (gFileInfo);
    }

    // set modification time
    const QString mtimeStr = metaData( "modified" );
    if ( !mtimeStr.isEmpty() ) {
        QDateTime dt = QDateTime::fromString( mtimeStr, Qt::ISODate );
        if ( dt.isValid() ) {

            guint64 gmtime = dt.toTime_t(); // modification time

            g_file_set_attribute(gFile, G_FILE_ATTRIBUTE_TIME_MODIFIED,
                G_FILE_ATTRIBUTE_TYPE_UINT64,
                &gmtime,
                G_FILE_QUERY_INFO_NONE,
                NULL,
                &err);

            if (err)
            {
                kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::put set time modified "<<  err->message;
                g_error_free(err);
                err=NULL;
            }
        }
    }

    g_object_unref (gFile);
    finished();
}

void GioBridge::open(const KUrl &url, QIODevice::OpenMode mode)
{
    kDebug() << "GioBridge::open " << url.url();

    Q_ASSERT(gFileOutputStream == NULL && gFileInputStream == NULL);

    openPath = QFile::encodeName(url.toLocalFile());

    GError * err = NULL;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    // query fileinfo

    QString contentType = QString();
    goffset gsize = 0;

    if (mode & QIODevice::ReadOnly || mode & QIODevice::Append)
    {
        GioBridgeMount autoMounter(this, url);

        GFileInfo* gFileInfo = autoMounter.op_g_file_query_info( gFile,
                "*",
                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                NULL,
                &err);

        if (err)
        {
            errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
            g_error_free(err);

            g_object_unref (gFile);
            return;
        }

        gsize = g_file_info_get_size(gFileInfo);
        const char * gContentType = g_file_info_get_attribute_string(gFileInfo, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
        contentType = QString(gContentType);
        g_object_unref(gFileInfo);
    }

    // open file

    if (mode & QIODevice::ReadOnly)
    {
        GioBridgeMount autoMounter(this, url);
        gFileInputStream = autoMounter.op_g_file_read(gFile, NULL, &err);
    }
    else if (mode & QIODevice::WriteOnly && !(mode & QIODevice::Truncate))
    {
        GioBridgeMount autoMounter(this, url);
        gFileOutputStream = autoMounter.op_g_file_create(gFile,
                        G_FILE_CREATE_NONE,
                        NULL,
                        &err);
    }
    else if (mode & QIODevice::WriteOnly && mode & QIODevice::Truncate)
    {
        GioBridgeMount autoMounter(this, url);
        gFileOutputStream = autoMounter.op_g_file_replace(gFile,
                        NULL,
                        FALSE,
                        G_FILE_CREATE_NONE,
                        NULL,
                        &err);
    }
    else if (mode & QIODevice::WriteOnly && mode & QIODevice::Append)
    {
        GioBridgeMount autoMounter(this, url);
        gFileOutputStream =  autoMounter.op_g_file_append_to(gFile,
                        G_FILE_CREATE_NONE,
                        NULL,
                        &err);
    }
    else
    {
        // not supported
        error(ERR_UNSUPPORTED_ACTION, "bad open mode " + mode);
        g_object_unref (gFile);
        return;
    }

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, url);
        g_error_free(err);

        g_object_unref (gFile);
        gFileOutputStream=NULL;
        gFileInputStream=NULL;

        return;
    }

    if (!contentType.isNull())
    {
        emit mimeType( contentType );
    }
    totalSize( gsize );
    position( 0 );

    emit opened();
}

void GioBridge::read(KIO::filesize_t bytes)
{
    GError * err = NULL;

    kDebug() << "File::read() n=" << bytes;

    Q_ASSERT(gFileInputStream != NULL);

    char buffer[ bytes ];

    while (true) {
        if (bytes <= 0) break;

        gssize n = g_input_stream_read(G_INPUT_STREAM(gFileInputStream),
                  buffer,
                  bytes,
                  NULL,
                  &err);

        if (n > 0) {
            QByteArray array = array.fromRawData(buffer, n);
            data( array );
            bytes -= n;
        } else {
            // eof or error
            data(QByteArray());
            if (n != 0) {
                g_error_free(err);
                error(KIO::ERR_COULD_NOT_READ, openPath);
                close();
            }
            break;
        }
    }
}

void GioBridge::write(const QByteArray &data)
{
    GError * err = NULL;

    kDebug() << "GioBridge::write()";
    Q_ASSERT(gFileOutputStream != NULL);

    gsize bytes_written=-1;

    g_output_stream_write_all(G_OUTPUT_STREAM(gFileOutputStream),
        data.constData(),
        data.size(),
        &bytes_written,
        NULL,
        &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_WRITE, QString(openPath));
        g_error_free(err);
    }
    else
    {
        written(data.size());
    }
}

void GioBridge::seek(KIO::filesize_t offset)
{
    GError * err = NULL;
    kDebug( 7101 ) << "GioBridge::seek() offset=" << offset;

    GSeekable *seekable;
    if (gFileOutputStream!=NULL)
    {
        seekable = G_SEEKABLE(gFileOutputStream);
    }
    else
    {
        seekable = G_SEEKABLE(gFileInputStream);
    }

    Q_ASSERT(seekable != NULL);

    gboolean success = g_seekable_seek(seekable, offset, G_SEEK_SET, NULL, &err);

    if (success) {
        position( offset );
    } else {
        // TODO other errors?
        g_error_free(err);
        error(KIO::ERR_COULD_NOT_SEEK, openPath );
        close();
    }
}

void GioBridge::close()
{
    GError * err = NULL;
    kDebug() << "GioBridge::close()";
    Q_ASSERT(gFileOutputStream != NULL || gFileInputStream != NULL);

    if (gFileOutputStream !=  NULL)
    {
        g_output_stream_close (G_OUTPUT_STREAM(gFileOutputStream), NULL, &err);
        g_object_unref (gFileOutputStream);
    }
    else
    {
        g_input_stream_close(G_INPUT_STREAM(gFileInputStream), NULL, &err);
        g_object_unref (gFileInputStream);
    }

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_CLOSE, QString(openPath));
        g_error_free(err);
    }
    else
    {
        finished();
    }
    gFileOutputStream=NULL;
    gFileInputStream=NULL;

    openPath.clear();
}



static void _my_GFileProgressCallback(goffset current_num_bytes,
                               goffset total_num_bytes,
                               gpointer user_data)
{
    GioBridge * bridge = (GioBridge * )user_data;

    bridge->processedSize( current_num_bytes );
    bridge->totalSize( total_num_bytes );

}


void GioBridge::copy( const KUrl &src, const KUrl &dest,
                         int _mode, JobFlags _flags )
{
    QString srcStr = prepareUrl(src);
    QString destStr = prepareUrl(dest);

    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::copy(): " << srcStr << " --> " << destStr;

    GError * err = NULL;

    GFile * gFileSource = g_file_new_for_uri(srcStr.toUtf8().data());
    GFile * gFileDest = g_file_new_for_uri(destStr.toUtf8().data());

    int copyFlags = G_FILE_COPY_NONE;
    if (_flags & KIO::Overwrite) copyFlags |= G_FILE_COPY_OVERWRITE;

    GioBridgeMount::op_g_file_copy(this, gFileSource, gFileDest,
        (GFileCopyFlags) copyFlags,
        NULL,
        _my_GFileProgressCallback,
        this,
        &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, src, dest);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFileSource);
    g_object_unref (gFileDest);
}

void GioBridge::rename( const KUrl &src, const KUrl &dest,
                           KIO::JobFlags _flags )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::rename(): " << src << " --> " << dest;

    GError * err = NULL;

    QString srcStr = prepareUrl(src);
    QString destStr = prepareUrl(dest);

    GFile * gFileSource = g_file_new_for_uri(srcStr.toUtf8().data());
    GFile * gFileDest = g_file_new_for_uri(destStr.toUtf8().data());

    GioBridgeMount::op_g_file_move(this, gFileSource, gFileDest,
        G_FILE_COPY_NONE,
        NULL,
        _my_GFileProgressCallback,
        this,
        &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, src, dest);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFileSource);
    g_object_unref (gFileDest);

}

void GioBridge::symlink( const QString &target, const KUrl &dest, KIO::JobFlags flags )
{

    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::symlink(): " << target << " <-- " << dest;

    GError * err = NULL;

    QString destStr = prepareUrl(dest);
    GFile * gFileDest = g_file_new_for_uri(destStr.toUtf8().data());

    g_file_make_symbolic_link(gFileDest,
              target.toUtf8 (),
              NULL,
              &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::IS_FILE_OP, dest, target);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFileDest);
}

void GioBridge::del( const KUrl& url, bool isfile)
{
    kDebug(GIOBRIDGE_DEBUG_AREA) <<  "Deleting "<< url.url() << " isfile=" << isfile;

    GError * err = NULL;

    QString urlStr = prepareUrl(url);
    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GioBridgeMount autoMounter(this, url);
    GFileInfo * gFileInfo = autoMounter.op_g_file_query_info( gFile,
            "*",
            G_FILE_QUERY_INFO_NONE,
            NULL,
            &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_DELETE, url);
        g_error_free(err);

        g_object_unref (gFile);
        if (gFileInfo) g_object_unref (gFileInfo);
        return;

    }

    GFileType gFileType = g_file_info_get_file_type(gFileInfo);
    if (gFileInfo) g_object_unref (gFileInfo);

    if (isfile && gFileType != G_FILE_TYPE_REGULAR)
    {
        error( KIO::ERR_IS_DIRECTORY, url.url() );
        g_object_unref (gFile);
        return;
    }

    if (!isfile && gFileType != G_FILE_TYPE_DIRECTORY)
    {
        error( KIO::ERR_IS_FILE, url.url() );
        g_object_unref (gFile);
        return;
    }

    g_file_delete(gFile, NULL, &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_DELETE, url);
        g_error_free(err);
    }
    else
    {
        finished();
    }
    g_object_unref (gFile);
}

void GioBridge::stat( const KUrl & url )
{
    if (remoteHandler.delegateStat(*this, url)) return;

    QString urlStr = prepareUrl(url);
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::stat " << urlStr;

    // detail level?
    QString sDetails = metaData(QLatin1String("details"));
    int details = sDetails.isEmpty() ? 2 : sDetails.toInt();
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::stat details=" << details;

    // stat file
    GError * err = NULL;

    GFile * gFile = g_file_new_for_uri(urlStr.toUtf8().data());

    GioBridgeMount autoMounter(this, url);

    GFileInfo * gFileInfo = autoMounter.op_g_file_query_info( gFile,
            "*",
            details > 1 ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
            NULL,
            &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_OTHER, url);
        g_error_free(err);

        g_object_unref (gFile);
        if (gFileInfo) g_object_unref (gFileInfo);
        return;
    }

    char * basename = g_file_get_basename(gFile);

    // create UDS Entry
    UDSEntry entry;

    gFileInfo2UDSEntry(gFileInfo, basename, entry, details, true /*with acls*/ );

    // push result

    statEntry( entry );

    finished();

    // free objects
    g_free(basename);
    g_object_unref (gFile);
    g_object_unref (gFileInfo);
}

void GioBridge::listDir( const KUrl& url)
{
    if (remoteHandler.delegateListDir(*this, url)) return;


    kDebug(GIOBRIDGE_DEBUG_AREA) << "========= LIST " << prepareUrl(url) << " " << url << " =========";

    GError * err = NULL;

    //KUrl myUrl=url;

    // fix KIO smb:/WORKGROUP -> smb://WORKGROUP
    QString kUrlUrl = prepareUrl(url);

    if (kUrlUrl.startsWith("smb:/") && !kUrlUrl.startsWith("smb://"))
    {
      kUrlUrl.insert(4,"/");
    }

    // enumerate files

    GFile * gFile = g_file_new_for_uri(kUrlUrl.toUtf8().data());

    GioBridgeMount autoMounter(this, kUrlUrl);

    GFileEnumerator * gFileEnumerator = autoMounter.op_g_file_enumerate_children(
         gFile,
         "*",
         G_FILE_QUERY_INFO_NONE,
         NULL,
         &err);

    if (err)
    {
        errorTranslated(err, GioBridgeErrorMapper::OP_OTHER, url);
        g_error_free(err);
        g_object_unref (gFile);
        g_object_unref (gFileEnumerator);
        return;
    }

    if (gFileEnumerator == NULL)  // shouldn't happen -> bug in GVFS
    {
        error( ERR_INTERNAL, "gFileEnumerator == NULL, but no err set" );
        g_object_unref (gFile);
        return;
    }

    UDSEntry entry;

    for(;;)
    {
        GFileInfo* gFileInfo = g_file_enumerator_next_file(gFileEnumerator,
                                        NULL,
                                        &err);

        if (err)
        {
            errorTranslated(err, GioBridgeErrorMapper::OP_LIST, url);
            g_error_free(err);
            g_object_unref (gFile);
            g_object_unref (gFileEnumerator);
            g_object_unref (gFileInfo);
            return;
        }

        if (!gFileInfo)
        {
            break;  // done!
        }

        const char*  basename = g_file_info_get_name(gFileInfo);

        entry.clear();

        // fill uds-entry
        gFileInfo2UDSEntry(gFileInfo, basename, entry, 2, true);

        listEntry( entry, false);
        g_object_unref(gFileInfo);
    }

    listEntry( entry, true ); // ready

    kDebug(GIOBRIDGE_DEBUG_AREA) << "============= COMPLETED LIST ============";

    finished();

    g_object_unref (gFile);
    g_object_unref (gFileEnumerator);
}


void GioBridge::errorTranslated(GError * err, GioBridgeErrorMapper::OperationHint opHint, const KUrl & url, const KUrl & targetUrl)
{
    GioBridgeErrorMapper mapper(err, opHint, url, targetUrl);
    error(mapper.resultErrId, mapper.resultText);
}


// Connected to KShred
void GioBridge::slotProcessedSize( KIO::filesize_t bytes )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::slotProcessedSize (" << (unsigned int) bytes << ")";
    processedSize( bytes );
}

// Connected to KShred
void GioBridge::slotInfoMessage( const QString & msg )
{
    kDebug(GIOBRIDGE_DEBUG_AREA) << "GioBridge::slotInfoMessage (" << msg << ")";
    infoMessage( msg );
}


#include "giobridge.moc"
