diff --git a/.gitignore b/.gitignore index f147edf..9b6fe97 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* + +# ignore build directory +build +.kdev4/ + +launch.kdev4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 41dcab3..651e179 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ find_package(Qt5 COMPONENTS Widgets REQUIRED) add_executable(launch src/main.cpp + src/utils/finder.h + src/utils//finder.cpp ) target_link_libraries(launch PRIVATE Qt5::Widgets) diff --git a/src/main.cpp b/src/main.cpp index a6544b7..2cdbeda 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,8 +10,11 @@ #include #include #include +#include #include #include +#include +#include "utils/finder.h" /* * This tool handles four types of applications: @@ -125,55 +128,6 @@ void handleError(QDetachableProcess *p, QString errorString){ } } - -QFileInfoList findAppsInside(QStringList locationsContainingApps, QFileInfoList candidates, QString firstArg) -{ - foreach (QString directory, locationsContainingApps) { - QDirIterator it(directory, QDirIterator::NoIteratorFlags); - while (it.hasNext()) { - QString filename = it.next(); - // qDebug() << "probono: Processing" << filename; - QString nameWithoutSuffix = QFileInfo(QDir(filename).canonicalPath()).baseName(); - QFileInfo file(filename); - if (file.fileName() == firstArg + ".app"){ - QString AppCand = filename + "/" + nameWithoutSuffix; - qDebug() << "################### Checking" << AppCand; - if(QFileInfo(AppCand).exists() == true){ - qDebug() << "# Found" << AppCand; - candidates.append(AppCand); - } - } - else if (file.fileName() == firstArg + ".AppDir"){ - QString AppCand = filename + "/" + "AppRun"; - qDebug() << "################### Checking" << AppCand; - if(QFileInfo(AppCand).exists() == true){ - qDebug() << "# Found" << AppCand; - candidates.append(AppCand); - } - } - else if (file.fileName() == firstArg + ".desktop") { - // .desktop file - qDebug() << "# Found" << file.fileName() << "TODO: Parse it for Exec="; - } - else if (locationsContainingApps.contains(filename) == false && file.isDir() && filename.endsWith("/..") == false && filename.endsWith("/.") == false && filename.endsWith(".app") == false && filename.endsWith(".AppDir") == false) { - // Now we have a directory that is not an .app bundle nor an .AppDir - // Shall we descend into it? Only if it contains at least one application, to optimize for speed - // by not descending into directory trees that do not contain any applications at all. Can make - // a big difference. - QStringList nameFilter({"*.app", "*.AppDir", "*.desktop"}); - QDir directory(filename); - int numberOfAppsInDirectory = directory.entryList(nameFilter).length(); - if(numberOfAppsInDirectory > 0) { - qDebug() << "# Descending into" << filename; - QStringList locationsToBeChecked = {filename}; - candidates = findAppsInside(locationsToBeChecked, candidates, firstArg); - } - } - } - } - return candidates; -} - int main(int argc, char *argv[]) { @@ -224,7 +178,8 @@ int main(int argc, char *argv[]) } QDetachableProcess p; - + Finder finder; + // Check whether the first argument exists or is on the $PATH QString executable = nullptr; @@ -233,35 +188,11 @@ int main(int argc, char *argv[]) // First, try to find something we can launch at the path, // either an executable or an .AppDir or an .app bundle firstArg = args.first(); - if (QFile::exists(firstArg)){ - QFileInfo info = QFileInfo(firstArg); - if ( firstArg.endsWith(".AppDir") || firstArg.endsWith(".app") ){ - qDebug() << "# Found" << firstArg; - QString candidate; - if(firstArg.endsWith(".AppDir")) { - candidate = firstArg + "/AppRun"; - } - else { - // The .app could be a symlink, so we need to determine the nameWithoutSuffix from its target - QFileInfo fileInfo = QFileInfo(QDir(firstArg).canonicalPath()); - QString nameWithoutSuffix = QFileInfo(fileInfo.completeBaseName()).fileName(); - candidate = firstArg + "/" + nameWithoutSuffix; - } - - QFileInfo candinfo = QFileInfo(candidate); - if(candinfo.isExecutable()) { - executable = candidate; - } - - } - else if (info.isExecutable()){ - qDebug() << "# Found executable" << firstArg; - executable = args.first(); - } - } - + + executable = finder.getExecutable(firstArg); + // Second, try to find an executable file on the $PATH - if(executable == nullptr){ + if(executable == nullptr) { QString candidate = QStandardPaths::findExecutable(firstArg); if (candidate != "") { qDebug() << "Found" << candidate << "on the $PATH"; @@ -286,21 +217,52 @@ int main(int argc, char *argv[]) // Iterate recursively through locationsContainingApps searching for AppRun files in matchingly named AppDirs QFileInfoList candidates; - QString firstArgWithoutWellKnownSuffix = firstArg.replace(".AppDir", "").replace(".app", "").replace(".desktop" ,""); + QString firstArgWithoutWellKnownSuffix = firstArg.replace(".AppDir", "").replace(".app", "").replace(".desktop" ,"").replace(".AppImage", ""); - candidates = findAppsInside(locationsContainingApps, candidates, firstArgWithoutWellKnownSuffix); + candidates = finder.findAppsInside(locationsContainingApps, candidates, firstArgWithoutWellKnownSuffix); qDebug() << "Took" << timer.elapsed() << "milliseconds to find candidates via the filesystem"; qDebug() << "Candidates:" << candidates; - - foreach (QFileInfo candidate, candidates) { - // Now that we may have collected different candidates, decide on which one to use - // e.g., the one with the highest self-declared version number. Again, a database might come in handy here - // For now, just use the first one - qDebug() << "Selected candidate:" << candidate.absoluteFilePath(); - executable = candidate.absoluteFilePath(); - break; - } + + QFileInfo candidate = candidates.first().absoluteFilePath(); + QFileInfoList::iterator it; + + qDebug() << candidate; + + // Attempt version detection + if (candidates.size() > 1) { + + // todo: loop through and compare versions + + for (int i = 0; i < candidates.size(); i++) + { + if (!candidate.fileName().contains("-")) { + candidate = candidates[i].absoluteFilePath(); + continue; + } + + try { + QStringList previousVersion = candidate.fileName().split("-"); + QStringList curVersion = candidates[i].fileName().split("-"); + + QVersionNumber previousVer = QVersionNumber::fromString(previousVersion[1]); + QVersionNumber newVer = QVersionNumber::fromString(curVersion[1]); + + int compare = QVersionNumber::compare(previousVer, newVer); + qDebug() << compare; + if (compare == -1) { + // previous one is older, use newer one + candidate = candidates[i].absoluteFilePath(); + } + } catch(std::exception &e) { + // catch any exeption that may occure + qDebug() << "Failed to compare application versions"; + } + } + } + + qDebug() << "Selected candidate:" << candidate.absoluteFilePath(); + executable = candidate.absoluteFilePath(); } p.setProgram(executable); diff --git a/src/utils/finder.cpp b/src/utils/finder.cpp new file mode 100644 index 0000000..349b27a --- /dev/null +++ b/src/utils/finder.cpp @@ -0,0 +1,125 @@ +#include "finder.h" + +#include +#include +#include +#include +#include +#include + +QFileInfoList Finder::findAppsInside(QStringList locationsContainingApps, QFileInfoList candidates, QString firstArg) +{ + foreach (QString directory, locationsContainingApps) { + QDirIterator it(directory, QDirIterator::NoIteratorFlags); + while (it.hasNext()) { + QString filename = it.next(); + // qDebug() << "probono: Processing" << filename; + QString nameWithoutSuffix = QFileInfo(QDir(filename).canonicalPath()).baseName(); + QFileInfo file(filename); + + if (file.fileName() == firstArg + ".app") { + qDebug() << filename; + QString AppCand = filename + "/" + nameWithoutSuffix; + qDebug() << "################### Checking" << AppCand; + if(QFileInfo(AppCand).exists() == true){ + qDebug() << "# Found" << AppCand; + candidates.append(AppCand); + } + } + else if (file.fileName() == firstArg + ".AppDir") { + QString AppCand = filename + "/" + "AppRun"; + + qDebug() << "################### Checking" << AppCand; + if(QFileInfo(AppCand).exists() == true){ + qDebug() << "# Found" << AppCand; + candidates.append(AppCand); + } + } + else if (file.fileName() == firstArg + ".AppImage" || file.fileName() == firstArg.replace(" ", "_") + ".AppImage" || file.fileName().endsWith(".AppName") & file.fileName().startsWith(firstArg + "-") || file.fileName().startsWith(firstArg.replace(" ", "_") + "-")) { + QString AppCand = getExecutable(filename); + candidates.append(AppCand); + } + else if (file.fileName() == firstArg + ".desktop") { + // load the .desktop file for parsing - QSettings::IniFormat returns values as strings by default + // see https://doc.qt.io/qt-5/qsettings.html + QSettings desktopFile(filename, QSettings::IniFormat); + QString AppCand = desktopFile.value("Desktop Entry/Exec").toString(); + + // null safety check + if (AppCand != NULL) { + qDebug() << "# Found" << AppCand; + candidates.append(AppCand); + } + } + else if (locationsContainingApps.contains(filename) == false && file.isDir() && filename.endsWith("/..") == false && filename.endsWith("/.") == false && filename.endsWith(".app") == false && filename.endsWith(".AppDir") == false && filename.endsWith(".AppImage") == false) { + // Now we have a directory that is not an .app bundle nor an .AppDir + // Shall we descend into it? Only if it contains at least one application, to optimize for speed + // by not descending into directory trees that do not contain any applications at all. Can make + // a big difference. + QStringList nameFilter({"*.app", "*.AppDir", "*.desktop", "*.AppImage"}); + QDir directory(filename); + int numberOfAppsInDirectory = directory.entryList(nameFilter).length(); + if(numberOfAppsInDirectory > 0) { + qDebug() << "# Descending into" << filename; + QStringList locationsToBeChecked = {filename}; + candidates = findAppsInside(locationsToBeChecked, candidates, firstArg); + } + } + } + } + + return candidates; +} + +QString Finder::getExecutable(QString &firstArg) +{ + + QString executable = nullptr; + + // check if the file exists + /*if (!QFile::exists(firstArg)) { + // try replacing space with _ + firstArg = firstArg.replace(" ", "_"); + }*/ + + if (QFile::exists(firstArg)) { + // get the file info + QFileInfo info = QFileInfo(firstArg); + + if (firstArg.endsWith(".AppDir") || firstArg.endsWith(".app") || firstArg.endsWith(".AppImage")) { + qDebug() << "# Found" << firstArg; + + // The potential candidate + QString candidate; + + if(firstArg.endsWith(".AppDir")) { + candidate = firstArg + "/AppRun"; + } + else if (firstArg.endsWith(".AppImage")) { + // this is a .AppImage file, we have nothing else to do here, so just make it a candidate + candidate = firstArg; + } + else { + // The .app could be a symlink, so we need to determine the nameWithoutSuffix from its target + QFileInfo fileInfo = QFileInfo(QDir(firstArg).canonicalPath()); + QString nameWithoutSuffix = QFileInfo(fileInfo.completeBaseName()).fileName(); + candidate = firstArg + "/" + nameWithoutSuffix; + } + + QFileInfo candinfo = QFileInfo(candidate); + if (candinfo.isExecutable()) { + executable = candidate; + } + + } + else if (info.isExecutable()){ + qDebug() << "# Found executable" << firstArg; + executable = firstArg; + } + } + + return executable; +} + + + diff --git a/src/utils/finder.h b/src/utils/finder.h new file mode 100644 index 0000000..e04ec8d --- /dev/null +++ b/src/utils/finder.h @@ -0,0 +1,13 @@ +#ifndef FINDER_H +#define FINDER_H + +#include + +class Finder +{ +public: + QFileInfoList findAppsInside(QStringList locationsContainingApps, QFileInfoList candidates, QString firstArg); + QString getExecutable(QString &firstArg); +}; + +#endif