<commit_before>/*
 * Copyright (C) 2012 - 2015  <NAME> (PCMan) <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */


#include "foldermodel.h"
#include "icontheme.h"
#include <iostream>
#include <algorithm>
#include <QtAlgorithms>
#include <QVector>
#include <qmimedata.h>
#include <QMimeData>
#include <QByteArray>
#include <QPixmap>
#include <QPainter>
#include <QTimer>
#include "utilities.h"
#include "fileoperation.h"

namespace Fm {

FolderModel::FolderModel():
    hasPendingThumbnailHandler_{false} {
}

FolderModel::~FolderModel() {
    qDebug("delete FolderModel");
    // if the thumbnail requests list is not empty, cancel them
    for(auto job: pendingThumbnailJobs_) {
        job->cancel();
    }
}

void FolderModel::setFolder(const std::shared_ptr<Fm::Folder>& new_folder) {
    if(folder_) {
        removeAll();        // remove old items
    }
    if(new_folder) {
        folder_ = new_folder;
        connect(folder_.get(), &Fm::Folder::startLoading, this, &FolderModel::onStartLoading);
        connect(folder_.get(), &Fm::Folder::finishLoading, this, &FolderModel::onFinishLoading);
        connect(folder_.get(), &Fm::Folder::filesAdded, this, &FolderModel::onFilesAdded);
        connect(folder_.get(), &Fm::Folder::filesChanged, this, &FolderModel::onFilesChanged);
        connect(folder_.get(), &Fm::Folder::filesRemoved, this, &FolderModel::onFilesRemoved);
        // handle the case if the folder is already loaded
        if(folder_->isLoaded()) {
            insertFiles(0, folder_->files());
        }
    }
}

void FolderModel::onStartLoading() {
    // remove all items
    removeAll();
}

void FolderModel::onFinishLoading() {
}

void FolderModel::onFilesAdded(const Fm::FileInfoList& files) {
    int n_files = files.size();
    beginInsertRows(QModelIndex(), items.count(), items.count() + n_files - 1);
    for(auto& info : files) {
        FolderModelItem item(info);
        /*
            if(fm_file_info_is_hidden(info)) {
              model->hiddenItems.append(item);
              continue;
            }
        */
        items.append(item);
    }
    endInsertRows();
}

void FolderModel::onFilesChanged(std::vector<Fm::FileInfoPair>& files) {
    for(auto& change : files) {
        int row;
        auto& oldInfo = change.first;
        auto& newInfo = change.second;
        QList<FolderModelItem>::iterator it = findItemByFileInfo(oldInfo.get(), &row);
        if(it != items.end()) {
            FolderModelItem& item = *it;
            // try to update the item
            item.info = newInfo;
            item.thumbnails.clear();
            QModelIndex index = createIndex(row, 0, &item);
            Q_EMIT dataChanged(index, index);
            if(oldInfo->size() != newInfo->size()) {
                Q_EMIT fileSizeChanged(index);
            }
        }
    }
}

void FolderModel::onFilesRemoved(const Fm::FileInfoList& files) {
    for(auto& info : files) {
        int row;
        QList<FolderModelItem>::iterator it = findItemByName(info->name().c_str(), &row);
        if(it != items.end()) {
            beginRemoveRows(QModelIndex(), row, row);
            items.erase(it);
            endRemoveRows();
        }
    }
}

void FolderModel::loadPendingThumbnails() {
    hasPendingThumbnailHandler_ = false;
    for(auto& item: thumbnailData_) {
        if(!item.pendingThumbnails_.empty()) {
            auto job = new Fm::ThumbnailJob(std::move(item.pendingThumbnails_), item.size_);
            pendingThumbnailJobs_.push_back(job);
            job->setAutoDelete(true);
            connect(job, &Fm::ThumbnailJob::thumbnailLoaded, this, &FolderModel::onThumbnailLoaded, Qt::BlockingQueuedConnection);
            connect(job, &Fm::ThumbnailJob::finished, this, &FolderModel::onThumbnailJobFinished, Qt::BlockingQueuedConnection);
            Fm::ThumbnailJob::threadPool()->start(job);
        }
    }
}

void FolderModel::queueLoadThumbnail(const std::shared_ptr<const Fm::FileInfo>& file, int size) {
    auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;});
    if(it != thumbnailData_.end()) {
        it->pendingThumbnails_.push_back(file);
        if(!hasPendingThumbnailHandler_) {
            QTimer::singleShot(0, this, &FolderModel::loadPendingThumbnails);
            hasPendingThumbnailHandler_ = true;
        }
    }
}

void FolderModel::insertFiles(int row, const Fm::FileInfoList& files) {
    int n_files = files.size();
    beginInsertRows(QModelIndex(), row, row + n_files - 1);
    for(auto& info : files) {
        FolderModelItem item(info);
        items.append(item);
    }
    endInsertRows();
}

void FolderModel::removeAll() {
    if(items.empty()) {
        return;
    }
    beginRemoveRows(QModelIndex(), 0, items.size() - 1);
    items.clear();
    endRemoveRows();
}

int FolderModel::rowCount(const QModelIndex& parent) const {
    if(parent.isValid()) {
        return 0;
    }
    return items.size();
}

int FolderModel::columnCount(const QModelIndex& parent = QModelIndex()) const {
    if(parent.isValid()) {
        return 0;
    }
    return NumOfColumns;
}

FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const {
    return reinterpret_cast<FolderModelItem*>(index.internalPointer());
}

std::shared_ptr<const Fm::FileInfo> FolderModel::fileInfoFromIndex(const QModelIndex& index) const {
    FolderModelItem* item = itemFromIndex(index);
    return item ? item->info : nullptr;
}

QVariant FolderModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const {
    if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) {
        return QVariant();
    }
    FolderModelItem* item = itemFromIndex(index);
    auto info = item->info;

    switch(role) {
    case Qt::ToolTipRole:
        return QVariant(item->displayName());
    case Qt::DisplayRole:  {
        switch(index.column()) {
        case ColumnFileName:
            return item->displayName();
        case ColumnFileType:
            return QString(info->mimeType()->desc());
        case ColumnFileMTime:
            return item->displayMtime();
        case ColumnFileSize:
            return item->displaySize();
        case ColumnFileOwner:
            return item->ownerName();
        }
        break;
    }
    case Qt::DecorationRole: {
        if(index.column() == 0) {
            return QVariant(item->icon());
        }
        break;
    }
    case Qt::EditRole: {
        if(index.column() == 0) {
            return QString::fromStdString(info->name());
        }
        break;
    }
    case FileInfoRole:
        return QVariant::fromValue(info);
    case FileIsDirRole:
        return QVariant(info->isDir());
    }
    return QVariant();
}

QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const {
    if(role == Qt::DisplayRole) {
        if(orientation == Qt::Horizontal) {
            QString title;
            switch(section) {
            case ColumnFileName:
                title = tr("Name");
                break;
            case ColumnFileType:
                title = tr("Type");
                break;
            case ColumnFileSize:
                title = tr("Size");
                break;
            case ColumnFileMTime:
                title = tr("Modified");
                break;
            case ColumnFileOwner:
                title = tr("Owner");
                break;
            }
            return QVariant(title);
        }
    }
    return QVariant();
}

QModelIndex FolderModel::index(int row, int column, const QModelIndex& /*parent*/) const {
    if(row < 0 || row >= items.size() || column < 0 || column >= NumOfColumns) {
        return QModelIndex();
    }
    const FolderModelItem& item = items.at(row);
    return createIndex(row, column, (void*)&item);
}

QModelIndex FolderModel::parent(const QModelIndex& /*index*/) const {
    return QModelIndex();
}

Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const {
    // FIXME: should not return same flags unconditionally for all columns
    Qt::ItemFlags flags;
    if(index.isValid()) {
        flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        if(index.column() == ColumnFileName) {
            flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
                      | Qt::ItemIsEditable); // inline renaming);
        }
    }
    else {
        flags = Qt::ItemIsDropEnabled;
    }
    return flags;
}

// FIXME: this is very inefficient and should be replaced with a
// more reasonable implementation later.
QList<FolderModelItem>::iterator FolderModel::findItemByPath(const Fm::FilePath& path, int* row) {
    QList<FolderModelItem>::iterator it = items.begin();
    int i = 0;
    while(it != items.end()) {
        FolderModelItem& item = *it;
        auto item_path = item.info->path();
        if(item_path == path) {
            *row = i;
            return it;
        }
        ++it;
        ++i;
    }
    return items.end();
}

// FIXME: this is very inefficient and should be replaced with a
// more reasonable implementation later.
QList<FolderModelItem>::iterator FolderModel::findItemByName(const char* name, int* row) {
    QList<FolderModelItem>::iterator it = items.begin();
    int i = 0;
    while(it != items.end()) {
        FolderModelItem& item = *it;
        if(item.info->name() == name) {
            *row = i;
            return it;
        }
        ++it;
        ++i;
    }
    return items.end();
}

QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(const Fm::FileInfo* info, int* row) {
    QList<FolderModelItem>::iterator it = items.begin();
    int i = 0;
    while(it != items.end()) {
        FolderModelItem& item = *it;
        if(item.info.get() == info) {
            *row = i;
            return it;
        }
        ++it;
        ++i;
    }
    return items.end();
}

QStringList FolderModel::mimeTypes() const {
    qDebug("FolderModel::mimeTypes");
    QStringList types = QAbstractItemModel::mimeTypes();
    // now types contains "application/x-qabstractitemmodeldatalist"

    // add support for freedesktop Xdnd direct save (XDS) protocol.
    // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2
    // the real implementation is in FolderView::childDropEvent().
    types << "XdndDirectSave0";
    types << "text/uri-list";
    // types << "x-special/gnome-copied-files";
    return types;
}

QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const {
    QMimeData* data = QAbstractItemModel::mimeData(indexes);
    qDebug("FolderModel::mimeData");
    // build a uri list
    QByteArray urilist;
    urilist.reserve(4096);

    for(const auto& index : indexes) {
        FolderModelItem* item = itemFromIndex(index);
        if(item && item->info) {
            auto path = item->info->path();
            if(path.isValid()) {
                auto uri = path.uri();
                urilist.append(uri.get());
                urilist.append('\n');
            }
        }
    }
    data->setData("text/uri-list", urilist);

    return data;
}

bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
    qDebug("FolderModel::dropMimeData");
    if(!folder_) {
        return false;
    }
    Fm::FilePath destPath;<commit_msg>Check nullity of QMimeData (#109)<commit_after>/*
 * Copyright (C) 2012 - 2015  <NAME> (PCMan) <<EMAIL>>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */


#include "foldermodel.h"
#include "icontheme.h"
#include <iostream>
#include <algorithm>
#include <QtAlgorithms>
#include <QVector>
#include <qmimedata.h>
#include <QMimeData>
#include <QByteArray>
#include <QPixmap>
#include <QPainter>
#include <QTimer>
#include "utilities.h"
#include "fileoperation.h"

namespace Fm {

FolderModel::FolderModel():
    hasPendingThumbnailHandler_{false} {
}

FolderModel::~FolderModel() {
    qDebug("delete FolderModel");
    // if the thumbnail requests list is not empty, cancel them
    for(auto job: pendingThumbnailJobs_) {
        job->cancel();
    }
}

void FolderModel::setFolder(const std::shared_ptr<Fm::Folder>& new_folder) {
    if(folder_) {
        removeAll();        // remove old items
    }
    if(new_folder) {
        folder_ = new_folder;
        connect(folder_.get(), &Fm::Folder::startLoading, this, &FolderModel::onStartLoading);
        connect(folder_.get(), &Fm::Folder::finishLoading, this, &FolderModel::onFinishLoading);
        connect(folder_.get(), &Fm::Folder::filesAdded, this, &FolderModel::onFilesAdded);
        connect(folder_.get(), &Fm::Folder::filesChanged, this, &FolderModel::onFilesChanged);
        connect(folder_.get(), &Fm::Folder::filesRemoved, this, &FolderModel::onFilesRemoved);
        // handle the case if the folder is already loaded
        if(folder_->isLoaded()) {
            insertFiles(0, folder_->files());
        }
    }
}

void FolderModel::onStartLoading() {
    // remove all items
    removeAll();
}

void FolderModel::onFinishLoading() {
}

void FolderModel::onFilesAdded(const Fm::FileInfoList& files) {
    int n_files = files.size();
    beginInsertRows(QModelIndex(), items.count(), items.count() + n_files - 1);
    for(auto& info : files) {
        FolderModelItem item(info);
        /*
            if(fm_file_info_is_hidden(info)) {
              model->hiddenItems.append(item);
              continue;
            }
        */
        items.append(item);
    }
    endInsertRows();
}

void FolderModel::onFilesChanged(std::vector<Fm::FileInfoPair>& files) {
    for(auto& change : files) {
        int row;
        auto& oldInfo = change.first;
        auto& newInfo = change.second;
        QList<FolderModelItem>::iterator it = findItemByFileInfo(oldInfo.get(), &row);
        if(it != items.end()) {
            FolderModelItem& item = *it;
            // try to update the item
            item.info = newInfo;
            item.thumbnails.clear();
            QModelIndex index = createIndex(row, 0, &item);
            Q_EMIT dataChanged(index, index);
            if(oldInfo->size() != newInfo->size()) {
                Q_EMIT fileSizeChanged(index);
            }
        }
    }
}

void FolderModel::onFilesRemoved(const Fm::FileInfoList& files) {
    for(auto& info : files) {
        int row;
        QList<FolderModelItem>::iterator it = findItemByName(info->name().c_str(), &row);
        if(it != items.end()) {
            beginRemoveRows(QModelIndex(), row, row);
            items.erase(it);
            endRemoveRows();
        }
    }
}

void FolderModel::loadPendingThumbnails() {
    hasPendingThumbnailHandler_ = false;
    for(auto& item: thumbnailData_) {
        if(!item.pendingThumbnails_.empty()) {
            auto job = new Fm::ThumbnailJob(std::move(item.pendingThumbnails_), item.size_);
            pendingThumbnailJobs_.push_back(job);
            job->setAutoDelete(true);
            connect(job, &Fm::ThumbnailJob::thumbnailLoaded, this, &FolderModel::onThumbnailLoaded, Qt::BlockingQueuedConnection);
            connect(job, &Fm::ThumbnailJob::finished, this, &FolderModel::onThumbnailJobFinished, Qt::BlockingQueuedConnection);
            Fm::ThumbnailJob::threadPool()->start(job);
        }
    }
}

void FolderModel::queueLoadThumbnail(const std::shared_ptr<const Fm::FileInfo>& file, int size) {
    auto it = std::find_if(thumbnailData_.begin(), thumbnailData_.end(), [size](ThumbnailData& item){return item.size_ == size;});
    if(it != thumbnailData_.end()) {
        it->pendingThumbnails_.push_back(file);
        if(!hasPendingThumbnailHandler_) {
            QTimer::singleShot(0, this, &FolderModel::loadPendingThumbnails);
            hasPendingThumbnailHandler_ = true;
        }
    }
}

void FolderModel::insertFiles(int row, const Fm::FileInfoList& files) {
    int n_files = files.size();
    beginInsertRows(QModelIndex(), row, row + n_files - 1);
    for(auto& info : files) {
        FolderModelItem item(info);
        items.append(item);
    }
    endInsertRows();
}

void FolderModel::removeAll() {
    if(items.empty()) {
        return;
    }
    beginRemoveRows(QModelIndex(), 0, items.size() - 1);
    items.clear();
    endRemoveRows();
}

int FolderModel::rowCount(const QModelIndex& parent) const {
    if(parent.isValid()) {
        return 0;
    }
    return items.size();
}

int FolderModel::columnCount(const QModelIndex& parent = QModelIndex()) const {
    if(parent.isValid()) {
        return 0;
    }
    return NumOfColumns;
}

FolderModelItem* FolderModel::itemFromIndex(const QModelIndex& index) const {
    return reinterpret_cast<FolderModelItem*>(index.internalPointer());
}

std::shared_ptr<const Fm::FileInfo> FolderModel::fileInfoFromIndex(const QModelIndex& index) const {
    FolderModelItem* item = itemFromIndex(index);
    return item ? item->info : nullptr;
}

QVariant FolderModel::data(const QModelIndex& index, int role/* = Qt::DisplayRole*/) const {
    if(!index.isValid() || index.row() > items.size() || index.column() >= NumOfColumns) {
        return QVariant();
    }
    FolderModelItem* item = itemFromIndex(index);
    auto info = item->info;

    switch(role) {
    case Qt::ToolTipRole:
        return QVariant(item->displayName());
    case Qt::DisplayRole:  {
        switch(index.column()) {
        case ColumnFileName:
            return item->displayName();
        case ColumnFileType:
            return QString(info->mimeType()->desc());
        case ColumnFileMTime:
            return item->displayMtime();
        case ColumnFileSize:
            return item->displaySize();
        case ColumnFileOwner:
            return item->ownerName();
        }
        break;
    }
    case Qt::DecorationRole: {
        if(index.column() == 0) {
            return QVariant(item->icon());
        }
        break;
    }
    case Qt::EditRole: {
        if(index.column() == 0) {
            return QString::fromStdString(info->name());
        }
        break;
    }
    case FileInfoRole:
        return QVariant::fromValue(info);
    case FileIsDirRole:
        return QVariant(info->isDir());
    }
    return QVariant();
}

QVariant FolderModel::headerData(int section, Qt::Orientation orientation, int role/* = Qt::DisplayRole*/) const {
    if(role == Qt::DisplayRole) {
        if(orientation == Qt::Horizontal) {
            QString title;
            switch(section) {
            case ColumnFileName:
                title = tr("Name");
                break;
            case ColumnFileType:
                title = tr("Type");
                break;
            case ColumnFileSize:
                title = tr("Size");
                break;
            case ColumnFileMTime:
                title = tr("Modified");
                break;
            case ColumnFileOwner:
                title = tr("Owner");
                break;
            }
            return QVariant(title);
        }
    }
    return QVariant();
}

QModelIndex FolderModel::index(int row, int column, const QModelIndex& /*parent*/) const {
    if(row < 0 || row >= items.size() || column < 0 || column >= NumOfColumns) {
        return QModelIndex();
    }
    const FolderModelItem& item = items.at(row);
    return createIndex(row, column, (void*)&item);
}

QModelIndex FolderModel::parent(const QModelIndex& /*index*/) const {
    return QModelIndex();
}

Qt::ItemFlags FolderModel::flags(const QModelIndex& index) const {
    // FIXME: should not return same flags unconditionally for all columns
    Qt::ItemFlags flags;
    if(index.isValid()) {
        flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
        if(index.column() == ColumnFileName) {
            flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled
                      | Qt::ItemIsEditable); // inline renaming);
        }
    }
    else {
        flags = Qt::ItemIsDropEnabled;
    }
    return flags;
}

// FIXME: this is very inefficient and should be replaced with a
// more reasonable implementation later.
QList<FolderModelItem>::iterator FolderModel::findItemByPath(const Fm::FilePath& path, int* row) {
    QList<FolderModelItem>::iterator it = items.begin();
    int i = 0;
    while(it != items.end()) {
        FolderModelItem& item = *it;
        auto item_path = item.info->path();
        if(item_path == path) {
            *row = i;
            return it;
        }
        ++it;
        ++i;
    }
    return items.end();
}

// FIXME: this is very inefficient and should be replaced with a
// more reasonable implementation later.
QList<FolderModelItem>::iterator FolderModel::findItemByName(const char* name, int* row) {
    QList<FolderModelItem>::iterator it = items.begin();
    int i = 0;
    while(it != items.end()) {
        FolderModelItem& item = *it;
        if(item.info->name() == name) {
            *row = i;
            return it;
        }
        ++it;
        ++i;
    }
    return items.end();
}

QList< FolderModelItem >::iterator FolderModel::findItemByFileInfo(const Fm::FileInfo* info, int* row) {
    QList<FolderModelItem>::iterator it = items.begin();
    int i = 0;
    while(it != items.end()) {
        FolderModelItem& item = *it;
        if(item.info.get() == info) {
            *row = i;
            return it;
        }
        ++it;
        ++i;
    }
    return items.end();
}

QStringList FolderModel::mimeTypes() const {
    qDebug("FolderModel::mimeTypes");
    QStringList types = QAbstractItemModel::mimeTypes();
    // now types contains "application/x-qabstractitemmodeldatalist"

    // add support for freedesktop Xdnd direct save (XDS) protocol.
    // http://www.freedesktop.org/wiki/Specifications/XDS/#index4h2
    // the real implementation is in FolderView::childDropEvent().
    types << "XdndDirectSave0";
    types << "text/uri-list";
    // types << "x-special/gnome-copied-files";
    return types;
}

QMimeData* FolderModel::mimeData(const QModelIndexList& indexes) const {
    QMimeData* data = QAbstractItemModel::mimeData(indexes);
    qDebug("FolderModel::mimeData");
    // build a uri list
    QByteArray urilist;
    urilist.reserve(4096);

    for(const auto& index : indexes) {
        FolderModelItem* item = itemFromIndex(index);
        if(item && item->info) {
            auto path = item->info->path();
            if(path.isValid()) {
                auto uri = path.uri();
                urilist.append(uri.get());
                urilist.append('\n');
            }
        }
    }
    data->setData("text/uri-list", urilist);

    return data;
}

bool FolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
    qDebug("FolderModel::dropMimeData");
    if(!folder_ || !data) {
        return false;
    }
    Fm::FilePath destPath;
{'fraction_non_alphanumeric': 0.13133934824702345, 'fraction_numerical': 0.003749021546574383, 'mean_word_length': 2.6862566438876234, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 119, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1160140', 'n_tokens_mistral': 7348, 'n_tokens_neox': 7208, 'n_words': 1658}