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"