-
Guillaume Roguez authored
VCardUtils::parseMimeAttributes() returns by value, but it was used in IMConversationManagerPrivate::newMessage() by reference. So this reference is on a stack-allocated object and can be invalidated by the system. Use a by-value copy, mostly optimized by compiler with copy elision [1]. [1] http://en.cppreference.com/w/cpp/language/copy_elision Change-Id: I1b7ef8eb1ebc738949243d5ace1363d4b4fa3caf Reviewed-by:
Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
Guillaume Roguez authoredVCardUtils::parseMimeAttributes() returns by value, but it was used in IMConversationManagerPrivate::newMessage() by reference. So this reference is on a stack-allocated object and can be invalidated by the system. Use a by-value copy, mostly optimized by compiler with copy elision [1]. [1] http://en.cppreference.com/w/cpp/language/copy_elision Change-Id: I1b7ef8eb1ebc738949243d5ace1363d4b4fa3caf Reviewed-by:
Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
text.cpp 10.05 KiB
/****************************************************************************
* Copyright (C) 2015-2016 by Savoir-faire Linux *
* Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2.1 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Lesser 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 "text.h"
//Dring
#include <media_const.h>
#include "dbus/callmanager.h"
#include "dbus/configurationmanager.h"
//Ring
#include <call.h>
#include <callmodel.h>
#include <account.h>
#include <person.h>
#include <contactmethod.h>
#include <mime.h>
#include <media/textrecording.h>
#include <media/recordingmodel.h>
#include <phonedirectorymodel.h>
#include <private/call_p.h>
#include <private/vcardutils.h>
#include <private/textrecording_p.h>
#include <private/imconversationmanagerprivate.h>
#include <accountmodel.h>
#include <personmodel.h>
/*
* Instant message have 3 major modes, "past", "in call" and "offline"
*
* Offline messages are currently implemented over the DHT and may eventually
* be implemented over account registration (publish--subscribe)
*
* In call messages are the fastest as they have a communication channels of their
* own.
*
* Past messages are local copy of past communication stored somewhere.
*
* All 3 sources have to be combined dynamically into a single stream per person.
*
* So chunks can come from all 3 sources for the same stream, including part that
* are shared in a conference with multiple peers.
*/
class MediaTextPrivate
{
public:
MediaTextPrivate(Media::Text* parent);
//Attributes
Media::TextRecording* m_pRecording;
bool m_HasChecked;
QHash<QString,bool> m_hMimeTypes;
QStringList m_lMimeTypes;
//Helper
void updateMimeList(const QMap<QString,QString>& payloads);
private:
Media::Text* q_ptr;
};
class ProfileChunk
{
public:
//Helper
static Person* addChunk( const QMap<QString,QString>& args, const QString& payload, ContactMethod *contactMethod);
private:
//Attributes
QHash<int, QString> m_hParts { };
int m_PartsCount { 0 };
static QHash<QString, ProfileChunk*> m_hRequest;
};
QHash<QString, ProfileChunk*> ProfileChunk::m_hRequest;
IMConversationManagerPrivate::IMConversationManagerPrivate(QObject* parent) : QObject(parent)
{
CallManagerInterface& callManager = CallManager::instance();
ConfigurationManagerInterface& configurationManager = ConfigurationManager::instance();
connect(&configurationManager, &ConfigurationManagerInterface::incomingAccountMessage, this, &IMConversationManagerPrivate::newAccountMessage);
connect(&configurationManager, &ConfigurationManagerInterface::accountMessageStatusChanged , this, &IMConversationManagerPrivate::accountMessageStatusChanged);
connect(&callManager , &CallManagerInterface::incomingMessage , this, &IMConversationManagerPrivate::newMessage );
}
IMConversationManagerPrivate& IMConversationManagerPrivate::instance()
{
static auto instance = new IMConversationManagerPrivate(nullptr);
return *instance;
}
Person* ProfileChunk::addChunk(const QMap<QString, QString>& args, const QString& payload, ContactMethod *contactMethod)
{
const int total = args[ "of" ].toInt();
const int part = args[ "part" ].toInt();
const QString id = args[ "id" ];
auto c = m_hRequest[id];
if (!c) {
c = new ProfileChunk();
c->m_PartsCount = total;
m_hRequest[id] = c;
}
if (part < 1 and part > total)
return nullptr;
c->m_hParts[part] = payload;
if (c->m_hParts.size() != c->m_PartsCount)
return nullptr;
QByteArray cv;
for (int i=0; i < c->m_PartsCount; ++i) {
cv += c->m_hParts[i+1];
}
m_hRequest[id] = nullptr;
delete c;
return VCardUtils::mapToPersonFromReceivedProfile(contactMethod, cv);
}
///Called when a new message is incoming
void IMConversationManagerPrivate::newMessage(const QString& callId, const QString& from, const QMap<QString,QString>& message)
{
Q_UNUSED(from)
auto call = CallModel::instance().getCall(callId);
if (!call and !call->peerContactMethod()) {
return;
}
static const int profileSize = QString(RingMimes::PROFILE_VCF).size();
//Intercept some messages early, those are intended for internal Ring usage
QMapIterator<QString, QString> iter(message);
while (iter.hasNext()) {
iter.next();
if (iter.key().left(profileSize) == RingMimes::PROFILE_VCF) {
auto args = VCardUtils::parseMimeAttributes(iter.key());
if (auto person = ProfileChunk::addChunk(args, iter.value(), call->peerContactMethod())) {
PersonModel::instance().addPeerProfile(person);
}
return;
}
}
Media::Text* media = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
if (!media) {
media = call->d_ptr->mediaFactory<Media::Text>(Media::Media::Direction::IN);
}
media->recording()->setCall(call);
media->recording()->d_ptr->insertNewMessage(message,call->peerContactMethod(),Media::Media::Direction::IN);
media->d_ptr->updateMimeList(message);
emit media->messageReceived(message);
}
void IMConversationManagerPrivate::newAccountMessage(const QString& accountId, const QString& from, const QMap<QString,QString>& payloads)
{
if (auto cm = PhoneDirectoryModel::instance().getNumber(from, AccountModel::instance().getById(accountId.toLatin1()))) {
auto txtRecording = cm->textRecording();
txtRecording->d_ptr->insertNewMessage(payloads, cm, Media::Media::Direction::IN);
}
}
void IMConversationManagerPrivate::accountMessageStatusChanged(const QString& accountId, uint64_t id, const QString& to, int status)
{
if (auto cm = PhoneDirectoryModel::instance().getNumber(to, AccountModel::instance().getById(accountId.toLatin1()))) {
auto txtRecording = cm->textRecording();
txtRecording->d_ptr->accountMessageStatusChanged(id, static_cast<DRing::Account::MessageStates>(status));
}
}
MediaTextPrivate::MediaTextPrivate(Media::Text* parent) : q_ptr(parent),m_pRecording(nullptr),m_HasChecked(false)
{
}
Media::Text::Text(Call* parent, const Media::Direction direction) : Media::Media(parent, direction), d_ptr(new MediaTextPrivate(this))
{
Q_ASSERT(parent);
}
Media::Media::Type Media::Text::type()
{
return Media::Media::Type::TEXT;
}
Media::Text::~Text()
{
delete d_ptr;
}
Media::TextRecording* Media::Text::recording() const
{
const bool wasChecked = d_ptr->m_HasChecked;
d_ptr->m_HasChecked = true;
if ((!wasChecked) && !d_ptr->m_pRecording) {
Text* other = call()->firstMedia<Text>(direction() == Media::Direction::OUT ?
Media::Direction::IN
: Media::Direction::OUT
);
if (other && other->recording())
d_ptr->m_pRecording = other->recording();
}
if ((!wasChecked) && !d_ptr->m_pRecording) {
if (auto otherRecording = call()->peerContactMethod()->textRecording())
d_ptr->m_pRecording = otherRecording;
else
d_ptr->m_pRecording = RecordingModel::instance().createTextRecording(call()->peerContactMethod());
}
return d_ptr->m_pRecording;
}
bool Media::Text::hasMimeType( const QString& mimeType ) const
{
return d_ptr->m_hMimeTypes.contains(mimeType);
}
QStringList Media::Text::mimeTypes() const
{
return d_ptr->m_lMimeTypes;
}
void MediaTextPrivate::updateMimeList(const QMap<QString,QString>& payloads)
{
const int prevSize = m_hMimeTypes.size();
QMapIterator<QString, QString> iter(payloads);
while (iter.hasNext()) {
iter.next();
// Mime types can have some arguments after ';'
const QString mimeType = iter.key();
const int hasArgs = mimeType.indexOf(';');
const QString strippedMimeType = hasArgs != -1 ? mimeType.left(hasArgs) : mimeType;
const int currentSize = m_hMimeTypes.size();
m_hMimeTypes[strippedMimeType] = true;
if (currentSize != m_hMimeTypes.size())
m_lMimeTypes << strippedMimeType;
}
if (prevSize != m_hMimeTypes.size())
emit q_ptr->mimeTypesChanged();
}
/**
* Send a message to the peer.
*
* @param message A messages encoded in various alternate payloads
*
* The send a single messages. Just as e-mails, the message can be
* encoded differently. The peer client will interpret the richest
* payload it support and then fallback to lesser ones.
*
* Suggested payloads include:
*
* "text/plain" : The most common plain UTF-8 text
* "text/enriched" : (RTF) The rich text format used by older applications (like WordPad and OS X TextEdit)
* "text/html" : The format used by web browsers
*/
void Media::Text::send(const QMap<QString,QString>& message, const bool isMixed)
{
CallManagerInterface& callManager = CallManager::instance();
Q_NOREPLY callManager.sendTextMessage(call()->dringId(), message, isMixed);
//Make sure the recording exist
recording();
d_ptr->m_pRecording->setCall(call());
d_ptr->m_pRecording->d_ptr->insertNewMessage(message,call()->peerContactMethod(),Media::Direction::OUT);
d_ptr->updateMimeList(message);
emit messageSent(message);
}
#include <text.moc>