Commit e90b2311 authored by Nicolas Follet's avatar Nicolas Follet

feat(app): provide a cli

parent e26dcceb
......@@ -93,6 +93,7 @@ endif ()
set(SOURCES
src/app/App.cpp
src/app/cli/Cli.cpp
src/app/logger/Logger.cpp
src/app/paths/Paths.cpp
src/app/providers/AvatarProvider.cpp
......@@ -141,6 +142,7 @@ set(SOURCES
set(HEADERS
src/app/App.hpp
src/app/cli/Cli.hpp
src/app/logger/Logger.hpp
src/app/paths/Paths.hpp
src/app/providers/AvatarProvider.hpp
......
......@@ -64,6 +64,18 @@
<source>commandLineOptionIconified</source>
<translation>Start in the system tray, do not show the main interface.</translation>
</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>
<name>AssistantAbstractView</name>
......@@ -387,6 +399,17 @@
Server url not configured.</translation>
</message>
</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>
<name>CodecsViewer</name>
<message>
......
......@@ -64,6 +64,18 @@
<source>commandLineOptionIconified</source>
<translation>Lancer l&apos;application dans la zone de notification.</translation>
</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>
<name>AssistantAbstractView</name>
......@@ -387,6 +399,17 @@
Url du serveur non configurée.</translation>
</message>
</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>
<name>CodecsViewer</name>
<message>
......
......@@ -37,6 +37,7 @@
#include "providers/AvatarProvider.hpp"
#include "providers/ThumbnailProvider.hpp"
#include "translator/DefaultTranslator.hpp"
#include "cli/Cli.hpp"
#include "App.hpp"
......@@ -139,16 +140,13 @@ void App::initContentApp () {
// Don't quit if last window is closed!!!
setQuitOnLastWindowClosed(false);
QObject::connect(
this, &App::receivedMessage, this, [this](int, QByteArray byteArray) {
QString message(byteArray);
QObject::connect(this, &App::receivedMessage, this, [this](int, const QByteArray &byteArray) {
QString command(byteArray);
qInfo() << QStringLiteral("Received command from other application: `%1`.").arg(command);
executeCommand(command);
});
qInfo() << QStringLiteral("Received message from other application: `%1`.").arg(message);
if (message == "show")
smartShowWindow(getMainWindow());
}
);
mCli = new Cli(this);
}
// Init core.
......@@ -207,12 +205,13 @@ void App::parseArgs () {
mParser.addHelpOption();
mParser.addVersionOption();
mParser.addOptions({
{ "config", tr("commandLineOptionConfig"), "file" },
{ "config", tr("commandLineOptionConfig"), tr("commandLineOptionConfigArg") },
#ifndef __APPLE__
{ "iconified", tr("commandLineOptionIconified") },
#endif // ifndef __APPLE__
{ "self-test", tr("commandLineOptionSelfTest") },
{ { "V", "verbose" }, tr("commandLineOptionVerbose") }
{ { "V", "verbose" }, tr("commandLineOptionVerbose") },
{ { "c", "cmd" }, tr("commandLineOptionCmd"), tr("commandLineOptionCmdArg") }
});
mParser.process(*this);
......@@ -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 () {
QString locale = getConfigLocale();
......@@ -267,15 +278,13 @@ QQuickWindow *App::getMainWindow () const {
QQuickWindow *App::getSettingsWindow () {
if (!mSettingsWindow) {
mSettingsWindow = createSubWindow(this, QML_VIEW_SETTINGS_WINDOW);
QObject::connect(
mSettingsWindow, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) {
QObject::connect(mSettingsWindow, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) {
if (visibility == QWindow::Hidden) {
qInfo() << QStringLiteral("Update nat policy.");
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
core->setNatPolicy(core->getNatPolicy());
}
}
);
});
}
return mSettingsWindow;
......@@ -329,12 +338,9 @@ void registerMetaType (const char *name) {
template<class T>
void registerSingletonType (const char *name) {
qmlRegisterSingletonType<T>(
"Linphone", 1, 0, name,
[](QQmlEngine *, QJSEngine *) -> QObject *{
qmlRegisterSingletonType<T>("Linphone", 1, 0, name, [](QQmlEngine *, QJSEngine *) -> QObject *{
return new T();
}
);
});
}
template<class T>
......@@ -408,8 +414,7 @@ void App::setTrayIcon () {
// trayIcon: Left click actions.
QMenu *menu = new QMenu();
root->connect(
systemTrayIcon, &QSystemTrayIcon::activated, [root](
root->connect(systemTrayIcon, &QSystemTrayIcon::activated, [root](
QSystemTrayIcon::ActivationReason reason
) {
if (reason == QSystemTrayIcon::Trigger) {
......@@ -418,8 +423,7 @@ void App::setTrayIcon () {
else
root->hide();
}
}
);
});
// Build trayIcon menu.
menu->addAction(restoreAction);
......@@ -491,6 +495,13 @@ void App::openAppAfterInit () {
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 @@
// =============================================================================
class Cli;
class DefaultTranslator;
class App : public SingleApplication {
......@@ -51,6 +52,9 @@ public:
void initContentApp ();
void parseArgs ();
QString getCommandArgument ();
void executeCommand (const QString &command);
void tryToUsePreferredLocale ();
QQmlEngine *getEngine () {
......@@ -119,6 +123,8 @@ private:
QQuickWindow *mCallsWindow = nullptr;
QQuickWindow *mSettingsWindow = nullptr;
Cli *mCli = nullptr;
};
#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) {
// Nobody to connect to
if (isPrimary()) return false;
// Make sure the socket is connected
d->connectToPrimary(timeout, Reconnect);
d->connectToPrimary(timeout, Reconnect);
d->socket->write(message);
bool dataWritten = d->socket->flush();
d->socket->waitForBytesWritten(timeout);
......
......@@ -72,7 +72,8 @@ int main (int argc, char *argv[]) {
app.parseArgs();
if (app.isSecondary()) {
app.sendMessage("show", 0);
QString command = app.getCommandArgument();
app.sendMessage(command.isEmpty() ? "show" : command.toLocal8Bit(), 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