/* SPDX-FileCopyrightText: 2003-2007 Craig Drummond SPDX-License-Identifier: GPL-2.0-or-later */ #include "JobRunner.h" #include "ActionLabel.h" #include "Fc.h" #include "KfiConstants.h" #include "Misc.h" #include "config-fontinst.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CFG_GROUP "Runner Dialog" #define CFG_DONT_SHOW_FINISHED_MSG "DontShowFinishedMsg" using namespace Qt::StringLiterals; namespace KFI { Q_GLOBAL_STATIC(FontInstInterface, theInterface) FontInstInterface *CJobRunner::dbus() { return theInterface; } QString CJobRunner::folderName(bool sys) { if (!theInterface) { return QString(); } QDBusPendingReply reply = theInterface->folderName(sys); reply.waitForFinished(); return reply.isError() ? QString() : reply.argumentAt<0>(); } void CJobRunner::startDbusService() { if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()))) { const QString fontinst = QStringLiteral(KFONTINST_LIB_EXEC_DIR "/fontinst"); qDebug() << "Service " << OrgKdeFontinstInterface::staticInterfaceName() << " not registered, starting" << fontinst; QProcess::startDetached(fontinst, QStringList()); } } static const int constDownloadFailed = -1; static const int constInterfaceCheck = 5 * 1000; static void decode(const QUrl &url, Misc::TFont &font, bool &system) { font = FC::decode(url); QUrlQuery query(url); system = (query.hasQueryItem(u"sys"_s) && query.queryItemValue(u"sys"_s) == QLatin1String("true")); } QUrl CJobRunner::encode(const QString &family, quint32 style, bool system) { QUrl url(FC::encode(family, style)); QUrlQuery query(url); query.addQueryItem(QStringLiteral("sys"), system ? QStringLiteral("true") : QStringLiteral("false")); url.setQuery(query); return url; } enum EPages { PAGE_PROGRESS, PAGE_SKIP, PAGE_ERROR, PAGE_CANCEL, PAGE_COMPLETE, }; enum Response { RESP_CONTINUE, RESP_AUTO, RESP_CANCEL, }; static void addIcon(QGridLayout *layout, QFrame *page, const QString &iconName, int iconSize) { QLabel *icon = new QLabel(page); icon->setPixmap(QIcon::fromTheme(iconName).pixmap(iconSize)); icon->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); layout->addWidget(icon, 0, 0); } CJobRunner::CJobRunner(QWidget *parent) : QDialog(parent) , m_it(m_urls.end()) , m_end(m_it) , m_autoSkip(false) , m_cancelClicked(false) , m_modified(false) , m_tempDir(nullptr) { setModal(true); m_buttonBox = new QDialogButtonBox; connect(m_buttonBox, &QDialogButtonBox::clicked, this, &CJobRunner::slotButtonClicked); m_skipButton = new QPushButton(i18n("Skip")); m_buttonBox->addButton(m_skipButton, QDialogButtonBox::ActionRole); m_skipButton->hide(); m_autoSkipButton = new QPushButton(i18n("AutoSkip")); m_buttonBox->addButton(m_autoSkipButton, QDialogButtonBox::ActionRole); m_autoSkipButton->hide(); m_stack = new QStackedWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(m_stack); mainLayout->addWidget(m_buttonBox); QStyleOption option; option.initFrom(this); int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this); QFrame *page = new QFrame(m_stack); QGridLayout *layout = new QGridLayout(page); m_statusLabel = new QLabel(page); m_progress = new QProgressBar(page); // m_statusLabel->setWordWrap(true); layout->addWidget(m_actionLabel = new CActionLabel(this), 0, 0, 2, 1); layout->addWidget(m_statusLabel, 0, 1); layout->addWidget(m_progress, 1, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 0); m_stack->insertWidget(PAGE_PROGRESS, page); page = new QFrame(m_stack); layout = new QGridLayout(page); m_skipLabel = new QLabel(page); m_skipLabel->setWordWrap(true); addIcon(layout, page, u"dialog-error"_s, iconSize); layout->addWidget(m_skipLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); m_stack->insertWidget(PAGE_SKIP, page); page = new QFrame(m_stack); layout = new QGridLayout(page); m_errorLabel = new QLabel(page); m_errorLabel->setWordWrap(true); addIcon(layout, page, u"dialog-error"_s, iconSize); layout->addWidget(m_errorLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); m_stack->insertWidget(PAGE_ERROR, page); page = new QFrame(m_stack); layout = new QGridLayout(page); QLabel *cancelLabel = new QLabel(i18n("

Cancel?

Are you sure you wish to cancel?

"), page); cancelLabel->setWordWrap(true); addIcon(layout, page, u"dialog-warning"_s, iconSize); layout->addWidget(cancelLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); m_stack->insertWidget(PAGE_CANCEL, page); if (KSharedConfig::openConfig(KFI_UI_CFG_FILE)->group(QStringLiteral(CFG_GROUP)).readEntry(CFG_DONT_SHOW_FINISHED_MSG, false)) { m_dontShowFinishedMsg = nullptr; } else { page = new QFrame(m_stack); layout = new QGridLayout(page); QLabel *finishedLabel = new QLabel(i18n("

Finished

" "

Please note that any open applications will need to be " "restarted in order for any changes to be noticed.

"), page); finishedLabel->setWordWrap(true); addIcon(layout, page, u"dialog-information"_s, iconSize); layout->addWidget(finishedLabel, 0, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 1, 0); m_dontShowFinishedMsg = new QCheckBox(i18n("Do not show this message again"), page); m_dontShowFinishedMsg->setChecked(false); layout->addItem(new QSpacerItem(0, layout->spacing(), QSizePolicy::Fixed, QSizePolicy::Fixed), 2, 0); layout->addWidget(m_dontShowFinishedMsg, 3, 1); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 4, 0); m_stack->insertWidget(PAGE_COMPLETE, page); } QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this); connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &CJobRunner::dbusServiceOwnerChanged); connect(dbus(), &OrgKdeFontinstInterface::status, this, &CJobRunner::dbusStatus); setMinimumSize(420, 160); } CJobRunner::~CJobRunner() { delete m_tempDir; } void CJobRunner::getAssociatedUrls(const QUrl &url, QList &list, bool afmAndPfm) { QString ext(url.path()); int dotPos(ext.lastIndexOf(u'.')); bool check(false); if (-1 == dotPos) { // Hmm, no extension - check anyway... check = true; } else // Cool, got an extension - see if it is a Type1 font... { ext = ext.mid(dotPos + 1); check = 0 == ext.compare(u"pfa", Qt::CaseInsensitive) || 0 == ext.compare(u"pfb", Qt::CaseInsensitive); } if (check) { constexpr const char *afm[] = {"afm", "AFM", "Afm", nullptr}, *pfm[] = {"pfm", "PFM", "Pfm", nullptr}; bool gotAfm(false), localFile(url.isLocalFile()); int e; for (e = 0; afm[e]; ++e) { QUrl statUrl(url); statUrl.setPath(Misc::changeExt(url.path(), QLatin1String(afm[e]))); bool urlExists = false; if (localFile) { urlExists = Misc::fExists(statUrl.toLocalFile()); } else { auto job = KIO::stat(statUrl); job->exec(); urlExists = !job->error(); } if (urlExists) { list.append(statUrl); gotAfm = true; break; } } if (afmAndPfm || !gotAfm) { for (e = 0; pfm[e]; ++e) { QUrl statUrl(url); statUrl.setPath(Misc::changeExt(url.path(), QLatin1String(pfm[e]))); bool urlExists = false; if (localFile) { urlExists = Misc::fExists(statUrl.toLocalFile()); } else { auto job = KIO::stat(statUrl); job->exec(); urlExists = !job->error(); } if (urlExists) { list.append(statUrl); break; } } } } } static void addEnableActions(CJobRunner::ItemList &urls) { CJobRunner::ItemList modified; CJobRunner::ItemList::ConstIterator it(urls.constBegin()), end(urls.constEnd()); for (; it != end; ++it) { if ((*it).isDisabled) { CJobRunner::Item item(*it); item.fileName = QLatin1String("--"); modified.append(item); } modified.append(*it); } urls = modified; } int CJobRunner::exec(ECommand cmd, const ItemList &urls, bool destIsSystem) { m_autoSkip = m_cancelClicked = m_modified = false; switch (cmd) { case CMD_INSTALL: setWindowTitle(i18n("Installing")); break; case CMD_DELETE: setWindowTitle(i18n("Uninstalling")); break; case CMD_ENABLE: setWindowTitle(i18n("Enabling")); break; case CMD_MOVE: setWindowTitle(i18n("Moving")); break; case CMD_UPDATE: setWindowTitle(i18n("Updating")); m_modified = true; break; case CMD_REMOVE_FILE: setWindowTitle(i18n("Removing")); break; default: case CMD_DISABLE: setWindowTitle(i18n("Disabling")); } m_destIsSystem = destIsSystem; m_urls = urls; if (CMD_INSTALL == cmd) { std::sort(m_urls.begin(), m_urls.end()); // Sort list of fonts so that we have type1 fonts followed by their metrics... } else if (CMD_MOVE == cmd) { addEnableActions(m_urls); } m_it = m_urls.constBegin(); m_end = m_urls.constEnd(); m_prev = m_end; m_progress->setValue(0); m_progress->setRange(0, m_urls.count() + 1); m_progress->show(); m_cmd = cmd; m_currentFile = QString(); m_statusLabel->setText(QString()); setPage(PAGE_PROGRESS); QTimer::singleShot(0, this, &CJobRunner::doNext); QTimer::singleShot(constInterfaceCheck, this, &CJobRunner::checkInterface); m_actionLabel->startAnimation(); int rv = QDialog::exec(); if (m_tempDir) { delete m_tempDir; m_tempDir = nullptr; } return rv; } void CJobRunner::doNext() { if (m_it == m_end /* || CMD_UPDATE==m_cmd*/) { if (m_modified) { // Force reconfig if command was already set to update... dbus()->reconfigure(getpid(), CMD_UPDATE == m_cmd); m_cmd = CMD_UPDATE; m_statusLabel->setText(i18n("Updating font configuration. Please wait…")); m_progress->setValue(m_progress->maximum()); Q_EMIT configuring(); } else { m_actionLabel->stopAnimation(); if (PAGE_ERROR != m_stack->currentIndex()) { reject(); } } } else { Misc::TFont font; bool system; switch (m_cmd) { case CMD_INSTALL: { m_currentFile = fileName((*m_it)); if (m_currentFile.isEmpty()) { // Failed to download... dbusStatus(getpid(), constDownloadFailed); } else { // Create AFM if this is a PFM, and the previous was not the AFM for this font... bool createAfm = Item::TYPE1_PFM == (*m_it).type && (m_prev == m_end || (*m_it).fileName != (*m_prev).fileName || Item::TYPE1_AFM != (*m_prev).type); dbus()->install(m_currentFile, createAfm, m_destIsSystem, getpid(), false); } break; } case CMD_DELETE: decode(*m_it, font, system); dbus()->uninstall(font.family, font.styleInfo, system, getpid(), false); break; case CMD_ENABLE: decode(*m_it, font, system); dbus()->enable(font.family, font.styleInfo, system, getpid(), false); break; case CMD_DISABLE: decode(*m_it, font, system); dbus()->disable(font.family, font.styleInfo, system, getpid(), false); break; case CMD_MOVE: decode(*m_it, font, system); // To 'Move' a disabled font, we first need to enable it. To accomplish this, JobRunner creates a 'fake' entry // with the filename "--" if ((*m_it).fileName == QLatin1String("--")) { setWindowTitle(i18n("Enabling")); dbus()->enable(font.family, font.styleInfo, system, getpid(), false); } else { if (m_prev != m_end && (*m_prev).fileName == QLatin1String("--")) { setWindowTitle(i18n("Moving")); } dbus()->move(font.family, font.styleInfo, m_destIsSystem, getpid(), false); } break; case CMD_REMOVE_FILE: decode(*m_it, font, system); dbus()->removeFile(font.family, font.styleInfo, (*m_it).fileName, system, getpid(), false); break; default: break; } m_statusLabel->setText(CMD_INSTALL == m_cmd ? (*m_it).url() : FC::createName(FC::decode(*m_it))); m_progress->setValue(m_progress->value() + 1); // Keep copy of this iterator - so that can check whether AFM should be created. m_prev = m_it; } } void CJobRunner::checkInterface() { if (m_it == m_urls.constBegin() && !FontInst::isStarted(dbus())) { setPage(PAGE_ERROR, i18n("Unable to start backend.")); m_actionLabel->stopAnimation(); m_it = m_end; } } void CJobRunner::dbusServiceOwnerChanged(const QString &name, const QString &from, const QString &to) { if (to.isEmpty() && !from.isEmpty() && name == QLatin1String(OrgKdeFontinstInterface::staticInterfaceName()) && m_it != m_end) { setPage(PAGE_ERROR, i18n("Backend died, but has been restarted. Please try again.")); m_actionLabel->stopAnimation(); m_it = m_end; } } void CJobRunner::dbusStatus(int pid, int status) { if (pid != getpid()) { return; } if (CMD_UPDATE == m_cmd) { setPage(PAGE_COMPLETE); return; } m_lastDBusStatus = status; if (m_cancelClicked) { m_actionLabel->stopAnimation(); setPage(PAGE_CANCEL); return; /* if(RESP_CANCEL==m_response) m_it=m_end; m_cancelClicked=false; setPage(PAGE_PROGRESS); m_actionLabel->startAnimation(); */ } // m_it will equal m_end if user decided to cancel the current op if (m_it == m_end) { doNext(); } else if (0 == status) { m_modified = true; ++m_it; doNext(); } else { bool cont(m_autoSkip && m_urls.count() > 1); QString currentName((*m_it).fileName); if (!cont) { m_actionLabel->stopAnimation(); if (FontInst::STATUS_SERVICE_DIED == status) { setPage(PAGE_ERROR, errorString(status)); m_it = m_end; } else { ItemList::ConstIterator next(m_it == m_end ? m_end : m_it + 1); // If we're installing a Type1 font, and its already installed - then we need to skip past AFM/PFM if (next != m_end && Item::TYPE1_FONT == (*m_it).type && (*next).fileName == currentName && (Item::TYPE1_AFM == (*next).type || Item::TYPE1_PFM == (*next).type)) { next++; if (next != m_end && (*next).fileName == currentName && (Item::TYPE1_AFM == (*next).type || Item::TYPE1_PFM == (*next).type)) { next++; } } if (1 == m_urls.count() || next == m_end) { setPage(PAGE_ERROR, errorString(status)); } else { setPage(PAGE_SKIP, errorString(status)); return; } } } contineuToNext(cont); } } void CJobRunner::contineuToNext(bool cont) { m_actionLabel->startAnimation(); if (cont) { if (CMD_INSTALL == m_cmd && Item::TYPE1_FONT == (*m_it).type) // Did we error on a pfa/pfb? if so, exclude the afm/pfm... { QString currentName((*m_it).fileName); ++m_it; // Skip afm/pfm if (m_it != m_end && (*m_it).fileName == currentName && (Item::TYPE1_AFM == (*m_it).type || Item::TYPE1_PFM == (*m_it).type)) { ++m_it; } // Skip pfm/afm if (m_it != m_end && (*m_it).fileName == currentName && (Item::TYPE1_AFM == (*m_it).type || Item::TYPE1_PFM == (*m_it).type)) { ++m_it; } } else { ++m_it; } } else { m_it = m_end = m_urls.constEnd(); } doNext(); } void CJobRunner::slotButtonClicked(QAbstractButton *button) { switch (m_stack->currentIndex()) { case PAGE_PROGRESS: if (m_it != m_end) { m_cancelClicked = true; } break; case PAGE_SKIP: setPage(PAGE_PROGRESS); if (button == m_skipButton) { contineuToNext(true); } else if (button == m_autoSkipButton) { m_autoSkip = true; contineuToNext(true); } else { contineuToNext(false); } break; case PAGE_CANCEL: if (button == m_buttonBox->button(QDialogButtonBox::Yes)) { m_it = m_end; } m_cancelClicked = false; setPage(PAGE_PROGRESS); m_actionLabel->startAnimation(); // Now continue... dbusStatus(getpid(), m_lastDBusStatus); break; case PAGE_COMPLETE: { if (m_dontShowFinishedMsg) { KConfigGroup grp(KSharedConfig::openConfig(KFI_UI_CFG_FILE)->group(QStringLiteral(CFG_GROUP))); grp.writeEntry(CFG_DONT_SHOW_FINISHED_MSG, m_dontShowFinishedMsg->isChecked()); } QDialog::accept(); break; } case PAGE_ERROR: QDialog::accept(); break; default: break; } } void CJobRunner::closeEvent(QCloseEvent *e) { if (PAGE_COMPLETE != m_stack->currentIndex()) { e->ignore(); slotButtonClicked(PAGE_CANCEL == m_stack->currentIndex() ? m_buttonBox->button(QDialogButtonBox::No) : m_buttonBox->button(QDialogButtonBox::Cancel)); } } void CJobRunner::setPage(int page, const QString &msg) { m_stack->setCurrentIndex(page); switch (page) { case PAGE_PROGRESS: m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel); m_skipButton->hide(); m_autoSkipButton->hide(); break; case PAGE_SKIP: m_skipLabel->setText(i18n("

Error

") + QLatin1String("

") + msg + QLatin1String("

")); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel); m_skipButton->show(); m_autoSkipButton->show(); break; case PAGE_ERROR: m_errorLabel->setText(i18n("

Error

") + QLatin1String("

") + msg + QLatin1String("

")); m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel); m_skipButton->hide(); m_autoSkipButton->hide(); break; case PAGE_CANCEL: m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); m_skipButton->hide(); m_autoSkipButton->hide(); break; case PAGE_COMPLETE: if (!m_dontShowFinishedMsg || m_dontShowFinishedMsg->isChecked()) { QDialog::accept(); } else { m_buttonBox->setStandardButtons(QDialogButtonBox::Close); m_skipButton->hide(); m_autoSkipButton->hide(); } break; } } QString CJobRunner::fileName(const QUrl &url) { if (url.isLocalFile()) { return url.toLocalFile(); } else { auto job = KIO::mostLocalUrl(url); job->exec(); QUrl local = job->mostLocalUrl(); if (local.isLocalFile()) { return local.toLocalFile(); // Yipee! no need to download!! } else { // Need to do actual download... if (!m_tempDir) { m_tempDir = new QTemporaryDir(QDir::tempPath() + "/fontinst"_L1); m_tempDir->setAutoRemove(true); } QString tempName(m_tempDir->filePath(Misc::getFile(url.path()))); auto job = KIO::file_copy(url, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite); if (job->exec()) { return tempName; } else { return QString(); } } } } QString CJobRunner::errorString(int value) const { Misc::TFont font(FC::decode(*m_it)); QString urlStr; if (CMD_REMOVE_FILE == m_cmd) { urlStr = (*m_it).fileName; } else if (font.family.isEmpty()) { urlStr = (*m_it).url(); } else { urlStr = FC::createName(font.family, font.styleInfo); } switch (value) { case constDownloadFailed: return i18n("Failed to download %1", urlStr); case FontInst::STATUS_SERVICE_DIED: return i18n("System backend died. Please try again.
%1", urlStr); case FontInst::STATUS_BITMAPS_DISABLED: return i18n("%1 is a bitmap font, and these have been disabled on your system.", urlStr); case FontInst::STATUS_ALREADY_INSTALLED: return i18n("%1 contains the font %2, which is already installed on your system.", urlStr, FC::getName(m_currentFile)); case FontInst::STATUS_NOT_FONT_FILE: return i18n("%1 is not a font.", urlStr); case FontInst::STATUS_PARTIAL_DELETE: return i18n("Could not remove all files associated with %1", urlStr); case FontInst::STATUS_NO_SYS_CONNECTION: return i18n("Failed to start the system daemon.
%1", urlStr); case KIO::ERR_FILE_ALREADY_EXIST: { QString name(Misc::modifyName(Misc::getFile((*m_it).fileName))), destFolder(Misc::getDestFolder(folderName(m_destIsSystem), name)); return i18n("%1 already exists.", destFolder + name); } case KIO::ERR_DOES_NOT_EXIST: return i18n("%1 does not exist.", urlStr); case KIO::ERR_WRITE_ACCESS_DENIED: return i18n("Permission denied.
%1", urlStr); case KIO::ERR_UNSUPPORTED_ACTION: return i18n("Unsupported action.
%1", urlStr); case KIO::ERR_CANNOT_AUTHENTICATE: return i18n("Authentication failed.
%1", urlStr); default: return i18n("Unexpected error while processing: %1", urlStr); } } CJobRunner::Item::Item(const QUrl &u, const QString &n, bool dis) : QUrl(u) , name(n) , fileName(Misc::getFile(u.path())) , isDisabled(dis) { type = Misc::checkExt(fileName, u"pfa") || Misc::checkExt(fileName, u"pfb") ? TYPE1_FONT : Misc::checkExt(fileName, u"afm") ? TYPE1_AFM : Misc::checkExt(fileName, u"pfm") ? TYPE1_PFM : OTHER_FONT; if (OTHER_FONT != type) { int pos(fileName.lastIndexOf(u'.')); if (-1 != pos) { fileName.truncate(pos); } } } CJobRunner::Item::Item(const QString &file, const QString &family, quint32 style, bool system) : QUrl(CJobRunner::encode(family, style, system)) , fileName(file) , type(OTHER_FONT) { } bool CJobRunner::Item::operator<(const Item &o) const { int nameComp(fileName.compare(o.fileName)); return nameComp < 0 || (0 == nameComp && type < o.type); } }