From d6f5a7f0cea876a411b2de2373834c46bf0de0c6 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Tue, 28 Apr 2020 14:29:50 +0200 Subject: [PATCH] Fix file transfert and notification behaviour - Add External Image Provider to avoid having error message when we try to show a file that is not an image (used to show an attachment where it can be a regular file). - When creating an image, try to read the headers of a file when the extension is not consistent with contents. - Build if not exists, a thumbnail image if the file has been displayed when filling a message - When trying to open the file, test if it was downloaded by reading the flag - Add a status for File Transfert In Progress - Use non-deprecated function to get file path from ChatMessage - Use FileSize in addtion of content Size to get the full size of a message - Show a progressbar when downloading a file - Show the image in notification when downloaded or the file name if it is not an image --- linphone-app/CMakeLists.txt | 2 + linphone-app/src/app/App.cpp | 2 + .../app/providers/ExternalImageProvider.cpp | 49 ++++++++ .../app/providers/ExternalImageProvider.hpp | 37 ++++++ .../src/components/chat/ChatModel.cpp | 119 +++++++++--------- .../src/components/chat/ChatModel.hpp | 2 + .../src/components/notifier/Notifier.cpp | 28 ++--- .../ui/modules/Linphone/Chat/FileMessage.qml | 4 +- .../modules/Linphone/Chat/OutgoingMessage.qml | 2 +- .../NotificationReceivedFileMessage.qml | 9 ++ 10 files changed, 177 insertions(+), 77 deletions(-) create mode 100644 linphone-app/src/app/providers/ExternalImageProvider.cpp create mode 100644 linphone-app/src/app/providers/ExternalImageProvider.hpp diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt index 2519671f3..415dff304 100644 --- a/linphone-app/CMakeLists.txt +++ b/linphone-app/CMakeLists.txt @@ -99,6 +99,7 @@ set(SOURCES src/app/paths/Paths.cpp src/app/providers/AvatarProvider.cpp src/app/providers/ImageProvider.cpp + src/app/providers/ExternalImageProvider.cpp src/app/providers/ThumbnailProvider.cpp src/app/translator/DefaultTranslator.cpp src/components/assistant/AssistantModel.cpp @@ -156,6 +157,7 @@ set(HEADERS src/app/paths/Paths.hpp src/app/providers/AvatarProvider.hpp src/app/providers/ImageProvider.hpp + src/app/providers/ExternalImageProvider.hpp src/app/providers/ThumbnailProvider.hpp src/app/single-application/SingleApplication.hpp src/app/translator/DefaultTranslator.hpp diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp index 09d0457dc..823328631 100644 --- a/linphone-app/src/app/App.cpp +++ b/linphone-app/src/app/App.cpp @@ -41,6 +41,7 @@ #include "paths/Paths.hpp" #include "providers/AvatarProvider.hpp" #include "providers/ImageProvider.hpp" +#include "providers/ExternalImageProvider.hpp" #include "providers/ThumbnailProvider.hpp" #include "translator/DefaultTranslator.hpp" #include "utils/LinphoneUtils.hpp" @@ -316,6 +317,7 @@ void App::initContentApp () { // Provide avatars/thumbnails providers. mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); + mEngine->addImageProvider(ExternalImageProvider::ProviderId, new ExternalImageProvider()); mEngine->addImageProvider(ThumbnailProvider::ProviderId, new ThumbnailProvider()); mEngine->rootContext()->setContextProperty("applicationUrl", APPLICATION_URL); diff --git a/linphone-app/src/app/providers/ExternalImageProvider.cpp b/linphone-app/src/app/providers/ExternalImageProvider.cpp new file mode 100644 index 000000000..76961e61c --- /dev/null +++ b/linphone-app/src/app/providers/ExternalImageProvider.cpp @@ -0,0 +1,49 @@ +/* + * 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 . + */ + +#include "app/paths/Paths.hpp" +#include "utils/Utils.hpp" + +#include "ExternalImageProvider.hpp" + +#include + +// ============================================================================= + +const QString ExternalImageProvider::ProviderId = "external"; + +ExternalImageProvider::ExternalImageProvider () : QQuickImageProvider( + QQmlImageProviderBase::Image, + QQmlImageProviderBase::ForceAsynchronousImageLoading +) { +} + +QImage ExternalImageProvider::requestImage (const QString &id, QSize *size, const QSize &) { + QImage image(id); + if(image.isNull()){// Try to determine format from headers instead of using suffix + QImageReader reader(id); + reader.setDecideFormatFromContent(true); + QByteArray format = reader.format(); + if(!format.isEmpty()) + image = QImage(id, format); + } + *size = image.size(); + return image; +} diff --git a/linphone-app/src/app/providers/ExternalImageProvider.hpp b/linphone-app/src/app/providers/ExternalImageProvider.hpp new file mode 100644 index 000000000..82b3964b2 --- /dev/null +++ b/linphone-app/src/app/providers/ExternalImageProvider.hpp @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +#ifndef EXTERNAL_IMAGE_PROVIDER_H_ +#define EXTERNAL_IMAGE_PROVIDER_H_ + +#include + +// ============================================================================= + +class ExternalImageProvider : public QQuickImageProvider { +public: + ExternalImageProvider (); + + QImage requestImage (const QString &id, QSize *size, const QSize &requestedSize) override; + + static const QString ProviderId; +}; + +#endif // EXTERNAL_IMAGE_PROVIDER_H_ diff --git a/linphone-app/src/components/chat/ChatModel.cpp b/linphone-app/src/components/chat/ChatModel.cpp index 811fce4ee..8c1c9ad73 100644 --- a/linphone-app/src/components/chat/ChatModel.cpp +++ b/linphone-app/src/components/chat/ChatModel.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "app/App.hpp" #include "app/paths/Paths.hpp" @@ -129,45 +131,46 @@ static inline void createThumbnail (const shared_ptr &mes std::list > contents = message->getContents(); if( contents.size() > 0) { - MessageAppData thumbnailData; + MessageAppData thumbnailData; thumbnailData.m_path = Utils::coreStringToAppString(contents.front()->getFilePath()); - QImage image(thumbnailData.m_path); - if (image.isNull()) - return; - - int rotation = 0; - QExifImageHeader exifImageHeader; - if (exifImageHeader.loadFromJpeg(thumbnailData.m_path)) - rotation = int(exifImageHeader.value(QExifImageHeader::ImageTag::Orientation).toShort()); - - QImage thumbnail = image.scaled( - ThumbnailImageFileWidth, ThumbnailImageFileHeight, - Qt::KeepAspectRatio, Qt::SmoothTransformation - ); - - if (rotation != 0) { - QTransform transform; - if (rotation == 3 || rotation == 4) - transform.rotate(180); - else if (rotation == 5 || rotation == 6) - transform.rotate(90); - else if (rotation == 7 || rotation == 8) - transform.rotate(-90); - - thumbnail = thumbnail.transformed(transform); - if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7) - thumbnail = thumbnail.mirrored(true, false); - } - - QString uuid = QUuid::createUuid().toString(); - thumbnailData.m_id = QStringLiteral("%1.jpg").arg(uuid.mid(1, uuid.length() - 2)); - - if (!thumbnail.save(Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + thumbnailData.m_id , "jpg", 100)) { - qWarning() << QStringLiteral("Unable to create thumbnail of: `%1`.").arg(thumbnailData.m_path); - return; - } - - message->setAppdata(Utils::appStringToCoreString(thumbnailData.toString())); + QImage image(thumbnailData.m_path); + if( image.isNull()){// Try to determine format from headers + QImageReader reader(thumbnailData.m_path); + reader.setDecideFormatFromContent(true); + QByteArray format = reader.format(); + if(!format.isEmpty()) + image = QImage(thumbnailData.m_path, format); + } + if (!image.isNull()){ + int rotation = 0; + QExifImageHeader exifImageHeader; + if (exifImageHeader.loadFromJpeg(thumbnailData.m_path)) + rotation = int(exifImageHeader.value(QExifImageHeader::ImageTag::Orientation).toShort()); + QImage thumbnail = image.scaled( + ThumbnailImageFileWidth, ThumbnailImageFileHeight, + Qt::KeepAspectRatio, Qt::SmoothTransformation + ); + + if (rotation != 0) { + QTransform transform; + if (rotation == 3 || rotation == 4) + transform.rotate(180); + else if (rotation == 5 || rotation == 6) + transform.rotate(90); + else if (rotation == 7 || rotation == 8) + transform.rotate(-90); + thumbnail = thumbnail.transformed(transform); + if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7) + thumbnail = thumbnail.mirrored(true, false); + } + QString uuid = QUuid::createUuid().toString(); + thumbnailData.m_id = QStringLiteral("%1.jpg").arg(uuid.mid(1, uuid.length() - 2)); + + if (!thumbnail.save(Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + thumbnailData.m_id , "jpg", 100)) { + qWarning() << QStringLiteral("Unable to create thumbnail of: `%1`.").arg(thumbnailData.m_path); + } + } + message->setAppdata(Utils::appStringToCoreString(thumbnailData.toString())); } } @@ -177,7 +180,7 @@ static inline void removeFileMessageThumbnail (const shared_ptr 0) { - QString thumbnailPath = Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + thumbnailFile.m_id; + QString thumbnailPath = Utils::coreStringToAppString(Paths::getThumbnailsDirPath()) + thumbnailFile.m_id; if (!QFile::remove(thumbnailPath)) qWarning() << QStringLiteral("Unable to remove `%1`.").arg(thumbnailPath); } @@ -202,10 +205,11 @@ static inline void fillMessageEntry (QVariantMap &dest, const shared_ptr content = message->getFileTransferInformation(); if (content) { dest["fileSize"] = quint64(content->getFileSize()); - dest["fileName"] = Utils::coreStringToAppString(content->getName()); - dest["wasDownloaded"] = ::fileWasDownloaded(message); - - fillThumbnailProperty(dest, message); + dest["fileName"] =Utils::coreStringToAppString(content->getName()); + if (state==linphone::ChatMessage::State::Displayed) + createThumbnail(message); + fillThumbnailProperty(dest, message); + dest["wasDownloaded"] = ::fileWasDownloaded(message); } } @@ -285,9 +289,7 @@ private: if (state == linphone::ChatMessage::State::FileTransferDone && !message->isOutgoing()) { createThumbnail(message); fillThumbnailProperty((*it).first, message); - (*it).first["wasDownloaded"] = true; - App::getInstance()->getNotifier()->notifyReceivedFileMessage(message); } @@ -518,12 +520,10 @@ void ChatModel::sendFileMessage (const QString &path) { content->setType(Utils::appStringToCoreString(mimeType[0])); content->setSubtype(Utils::appStringToCoreString(mimeType[1])); } - - content->setSize(size_t(fileSize)); - content->setName(Utils::appStringToCoreString(QFileInfo(file).fileName())); - + content->setSize(size_t(fileSize)); + content->setName(Utils::appStringToCoreString( QFileInfo(file).fileName())); shared_ptr message = mChatRoom->createFileTransferMessage(content); - message->getContents().front()->setFilePath(Utils::appStringToCoreString(path));// Sending only one File Path? + message->getContents().front()->setFilePath(Utils::appStringToCoreString(path)); message->addListener(mMessageHandlers); createThumbnail(message); @@ -570,9 +570,9 @@ void ChatModel::downloadFile (int id) { message->getContents().front()->setFilePath(Utils::appStringToCoreString(safeFilePath)); - if( !message->isFileTransfer()) - QMessageBox::warning(nullptr, "Download File", "This file was already downloaded and is no more on the server. Your peer have to resend it if you want to get it"); - else + if( !message->isFileTransfer()){ + QMessageBox::warning(nullptr, "Download File", "This file was already downloaded and is no more on the server. Your peer have to resend it if you want to get it"); + }else { if (!message->downloadContent(message->getFileTransferInformation())) qWarning() << QStringLiteral("Unable to download file of entry %1.").arg(id); @@ -585,15 +585,14 @@ void ChatModel::openFile (int id, bool showDirectory) { return; shared_ptr message = static_pointer_cast(entry.second); - if (!::fileWasDownloaded(message)) { + if (!entry.first["wasDownloaded"].toBool()) { downloadFile(id); - return; + }else{ + QFileInfo info(getMessageAppData(message).m_path); + QDesktopServices::openUrl( + QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath())) + ); } - - QFileInfo info(getMessageAppData(message).m_path); - QDesktopServices::openUrl( - QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath())) - ); } bool ChatModel::fileWasDownloaded (int id) { diff --git a/linphone-app/src/components/chat/ChatModel.hpp b/linphone-app/src/components/chat/ChatModel.hpp index 60460f33e..2bee38c9a 100644 --- a/linphone-app/src/components/chat/ChatModel.hpp +++ b/linphone-app/src/components/chat/ChatModel.hpp @@ -61,9 +61,11 @@ public: MessageStatusDisplayed = int(linphone::ChatMessage::State::Displayed), MessageStatusFileTransferDone = int(linphone::ChatMessage::State::FileTransferDone), MessageStatusFileTransferError = int(linphone::ChatMessage::State::FileTransferError), + MessageStatusFileTransferInProgress = int(linphone::ChatMessage::State::FileTransferInProgress), MessageStatusIdle = int(linphone::ChatMessage::State::Idle), MessageStatusInProgress = int(linphone::ChatMessage::State::InProgress), MessageStatusNotDelivered = int(linphone::ChatMessage::State::NotDelivered) + }; Q_ENUM(MessageStatus); diff --git a/linphone-app/src/components/notifier/Notifier.cpp b/linphone-app/src/components/notifier/Notifier.cpp index ab7e9e76a..baaf29c3d 100644 --- a/linphone-app/src/components/notifier/Notifier.cpp +++ b/linphone-app/src/components/notifier/Notifier.cpp @@ -228,7 +228,7 @@ void Notifier::deleteNotification (QVariant notification) { // ----------------------------------------------------------------------------- void Notifier::notifyReceivedMessage (const shared_ptr &message) { - CREATE_NOTIFICATION(Notifier::ReceivedMessage); + CREATE_NOTIFICATION(Notifier::ReceivedMessage) QVariantMap map; map["message"] = message->getFileTransferInformation() @@ -240,21 +240,21 @@ void Notifier::notifyReceivedMessage (const shared_ptr &m map["localAddress"] = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asStringUriOnly()); map["window"].setValue(App::getInstance()->getMainWindow()); - SHOW_NOTIFICATION(map); + SHOW_NOTIFICATION(map) } void Notifier::notifyReceivedFileMessage (const shared_ptr &message) { - CREATE_NOTIFICATION(Notifier::ReceivedFileMessage); + CREATE_NOTIFICATION(Notifier::ReceivedFileMessage) QVariantMap map; - map["fileUri"] = Utils::coreStringToAppString(message->getFileTransferFilepath()); - map["fileSize"] = quint64(message->getFileTransferInformation()->getSize()); + map["fileUri"] = Utils::coreStringToAppString(message->getFileTransferInformation()->getFilePath()); + map["fileSize"] = quint64(message->getFileTransferInformation()->getSize() +message->getFileTransferInformation()->getFileSize()); - SHOW_NOTIFICATION(map); + SHOW_NOTIFICATION(map) } void Notifier::notifyReceivedCall (const shared_ptr &call) { - CREATE_NOTIFICATION(Notifier::ReceivedCall); + CREATE_NOTIFICATION(Notifier::ReceivedCall) CallModel *callModel = &call->getData("call-model"); @@ -266,35 +266,35 @@ void Notifier::notifyReceivedCall (const shared_ptr &call) { QVariantMap map; map["call"].setValue(callModel); - SHOW_NOTIFICATION(map); + SHOW_NOTIFICATION(map) } void Notifier::notifyNewVersionAvailable (const QString &version, const QString &url) { - CREATE_NOTIFICATION(Notifier::NewVersionAvailable); + CREATE_NOTIFICATION(Notifier::NewVersionAvailable) QVariantMap map; map["message"] = tr("newVersionAvailable").arg(version); map["url"] = url; - SHOW_NOTIFICATION(map); + SHOW_NOTIFICATION(map) } void Notifier::notifySnapshotWasTaken (const QString &filePath) { - CREATE_NOTIFICATION(Notifier::SnapshotWasTaken); + CREATE_NOTIFICATION(Notifier::SnapshotWasTaken) QVariantMap map; map["filePath"] = filePath; - SHOW_NOTIFICATION(map); + SHOW_NOTIFICATION(map) } void Notifier::notifyRecordingCompleted (const QString &filePath) { - CREATE_NOTIFICATION(Notifier::RecordingCompleted); + CREATE_NOTIFICATION(Notifier::RecordingCompleted) QVariantMap map; map["filePath"] = filePath; - SHOW_NOTIFICATION(map); + SHOW_NOTIFICATION(map) } #undef SHOW_NOTIFICATION diff --git a/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml b/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml index 13d2f6420..57460f086 100644 --- a/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/FileMessage.qml @@ -200,7 +200,7 @@ Row { to: $chatEntry.fileSize value: $chatEntry.fileOffset || 0 - visible: $chatEntry.status === ChatModel.MessageStatusInProgress + visible: $chatEntry.status === ChatModel.MessageStatusInProgress || $chatEntry.status === ChatModel.MessageStatusFileTransferInProgress background: Rectangle { color: ChatStyle.entry.message.file.status.bar.background.color @@ -322,7 +322,7 @@ Row { sourceComponent: $chatEntry.isOutgoing ? ( - $chatEntry.status === ChatModel.MessageStatusInProgress + $chatEntry.status === ChatModel.MessageStatusInProgress || $chatEntry.status === ChatModel.MessageStatusFileTransferInProgress ? indicator : icon ) : undefined diff --git a/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml b/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml index 9318c3171..ebdf19302 100644 --- a/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/OutgoingMessage.qml @@ -80,7 +80,7 @@ Item { height: ChatStyle.entry.lineHeight width: ChatStyle.entry.message.outgoing.areaSize - sourceComponent: $chatEntry.status === ChatModel.MessageStatusInProgress + sourceComponent: $chatEntry.status === ChatModel.MessageStatusInProgress || $chatEntry.status === ChatModel.MessageStatusFileTransferInProgress ? indicator : icon } diff --git a/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedFileMessage.qml b/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedFileMessage.qml index 2ca3cfdfd..56dbe2af0 100644 --- a/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedFileMessage.qml +++ b/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedFileMessage.qml @@ -39,6 +39,15 @@ Notification { elide: Text.ElideRight font.pointSize: NotificationReceivedFileMessageStyle.fileName.pointSize text: Utils.basename(notification.fileUri) + visible:!image.visible + } + Image{ + id:image + Layout.fillHeight: true + Layout.fillWidth: true + fillMode: Image.PreserveAspectFit + source: "image://external/"+notification.fileUri + visible: image.status == Image.Ready } Text {