/*
    SPDX-FileCopyrightText: 2003-2009 Craig Drummond <craig@kde.org>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "FontInst.h"
#include "Fc.h"
#include "Misc.h"
#include "Utils.h"
#include "WritingSystems.h"
#include "fontinstadaptor.h"
#include "kfontinst_debug.h"

#include <KAuth/Action>
#include <KAuth/ActionReply>
#include <KAuth/ExecuteJob>
#include <KAuth/HelperSupport>
#include <QDebug>
#include <QTimer>
#include <fontconfig/fontconfig.h>
#include <kio/global.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

using namespace Qt::StringLiterals;

namespace KFI
{
static void decompose(const QString &name, QString &family, QString &style)
{
    int commaPos = name.lastIndexOf(u',');

    family = -1 == commaPos ? name : name.left(commaPos);
    style = -1 == commaPos ? QString::fromUtf8(KFI_WEIGHT_REGULAR.untranslatedText()) : name.mid(commaPos + 2);
}

static bool isSystem = false;
static Folder theFolders[FontInst::FOLDER_COUNT];
static const int constSystemReconfigured = -1;
static const int constConnectionsTimeout = 30 * 1000;
static const int constFontListTimeout = 10 * 1000;

typedef void (*SignalHandler)(int);

static void registerSignalHandler(SignalHandler handler)
{
    if (!handler) {
        handler = SIG_DFL;
    }

    sigset_t mask;
    sigemptyset(&mask);

#ifdef SIGSEGV
    signal(SIGSEGV, handler);
    sigaddset(&mask, SIGSEGV);
#endif
#ifdef SIGFPE
    signal(SIGFPE, handler);
    sigaddset(&mask, SIGFPE);
#endif
#ifdef SIGILL
    signal(SIGILL, handler);
    sigaddset(&mask, SIGILL);
#endif
#ifdef SIGABRT
    signal(SIGABRT, handler);
    sigaddset(&mask, SIGABRT);
#endif

    sigprocmask(SIG_UNBLOCK, &mask, nullptr);
}

void signalHander(int)
{
    static bool inHandler = false;

    if (!inHandler) {
        inHandler = true;
        theFolders[isSystem ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER].saveDisabled();
        inHandler = false;
    }
}

FontInst::FontInst()
{
    isSystem = Misc::root();
    registerTypes();

    new FontinstAdaptor(this);
    QDBusConnection bus = QDBusConnection::sessionBus();

    // qDebug() << "Connecting to session bus";
    if (!bus.registerService(QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()))) {
        // qDebug() << "Failed to register service!";
        ::exit(-1);
    }
    if (!bus.registerObject(FONTINST_PATH, this)) {
        // qDebug() << "Failed to register object!";
        ::exit(-1);
    }

    registerSignalHandler(signalHander);
    m_connectionsTimer = new QTimer(this);
    m_fontListTimer = new QTimer(this);
    connect(m_connectionsTimer, &QTimer::timeout, this, &FontInst::connectionsTimeout);
    connect(m_fontListTimer, &QTimer::timeout, this, &FontInst::fontListTimeout);
    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);

    for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
        theFolders[i].init(FOLDER_SYS == i, isSystem);
    }

    updateFontList(false);
}

FontInst::~FontInst()
{
    for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
        theFolders[i].saveDisabled();
    }
}

void FontInst::list(int folders, int pid)
{
    // qDebug() << folders << pid;

    m_connections.insert(pid);
    updateFontList(false);
    QList<KFI::Families> fonts;

    for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
        if (0 == folders || folders & (1 << i)) {
            fonts += theFolders[i].list();
        }
    }

    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);
    Q_EMIT fontList(pid, fonts);
}

void FontInst::statFont(const QString &name, int folders, int pid)
{
    // qDebug() << name << folders << pid;

    bool checkSystem = 0 == folders || folders & SYS_MASK || isSystem, checkUser = 0 == folders || (folders & USR_MASK && !isSystem);
    FamilyCont::ConstIterator fam;
    StyleCont::ConstIterator st;

    m_connections.insert(pid);
    if ((checkSystem && findFont(name, FOLDER_SYS, fam, st)) || (checkUser && findFont(name, FOLDER_USER, fam, st, !checkSystem))) {
        Family rv((*fam).name());
        rv.add(*st);
        // qDebug() << "Found font, Q_EMIT details...";
        Q_EMIT fontStat(pid, rv);
    } else {
        // qDebug() << "Font not found, Q_EMIT empty details...";
        Q_EMIT fontStat(pid, Family(name));
    }
}

void FontInst::install(const QString &file, bool createAfm, bool toSystem, int pid, bool checkConfig)
{
    // qDebug() << file << createAfm << toSystem << pid << checkConfig;

    m_connections.insert(pid);

    if (checkConfig) {
        updateFontList();
    }

    EFolder folder = isSystem || toSystem ? FOLDER_SYS : FOLDER_USER;
    Family font;
    Utils::EFileType type = Utils::check(file, font);

    int result = Utils::FILE_BITMAP == type && !FC::bitmapsEnabled() ? STATUS_BITMAPS_DISABLED : Utils::FILE_INVALID == type ? STATUS_NOT_FONT_FILE : STATUS_OK;

    if (STATUS_OK == result) {
        if (Utils::FILE_AFM != type && Utils::FILE_PFM != type) {
            for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT) && STATUS_OK == result; ++i) {
                if (theFolders[i].contains(font.name(), (*font.styles().begin()).value())) {
                    result = STATUS_ALREADY_INSTALLED;
                }
            }
        }

        if (STATUS_OK == result) {
            QString name(Misc::modifyName(Misc::getFile(file))), destFolder(Misc::getDestFolder(theFolders[folder].location(), name));

            result = Utils::FILE_AFM != type && Utils::FILE_PFM != type && Misc::fExists(destFolder + name) ? (int)KIO::ERR_FILE_ALREADY_EXIST : (int)STATUS_OK;
            if (STATUS_OK == result) {
                if (toSystem && !isSystem) {
                    // qDebug() << "Send request to system helper" << file << destFolder << name;
                    QVariantMap args;
                    args[u"method"_s] = u"install"_s;
                    args[u"file"_s] = file;
                    args[u"name"_s] = name;
                    args[u"destFolder"_s] = destFolder;
                    args[u"createAfm"_s] = createAfm;
                    args[u"type"_s] = (int)type;
                    result = performAction(args);
                } else {
                    if (!Misc::dExists(destFolder)) {
                        result = Misc::createDir(destFolder) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED;
                    }

                    if (STATUS_OK == result) {
                        result = QFile::copy(file, destFolder + name) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED;
                    }

                    if (STATUS_OK == result) {
                        Misc::setFilePerms(QFile::encodeName(destFolder + name));
                        if ((Utils::FILE_SCALABLE == type || Utils::FILE_PFM == type) && createAfm) {
                            Utils::createAfm(destFolder + name, type);
                        }
                    }
                }

                if (STATUS_OK == result && Utils::FILE_AFM != type && Utils::FILE_PFM != type) {
                    StyleCont::ConstIterator st(font.styles().begin());
                    FileCont::ConstIterator f((*st).files().begin());
                    File df(destFolder + name, (*f).foundry(), (*f).index());

                    (*st).clearFiles();
                    (*st).add(df);
                    theFolders[folder].add(font);
                    theFolders[folder].addModifiedDir(destFolder);
                    Q_EMIT fontsAdded(Families(font, FOLDER_SYS == folder));
                }
            }
        }
    }

    Q_EMIT status(pid, result);
    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);
}

void FontInst::uninstall(const QString &family, quint32 style, bool fromSystem, int pid, bool checkConfig)
{
    // qDebug() << family << style << fromSystem << pid << checkConfig;

    m_connections.insert(pid);

    if (checkConfig) {
        updateFontList();
    }

    EFolder folder = isSystem || fromSystem ? FOLDER_SYS : FOLDER_USER;
    FamilyCont::ConstIterator fam;
    StyleCont::ConstIterator st;
    int result = findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST;

    if (STATUS_OK == result) {
        Family del((*fam).name());
        Style s((*st).value(), (*st).scalable(), (*st).writingSystems());
        FileCont files((*st).files());
        FileCont::ConstIterator it(files.begin()), end(files.end());

        if (fromSystem && !isSystem) {
            QStringList fileList;
            bool wasDisabled(false);

            for (; it != end; ++it) {
                fileList.append((*it).path());
                theFolders[FOLDER_SYS].addModifiedDir(Misc::getDir((*it).path()));
                if (!wasDisabled && Misc::isHidden(Misc::getFile((*it).path()))) {
                    wasDisabled = true;
                }
            }
            QVariantMap args;
            args[u"method"_s] = u"uninstall"_s;
            args[u"files"_s] = fileList;
            result = performAction(args);

            if (STATUS_OK == result) {
                FileCont empty;
                s.setFiles(files);
                (*st).setFiles(empty);
                if (wasDisabled) {
                    theFolders[FOLDER_SYS].setDisabledDirty();
                }
            }
        } else {
            for (; it != end; ++it) {
                if (!Misc::fExists((*it).path()) || QFile::remove((*it).path())) {
                    // Also remove any AFM or PFM files...
                    QStringList other;
                    Misc::getAssociatedFiles((*it).path(), other);
                    QStringList::ConstIterator oit(other.constBegin()), oend(other.constEnd());
                    for (; oit != oend; ++oit) {
                        QFile::remove(*oit);
                    }

                    theFolders[folder].addModifiedDir(Misc::getDir((*it).path()));
                    (*st).remove(*it);
                    s.add(*it);
                    if (!theFolders[folder].disabledDirty() && Misc::isHidden(Misc::getFile((*it).path()))) {
                        theFolders[folder].setDisabledDirty();
                    }
                }
            }
        }

        if (STATUS_OK == result) {
            if ((*st).files().isEmpty()) {
                (*fam).remove(*st);
                if ((*fam).styles().isEmpty()) {
                    theFolders[folder].removeFont(*fam);
                }
            } else {
                result = STATUS_PARTIAL_DELETE;
            }
            del.add(s);
        }
        Q_EMIT fontsRemoved(Families(del, FOLDER_SYS == folder));
    }
    // qDebug() << "status" << result;
    Q_EMIT status(pid, result);

    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);
}

void FontInst::uninstall(const QString &name, bool fromSystem, int pid, bool checkConfig)
{
    // qDebug() << name << fromSystem << pid << checkConfig;

    FamilyCont::ConstIterator fam;
    StyleCont::ConstIterator st;
    if (findFont(name, fromSystem || isSystem ? FOLDER_SYS : FOLDER_USER, fam, st)) {
        uninstall((*fam).name(), (*st).value(), fromSystem, pid, checkConfig);
    } else {
        Q_EMIT status(pid, KIO::ERR_DOES_NOT_EXIST);
    }
}

void FontInst::move(const QString &family, quint32 style, bool toSystem, int pid, bool checkConfig)
{
    // qDebug() << family << style << toSystem << pid << checkConfig;

    m_connections.insert(pid);
    if (checkConfig) {
        updateFontList();
    }

    if (isSystem) {
        Q_EMIT status(pid, KIO::ERR_UNSUPPORTED_ACTION);
    } else {
        FamilyCont::ConstIterator fam;
        StyleCont::ConstIterator st;
        EFolder from = toSystem ? FOLDER_USER : FOLDER_SYS, to = toSystem ? FOLDER_SYS : FOLDER_USER;

        if (findFont(family, style, from, fam, st)) {
            FileCont::ConstIterator it((*st).files().begin()), end((*st).files().end());
            QStringList files;

            for (; it != end; ++it) {
                files.append((*it).path());
                theFolders[from].addModifiedDir(Misc::getDir((*it).path()));
                // Actual 'to' folder does not really matter, as we only want to call fc-cache
                // ...actual folders only matter for xreating fonts.dir, etc, and we wont be doing this...
                theFolders[to].addModifiedDir(theFolders[to].location());
            }

            QVariantMap args;
            args[u"method"_s] = u"move"_s;
            args[u"files"_s] = files;
            args[u"toSystem"_s] = toSystem;
            args[u"dest"_s] = theFolders[to].location();
            args[u"uid"_s] = getuid();
            args[u"gid"_s] = getgid();
            int result = performAction(args);

            if (STATUS_OK == result) {
                updateFontList();
            }
            Q_EMIT status(pid, result);
        } else {
            // qDebug() << "does not exist";
            Q_EMIT status(pid, KIO::ERR_DOES_NOT_EXIST);
        }
    }

    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);
}

static bool renameFontFile(const QString &from, const QString &to, int uid = -1, int gid = -1)
{
    if (!QFile::rename(from, to)) {
        return false;
    }

    QByteArray dest(QFile::encodeName(to));
    Misc::setFilePerms(dest);
    if (-1 != uid && -1 != gid) {
        ::chown(dest.data(), uid, gid);
    }
    return true;
}

void FontInst::enable(const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig)
{
    // qDebug() << family << style << inSystem << pid << checkConfig;
    toggle(true, family, style, inSystem, pid, checkConfig);
}

void FontInst::disable(const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig)
{
    // qDebug() << family << style << inSystem << pid << checkConfig;
    toggle(false, family, style, inSystem, pid, checkConfig);
}

void FontInst::removeFile(const QString &family, quint32 style, const QString &file, bool fromSystem, int pid, bool checkConfig)
{
    // qDebug() << family << style << file << fromSystem << pid << checkConfig;

    m_connections.insert(pid);

    if (checkConfig) {
        updateFontList();
    }

    // First find the family/style
    EFolder folder = isSystem || fromSystem ? FOLDER_SYS : FOLDER_USER;
    FamilyCont::ConstIterator fam;
    StyleCont::ConstIterator st;
    int result = findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST;

    if (STATUS_OK == result) {
        // Family/style found - now check that the requested file is *within* the same folder as one
        // of the files linked to this font...
        FileCont files((*st).files());
        FileCont::ConstIterator it(files.begin()), end(files.end());
        QString dir(Misc::getDir(file));

        result = KIO::ERR_DOES_NOT_EXIST;
        for (; it != end && STATUS_OK != result; ++it) {
            if (Misc::getDir((*it).path()) == dir) {
                result = STATUS_OK;
            }
        }

        if (STATUS_OK == result) {
            // OK, found folder - so can now proceed to delete the file...
            if (fromSystem && !isSystem) {
                QVariantMap args;
                args[u"method"_s] = u"removeFile"_s;
                args[u"file"_s] = file;
                result = performAction(args);
            } else {
                result = Misc::fExists(file) ? QFile::remove(file) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED : (int)KIO::ERR_DOES_NOT_EXIST;
            }

            if (STATUS_OK == result) {
                theFolders[folder].addModifiedDir(dir);
            }
        }
    }

    Q_EMIT status(pid, result);
}

void FontInst::reconfigure(int pid, bool force)
{
    // qDebug() << pid << force;
    bool sysModified(theFolders[FOLDER_SYS].isModified());

    saveDisabled();

    // qDebug() << theFolders[FOLDER_USER].isModified() << sysModified;
    if (!isSystem && (force || theFolders[FOLDER_USER].isModified())) {
        theFolders[FOLDER_USER].configure(force);
    }

    if (sysModified) {
        if (isSystem) {
            theFolders[FOLDER_SYS].configure();
        } else {
            QVariantMap args;
            args[u"method"_s] = u"reconfigure"_s;
            performAction(args);
            theFolders[FOLDER_SYS].clearModified();
        }
    }

    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);

    updateFontList();
    Q_EMIT status(pid, isSystem ? constSystemReconfigured : STATUS_OK);
}

QString FontInst::folderName(bool sys)
{
    return theFolders[sys || isSystem ? FOLDER_SYS : FOLDER_USER].location();
}

void FontInst::saveDisabled()
{
    if (isSystem) {
        theFolders[FOLDER_SYS].saveDisabled();
    } else {
        for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
            if (FOLDER_SYS == i && !isSystem) {
                if (theFolders[i].disabledDirty()) {
                    QVariantMap args;
                    args[u"method"_s] = u"saveDisabled"_s;
                    performAction(args);
                    theFolders[i].saveDisabled();
                }
            } else {
                theFolders[i].saveDisabled();
            }
        }
    }
}

void FontInst::connectionsTimeout()
{
    bool canExit(true);

    // qDebug() << "exiting";
    checkConnections();

    for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
        if (theFolders[i].disabledDirty()) {
            canExit = false;
        }
        theFolders[i].saveDisabled();
    }

    if (0 == m_connections.count()) {
        if (canExit) {
            qApp->exit(0);
        } else { // Try again later...
            m_connectionsTimer->start(constConnectionsTimeout);
        }
    }
}

void FontInst::fontListTimeout()
{
    updateFontList(true);
    m_fontListTimer->start(constFontListTimeout);
}

void FontInst::updateFontList(bool emitChanges)
{
    // For some reason just the "!FcConfigUptoDate(0)" check does not always work :-(
    FcBool fcModified = !FcConfigUptoDate(nullptr);

    if (fcModified || theFolders[FOLDER_SYS].fonts().isEmpty() || (!isSystem && theFolders[FOLDER_USER].fonts().isEmpty())
        || theFolders[FOLDER_SYS].disabledDirty() || (!isSystem && theFolders[FOLDER_USER].disabledDirty())) {
        // qDebug() << "Need to refresh font lists";
        if (fcModified) {
            // qDebug() << "Re-init FC";
            if (!FcInitReinitialize()) {
                // qDebug() << "Re-init failed????";
            }
        }

        Folder::Flat old[FOLDER_COUNT];

        if (emitChanges) {
            // qDebug() << "Flatten existing font lists";
            for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
                old[i] = theFolders[i].flatten();
            }
        }

        saveDisabled();

        for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
            theFolders[i].clearFonts();
        }

        // qDebug() << "update list of fonts";

        FcPattern *pat = FcPatternCreate();
        FcObjectSet *os = FcObjectSetBuild(FC_FILE,
                                           FC_FAMILY,
                                           FC_FAMILYLANG,
                                           FC_WEIGHT,
                                           FC_LANG,
                                           FC_CHARSET,
                                           FC_SCALABLE,
#ifndef KFI_FC_NO_WIDTHS
                                           FC_WIDTH,
#endif
                                           FC_SLANT,
                                           FC_INDEX,
                                           FC_FOUNDRY,
                                           (void *)nullptr);

        FcFontSet *list = FcFontList(nullptr, pat, os);

        FcPatternDestroy(pat);
        FcObjectSetDestroy(os);

        theFolders[FOLDER_SYS].loadDisabled();
        if (!isSystem) {
            theFolders[FOLDER_USER].loadDisabled();
        }

        if (list) {
            QString home(Misc::dirSyntax(QDir::homePath()));

            for (int i = 0; i < list->nfont; i++) {
                QString fileName(Misc::fileSyntax(FC::getFcString(list->fonts[i], FC_FILE)));

                if (!fileName.isEmpty() && Misc::fExists(fileName)) // && 0!=fileName.indexOf(constDefomaLocation))
                {
                    QString family, foundry;
                    quint32 styleVal;
                    int index;
                    qulonglong writingSystems(WritingSystems::instance()->get(list->fonts[i]));
                    FcBool scalable = FcFalse;

                    if (FcResultMatch != FcPatternGetBool(list->fonts[i], FC_SCALABLE, 0, &scalable)) {
                        scalable = FcFalse;
                    }

                    FC::getDetails(list->fonts[i], family, styleVal, index, foundry);
                    FamilyCont::ConstIterator fam = theFolders[isSystem || 0 != fileName.indexOf(home) ? FOLDER_SYS : FOLDER_USER].addFont(Family(family));
                    StyleCont::ConstIterator style = (*fam).add(Style(styleVal));
                    FileCont::ConstIterator file = (*style).add(File(fileName, foundry, index));
                    Q_UNUSED(file);

                    (*style).setWritingSystems((*style).writingSystems() | writingSystems);
                    if (scalable) {
                        (*style).setScalable();
                    }
                }
            }

            FcFontSetDestroy(list);
        }

        if (emitChanges) {
            // qDebug() << "Look for differences";
            for (int i = 0; i < (isSystem ? 1 : FOLDER_COUNT); ++i) {
                // qDebug() << "Flatten, and take copies...";
                Folder::Flat newList = theFolders[i].flatten(), onlyNew = newList;

                // qDebug() << "Determine differences...";
                onlyNew.subtract(old[i]);
                old[i].subtract(newList);

                // qDebug() << "Emit changes...";
                Families families = onlyNew.build(isSystem || i == FOLDER_SYS);

                if (!families.items.isEmpty()) {
                    Q_EMIT fontsAdded(families);
                }

                families = old[i].build(isSystem || i == FOLDER_SYS);
                if (!families.items.isEmpty()) {
                    Q_EMIT fontsRemoved(families);
                }
            }
        }
        // qDebug() << "updated list of fonts";
    }
}

void FontInst::toggle(bool enable, const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig)
{
    m_connections.insert(pid);

    if (checkConfig) {
        updateFontList();
    }

    EFolder folder = isSystem || inSystem ? FOLDER_SYS : FOLDER_USER;
    FamilyCont::ConstIterator fam;
    StyleCont::ConstIterator st;
    int result = findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST;

    if (STATUS_OK == result) {
        FileCont files((*st).files()), toggledFiles;
        FileCont::ConstIterator it(files.begin()), end(files.end());
        QHash<File, QString> movedFonts;
        QHash<QString, QString> movedAssoc;
        QSet<QString> modifiedDirs;

        for (; it != end && STATUS_OK == result; ++it) {
            QString to = Misc::getDir((*it).path()) + QString(enable ? Misc::unhide(Misc::getFile((*it).path())) : Misc::hide(Misc::getFile((*it).path())));
            if (to != (*it).path()) {
                // qDebug() << "MOVE:" << (*it).path() << " to " << to;
                // If the font is a system font, and we're not root, then just go through the actions here - so
                // that we can build the list of changes that would happen...
                if ((inSystem && !isSystem) || renameFontFile((*it).path(), to)) {
                    modifiedDirs.insert(Misc::getDir(enable ? to : (*it).path()));
                    toggledFiles.insert(File(to, (*it).foundry(), (*it).index()));
                    // Now try to move an associated AFM or PFM files...
                    QStringList assoc;

                    movedFonts[*it] = to;
                    Misc::getAssociatedFiles((*it).path(), assoc);

                    QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd());

                    for (; ait != aend && STATUS_OK == result; ++ait) {
                        to = Misc::getDir(*ait) + QString(enable ? Misc::unhide(Misc::getFile(*ait)) : Misc::hide(Misc::getFile(*ait)));

                        if (to != *ait) {
                            if ((inSystem && !isSystem) || renameFontFile(*ait, to)) {
                                movedAssoc[*ait] = to;
                            } else {
                                result = KIO::ERR_WRITE_ACCESS_DENIED;
                            }
                        }
                    }
                } else {
                    result = KIO::ERR_WRITE_ACCESS_DENIED;
                }
            }
        }

        if (inSystem && !isSystem) {
            Family toggleFam((*fam).name());

            toggleFam.add(*st);
            QVariantMap args;
            args[u"method"_s] = u"toggle"_s;
            QString xml;
            QTextStream str(&xml);
            toggleFam.toXml(false, str);
            args[u"xml"_s] = xml;
            args[u"enable"_s] = enable;
            result = performAction(args);
        }

        if (STATUS_OK == result) {
            Family addFam((*fam).name()), delFam((*fam).name());
            Style addStyle((*st).value(), (*st).scalable(), (*st).writingSystems()), delStyle((*st).value(), (*st).scalable(), (*st).writingSystems());

            addStyle.setFiles(toggledFiles);
            addFam.add(addStyle);
            delStyle.setFiles(files);
            delFam.add(delStyle);
            (*st).setFiles(toggledFiles);

            theFolders[folder].addModifiedDirs(modifiedDirs);
            Q_EMIT fontsAdded(Families(addFam, FOLDER_SYS == folder));
            Q_EMIT fontsRemoved(Families(delFam, FOLDER_SYS == folder));

            theFolders[folder].setDisabledDirty();
        } else // un-move fonts!
        {
            QHash<File, QString>::ConstIterator fit(movedFonts.constBegin()), fend(movedFonts.constEnd());
            QHash<QString, QString>::ConstIterator ait(movedAssoc.constBegin()), aend(movedAssoc.constEnd());

            for (; fit != fend; ++fit) {
                renameFontFile(fit.value(), fit.key().path());
            }
            for (; ait != aend; ++ait) {
                renameFontFile(ait.value(), ait.key());
            }
        }
    }
    Q_EMIT status(pid, result);

    m_connectionsTimer->start(constConnectionsTimeout);
    m_fontListTimer->start(constFontListTimeout);
}

void FontInst::addModifedSysFolders(const Family &family)
{
    StyleCont::ConstIterator style(family.styles().begin()), styleEnd(family.styles().end());

    for (; style != styleEnd; ++style) {
        FileCont::ConstIterator file((*style).files().begin()), fileEnd((*style).files().end());

        for (; file != fileEnd; ++file) {
            theFolders[FOLDER_SYS].addModifiedDir(Misc::getDir((*file).path()));
        }
    }
}

void FontInst::checkConnections()
{
    QSet<int>::ConstIterator it(m_connections.begin()), end(m_connections.end());
    QSet<int> remove;

    for (; it != end; ++it) {
        if (0 != kill(*it, 0)) {
            remove.insert(*it);
        }
    }
    m_connections.subtract(remove);
}

bool FontInst::findFontReal(const QString &family, const QString &style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st)
{
    Family f(family);
    fam = theFolders[folder].fonts().find(f);
    if (theFolders[folder].fonts().end() == fam) {
        return false;
    }

    StyleCont::ConstIterator end((*fam).styles().end());
    for (st = (*fam).styles().begin(); st != end; ++st) {
        if (FC::createStyleName((*st).value()) == style) {
            return true;
        }
    }

    return false;
}

bool FontInst::findFont(const QString &font, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st, bool updateList)
{
    QString family, style;

    decompose(font, family, style);

    if (!findFontReal(family, style, folder, fam, st)) {
        if (updateList) {
            // Not found, so refresh font list and try again...
            updateFontList();
            return findFontReal(family, style, folder, fam, st);
        } else {
            return false;
        }
    }

    return true;
}

bool FontInst::findFontReal(const QString &family, quint32 style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st)
{
    fam = theFolders[folder].fonts().find(Family(family));

    if (theFolders[folder].fonts().end() == fam) {
        return false;
    } else {
        st = (*fam).styles().find(style);

        return (*fam).styles().end() != st;
    }
}

bool FontInst::findFont(const QString &family, quint32 style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st, bool updateList)
{
    if (!findFontReal(family, style, folder, fam, st)) {
        if (updateList) {
            // Not found, so refresh font list and try again...
            updateFontList();
            return findFontReal(family, style, folder, fam, st);
        } else {
            return false;
        }
    }
    return true;
}

int FontInst::performAction(const QVariantMap &args)
{
    KAuth::Action action(u"org.kde.fontinst.manage"_s);

    action.setHelperId(u"org.kde.fontinst"_s);
    action.setArguments(args);
    // qDebug() << "Call " << args["method"].toString() << " on helper";
    m_fontListTimer->stop();
    m_connectionsTimer->stop();

    KAuth::ExecuteJob *j = action.execute();
    j->exec();
    if (j->error()) {
        qCWarning(KFONTINST_DEBUG) << "kauth action failed" << j->errorString() << j->errorText();
        // error is a KAuth::ActionReply::Error rest of this code expects KIO error codes which are extended by EStatus
        switch (j->error()) {
        case KAuth::ActionReply::Error::UserCancelledError:
            return KIO::ERR_USER_CANCELED;
        case KAuth::ActionReply::Error::AuthorizationDeniedError:
            /*fall through*/
        case KAuth::ActionReply::Error::NoSuchActionError:
            return KIO::ERR_CANNOT_AUTHENTICATE;
        default:
            return KIO::ERR_INTERNAL;
        }
        return KIO::ERR_INTERNAL;
    }
    // qDebug() << "Success!";
    return STATUS_OK;
}

}
