- Download configuration file if it is a webfile

- Download the configuration file directly to the config path and overwrite old file
- Add an interface to config directory path
- Prevent a crash from the deletion of the Handler where we are still using it
- Add synchronous mechanism to download file and Instance Manager deletion
- Add Command parser features : clean specific keys, parse from list of arguments
- Reorder config search to avoid unwanted config file overwrite
- Move CLI execution at the end of initialization
- Based openAppAfterInit on Manager and not on Handler
- Add Call feature to Command line (--call=<SipAddress>)
- Launch a call on request
- Add generic parameters on show and call method to CLI
- Replace connectOnce function that didn't fully work by a connection on context where it is manage by Qt
- Remove redundant parser initializations
- Update SDK to fix configuration updates and FEC
This commit is contained in:
Julien Wadel 2020-10-23 11:54:55 +02:00
parent b378ac59d3
commit 2629e2461d
26 changed files with 450 additions and 141 deletions

View file

@ -100,6 +100,7 @@ job-centos7-ninja-gcc-package:
artifacts:
paths:
- build/OUTPUT/Packages/*.AppImage
when: always
expire_in: 1 week
#################################################

View file

@ -97,6 +97,7 @@ job-macosx-makefile-package:
- codesign --options runtime,library --verbose -s "$MACOS_SIGNING_IDENTITY" OUTPUT/Packages/Linphone*.dmg
- ./../tools/app_notarization.sh
artifacts:
when: always
paths:
- build/OUTPUT/*
when: always

View file

@ -82,6 +82,7 @@ job-windows-vs2017-package:
artifacts:
paths:
- results\*
when: always
expire_in: 1 weeks

View file

@ -38,11 +38,12 @@ endforeach()
if(ENABLE_BUILD_VERBOSE)
message("User Args : ${USER_ARGS}")
endif()
if(NOT CMAKE_GENERATOR_PLATFORM AND WIN32)
if(WIN32)
if(NOT ${CMAKE_GENERATOR} MATCHES "Ninja")
set(CMAKE_GENERATOR_PLATFORM "Win32")
message(STATUS "Setting Platform to ${CMAKE_GENERATOR_PLATFORM}")
if(NOT CMAKE_GENERATOR_PLATFORM)
set(CMAKE_GENERATOR_PLATFORM "Win32")
message(STATUS "Setting Platform to ${CMAKE_GENERATOR_PLATFORM}")
endif()
endif()
endif()

View file

@ -89,7 +89,7 @@ if( WIN32)
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)#useful for config.h
set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Concurrent Network)
set(QT5_PACKAGES Core Gui Quick Widgets QuickControls2 Svg LinguistTools Concurrent Network Test)
if (UNIX AND NOT APPLE)
list(APPEND QT5_PACKAGES DBus)
endif ()

View file

@ -92,6 +92,22 @@
<source>about</source>
<translation>About</translation>
</message>
<message>
<source>commandLineOptionFetchConfig</source>
<translation>specify the %1 configuration file to be fetch. It will be merged with the current configuration.</translation>
</message>
<message>
<source>commandLineOptionFetchConfigArg</source>
<translation>url, path or file</translation>
</message>
<message>
<source>commandLineOptionCall</source>
<translation>make a call</translation>
</message>
<message>
<source>commandLineOptionCallArg</source>
<translation>sip address</translation>
</message>
</context>
<context>
<name>AssistantAbstractView</name>

View file

@ -7,5 +7,5 @@ Exec=@EXECUTABLE_NAME@ %u
Icon=@EXECUTABLE_NAME@
Terminal=false
Categories=Network;Telephony;
MimeType=x-scheme-handler/sip-linphone;x-scheme-handler/sip;x-scheme-handler/sips-linphone;x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/callto;
MimeType=x-scheme-handler/sip-linphone;x-scheme-handler/sip;x-scheme-handler/sips-linphone;x-scheme-handler/sips;x-scheme-handler/tel;x-scheme-handler/callto;x-scheme-handler/linphone-config;
X-PulseAudio-Properties=media.role=phone

View file

@ -45,6 +45,7 @@
<string>sips-linphone</string>
<string>tel</string>
<string>callto</string>
<string>linphone-config</string>
</array>
</dict>
</array>

View file

@ -19,6 +19,9 @@ WriteRegStr HKCR "sip" "URL Protocol" ""
WriteRegStr HKCR "sip-linphone" "" "URL:sip-linphone Protocol"
WriteRegStr HKCR "sip-linphone" "URL Protocol" ""
WriteRegStr HKCR "linphone-config" "" "URL:linphone-config Protocol"
WriteRegStr HKCR "linphone-config" "URL Protocol" ""
WriteRegStr HKCR "sips" "" "URL:sips Protocol"
WriteRegStr HKCR "sips" "URL Protocol" ""
@ -62,6 +65,13 @@ WriteRegStr HKCR "@APPLICATION_NAME@.sips-linphone\Shell\Open" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.sips-linphone\Shell\Open\Command" "" "$INSTDIR\bin\@EXECUTABLE_NAME@.exe $\"%1$\""
WriteRegStr HKLM "SOFTWARE\@APPLICATION_VENDOR@\@APPLICATION_NAME@\Capabilities\URLAssociations" "sips-linphone" "@APPLICATION_NAME@.sips-linphone"
## LINPHONE-CONFIG
WriteRegStr HKCR "@APPLICATION_NAME@.linphone-config" "" "@APPLICATION_NAME@ linphone-config Protocol"
WriteRegStr HKCR "@APPLICATION_NAME@.linphone-config\Shell" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.linphone-config\Shell\Open" "" ""
WriteRegStr HKCR "@APPLICATION_NAME@.linphone-config\Shell\Open\Command" "" "$INSTDIR\bin\@EXECUTABLE_NAME@.exe $\"%1$\""
WriteRegStr HKLM "SOFTWARE\@APPLICATION_VENDOR@\@APPLICATION_NAME@\Capabilities\URLAssociations" "linphone-config" "@APPLICATION_NAME@.linphone-config"
## TEL
WriteRegStr HKCR "@APPLICATION_NAME@.tel" "" "@APPLICATION_NAME@ tel Protocol"
WriteRegStr HKCR "@APPLICATION_NAME@.tel\Shell" "" ""

View file

@ -17,6 +17,7 @@ DeleteRegKey HKCR "@APPLICATION_NAME@.sip"
DeleteRegKey HKCR "@APPLICATION_NAME@.sip-linphone"
DeleteRegKey HKCR "@APPLICATION_NAME@.sips"
DeleteRegKey HKCR "@APPLICATION_NAME@.sips-linphone"
DeleteRegKey HKCR "@APPLICATION_NAME@.linphone-config"
DeleteRegKey HKCR "@APPLICATION_NAME@.tel"
DeleteRegKey HKCR "@APPLICATION_NAME@.callto"

View file

@ -156,18 +156,50 @@ static inline bool installLocale (App &app, QTranslator &translator, const QLoca
return translator.load(locale, LanguagePath) && app.installTranslator(&translator);
}
static inline shared_ptr<linphone::Config> getConfigIfExists (const QCommandLineParser &parser) {
string configPath(Paths::getConfigFilePath(parser.value("config"), false));
if (!Paths::filePathExists(configPath))
configPath.clear();
static inline string getConfigPathIfExists (const QCommandLineParser &parser) {
QString filePath = parser.value("config");
string configPath;
if(!QUrl(filePath).isRelative()){
configPath = Utils::appStringToCoreString(FileDownloader::synchronousDownload(filePath, Utils::coreStringToAppString(Paths::getConfigDirPath(false)), true));
}
if( configPath == "")
configPath = Paths::getConfigFilePath(filePath, false);
if( configPath == "" )
configPath = Paths::getConfigFilePath("", false);
return configPath;
}
static inline shared_ptr<linphone::Config> getConfigIfExists (const string &configPath) {
string factoryPath(Paths::getFactoryConfigFilePath());
if (!Paths::filePathExists(factoryPath))
factoryPath.clear();
return linphone::Config::newWithFactory(configPath, factoryPath);
}
bool App::setFetchConfig (QCommandLineParser *parser) {
bool fetched = false;
QString filePath = parser->value("fetch-config");
if( !filePath.isEmpty()){
if(QUrl(filePath).isRelative()){// this is a file path
filePath = Utils::coreStringToAppString(Paths::getConfigFilePath(filePath, false));
if(!filePath.isEmpty())
filePath = "file://"+filePath;
}
if(!filePath.isEmpty()){
auto instance = CoreManager::getInstance();
if(instance){
auto core = instance->getCore();
if(core){
filePath.replace('\\','/');
core->setProvisioningUri(Utils::appStringToCoreString(filePath));
parser->process(cleanParserKeys(parser, QStringList("fetch-config")));// Remove this parameter from the parser
fetched = true;
}
}
}
}
return fetched;
}
// -----------------------------------------------------------------------------
App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) {
@ -180,7 +212,7 @@ App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::U
mParser->process(*this);
// Initialize logger.
shared_ptr<linphone::Config> config = getConfigIfExists(*mParser);
shared_ptr<linphone::Config> config = getConfigIfExists(getConfigPathIfExists(*mParser));
Logger::init(config);
if (mParser->isSet("verbose"))
Logger::getInstance()->setVerbose(true);
@ -195,7 +227,6 @@ App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::U
initLocale(config);
if (mParser->isSet("help")) {
createParser();
mParser->showHelp();
}
@ -221,6 +252,30 @@ App::~App () {
// -----------------------------------------------------------------------------
QStringList App::cleanParserKeys(QCommandLineParser * parser, QStringList keys){
QStringList oldArguments = parser->optionNames();
QStringList parameters;
parameters << "dummy";
for(int i = 0 ; i < oldArguments.size() ; ++i){
if( !keys.contains(oldArguments[i])){
if( mParser->value(oldArguments[i]).isEmpty())
parameters << "--"+oldArguments[i];
else
parameters << "--"+oldArguments[i]+"="+parser->value(oldArguments[i]);
}
}
return parameters;
}
void App::processArguments(QHash<QString,QString> args){
QList<QString> keys = args.keys();
QStringList parameters = cleanParserKeys(mParser, keys);
for(auto i = keys.begin() ; i != keys.end() ; ++i){
parameters << "--"+(*i)+"="+args.value(*i);
}
mParser->process(parameters);
}
static QQuickWindow *createSubWindow (QQmlApplicationEngine *engine, const char *path) {
qInfo() << QStringLiteral("Creating subwindow: `%1`.").arg(path);
@ -243,18 +298,22 @@ static QQuickWindow *createSubWindow (QQmlApplicationEngine *engine, const char
// -----------------------------------------------------------------------------
void App::initContentApp () {
shared_ptr<linphone::Config> config = getConfigIfExists(*mParser);
std::string configPath;
shared_ptr<linphone::Config> config;
bool mustBeIconified = false;
bool needRestart = true;
// Destroy qml components and linphone core if necessary.
if (mEngine) {
needRestart = false;
setFetchConfig(mParser);
setOpened(false);
qInfo() << QStringLiteral("Restarting app...");
delete mEngine;
mNotifier = nullptr;
mSystemTrayIcon = nullptr;
//
CoreManager::uninit();
removeTranslator(mTranslator);
removeTranslator(mDefaultTranslator);
@ -262,8 +321,12 @@ void App::initContentApp () {
delete mDefaultTranslator;
mTranslator = new DefaultTranslator(this);
mDefaultTranslator = new DefaultTranslator(this);
configPath = getConfigPathIfExists(*mParser);
config = getConfigIfExists(configPath);
initLocale(config);
} else {
configPath = getConfigPathIfExists(*mParser);
config = getConfigIfExists(configPath);
// Update and download codecs.
VideoCodecsModel::updateCodecs();
VideoCodecsModel::downloadUpdatableCodecs(this);
@ -289,18 +352,8 @@ void App::initContentApp () {
mColors->useConfig(config);
// Init core.
CoreManager::init(this, mParser->value("config"));
CoreManager::init(this, Utils::coreStringToAppString(configPath));
// Execute command argument if needed.
if (!mEngine) {
const QString commandArgument = getCommandArgument();
if (!commandArgument.isEmpty()) {
Cli::CommandFormat format;
Cli::executeCommand(commandArgument, &format);
if (format == Cli::UriFormat)
mustBeIconified = true;
}
}
// Init engine content.
mEngine = new QQmlApplicationEngine();
@ -343,12 +396,22 @@ void App::initContentApp () {
qFatal("Unable to open main window.");
QObject::connect(
CoreManager::getInstance()->getHandlers().get(),
&CoreHandlers::coreStarted,
[this, mustBeIconified]() {
openAppAfterInit(mustBeIconified);
CoreManager::getInstance(),
&CoreManager::coreStarted, CoreManager::getInstance(),
[this, mustBeIconified]() mutable {
if(CoreManager::getInstance()->started())
openAppAfterInit(mustBeIconified);
}
);
// Execute command argument if needed.
const QString commandArgument = getCommandArgument();
if (!commandArgument.isEmpty()) {
Cli::CommandFormat format;
Cli::executeCommand(commandArgument, &format);
if (format == Cli::UriFormat || format == Cli::UrlFormat )
mustBeIconified = true;
}
}
// -----------------------------------------------------------------------------
@ -434,6 +497,8 @@ void App::createParser () {
{ "cli-help", tr("commandLineOptionCliHelp").replace("%1", APPLICATION_NAME) },
{ { "v", "version" }, tr("commandLineOptionVersion") },
{ "config", tr("commandLineOptionConfig").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionConfigArg") },
{ "fetch-config", tr("commandLineOptionFetchConfig").replace("%1", EXECUTABLE_NAME), tr("commandLineOptionFetchConfigArg") },
{ { "c", "call" }, tr("commandLineOptionCall").replace("%1", EXECUTABLE_NAME),tr("commandLineOptionCallArg") },
#ifndef Q_OS_MACOS
{ "iconified", tr("commandLineOptionIconified") },
#endif // ifndef Q_OS_MACOS
@ -782,14 +847,14 @@ void App::setAutoStart (bool enabled) {
void App::openAppAfterInit (bool mustBeIconified) {
qInfo() << QStringLiteral("Open " APPLICATION_NAME " app.");
auto coreManager = CoreManager::getInstance();
// Create other windows.
mCallsWindow = createSubWindow(mEngine, QmlViewCallsWindow);
mSettingsWindow = createSubWindow(mEngine, QmlViewSettingsWindow);
QObject::connect(mSettingsWindow, &QWindow::visibilityChanged, this, [](QWindow::Visibility visibility) {
QObject::connect(mSettingsWindow, &QWindow::visibilityChanged, this, [coreManager](QWindow::Visibility visibility) {
if (visibility == QWindow::Hidden) {
qInfo() << QStringLiteral("Update nat policy.");
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
shared_ptr<linphone::Core> core = coreManager->getCore();
core->setNatPolicy(core->getNatPolicy());
}
});
@ -802,16 +867,10 @@ void App::openAppAfterInit (bool mustBeIconified) {
qWarning("System tray not found on this system.");
else
setTrayIcon();
if (!mustBeIconified)
smartShowWindow(mainWindow);
#else
Q_UNUSED(mustBeIconified);
smartShowWindow(mainWindow);
#endif // ifndef __APPLE__
// Display Assistant if it does not exist proxy config.
if (CoreManager::getInstance()->getCore()->getProxyConfigList().empty())
if (coreManager->getCore()->getProxyConfigList().empty())
QMetaObject::invokeMethod(mainWindow, "setView", Q_ARG(QVariant, AssistantViewName), Q_ARG(QVariant, QString("")));
#ifdef ENABLE_UPDATE_CHECK
@ -824,7 +883,36 @@ void App::openAppAfterInit (bool mustBeIconified) {
checkForUpdate();
#endif // ifdef ENABLE_UPDATE_CHECK
setOpened(true);
if(setFetchConfig(mParser))
restart();
else{
// Launch call if wanted and clean parser
if( mParser->isSet("call")){
QString sipAddress = mParser->value("call");
mParser->parse(cleanParserKeys(mParser, QStringList("call")));// Clean call from parser
if(coreManager->started()){
coreManager->getCallsListModel()->launchAudioCall(sipAddress);
}else{
QObject * context = new QObject();
QObject::connect(CoreManager::getInstance(), &CoreManager::coreStarted,context,
[sipAddress,coreManager, context]() mutable {
if(context){
delete context;
context = nullptr;
coreManager->getCallsListModel()->launchAudioCall(sipAddress);
}
});
}
}
#ifndef __APPLE__
if (!mustBeIconified)
smartShowWindow(mainWindow);
#else
Q_UNUSED(mustBeIconified);
smartShowWindow(mainWindow);
#endif
setOpened(true);
}
}
// -----------------------------------------------------------------------------

View file

@ -55,9 +55,13 @@ public:
~App ();
void initContentApp ();
QStringList cleanParserKeys(QCommandLineParser * parser, QStringList keys);// Get all options from parser and remove the selected keys. Return the result that can be passed to parser process.
void processArguments(QHash<QString,QString> args);
QString getCommandArgument ();
bool setFetchConfig (QCommandLineParser *parser);
#ifdef Q_OS_MACOS
bool event (QEvent *event) override;
#endif // ifdef Q_OS_MACOS

View file

@ -67,7 +67,17 @@ AppController::AppController (int &argc, char *argv[]) {
#endif // ifdef Q_OS_MACOS
QString command = mApp->getCommandArgument();
mApp->sendMessage(command.isEmpty() ? "show" : command.toLocal8Bit(), -1);
if( command.isEmpty()){
command = "show";
QStringList parametersList;
for(int i = 1 ; i < argc ; ++i){
QString a = argv[i];
if(a.startsWith("--"))// show is a command : remove <-->-style parameters
a.remove(0,2);
command += " "+a;
}
}
mApp->sendMessage(command.toLocal8Bit(), -1);
return;
}

View file

@ -41,13 +41,24 @@ using namespace std;
// API.
// =============================================================================
static void cliShow (QHash<QString, QString> &) {
static void cliShow (QHash<QString, QString> &args) {
App *app = App::getInstance();
if( args.size() > 0){
app->processArguments(args);
app->initContentApp();
}
app->smartShowWindow(app->getMainWindow());
}
static void cliCall (QHash<QString, QString> &args) {
CoreManager::getInstance()->getCallsListModel()->launchAudioCall(args["sip-address"]);
if(args.size() > 1){// Call with options
App *app = App::getInstance();
args["call"] = args["sip-address"];// Swap cli def to parser
args.remove("sip-address");
app->processArguments(args);
app->initContentApp();
}else
CoreManager::getInstance()->getCallsListModel()->launchAudioCall(args["sip-address"]);
}
static void cliJoinConference (QHash<QString, QString> &args) {
@ -247,24 +258,26 @@ Cli::Command::Command (
const QString &functionName,
const char *functionDescription,
Cli::Function function,
const QHash<QString, Cli::Argument> &argsScheme
const QHash<QString, Cli::Argument> &argsScheme,
const bool &genericArguments
) :
mFunctionName(functionName),
mFunctionDescription(functionDescription),
mFunction(function),
mArgsScheme(argsScheme) {}
mArgsScheme(argsScheme),
mGenericArguments(genericArguments) {}
void Cli::Command::execute (QHash<QString, QString> &args) const {
// Check arguments validity.
for (const auto &argName : args.keys()) {
if (!mArgsScheme.contains(argName)) {
qWarning() << QStringLiteral("Command with invalid argument: `%1 (%2)`.")
.arg(mFunctionName).arg(argName);
return;
if(!mGenericArguments){// Check arguments validity.
for (const auto &argName : args.keys()) {
if (!mArgsScheme.contains(argName)) {
qWarning() << QStringLiteral("Command with invalid argument: `%1 (%2)`.")
.arg(mFunctionName).arg(argName);
return;
}
}
}
// Check missing arguments.
for (const auto &argName : mArgsScheme.keys()) {
if (!mArgsScheme[argName].isOptional && (!args.contains(argName) || args[argName].isEmpty())) {
@ -281,16 +294,38 @@ void Cli::Command::execute (QHash<QString, QString> &args) const {
(*mFunction)(args);
} else {
Function f = mFunction;
Utils::connectOnce(app, &App::opened, app, [f, args] {
qInfo() << QStringLiteral("Execute deferred command:") << args;
QHash<QString, QString> fuckConst = args;
(*f)(fuckConst);
});
QObject * context = new QObject();
QObject::connect(app, &App::opened,
[f, args, context]()mutable {
if(context){
delete context;
context = nullptr;
qInfo() << QStringLiteral("Execute deferred command:") << args;
QHash<QString, QString> fuckConst = args;
(*f)(fuckConst);
}
}
);
}
}
void Cli::Command::executeUri (const shared_ptr<linphone::Address> &address) const {
QHash<QString, QString> args;
QString qAddress = Utils::coreStringToAppString(address->asString());
QUrl url(qAddress);
QString query = url.query();
QStringList parameters = query.split('&');
for(int i = 0 ; i < parameters.size() ; ++i){
QStringList parameter = parameters[i].split('=');
if( parameter[0] != "method"){
if(parameter.size() > 1)
args[parameter[0]] = QByteArray::fromBase64(parameter[1].toUtf8() );
else
args[parameter[0]] = "";
}
}
// TODO: check if there is too much headers.
for (const auto &argName : mArgsScheme.keys()) {
const string header = address->getHeader(Utils::appStringToCoreString(argName));
@ -301,6 +336,25 @@ void Cli::Command::executeUri (const shared_ptr<linphone::Address> &address) con
execute(args);
}
// pUrl can be `anytoken?p1=x&p2=y` or `p1=x&p2=y`. It will only use p1 and p2
void Cli::Command::executeUrl (const QString &pUrl) const {
QHash<QString, QString> args;
QUrl url(pUrl);
QString query = (url.hasQuery()?url.query():pUrl);
QStringList parameters = query.split('&');
for(int i = 0 ; i < parameters.size() ; ++i){
QStringList parameter = parameters[i].split('=');
if( parameter[0] != "method"){
if(parameter.size() > 1)
args[parameter[0]] = QByteArray::fromBase64(parameter[1].toUtf8() );
else
args[parameter[0]] = "";
}
}
execute(args);
}
QString Cli::Command::getFunctionSyntax () const {
QString functionSyntax;
functionSyntax += QStringLiteral("\"");
@ -333,10 +387,10 @@ QRegExp Cli::mRegExpArgs("(?:(?:([\\w-]+)\\s*)=\\s*(?:\"([^\"\\\\]*(?:\\\\.[^\"\
QRegExp Cli::mRegExpFunctionName("^\\s*([a-z-]+)\\s*");
QMap<QString, Cli::Command> Cli::mCommands = {
createCommand("show", QT_TR_NOOP("showFunctionDescription"), cliShow),
createCommand("show", QT_TR_NOOP("showFunctionDescription"), cliShow, QHash<QString, Argument>(), true),
createCommand("call", QT_TR_NOOP("callFunctionDescription"), cliCall, {
{ "sip-address", {} }
}),
}, true),
createCommand("initiate-conference", QT_TR_NOOP("initiateConferenceFunctionDescription"), cliInitiateConference, {
{ "sip-address", {} }, { "conference-id", {} }
}),
@ -364,43 +418,52 @@ void Cli::executeCommand (const QString &command, CommandFormat *format) {
QHash<QString, QString> args = parseArgs(command);
mCommands[functionName].execute(args);
}
if (format)
*format = CliFormat;
return;
}else{
string scheme = address->getScheme();
bool ok = false;
for (const string &validScheme : { "sip", "sip-linphone", "sips", "sips-linphone", "tel", "callto", "linphone-config" })
if (scheme == validScheme)
ok = true;
if( !ok){
qWarning() << QStringLiteral("Not a valid uri: `%1` Unsupported scheme: `%2`.").arg(command).arg(Utils::coreStringToAppString(scheme));
return;
}else{
if( scheme == "linphone-config" ){
QHash<QString, QString> args = parseArgs(command);
if(args.contains("fetch-config"))
args["fetch-config"] = QByteArray::fromBase64(args["fetch-config"].toUtf8() );
else {
QUrl url(command);
url.setScheme("https");
args["fetch-config"] = url.toString();
}
if (format)
*format = CliFormat;
mCommands["show"].execute(args);
}else{
if (format)
*format = UriFormat;
// Execute uri command.
qInfo() << QStringLiteral("Detecting uri command: `%1`...").arg(command);
if (address->getUsername().empty()) {
qWarning() << QStringLiteral("Failed to execute command. No username given.");
return;
}
const QString functionName = Utils::coreStringToAppString(address->getHeader("method")).isEmpty()
? QStringLiteral("call")
: Utils::coreStringToAppString(address->getHeader("method"));
if (!functionName.isEmpty() && !mCommands.contains(functionName)) {
qWarning() << QStringLiteral("This command doesn't exist: `%1`.").arg(functionName);
return;
}
mCommands[functionName].executeUri(address);
}
}
}
if (format)
*format = UriFormat;
// Execute uri command.
qInfo() << QStringLiteral("Detecting uri command: `%1`...").arg(command);
if (address->getUsername().empty()) {
qWarning() << QStringLiteral("Failed to execute command. No username given.");
return;
}
string scheme = address->getScheme();
for (const string &validScheme : { "sip", "sip-linphone", "sips", "sips-linphone", "tel", "callto" })
if (scheme == validScheme)
goto success;
qWarning() << QStringLiteral("Not a valid uri: `%1` Unsupported scheme: `%2`.")
.arg(command).arg(Utils::coreStringToAppString(scheme));
return;
success:
const QString functionName = Utils::coreStringToAppString(address->getHeader("method")).isEmpty()
? QStringLiteral("call")
: Utils::coreStringToAppString(address->getHeader("method"));
if (!functionName.isEmpty() && !mCommands.contains(functionName)) {
qWarning() << QStringLiteral("This command doesn't exist: `%1`.").arg(functionName);
return;
}
mCommands[functionName].executeUri(address);
}
void Cli::showHelp () {
@ -425,9 +488,10 @@ pair<QString, Cli::Command> Cli::createCommand (
const QString &functionName,
const char *functionDescription,
Function function,
const QHash<QString, Argument> &argsScheme
const QHash<QString, Argument> &argsScheme,
const bool &genericArguments
) {
return { functionName, Cli::Command(functionName, functionDescription, function, argsScheme) };
return { functionName, Cli::Command(functionName, functionDescription, function, argsScheme, genericArguments) };
}
// -----------------------------------------------------------------------------

View file

@ -59,11 +59,13 @@ class Cli : public QObject {
const QString &functionName,
const char *functionDescription,
Function function,
const QHash<QString, Argument> &argsScheme
const QHash<QString, Argument> &argsScheme,
const bool &genericArguments=false
);
void execute (QHash<QString, QString> &args) const;
void executeUri (const std::shared_ptr<linphone::Address> &address) const;
void executeUrl (const QString &url) const;
const char *getFunctionDescription () const {
return mFunctionDescription;
@ -76,13 +78,15 @@ class Cli : public QObject {
const char *mFunctionDescription;
Function mFunction = nullptr;
QHash<QString, Argument> mArgsScheme;
bool mGenericArguments=false;// Used to avoid check on arguments
};
public:
enum CommandFormat {
UnknownFormat,
CliFormat,
UriFormat
UriFormat, // Parameters are in base64
UrlFormat
};
static void executeCommand (const QString &command, CommandFormat *format = nullptr);
@ -96,7 +100,8 @@ private:
const QString &functionName,
const char *functionDescription,
Function function,
const QHash<QString, Argument> &argsScheme = QHash<QString, Argument>()
const QHash<QString, Argument> &argsScheme = QHash<QString, Argument>(),
const bool &genericArguments=false
);
static QString parseFunctionName (const QString &command);

View file

@ -215,11 +215,25 @@ string Paths::getCodecsDirPath () {
return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + PathCodecs);
}
string Paths::getConfigFilePath (const QString &configPath, bool writable) {
const QString path = configPath.isEmpty()
? getAppConfigFilePath()
: QFileInfo(configPath).absoluteFilePath();
string Paths::getConfigDirPath (bool writable) {
return writable ? getWritableFilePath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)+QDir::separator()) : getReadableFilePath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)+QDir::separator());
}
string Paths::getConfigFilePath (const QString &configPath, bool writable) {
QString path;
if( !configPath.isEmpty()){
QFileInfo file(configPath);
if( !writable && (!file.exists() || !file.isFile())){// This file cannot be found. Check if it exists in standard folder
QString defaultConfigPath = Utils::coreStringToAppString(getConfigDirPath(false));
file = QFileInfo(defaultConfigPath+QDir::separator()+configPath);
if( !file.exists() || !file.isFile())
path = "";
else
path = file.absoluteFilePath();
}else
path = file.absoluteFilePath();
}else
path = getAppConfigFilePath();
return writable ? getWritableFilePath(path) : getReadableFilePath(path);
}

View file

@ -33,6 +33,7 @@ namespace Paths {
std::string getCallHistoryFilePath ();
std::string getCapturesDirPath ();
std::string getCodecsDirPath ();
std::string getConfigDirPath (bool writable = true);
std::string getConfigFilePath (const QString &configPath = QString(), bool writable = true);
std::string getDownloadDirPath ();
std::string getFactoryConfigFilePath ();

View file

@ -113,7 +113,23 @@ void CallsListModel::launchAudioCall (const QString &sipAddress, const QHash<QSt
}
params->setProxyConfig(core->getDefaultProxyConfig());
CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername()));
core->inviteAddressWithParams(address, params);
shared_ptr<linphone::ProxyConfig> currentProxyConfig = core->getDefaultProxyConfig();
if(currentProxyConfig){
if(currentProxyConfig->getState() == linphone::RegistrationState::Ok)
core->inviteAddressWithParams(address, params);
else{
QObject * context = new QObject();
QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context,
[address,core,params,currentProxyConfig, context](const std::shared_ptr<linphone::ProxyConfig> &proxyConfig, linphone::RegistrationState state) mutable {
if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){
delete context;
context = nullptr;
core->inviteAddressWithParams(address, params);
}
});
}
}else
core->inviteAddressWithParams(address, params);
}
void CallsListModel::launchVideoCall (const QString &sipAddress) const {

View file

@ -56,6 +56,7 @@ CoreHandlers::CoreHandlers (CoreManager *coreManager) {
CoreHandlers::~CoreHandlers () {
delete mCoreStartedLock;
mCoreStartedLock = nullptr;
}
// -----------------------------------------------------------------------------
@ -66,7 +67,6 @@ void CoreHandlers::handleCoreCreated () {
Q_ASSERT(mCoreCreated == false);
mCoreCreated = true;
notifyCoreStarted();
mCoreStartedLock->unlock();
}

View file

@ -24,7 +24,7 @@
#include <QtConcurrent>
#include <QTimer>
#include <QFile>
#include <QTest>
#include "config.h"
#include "app/paths/Paths.hpp"
@ -79,6 +79,7 @@ CoreManager::CoreManager (QObject *parent, const QString &configPath) :
createLinphoneCore(configPath);
qInfo() << QStringLiteral("Core created. Enable iterate.");
mInstance->mCbsTimer->start();
std::shared_ptr<CoreHandlers> h = mInstance->getHandlers();// Protect handler as we will enter its function where it can be deleted (like while restarting)
emit mInstance->coreCreated();
});
@ -171,8 +172,17 @@ void CoreManager::init (QObject *parent, const QString &configPath) {
void CoreManager::uninit () {
if (mInstance) {
delete mInstance;
mInstance = nullptr;
connect(mInstance, &QObject::destroyed, []()mutable{
mInstance = nullptr;
qInfo() << "Linphone Core is destroyed";
});
mInstance->mCbsTimer->stop();
mInstance->deleteLater();// Ensure to take time to delete the instance
QTest::qWaitFor([&]() {return mInstance == nullptr;},10000);
if( mInstance){
qWarning() << "Linphone Core couldn't destroy in time. It may lead to have multiple session of Linphone Core";
mInstance = nullptr;
}
}
}

View file

@ -55,7 +55,6 @@ public:
}
std::shared_ptr<linphone::Core> getCore () {
Q_CHECK_PTR(mCore);
return mCore;
}
@ -110,6 +109,11 @@ public:
return mAccountSettingsModel;
}
static CoreManager *getInstance () {
Q_CHECK_PTR(mInstance);
return mInstance;
}
// ---------------------------------------------------------------------------
// Initialization.
// ---------------------------------------------------------------------------
@ -117,11 +121,6 @@ public:
static void init (QObject *parent, const QString &configPath);
static void uninit ();
static CoreManager *getInstance () {
Q_CHECK_PTR(mInstance);
return mInstance;
}
// ---------------------------------------------------------------------------
// Must be used in a qml scene.
@ -136,6 +135,8 @@ public:
int getMissedCallCount(const QString &peerAddress, const QString &localAddress) const;// Get missed call count from a chat (useful for showing bubbles on Timelines)
int getMissedCallCountFromLocal(const QString &localAddress) const;// Get missed call count from a chat (useful for showing bubbles on Timelines)
static bool isInstanciated(){return mInstance!=nullptr;}
signals:
void coreCreated ();
void coreStarted ();

View file

@ -115,7 +115,7 @@ void EventCountNotifier::notifyEventCount (int n) {
void EventCountNotifier::update () {
QSystemTrayIcon *sysTrayIcon = App::getInstance()->getSystemTrayIcon();
Q_CHECK_PTR(sysTrayIcon);
sysTrayIcon->setIcon(QIcon(mDisplayCounter ? *mBufWithCounter : *mBuf));
if(sysTrayIcon)
sysTrayIcon->setIcon(QIcon(mDisplayCounter ? *mBufWithCounter : *mBuf));
mDisplayCounter = !mDisplayCounter;
}

View file

@ -18,6 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QTest>
#include "app/paths/Paths.hpp"
#include "components/core/CoreManager.hpp"
#include "components/settings/SettingsModel.hpp"
@ -31,13 +32,15 @@ namespace {
constexpr char cDefaultFileName[] = "download";
}
static QString getDownloadFilePath (const QString &folder, const QUrl &url) {
static QString getDownloadFilePath (const QString &folder, const QUrl &url, const bool& overwrite) {
QFileInfo fileInfo(url.path());
QString fileName = fileInfo.fileName();
if (fileName.isEmpty())
fileName = cDefaultFileName;
fileName.prepend(folder);
if( overwrite && QFile::exists(fileName))
QFile::remove(fileName);
if (!QFile::exists(fileName))
return fileName;
@ -89,12 +92,15 @@ void FileDownloader::download () {
#endif
if (mDownloadFolder.isEmpty()) {
mDownloadFolder = CoreManager::getInstance()->getSettingsModel()->getDownloadFolder();
if(CoreManager::isInstanciated())
mDownloadFolder = CoreManager::getInstance()->getSettingsModel()->getDownloadFolder();
else
mDownloadFolder = QDir::cleanPath(Utils::coreStringToAppString(Paths::getDownloadDirPath ()) + QDir::separator());
emit downloadFolderChanged(mDownloadFolder);
}
Q_ASSERT(!mDestinationFile.isOpen());
mDestinationFile.setFileName(getDownloadFilePath(QDir::cleanPath(mDownloadFolder) + QDir::separator(), mUrl));
mDestinationFile.setFileName(getDownloadFilePath(QDir::cleanPath(mDownloadFolder) + QDir::separator(), mUrl, mOverwriteFile));
if (!mDestinationFile.open(QIODevice::WriteOnly))
emitOutputError();
else {
@ -210,6 +216,43 @@ void FileDownloader::setDownloadFolder (const QString &downloadFolder) {
}
}
QString FileDownloader::getDestinationFileName () const{
return mDestinationFile.fileName();
}
void FileDownloader::setOverwriteFile(const bool &overwrite){
mOverwriteFile = overwrite;
}
QString FileDownloader::synchronousDownload(const QUrl &url, const QString &destinationFolder, const bool &overwriteFile){
QString filePath;
FileDownloader downloader;
if(url.isRelative())
qWarning() << "FileDownloader: The specified URL is not valid";
else{
bool isOver = false;
bool * pIsOver = &isOver;
downloader.setUrl(url);
downloader.setOverwriteFile(overwriteFile);
downloader.setDownloadFolder(destinationFolder);
connect(&downloader, &FileDownloader::downloadFinished, [pIsOver]()mutable{
*pIsOver=true;
});
connect(&downloader, &FileDownloader::downloadFailed, [pIsOver]()mutable{
*pIsOver=true;
});
downloader.download();
if(QTest::qWaitFor([&]() {return isOver;}, DefaultTimeout)){
filePath = downloader.getDestinationFileName();
if(!QFile::exists(filePath)) {
filePath = "";
qWarning() << "FileDownloader: Cannot download the specified file";
}
}
}
return filePath;
}
qint64 FileDownloader::getReadBytes () const {
return mReadBytes;
}

View file

@ -23,12 +23,13 @@
#include <QObject>
#include <QtNetwork>
#include <QThread>
// =============================================================================
class QSslError;
class FileDownloader : public QObject {
class FileDownloader : public QObject{
Q_OBJECT;
// TODO: Add an error property to use in UI.
@ -57,6 +58,11 @@ public:
QString getDownloadFolder () const;
void setDownloadFolder (const QString &downloadFolder);
QString getDestinationFileName () const;
void setOverwriteFile(const bool &overwrite);
static QString synchronousDownload(const QUrl &url, const QString &destinationFolder, const bool &overwriteFile);// Return the filpath. Empty if nof file could be downloaded
signals:
void urlChanged (const QUrl &url);
void downloadFolderChanged (const QString &downloadFolder);
@ -95,6 +101,7 @@ private:
qint64 mReadBytes = 0;
qint64 mTotalBytes = 0;
bool mDownloading = false;
bool mOverwriteFile = false;
QPointer<QNetworkReply> mNetworkReply;
QNetworkAccessManager mManager;

View file

@ -263,37 +263,51 @@ QStringList SettingsModel::getPlaybackDevices () const {
// -----------------------------------------------------------------------------
QString SettingsModel::getCaptureDevice () const {
return Utils::coreStringToAppString(
CoreManager::getInstance()->getCore()->getCaptureDevice()
);
auto audioDevice = CoreManager::getInstance()->getCore()->getInputAudioDevice();
return Utils::coreStringToAppString(audioDevice? audioDevice->getId() : CoreManager::getInstance()->getCore()->getCaptureDevice());
}
void SettingsModel::setCaptureDevice (const QString &device) {
CoreManager::getInstance()->getCore()->setCaptureDevice(
Utils::appStringToCoreString(device)
);
emit captureDeviceChanged(device);
if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) {
createCaptureGraph();
}
std::string devId = Utils::appStringToCoreString(device);
auto list = CoreManager::getInstance()->getCore()->getExtendedAudioDevices();
auto audioDevice = find_if(list.cbegin(), list.cend(), [&] ( const std::shared_ptr<linphone::AudioDevice> & audioItem) {
return audioItem->getId() == devId;
});
if(audioDevice != list.cend()){
CoreManager::getInstance()->getCore()->setCaptureDevice(devId);
CoreManager::getInstance()->getCore()->setInputAudioDevice(*audioDevice);
emit captureDeviceChanged(device);
if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) {
createCaptureGraph();
}
}else
qWarning() << "Cannot set Capture device. The ID cannot be matched with an existant device : " << device;
}
// -----------------------------------------------------------------------------
QString SettingsModel::getPlaybackDevice () const {
return Utils::coreStringToAppString(
CoreManager::getInstance()->getCore()->getPlaybackDevice()
);
auto audioDevice = CoreManager::getInstance()->getCore()->getOutputAudioDevice();
return Utils::coreStringToAppString(audioDevice? audioDevice->getId() : CoreManager::getInstance()->getCore()->getPlaybackDevice());
}
void SettingsModel::setPlaybackDevice (const QString &device) {
CoreManager::getInstance()->getCore()->setPlaybackDevice(
Utils::appStringToCoreString(device)
);
emit playbackDeviceChanged(device);
if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) {
createCaptureGraph();
}
std::string devId = Utils::appStringToCoreString(device);
auto list = CoreManager::getInstance()->getCore()->getExtendedAudioDevices();
auto audioDevice = find_if(list.cbegin(), list.cend(), [&] ( const std::shared_ptr<linphone::AudioDevice> & audioItem) {
return audioItem->getId() == devId;
});
if(audioDevice != list.cend()){
CoreManager::getInstance()->getCore()->setPlaybackDevice(devId);
CoreManager::getInstance()->getCore()->setOutputAudioDevice(*audioDevice);
emit playbackDeviceChanged(device);
if (mSimpleCaptureGraph && mSimpleCaptureGraph->isRunning()) {
createCaptureGraph();
}
}else
qWarning() << "Cannot set Playback device. The ID cannot be matched with an existant device : " << device;
}
// -----------------------------------------------------------------------------

@ -1 +1 @@
Subproject commit c8700f691113dc7771d24e80feb339e1cf0970a0
Subproject commit 3609b38153ce4b715e93389673538003425cbac2