/* * Copyright (c) 2021 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 "ContentModel.hpp" #include #include #include #include #include "app/App.hpp" #include "app/paths/Paths.hpp" #include "app/providers/ThumbnailProvider.hpp" #include "components/chat-events/ChatMessageModel.hpp" #include "utils/QExifImageHeader.hpp" #include "utils/Utils.hpp" #include "utils/Constants.hpp" #include "components/Components.hpp" // ============================================================================= ContentModel::ContentModel(ChatMessageModel* chatModel) : mAppData(""){ App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE mChatMessageModel = chatModel; mWasDownloaded = false; mFileOffset = 0; } ContentModel::ContentModel(std::shared_ptr content, ChatMessageModel* chatModel) : mAppData(""){ App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE mChatMessageModel = chatModel; mWasDownloaded = false; mFileOffset = 0; setContent(content); } std::shared_ptr ContentModel::getContent()const{ return mContent; } ChatMessageModel * ContentModel::getChatMessageModel()const{ return mChatMessageModel; } quint64 ContentModel::getFileSize() const{ auto s = mContent->getFileSize(); return (quint64)s; } QString ContentModel::getName() const{ return QString::fromStdString(mContent->getName()); } QString ContentModel::getThumbnail() const{ return mThumbnail; } QString ContentModel::getFilePath() const{ return Utils::coreStringToAppString(mContent->getFilePath()); } QString ContentModel::getUtf8Text() const{ return QString::fromStdString(mContent->getUtf8Text()); } void ContentModel::setFileOffset(quint64 fileOffset){ if( mFileOffset != fileOffset) { mFileOffset = fileOffset; emit fileOffsetChanged(); } } void ContentModel::setThumbnail(const QString& data){ if( mThumbnail != data) { mThumbnail = data; emit thumbnailChanged(); } } void ContentModel::setWasDownloaded(bool wasDownloaded){ if( mWasDownloaded != wasDownloaded) { mWasDownloaded = wasDownloaded; emit wasDownloadedChanged(); } } void ContentModel::setContent(std::shared_ptr content){ mContent = content; if(isFile() || isFileEncrypted() || isFileTransfer() ){ QString path = Utils::coreStringToAppString(mContent->getFilePath()); if (!path.isEmpty()) createThumbnail(); } } bool ContentModel::isFile() const{ return mContent->isFile(); } bool ContentModel::isFileEncrypted() const{ return mContent->isFileEncrypted(); } bool ContentModel::isFileTransfer() const{ return mContent->isFileTransfer(); } bool ContentModel::isIcalendar() const{ return mContent->isIcalendar(); } bool ContentModel::isMultipart() const{ return mContent->isMultipart(); } bool ContentModel::isText() const{ return mContent->isText(); } bool ContentModel::isVoiceRecording()const{ return mContent->isVoiceRecording(); } // Create a thumbnail from the first content that have a file and store it in Appdata void ContentModel::createThumbnail (const bool& force) { if(force || isFile() || isFileEncrypted() || isFileTransfer()){ QString id; QString path = getFilePath(); auto appdata = ChatMessageModel::AppDataManager(mChatMessageModel ? QString::fromStdString(mChatMessageModel->getChatMessage()->getAppdata()) : ""); if(!appdata.mData.contains(path) || !QFileInfo(QString::fromStdString(Paths::getThumbnailsDirPath())+appdata.mData[path]).isFile()){ // File don't exist. Create the thumbnail QImage image(path); if( image.isNull()){// Try to determine format from headers QImageReader reader(path); reader.setDecideFormatFromContent(true); QByteArray format = reader.format(); if(!format.isEmpty()) image = QImage(path, format); } if (!image.isNull()){ int rotation = 0; QExifImageHeader exifImageHeader; if (exifImageHeader.loadFromJpeg(path)) rotation = int(exifImageHeader.value(QExifImageHeader::ImageTag::Orientation).toShort()); QImage thumbnail = image.scaled( Constants::ThumbnailImageFileWidth, Constants::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(); id = QStringLiteral("%1.jpg").arg(uuid.mid(1, uuid.length() - 2)); if (!thumbnail.save(QString::fromStdString(Paths::getThumbnailsDirPath()) + id , "jpg", 100)) { qWarning() << QStringLiteral("Unable to create thumbnail of: `%1`.").arg(path); }else{ appdata.mData[path] = id; mAppData.mData[path] = id; if(mChatMessageModel) mChatMessageModel->getChatMessage()->setAppdata(appdata.toString().toStdString()); } } } if( path != ""){ setWasDownloaded( !path.isEmpty() && QFileInfo(path).isFile()); if(appdata.mData.contains(path) && !appdata.mData[path].isEmpty()) setThumbnail(QStringLiteral("image://%1/%2").arg(ThumbnailProvider::ProviderId).arg(appdata.mData[path])); } } } void ContentModel::removeThumbnail(){ for(QMap::iterator itData = mAppData.mData.begin() ; itData != mAppData.mData.end() ; ++itData){ QString thumbnailPath = QString::fromStdString(Paths::getThumbnailsDirPath()) +itData.value(); if( QFileInfo(thumbnailPath).isFile()){ QFile(thumbnailPath).remove(); } } mAppData.mData.clear(); } void ContentModel::downloadFile(){ switch (mChatMessageModel->getState()) { case LinphoneEnums::ChatMessageStateDelivered: case LinphoneEnums::ChatMessageStateDeliveredToUser: case LinphoneEnums::ChatMessageStateDisplayed: case LinphoneEnums::ChatMessageStateFileTransferDone: break; default: qWarning() << QStringLiteral("Wrong message state when requesting downloading, state=%1.").arg(mChatMessageModel->getState()); } bool soFarSoGood; QString filename = getName();//mFileTransfertContent->getName(); const QString safeFilePath = Utils::getSafeFilePath( QStringLiteral("%1%2") .arg(CoreManager::getInstance()->getSettingsModel()->getDownloadFolder()) .arg(filename), &soFarSoGood ); if (!soFarSoGood) { qWarning() << QStringLiteral("Unable to create safe file path for: %1.").arg(filename); return; } mContent->setFilePath(Utils::appStringToCoreString(safeFilePath)); if( !mContent->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 (!mChatMessageModel->getChatMessage()->downloadContent(mContent)) qWarning() << QStringLiteral("Unable to download file of entry %1.").arg(filename); } } void ContentModel::openFile (bool showDirectory) { if (mChatMessageModel && ((!mWasDownloaded && !mChatMessageModel->isOutgoing()) || mContent->getFilePath() == "")) { downloadFile(); }else{ QFileInfo info( Utils::coreStringToAppString(mContent->getFilePath())); showDirectory = showDirectory || !info.exists(); QDesktopServices::openUrl( QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath())) ); } } void ContentModel::updateTransferData(){ }