From dd1cdd3085fb6ffa43d1d8e859fd3b1deb8c813e Mon Sep 17 00:00:00 2001 From: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> Date: Wed, 27 Jan 2016 14:14:58 -0500 Subject: [PATCH] Load saved TextRecordings from .json files. The main goal of this patch is to be able to recover ContactMethods and conversations which have not been added to the users's contacts and/or which have no call history. This happens in the case that the users only sends chat messages to a ContactMethod. Note that there is a possibility for more than one file to be associated with the same CM, in the case the sha1 of that CM has changed (eg: a contact UID has been associated with the CM at some point). In this case, the most recent recording is used. However, this is a problem which already exists and is unrelated to this patch. Tuleap: #270 Change-Id: I5c67be83028af3fc3e1950909574ec947c55c50c --- src/localtextrecordingcollection.cpp | 54 +++++++++++++++- src/media/textrecording.cpp | 94 +++++++++++++++++++--------- src/media/textrecording.h | 1 + 3 files changed, 116 insertions(+), 33 deletions(-) diff --git a/src/localtextrecordingcollection.cpp b/src/localtextrecordingcollection.cpp index 91623caa..a9f46cd2 100644 --- a/src/localtextrecordingcollection.cpp +++ b/src/localtextrecordingcollection.cpp @@ -171,9 +171,57 @@ bool LocalTextRecordingCollection::isEnabled() const bool LocalTextRecordingCollection::load() { - //This collection is special as it use the history collection - //as its source, there is no loading - return true; + // load all text recordings so we can recover CMs that are not in the call history + QDir dir(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/text/"); + if (dir.exists()) { + // get .json files, sorted by time, latest first + QStringList filters; + filters << "*.json"; + auto list = dir.entryInfoList(filters, QDir::Files | QDir::NoSymLinks | QDir::Readable, QDir::Time); + + for (int i = 0; i < list.size(); ++i) { + QFileInfo fileInfo = list.at(i); + + QString content; + QFile file(fileInfo.absoluteFilePath()); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + content = QString::fromUtf8(file.readAll()); + } else { + qWarning() << "Could not open text recording json file"; + } + + if (!content.isEmpty()) { + QJsonParseError err; + QJsonDocument loadDoc = QJsonDocument::fromJson(content.toUtf8(), &err); + + if (err.error == QJsonParseError::ParseError::NoError) { + Media::TextRecording* r = Media::TextRecording::fromJson({loadDoc.object()}); + + editor<Media::Recording>()->addExisting(r); + r->setCollection(this); + + // get CMs from recording + for (ContactMethod *cm : r->peers()) { + // since we load the recordings in order from newest to oldest, if there is + // more than one found associated with a CM, we take the newest one + if (!cm->d_ptr->m_pTextRecording) { + cm->d_ptr->setTextRecording(r); + } else { + qWarning() << "CM already has text recording" << cm; + } + } + } else { + qWarning() << "Error Decoding Text Message History Json" << err.errorString(); + } + } else { + qWarning() << "Text recording file is empty"; + } + } + } + + // always return true, even if noting was loaded, since the collection can still be used to + // save files + return true; } bool LocalTextRecordingCollection::reload() diff --git a/src/media/textrecording.cpp b/src/media/textrecording.cpp index 5c99c66c..215631de 100644 --- a/src/media/textrecording.cpp +++ b/src/media/textrecording.cpp @@ -208,6 +208,19 @@ void Media::TextRecording::setAllRead() } } +QVector<ContactMethod*> Media::TextRecording::peers() const +{ + QVector<ContactMethod*> cms; + + for (const Serializable::Peers* peers : d_ptr->m_lAssociatedPeers) { + for (const Serializable::Peer* peer : peers->peers) { + cms << peer->m_pContactMethod; + } + } + + return cms; +} + /** * I (Emmanuel Lepage) is in the process of writing a better one for this that * can be upstreamed into Qt (there is interest in merging a generic QVariant @@ -304,40 +317,61 @@ QHash<QByteArray,QByteArray> Media::TextRecordingPrivate::toJsons() const Media::TextRecording* Media::TextRecording::fromJson(const QList<QJsonObject>& items, const ContactMethod* cm) { - TextRecording* t = new TextRecording(); + TextRecording* t = new TextRecording(); - //Load the history data - for (const QJsonObject& obj : items) { - Serializable::Peers* p = SerializableEntityManager::fromJson(obj,cm); - t->d_ptr->m_lAssociatedPeers << p; - } + //Load the history data + for (const QJsonObject& obj : items) { + Serializable::Peers* p = SerializableEntityManager::fromJson(obj,cm); + t->d_ptr->m_lAssociatedPeers << p; + } - //Create the model - t->instantMessagingModel(); - - //Reconstruct the conversation - //TODO do it right, right now it flatten the graph - for (const Serializable::Peers* p : t->d_ptr->m_lAssociatedPeers) { - for (const Serializable::Group* g : p->groups) { - for (Serializable::Message* m : g->messages) { - ::TextMessageNode* n = new ::TextMessageNode(); - n->m_pMessage = m ; - if (!n->m_pMessage->contactMethod) { - n->m_pMessage->contactMethod = const_cast<ContactMethod*>(cm); //TODO remove in 2016 - n->m_pMessage->authorSha1 = cm->sha1(); - - if (p->peers.isEmpty()) - addPeer(const_cast<Serializable::Peers*>(p), cm); + //Create the model + t->instantMessagingModel(); + + //Reconstruct the conversation + //TODO do it right, right now it flatten the graph + for (const Serializable::Peers* p : t->d_ptr->m_lAssociatedPeers) { + // TODO: for now assume the convo is with only 1 CM at a time + auto peerCM = p->peers.at(0)->m_pContactMethod; + + // get the latest timestamp to set last used + time_t lastUsed = 0; + for (const Serializable::Group* g : p->groups) { + for (Serializable::Message* m : g->messages) { + ::TextMessageNode* n = new ::TextMessageNode(); + n->m_pMessage = m ; + if (!n->m_pMessage->contactMethod) { + if (cm) { + n->m_pMessage->contactMethod = const_cast<ContactMethod*>(cm); //TODO remove in 2016 + n->m_pMessage->authorSha1 = cm->sha1(); + + if (p->peers.isEmpty()) + addPeer(const_cast<Serializable::Peers*>(p), cm); + } else { + if (p->m_hSha1.contains(n->m_pMessage->authorSha1)) { + n->m_pMessage->contactMethod = p->m_hSha1[n->m_pMessage->authorSha1]; + } else { + // message was outgoing and author sha1 was set to that of the sending account + n->m_pMessage->contactMethod = peerCM; + n->m_pMessage->authorSha1 = peerCM->sha1(); + } + } + } + n->m_pContactMethod = m->contactMethod; + t->d_ptr->m_pImModel->addRowBegin(); + t->d_ptr->m_lNodes << n; + t->d_ptr->m_pImModel->addRowEnd(); + + if (lastUsed < n->m_pMessage->timestamp) + lastUsed = n->m_pMessage->timestamp; } - n->m_pContactMethod = m->contactMethod; - t->d_ptr->m_pImModel->addRowBegin(); - t->d_ptr->m_lNodes << n; - t->d_ptr->m_pImModel->addRowEnd(); - } - } - } + } + + // update the timestamp of the CM + peerCM->setLastUsed(lastUsed); + } - return t; + return t; } void Media::TextRecordingPrivate::insertNewMessage(const QMap<QString,QString>& message, ContactMethod* cm, Media::Media::Direction direction) diff --git a/src/media/textrecording.h b/src/media/textrecording.h index fa4456f7..f0602e48 100644 --- a/src/media/textrecording.h +++ b/src/media/textrecording.h @@ -75,6 +75,7 @@ public: bool isEmpty ( ) const; bool hasMimeType ( const QString& mimeType ) const; QStringList mimeTypes ( ) const; + QVector<ContactMethod*> peers ( ) const; //Helper void setAllRead(); -- GitLab