Commit e90b2311 authored by Nicolas Follet's avatar Nicolas Follet

feat(app): provide a cli

parent e26dcceb
...@@ -93,6 +93,7 @@ endif () ...@@ -93,6 +93,7 @@ endif ()
set(SOURCES set(SOURCES
src/app/App.cpp src/app/App.cpp
src/app/cli/Cli.cpp
src/app/logger/Logger.cpp src/app/logger/Logger.cpp
src/app/paths/Paths.cpp src/app/paths/Paths.cpp
src/app/providers/AvatarProvider.cpp src/app/providers/AvatarProvider.cpp
...@@ -141,6 +142,7 @@ set(SOURCES ...@@ -141,6 +142,7 @@ set(SOURCES
set(HEADERS set(HEADERS
src/app/App.hpp src/app/App.hpp
src/app/cli/Cli.hpp
src/app/logger/Logger.hpp src/app/logger/Logger.hpp
src/app/paths/Paths.hpp src/app/paths/Paths.hpp
src/app/providers/AvatarProvider.hpp src/app/providers/AvatarProvider.hpp
......
...@@ -64,6 +64,18 @@ ...@@ -64,6 +64,18 @@
<source>commandLineOptionIconified</source> <source>commandLineOptionIconified</source>
<translation>Start in the system tray, do not show the main interface.</translation> <translation>Start in the system tray, do not show the main interface.</translation>
</message> </message>
<message>
<source>commandLineOptionConfigArg</source>
<translation>file</translation>
</message>
<message>
<source>commandLineOptionCmd</source>
<translation>Run a command line.</translation>
</message>
<message>
<source>commandLineOptionCmdArg</source>
<translation>command line</translation>
</message>
</context> </context>
<context> <context>
<name>AssistantAbstractView</name> <name>AssistantAbstractView</name>
...@@ -387,6 +399,17 @@ ...@@ -387,6 +399,17 @@
Server url not configured.</translation> Server url not configured.</translation>
</message> </message>
</context> </context>
<context>
<name>Cli</name>
<message>
<source>showFunctionDescription</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>showFunctionCall</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>CodecsViewer</name> <name>CodecsViewer</name>
<message> <message>
......
...@@ -64,6 +64,18 @@ ...@@ -64,6 +64,18 @@
<source>commandLineOptionIconified</source> <source>commandLineOptionIconified</source>
<translation>Lancer l&apos;application dans la zone de notification.</translation> <translation>Lancer l&apos;application dans la zone de notification.</translation>
</message> </message>
<message>
<source>commandLineOptionConfigArg</source>
<translation>file</translation>
</message>
<message>
<source>commandLineOptionCmd</source>
<translation>Éxécuter une ligne de commande.</translation>
</message>
<message>
<source>commandLineOptionCmdArg</source>
<translation>ligne de commande</translation>
</message>
</context> </context>
<context> <context>
<name>AssistantAbstractView</name> <name>AssistantAbstractView</name>
...@@ -387,6 +399,17 @@ ...@@ -387,6 +399,17 @@
Url du serveur non configurée.</translation> Url du serveur non configurée.</translation>
</message> </message>
</context> </context>
<context>
<name>Cli</name>
<message>
<source>showFunctionDescription</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>showFunctionCall</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>CodecsViewer</name> <name>CodecsViewer</name>
<message> <message>
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include "providers/AvatarProvider.hpp" #include "providers/AvatarProvider.hpp"
#include "providers/ThumbnailProvider.hpp" #include "providers/ThumbnailProvider.hpp"
#include "translator/DefaultTranslator.hpp" #include "translator/DefaultTranslator.hpp"
#include "cli/Cli.hpp"
#include "App.hpp" #include "App.hpp"
...@@ -139,16 +140,13 @@ void App::initContentApp () { ...@@ -139,16 +140,13 @@ void App::initContentApp () {
// Don't quit if last window is closed!!! // Don't quit if last window is closed!!!
setQuitOnLastWindowClosed(false); setQuitOnLastWindowClosed(false);
QObject::connect( QObject::connect(this, &App::receivedMessage, this, [this](int, const QByteArray &byteArray) {
this, &App::receivedMessage, this, [this](int, QByteArray byteArray) { QString command(byteArray);
QString message(byteArray); qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command);
executeCommand(command);
});
qInfo() << QStringLiteral("Received message from other application: `%1`.").arg(message); mCli = new Cli(this);
if (message == "show")
smartShowWindow(getMainWindow());
}
);
} }
// Init core. // Init core.
...@@ -207,12 +205,13 @@ void App::parseArgs () { ...@@ -207,12 +205,13 @@ void App::parseArgs () {
mParser.addHelpOption(); mParser.addHelpOption();
mParser.addVersionOption(); mParser.addVersionOption();
mParser.addOptions({ mParser.addOptions({
{ "config", tr("commandLineOptionConfig"), "file" }, { "config", tr("commandLineOptionConfig"), tr("commandLineOptionConfigArg") },
#ifndef __APPLE__ #ifndef __APPLE__
{ "iconified", tr("commandLineOptionIconified") }, { "iconified", tr("commandLineOptionIconified") },
#endif // ifndef __APPLE__ #endif // ifndef __APPLE__
{ "self-test", tr("commandLineOptionSelfTest") }, { "self-test", tr("commandLineOptionSelfTest") },
{ { "V", "verbose" }, tr("commandLineOptionVerbose") } { { "V", "verbose" }, tr("commandLineOptionVerbose") },
{ { "c", "cmd" }, tr("commandLineOptionCmd"), tr("commandLineOptionCmdArg") }
}); });
mParser.process(*this); mParser.process(*this);
...@@ -228,6 +227,18 @@ void App::parseArgs () { ...@@ -228,6 +227,18 @@ void App::parseArgs () {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
QString App::getCommandArgument () {
return mParser.value("cmd");
}
// -----------------------------------------------------------------------------
void App::executeCommand (const QString &command) {
mCli->executeCommand(command);
}
// -----------------------------------------------------------------------------
void App::tryToUsePreferredLocale () { void App::tryToUsePreferredLocale () {
QString locale = getConfigLocale(); QString locale = getConfigLocale();
...@@ -267,15 +278,13 @@ QQuickWindow *App::getMainWindow () const { ...@@ -267,15 +278,13 @@ QQuickWindow *App::getMainWindow () const {
QQuickWindow *App::getSettingsWindow () { QQuickWindow *App::getSettingsWindow () {
if (!mSettingsWindow) { if (!mSettingsWindow) {
mSettingsWindow = createSubWindow(this, QML_VIEW_SETTINGS_WINDOW); mSettingsWindow = createSubWindow(this, QML_VIEW_SETTINGS_WINDOW);
QObject::connect( QObject::connect(mSettingsWindow, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) {
mSettingsWindow, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) {
if (visibility == QWindow::Hidden) { if (visibility == QWindow::Hidden) {
qInfo() << QStringLiteral("Update nat policy."); qInfo() << QStringLiteral("Update nat policy.");
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore(); shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
core->setNatPolicy(core->getNatPolicy()); core->setNatPolicy(core->getNatPolicy());
} }
} });
);
} }
return mSettingsWindow; return mSettingsWindow;
...@@ -329,12 +338,9 @@ void registerMetaType (const char *name) { ...@@ -329,12 +338,9 @@ void registerMetaType (const char *name) {
template<class T> template<class T>
void registerSingletonType (const char *name) { void registerSingletonType (const char *name) {
qmlRegisterSingletonType<T>( qmlRegisterSingletonType<T>("Linphone", 1, 0, name, [](QQmlEngine *, QJSEngine *) -> QObject *{
"Linphone", 1, 0, name,
[](QQmlEngine *, QJSEngine *) -> QObject *{
return new T(); return new T();
} });
);
} }
template<class T> template<class T>
...@@ -408,8 +414,7 @@ void App::setTrayIcon () { ...@@ -408,8 +414,7 @@ void App::setTrayIcon () {
// trayIcon: Left click actions. // trayIcon: Left click actions.
QMenu *menu = new QMenu(); QMenu *menu = new QMenu();
root->connect( root->connect(systemTrayIcon, &QSystemTrayIcon::activated, [root](
systemTrayIcon, &QSystemTrayIcon::activated, [root](
QSystemTrayIcon::ActivationReason reason QSystemTrayIcon::ActivationReason reason
) { ) {
if (reason == QSystemTrayIcon::Trigger) { if (reason == QSystemTrayIcon::Trigger) {
...@@ -418,8 +423,7 @@ void App::setTrayIcon () { ...@@ -418,8 +423,7 @@ void App::setTrayIcon () {
else else
root->hide(); root->hide();
} }
} });
);
// Build trayIcon menu. // Build trayIcon menu.
menu->addAction(restoreAction); menu->addAction(restoreAction);
...@@ -491,6 +495,13 @@ void App::openAppAfterInit () { ...@@ -491,6 +495,13 @@ void App::openAppAfterInit () {
config->setInt(SettingsModel::UI_SECTION, "force_assistant_at_startup", 0); config->setInt(SettingsModel::UI_SECTION, "force_assistant_at_startup", 0);
} }
} }
// Execute command argument if needed.
{
const QString &commandArgument = getCommandArgument();
if (!commandArgument.isEmpty())
executeCommand(commandArgument);
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
// ============================================================================= // =============================================================================
class Cli;
class DefaultTranslator; class DefaultTranslator;
class App : public SingleApplication { class App : public SingleApplication {
...@@ -51,6 +52,9 @@ public: ...@@ -51,6 +52,9 @@ public:
void initContentApp (); void initContentApp ();
void parseArgs (); void parseArgs ();
QString getCommandArgument ();
void executeCommand (const QString &command);
void tryToUsePreferredLocale (); void tryToUsePreferredLocale ();
QQmlEngine *getEngine () { QQmlEngine *getEngine () {
...@@ -119,6 +123,8 @@ private: ...@@ -119,6 +123,8 @@ private:
QQuickWindow *mCallsWindow = nullptr; QQuickWindow *mCallsWindow = nullptr;
QQuickWindow *mSettingsWindow = nullptr; QQuickWindow *mSettingsWindow = nullptr;
Cli *mCli = nullptr;
}; };
#endif // APP_H_ #endif // APP_H_
/*
* Cli.cpp
* Copyright (C) 2017 Belledonne Communications, Grenoble, France
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Created on: June 6, 2017
* Author: Nicolas Follet
*/
#include <stdexcept>
#include <QObject>
#include "../../components/core/CoreManager.hpp"
#include "../../Utils.hpp"
#include "../App.hpp"
#include "Cli.hpp"
using namespace std;
// =============================================================================
// API.
// =============================================================================
static void cliShow (const QHash<QString, QString> &) {
App *app = App::getInstance();
app->smartShowWindow(app->getMainWindow());
}
static void cliCall (const QHash<QString, QString> &args) {
CoreManager::getInstance()->getCallsListModel()->launchAudioCall(args["sipAddress"]);
}
// =============================================================================
Cli::Command::Command (const QString &functionName, const QString &description, Cli::Function function, const QHash<QString, Cli::Argument> &argsScheme) :
mFunctionName(functionName),
mDescription(description),
mFunction(function),
mArgsScheme(argsScheme) {}
void Cli::Command::execute (const QHash<QString, QString> &args) {
for (const auto &argName : mArgsScheme.keys()) {
if (!args.contains(argName) && !mArgsScheme[argName].isOptional) {
qWarning() << QStringLiteral("Missing argument for command: `%1 (%2)`.")
.arg(mFunctionName).arg(argName);
return;
}
}
(*mFunction)(args);
}
// =============================================================================
Cli::Cli (QObject *parent) : QObject(parent) {
// FIXME: Do not accept args without value like: cmd toto.
// In the future `toto` could be a boolean argument.
mRegExpArgs = QRegExp("(?:(?:(\\w+)\\s*)=\\s*(?:\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"|([^\\s]+)\\s*))");
mRegExpFunctionName = QRegExp("^\\s*(\\w+)\\s*");
makeCommands();
}
// -----------------------------------------------------------------------------
void Cli::addCommand (const QString &functionName, const QString &description, Function function, const QHash<QString, Argument> &argsScheme) noexcept {
if (mCommands.contains(functionName))
qWarning() << QStringLiteral("Command already exists: `%1`.").arg(functionName);
else
mCommands[functionName] = Cli::Command(functionName, description, function, argsScheme);
}
void Cli::makeCommands () noexcept {
addCommand("show", tr("showFunctionDescription"), cliShow);
addCommand("call", tr("showFunctionCall"), cliCall, {
{ "sipAddress", {} }
});
}
// -----------------------------------------------------------------------------
void Cli::executeCommand (const QString &command) noexcept {
const QString &functionName = parseFunctionName(command);
if (functionName.isEmpty())
return;
bool soFarSoGood;
const QHash<QString, QString> &args = parseArgs(command, functionName, soFarSoGood);
if (!soFarSoGood)
return;
mCommands[functionName].execute(args);
}
// -----------------------------------------------------------------------------
const QString Cli::parseFunctionName (const QString &command) noexcept {
mRegExpFunctionName.indexIn(command);
const QStringList &texts = mRegExpFunctionName.capturedTexts();
if (texts.size() < 2) {
qWarning() << QStringLiteral("Unable to parse function name of command: `%1`.").arg(command);
return QStringLiteral("");
}
const QString functionName = texts[1];
if (!mCommands.contains(functionName)) {
qWarning() << QStringLiteral("This command doesn't exist: `%1`.").arg(functionName);
return QStringLiteral("");
}
return functionName;
}
const QHash<QString, QString> Cli::parseArgs (const QString &command, const QString functionName, bool &soFarSoGood) noexcept {
QHash<QString, QString> args;
int pos = 0;
soFarSoGood = true;
while ((pos = mRegExpArgs.indexIn(command, pos)) != -1) {
pos += mRegExpArgs.matchedLength();
if (!mCommands[functionName].argNameExists(mRegExpArgs.cap(1))) {
qWarning() << QStringLiteral("Command with invalid argument(s): `%1 (%2)`.")
.arg(functionName).arg(mRegExpArgs.cap(1));
soFarSoGood = false;
return args;
}
args[mRegExpArgs.cap(1)] = (mRegExpArgs.cap(2).isEmpty() ? mRegExpArgs.cap(3) : mRegExpArgs.cap(2));
}
return args;
}
/*
* Cli.hpp
* Copyright (C) 2017 Belledonne Communications, Grenoble, France
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Created on: June 6, 2017
* Author: Nicolas Follet
*/
#ifndef CLI_H_
#define CLI_H_
#include <QHash>
#include <QObject>
// =============================================================================
class Cli : public QObject {
typedef void (*Function)(const QHash<QString, QString> &);
enum ArgumentType {
STRING
};
struct Argument {
Argument (ArgumentType type = STRING, bool isOptional = false) {
this->type = type;
this->isOptional = isOptional;
}
ArgumentType type;
bool isOptional;
};
class Command {
public:
Command () = default;
Command (const QString &functionName, const QString &description, Function function, const QHash<QString, Argument> &argsScheme);
void execute (const QHash<QString, QString> &args);
bool argNameExists (const QString &argName) {
return mArgsScheme.contains(argName);
}
private:
QString mFunctionName;
QString mDescription;
Function mFunction = nullptr;
QHash<QString, Argument> mArgsScheme;
};
public:
Cli (QObject *parent = Q_NULLPTR);
~Cli () = default;
void executeCommand (const QString &command) noexcept;
const QString parseFunctionName (const QString &command) noexcept;
const QHash<QString, QString> parseArgs (const QString &command, const QString functionName, bool &soFarSoGood) noexcept;
private:
void addCommand (const QString &functionName, const QString &description, Function function, const QHash<QString, Argument> &argsScheme = {}) noexcept;
void makeCommands () noexcept;
QRegExp mRegExpArgs;
QRegExp mRegExpFunctionName;
QHash<QString, Command> mCommands;
};
#endif // CLI_H_
...@@ -453,10 +453,9 @@ bool SingleApplication::sendMessage (QByteArray message, int timeout) { ...@@ -453,10 +453,9 @@ bool SingleApplication::sendMessage (QByteArray message, int timeout) {
// Nobody to connect to // Nobody to connect to
if (isPrimary()) return false; if (isPrimary()) return false;
// Make sure the socket is connected // Make sure the socket is connected
d->connectToPrimary(timeout, Reconnect);
d->connectToPrimary(timeout, Reconnect);
d->socket->write(message); d->socket->write(message);
bool dataWritten = d->socket->flush(); bool dataWritten = d->socket->flush();
d->socket->waitForBytesWritten(timeout); d->socket->waitForBytesWritten(timeout);
......
...@@ -72,7 +72,8 @@ int main (int argc, char *argv[]) { ...@@ -72,7 +72,8 @@ int main (int argc, char *argv[]) {
app.parseArgs(); app.parseArgs();
if (app.isSecondary()) { if (app.isSecondary()) {
app.sendMessage("show", 0); QString command = app.getCommandArgument();
app.sendMessage(command.isEmpty() ? "show" : command.toLocal8Bit(), 0);
return 0; return 0;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment