/***************************************************************************
                       normal_vfs.cpp
                   -------------------
    copyright            : (C) 2000 by Rafi Yanai
    e-mail               : krusader@users.sourceforge.net
    web site             : http://krusader.sourceforge.net
 ---------------------------------------------------------------------------

 ***************************************************************************

  A

     db   dD d8888b. db    db .d8888.  .d8b.  d8888b. d88888b d8888b.
     88 ,8P' 88  `8D 88    88 88'  YP d8' `8b 88  `8D 88'     88  `8D
     88,8P   88oobY' 88    88 `8bo.   88ooo88 88   88 88ooooo 88oobY'
     88`8b   88`8b   88    88   `Y8b. 88~~~88 88   88 88~~~~~ 88`8b
     88 `88. 88 `88. 88b  d88 db   8D 88   88 88  .8D 88.     88 `88.
     YP   YD 88   YD ~Y8888P' `8888Y' YP   YP Y8888D' Y88888P 88   YD

                                                     S o u r c e    F i l e

 ***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "normal_vfs.h"

#ifdef HAVE_POSIX_ACL
#include <sys/acl.h>
#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
#include <acl/libacl.h>
#endif
#endif

// QtCore
#include <QTimer>
#include <QByteArray>
#include <QDir>
#include <qplatformdefs.h>

#include <KConfigCore/KSharedConfig>
#include <KI18n/KLocalizedString>
#include <KIO/DeleteJob>
#include <KIO/DropJob>
#include <KIO/JobUiDelegate>
#include <KIOCore/KFileItem>
#include <KWidgetsAddons/KMessageBox>

#include "../Dialogs/krdialogs.h"
#include "../MountMan/kmountman.h"
#include "krpermhandler.h"
#include "../krglobal.h"
#include "../defaults.h"
#include "../krservices.h"

normal_vfs::normal_vfs(QObject* panel): vfs(panel), watcher(0)
{
    vfs_type = VFS_NORMAL;
}

bool normal_vfs::populateVfsList(const QUrl &origin, bool showHidden)
{
    QString path = KrServices::urlToLocalPath(origin);
#ifdef Q_WS_WIN
    if (! path.contains("/")) {  //change C: to C:/
        path = path + QString("/");
    }
#endif

    // set the writable attribute to true, if that's not the case - the KIO job
    // will give the warnings and errors
    isWritable = true;

    if (watcher) delete watcher;  //stop watching the old dir
    watcher = 0;

    // set the origin...
    vfs_origin = origin;
    vfs_origin.setPath(path);
    //vfs_origin.vfs_origin = vfs_origin.adjusted(QUrl::StripTrailingSlash));
    vfs_origin.setScheme("file"); // do not remove !
    vfs_origin.setPath(QDir::cleanPath(vfs_origin.path()));

    // check that the new origin exists
    if (!QDir(path).exists()) {
        if (!quietMode)
            emit error(i18n("The folder %1 does not exist.", path));
        return false;
    }

    KConfigGroup group(krConfig, "Advanced");
    if (group.readEntry("AutoMount", _AutoMount) && !mountMan.isNull())
        mountMan->autoMount(path);

    QT_DIR* dir = QT_OPENDIR(path.toLocal8Bit());
    if (!dir) {
        if (!quietMode)
            emit error(i18n("Cannot open the folder %1.", path));
        return false;
    }

    // change directory to the new directory
    QString save = QDir::currentPath();
    if (! QDir::setCurrent(path)) {
        if (!quietMode)
            emit error(i18nc("%1=folder path", "Access to %1 denied", path));
        QT_CLOSEDIR(dir);
        return false;
    }

    QT_DIRENT* dirEnt;
    QString name;

    while ((dirEnt = QT_READDIR(dir)) != NULL) {
        name = QString::fromLocal8Bit(dirEnt->d_name);

        // show hidden files ?
        if (!showHidden && name.left(1) == ".") continue ;
        // we don't need the ".",".." entries
        if (name == "." || name == "..") continue;

        vfile* temp = vfileFromName(name, dirEnt->d_name);
        foundVfile(temp);
    }
    // clean up
    QT_CLOSEDIR(dir);
    QDir::setCurrent(save);

    if (panelConnected) {
        watcher = new KDirWatch();
        // connect the watcher
        connect(watcher, SIGNAL(dirty(const QString&)), this, SLOT(vfs_slotDirty(const QString&)));
        connect(watcher, SIGNAL(created(const QString&)), this, SLOT(vfs_slotCreated(const QString&)));
        connect(watcher, SIGNAL(deleted(const QString&)), this, SLOT(vfs_slotDeleted(const QString&)));
        watcher->addDir(vfs_getOrigin().adjusted(QUrl::StripTrailingSlash).path(), KDirWatch::WatchFiles); //start watching the new dir
        watcher->startScan(true);
    }

    return true;
}

// copy a file to the vfs (physical)
void normal_vfs::vfs_addFiles(const QList<QUrl> &fileUrls, KIO::CopyJob::CopyMode mode, QObject* toNotify, QString dir, PreserveMode pmode)
{
    //if( watcher ) watcher->stopScan(); // we will refresh manually this time...
    if (watcher) {
        delete watcher;   // stopScan is buggy, leaves reference on the directory, that's why we delete the watcher
        watcher = 0;
    }

    QUrl dest = QUrl::fromLocalFile(vfs_workingDir() + '/' + dir);

    KIO::Job* job = PreservingCopyJob::createCopyJob(pmode, fileUrls, dest, mode, false, true);
    connect(job, SIGNAL(result(KJob*)), this, SLOT(vfs_refresh(KJob *)));
    if (mode == KIO::CopyJob::Move && toNotify) // notify the other panel
        connect(job, SIGNAL(result(KJob*)), toNotify, SLOT(vfs_refresh(KJob*)));
    else
        job->ui()->setAutoErrorHandlingEnabled(true);
}

void normal_vfs::vfs_drop(const QUrl &destination, QDropEvent *event) {
    KIO::DropJob *job = KIO::drop(event, destination);
    connect(job, SIGNAL(result(KJob*)), this, SLOT(vfs_refresh(KJob *)));
    QObject *dropSource = event->source();
    if (dropSource) { // refresh source because files may have been removed
        connect(job, SIGNAL(result(KJob*)), dropSource, SLOT(vfs_refresh(KJob*)));
    }
}

// remove a file from the vfs (physical)
void normal_vfs::vfs_delFiles(const QStringList &fileNames, bool reallyDelete)
{
//  if( watcher ) watcher->stopScan(); // we will refresh manually this time...
    if (watcher) {
        delete watcher;   // stopScan is buggy, leaves reference on the directory, that's why we delete the watcher
        watcher = 0;
    }

    // names -> urls
    QList<QUrl> filesUrls = vfs_getFiles(fileNames);

    KIO::Job *job;

    // delete of move to trash ?
    KConfigGroup group(krConfig, "General");
    if (!reallyDelete && group.readEntry("Move To Trash", _MoveToTrash)) {
        job = KIO::trash(filesUrls);
        emit trashJobStarted(job);
    } else
        job = KIO::del(filesUrls);

    connect(job, SIGNAL(result(KJob*)), this, SLOT(vfs_refresh(KJob*)));
}

// return a path to the file
QUrl normal_vfs::vfs_getFile(const QString& name)
{
    QString url;
    if (vfs_workingDir() == "/") url = "/" + name;
    else url = vfs_workingDir() + '/' + name;

    return QUrl::fromLocalFile(url);
}

QList<QUrl> normal_vfs::vfs_getFiles(const QStringList &names)
{
    QList<QUrl> urls;
    foreach (const QString &name, names) {
        urls.append(vfs_getFile(name));
    }
    return urls;
}

void normal_vfs::vfs_mkdir(const QString& name)
{
    if (!QDir(vfs_workingDir()).mkdir(name))
        if (!quietMode)
            KMessageBox::sorry(parentWindow, i18n("Cannot create a folder. Check your permissions."));
    vfs::vfs_refresh();
}

void normal_vfs::vfs_rename(const QString& fileName, const QString& newName)
{
    //if( watcher ) watcher->stopScan(); // we will refresh manually this time...
    if (watcher) {
        delete watcher;   // stopScan is buggy, leaves reference on the directory, that's why we delete the watcher
        watcher = 0;
    }

    QList<QUrl> fileUrls;
    fileUrls.append(QUrl::fromLocalFile(vfs_workingDir() + '/' + fileName));

    KIO::Job *job = KIO::move(fileUrls, QUrl::fromLocalFile(vfs_workingDir() + '/' + newName));
    connect(job, SIGNAL(result(KJob*)), this, SLOT(vfs_refresh(KJob*)));
}

vfile* normal_vfs::vfileFromName(const QString& name, char * rawName)
{
    QString path = vfs_workingDir() + '/' + name;
    QByteArray fileName = rawName == 0 ? path.toLocal8Bit() : (vfs_workingDir() + '/').toLocal8Bit().append(rawName);

    QT_STATBUF stat_p;
    stat_p.st_size = 0;
    stat_p.st_mode = 0;
    stat_p.st_mtime = 0;
    stat_p.st_uid = 0;
    stat_p.st_gid = 0;
    QT_LSTAT(fileName.data(), &stat_p);
    KIO::filesize_t size = stat_p.st_size;
    QString perm = KRpermHandler::mode2QString(stat_p.st_mode);
    bool symLink = S_ISLNK(stat_p.st_mode);
    bool brokenLink = false;
    if (S_ISDIR(stat_p.st_mode)) perm[0] = 'd';

    QUrl mimeUrl = QUrl::fromLocalFile(path);
    QString mime;

    QString symDest;
    if (S_ISLNK(stat_p.st_mode)) {  // who the link is pointing to ?
        // the path of the symlink target cannot be longer than the file size of the symlink
        char buffer[stat_p.st_size];
        memset(buffer, 0, sizeof(buffer));
        int bytesRead = readlink(fileName.data(), buffer, sizeof(buffer));
        if (bytesRead != -1) {
            symDest = QString::fromLocal8Bit(buffer, bytesRead);
            if (QDir(symDest).exists())
                perm[0] = 'd';
            if (!QDir(vfs_workingDir()).exists(symDest))
                brokenLink = true;
        } else
            krOut << "Failed to read link: " << path << endl;
    }

    int rwx = 0;
    if (::access(fileName.data(), R_OK) == 0)
        rwx |= R_OK;
    if (::access(fileName.data(), W_OK) == 0)
        rwx |= W_OK;

#ifndef Q_CC_MSVC
    if (::access(fileName.data(), X_OK) == 0)
        rwx |= X_OK;
#endif

    // create a new virtual file object
    vfile* temp = new vfile(name, size, perm, stat_p.st_mtime, symLink, brokenLink, stat_p.st_uid,
                            stat_p.st_gid, mime, symDest, stat_p.st_mode, rwx);
    temp->vfile_setUrl(mimeUrl);
    return temp;
}

void normal_vfs::getACL(vfile *file, QString &acl, QString &defAcl)
{
    Q_UNUSED(file);
    acl.clear();
    defAcl.clear();
#ifdef HAVE_POSIX_ACL
    QString fileName = file->vfile_getUrl().adjusted(QUrl::StripTrailingSlash).path();
#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
    if (acl_extended_file(fileName)) {
#endif
        acl = getACL(fileName, ACL_TYPE_ACCESS);
        if (file->vfile_isDir())
            defAcl = getACL(fileName, ACL_TYPE_DEFAULT);
#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
    }
#endif
#endif
}

QString normal_vfs::getACL(const QString & path, int type)
{
    Q_UNUSED(path);
    Q_UNUSED(type);
#ifdef HAVE_POSIX_ACL
    acl_t acl = 0;
    // do we have an acl for the file, and/or a default acl for the dir, if it is one?
    if ((acl = acl_get_file(path.toLocal8Bit(), type)) != 0) {
        bool aclExtended = false;

#ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
        aclExtended = acl_equiv_mode(acl, 0);
#else
        acl_entry_t entry;
        int ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
        while (ret == 1) {
            acl_tag_t currentTag;
            acl_get_tag_type(entry, &currentTag);
            if (currentTag != ACL_USER_OBJ &&
                    currentTag != ACL_GROUP_OBJ &&
                    currentTag != ACL_OTHER) {
                aclExtended = true;
                break;
            }
            ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
        }
#endif

        if (!aclExtended) {
            acl_free(acl);
            acl = 0;
        }
    }

    if (acl == 0)
        return QString();

    char *aclString = acl_to_text(acl, 0);
    QString ret = QString::fromLatin1(aclString);
    acl_free((void*)aclString);
    acl_free(acl);

    return ret;
#else
    return QString();
#endif
}

void normal_vfs::vfs_slotRefresh()
{
    KConfigGroup group(krConfig, "Advanced");
    int maxRefreshFrequency = group.readEntry("Max Refresh Frequency", 1000);
    vfs_refresh();
    disconnect(&refreshTimer, SIGNAL(timeout()), this, SLOT(vfs_slotRefresh()));
    refreshTimer.setSingleShot(true);
    refreshTimer.start(maxRefreshFrequency);
}

bool normal_vfs::burstRefresh(const QString& path)
{
    QString parentPath = path;
    int ndx = path.lastIndexOf(DIR_SEPARATOR);
    if (ndx >= 0)
        parentPath = path.left(ndx == 0 ? 1 : ndx);

    if (path == vfs_getOrigin().adjusted(QUrl::StripTrailingSlash).path() ||
            parentPath == vfs_getOrigin().adjusted(QUrl::StripTrailingSlash).path()) {
        if (!refreshTimer.isActive()) {
            // the directory itself is dirty - full refresh is needed
            QTimer::singleShot(0, this, SLOT(vfs_slotRefresh()));    // safety: dirty signal comes from KDirWatch!
            return true;
        }
        disconnect(&refreshTimer, SIGNAL(timeout()), this, SLOT(vfs_slotRefresh()));
        connect(&refreshTimer, SIGNAL(timeout()), this, SLOT(vfs_slotRefresh()));
        postponedRefreshURL = QUrl::fromLocalFile(path);
        return true;
    }
    return false;
}

void normal_vfs::vfs_slotDirty(const QString& path)
{
    if (disableRefresh) {
        if (postponedRefreshURL.isEmpty())
            postponedRefreshURL = vfs_getOrigin();
        return;
    }

    if (burstRefresh(path))
        return;

    QUrl url = QUrl::fromLocalFile(path);
    QString name = url.fileName();

    if (name.left(1) == "." && !vfs_showHidden())
        return;

    // do we have it already ?
    vfile * vf = vfs_search(name);
    if (!vf) return vfs_slotCreated(path);

    // we have an updated file..
    vfile *newVf = vfileFromName(name, 0);
    *vf = *newVf;
    delete newVf;
    emit updatedVfile(vf);
}

void normal_vfs::vfs_slotCreated(const QString& path)
{
    if (disableRefresh) {
        if (postponedRefreshURL.isEmpty())
            postponedRefreshURL = vfs_getOrigin();
        return;
    }

    if (burstRefresh(path))
        return;

    QUrl url = QUrl::fromLocalFile(path);
    QString name = url.fileName();

    if (name.left(1) == "." && !vfs_showHidden())
        return;

    // if it's in the CVS - it's an update not new file
    if (vfs_search(name))
        return vfs_slotDirty(path);

    vfile* vf = vfileFromName(name, 0);
    addToList(vf);
    emit addedVfile(vf);
}

void normal_vfs::vfs_slotDeleted(const QString& path)
{
    if (disableRefresh) {
        if (postponedRefreshURL.isEmpty())
            postponedRefreshURL = vfs_getOrigin();
        return;
    }

    if (burstRefresh(path))
        return;


    QUrl url = QUrl::fromLocalFile(path);
    QString name = url.fileName();

    // if it's not in the CVS - do nothing
    vfile *vf = vfs_search(name);
    if (vf) {
        emit deletedVfile(name);
        removeFromList(name);
        delete vf;
    }
}

