/*
 * ark -- archiver for the KDE project
 *
 * Copyright (C) 2007 Henrique Pinto <henrique.pinto@kdemail.net>
 * Copyright (C) 2008-2009 Harald Hvaal <haraldhv@stud.ntnu.no>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */
#include "archivemodel.h"
#include "kerfuffle/archive.h"
#include "kerfuffle/jobs.h"

#include <QList>
#include <QPixmap>
#include <QFont>
#include <QMimeData>
#include <QDir>
#include <QtDBus/QtDBus>
#include <QLatin1String>
#include <QPersistentModelIndex>

#include <KDebug>
#include <KLocale>
#include <KMimeType>
#include <KIconLoader>
#include <KIO/NetAccess>

using namespace Kerfuffle;

class ArchiveDirNode;

//used to speed up the loading of large archives
static ArchiveNode* previousMatch = NULL;
static QStringList previousPieces;
static int currentSortColumn = 0;


class ArchiveNode
{
public:
    ArchiveNode(ArchiveDirNode *parent, const ArchiveEntry & entry)
            : m_parent(parent) {
        setEntry(entry);
    }

    virtual ~ArchiveNode() {}

    static bool compareAscending(const QPair<ArchiveNode*,int> &a, const QPair<ArchiveNode*,int> &b) {
        // #234373: sort folders before files
        if (a.first->isDir() != b.first->isDir())
            return a.first->isDir();

        if (currentSortColumn == FileName)
            return (a.first->m_name < b.first->m_name);
        else if ((currentSortColumn == Size) ||
                 (currentSortColumn == CompressedSize))
            return (a.first->entry()[currentSortColumn].toInt() < b.first->entry()[currentSortColumn].toInt());
        else
            return (a.first->entry()[currentSortColumn].toString() < b.first->entry()[currentSortColumn].toString());
    }

    static bool compareDescending(const QPair<ArchiveNode*,int> &a, const QPair<ArchiveNode*,int> &b) {
        // #234373: sort folders before files
        if (a.first->isDir() != b.first->isDir())
            return !(a.first->isDir());

        if (currentSortColumn == FileName)
            return (a.first->m_name > b.first->m_name);
        else if ((currentSortColumn == Size) ||
                 (currentSortColumn == CompressedSize))
            return (a.first->entry()[currentSortColumn].toInt() > b.first->entry()[currentSortColumn].toInt());
        else
            return (a.first->entry()[currentSortColumn].toString() > b.first->entry()[currentSortColumn].toString());
    }

    const ArchiveEntry &entry() const {
        return m_entry;
    }
    void setEntry(const ArchiveEntry & entry) {
        m_entry = entry;
        QStringList pieces = entry[ FileName ].toString().split('/', QString::SkipEmptyParts);
        m_name = pieces.isEmpty() ? QString() : pieces.last();
    }

    ArchiveDirNode *parent() const {
        return m_parent;
    }

    int row();

    virtual bool isDir() const {
        return false;
    }

    QPixmap icon() {
        if (m_icon.isNull()) {
            KMimeType::Ptr mimeType = KMimeType::findByPath(m_entry[ FileName ].toString(), 0, true);
            m_icon = KIconLoader::global()->loadMimeTypeIcon(mimeType->iconName(), KIconLoader::Small);
        }
        return m_icon;
    }
    QString         m_name;

protected:
    QPixmap         m_icon;

private:
    ArchiveEntry    m_entry;
    ArchiveDirNode *m_parent;
};


class ArchiveDirNode: public ArchiveNode
{
public:
    ArchiveDirNode(ArchiveDirNode *parent, const ArchiveEntry & entry)
            : ArchiveNode(parent, entry) {
        m_icon = KIconLoader::global()->loadMimeTypeIcon(KMimeType::mimeType("inode/directory")->iconName(), KIconLoader::Small);
    }


    ~ArchiveDirNode() {
        clear();
    }

    QList<ArchiveNode*>& entries() {
        return m_entries;
    }

    virtual bool isDir() const {
        return true;
    }

    ArchiveNode* find(const QString & name) {
        foreach(ArchiveNode *node, m_entries) {
            if (node && (node->m_name == name)) {
                return node;
            }
        }
        return 0;
    }

    ArchiveNode* findByPath(const QStringList & pieces, int index = 0) {

        if (index == pieces.count()) {
            return 0;
        }

        ArchiveNode *next = find(pieces.at(index));

        if (index == pieces.count() - 1) {
            return next;
        }
        if (next && next->isDir()) {
            return static_cast<ArchiveDirNode*>(next)->findByPath(pieces, index + 1);
        }
        return 0;
    }

    void returnDirNodes(QList<ArchiveDirNode*> *store) {
        foreach(ArchiveNode *node, m_entries) {
            if (node->isDir()) {
                store->prepend(static_cast<ArchiveDirNode*>(node));
                static_cast<ArchiveDirNode*>(node)->returnDirNodes(store);
            }
        }
    }

    void clear() {
        qDeleteAll(m_entries);
        m_entries.clear();
    }

private:
    QList<ArchiveNode*> m_entries;
};

int ArchiveNode::row()
{
    if (parent()) {
        return parent()->entries().indexOf(const_cast<ArchiveNode*>(this));
    }
    return 0;
}

ArchiveModel::ArchiveModel(QObject *parent)
        : QAbstractItemModel(parent), m_archive(0),
        m_rootNode(new ArchiveDirNode(0, ArchiveEntry()))
{
}

ArchiveModel::~ArchiveModel()
{
    delete m_archive;
    m_archive = 0;

    delete m_rootNode;
    m_rootNode = 0;
}

QVariant ArchiveModel::data(const QModelIndex &index, int role) const
{
    if (index.isValid()) {
        ArchiveNode *node = static_cast<ArchiveNode*>(index.internalPointer());
        switch (role) {
        case Qt::DisplayRole: {
            //TODO: complete the columns
            int columnId = m_showColumns.at(index.column());
            switch (columnId) {
            case FileName:
                return node->m_name;
            case Size:
                if (node->isDir() || node->entry().contains(Link)) {
                    return QVariant();
                } else {
                    return KIO::convertSize(node->entry()[ Size ].toULongLong());
                }
            case CompressedSize:
                if (node->isDir() || node->entry().contains(Link)) {
                    return QVariant();
                } else {
                    qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong();
                    if (compressedSize != 0) {
                        return KIO::convertSize(compressedSize);
                    } else {
                        return QVariant();
                    }
                }
            case Ratio:
                if (node->isDir() || node->entry().contains(Link)) {
                    return QVariant();
                } else {
                    qulonglong compressedSize = node->entry()[ CompressedSize ].toULongLong();
                    qulonglong size = node->entry()[ Size ].toULongLong();
                    if (compressedSize == 0 || size == 0) {
                        return QVariant();
                    } else {
                        int ratio = int(100 * ((double)size - compressedSize) / size);
                        return QString::number(ratio) + " %";
                    }
                }

            case Timestamp: {
                const QDateTime timeStamp = node->entry().value(Timestamp).toDateTime();
                return  KGlobal::locale()->formatDateTime(timeStamp);
            }

            default:
                return node->entry().value(columnId);
            }
            break;
        }
        case Qt::DecorationRole:
            if (index.column() == 0) {
                return node->icon();
            }
            return QVariant();
        case Qt::FontRole: {
            QFont f;
            f.setItalic(node->entry()[ IsPasswordProtected ].toBool());
            return f;
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}

Qt::ItemFlags ArchiveModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index);

    if (index.isValid()) {
        return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | defaultFlags;
    }

    return 0;
}

QVariant ArchiveModel::headerData(int section, Qt::Orientation, int role) const
{
    if (role == Qt::DisplayRole) {
        if (section >= m_showColumns.size()) {
            kDebug() << "WEIRD: showColumns.size = " << m_showColumns.size()
            << " and section = " << section;
            return QVariant();
        }

        int columnId = m_showColumns.at(section);

        switch (columnId) {
        case FileName:
            return i18nc("Name of a file inside an archive", "Name");
        case Size:
            return i18nc("Uncompressed size of a file inside an archive", "Size");
        case CompressedSize:
            return i18nc("Compressed size of a file inside an archive", "Compressed");
        case Ratio:
            return i18nc("Compression rate of file", "Rate");
        case Owner:
            return i18nc("File's owner username", "Owner");
        case Group:
            return i18nc("File's group", "Group");
        case Permissions:
            return i18nc("File permissions", "Mode");
        case CRC:
            return i18nc("CRC hash code", "CRC");
        case Method:
            return i18nc("Compression method", "Method");
        case Version:
            //TODO: what exactly is a file version?
            return i18nc("File version", "Version");
        case Timestamp:
            return i18nc("Timestamp", "Date");
        case Comment:
            return i18nc("File comment", "Comment");
        default:
            return i18nc("Unnamed column", "??");

        }
    }
    return QVariant();
}

QModelIndex ArchiveModel::index(int row, int column, const QModelIndex &parent) const
{
    if (hasIndex(row, column, parent)) {
        ArchiveDirNode *parentNode = parent.isValid() ? static_cast<ArchiveDirNode*>(parent.internalPointer()) : m_rootNode;

        Q_ASSERT(parentNode->isDir());

        ArchiveNode *item = parentNode->entries().value(row, 0);
        if (item) {
            return createIndex(row, column, item);
        }
    }

    return QModelIndex();
}

QModelIndex ArchiveModel::parent(const QModelIndex &index) const
{
    if (index.isValid()) {
        ArchiveNode *item = static_cast<ArchiveNode*>(index.internalPointer());
        Q_ASSERT(item);
        if (item->parent() && (item->parent() != m_rootNode)) {
            return createIndex(item->parent()->row(), 0, item->parent());
        }
    }
    return QModelIndex();
}

ArchiveEntry ArchiveModel::entryForIndex(const QModelIndex &index)
{
    if (index.isValid()) {
        ArchiveNode *item = static_cast<ArchiveNode*>(index.internalPointer());
        Q_ASSERT(item);
        return item->entry();
    }
    return ArchiveEntry();
}

int ArchiveModel::childCount(const QModelIndex &index)
{
    if (index.isValid()) {
        ArchiveNode *item = static_cast<ArchiveNode*>(index.internalPointer());
        Q_ASSERT(item);
        if (item->isDir()) {
            return static_cast<ArchiveDirNode*>(item)->entries().count();
        }
        return 0;
    }
    return -1;
}

int ArchiveModel::rowCount(const QModelIndex &parent) const
{
    if (parent.column() <= 0) {
        ArchiveNode *parentNode = parent.isValid() ? static_cast<ArchiveNode*>(parent.internalPointer()) : m_rootNode;

        if (parentNode && parentNode->isDir()) {
            return static_cast<ArchiveDirNode*>(parentNode)->entries().count();
        }
    }
    return 0;
}

int ArchiveModel::columnCount(const QModelIndex &parent) const
{
    return m_showColumns.size();
    if (parent.isValid()) {
        return static_cast<ArchiveNode*>(parent.internalPointer())->entry().size();
    }
}

void ArchiveModel::sort(int column, Qt::SortOrder order)
{
    if (m_showColumns.size() <= column)
        return;

    currentSortColumn = m_showColumns.at(column);

    emit layoutAboutToBeChanged();

    QList<ArchiveDirNode*> dirNodes;
    m_rootNode->returnDirNodes(&dirNodes);
    dirNodes.append(m_rootNode);

    foreach(ArchiveDirNode* dir, dirNodes) {
        QVector < QPair<ArchiveNode*,int> > sorting(dir->entries().count());
        for (int i = 0; i < dir->entries().count(); ++i) {
            ArchiveNode *item = dir->entries().at(i);
            sorting[i].first = item;
            sorting[i].second = i;
        }

        if (order == Qt::AscendingOrder)
            qSort(sorting.begin(), sorting.end(), ArchiveNode::compareAscending);
        else
            qSort(sorting.begin(), sorting.end(), ArchiveNode::compareDescending);

        QModelIndexList fromIndexes;
        QModelIndexList toIndexes;
        for (int r = 0; r < sorting.count(); ++r) {
            ArchiveNode *item = sorting.at(r).first;
            toIndexes.append(createIndex(r, 0, item));
            fromIndexes.append(createIndex(sorting.at(r).second, 0, sorting.at(r).first));
            dir->entries()[r] = sorting.at(r).first;
        }

        changePersistentIndexList(fromIndexes, toIndexes);

        emit dataChanged(
            index(0, 0, indexForNode(dir)),
            index(dir->entries().size() - 1, 0, indexForNode(dir)));
    }

    emit layoutChanged();
}

Qt::DropActions ArchiveModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

QStringList ArchiveModel::mimeTypes() const
{
    QStringList types;

    types << QString("text/uri-list")
    << QString("text/plain")
    << QString("text/x-moz-url")
    << QString("application/x-kde-urilist")
    << QString("application/x-kde-extractdrag");

    return types;
}

QMimeData * ArchiveModel::mimeData(const QModelIndexList & indexes) const
{
    kDebug() ;
    //prepare the fallback kio_slave filenames
    QStringList files;
    bool noFallback = false;

    QString archiveName = m_archive->fileName();
    QString ext = QFileInfo(archiveName).suffix().toUpper();
    if (ext == "TAR") {
        archiveName.prepend("tar:");
    } else if (ext == "ZIP") {
        archiveName.prepend("zip:");
    } else if (archiveName.right(6).toUpper() == "TAR.GZ") {
        archiveName.prepend("tar:");
    } else
        noFallback = true;

    if (!archiveName.endsWith('/')) {
        archiveName.append('/');
    }

    //Populate the internal list of files
    foreach(const QModelIndex &index, indexes) {

        //to limit only one index per row
        if (index.column() != 0) continue;

        const QString file = archiveName + static_cast<ArchiveNode*>(index.internalPointer())->entry()[ FileName ].toString();
        files << file;
    }

    KUrl::List kiolist(files);

    //prepare the dbus-based drag/drop mimedata
    QMimeData *data = new QMimeData();
    data->setData("application/x-kde-dndextract",
                  QDBusConnection::sessionBus().baseService().toUtf8()
                 );


    if (!noFallback)
        kiolist.populateMimeData(data);
    return data;
}

bool ArchiveModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
    Q_UNUSED(action)
    Q_UNUSED(row)
    Q_UNUSED(column)
    Q_UNUSED(parent)

    if (!data->hasUrls())
        return false;

    QStringList paths;
    foreach(const QUrl &url, data->urls()) {
        paths << url.toLocalFile();
    }

    //for now, this code is not used because adding files to paths inside the
    //archive is not supported yet. need a solution for this later.
    QString path;
#if 0
    if (parent.isValid()) {
        QModelIndex droppedOnto = index(row, column, parent);
        if (entryForIndex(droppedOnto).value(IsDirectory).toBool()) {
            kDebug() << "Using entry";
            path = entryForIndex(droppedOnto).value(FileName).toString();
        } else {
            path = entryForIndex(parent).value(FileName).toString();
        }
    }

    kDebug() << "Dropped onto " << path;

#endif

    emit droppedFiles(paths, path);

    return true;
}

// For a rationale, see bug #194241
QString ArchiveModel::cleanFileName(const QString& fileName)
{
    QString cleanName(fileName);

    if (cleanName == ".") { // "." is present in ISO files
        cleanName.clear();
    } else if (cleanName.startsWith(QLatin1String("./"))) {
        cleanName.remove(0, 2);
    }

    return cleanName;
}

ArchiveDirNode* ArchiveModel::parentFor(const ArchiveEntry& entry)
{
    QStringList pieces = entry[ FileName ].toString().split('/', QString::SkipEmptyParts);
    if (pieces.isEmpty()) return NULL;
    pieces.removeLast();

    if (previousMatch) {

        //the number of path elements must be the same for the shortcut
        //to work
        if (previousPieces.count() == pieces.count()) {

            bool equal = true;

            //make sure all the pieces match up
            for (int i = 0; i < previousPieces.count(); ++i) {
                if (previousPieces.at(i) != pieces.at(i)) {
                    equal = false;
                    break;
                }
            }

            //if match return it
            if (equal) {
                return static_cast<ArchiveDirNode*>(previousMatch);

            }
        }

    }

    ArchiveDirNode *parent = m_rootNode;

    foreach(const QString &piece, pieces) {
        ArchiveNode *node = parent->find(piece);
        if (!node) {
            ArchiveEntry e;
            e[ FileName ] = (parent == m_rootNode) ?
                            piece : parent->entry()[ FileName ].toString() + '/' + piece;
            e[ IsDirectory ] = true;
            node = new ArchiveDirNode(parent, e);
            insertNode(node);
        }
        if (!node->isDir()) {
            ArchiveEntry e(node->entry());
            node = new ArchiveDirNode(parent, e);
            //Maybe we have both a file and a directory of the same name
            // We avoid removing previous entries unless necessary
            insertNode(node);
        }
        parent = static_cast<ArchiveDirNode*>(node);
    }

    previousMatch = parent;
    previousPieces = pieces;

    return parent;
}
QModelIndex ArchiveModel::indexForNode(ArchiveNode *node)
{
    Q_ASSERT(node);
    if (node != m_rootNode) {
        Q_ASSERT(node->parent());
        Q_ASSERT(node->parent()->isDir());
        return createIndex(node->row(), 0, node);
    }
    return QModelIndex();
}

void ArchiveModel::slotEntryRemoved(const QString & path)
{
    kDebug() << "Removed node at path " << path;

    const QString entryFileName(cleanFileName(path));
    if (entryFileName.isEmpty()) {
        return;
    }

    ArchiveNode *entry = m_rootNode->findByPath(entryFileName.split('/', QString::SkipEmptyParts));
    if (entry) {
        ArchiveDirNode *parent = entry->parent();
        QModelIndex index = indexForNode(entry);

        beginRemoveRows(indexForNode(parent), entry->row(), entry->row());

        //delete parent->entries()[ entry->row() ];
        //parent->entries()[ entry->row() ] = 0;
        delete parent->entries().takeAt(entry->row());

        endRemoveRows();
    } else {
        kDebug() << "Did not find the removed node";
    }
}

void ArchiveModel::slotUserQuery(Query *query)
{
    query->execute();
}

void ArchiveModel::slotNewEntryFromSetArchive(const ArchiveEntry& entry)
{
    // we cache all entries that appear when opening a new archive
    // so we can all them together once it's done, this is a huge
    // performance improvement because we save from doing lots of
    // begin/endInsertRows
    m_newArchiveEntries.push_back(entry);
}

void ArchiveModel::slotNewEntry(const ArchiveEntry& entry)
{
    newEntry(entry, NotifyViews);
}

void ArchiveModel::newEntry(const ArchiveEntry& receivedEntry, InsertBehaviour behaviour)
{
    if (receivedEntry[FileName].toString().isEmpty()) {
        kDebug() << "Weird, received empty entry (no filename) - skipping";
        return;
    }

    //if there are no addidional columns registered, then have a look at the
    //entry and populate some
    if (m_showColumns.isEmpty()) {

        //these are the columns we are interested in showing in the display
        static const QList<int> columnsForDisplay =
            QList<int>()
            << FileName
            << Size
            << CompressedSize
            << Permissions
            << Owner
            << Group
            << Ratio
            << CRC
            << Method
            << Version
            << Timestamp
            << Comment;

        QList<int> toInsert;

        foreach(int column, columnsForDisplay) {
            if (receivedEntry.contains(column))
                toInsert << column;
        }
        beginInsertColumns(QModelIndex(), 0, toInsert.size() - 1);
        m_showColumns << toInsert;
        endInsertColumns();

        kDebug() << "Show columns detected: " << m_showColumns;

    }

    //make a copy
    ArchiveEntry entry = receivedEntry;

    //#194241: Filenames such as "./file" should be displayed as "file"
    QString entryFileName = cleanFileName(entry[FileName].toString());
    if (entryFileName.isEmpty()) { // The entry contains only "." or "./"
        return;
    }
    entry[FileName] = entryFileName;

    /// 1. Skip already created nodes
    if (m_rootNode) {
        ArchiveNode *existing = m_rootNode->findByPath(entry[ FileName ].toString().split('/'));
        if (existing) {
            kDebug() << "Refreshing entry for" << entry[FileName].toString();
            //TODO: benchmark whether it's a bad idea to reset the entry here.
            existing->setEntry(entry);
            return;
        }
    }

    /// 2. Find Parent Node, creating missing ArchiveDirNodes in the process
    ArchiveDirNode *parent = parentFor(entry);

    /// 3. Create an ArchiveNode
    QString name = entry[ FileName ].toString().split('/', QString::SkipEmptyParts).last();
    ArchiveNode *node = parent->find(name);
    if (node) {
        node->setEntry(entry);
    } else {
        if (entry[ FileName ].toString().endsWith('/') || (entry.contains(IsDirectory) && entry[ IsDirectory ].toBool())) {
            node = new ArchiveDirNode(parent, entry);
        } else {
            node = new ArchiveNode(parent, entry);
        }
        insertNode(node, behaviour);
    }
}

void ArchiveModel::slotLoadingFinished(KJob *job)
{
    //kDebug() << entry;
    foreach(const ArchiveEntry &entry, m_newArchiveEntries) {
        newEntry(entry, DoNotNotifyViews);
    }
    reset();
    m_newArchiveEntries.clear();

    emit loadingFinished(job);
}

void ArchiveModel::insertNode(ArchiveNode *node, InsertBehaviour behaviour)
{
    Q_ASSERT(node);
    ArchiveDirNode *parent = node->parent();
    Q_ASSERT(parent);
    if (behaviour == NotifyViews) beginInsertRows(indexForNode(parent), parent->entries().count(), parent->entries().count());
    parent->entries().append(node);
    if (behaviour == NotifyViews) endInsertRows();
}

Kerfuffle::Archive* ArchiveModel::archive() const
{
    return m_archive;
}

KJob* ArchiveModel::setArchive(Kerfuffle::Archive *archive)
{
    delete m_archive;
    m_archive = archive;
    m_rootNode->clear();
    previousMatch = 0;
    previousPieces = QStringList();


    Kerfuffle::ListJob *job = NULL;

    m_newArchiveEntries.clear();
    if (m_archive) {
        job = m_archive->list(); // TODO: call "open" or "create"?

        connect(job, SIGNAL(newEntry(const ArchiveEntry&)),
                this, SLOT(slotNewEntryFromSetArchive(const ArchiveEntry&)));

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

        connect(job, SIGNAL(userQuery(Query*)), this, SLOT(slotUserQuery(Query*)));

        emit loadingStarted();

        // TODO: make sure if it's ok to not have calls to beginRemoveColumns here
        m_showColumns.clear();
    }
    reset();
    return job;
}

ExtractJob* ArchiveModel::extractFile(const QVariant& fileName, const QString & destinationDir, const Kerfuffle::ExtractionOptions options) const
{
    QList<QVariant> files;
    files << fileName;
    return extractFiles(files, destinationDir, options);
}

ExtractJob* ArchiveModel::extractFiles(const QList<QVariant>& files, const QString & destinationDir, const Kerfuffle::ExtractionOptions options) const
{
    Q_ASSERT(m_archive);
    ExtractJob *newJob = m_archive->copyFiles(files, destinationDir, options);
    connect(newJob, SIGNAL(userQuery(Query*)),
            this, SLOT(slotUserQuery(Query*)));
    return newJob;
}

AddJob* ArchiveModel::addFiles(const QStringList & filenames, const CompressionOptions& options)
{
    if (!m_archive) return NULL;

    if (!m_archive->isReadOnly()) {
        AddJob *job = m_archive->addFiles(filenames, options);
        connect(job, SIGNAL(newEntry(const ArchiveEntry&)),
                this, SLOT(slotNewEntry(const ArchiveEntry&)));
        connect(job, SIGNAL(userQuery(Query*)),
                this, SLOT(slotUserQuery(Query*)));


        return job;
    }
    return 0;
}

DeleteJob* ArchiveModel::deleteFiles(const QList<QVariant> & files)
{
    Q_ASSERT(m_archive);
    if (!m_archive->isReadOnly()) {
        DeleteJob *job = m_archive->deleteFiles(files);
        connect(job, SIGNAL(entryRemoved(const QString &)),
                this, SLOT(slotEntryRemoved(const QString &)));

        connect(job, SIGNAL(finished(KJob*)),
                this, SLOT(slotCleanupEmptyDirs()));

        connect(job, SIGNAL(userQuery(Query*)),
                this, SLOT(slotUserQuery(Query*)));
        return job;
    }
    return 0;
}

void ArchiveModel::slotCleanupEmptyDirs()
{
    kDebug();
    QList<QPersistentModelIndex> queue;
    QList<QPersistentModelIndex> nodesToDelete;

    //add root nodes
    for (int i = 0; i < rowCount(); ++i) {
        queue.append(QPersistentModelIndex(index(i, 0)));
    }

    //breadth-first traverse
    while (!queue.isEmpty()) {
        QPersistentModelIndex node = queue.takeFirst();
        ArchiveEntry entry = entryForIndex(node);
        //kDebug() << "Trying " << entry[FileName].toString();

        if (!hasChildren(node)) {
            if (!entry.contains(InternalID)) {
                nodesToDelete << node;
            }
        } else {
            for (int i = 0; i < rowCount(node); ++i) {
                queue.append(QPersistentModelIndex(index(i, 0, node)));
            }
        }
    }

    foreach(const QPersistentModelIndex& node, nodesToDelete) {
        ArchiveNode *rawNode = static_cast<ArchiveNode*>(node.internalPointer());
        kDebug() << "Delete with parent entries " << rawNode->parent()->entries() << " and row " << rawNode->row();
        beginRemoveRows(parent(node), rawNode->row(), rawNode->row());
        delete rawNode->parent()->entries().takeAt(rawNode->row());
        endRemoveRows();
        //kDebug() << "Removed entry " << entry[FileName].toString();
    }
}

#include "archivemodel.moc"
