2020-09-06 19:40:46 +02:00
|
|
|
#include "entryprovider.h"
|
2018-01-03 09:52:05 +01:00
|
|
|
#include <QDebug>
|
2020-09-06 19:40:46 +02:00
|
|
|
#include <QDirIterator>
|
2018-01-03 09:52:05 +01:00
|
|
|
#include <QTextStream>
|
|
|
|
|
2020-09-06 19:40:46 +02:00
|
|
|
EntryProvider::EntryProvider(QStringList userEntriesDirsPaths, QStringList systemEntriesDirsPaths)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-09-06 19:40:46 +02:00
|
|
|
this->userEntriesDirsPaths = userEntriesDirsPaths;
|
|
|
|
this->systemEntriesDirsPaths = systemEntriesDirsPaths;
|
|
|
|
_desktopIgnoreArgs << "%F"
|
|
|
|
<< "%f"
|
|
|
|
<< "%U"
|
|
|
|
<< "%u";
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
|
|
|
|
2020-10-08 22:39:41 +02:00
|
|
|
bool EntryProvider::isSavable(const EntryConfig &config) const
|
|
|
|
{
|
|
|
|
return ! config.entryPath.isEmpty() && (config.type == EntryType::USER || config.type == EntryType::INHERIT);
|
|
|
|
}
|
|
|
|
|
2020-09-06 19:40:46 +02:00
|
|
|
EntryConfig EntryProvider::readFromDesktopFile(const QString &path)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
|
|
|
EntryConfig result;
|
|
|
|
QFile file(path);
|
2020-09-06 19:40:46 +02:00
|
|
|
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-09-06 19:40:46 +02:00
|
|
|
// TODO: better exception class
|
2020-10-19 21:45:18 +02:00
|
|
|
throw std::runtime_error("Failed to open file");
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
|
|
|
QTextStream stream(&file);
|
2020-09-06 19:40:46 +02:00
|
|
|
// There should be nothing preceding this group in the desktop entry file but possibly one or more comments.
|
|
|
|
// https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s03.html#group-header
|
2019-08-23 23:39:03 +02:00
|
|
|
QString firstLine;
|
|
|
|
do
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2019-08-23 23:39:03 +02:00
|
|
|
firstLine = stream.readLine().trimmed();
|
2020-09-06 19:40:46 +02:00
|
|
|
} while(!stream.atEnd() && (firstLine.isEmpty() || firstLine[0] == '#'));
|
2019-08-23 23:39:03 +02:00
|
|
|
|
|
|
|
if(firstLine != "[Desktop Entry]")
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2019-08-23 23:39:03 +02:00
|
|
|
throw ConfigFormatException(".desktop file does not start with [Desktop Entry]: " + path.toStdString());
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
while(!stream.atEnd())
|
|
|
|
{
|
|
|
|
QString line = stream.readLine();
|
2020-09-06 19:40:46 +02:00
|
|
|
// new group, so we are finished with [Desktop Entry]
|
2018-01-17 22:36:57 +01:00
|
|
|
if(line.startsWith("[") && line.endsWith("]"))
|
|
|
|
{
|
|
|
|
return result;
|
|
|
|
}
|
2019-08-23 23:39:03 +02:00
|
|
|
|
2020-09-06 19:40:46 +02:00
|
|
|
QString key = line.section('=', 0, 0).toLower();
|
2019-08-23 23:39:03 +02:00
|
|
|
QString args = line.section('=', 1);
|
|
|
|
if(key == "name")
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2019-08-23 23:39:03 +02:00
|
|
|
if(result.name.length() == 0)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2019-08-23 23:39:03 +02:00
|
|
|
result.name = args;
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
2019-08-23 23:39:03 +02:00
|
|
|
}
|
|
|
|
if(key == "icon")
|
|
|
|
{
|
2020-09-27 17:00:28 +02:00
|
|
|
result.iconPath = args;
|
2019-08-23 23:39:03 +02:00
|
|
|
}
|
|
|
|
if(key == "exec")
|
|
|
|
{
|
|
|
|
QStringList arguments = args.split(" ");
|
2018-01-03 09:52:05 +01:00
|
|
|
|
2019-08-23 23:39:03 +02:00
|
|
|
result.command = arguments[0];
|
|
|
|
arguments = arguments.mid(1);
|
|
|
|
if(arguments.length() > 1)
|
|
|
|
{
|
|
|
|
for(QString &arg : arguments)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-09-06 19:40:46 +02:00
|
|
|
if(!_desktopIgnoreArgs.contains(arg))
|
2018-01-17 22:36:57 +01:00
|
|
|
{
|
2019-08-23 23:39:03 +02:00
|
|
|
result.arguments.append(arg);
|
2018-01-17 22:36:57 +01:00
|
|
|
}
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-06 22:08:54 +02:00
|
|
|
if(key == "nodisplay")
|
|
|
|
{
|
|
|
|
result.hidden = args == "true";
|
|
|
|
}
|
2020-10-04 22:16:49 +02:00
|
|
|
if(key == "terminal")
|
|
|
|
{
|
|
|
|
result.isTerminalCommand = args == "true";
|
|
|
|
}
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
result.type = EntryType::SYSTEM;
|
2018-01-03 09:52:05 +01:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-09-06 22:23:36 +02:00
|
|
|
std::optional<EntryConfig> EntryProvider::readEntryFromPath(const QString &path)
|
|
|
|
{
|
|
|
|
QFileInfo info(path);
|
|
|
|
if(info.isFile())
|
|
|
|
{
|
|
|
|
QString suffix = info.suffix();
|
|
|
|
if(suffix == "desktop")
|
|
|
|
{
|
|
|
|
return readFromDesktopFile(path);
|
|
|
|
}
|
|
|
|
if(suffix == "qsrun")
|
|
|
|
{
|
|
|
|
return readqsrunFile(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
/* qsrun's own format */
|
|
|
|
EntryConfig EntryProvider::readqsrunFile(const QString &path)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
|
|
|
EntryConfig result;
|
2020-09-06 19:40:46 +02:00
|
|
|
EntryConfig inheritedConfig;
|
2018-01-03 09:52:05 +01:00
|
|
|
QFile file(path);
|
2020-09-06 19:40:46 +02:00
|
|
|
if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-09-06 19:40:46 +02:00
|
|
|
// TODO: better exception class
|
2020-10-19 21:45:18 +02:00
|
|
|
throw std::runtime_error("Failed to open file");
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
2020-10-04 15:46:03 +02:00
|
|
|
QHash<QString, QString> map;
|
2018-01-03 09:52:05 +01:00
|
|
|
QTextStream stream(&file);
|
|
|
|
while(!stream.atEnd())
|
|
|
|
{
|
|
|
|
QString line = stream.readLine();
|
2020-10-04 15:46:03 +02:00
|
|
|
|
|
|
|
int spacePos = line.indexOf(' ');
|
|
|
|
if(spacePos == -1)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-10-19 21:45:18 +02:00
|
|
|
throw ConfigFormatException("misformated line in .qsrun config file " + path.toStdString());
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
2020-10-04 15:46:03 +02:00
|
|
|
|
|
|
|
QString key = line.mid(0, spacePos);
|
2020-10-04 19:30:41 +02:00
|
|
|
QString value = line.mid(spacePos + 1);
|
2020-10-04 15:46:03 +02:00
|
|
|
|
|
|
|
if(key == "" || value == "")
|
|
|
|
{
|
2020-10-19 21:45:18 +02:00
|
|
|
throw ConfigFormatException("empty key or value in .qsrun config file " + path.toStdString());
|
2020-10-04 15:46:03 +02:00
|
|
|
}
|
|
|
|
map[key] = value;
|
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
|
2020-10-04 18:32:22 +02:00
|
|
|
if(map.contains("inherit"))
|
|
|
|
{
|
|
|
|
auto entry = readEntryFromPath(map["inherit"]);
|
|
|
|
if(entry)
|
|
|
|
{
|
|
|
|
result = entry.value();
|
|
|
|
result.inherit = map["inherit"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-10-19 21:45:18 +02:00
|
|
|
throw ConfigFormatException("Error attempting to read inherited entry");
|
2020-10-04 18:32:22 +02:00
|
|
|
}
|
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
QString type = map["type"];
|
|
|
|
if(!type.isEmpty())
|
2020-10-04 15:46:03 +02:00
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
if(type == "system")
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-10-19 21:45:18 +02:00
|
|
|
throw ConfigFormatException(".qsrun files cannot be designated as system entries " +
|
2020-10-04 19:30:41 +02:00
|
|
|
path.toStdString());
|
|
|
|
}
|
|
|
|
else if(type == "inherit")
|
|
|
|
{
|
|
|
|
result.type = EntryType::INHERIT;
|
|
|
|
}
|
|
|
|
else if(type == "user")
|
|
|
|
{
|
|
|
|
result.type = EntryType::USER;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-10-19 21:45:18 +02:00
|
|
|
throw ConfigFormatException("Invalid value for type provided in file: " + path.toStdString());
|
2020-10-04 19:30:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result.type = EntryType::USER;
|
|
|
|
}
|
|
|
|
if(result.type != EntryType::INHERIT)
|
|
|
|
{
|
|
|
|
if(map.contains("arguments"))
|
|
|
|
{
|
|
|
|
auto args = map["arguments"].split(' ');
|
|
|
|
QString merged;
|
|
|
|
for(QString &str : args)
|
2019-06-09 13:06:14 +02:00
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
if(str.startsWith('"') && !str.endsWith("'"))
|
|
|
|
{
|
|
|
|
merged += str.mid(1) + " ";
|
|
|
|
}
|
|
|
|
else if(str.endsWith('"'))
|
|
|
|
{
|
|
|
|
str.chop(1);
|
|
|
|
merged += str;
|
|
|
|
result.arguments.append(merged);
|
|
|
|
merged = "";
|
|
|
|
}
|
|
|
|
else if(merged != "")
|
|
|
|
{
|
|
|
|
merged += str + " ";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result.arguments.append(str);
|
|
|
|
}
|
2020-10-04 15:46:03 +02:00
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
if(merged != "")
|
2020-10-04 15:46:03 +02:00
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
throw ConfigFormatException("non-closed \" in config file " + path.toStdString());
|
2020-10-04 15:46:03 +02:00
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
}
|
|
|
|
auto assignIfSourceNotEmpty = [](QString source, QString &val) {
|
|
|
|
if(!source.isEmpty())
|
2020-10-04 15:46:03 +02:00
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
val = source;
|
2019-06-09 13:06:14 +02:00
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
};
|
|
|
|
assignIfSourceNotEmpty(map["key"].toLower(), result.key);
|
|
|
|
assignIfSourceNotEmpty(map["command"], result.command);
|
|
|
|
assignIfSourceNotEmpty(map["icon"], result.iconPath);
|
|
|
|
assignIfSourceNotEmpty(map["name"], result.name);
|
2020-10-04 15:46:03 +02:00
|
|
|
}
|
|
|
|
result.col = map["col"].toInt();
|
|
|
|
result.row = map["row"].toInt();
|
2020-10-05 22:50:53 +02:00
|
|
|
result.isTerminalCommand = map["terminal"] == "true";
|
2020-10-04 18:32:22 +02:00
|
|
|
return result;
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
|
|
|
|
2020-09-06 19:40:46 +02:00
|
|
|
QString EntryProvider::resolveEntryPath(QString path)
|
|
|
|
{
|
|
|
|
if(path.trimmed().isEmpty())
|
|
|
|
{
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
if(path[0] == '/')
|
|
|
|
{
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
QStringList paths = this->userEntriesDirsPaths + this->systemEntriesDirsPaths;
|
|
|
|
for(QString &configPath : paths)
|
|
|
|
{
|
|
|
|
QDir dir(configPath);
|
|
|
|
QString desktopFilePath = dir.absoluteFilePath(path);
|
|
|
|
if(QFileInfo::exists(desktopFilePath))
|
|
|
|
{
|
|
|
|
return desktopFilePath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2020-10-04 19:30:41 +02:00
|
|
|
QVector<EntryConfig> EntryProvider::readConfig(QStringList paths)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
|
|
|
QVector<EntryConfig> result;
|
2020-09-06 19:40:46 +02:00
|
|
|
for(QString &configPath : paths)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2019-09-01 20:48:39 +02:00
|
|
|
QDirIterator it(configPath, QDirIterator::Subdirectories);
|
2019-05-30 10:57:33 +02:00
|
|
|
while(it.hasNext())
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2019-05-30 10:57:33 +02:00
|
|
|
QString path = it.next();
|
2020-09-06 22:23:36 +02:00
|
|
|
std::optional<EntryConfig> entry = readEntryFromPath(path);
|
|
|
|
if(entry)
|
2018-01-03 09:52:05 +01:00
|
|
|
{
|
2020-09-06 22:23:36 +02:00
|
|
|
if(!entry->hidden)
|
2020-09-06 22:08:54 +02:00
|
|
|
{
|
2020-09-13 18:59:44 +02:00
|
|
|
entry->entryPath = path;
|
2020-09-06 22:23:36 +02:00
|
|
|
result.append(*entry);
|
2019-05-30 10:57:33 +02:00
|
|
|
}
|
2018-01-03 09:52:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2020-09-06 19:40:46 +02:00
|
|
|
|
|
|
|
QVector<EntryConfig> EntryProvider::getUserEntries()
|
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
return readConfig(this->userEntriesDirsPaths);
|
2020-09-06 19:40:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QVector<EntryConfig> EntryProvider::getSystemEntries()
|
|
|
|
{
|
|
|
|
return readConfig(this->systemEntriesDirsPaths);
|
|
|
|
}
|
2020-09-06 21:37:15 +02:00
|
|
|
|
2020-09-13 18:59:44 +02:00
|
|
|
void EntryProvider::saveUserEntry(const EntryConfig &config)
|
|
|
|
{
|
2020-10-08 22:39:41 +02:00
|
|
|
if(!isSavable(config))
|
2020-09-13 18:59:44 +02:00
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
throw std::runtime_error("Only user/inherited entries can be saved");
|
2020-09-13 18:59:44 +02:00
|
|
|
}
|
2020-09-13 21:08:17 +02:00
|
|
|
QString transitPath = config.entryPath + ".transit";
|
|
|
|
QFile file{transitPath};
|
|
|
|
if(!file.open(QIODevice::WriteOnly))
|
|
|
|
{
|
|
|
|
throw std::runtime_error("Error: Can not open file for writing");
|
|
|
|
}
|
|
|
|
QTextStream outStream(&file);
|
2020-10-04 19:30:41 +02:00
|
|
|
outStream << "type" << " " << ((config.type == EntryType::USER) ? "user" : "inherit") << endl;
|
2020-09-13 21:08:17 +02:00
|
|
|
if(!config.inherit.isEmpty())
|
|
|
|
{
|
|
|
|
outStream << "inherit" << " " << config.inherit << endl;
|
|
|
|
}
|
|
|
|
outStream << "row" << " " << config.row << endl;
|
|
|
|
outStream << "col" << " " << config.col << endl;
|
2020-10-04 21:36:40 +02:00
|
|
|
outStream << "hidden" << " " << config.hidden << endl;
|
2020-10-19 21:29:51 +02:00
|
|
|
if(!config.key.isEmpty())
|
|
|
|
{
|
|
|
|
outStream << "key" << " " << config.key << endl;
|
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
if(config.type == EntryType::USER)
|
|
|
|
{
|
|
|
|
if(!config.name.isEmpty())
|
|
|
|
{
|
|
|
|
outStream << "name" << " " << config.name << endl;
|
|
|
|
}
|
|
|
|
if(!config.command.isEmpty())
|
|
|
|
{
|
|
|
|
outStream << "command" << " " << config.command << endl;
|
|
|
|
}
|
|
|
|
if(!config.iconPath.isEmpty())
|
|
|
|
{
|
|
|
|
outStream << "icon" << " " << config.iconPath << endl;
|
|
|
|
}
|
2020-10-04 21:36:40 +02:00
|
|
|
if(!config.arguments.empty())
|
|
|
|
{
|
|
|
|
outStream << "arguments" << " " << config.arguments.join(' ') << endl;
|
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
}
|
|
|
|
|
2020-09-13 21:08:17 +02:00
|
|
|
outStream.flush();
|
|
|
|
file.close();
|
|
|
|
|
|
|
|
// Qts don't work if file already exists and c++17... don't want to pull in the fs lib yet
|
|
|
|
int ret = rename(transitPath.toStdString().c_str(), config.entryPath.toStdString().c_str());
|
|
|
|
if(ret != 0)
|
|
|
|
{
|
|
|
|
qDebug() << strerror(errno);
|
|
|
|
throw std::runtime_error("Failed to save entry file: Error during rename");
|
|
|
|
}
|
2020-09-13 18:59:44 +02:00
|
|
|
}
|
|
|
|
|
2020-09-27 22:28:16 +02:00
|
|
|
bool EntryProvider::deleteUserEntry(const EntryConfig &config)
|
|
|
|
{
|
2020-10-08 22:39:41 +02:00
|
|
|
if(!isSavable(config))
|
2020-09-27 22:28:16 +02:00
|
|
|
{
|
2020-10-04 19:30:41 +02:00
|
|
|
throw std::runtime_error("Only user/inherited entries can be deleted");
|
2020-09-27 22:28:16 +02:00
|
|
|
}
|
2020-10-04 19:30:41 +02:00
|
|
|
QFile file{config.entryPath};
|
2020-09-27 22:28:16 +02:00
|
|
|
return file.remove();
|
|
|
|
}
|
|
|
|
|
2020-09-06 21:37:15 +02:00
|
|
|
template <class T> void assignIfDestDefault(T &dest, const T &source)
|
|
|
|
{
|
|
|
|
if(dest == T())
|
|
|
|
{
|
|
|
|
dest = source;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EntryConfig &EntryConfig::update(const EntryConfig &o)
|
|
|
|
{
|
|
|
|
assignIfDestDefault(this->arguments, o.arguments);
|
|
|
|
assignIfDestDefault(this->col, o.col);
|
|
|
|
assignIfDestDefault(this->command, o.command);
|
2020-09-27 17:00:28 +02:00
|
|
|
if(this->iconPath.isEmpty())
|
2020-09-06 21:37:15 +02:00
|
|
|
{
|
2020-09-27 17:00:28 +02:00
|
|
|
this->iconPath = o.iconPath;
|
2020-09-06 21:37:15 +02:00
|
|
|
}
|
|
|
|
assignIfDestDefault(this->key, o.key);
|
|
|
|
assignIfDestDefault(this->name, o.name);
|
|
|
|
assignIfDestDefault(this->row, o.row);
|
2020-09-28 18:52:47 +02:00
|
|
|
assignIfDestDefault(this->hidden, o.hidden);
|
|
|
|
assignIfDestDefault(this->inherit, o.inherit);
|
|
|
|
assignIfDestDefault(this->entryPath, o.entryPath);
|
2020-10-04 19:30:41 +02:00
|
|
|
assignIfDestDefault(this->type, o.type);
|
2020-09-06 21:37:15 +02:00
|
|
|
return *this;
|
|
|
|
}
|