H264 Downloadable codec

This commit is contained in:
Christophe Deschamps 2024-11-08 15:32:20 +00:00
parent 85ca6d79ce
commit 425751413d
29 changed files with 1262 additions and 58 deletions

View file

@ -433,8 +433,11 @@ void App::initCore() {
QMetaObject::invokeMethod(
mLinphoneThread->getThreadId(),
[this]() mutable {
lInfo() << log().arg("Updating downloaded codec files");
Utils::updateCodecs(); // removing codec updates suffic (.in) before the core is created.
lInfo() << log().arg("Starting Core");
CoreModel::getInstance()->start();
Utils::loadDownloadedCodecs();
auto coreStarted = CoreModel::getInstance()->getCore()->getGlobalState() == linphone::GlobalState::On;
lDebug() << log().arg("Creating SettingsModel");
SettingsModel::create();
@ -541,6 +544,8 @@ void App::initCore() {
},
Qt::QueuedConnection);
Utils::checkDownloadedCodecsUpdates();
mEngine->load(url);
});
},
@ -631,6 +636,7 @@ void App::initCppInterfaces() {
qmlRegisterType<PayloadTypeGui>(Constants::MainQmlUri, 1, 0, "PayloadTypeGui");
qmlRegisterType<PayloadTypeProxy>(Constants::MainQmlUri, 1, 0, "PayloadTypeProxy");
qmlRegisterType<PayloadTypeCore>(Constants::MainQmlUri, 1, 0, "PayloadTypeCore");
qmlRegisterType<PayloadTypeCore>(Constants::MainQmlUri, 1, 0, "DownloadablePayloadTypeCore");
LinphoneEnums::registerMetaTypes();
}

View file

@ -80,6 +80,7 @@ list(APPEND _LINPHONEAPP_SOURCES
core/address-books/carddav/CarddavList.cpp
core/payload-type/PayloadTypeCore.cpp
core/payload-type/DownloadablePayloadTypeCore.cpp
core/payload-type/PayloadTypeGui.cpp
core/payload-type/PayloadTypeProxy.cpp
core/payload-type/PayloadTypeList.cpp

View file

@ -0,0 +1,142 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "DownloadablePayloadTypeCore.hpp"
#include "core/App.hpp"
#include "core/path/Paths.hpp"
#include "tool/file/FileDownloader.hpp"
#include "tool/file/FileExtractor.hpp"
DEFINE_ABSTRACT_OBJECT(DownloadablePayloadTypeCore)
QSharedPointer<DownloadablePayloadTypeCore> DownloadablePayloadTypeCore::create(PayloadTypeCore::Family family,
const QString &mimeType,
const QString &encoderDescription,
const QString &downloadUrl,
const QString &installName,
const QString &checkSum) {
auto sharedPointer = QSharedPointer<DownloadablePayloadTypeCore>(
new DownloadablePayloadTypeCore(family, mimeType, encoderDescription, downloadUrl, installName, checkSum),
&QObject::deleteLater);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
DownloadablePayloadTypeCore::DownloadablePayloadTypeCore(PayloadTypeCore::Family family,
const QString &mimeType,
const QString &encoderDescription,
const QString &downloadUrl,
const QString &installName,
const QString &checkSum)
: PayloadTypeCore() {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mFamily = family;
mMimeType = mimeType;
mEnabled = false;
mDownloadable = true;
mEncoderDescription = encoderDescription;
mDownloadUrl = downloadUrl;
mInstallName = installName;
mCheckSum = checkSum;
}
DownloadablePayloadTypeCore::~DownloadablePayloadTypeCore() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
}
void DownloadablePayloadTypeCore::downloadAndExtract(bool isUpdate) {
lInfo() << log().arg("Downloading `%1` codec...").arg(mMimeType);
auto codecsFolder = Paths::getCodecsDirPath();
QString versionFilePath = codecsFolder + mMimeType + ".txt";
QFile versionFile(versionFilePath);
FileDownloader *fileDownloader = new FileDownloader(this);
fileDownloader->setUrl(QUrl(mDownloadUrl));
fileDownloader->setDownloadFolder(codecsFolder);
FileExtractor *fileExtractor = new FileExtractor(fileDownloader);
fileExtractor->setExtractFolder(codecsFolder);
fileExtractor->setExtractName(mInstallName + (isUpdate ? ".in" : ""));
QObject::connect(fileDownloader, &FileDownloader::downloadFinished,
[this, fileDownloader, fileExtractor, checksum = mCheckSum](const QString &filePath) {
fileExtractor->setFile(filePath);
QString fileChecksum = Utils::getFileChecksum(filePath);
if (checksum.isEmpty() || fileChecksum == checksum) fileExtractor->extract();
else {
lWarning() << log().arg("File cannot be downloaded : Bad checksum : ") << fileChecksum;
fileDownloader->remove();
fileDownloader->deleteLater();
emit downloadError();
}
});
QObject::connect(fileDownloader, &FileDownloader::downloadFailed, [this, fileDownloader]() {
fileDownloader->deleteLater();
emit downloadError();
});
QObject::connect(fileExtractor, &FileExtractor::extractFinished,
[this, fileDownloader, fileExtractor, versionFilePath, downloadUrl = mDownloadUrl]() {
QFile versionFile(versionFilePath);
if (!versionFile.open(QIODevice::WriteOnly)) {
lWarning() << log().arg("Unable to write codec version in: `%1`.").arg(versionFilePath);
emit extractError();
} else if (versionFile.write(Utils::appStringToCoreString(downloadUrl).c_str(),
downloadUrl.length()) == -1) {
fileExtractor->remove();
versionFile.close();
versionFile.remove();
emit extractError();
} else emit success();
fileDownloader->remove();
fileDownloader->deleteLater();
});
QObject::connect(fileExtractor, &FileExtractor::extractFailed, [this, fileDownloader]() {
fileDownloader->remove();
fileDownloader->deleteLater();
emit extractError();
});
fileDownloader->download();
}
bool DownloadablePayloadTypeCore::shouldDownloadUpdate() {
auto codecsFolder = Paths::getCodecsDirPath();
QString versionFilePath = codecsFolder + mMimeType + ".txt";
QFile versionFile(versionFilePath);
if (!versionFile.exists() && !QFileInfo::exists(codecsFolder + mInstallName)) {
lWarning() << log().arg("Codec `%1` is not installed.").arg(versionFilePath);
return false;
}
if (!versionFile.open(QIODevice::ReadOnly)) {
lWarning() << log().arg("Codec `%1` : unable to read codec version, attempting download.").arg(versionFilePath);
return true;
} else if (!QString::compare(QTextStream(&versionFile).readAll(), mDownloadUrl, Qt::CaseInsensitive)) {
lInfo() << log().arg("Codec `%1` is installed and up to date.").arg(versionFilePath);
return false;
} else {
return true;
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef DOWNLOADABLE_PAYLOAD_TYPE_CORE_H_
#define DOWNLOADABLE_PAYLOAD_TYPE_CORE_H_
#include "PayloadTypeCore.hpp"
#include "tool/AbstractObject.hpp"
#include <QObject>
#include <QSharedPointer>
#include <linphone++/linphone.hh>
class DownloadablePayloadTypeCore : public PayloadTypeCore {
Q_OBJECT
public:
Q_INVOKABLE void downloadAndExtract(bool isUpdate = false);
bool shouldDownloadUpdate();
static QSharedPointer<DownloadablePayloadTypeCore> create(PayloadTypeCore::Family family,
const QString &mime,
const QString &encoderDescription,
const QString &downloadUrl,
const QString &installName,
const QString &checkSum);
DownloadablePayloadTypeCore(PayloadTypeCore::Family family,
const QString &mimeType,
const QString &encoderDescription,
const QString &downloadUrl,
const QString &installName,
const QString &checkSum);
~DownloadablePayloadTypeCore();
void setSelf(QSharedPointer<DownloadablePayloadTypeCore> me);
signals:
void success();
void downloadError();
void extractError();
void installedChanged();
void versionChanged();
private:
QString mDownloadUrl;
QString mInstallName;
QString mCheckSum;
bool mInstalled;
QString mVersion;
DECLARE_ABSTRACT_OBJECT
};
Q_DECLARE_METATYPE(DownloadablePayloadTypeCore *)
#endif

View file

@ -23,16 +23,16 @@
DEFINE_ABSTRACT_OBJECT(PayloadTypeCore)
QSharedPointer<PayloadTypeCore> PayloadTypeCore::create(const std::shared_ptr<linphone::PayloadType> &payloadType,
Family family) {
QSharedPointer<PayloadTypeCore> PayloadTypeCore::create(Family family,
const std::shared_ptr<linphone::PayloadType> &payloadType) {
auto sharedPointer =
QSharedPointer<PayloadTypeCore>(new PayloadTypeCore(payloadType, family), &QObject::deleteLater);
QSharedPointer<PayloadTypeCore>(new PayloadTypeCore(family, payloadType), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
PayloadTypeCore::PayloadTypeCore(const std::shared_ptr<linphone::PayloadType> &payloadType, Family family)
PayloadTypeCore::PayloadTypeCore(Family family, const std::shared_ptr<linphone::PayloadType> &payloadType)
: QObject(nullptr) {
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
@ -42,6 +42,7 @@ PayloadTypeCore::PayloadTypeCore(const std::shared_ptr<linphone::PayloadType> &p
INIT_CORE_MEMBER(ClockRate, mPayloadTypeModel)
INIT_CORE_MEMBER(MimeType, mPayloadTypeModel)
INIT_CORE_MEMBER(RecvFmtp, mPayloadTypeModel)
INIT_CORE_MEMBER(EncoderDescription, mPayloadTypeModel)
}
PayloadTypeCore::~PayloadTypeCore() {
@ -62,3 +63,7 @@ PayloadTypeCore::Family PayloadTypeCore::getFamily() {
QString PayloadTypeCore::getMimeType() {
return mMimeType;
}
bool PayloadTypeCore::getDownloadable() {
return mDownloadable;
}

View file

@ -33,25 +33,32 @@ class PayloadTypeCore : public QObject, public AbstractObject {
Q_ENUMS(Family)
Q_PROPERTY(Family family MEMBER mFamily CONSTANT)
DECLARE_CORE_GETSET_MEMBER(bool, enabled, Enabled)
DECLARE_CORE_MEMBER(int, clockRate, ClockRate)
DECLARE_CORE_MEMBER(QString, mimeType, MimeType)
DECLARE_CORE_MEMBER(QString, recvFmtp, RecvFmtp)
public:
enum Family { None, Audio, Video, Text };
static QSharedPointer<PayloadTypeCore> create(const std::shared_ptr<linphone::PayloadType> &payloadType,
Family family);
PayloadTypeCore(const std::shared_ptr<linphone::PayloadType> &payloadType, Family family);
static QSharedPointer<PayloadTypeCore> create(Family family,
const std::shared_ptr<linphone::PayloadType> &payloadType);
PayloadTypeCore(Family family, const std::shared_ptr<linphone::PayloadType> &payloadType);
PayloadTypeCore() {};
~PayloadTypeCore();
void setSelf(QSharedPointer<PayloadTypeCore> me);
Family getFamily();
bool getDownloadable();
QString getMimeType();
private:
protected:
Family mFamily;
bool mDownloadable = false;
DECLARE_CORE_GETSET_MEMBER(bool, enabled, Enabled)
DECLARE_CORE_MEMBER(QString, mimeType, MimeType)
DECLARE_CORE_MEMBER(QString, encoderDescription, EncoderDescription)
private:
std::shared_ptr<PayloadTypeModel> mPayloadTypeModel;
QSharedPointer<SafeConnection<PayloadTypeCore, PayloadTypeModel>> mPayloadTypeModelConnection;

View file

@ -19,8 +19,10 @@
*/
#include "PayloadTypeList.hpp"
#include "DownloadablePayloadTypeCore.hpp"
#include "PayloadTypeGui.hpp"
#include "core/App.hpp"
#include "core/path/Paths.hpp"
#include "model/object/VariantObject.hpp"
#include <QSharedPointer>
#include <linphone++/linphone.hh>
@ -37,12 +39,12 @@ QSharedPointer<PayloadTypeList> PayloadTypeList::create() {
}
PayloadTypeList::PayloadTypeList(QObject *parent) : ListProxy(parent) {
mustBeInMainThread(getClassName());
mustBeInMainThread(log().arg(Q_FUNC_INFO));
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
}
PayloadTypeList::~PayloadTypeList() {
mustBeInMainThread("~" + getClassName());
mustBeInMainThread(log().arg(Q_FUNC_INFO));
mModelConnection = nullptr;
}
@ -52,21 +54,40 @@ void PayloadTypeList::setSelf(QSharedPointer<PayloadTypeList> me) {
mModelConnection->makeConnectToCore(&PayloadTypeList::lUpdate, [this]() {
mModelConnection->invokeToModel([this]() {
QList<QSharedPointer<PayloadTypeCore>> *payloadTypes = new QList<QSharedPointer<PayloadTypeCore>>();
mustBeInLinphoneThread(getClassName());
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
Utils::loadDownloadedCodecs();
// Audio
for (auto payloadType : CoreModel::getInstance()->getCore()->getAudioPayloadTypes()) {
auto model = PayloadTypeCore::create(payloadType, PayloadTypeCore::Family::Audio);
payloadTypes->push_back(model);
auto core = PayloadTypeCore::create(PayloadTypeCore::Family::Audio, payloadType);
payloadTypes->push_back(core);
}
for (auto payloadType : CoreModel::getInstance()->getCore()->getVideoPayloadTypes()) {
auto model = PayloadTypeCore::create(payloadType, PayloadTypeCore::Family::Video);
payloadTypes->push_back(model);
// Video
auto videoCodecs = CoreModel::getInstance()->getCore()->getVideoPayloadTypes();
for (auto payloadType : videoCodecs) {
auto core = PayloadTypeCore::create(PayloadTypeCore::Family::Video, payloadType);
payloadTypes->push_back(core);
}
// Downloadable Video
for (auto downloadableVideoCodec : Utils::getDownloadableVideoPayloadTypes()) {
if (find_if(videoCodecs.begin(), videoCodecs.end(),
[downloadableVideoCodec](const std::shared_ptr<linphone::PayloadType> &codec) {
return Utils::coreStringToAppString(codec->getMimeType()) ==
downloadableVideoCodec->getMimeType();
}) == videoCodecs.end())
payloadTypes->append(downloadableVideoCodec.dynamicCast<PayloadTypeCore>());
}
// Text
for (auto payloadType : CoreModel::getInstance()->getCore()->getTextPayloadTypes()) {
auto model = PayloadTypeCore::create(payloadType, PayloadTypeCore::Family::Text);
payloadTypes->push_back(model);
auto core = PayloadTypeCore::create(PayloadTypeCore::Family::Text, payloadType);
payloadTypes->push_back(core);
}
mModelConnection->invokeToCore([this, payloadTypes]() {
mustBeInMainThread(getClassName());
mustBeInMainThread(log().arg(Q_FUNC_INFO));
resetData<PayloadTypeCore>(*payloadTypes);
delete payloadTypes;
});

View file

@ -36,7 +36,7 @@ class PayloadTypeList : public ListProxy, public AbstractObject {
public:
static QSharedPointer<PayloadTypeList> create();
PayloadTypeList(QObject *parent = Q_NULLPTR);
~PayloadTypeList();
@ -49,6 +49,7 @@ signals:
private:
QSharedPointer<SafeConnection<PayloadTypeList, CoreModel>> mModelConnection;
DECLARE_ABSTRACT_OBJECT
};

View file

@ -44,9 +44,21 @@ void PayloadTypeProxy::setFamily(PayloadTypeCore::Family data) {
}
}
bool PayloadTypeProxy::isDownloadable() const {
return dynamic_cast<SortFilterList *>(sourceModel())->mDownloadable;
}
void PayloadTypeProxy::setDownloadable(bool data) {
auto list = dynamic_cast<SortFilterList *>(sourceModel());
if (list->mDownloadable != data) {
list->mDownloadable = data;
downloadableChanged();
}
}
bool PayloadTypeProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
auto payload = qobject_cast<PayloadTypeList *>(sourceModel())->getAt<PayloadTypeCore>(sourceRow);
return payload->getFamily() == mFamily;
return payload->getFamily() == mFamily && payload->getDownloadable() == mDownloadable;
}
bool PayloadTypeProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const {
@ -55,3 +67,7 @@ bool PayloadTypeProxy::SortFilterList::lessThan(const QModelIndex &sourceLeft, c
return l->getMimeType() < r->getMimeType();
}
void PayloadTypeProxy::reload() {
emit mPayloadTypeList->lUpdate();
}

View file

@ -31,18 +31,24 @@ class PayloadTypeProxy : public LimitProxy, public AbstractObject {
Q_OBJECT
Q_PROPERTY(PayloadTypeCore::Family family READ getFamily WRITE setFamily NOTIFY familyChanged)
Q_PROPERTY(bool downloadable READ isDownloadable WRITE setDownloadable NOTIFY downloadableChanged)
public:
DECLARE_SORTFILTER_CLASS(PayloadTypeCore::Family mFamily;)
DECLARE_SORTFILTER_CLASS(PayloadTypeCore::Family mFamily; bool mDownloadable;)
Q_INVOKABLE void reload();
PayloadTypeProxy(QObject *parent = Q_NULLPTR);
~PayloadTypeProxy();
PayloadTypeCore::Family getFamily() const;
void setFamily(PayloadTypeCore::Family data);
bool isDownloadable() const;
void setDownloadable(bool data);
signals:
void familyChanged();
void downloadableChanged();
protected:
QSharedPointer<PayloadTypeList> mPayloadTypeList;

View file

@ -98,6 +98,7 @@ SettingsCore::SettingsCore(QObject *parent) : QObject(parent) {
INIT_CORE_MEMBER(SyncLdapContacts, settingsModel)
INIT_CORE_MEMBER(Ipv6Enabled, settingsModel)
INIT_CORE_MEMBER(ConfigLocale, settingsModel)
INIT_CORE_MEMBER(DownloadFolder, settingsModel)
}
SettingsCore::~SettingsCore() {
@ -348,6 +349,8 @@ void SettingsCore::setSelf(QSharedPointer<SettingsCore> me) {
Ipv6Enabled)
DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, QString,
configLocale, ConfigLocale)
DEFINE_CORE_GETSET_CONNECT(mSettingsModelConnection, SettingsCore, SettingsModel, settingsModel, QString,
downloadFolder, DownloadFolder)
auto coreModelConnection = QSharedPointer<SafeConnection<SettingsCore, CoreModel>>(
new SafeConnection<SettingsCore, CoreModel>(me, CoreModel::getInstance()), &QObject::deleteLater);
@ -523,3 +526,9 @@ bool SettingsCore::getSyncLdapContacts() const {
QString SettingsCore::getConfigLocale() const {
return mConfigLocale;
}
QString SettingsCore::getDownloadFolder() const {
auto path = mDownloadFolder;
if (mDownloadFolder.isEmpty()) path = Paths::getDownloadDirPath();
return QDir::cleanPath(path) + QDir::separator();
}

View file

@ -168,6 +168,7 @@ public:
DECLARE_CORE_GETSET_MEMBER(QVariantList, audioCodecs, AudioCodecs)
DECLARE_CORE_GETSET_MEMBER(QVariantList, videoCodecs, VideoCodecs)
DECLARE_CORE_GETSET(QString, configLocale, ConfigLocale)
DECLARE_CORE_GETSET(QString, downloadFolder, DownloadFolder)
signals:

View file

@ -36,3 +36,4 @@ DEFINE_GETSET_ENABLE(PayloadTypeModel, enabled, Enabled, mPayloadType)
DEFINE_GET(PayloadTypeModel, int, ClockRate, mPayloadType)
DEFINE_GET_STRING(PayloadTypeModel, MimeType, mPayloadType)
DEFINE_GET_STRING(PayloadTypeModel, RecvFmtp, mPayloadType)
DEFINE_GET_STRING(PayloadTypeModel, EncoderDescription, mPayloadType)

View file

@ -35,6 +35,7 @@ public:
int getClockRate() const;
QString getMimeType() const;
QString getRecvFmtp() const;
QString getEncoderDescription() const;
DECLARE_GETSET(bool, enabled, Enabled)

View file

@ -571,6 +571,7 @@ void SettingsModel::notifyConfigReady(){
DEFINE_NOTIFY_CONFIG_READY(exitOnClose, ExitOnClose)
DEFINE_NOTIFY_CONFIG_READY(syncLdapContacts, SyncLdapContacts)
DEFINE_NOTIFY_CONFIG_READY(configLocale, ConfigLocale)
DEFINE_NOTIFY_CONFIG_READY(downloadFolder, DownloadFolder)
}
DEFINE_GETSET_CONFIG(SettingsModel, bool, Bool, disableChatFeature, DisableChatFeature, "disable_chat_feature", true)
@ -673,4 +674,9 @@ DEFINE_GETSET_CONFIG_STRING(SettingsModel,
ConfigLocale,
"locale",
"")
DEFINE_GETSET_CONFIG_STRING(SettingsModel,
downloadFolder,
DownloadFolder,
"download_folder",
"")
// clang-format on

View file

@ -155,6 +155,7 @@ public:
DECLARE_GETSET(bool, syncLdapContacts, SyncLdapContacts)
DECLARE_GETSET(bool, ipv6Enabled, Ipv6Enabled)
DECLARE_GETSET(QString, configLocale, ConfigLocale)
DECLARE_GETSET(QString, downloadFolder, DownloadFolder)
signals:

View file

@ -15,6 +15,10 @@ list(APPEND _LINPHONEAPP_SOURCES
tool/request/RequestDialog.cpp
tool/request/AuthenticationDialog.cpp
tool/file/FileDownloader.cpp
tool/file/FileExtractor.cpp
)
if (APPLE)

View file

@ -159,3 +159,4 @@ constexpr char Constants::LinphoneBZip2_exe[];
constexpr char Constants::LinphoneBZip2_dll[];
constexpr char Constants::DefaultRlsUri[];
constexpr char Constants::DefaultLogsEmail[];
constexpr char Constants::DownloadDefaultFileName[];

View file

@ -175,6 +175,8 @@ public:
// 4 = RTP bundle mode
// 5 = Video Conference URI
// 6 = Publish expires
static constexpr char DownloadDefaultFileName[] = "download";
//--------------------------------------------------------------------------------
// CISCO
//--------------------------------------------------------------------------------

View file

@ -26,6 +26,7 @@
#include "core/conference/ConferenceInfoGui.hpp"
#include "core/friend/FriendGui.hpp"
#include "core/path/Paths.hpp"
#include "core/payload-type/DownloadablePayloadTypeCore.hpp"
#include "model/object/VariantObject.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/providers/AvatarProvider.hpp"
@ -33,13 +34,18 @@
#include <limits.h>
#include <QClipboard>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QDirIterator>
#include <QHostAddress>
#include <QImageReader>
#include <QLibrary>
#include <QQuickWindow>
#include <QRandomGenerator>
#include <QRegularExpression>
DEFINE_ABSTRACT_OBJECT(Utils)
// =============================================================================
char *Utils::rstrstr(const char *a, const char *b) {
@ -1403,3 +1409,77 @@ QString Utils::boldTextPart(const QString &text, const QString &regex) {
if (splittedText.size() > 0) result.append(splittedText[splittedText.size() - 1]);
return result;
}
QString Utils::getFileChecksum(const QString &filePath) {
QFile file(filePath);
if (file.open(QFile::ReadOnly)) {
QCryptographicHash hash(QCryptographicHash::Sha256);
if (hash.addData(&file)) {
return hash.result().toHex();
}
}
return QString();
}
// Codecs download
QList<QSharedPointer<DownloadablePayloadTypeCore>> Utils::getDownloadableVideoPayloadTypes() {
QList<QSharedPointer<DownloadablePayloadTypeCore>> payloadTypes;
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
auto ciscoH264 = DownloadablePayloadTypeCore::create(PayloadTypeCore::Family::Video, "H264",
Constants::H264Description, Constants::PluginUrlH264,
Constants::H264InstallName, Constants::PluginH264Check);
payloadTypes.push_back(ciscoH264);
#endif
return payloadTypes;
}
void Utils::checkDownloadedCodecsUpdates() {
for (auto codec : getDownloadableVideoPayloadTypes()) {
if (codec->shouldDownloadUpdate()) codec->downloadAndExtract(true);
}
}
// Load downloaded codecs like OpenH264 (needs to be after core is created and has loaded its plugins, as
// reloadMsPlugins modifies plugin path for the factory)
void Utils::loadDownloadedCodecs() {
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
QDirIterator it(Paths::getCodecsDirPath());
while (it.hasNext()) {
QFileInfo info(it.next());
const QString filename(info.fileName());
if (QLibrary::isLibrary(filename)) {
qInfo() << QStringLiteral("Loading `%1` symbols...").arg(filename);
if (!QLibrary(info.filePath()).load()) // lib.load())
qWarning() << QStringLiteral("Failed to load `%1` symbols.").arg(filename);
else qInfo() << QStringLiteral("Loaded `%1` symbols...").arg(filename);
}
}
CoreModel::getInstance()->getCore()->reloadMsPlugins("");
#endif // if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
}
// Removes .in suffix from downloaded updates.
// Updates are downloaded with .in suffix as they can't overwrite already loaded plugin
// they are loaded at next app startup.
void Utils::updateCodecs() {
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
static const QString codecSuffix = QStringLiteral(".%1").arg(Constants::LibraryExtension);
QDirIterator it(Paths::getCodecsDirPath());
while (it.hasNext()) {
QFileInfo info(it.next());
if (info.suffix() == QLatin1String("in")) {
QString codecName = info.completeBaseName();
if (codecName.endsWith(codecSuffix)) {
QString codecPath = info.dir().path() + QDir::separator() + codecName;
QFile::remove(codecPath);
QFile::rename(info.filePath(), codecPath);
}
}
}
#endif // if defined(Q_OS_LINUX) || defined(Q_OS_WIN)
}

View file

@ -26,6 +26,7 @@
#include <QString>
#include "Constants.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/LinphoneEnums.hpp"
// =============================================================================
@ -47,8 +48,9 @@ class QQuickWindow;
class VariantObject;
class CallGui;
class ConferenceInfoGui;
class DownloadablePayloadTypeCore;
class Utils : public QObject {
class Utils : public QObject, public AbstractObject {
Q_OBJECT
public:
Utils(QObject *parent = nullptr) : QObject(parent) {
@ -131,10 +133,15 @@ public:
Q_INVOKABLE void playDtmf(const QString &dtmf);
Q_INVOKABLE bool isInteger(const QString &text);
Q_INVOKABLE QString boldTextPart(const QString &text, const QString &regex);
Q_INVOKABLE static QString getFileChecksum(const QString &filePath);
static QString getApplicationProduct();
static QString getOsProduct();
static QString computeUserAgent();
static QList<QSharedPointer<DownloadablePayloadTypeCore>> getDownloadableVideoPayloadTypes();
static void checkDownloadedCodecsUpdates();
static void loadDownloadedCodecs();
static void updateCodecs();
static inline QString coreStringToAppString(const std::string &str) {
if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str);
@ -165,6 +172,9 @@ public:
return (volume - VuMin) / (VuMax - VuMin);
}
private:
DECLARE_ABSTRACT_OBJECT
};
#define lDebug() qDebug().noquote()

View file

@ -0,0 +1,290 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "core/App.hpp"
#include "core/path/Paths.hpp"
#include "tool/Utils.hpp"
#include <QDebug>
#include <QTest>
#include "FileDownloader.hpp"
// =============================================================================
static QString getDownloadFilePath(const QString &folder, const QUrl &url, const bool &overwrite) {
QString defaultFileName = QString(Constants::DownloadDefaultFileName);
QFileInfo fileInfo(url.path());
QString fileName = fileInfo.fileName();
if (fileName.isEmpty()) fileName = defaultFileName;
fileName.prepend(folder);
if (overwrite && QFile::exists(fileName)) QFile::remove(fileName);
if (!QFile::exists(fileName)) return fileName;
// Already exists, don't overwrite.
QString baseName = fileInfo.completeBaseName();
if (baseName.isEmpty()) baseName = defaultFileName;
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);
QNetworkReply *data = mNetworkReply.data();
QObject::connect(data, &QNetworkReply::readyRead, this, &FileDownloader::handleReadyData);
QObject::connect(data, &QNetworkReply::finished, this, &FileDownloader::handleDownloadFinished);
QObject::connect(data, &QNetworkReply::errorOccurred, this, &FileDownloader::handleError);
QObject::connect(data, &QNetworkReply::downloadProgress, this, &FileDownloader::handleDownloadProgress);
#if QT_CONFIG(ssl)
QObject::connect(data, &QNetworkReply::sslErrors, this, &FileDownloader::handleSslErrors);
#endif
if (mDownloadFolder.isEmpty()) {
mDownloadFolder = App::getInstance()->getSettings()->getDownloadFolder();
emit downloadFolderChanged(mDownloadFolder);
}
Q_ASSERT(!mDestinationFile.isOpen());
mDestinationFile.setFileName(
getDownloadFilePath(QDir::cleanPath(mDownloadFolder) + QDir::separator(), mUrl, mOverwriteFile));
if (!mDestinationFile.open(QIODevice::WriteOnly)) emitOutputError();
else {
mTimeoutReadBytes = 0;
mTimeout.start();
}
}
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::cleanDownloadEnd() {
mTimeout.stop();
mNetworkReply->deleteLater();
setDownloading(false);
}
void FileDownloader::handleReadyData() {
QByteArray data = mNetworkReply->readAll();
if (mDestinationFile.write(data) == -1) emitOutputError();
}
void FileDownloader::handleDownloadFinished() {
if (mNetworkReply->error() != QNetworkReply::NoError) return;
if (isHttpRedirect(mNetworkReply)) {
qWarning() << QStringLiteral("Request was redirected.");
mDestinationFile.remove();
cleanDownloadEnd();
emit downloadFailed();
} else {
qInfo() << QStringLiteral("Download of %1 finished to %2").arg(mUrl.toString(), mDestinationFile.fileName());
mDestinationFile.close();
cleanDownloadEnd();
QString fileChecksum = Utils::getFileChecksum(mDestinationFile.fileName());
if (mCheckSum.isEmpty() || fileChecksum == mCheckSum) emit downloadFinished(mDestinationFile.fileName());
else {
qCritical() << "File cannot be downloaded : Bad checksum " << fileChecksum;
mDestinationFile.remove();
emit downloadFailed();
}
}
}
void FileDownloader::handleError(QNetworkReply::NetworkError code) {
if (code != QNetworkReply::OperationCanceledError)
qWarning()
<< QStringLiteral("Download of %1 failed: %2").arg(mUrl.toString()).arg(mNetworkReply->errorString());
mDestinationFile.remove();
cleanDownloadEnd();
emit downloadFailed();
}
void FileDownloader::handleSslErrors(const QList<QSslError> &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::handleTimeout() {
if (mReadBytes == mTimeoutReadBytes) {
qWarning() << QStringLiteral("Download of %1 failed: timeout.").arg(mUrl.toString());
mNetworkReply->abort();
} else mTimeoutReadBytes = mReadBytes;
}
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;
if (!QSslSocket::supportsSsl() && mUrl.scheme() == "https") {
qWarning() << "Https has been requested but SSL is not supported. Fallback to http. Install manually "
"OpenSSL libraries in your PATH.";
mUrl.setScheme("http");
}
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);
}
}
QString FileDownloader::getDestinationFileName() const {
return mDestinationFile.fileName();
}
void FileDownloader::setOverwriteFile(const bool &overwrite) {
mOverwriteFile = overwrite;
}
QString
FileDownloader::synchronousDownload(const QUrl &url, const QString &destinationFolder, const bool &overwriteFile) {
QString filePath;
FileDownloader downloader;
if (url.isRelative()) qWarning() << "FileDownloader: The specified URL is not valid";
else {
bool isOver = false;
bool *pIsOver = &isOver;
downloader.setUrl(url);
downloader.setOverwriteFile(overwriteFile);
downloader.setDownloadFolder(destinationFolder);
connect(&downloader, &FileDownloader::downloadFinished, [pIsOver]() mutable { *pIsOver = true; });
connect(&downloader, &FileDownloader::downloadFailed, [pIsOver]() mutable { *pIsOver = true; });
downloader.download();
if (QTest::qWaitFor([&]() { return isOver; }, DefaultTimeout)) {
filePath = downloader.getDestinationFileName();
if (!QFile::exists(filePath)) {
filePath = "";
qWarning() << "FileDownloader: Cannot download the specified file";
}
}
}
return filePath;
}
QString FileDownloader::getChecksum() const {
return mCheckSum;
}
void FileDownloader::setChecksum(const QString &code) {
if (mCheckSum != code) {
mCheckSum = code;
emit checksumChanged();
}
}
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);
}
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILE_DOWNLOADER_H_
#define FILE_DOWNLOADER_H_
#include <QObject>
#include <QThread>
#include <QtNetwork>
// =============================================================================
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);
Q_PROPERTY(QString checksum READ getChecksum WRITE setChecksum NOTIFY checksumChanged);
public:
FileDownloader(QObject *parent = Q_NULLPTR) : QObject(parent) {
// See: https://bugreports.qt.io/browse/QTBUG-57390
mTimeout.setInterval(DefaultTimeout);
QObject::connect(&mTimeout, &QTimer::timeout, this, &FileDownloader::handleTimeout);
}
~FileDownloader() {
if (mNetworkReply) mNetworkReply->abort();
}
Q_INVOKABLE void download();
Q_INVOKABLE bool remove();
QUrl getUrl() const;
void setUrl(const QUrl &url);
QString getDownloadFolder() const;
void setDownloadFolder(const QString &downloadFolder);
QString getDestinationFileName() const;
void setOverwriteFile(const bool &overwrite);
static QString
synchronousDownload(const QUrl &url,
const QString &destinationFolder,
const bool &overwriteFile); // Return the filpath. Empty if nof file could be downloaded
QString getChecksum() const;
void setChecksum(const QString &code);
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();
void checksumChanged();
private:
qint64 getReadBytes() const;
void setReadBytes(qint64 readBytes);
qint64 getTotalBytes() const;
void setTotalBytes(qint64 totalBytes);
bool getDownloading() const;
void setDownloading(bool downloading);
void emitOutputError();
void cleanDownloadEnd();
void handleReadyData();
void handleDownloadFinished();
void handleError(QNetworkReply::NetworkError code);
void handleSslErrors(const QList<QSslError> &errors);
void handleTimeout();
void handleDownloadProgress(qint64 readBytes, qint64 totalBytes);
QUrl mUrl;
QString mDownloadFolder;
QFile mDestinationFile;
QString mCheckSum;
qint64 mReadBytes = 0;
qint64 mTotalBytes = 0;
bool mDownloading = false;
bool mOverwriteFile = false;
QPointer<QNetworkReply> mNetworkReply;
QNetworkAccessManager mManager;
qint64 mTimeoutReadBytes;
QTimer mTimeout;
static constexpr int DefaultTimeout = 5000;
};
#endif // FILE_DOWNLOADER_H_

View file

@ -0,0 +1,237 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <QDebug>
#include <QDir>
#include <QProcess>
#include <QTimer>
#include "FileDownloader.hpp"
#include "FileExtractor.hpp"
#include "core/path/Paths.hpp"
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
// =============================================================================
using namespace std;
FileExtractor::FileExtractor(QObject *parent) : QObject(parent) {
}
FileExtractor::~FileExtractor() {
}
void FileExtractor::extract() {
if (mExtracting) {
qWarning() << "Unable to extract file. Already extracting!";
return;
}
setExtracting(true);
QFileInfo fileInfo(mFile);
if (!fileInfo.isReadable()) {
emitExtractFailed(-1);
return;
}
mDestinationFile = QDir::cleanPath(mExtractFolder) + QDir::separator() +
(mExtractName.isEmpty() ? fileInfo.completeBaseName() : mExtractName);
if (QFile::exists(mDestinationFile) && !QFile::remove(mDestinationFile)) {
emitOutputError();
return;
}
if (mTimer == nullptr) {
mTimer = new QTimer(this);
QObject::connect(mTimer, &QTimer::timeout, this, &FileExtractor::handleExtraction);
}
#ifdef WIN32
// Test the presence of bzip2 in the system
QProcess process;
process.closeReadChannel(QProcess::StandardOutput);
process.closeReadChannel(QProcess::StandardError);
process.start("bzip2.exe", QStringList("--help"));
// int result = QProcess::execute("bzip2.exe", QStringList("--help"));
if (process.error() != QProcess::FailedToStart ||
QProcess::execute(Paths::getToolsDirPath() + "\\bzip2.exe", QStringList()) != -2) {
mTimer->start();
} else { // Download bzip2
qWarning() << "bzip2 was not found. Downloading it.";
QTimer *timer = mTimer;
FileDownloader *fileDownloader = new FileDownloader();
int downloadStep = 0;
fileDownloader->setUrl(QUrl(Constants::LinphoneBZip2_exe));
fileDownloader->setDownloadFolder(Paths::getToolsDirPath());
QObject::connect(fileDownloader, &FileDownloader::totalBytesChanged, this, &FileExtractor::setTotalBytes);
QObject::connect(fileDownloader, &FileDownloader::readBytesChanged, this, &FileExtractor::setReadBytes);
QObject::connect(fileDownloader, &FileDownloader::downloadFinished,
[fileDownloader, timer, downloadStep, this]() mutable {
if (downloadStep++ == 0) {
fileDownloader->setUrl(QUrl(Constants::LinphoneBZip2_dll));
fileDownloader->download();
} else {
fileDownloader->deleteLater();
QObject::disconnect(fileDownloader, &FileDownloader::totalBytesChanged, this,
&FileExtractor::setTotalBytes);
QObject::disconnect(fileDownloader, &FileDownloader::readBytesChanged, this,
&FileExtractor::setReadBytes);
timer->start();
}
});
QObject::connect(fileDownloader, &FileDownloader::downloadFailed, [fileDownloader, this]() {
fileDownloader->deleteLater();
emitExtractorFailed();
});
fileDownloader->download();
}
#else
mTimer->start();
#endif
}
bool FileExtractor::remove() {
return QFile::exists(mDestinationFile) && QFile::remove(mDestinationFile);
}
QString FileExtractor::getFile() const {
return mFile;
}
void FileExtractor::setFile(const QString &file) {
if (mExtracting) {
qWarning() << QStringLiteral("Unable to set file, a file is extracting.");
return;
}
if (mFile != file) {
mFile = file;
emit fileChanged(mFile);
}
}
QString FileExtractor::getExtractFolder() const {
return mExtractFolder;
}
void FileExtractor::setExtractFolder(const QString &extractFolder) {
if (mExtracting) {
qWarning() << QStringLiteral("Unable to set extract folder, a file is extracting.");
return;
}
if (mExtractFolder != extractFolder) {
mExtractFolder = extractFolder;
emit extractFolderChanged(mExtractFolder);
}
}
QString FileExtractor::getExtractName() const {
return mExtractName;
}
void FileExtractor::setExtractName(const QString &extractName) {
if (mExtracting) {
qWarning() << QStringLiteral("Unable to set extract name, a file is extracting.");
return;
}
if (mExtractName != extractName) {
mExtractName = extractName;
emit extractNameChanged(mExtractName);
}
}
bool FileExtractor::getExtracting() const {
return mExtracting;
}
void FileExtractor::setExtracting(bool extracting) {
if (mExtracting != extracting) {
mExtracting = extracting;
emit extractingChanged(extracting);
}
}
qint64 FileExtractor::getReadBytes() const {
return mReadBytes;
}
void FileExtractor::setReadBytes(qint64 readBytes) {
mReadBytes = readBytes;
emit readBytesChanged(readBytes);
}
qint64 FileExtractor::getTotalBytes() const {
return mTotalBytes;
}
void FileExtractor::setTotalBytes(qint64 totalBytes) {
mTotalBytes = totalBytes;
emit totalBytesChanged(totalBytes);
}
void FileExtractor::clean() {
if (mTimer) {
mTimer->stop();
mTimer->deleteLater();
mTimer = nullptr;
}
setExtracting(false);
}
void FileExtractor::emitExtractorFailed() {
qWarning() << QStringLiteral("Unable to extract file `%1`. bzip2 is unavailable, please install it.").arg(mFile);
clean();
emit extractFailed();
}
void FileExtractor::emitExtractFailed(int error) {
qWarning() << QStringLiteral("Unable to extract file with bzip2: `%1` (code: %2).").arg(mFile).arg(error);
clean();
emit extractFailed();
}
void FileExtractor::emitExtractFinished() {
clean();
emit extractFinished();
}
void FileExtractor::emitOutputError() {
qWarning() << QStringLiteral("Could not write into `%1`.").arg(mDestinationFile);
clean();
emit extractFailed();
}
void FileExtractor::handleExtraction() {
QString tempDestination = mDestinationFile + "." + QFileInfo(mFile).suffix();
QStringList args;
args.push_back("-dq");
args.push_back(tempDestination);
QFile::copy(mFile, tempDestination);
#ifdef WIN32
int result = QProcess::execute("bzip2.exe", args);
if (result == -2) result = QProcess::execute(Paths::getToolsDirPath() + "\\bzip2.exe", args);
#else
int result = QProcess::execute("bzip2", args);
#endif
if (QFile::exists(tempDestination)) QFile::remove(tempDestination);
if (result == 0) {
setReadBytes(getTotalBytes());
emitExtractFinished();
} else if (result > 0) emitExtractFailed(result);
else if (result == -2) emitExtractorFailed();
else emitOutputError();
}

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-desktop
* (see https://www.linphone.org).
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILE_EXTRACTOR_H_
#define FILE_EXTRACTOR_H_
#include <QFile>
// =============================================================================
class QTimer;
// Supports only bzip file.
class FileExtractor : public QObject {
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(QString extractName READ getExtractName WRITE setExtractName NOTIFY extractNameChanged);
Q_PROPERTY(bool extracting READ getExtracting NOTIFY extractingChanged);
Q_PROPERTY(qint64 readBytes READ getReadBytes NOTIFY readBytesChanged);
Q_PROPERTY(qint64 totalBytes READ getTotalBytes NOTIFY totalBytesChanged);
public:
FileExtractor(QObject *parent = nullptr);
~FileExtractor();
Q_INVOKABLE void extract();
Q_INVOKABLE bool remove();
QString getFile() const;
void setFile(const QString &file);
QString getExtractFolder() const;
void setExtractFolder(const QString &extractFolder);
QString getExtractName() const;
void setExtractName(const QString &extractName);
signals:
void fileChanged(const QString &file);
void extractFolderChanged(const QString &extractFolder);
void extractNameChanged(const QString &extractName);
void readBytesChanged(qint64 readBytes);
void totalBytesChanged(qint64 totalBytes);
void extractingChanged(bool extracting);
void extractFinished();
void extractFailed();
private:
qint64 getReadBytes() const;
void setReadBytes(qint64 readBytes);
qint64 getTotalBytes() const;
void setTotalBytes(qint64 totalBytes);
bool getExtracting() const;
void setExtracting(bool extracting);
void clean();
void emitExtractFinished();
void emitExtractorFailed(); // Used when bzip2 cannot be used
void emitExtractFailed(int error);
void emitOutputError();
void handleExtraction();
QString mFile;
QString mExtractFolder;
QString mExtractName;
QString mDestinationFile;
bool mExtracting = false;
qint64 mReadBytes = 0;
qint64 mTotalBytes = 0;
QTimer *mTimer = nullptr;
};
#endif // FILE_EXTRACTOR_H_

View file

@ -12,6 +12,12 @@ RowLayout {
property bool enabled: true
spacing : 20 * DefaultStyle.dp
Layout.minimumHeight: 32 * DefaultStyle.dp
signal checkedChanged(bool checked)
function setChecked(value) {
switchButton.checked = value
}
ColumnLayout {
Text {
text: titleText
@ -34,9 +40,8 @@ RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
checked: propertyOwner[mainItem.propertyName]
enabled: mainItem.enabled
onToggled: {
binding.when = true
}
onCheckedChanged: mainItem.checkedChanged(checked)
onToggled: binding.when = true
}
Binding {
id: binding

View file

@ -684,34 +684,33 @@ function computeAvatarSize (container, maxSize, ratio) {
// -----------------------------------------------------------------------------
function openCodecOnlineInstallerDialog (window, codecInfo, cb) {
var VideoCodecsModel = Linphone.VideoCodecsModel
window.attachVirtualWindow(buildCommonDialogUri('ConfirmDialog'), {
descriptionText: qsTr('downloadCodecDescription')
.replace('%1', codecInfo.mime)
.replace('%2', codecInfo.encoderDescription)
}, function (status) {
if (status) {
window.attachVirtualWindow(buildLinphoneDialogUri('OnlineInstallerDialog'), {
downloadUrl: codecInfo.downloadUrl,
extract: true,
installFolder: VideoCodecsModel.codecsFolder,
installName: codecInfo.installName,
mime: codecInfo.mime,
checksum: codecInfo.checksum
}, function (status) {
if (status) {
VideoCodecsModel.reload()
}
if (cb) {
cb(window)
}
})
}
else if (cb) {
cb(window)
}
})
function openCodecOnlineInstallerDialog (mainWindow, coreObject, cancelCallBack, successCallBack) {
mainWindow.showConfirmationLambdaPopup("",
qsTr("Installation de codec"),
qsTr("Télécharger le codec ") + capitalizeFirstLetter(coreObject.mimeType) + " ("+coreObject.encoderDescription+")"+" ?",
function (confirmed) {
if (confirmed) {
coreObject.success.connect(function() {
if (successCallBack)
successCallBack()
mainWindow.closeLoadingPopup()
mainWindow.showInformationPopup(qsTr("Succès"), qsTr("Le codec a été téléchargé avec succès."), true)
})
coreObject.extractError.connect(function() {
mainWindow.closeLoadingPopup()
mainWindow.showInformationPopup(qsTr("Erreur"), qsTr("Le codec n'a pas pu être sauvegardé."), true)
})
coreObject.downloadError.connect(function() {
mainWindow.closeLoadingPopup()
mainWindow.showInformationPopup(qsTr("Erreur"), qsTr("Le codec n'a pas pu être téléchargé."), true)
})
mainWindow.showLoadingPopup(qsTr("Téléchargement en cours ..."))
coreObject.downloadAndExtract()
} else
if (cancelCallBack)
cancelCallBack()
}
)
}
function printObject(o) {

View file

@ -150,16 +150,43 @@ AbstractSettingsLayout {
Layout.leftMargin: 64 * DefaultStyle.dp
Repeater {
model: PayloadTypeProxy {
id: videoPayloadTypeProxy
family: PayloadTypeCore.Video
}
SwitchSetting {
Layout.fillWidth: true
titleText: Utils.capitalizeFirstLetter(modelData.core.mimeType)
subTitleText: modelData.core.recvFmtp
subTitleText: modelData.core.encoderDescription
propertyName: "enabled"
propertyOwner: modelData.core
}
}
Repeater {
model: PayloadTypeProxy {
id: downloadableVideoPayloadTypeProxy
family: PayloadTypeCore.Video
downloadable: true
}
SwitchSetting {
Layout.fillWidth: true
titleText: Utils.capitalizeFirstLetter(modelData.core.mimeType)
subTitleText: modelData.core.encoderDescription
onCheckChanged: function(checked) {
if (checked)
UtilsCpp.getMainWindow().showConfirmationLambdaPopup("",
qsTr("Installation"),
qsTr("Télécharger le codec ") + Utils.capitalizeFirstLetter(modelData.core.mimeType) + " ("+modelData.core.encoderDescription+")"+" ?",
function (confirmed) {
if (confirmed) {
UtilsCpp.getMainWindow().showLoadingPopup(qsTr("Téléchargement en cours ..."))
modelData.core.downloadAndExtract()
} else
setChecked(false)
}
)
}
}
}
}
}
Rectangle {

View file

@ -5,6 +5,7 @@ import QtQuick.Controls.Basic
import Linphone
import UtilsCpp
import SettingsCpp
import 'qrc:/qt/qml/Linphone/view/Control/Tool/Helper/utils.js' as Utils
AbstractWindow {
id: mainWindow
@ -140,6 +141,7 @@ AbstractWindow {
onGoToRegister: mainWindowStackView.replace(registerPage)
onConnectionSucceed: {
openMainPage()
proposeH264CodecsDownload()
}
}
}
@ -156,6 +158,7 @@ AbstractWindow {
onConnectionSucceed: {
openMainPage()
proposeH264CodecsDownload()
}
}
}
@ -225,4 +228,25 @@ AbstractWindow {
// StackView.onActivated: connectionSecured(0) // TODO : connect to cpp part when ready
}
}
// H264 Cisco codec download
PayloadTypeProxy {
id: downloadableVideoPayloadTypeProxy
family: PayloadTypeCore.Video
downloadable: true
}
Repeater {
id: codecDownloader
model: null
Item {
Component.onCompleted: {
if (modelData.core.mimeType == "H264")
Utils.openCodecOnlineInstallerDialog(mainWindow, modelData.core)
}
}
}
function proposeH264CodecsDownload() {
codecDownloader.model = downloadableVideoPayloadTypeProxy
}
}