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