diff --git a/CMakeLists.txt b/CMakeLists.txt
index 659c89e76..2f04f8a93 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -145,6 +145,7 @@ set(SOURCES
src/components/core/CoreHandlers.cpp
src/components/core/CoreManager.cpp
src/components/core/messages-count-notifier/AbstractMessagesCountNotifier.cpp
+ src/components/file/FileDownloader.cpp
src/components/file/FileExtractor.cpp
src/components/notifier/Notifier.cpp
src/components/other/clipboard/Clipboard.cpp
@@ -163,8 +164,8 @@ set(SOURCES
src/components/timeline/TimelineModel.cpp
src/components/url-handlers/UrlHandlers.cpp
src/utils/LinphoneUtils.cpp
- src/utils/Utils.cpp
src/utils/QExifImageHeader.cpp
+ src/utils/Utils.cpp
)
set(HEADERS
@@ -202,6 +203,7 @@ set(HEADERS
src/components/core/CoreHandlers.hpp
src/components/core/CoreManager.hpp
src/components/core/messages-count-notifier/AbstractMessagesCountNotifier.hpp
+ src/components/file/FileDownloader.hpp
src/components/file/FileExtractor.hpp
src/components/notifier/Notifier.hpp
src/components/other/clipboard/Clipboard.hpp
diff --git a/assets/languages/en.ts b/assets/languages/en.ts
index 1e50a415a..330ae95cd 100644
--- a/assets/languages/en.ts
+++ b/assets/languages/en.ts
@@ -1003,6 +1003,29 @@ your friend's SIP address or username.
New attachment received!
+
+ OnlineInstallerDialog
+
+ confirm
+ CONFIRM
+
+
+ onlineInstallerExtractingDescription
+ Extracting %1...
+
+
+ onlineInstallerDownloadingDescription
+ Downloading %1...
+
+
+ onlineInstallerFinishedDescription
+ %1 is now installed!
+
+
+ onlineInstallerFailedDescription
+ Failed to install %1!
+
+
OutgoingMessage
@@ -1672,4 +1695,11 @@ your friend's SIP address or username.
CONFIRM
+
+ linphone-utils
+
+ downloadCodecDescription
+ Do you want to download %1 (%2)?
+
+
diff --git a/assets/languages/fr_FR.ts b/assets/languages/fr_FR.ts
index ebe3f8ee9..25ab743f0 100644
--- a/assets/languages/fr_FR.ts
+++ b/assets/languages/fr_FR.ts
@@ -1001,6 +1001,29 @@ Cliquez ici : <a href="%1">%1</a>
Pièce jointe reçue !
+
+ OnlineInstallerDialog
+
+ confirm
+ CONFIRM
+
+
+ onlineInstallerExtractingDescription
+ Extraction de %1...
+
+
+ onlineInstallerDownloadingDescription
+ Téléchargement de %1...
+
+
+ onlineInstallerFinishedDescription
+ Installation de %1 terminée !
+
+
+ onlineInstallerFailedDescription
+ L'installation de %1 a échoué !
+
+
OutgoingMessage
@@ -1670,4 +1693,11 @@ Cliquez ici : <a href="%1">%1</a>
CONFIRMER
+
+ linphone-utils
+
+ downloadCodecDescription
+ Voulez-vous installer %1 (%2) ?
+
+
diff --git a/assets/languages/ru.ts b/assets/languages/ru.ts
index 812f0c1cd..fde522e15 100644
--- a/assets/languages/ru.ts
+++ b/assets/languages/ru.ts
@@ -1001,6 +1001,29 @@
Получен новый файл!
+
+ OnlineInstallerDialog
+
+ confirm
+
+
+
+ onlineInstallerExtractingDescription
+
+
+
+ onlineInstallerDownloadingDescription
+
+
+
+ onlineInstallerFinishedDescription
+
+
+
+ onlineInstallerFailedDescription
+
+
+
OutgoingMessage
@@ -1670,4 +1693,11 @@
ПОДТВЕРДИТЬ
+
+ linphone-utils
+
+ downloadCodecDescription
+
+
+
diff --git a/assets/languages/tr.ts b/assets/languages/tr.ts
index af0bf6664..1ab32c037 100644
--- a/assets/languages/tr.ts
+++ b/assets/languages/tr.ts
@@ -1003,6 +1003,29 @@ arkadaşınızın SIP adresini veya kullanıcı adını girin.
Yeni ek alındı!
+
+ OnlineInstallerDialog
+
+ confirm
+
+
+
+ onlineInstallerExtractingDescription
+
+
+
+ onlineInstallerDownloadingDescription
+
+
+
+ onlineInstallerFinishedDescription
+
+
+
+ onlineInstallerFailedDescription
+
+
+
OutgoingMessage
@@ -1672,4 +1695,11 @@ arkadaşınızın SIP adresini veya kullanıcı adını girin.
ONAYLA
+
+ linphone-utils
+
+ downloadCodecDescription
+
+
+
diff --git a/resources.qrc b/resources.qrc
index c36f54360..9e47c852f 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -332,6 +332,7 @@
ui/modules/Linphone/Contact/ContactDescription.qml
ui/modules/Linphone/Contact/Contact.qml
ui/modules/Linphone/Contact/MessagesCounter.qml
+ ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml
ui/modules/Linphone/Menus/SipAddressesMenu.qml
ui/modules/Linphone/Notifications/NotificationBasic.qml
ui/modules/Linphone/Notifications/NotificationNewVersionAvailable.qml
@@ -357,6 +358,7 @@
ui/modules/Linphone/Styles/Contact/ContactDescriptionStyle.qml
ui/modules/Linphone/Styles/Contact/ContactStyle.qml
ui/modules/Linphone/Styles/Contact/MessagesCounterStyle.qml
+ ui/modules/Linphone/Styles/Dialog/OnlineInstallerDialogStyle.qml
ui/modules/Linphone/Styles/Menus/SipAddressesMenuStyle.qml
ui/modules/Linphone/Styles/Notifications/NotificationBasicStyle.qml
ui/modules/Linphone/Styles/Notifications/NotificationReceivedCallStyle.qml
diff --git a/src/app/App.cpp b/src/app/App.cpp
index b9b9f4149..b761a410f 100644
--- a/src/app/App.cpp
+++ b/src/app/App.cpp
@@ -176,10 +176,6 @@ void App::initContentApp () {
Cli::executeCommand(command);
});
- // Add plugins directory.
- addLibraryPath(::Utils::coreStringToAppString(Paths::getPluginsDirPath()));
- qInfo() << QStringLiteral("Library paths:") << libraryPaths();
-
mustBeIconified = mParser->isSet("iconified");
}
@@ -405,6 +401,7 @@ void App::registerTypes () {
registerType("ConferenceHelperModel");
registerType("ConferenceModel");
registerType("ContactsListProxyModel");
+ registerType("FileDownloader");
registerType("FileExtractor");
registerType("SipAddressesProxyModel");
registerType("SoundPlayer");
diff --git a/src/app/logger/Logger.cpp b/src/app/logger/Logger.cpp
index 85fae32ff..a9ac6f328 100644
--- a/src/app/logger/Logger.cpp
+++ b/src/app/logger/Logger.cpp
@@ -159,7 +159,7 @@ void Logger::log (QtMsgType type, const QMessageLogContext &context, const QStri
contextStr = contextArr.constData();
}
#else
- (void)context;
+ Q_UNUSED(context);
#endif // ifdef QT_MESSAGELOGCONTEXT
QByteArray localMsg = msg.toLocal8Bit();
diff --git a/src/app/paths/Paths.cpp b/src/app/paths/Paths.cpp
index 19895ca53..4eddcb3c0 100644
--- a/src/app/paths/Paths.cpp
+++ b/src/app/paths/Paths.cpp
@@ -40,6 +40,7 @@ namespace {
constexpr char cPathAssistantConfig[] = "/linphone/assistant/";
constexpr char cPathAvatars[] = "/avatars/";
constexpr char cPathCaptures[] = "/Linphone/captures/";
+ constexpr char cPathCodecs[] = "/codecs/";
constexpr char cPathLogs[] = "/logs/";
constexpr char cPathPlugins[] = "/plugins/";
constexpr char cPathThumbnails[] = "/thumbnails/";
@@ -181,6 +182,10 @@ string Paths::getCapturesDirPath () {
return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + cPathCaptures);
}
+string Paths::getCodecsDirPath () {
+ return getWritableDirPath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + cPathCodecs);
+}
+
string Paths::getConfigFilePath (const QString &configPath, bool writable) {
const QString path = configPath.isEmpty()
? getAppConfigFilePath()
@@ -217,10 +222,6 @@ string Paths::getPackageMsPluginsDirPath () {
return getReadableDirPath(getAppPackageMsPluginsDirPath());
}
-string Paths::getPluginsDirPath () {
- return getReadableDirPath(getAppPluginsDirPath());
-}
-
string Paths::getRootCaFilePath () {
return getReadableFilePath(getAppRootCaFilePath());
}
diff --git a/src/app/paths/Paths.hpp b/src/app/paths/Paths.hpp
index 6ecdd88ae..57ba3da03 100644
--- a/src/app/paths/Paths.hpp
+++ b/src/app/paths/Paths.hpp
@@ -34,15 +34,15 @@ namespace Paths {
std::string getAvatarsDirPath ();
std::string getCallHistoryFilePath ();
std::string getCapturesDirPath ();
+ std::string getCodecsDirPath ();
std::string getConfigFilePath (const QString &configPath = QString(), bool writable = true);
+ std::string getDownloadDirPath ();
std::string getFactoryConfigFilePath ();
std::string getFriendsListFilePath ();
- std::string getDownloadDirPath ();
std::string getLogsDirPath ();
std::string getMessageHistoryFilePath ();
std::string getPackageDataDirPath ();
std::string getPackageMsPluginsDirPath ();
- std::string getPluginsDirPath ();
std::string getRootCaFilePath ();
std::string getThumbnailsDirPath ();
std::string getUserCertificatesDirPath ();
diff --git a/src/components/Components.hpp b/src/components/Components.hpp
index bfb018de0..7de432886 100644
--- a/src/components/Components.hpp
+++ b/src/components/Components.hpp
@@ -36,6 +36,7 @@
#include "conference/ConferenceModel.hpp"
#include "contacts/ContactsListProxyModel.hpp"
#include "core/CoreManager.hpp"
+#include "file/FileDownloader.hpp"
#include "file/FileExtractor.hpp"
#include "presence/OwnPresenceModel.hpp"
#include "settings/AccountSettingsModel.hpp"
diff --git a/src/components/codecs/AbstractCodecsModel.cpp b/src/components/codecs/AbstractCodecsModel.cpp
index 608ab0aea..15abb820d 100644
--- a/src/components/codecs/AbstractCodecsModel.cpp
+++ b/src/components/codecs/AbstractCodecsModel.cpp
@@ -20,17 +20,18 @@
* Author: Ronan Abhamon
*/
+#include "../../app/paths/Paths.hpp"
#include "../../utils/Utils.hpp"
#include "../core/CoreManager.hpp"
#include "AbstractCodecsModel.hpp"
-using namespace std;
-
// =============================================================================
+using namespace std;
+
static inline shared_ptr getCodecFromMap (const QVariantMap &map) {
- return map.value("__codec").value >();
+ return map.value("__codec").value>();
}
// -----------------------------------------------------------------------------
@@ -65,12 +66,12 @@ void AbstractCodecsModel::enableCodec (int id, bool status) {
Q_ASSERT(id >= 0 && id < mCodecs.count());
QVariantMap &map = mCodecs[id];
- shared_ptr codec = ::getCodecFromMap(map);
-
- codec->enable(status);
- map["enabled"] = codec->enabled();
-
- emit dataChanged(index(id, 0), index(id, 0));
+ shared_ptr codec = getCodecFromMap(map);
+ if (codec) {
+ codec->enable(status);
+ map["enabled"] = codec->enabled();
+ emit dataChanged(index(id, 0), index(id, 0));
+ }
}
void AbstractCodecsModel::moveCodec (int source, int destination) {
@@ -81,12 +82,12 @@ void AbstractCodecsModel::setBitrate (int id, int bitrate) {
Q_ASSERT(id >= 0 && id < mCodecs.count());
QVariantMap &map = mCodecs[id];
- shared_ptr codec = ::getCodecFromMap(map);
-
- codec->setNormalBitrate(bitrate);
- map["bitrate"] = codec->getNormalBitrate();
-
- emit dataChanged(index(id, 0), index(id, 0));
+ shared_ptr codec = getCodecFromMap(map);
+ if (codec) {
+ codec->setNormalBitrate(bitrate);
+ map["bitrate"] = codec->getNormalBitrate();
+ emit dataChanged(index(id, 0), index(id, 0));
+ }
}
void AbstractCodecsModel::setRecvFmtp (int id, const QString &recvFmtp) {
@@ -94,11 +95,11 @@ void AbstractCodecsModel::setRecvFmtp (int id, const QString &recvFmtp) {
QVariantMap &map = mCodecs[id];
shared_ptr codec = ::getCodecFromMap(map);
-
- codec->setRecvFmtp(::Utils::appStringToCoreString(recvFmtp));
- map["recvFmtp"] = ::Utils::coreStringToAppString(codec->getRecvFmtp());
-
- emit dataChanged(index(id, 0), index(id, 0));
+ if (codec) {
+ codec->setRecvFmtp(Utils::appStringToCoreString(recvFmtp));
+ map["recvFmtp"] = Utils::coreStringToAppString(codec->getRecvFmtp());
+ emit dataChanged(index(id, 0), index(id, 0));
+ }
}
// -----------------------------------------------------------------------------
@@ -110,6 +111,8 @@ bool AbstractCodecsModel::moveRows (
const QModelIndex &destinationParent,
int destinationChild
) {
+ // TODO: Do not move downloadable codecs.
+
int limit = sourceRow + count - 1;
{
@@ -139,9 +142,13 @@ bool AbstractCodecsModel::moveRows (
}
// Update linphone codecs list.
- list > codecs;
- for (const auto &map : mCodecs)
- codecs.push_back(::getCodecFromMap(map));
+ list> codecs;
+ for (const auto &map : mCodecs) {
+ // Do not update downloadable codecs.
+ shared_ptr codec = getCodecFromMap(map);
+ if (codec)
+ codecs.push_back(codec);
+ }
updateCodecs(codecs);
endMoveRows();
@@ -157,15 +164,40 @@ void AbstractCodecsModel::addCodec (shared_ptr &codec) {
map["bitrate"] = codec->getNormalBitrate();
map["channels"] = codec->getChannels();
map["clockRate"] = codec->getClockRate();
- map["description"] = ::Utils::coreStringToAppString(codec->getDescription());
+ map["description"] = Utils::coreStringToAppString(codec->getDescription());
map["enabled"] = codec->enabled();
- map["encoderDescription"] = ::Utils::coreStringToAppString(codec->getEncoderDescription());
+ map["encoderDescription"] = Utils::coreStringToAppString(codec->getEncoderDescription());
map["isUsable"] = codec->isUsable(); // TODO: Notify in UI when unusable.
map["isVbr"] = codec->isVbr();
- map["mime"] = ::Utils::coreStringToAppString(codec->getMimeType());
+ map["mime"] = Utils::coreStringToAppString(codec->getMimeType());
map["number"] = codec->getNumber();
- map["recvFmtp"] = ::Utils::coreStringToAppString(codec->getRecvFmtp());
+ map["recvFmtp"] = Utils::coreStringToAppString(codec->getRecvFmtp());
map["__codec"] = QVariant::fromValue(codec);
mCodecs << map;
}
+
+void AbstractCodecsModel::addDownloadableCodec (
+ const QString &mime,
+ const QString &downloadUrl,
+ const QString &encoderDescription
+) {
+ QVariantMap map;
+
+ map["mime"] = mime;
+ map["downloadUrl"] = downloadUrl;
+ map["encoderDescription"] = encoderDescription;
+
+ mCodecs << map;
+}
+
+QVariantMap AbstractCodecsModel::getCodecInfo (const QString &mime) const {
+ for (const auto &codec : mCodecs)
+ if (codec.value("mime") == mime)
+ return codec;
+ return QVariantMap();
+};
+
+QString AbstractCodecsModel::getCodecsFolder () const {
+ return Utils::coreStringToAppString(Paths::getCodecsDirPath());
+}
diff --git a/src/components/codecs/AbstractCodecsModel.hpp b/src/components/codecs/AbstractCodecsModel.hpp
index f9867198e..73cf843f6 100644
--- a/src/components/codecs/AbstractCodecsModel.hpp
+++ b/src/components/codecs/AbstractCodecsModel.hpp
@@ -36,6 +36,8 @@ namespace linphone {
class AbstractCodecsModel : public QAbstractListModel {
Q_OBJECT;
+ Q_PROPERTY(QString codecsFolder READ getCodecsFolder CONSTANT);
+
public:
AbstractCodecsModel (QObject *parent = Q_NULLPTR);
virtual ~AbstractCodecsModel () = default;
@@ -51,6 +53,10 @@ public:
Q_INVOKABLE void setBitrate (int id, int bitrate);
Q_INVOKABLE void setRecvFmtp (int id, const QString &recvFmtp);
+ Q_INVOKABLE virtual void reload () {};
+
+ Q_INVOKABLE QVariantMap getCodecInfo (const QString &mime) const;
+
protected:
bool moveRows (
const QModelIndex &sourceParent,
@@ -61,10 +67,12 @@ protected:
) override;
void addCodec (std::shared_ptr &codec);
+ void addDownloadableCodec (const QString &mime, const QString &downloadUrl, const QString &encoderDescription);
- virtual void updateCodecs (std::list > &codecs) = 0;
+ QString getCodecsFolder () const;
+
+ virtual void updateCodecs (std::list> &codecs) = 0;
-private:
QList mCodecs;
};
diff --git a/src/components/codecs/AudioCodecsModel.cpp b/src/components/codecs/AudioCodecsModel.cpp
index 278896c90..8f2e19981 100644
--- a/src/components/codecs/AudioCodecsModel.cpp
+++ b/src/components/codecs/AudioCodecsModel.cpp
@@ -24,15 +24,15 @@
#include "AudioCodecsModel.hpp"
-using namespace std;
-
// =============================================================================
+using namespace std;
+
AudioCodecsModel::AudioCodecsModel (QObject *parent) : AbstractCodecsModel(parent) {
for (auto &codec : CoreManager::getInstance()->getCore()->getAudioPayloadTypes())
addCodec(codec);
}
-void AudioCodecsModel::updateCodecs (list > &codecs) {
+void AudioCodecsModel::updateCodecs (list> &codecs) {
CoreManager::getInstance()->getCore()->setAudioPayloadTypes(codecs);
}
diff --git a/src/components/codecs/AudioCodecsModel.hpp b/src/components/codecs/AudioCodecsModel.hpp
index 18964c00d..989c0234a 100644
--- a/src/components/codecs/AudioCodecsModel.hpp
+++ b/src/components/codecs/AudioCodecsModel.hpp
@@ -34,8 +34,8 @@ public:
AudioCodecsModel (QObject *parent = Q_NULLPTR);
~AudioCodecsModel () = default;
-protected:
- void updateCodecs (std::list > &codecs) override;
+private:
+ void updateCodecs (std::list> &codecs) override;
};
#endif // AUDIO_CODECS_MODEL_H_
diff --git a/src/components/codecs/VideoCodecsModel.cpp b/src/components/codecs/VideoCodecsModel.cpp
index 153a10e45..4cb25cd4c 100644
--- a/src/components/codecs/VideoCodecsModel.cpp
+++ b/src/components/codecs/VideoCodecsModel.cpp
@@ -20,19 +20,79 @@
* Author: Ronan Abhamon
*/
+#include
+#include
+
+#include "../../app/paths/Paths.hpp"
+#include "../../utils/Utils.hpp"
#include "../core/CoreManager.hpp"
#include "VideoCodecsModel.hpp"
-using namespace std;
-
// =============================================================================
-VideoCodecsModel::VideoCodecsModel (QObject *parent) : AbstractCodecsModel(parent) {
- for (auto &codec : CoreManager::getInstance()->getCore()->getVideoPayloadTypes())
- addCodec(codec);
+using namespace std;
+
+namespace {
+ constexpr char cH264Description[] = "Provided by CISCO SYSTEM,INC";
+
+ #ifdef Q_OS_LINUX
+ constexpr char cLibraryExtension[] = "so";
+ #ifdef Q_PROCESSOR_X86_64
+ constexpr char cPluginUrlH264[] = "http://ciscobinary.openh264.org/libopenh264-1.7.0-linux64.4.so.bz2";
+ #else
+ constexpr char cPluginUrlH264[] = "http://ciscobinary.openh264.org/libopenh264-1.7.0-linux32.4.so.bz2";
+ #endif // ifdef Q_PROCESSOR_X86_64
+ #elif defined(Q_OS_WIN)
+ constexpr char cLibraryExtension[] = "dll";
+ #ifdef Q_OS_WIN64
+ constexpr char cPluginUrlH264[] = "http://ciscobinary.openh264.org/openh264-1.7.0-win64.dll.bz2";
+ #elif defined(Q_OS_WIN32)
+ constexpr char cPluginUrlH264[] = "http://ciscobinary.openh264.org/openh264-1.7.0-win32.dll.bz2";
+ #endif // ifdef Q_OS_WIN64
+ #endif // ifdef Q_OS_LINUX
}
-void VideoCodecsModel::updateCodecs (list > &codecs) {
+VideoCodecsModel::VideoCodecsModel (QObject *parent) : AbstractCodecsModel(parent) {
+ load();
+}
+
+void VideoCodecsModel::updateCodecs (list> &codecs) {
CoreManager::getInstance()->getCore()->setVideoPayloadTypes(codecs);
}
+
+void VideoCodecsModel::load () {
+ mCodecs.clear();
+
+ shared_ptr core = CoreManager::getInstance()->getCore();
+
+ // Load downloaded codecs like OpenH264.
+ #if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
+ QDirIterator it(Utils::coreStringToAppString(Paths::getCodecsDirPath()));
+ while (it.hasNext()) {
+ QFileInfo info(it.next());
+ if (info.suffix() == cLibraryExtension)
+ QLibrary(info.filePath()).load();
+ }
+ core->reloadMsPlugins("");
+ #endif
+
+ // Add codecs.
+ auto codecs = core->getVideoPayloadTypes();
+ for (auto &codec : codecs)
+ addCodec(codec);
+
+ // Add downloadable codecs.
+ #if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
+ if (find_if(codecs.begin(), codecs.end(), [](const shared_ptr &codec) {
+ return codec->getMimeType() == "H264";
+ }) == codecs.end())
+ addDownloadableCodec("H264", cPluginUrlH264, cH264Description);
+ #endif
+}
+
+void VideoCodecsModel::reload () {
+ beginResetModel();
+ load();
+ endResetModel();
+}
diff --git a/src/components/codecs/VideoCodecsModel.hpp b/src/components/codecs/VideoCodecsModel.hpp
index 498abf283..8b07c687a 100644
--- a/src/components/codecs/VideoCodecsModel.hpp
+++ b/src/components/codecs/VideoCodecsModel.hpp
@@ -34,8 +34,11 @@ public:
VideoCodecsModel (QObject *parent = Q_NULLPTR);
~VideoCodecsModel () = default;
-protected:
- void updateCodecs (std::list > &codecs) override;
+private:
+ void updateCodecs (std::list> &codecs) override;
+
+ void load ();
+ void reload () override;
};
#endif // VIDEO_CODECS_MODEL_H_
diff --git a/src/components/core/messages-count-notifier/MessagesCountNotifierWindows.cpp b/src/components/core/messages-count-notifier/MessagesCountNotifierWindows.cpp
index 5123971e0..37dde1bac 100644
--- a/src/components/core/messages-count-notifier/MessagesCountNotifierWindows.cpp
+++ b/src/components/core/messages-count-notifier/MessagesCountNotifierWindows.cpp
@@ -30,5 +30,5 @@ MessagesCountNotifier::MessagesCountNotifier (QObject *parent) : AbstractMessage
void MessagesCountNotifier::notifyUnreadMessagesCount (int n) {
// TODO.
- (void)n;
+ Q_UNUSED(n);
}
diff --git a/src/components/file/FileDownloader.cpp b/src/components/file/FileDownloader.cpp
new file mode 100644
index 000000000..a4cd2e566
--- /dev/null
+++ b/src/components/file/FileDownloader.cpp
@@ -0,0 +1,221 @@
+/*
+ * FileDownloader.cpp
+ * Copyright (C) 2017-2018 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: February 6, 2018
+ * Author: Danmei Chen
+ */
+
+#include "../../app/paths/Paths.hpp"
+#include "../../utils/Utils.hpp"
+#include "../core/CoreManager.hpp"
+
+#include "FileDownloader.hpp"
+
+// =============================================================================
+
+namespace {
+ constexpr char cDefaultFileName[] = "download";
+}
+
+static QString getDownloadFilePath (const QString &folder, const QUrl &url) {
+ QFileInfo fileInfo(url.path());
+ QString fileName = fileInfo.fileName();
+ if (fileName.isEmpty())
+ fileName = cDefaultFileName;
+
+ fileName.prepend(folder);
+ if (!QFile::exists(fileName))
+ return fileName;
+
+ // Already exists, don't overwrite.
+ QString baseName = fileInfo.completeBaseName();
+ if (baseName.isEmpty())
+ baseName = cDefaultFileName;
+
+ QString suffix = fileInfo.suffix();
+ if (!suffix.isEmpty())
+ suffix.prepend(".");
+
+ for (int i = 1; true; ++i) {
+ fileName = folder + baseName + "(" + QString::number(i) + ")" + suffix;
+ if (!QFile::exists(fileName))
+ break;
+ }
+ return fileName;
+}
+
+static bool isHttpRedirect (QNetworkReply *reply) {
+ Q_CHECK_PTR(reply);
+ int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ return statusCode == 301 || statusCode == 302 || statusCode == 303
+ || statusCode == 305 || statusCode == 307 || statusCode == 308;
+}
+
+// -----------------------------------------------------------------------------
+
+void FileDownloader::download () {
+ if (mDownloading) {
+ qWarning() << "Unable to download file. Already downloading!";
+ return;
+ }
+ setDownloading(true);
+
+ QNetworkRequest request(mUrl);
+ mNetworkReply = mManager.get(request);
+
+ #if QT_CONFIG(ssl)
+ QObject::connect(mNetworkReply, &QNetworkReply::sslErrors, this, &FileDownloader::handleSslErrors);
+ #endif
+
+ QObject::connect(mNetworkReply, &QNetworkReply::downloadProgress, this, &FileDownloader::handleDownloadProgress);
+ QObject::connect(mNetworkReply, &QNetworkReply::readyRead, this, &FileDownloader::handleReadyData);
+ QObject::connect(mNetworkReply, &QNetworkReply::finished, this, &FileDownloader::handleDownloadFinished);
+
+ if (mDownloadFolder.isEmpty()) {
+ mDownloadFolder = CoreManager::getInstance()->getSettingsModel()->getDownloadFolder();
+ emit downloadFolderChanged(mDownloadFolder);
+ }
+
+ // TODO: Deal with connection error like timeout.
+
+ Q_ASSERT(!mDestinationFile.isOpen());
+ mDestinationFile.setFileName(getDownloadFilePath(QDir::cleanPath(mDownloadFolder) + QDir::separator(), mUrl));
+ if (!mDestinationFile.open(QIODevice::WriteOnly))
+ emitOutputError();
+}
+
+bool FileDownloader::remove () {
+ return mDestinationFile.exists() && !mDestinationFile.isOpen() && mDestinationFile.remove();
+}
+
+void FileDownloader::emitOutputError () {
+ qWarning() << QStringLiteral("Could not write into `%1` (%2).")
+ .arg(mDestinationFile.fileName()).arg(mDestinationFile.errorString());
+ mNetworkReply->abort();
+}
+
+void FileDownloader::handleReadyData () {
+ QByteArray data = mNetworkReply->readAll();
+ if (mDestinationFile.write(data) == -1)
+ emitOutputError();
+}
+
+void FileDownloader::handleDownloadFinished() {
+ QNetworkReply::NetworkError error = mNetworkReply->error();
+ if (error != QNetworkReply::NoError) {
+ if (error != QNetworkReply::OperationCanceledError)
+ qWarning() << QStringLiteral("Download of %1 failed: %2")
+ .arg(mUrl.toString()).arg(mNetworkReply->errorString());
+ mDestinationFile.remove();
+ emit downloadFailed();
+ } else {
+ // TODO: Deal with redirection.
+ if (isHttpRedirect(mNetworkReply)) {
+ qWarning() << QStringLiteral("Request was redirected.");
+ mDestinationFile.remove();
+ emit downloadFailed();
+ } else {
+ mDestinationFile.close();
+ emit downloadFinished(mDestinationFile.fileName());
+ }
+ }
+
+ mNetworkReply->deleteLater();
+ setDownloading(false);
+}
+
+void FileDownloader::handleSslErrors (const QList &sslErrors) {
+ #if QT_CONFIG(ssl)
+ for (const QSslError &error : sslErrors)
+ qWarning() << QStringLiteral("SSL error: %1").arg(error.errorString());
+ #else
+ Q_UNUSED(sslErrors);
+ #endif
+}
+
+void FileDownloader::handleDownloadProgress (qint64 readBytes, qint64 totalBytes) {
+ setReadBytes(readBytes);
+ setTotalBytes(totalBytes);
+}
+
+// -----------------------------------------------------------------------------
+
+QUrl FileDownloader::getUrl () const {
+ return mUrl;
+}
+
+void FileDownloader::setUrl (const QUrl &url) {
+ if (mDownloading) {
+ qWarning() << QStringLiteral("Unable to set url, a file is downloading.");
+ return;
+ }
+
+ if (mUrl != url) {
+ mUrl = url;
+ emit urlChanged(mUrl);
+ }
+}
+
+QString FileDownloader::getDownloadFolder () const {
+ return mDownloadFolder;
+}
+
+void FileDownloader::setDownloadFolder (const QString &downloadFolder) {
+ if (mDownloading) {
+ qWarning() << QStringLiteral("Unable to set download folder, a file is downloading.");
+ return;
+ }
+
+ if (mDownloadFolder != downloadFolder) {
+ mDownloadFolder = downloadFolder;
+ emit downloadFolderChanged(mDownloadFolder);
+ }
+}
+
+qint64 FileDownloader::getReadBytes () const {
+ return mReadBytes;
+}
+
+void FileDownloader::setReadBytes (qint64 readBytes) {
+ if (mReadBytes != readBytes) {
+ mReadBytes = readBytes;
+ emit readBytesChanged(readBytes);
+ }
+}
+
+qint64 FileDownloader::getTotalBytes () const {
+ return mTotalBytes;
+}
+
+void FileDownloader::setTotalBytes (qint64 totalBytes) {
+ if (mTotalBytes != totalBytes) {
+ mTotalBytes = totalBytes;
+ emit totalBytesChanged(totalBytes);
+ }
+}
+
+bool FileDownloader::getDownloading () const {
+ return mDownloading;
+}
+
+void FileDownloader::setDownloading (bool downloading) {
+ if (mDownloading != downloading) {
+ mDownloading = downloading;
+ emit downloadingChanged(downloading);
+ }
+}
diff --git a/src/components/file/FileDownloader.hpp b/src/components/file/FileDownloader.hpp
new file mode 100644
index 000000000..86046e1b2
--- /dev/null
+++ b/src/components/file/FileDownloader.hpp
@@ -0,0 +1,88 @@
+/*
+ * FileDownloader.hpp
+ * Copyright (C) 2017-2018 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: February 6, 2018
+ * Author: Danmei Chen
+ */
+
+#include
+#include
+
+// =============================================================================
+
+class QSslError;
+
+class FileDownloader : public QObject {
+ Q_OBJECT;
+
+ // TODO: Add an error property to use in UI.
+
+ Q_PROPERTY(QUrl url READ getUrl WRITE setUrl NOTIFY urlChanged);
+ Q_PROPERTY(QString downloadFolder READ getDownloadFolder WRITE setDownloadFolder NOTIFY downloadFolderChanged);
+ Q_PROPERTY(qint64 readBytes READ getReadBytes NOTIFY readBytesChanged);
+ Q_PROPERTY(qint64 totalBytes READ getTotalBytes NOTIFY totalBytesChanged);
+ Q_PROPERTY(bool downloading READ getDownloading NOTIFY downloadingChanged);
+
+public:
+ Q_INVOKABLE void download ();
+ Q_INVOKABLE bool remove();
+
+signals:
+ void urlChanged (const QUrl &url);
+ void downloadFolderChanged (const QString &downloadFolder);
+ void readBytesChanged (qint64 readBytes);
+ void totalBytesChanged (qint64 totalBytes);
+ void downloadingChanged (bool downloading);
+ void downloadFinished (const QString &filePath);
+ void downloadFailed();
+
+private:
+ QUrl getUrl () const;
+ void setUrl (const QUrl &url);
+
+ QString getDownloadFolder () const;
+ void setDownloadFolder (const QString &downloadFolder);
+
+ qint64 getReadBytes () const;
+ void setReadBytes (qint64 readBytes);
+
+ qint64 getTotalBytes () const;
+ void setTotalBytes (qint64 totalBytes);
+
+ bool getDownloading () const;
+ void setDownloading (bool downloading);
+
+ void emitOutputError ();
+
+ void handleReadyData ();
+ void handleDownloadFinished ();
+
+ void handleSslErrors (const QList &errors);
+ void handleDownloadProgress (qint64 readBytes, qint64 totalBytes);
+
+ QUrl mUrl;
+ QString mDownloadFolder;
+ QFile mDestinationFile;
+
+ qint64 mReadBytes = 0;
+ qint64 mTotalBytes = 0;
+ bool mDownloading = false;
+
+ QPointer mNetworkReply;
+ QNetworkAccessManager mManager;
+};
diff --git a/src/components/file/FileExtractor.cpp b/src/components/file/FileExtractor.cpp
index 0034b4335..f9e562133 100644
--- a/src/components/file/FileExtractor.cpp
+++ b/src/components/file/FileExtractor.cpp
@@ -20,7 +20,9 @@
* Author: Ronan Abhamon
*/
+#include
#include
+#include
#include
#include
#include
@@ -31,17 +33,61 @@
using namespace std;
-static int openMinizipStream (void **stream, const char *filePath) {
- *stream = nullptr;
- if (!mz_stream_bzip_create(stream))
- return MZ_MEM_ERROR;
- Q_CHECK_PTR(*stream);
- qInfo() << QStringLiteral("Opening `%1`...").arg(filePath);
- return mz_stream_bzip_open(*stream, filePath, MZ_OPEN_MODE_READ);
-}
+class FileExtractor::ExtractStream {
+public:
+ ExtractStream () : mFileStream(nullptr), mBzipStream(nullptr) {}
+
+ ~ExtractStream () {
+ if (mBzipStream) {
+ mz_stream_bzip_close(mBzipStream);
+ mz_stream_bzip_delete(&mBzipStream);
+ }
+
+ if (mFileStream) {
+ mz_stream_os_close(mFileStream);
+ mz_stream_os_delete(&mFileStream);
+ }
+ }
+
+ void *getInternalStream () const {
+ return mBzipStream;
+ }
+
+ int load (const char *filePath) {
+ Q_ASSERT(!mFileStream);
+ Q_ASSERT(!mBzipStream);
+
+ // 1. Open file stream.
+ if (!mz_stream_os_create(&mFileStream))
+ return MZ_MEM_ERROR;
+ Q_CHECK_PTR(mFileStream);
+
+ int error;
+ if ((error = mz_stream_os_open(mFileStream, filePath, MZ_OPEN_MODE_READ)) != MZ_OK)
+ return error;
+
+ // 2. Open bzip stream.
+ if (!mz_stream_bzip_create(&mBzipStream))
+ return MZ_MEM_ERROR;
+ Q_CHECK_PTR(mBzipStream);
+ if ((error = mz_stream_bzip_open(mBzipStream, NULL, MZ_OPEN_MODE_READ)) != MZ_OK)
+ return error;
+
+ // 3. Link file stream to bzip stream.
+ return mz_stream_set_base(mBzipStream, mFileStream);
+ }
+
+private:
+ void *mFileStream;
+ void *mBzipStream;
+};
// -----------------------------------------------------------------------------
+FileExtractor::FileExtractor (QObject *parent) : QObject(parent) {}
+
+FileExtractor::~FileExtractor () {}
+
void FileExtractor::extract () {
if (mExtracting) {
qWarning() << "Unable to extract file. Already extracting!";
@@ -56,7 +102,9 @@ void FileExtractor::extract () {
// 1. Open archive stream.
// TODO: Test extension.
- int error = openMinizipStream(&mStream, mFile.toLatin1().constData());
+ Q_ASSERT(!mStream);
+ mStream.reset(new ExtractStream());
+ int error = mStream->load(mFile.toLatin1().constData());
if (error != MZ_OK) {
emitExtractFailed(error);
return;
@@ -145,10 +193,15 @@ void FileExtractor::setExtracting (bool extracting) {
}
void FileExtractor::clean () {
- mz_stream_bzip_delete(&mStream);
+ mStream.reset(nullptr);
mDestinationFile.close();
- mTimer->stop();
- mTimer->deleteLater();
+
+ if (mTimer) {
+ mTimer->stop();
+ mTimer->deleteLater();
+ mTimer = nullptr;
+ }
+
setExtracting(false);
}
@@ -175,14 +228,19 @@ void FileExtractor::emitOutputError () {
void FileExtractor::handleExtraction () {
char buffer[4096];
- int32_t readBytes = mz_stream_bzip_read(mStream, buffer, sizeof buffer);
+
+ void *stream = mStream.data()->getInternalStream();
+
+ int32_t readBytes = mz_stream_bzip_read(stream, buffer, sizeof buffer);
if (readBytes == 0)
emitExtractFinished();
else if (readBytes < 0)
emitExtractFailed(readBytes);
else {
- setReadBytes(mReadBytes + readBytes);
- if (mDestinationFile.write(buffer, sizeof buffer) == -1)
+ int64_t inputReadBytes;
+ mz_stream_bzip_get_prop_int64(stream, MZ_STREAM_PROP_TOTAL_IN, &inputReadBytes);
+ setReadBytes(inputReadBytes);
+ if (mDestinationFile.write(buffer, readBytes) == -1)
emitOutputError();
}
}
diff --git a/src/components/file/FileExtractor.hpp b/src/components/file/FileExtractor.hpp
index 7b5cc6ed9..24c6f51ba 100644
--- a/src/components/file/FileExtractor.hpp
+++ b/src/components/file/FileExtractor.hpp
@@ -30,8 +30,12 @@
// Supports only bzip file.
class FileExtractor : public QObject {
+ class ExtractStream;
+
Q_OBJECT;
+ // TODO: Add an error property to use in UI.
+
Q_PROPERTY(QString file READ getFile WRITE setFile NOTIFY fileChanged);
Q_PROPERTY(QString extractFolder READ getExtractFolder WRITE setExtractFolder NOTIFY extractFolderChanged);
Q_PROPERTY(qint64 readBytes READ getReadBytes NOTIFY readBytesChanged);
@@ -39,6 +43,9 @@ class FileExtractor : public QObject {
Q_PROPERTY(bool extracting READ getExtracting NOTIFY extractingChanged);
public:
+ FileExtractor (QObject *parent = nullptr);
+ ~FileExtractor ();
+
Q_INVOKABLE void extract ();
signals:
@@ -82,7 +89,7 @@ private:
qint64 mTotalBytes = 0;
bool mExtracting = false;
- void *mStream = nullptr;
+ QScopedPointer mStream;
QTimer *mTimer = nullptr;
};
diff --git a/src/utils/Utils.hpp b/src/utils/Utils.hpp
index fd9c72f61..a77e1b6d1 100644
--- a/src/utils/Utils.hpp
+++ b/src/utils/Utils.hpp
@@ -41,12 +41,12 @@
#endif // ifndef UTILS_NO_BREAK
namespace Utils {
- inline QString coreStringToAppString (const std::string &string) {
- return QString::fromLocal8Bit(string.c_str(), int(string.size()));
+ inline QString coreStringToAppString (const std::string &str) {
+ return QString::fromLocal8Bit(str.c_str(), int(str.size()));
}
- inline std::string appStringToCoreString (const QString &string) {
- return string.toLocal8Bit().constData();
+ inline std::string appStringToCoreString (const QString &str) {
+ return qPrintable(str);
}
// Reverse function of strstr.
diff --git a/ui/modules/Common/Form/ComboBox.qml b/ui/modules/Common/Form/ComboBox.qml
index d0873fa6d..9d6a93bd2 100644
--- a/ui/modules/Common/Form/ComboBox.qml
+++ b/ui/modules/Common/Form/ComboBox.qml
@@ -4,7 +4,6 @@ import QtQuick.Layouts 1.3
import Common 1.0
import Common.Styles 1.0
-import Linphone 1.0
import Utils 1.0
import 'ComboBox.js' as Logic
diff --git a/ui/modules/Common/Window/Window.js b/ui/modules/Common/Window/Window.js
index 0ae78c5c0..2051a318d 100644
--- a/ui/modules/Common/Window/Window.js
+++ b/ui/modules/Common/Window/Window.js
@@ -20,10 +20,10 @@ function attachVirtualWindow (component, properties, exitStatusHandler) {
properties: properties
})
+ object.exitStatus.connect(detachVirtualWindow)
if (exitStatusHandler) {
object.exitStatus.connect(exitStatusHandler)
}
- object.exitStatus.connect(detachVirtualWindow)
virtualWindow.setContent(object)
diff --git a/ui/modules/Linphone/Chat/FileMessage.qml b/ui/modules/Linphone/Chat/FileMessage.qml
index 33f3037df..13d2f6420 100644
--- a/ui/modules/Linphone/Chat/FileMessage.qml
+++ b/ui/modules/Linphone/Chat/FileMessage.qml
@@ -212,6 +212,8 @@ Row {
color: ChatStyle.entry.message.file.status.bar.contentItem.color
height: parent.height
width: progressBar.visualPosition * parent.width
+
+ radius: ChatStyle.entry.message.file.status.bar.radius
}
}
}
diff --git a/ui/modules/Linphone/Codecs/CodecsViewer.qml b/ui/modules/Linphone/Codecs/CodecsViewer.qml
index bb38295bb..f61304eaa 100644
--- a/ui/modules/Linphone/Codecs/CodecsViewer.qml
+++ b/ui/modules/Linphone/Codecs/CodecsViewer.qml
@@ -8,8 +8,16 @@ import Linphone.Styles 1.0
// =============================================================================
Column {
+ id: codecsViewer
+
+ // ---------------------------------------------------------------------------
+
property alias model: view.model
+ // ---------------------------------------------------------------------------
+
+ signal downloadRequested (var codecInfo)
+
// ---------------------------------------------------------------------------
// Header.
// ---------------------------------------------------------------------------
@@ -75,9 +83,9 @@ Column {
height: count * CodecsViewerStyle.attribute.height
- // -----------------------------------------------------------------------
+ // -------------------------------------------------------------------------
// One codec.
- // -----------------------------------------------------------------------
+ // -------------------------------------------------------------------------
delegate: MouseArea {
id: dragArea
@@ -110,6 +118,8 @@ Column {
Rectangle {
id: content
+ readonly property bool isDownloadable: Boolean($codec.downloadUrl)
+
Drag.active: dragArea.held
Drag.source: dragArea
Drag.hotSpot.x: width / 2
@@ -139,25 +149,26 @@ Column {
CodecAttribute {
Layout.preferredWidth: CodecsViewerStyle.column.encoderDescriptionWidth
- text: $codec.encoderDescription
+ text: $codec.encoderDescription || ''
}
CodecAttribute {
Layout.preferredWidth: CodecsViewerStyle.column.clockRateWidth
- text: $codec.clockRate
+ text: $codec.clockRate || ''
}
NumericField {
Layout.preferredWidth: CodecsViewerStyle.column.bitrateWidth
- readOnly: !$codec.isVbr
- text: $codec.bitrate
+ readOnly: content.isDownloadable || !$codec.isVbr
+ text: $codec.bitrate || ''
onEditingFinished: view.model.setBitrate(index, text)
}
TextField {
Layout.preferredWidth: CodecsViewerStyle.column.recvFmtpWidth
- text: $codec.recvFmtp
+ readOnly: content.isDownloadable
+ text: $codec.recvFmtp || ''
onEditingFinished: view.model.setRecvFmtp(index, text)
}
@@ -165,9 +176,11 @@ Column {
Switch {
Layout.fillWidth: true
- checked: $codec.enabled
+ checked: Boolean($codec.enabled)
- onClicked: view.model.enableCodec(index, !checked)
+ onClicked: !checked && content.isDownloadable
+ ? downloadRequested($codec)
+ : view.model.enableCodec(index, !checked)
}
}
}
diff --git a/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml b/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml
new file mode 100644
index 000000000..89016e5f8
--- /dev/null
+++ b/ui/modules/Linphone/Dialog/OnlineInstallerDialog.qml
@@ -0,0 +1,145 @@
+import QtQuick 2.7
+import QtQuick.Controls 2.2
+
+import Common 1.0
+import Linphone 1.0
+import Linphone.Styles 1.0
+import Utils 1.0
+
+// =============================================================================
+
+DialogPlus {
+ id: dialog
+
+ // ---------------------------------------------------------------------------
+
+ property alias downloadUrl: fileDownloader.url
+ property alias installFolder: fileDownloader.downloadFolder
+ property bool extract: false
+ property string fileName
+
+ property bool _installing: false
+ property int _exitStatus: -1 // Not downloaded for the moment.
+
+ // ---------------------------------------------------------------------------
+
+ function install () {
+ dialog._installing = true
+ fileDownloader.download()
+ }
+
+ function _endInstall (exitStatus) {
+ if (dialog.extract)
+ fileDownloader.remove()
+ dialog._exitStatus = exitStatus
+ dialog._installing = false
+ }
+
+ // ---------------------------------------------------------------------------
+
+ // TODO: Improve one day. Do not launch download directly.
+ // Provide a download function (window.attachVirtualWindow cannot call
+ // function after creation at this moment).
+ Component.onCompleted: dialog.install()
+
+ // ---------------------------------------------------------------------------
+
+ buttons: [
+ // TODO: Add a retry button???
+ TextButtonB {
+ enabled: !dialog._installing && !fileDownloader.downloading && !fileExtractor.extracting
+ text: qsTr('confirm')
+
+ onClicked: exit(1)
+ }
+ ]
+
+ centeredButtons: true
+ descriptionText: {
+ var str
+
+ if (dialog.extracting) {
+ str = qsTr('onlineInstallerExtractingDescription')
+ } else if (dialog._installing) {
+ str = qsTr('onlineInstallerDownloadingDescription')
+ } else if (dialog._exitStatus > 0) {
+ str = qsTr('onlineInstallerFinishedDescription')
+ } else {
+ str = qsTr('onlineInstallerFailedDescription')
+ }
+
+ return str.replace('%1', dialog.fileName)
+ }
+ height: OnlineInstallerDialogStyle.height
+ width: OnlineInstallerDialogStyle.width
+
+ Column {
+ anchors.verticalCenter: parent.verticalCenter
+ width: parent.width
+ spacing: OnlineInstallerDialogStyle.column.spacing
+
+ ProgressBar {
+ id: progressBar
+
+ property var target: fileDownloader
+
+ height: OnlineInstallerDialogStyle.column.bar.height
+ width: parent.width
+
+ to: target.totalBytes
+ value: target.readBytes
+ indeterminate : true
+
+ background: Rectangle {
+ color: OnlineInstallerDialogStyle.column.bar.background.color
+ radius: OnlineInstallerDialogStyle.column.bar.radius
+ }
+
+ contentItem: Item {
+ Rectangle {
+ color: dialog._exitStatus
+ ? OnlineInstallerDialogStyle.column.bar.contentItem.color.normal
+ : OnlineInstallerDialogStyle.column.bar.contentItem.color.failed
+ height: parent.height
+ radius: OnlineInstallerDialogStyle.column.bar.radius
+ width: progressBar.visualPosition * parent.width
+ }
+ }
+ }
+
+ Text {
+ anchors.right: parent.right
+ color: OnlineInstallerDialogStyle.column.text.color
+ font.pointSize: OnlineInstallerDialogStyle.column.text.pointSize
+
+ text: {
+ var fileSize = Utils.formatSize(fileDownloader.totalBytes)
+ return Utils.formatSize(fileDownloader.readBytes) + '/' + fileSize
+ }
+ }
+
+ FileDownloader {
+ id: fileDownloader
+
+ onDownloadFailed: dialog._endInstall(0)
+ onDownloadFinished: {
+ fileExtractor.file = filePath
+ if (dialog.extract) {
+ progressBar.target = fileExtractor
+ fileExtractor.extract()
+ } else {
+ dialog._endInstall(1)
+ }
+ }
+ }
+
+ FileExtractor {
+ id: fileExtractor
+
+ extractFolder: dialog.installFolder
+
+ onExtractFailed: dialog._endInstall(0)
+ onExtractFinished: dialog._endInstall(1)
+ }
+ }
+}
diff --git a/ui/modules/Linphone/Styles/Dialog/OnlineInstallerDialogStyle.qml b/ui/modules/Linphone/Styles/Dialog/OnlineInstallerDialogStyle.qml
new file mode 100644
index 000000000..2bbb33b82
--- /dev/null
+++ b/ui/modules/Linphone/Styles/Dialog/OnlineInstallerDialogStyle.qml
@@ -0,0 +1,37 @@
+pragma Singleton
+import QtQml 2.2
+
+import Colors 1.0
+import Units 1.0
+
+// =============================================================================
+
+QtObject {
+ property int height: 200
+ property int width: 400
+
+ property QtObject column: QtObject {
+ property int spacing: 6
+
+ property QtObject bar: QtObject {
+ property int height: 20
+ property int radius: 6
+
+ property QtObject background: QtObject {
+ property color color: Colors.f
+ }
+
+ property QtObject contentItem: QtObject {
+ property QtObject color: QtObject {
+ property color failed: Colors.error
+ property color normal: Colors.z
+ }
+ }
+ }
+
+ property QtObject text: QtObject {
+ property color color: Colors.r
+ property int pointSize: Units.dp * 11
+ }
+ }
+}
diff --git a/ui/modules/Linphone/Styles/qmldir b/ui/modules/Linphone/Styles/qmldir
index 8b2c423b6..deb37e948 100644
--- a/ui/modules/Linphone/Styles/qmldir
+++ b/ui/modules/Linphone/Styles/qmldir
@@ -23,6 +23,8 @@ singleton ContactDescriptionStyle 1.0 Contact/ContactDescriptionSty
singleton ContactStyle 1.0 Contact/ContactStyle.qml
singleton MessagesCounterStyle 1.0 Contact/MessagesCounterStyle.qml
+singleton OnlineInstallerDialogStyle 1.0 Dialog/OnlineInstallerDialogStyle.qml
+
singleton SipAddressesMenuStyle 1.0 Menus/SipAddressesMenuStyle.qml
singleton NotificationBasicStyle 1.0 Notifications/NotificationBasicStyle.qml
diff --git a/ui/scripts/LinphoneUtils/linphone-utils.js b/ui/scripts/LinphoneUtils/linphone-utils.js
index 49186b570..c64a3b106 100644
--- a/ui/scripts/LinphoneUtils/linphone-utils.js
+++ b/ui/scripts/LinphoneUtils/linphone-utils.js
@@ -4,8 +4,12 @@
.pragma library
+.import Linphone 1.0 as Linphone
+
.import 'qrc:/ui/scripts/Utils/utils.js' as Utils
+// =============================================================================
+// Contact/SIP address helpers.
// =============================================================================
function _getDisplayNameFromQuotedString (str) {
@@ -84,3 +88,43 @@ function getContactUsername (contact) {
name = _getUsername(object)
return name == null ? 'Bad EGG' : name
}
+
+// =============================================================================
+// Codec helpers.
+// =============================================================================
+
+function openCodecOnlineInstallerDialog (window, codecInfo, cb) {
+ var VideoCodecsModel = Linphone.VideoCodecsModel
+ window.attachVirtualWindow(Utils.buildDialogUri('ConfirmDialog'), {
+ descriptionText: qsTr('downloadCodecDescription')
+ .replace('%1', codecInfo.mime)
+ .replace('%2', codecInfo.encoderDescription)
+ }, function (status) {
+ if (status) {
+ window.attachVirtualWindow(buildDialogUri('OnlineInstallerDialog'), {
+ downloadUrl: codecInfo.downloadUrl,
+ extract: true,
+ fileName: codecInfo.mime,
+ installFolder: VideoCodecsModel.codecsFolder
+ }, function (status) {
+ if (status) {
+ VideoCodecsModel.reload()
+ }
+ if (cb) {
+ cb(window)
+ }
+ })
+ }
+ else if (cb) {
+ cb(window)
+ }
+ })
+}
+
+// =============================================================================
+// QML helpers.
+// =============================================================================
+
+function buildDialogUri (component) {
+ return 'qrc:/ui/modules/Linphone/Dialog/' + component + '.qml'
+}
diff --git a/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithEmail.qml b/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithEmail.qml
index 34aeed44b..4d7b8520d 100644
--- a/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithEmail.qml
+++ b/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithEmail.qml
@@ -2,6 +2,7 @@ import QtQuick 2.7
import Common 1.0
import Linphone 1.0
+import LinphoneUtils 1.0
import App.Styles 1.0
@@ -50,8 +51,16 @@ AssistantAbstractView {
onActivateStatusChanged: {
requestBlock.stop(error)
if (!error.length) {
- window.unlockView()
- window.setView('Home')
+ function quitToHome (window) {
+ window.unlockView()
+ window.setView('Home')
+ }
+ var codecInfo = VideoCodecsModel.getCodecInfo('H264')
+ if (codecInfo.downloadUrl) {
+ LinphoneUtils.openCodecOnlineInstallerDialog(window, codecInfo, quitToHome)
+ } else {
+ quitToHome(window)
+ }
}
}
}
diff --git a/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithPhoneNumber.qml b/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithPhoneNumber.qml
index c996b3885..c53757355 100644
--- a/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithPhoneNumber.qml
+++ b/ui/views/App/Main/Assistant/ActivateLinphoneSipAccountWithPhoneNumber.qml
@@ -2,6 +2,7 @@ import QtQuick 2.7
import Common 1.0
import Linphone 1.0
+import LinphoneUtils 1.0
import App.Styles 1.0
@@ -62,8 +63,16 @@ AssistantAbstractView {
onActivateStatusChanged: {
requestBlock.stop(error)
if (!error.length) {
- window.unlockView()
- window.setView('Home')
+ function quitToHome (window) {
+ window.unlockView()
+ window.setView('Home')
+ }
+ var codecInfo = VideoCodecsModel.getCodecInfo('H264')
+ if (codecInfo.downloadUrl) {
+ LinphoneUtils.openCodecOnlineInstallerDialog(window, codecInfo, quitToHome)
+ } else {
+ quitToHome(window)
+ }
}
}
}
diff --git a/ui/views/App/Main/Assistant/UseLinphoneSipAccount.qml b/ui/views/App/Main/Assistant/UseLinphoneSipAccount.qml
index aa38ec928..2b74d8db8 100644
--- a/ui/views/App/Main/Assistant/UseLinphoneSipAccount.qml
+++ b/ui/views/App/Main/Assistant/UseLinphoneSipAccount.qml
@@ -2,6 +2,7 @@ import QtQuick 2.7
import Common 1.0
import Linphone 1.0
+import LinphoneUtils 1.0
import App.Styles 1.0
@@ -17,6 +18,8 @@ AssistantAbstractView {
title: qsTr('useLinphoneSipAccountTitle')
+
+
// ---------------------------------------------------------------------------
Column {
@@ -86,7 +89,14 @@ AssistantAbstractView {
onLoginStatusChanged: {
requestBlock.stop(error)
if (!error.length) {
- window.setView('Home')
+ var codecInfo = VideoCodecsModel.getCodecInfo('H264')
+ if (codecInfo.downloadUrl) {
+ LinphoneUtils.openCodecOnlineInstallerDialog(window, codecInfo, function cb (window) {
+ window.setView('Home')
+ })
+ } else {
+ window.setView('Home')
+ }
}
}
diff --git a/ui/views/App/Settings/SettingsVideo.js b/ui/views/App/Settings/SettingsVideo.js
index 6e89a91cb..7acc12e1b 100644
--- a/ui/views/App/Settings/SettingsVideo.js
+++ b/ui/views/App/Settings/SettingsVideo.js
@@ -4,6 +4,8 @@
.import Linphone 1.0 as Linphone
+.import 'qrc:/ui/scripts/LinphoneUtils/linphone-utils.js' as LinphoneUtils
+
// =============================================================================
function showVideoPreview (account) {
@@ -23,3 +25,7 @@ function updateVideoPreview () {
function hideVideoPreview () {
window.detachVirtualWindow()
}
+
+function handleCodecDownloadRequested (codecInfo) {
+ LinphoneUtils.openCodecOnlineInstallerDialog(window, codecInfo)
+}
diff --git a/ui/views/App/Settings/SettingsVideo.qml b/ui/views/App/Settings/SettingsVideo.qml
index c270169e6..ecda36380 100644
--- a/ui/views/App/Settings/SettingsVideo.qml
+++ b/ui/views/App/Settings/SettingsVideo.qml
@@ -142,6 +142,8 @@ TabContainer {
CodecsViewer {
model: VideoCodecsModel
width: parent.width
+
+ onDownloadRequested: Logic.handleCodecDownloadRequested(codecInfo)
}
}
}
diff --git a/ui/views/App/Settings/SettingsWindow.qml b/ui/views/App/Settings/SettingsWindow.qml
index 589095447..2076c4cef 100644
--- a/ui/views/App/Settings/SettingsWindow.qml
+++ b/ui/views/App/Settings/SettingsWindow.qml
@@ -12,7 +12,6 @@ import App.Styles 1.0
ApplicationWindow {
id: window
-
minimumHeight: SettingsWindowStyle.height
minimumWidth: SettingsWindowStyle.width