File indexing completed on 2025-04-27 03:58:22

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-12-23
0007  * Description : Autodetect binary program and version
0008  *
0009  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2012-2016 by Benjamin Girault <benjamin dot girault at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "dbinaryiface.h"
0017 
0018 // Qt includes
0019 
0020 #include <QProcess>
0021 #include <QMessageBox>
0022 #include <QRegularExpression>
0023 
0024 // KDE includes
0025 
0026 #include <klocalizedstring.h>
0027 #include <ksharedconfig.h>
0028 #include <kconfiggroup.h>
0029 
0030 // Local includes
0031 
0032 #include "dfiledialog.h"
0033 #include "digikam_debug.h"
0034 #include "digikam_config.h"
0035 #include "digikam_globals.h"
0036 
0037 namespace Digikam
0038 {
0039 
0040 DBinaryIface::DBinaryIface(const QString& binaryName,
0041                            const QString& projectName,
0042                            const QString& url,
0043                            const QString& toolName,
0044                            const QStringList& args,
0045                            const QString& desc)
0046     : m_checkVersion        (false),
0047       m_headerStarts        (QLatin1String("")),
0048       m_headerLine          (0),
0049       m_minimalVersion      (QLatin1String("")),
0050       m_configGroup         (!toolName.isEmpty() ? QString::fromLatin1("%1 Settings").arg(toolName) : QLatin1String("")),
0051       m_binaryBaseName      (goodBaseName(binaryName)),
0052       m_binaryArguments     (args),
0053       m_projectName         (projectName),
0054       m_url                 (QUrl(url)),
0055       m_isFound             (false),
0056       m_hasError            (false),
0057       m_developmentVersion  (false),
0058       m_version             (QLatin1String("")),
0059       m_pathDir             (QLatin1String("")),
0060       m_description         (desc),
0061       m_pathWidget          (nullptr),
0062       m_binaryLabel         (nullptr),
0063       m_versionLabel        (nullptr),
0064       m_pathButton          (nullptr),
0065       m_downloadButton      (nullptr),
0066       m_lineEdit            (nullptr),
0067       m_statusIcon          (nullptr)
0068 {
0069 }
0070 
0071 DBinaryIface::DBinaryIface(const QString& binaryName,
0072                            const QString& minimalVersion,
0073                            const QString& header,
0074                            const int headerLine,
0075                            const QString& projectName,
0076                            const QString& url,
0077                            const QString& toolName,
0078                            const QStringList& args,
0079                            const QString& desc)
0080     : m_checkVersion        (true),
0081       m_headerStarts        (header),
0082       m_headerLine          (headerLine),
0083       m_minimalVersion      (minimalVersion),
0084       m_configGroup         (!toolName.isEmpty() ? QString::fromLatin1("%1 Settings").arg(toolName) : QLatin1String("")),
0085       m_binaryBaseName      (goodBaseName(binaryName)),
0086       m_binaryArguments     (args),
0087       m_projectName         (projectName),
0088       m_url                 (QUrl(url)),
0089       m_isFound             (false),
0090       m_hasError            (false),
0091       m_developmentVersion  (false),
0092       m_version             (QLatin1String("")),
0093       m_pathDir             (QLatin1String("")),
0094       m_description         (desc),
0095       m_pathWidget          (nullptr),
0096       m_binaryLabel         (nullptr),
0097       m_versionLabel        (nullptr),
0098       m_pathButton          (nullptr),
0099       m_downloadButton      (nullptr),
0100       m_lineEdit            (nullptr),
0101       m_statusIcon          (nullptr)
0102 {
0103 }
0104 
0105 DBinaryIface::~DBinaryIface()
0106 {
0107 }
0108 
0109 const QString& DBinaryIface::version() const
0110 {
0111     return m_version;
0112 }
0113 
0114 bool DBinaryIface::versionIsRight() const
0115 {
0116     if (!m_checkVersion)
0117     {
0118         return true;
0119     }
0120 
0121     QRegularExpression verRegExp(QLatin1String("^(\\d*[.]\\d*)"));
0122     float floatVersion = verRegExp.match(version()).captured(0).toFloat();
0123 
0124     return (!version().isNull() &&
0125             isFound()           &&
0126             (floatVersion >= minimalVersion().toFloat()));
0127 }
0128 
0129 bool DBinaryIface::versionIsRight(const float customVersion) const
0130 {
0131     if (!m_checkVersion)
0132     {
0133         return true;
0134     }
0135 
0136     QRegularExpression verRegExp(QLatin1String("^(\\d*[.]\\d*)"));
0137     float floatVersion = verRegExp.match(version()).captured(0).toFloat();
0138 
0139     qCDebug(DIGIKAM_GENERAL_LOG) << "Found (" << isFound()
0140                                  << ") :: Version : " << version()
0141                                  << "(" << floatVersion
0142                                  << ")  [" << customVersion << "]";
0143 
0144     return (!version().isNull() &&
0145             isFound()           &&
0146             (floatVersion >= customVersion));
0147 }
0148 
0149 QString DBinaryIface::findHeader(const QStringList& output, const QString& header) const
0150 {
0151     Q_FOREACH (const QString& s, output)
0152     {
0153         if (s.startsWith(header))
0154         {   // cppcheck-suppress useStlAlgorithm
0155             return s;
0156         }
0157     }
0158 
0159     return QString();
0160 }
0161 
0162 bool DBinaryIface::parseHeader(const QString& output)
0163 {
0164     QString firstLine = output.section(QLatin1Char('\n'), m_headerLine, m_headerLine);
0165     qCDebug(DIGIKAM_GENERAL_LOG) << path() << " help header line: \n" << firstLine;
0166 
0167     if (firstLine.startsWith(m_headerStarts))
0168     {
0169         QString version = firstLine.remove(0, m_headerStarts.length());
0170 
0171         if (version.startsWith(QLatin1String("Pre-Release ")))
0172         {
0173             version.remove(QLatin1String("Pre-Release "));            // Special case with Hugin beta.
0174             m_developmentVersion = true;
0175         }
0176 
0177         setVersion(version);
0178         return true;
0179     }
0180 
0181     return false;
0182 }
0183 
0184 void DBinaryIface::setVersion(QString& version)
0185 {
0186     QRegularExpression verRegExp(QLatin1String("\\d*(\\.\\d+)*"));
0187     m_version = verRegExp.match(version).captured(0);
0188 }
0189 
0190 void DBinaryIface::slotNavigateAndCheck()
0191 {
0192     QUrl start;
0193 
0194     if (isValid() && !m_pathDir.isEmpty())
0195     {
0196         start = QUrl::fromLocalFile(m_pathDir);
0197     }
0198     else
0199     {
0200 
0201 #if defined Q_OS_MACOS
0202 
0203         start = QUrl::fromLocalFile(QLatin1String("/Applications/"));
0204 
0205 #elif defined Q_OS_WIN
0206 
0207         start = QUrl::fromLocalFile(QLatin1String("C:/Program Files/"));
0208 
0209 #else
0210 
0211         start = QUrl::fromLocalFile(QLatin1String("/usr/bin/"));
0212 
0213 #endif
0214 
0215     }
0216 
0217     QString f   = DFileDialog::getOpenFileName(nullptr, i18nc("@title:window", "Navigate to %1", m_binaryBaseName),
0218                                                start.toLocalFile(),
0219                                                m_binaryBaseName);
0220 
0221     QString dir = QUrl::fromLocalFile(f).adjusted(QUrl::RemoveFilename).toLocalFile();
0222     m_searchPaths << dir;
0223 
0224     if (checkDirForPath(dir))
0225     {
0226         Q_EMIT signalSearchDirectoryAdded(dir);
0227     }
0228 }
0229 
0230 void DBinaryIface::slotAddPossibleSearchDirectory(const QString& dir)
0231 {
0232     if (!isValid())
0233     {
0234         m_searchPaths << dir;
0235         checkDirForPath(dir);
0236     }
0237     else
0238     {
0239         m_searchPaths << dir;
0240     }
0241 }
0242 
0243 void DBinaryIface::slotAddSearchDirectory(const QString& dir)
0244 {
0245     m_searchPaths << dir;
0246     checkDirForPath(dir);       // Forces the use of that directory
0247 }
0248 
0249 QString DBinaryIface::readConfig()
0250 {
0251     if (m_configGroup.isEmpty())
0252     {
0253         return QLatin1String("");
0254     }
0255 
0256     KSharedConfigPtr config = KSharedConfig::openConfig();
0257     KConfigGroup group      = config->group(m_configGroup);
0258 
0259     return group.readPathEntry(QString::fromUtf8("%1Binary").arg(m_binaryBaseName), QLatin1String(""));
0260 }
0261 
0262 void DBinaryIface::writeConfig()
0263 {
0264     if (m_configGroup.isEmpty())
0265     {
0266         return;
0267     }
0268 
0269     KSharedConfigPtr config = KSharedConfig::openConfig();
0270     KConfigGroup group      = config->group(m_configGroup);
0271     group.writePathEntry(QString::fromUtf8("%1Binary").arg(m_binaryBaseName), m_pathDir);
0272 }
0273 
0274 QString DBinaryIface::path(const QString& dir) const
0275 {
0276     if (dir.isEmpty())
0277     {
0278         return baseName();
0279     }
0280 
0281     if (dir.endsWith(QLatin1Char('/')))
0282     {
0283         return QString::fromUtf8("%1%2").arg(dir).arg(baseName());
0284     }
0285 
0286     return QString::fromUtf8("%1/%2").arg(dir).arg(baseName());
0287 }
0288 
0289 void DBinaryIface::setup(const QString& prev)
0290 {
0291     QString previousDir = prev;
0292 
0293     if (!previousDir.isEmpty())
0294     {
0295         m_searchPaths << previousDir;
0296         checkDirForPath(previousDir);
0297 
0298         return;
0299     }
0300 
0301     previousDir         = readConfig();
0302     m_searchPaths << previousDir;
0303     checkDirForPath(previousDir);
0304 
0305     if ((!previousDir.isEmpty()) && !isValid())
0306     {
0307         m_searchPaths << QLatin1String("");
0308         checkDirForPath(QLatin1String(""));
0309     }
0310 }
0311 
0312 bool DBinaryIface::checkDirForPath(const QString& possibleDir)
0313 {
0314     bool ret             = false;
0315     QString possiblePath = path(possibleDir);
0316 
0317     qCDebug(DIGIKAM_GENERAL_LOG) << "Testing " << possiblePath << "...";
0318 
0319     if (m_binaryArguments.isEmpty())
0320     {
0321         if (QFile::exists(possiblePath))
0322         {
0323             m_pathDir = possibleDir;
0324             m_isFound = true;
0325             writeConfig();
0326 
0327             qCDebug(DIGIKAM_GENERAL_LOG) << "Found " << path();
0328             ret       = true;
0329         }
0330     }
0331     else
0332     {
0333         QProcess process;
0334         process.setProcessChannelMode(QProcess::MergedChannels);
0335         process.setProcessEnvironment(adjustedEnvironmentForAppImage());
0336         process.start(possiblePath, m_binaryArguments);
0337 
0338         bool val = process.waitForFinished();
0339 
0340         if (val && (process.error() != QProcess::FailedToStart))
0341         {
0342             m_isFound = true;
0343 
0344             if (m_checkVersion)
0345             {
0346                 QString stdOut = QString::fromUtf8(process.readAllStandardOutput());
0347 
0348                 if (parseHeader(stdOut))
0349                 {
0350                     m_pathDir = possibleDir;
0351                     writeConfig();
0352 
0353                     qCDebug(DIGIKAM_GENERAL_LOG) << "Found " << path() << " version: " << version();
0354                     ret       = true;
0355                 }
0356                 else
0357                 {
0358                     m_hasError = true;
0359                 }
0360             }
0361             else
0362             {
0363                 m_pathDir = possibleDir;
0364                 writeConfig();
0365 
0366                 qCDebug(DIGIKAM_GENERAL_LOG) << "Found " << path();
0367                 ret       = true;
0368             }
0369         }
0370         else if (QFile::exists(possiblePath))
0371         {
0372             m_hasError = true;
0373         }
0374     }
0375 
0376     Q_EMIT signalBinaryValid();
0377 
0378     return ret;
0379 }
0380 
0381 bool DBinaryIface::recheckDirectories()
0382 {
0383     if (isValid())
0384     {
0385         // No need for recheck if it is already valid...
0386 
0387         return true;
0388     }
0389 
0390     Q_FOREACH (const QString& dir, m_searchPaths)
0391     {
0392         checkDirForPath(dir);
0393 
0394         if (isValid())
0395         {
0396             return true;
0397         }
0398     }
0399 
0400     return false;
0401 }
0402 
0403 QString DBinaryIface::goodBaseName(const QString& b)
0404 {
0405 
0406 #ifdef Q_OS_WIN
0407 
0408     if (b.endsWith(QLatin1String(".jar")))
0409     {
0410         // Special case if we check a java archive.
0411 
0412         return b;
0413     }
0414     else
0415     {
0416         return b + QLatin1String(".exe");
0417     }
0418 
0419 #else
0420 
0421     return b;
0422 
0423 #endif // Q_OS_WIN
0424 
0425 }
0426 
0427 } // namespace Digikam
0428 
0429 #include "moc_dbinaryiface.cpp"