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
This commit is contained in:
Julien Wadel 2020-04-28 14:29:50 +02:00
parent dbf1f6801a
commit d6f5a7f0ce
10 changed files with 177 additions and 77 deletions

View file

@ -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

View file

@ -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);

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "app/paths/Paths.hpp"
#include "utils/Utils.hpp"
#include "ExternalImageProvider.hpp"
#include <QImageReader>
// =============================================================================
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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef EXTERNAL_IMAGE_PROVIDER_H_
#define EXTERNAL_IMAGE_PROVIDER_H_
#include <QQuickImageProvider>
// =============================================================================
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_

View file

@ -28,6 +28,8 @@
#include <QTimer>
#include <QUuid>
#include <QMessageBox>
#include <QUrlQuery>
#include <QImageReader>
#include "app/App.hpp"
#include "app/paths/Paths.hpp"
@ -129,45 +131,46 @@ static inline void createThumbnail (const shared_ptr<linphone::ChatMessage> &mes
std::list<std::shared_ptr<linphone::Content> > 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<linphone::ChatMe
MessageAppData thumbnailFile = getMessageAppData(message);
if(thumbnailFile.m_id.size() > 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<linphon
shared_ptr<const linphone::Content> 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<linphone::ChatMessage> 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<linphone::ChatMessage> message = static_pointer_cast<linphone::ChatMessage>(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) {

View file

@ -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);

View file

@ -228,7 +228,7 @@ void Notifier::deleteNotification (QVariant notification) {
// -----------------------------------------------------------------------------
void Notifier::notifyReceivedMessage (const shared_ptr<linphone::ChatMessage> &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<linphone::ChatMessage> &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<linphone::ChatMessage> &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<linphone::Call> &call) {
CREATE_NOTIFICATION(Notifier::ReceivedCall);
CREATE_NOTIFICATION(Notifier::ReceivedCall)
CallModel *callModel = &call->getData<CallModel>("call-model");
@ -266,35 +266,35 @@ void Notifier::notifyReceivedCall (const shared_ptr<linphone::Call> &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

View file

@ -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

View file

@ -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
}

View file

@ -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 {