[Merge] lp:history-service/staging into lp:history-service

Roberto Mier Escandón  roberto.escandon at canonical.com
Wed Oct 26 11:28:27 UTC 2016


Review: Needs Fixing

Left some comments

Diff comments:

> 
> === modified file 'Ubuntu/History/historymodel.cpp'
> --- Ubuntu/History/historymodel.cpp	2015-10-08 21:52:59 +0000
> +++ Ubuntu/History/historymodel.cpp	2016-10-20 14:04:14 +0000
> @@ -89,16 +92,98 @@
>      case TypeRole:
>          result = properties[History::FieldType];
>          break;
> -    case ParticipantsRole:
> +    case ParticipantsRole: {
> +        // FIXME: reimplement in a cleaner way
> +        History::Participants participants = History::Participants::fromVariantList(properties[History::FieldParticipants].toList());
>          if (mMatchContacts) {
> -            result = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
> -                                                                      History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers());
> +            QVariantList finalParticipantsList;
> +            QVariantList participantsInfo = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
> +                                                                      participants.identifiers());
> +            int count = 0;
> +            Q_FOREACH(const QVariant &participantInfo, participantsInfo) {
> +                QVariantMap newMap = participantInfo.toMap();
> +                if (participants.at(count).state() != History::ParticipantStateRegular) {
> +                   count++;
> +                   continue;
> +                }
> +                newMap[History::FieldParticipantState] = participants.at(count).state();
> +                newMap[History::FieldParticipantRoles] = participants.at(count++).roles();
> +                finalParticipantsList << newMap;
> +            }

This Q_FOREACH sounds weird to me. Wouldn't be better doing like this?:

for (int i = 0; i < participantsInfo.size(); ++i) {
	if (participants.at(i).state() == History::ParticipantStateRegular) {
		QVariantMap newMap = participantsInfo.value(i).toMap();
		newMap[History::FieldParticipantState] = participants.at(i).state();
		newMap[History::FieldParticipantRoles] = participants.at(i).roles();
		finalParticipantsList << newMap;
	}
}

> +            result = finalParticipantsList;
>          } else {
>              //FIXME: handle contact changes
>              result = properties[History::FieldParticipants];
>          }
>          break;
>      }
> +    case ParticipantsRemotePendingRole: {
> +        // FIXME: reimplement in a cleaner way
> +        QStringList identifiers;
> +        History::Participants participants;
> +        // filter remote pending participants
> +        Q_FOREACH(const History::Participant &participant, History::Participants::fromVariantList(properties[History::FieldParticipants].toList())) {
> +            if (participant.state() == History::ParticipantStateRemotePending) {
> +                participants << participant;
> +            }
> +        }
> +
> +        if (mMatchContacts) {
> +            QVariantList finalParticipantsList;
> +            QVariantList participantsInfo = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
> +                                                                      participants.identifiers());
> +            int count = 0;
> +            Q_FOREACH(const QVariant &participantInfo, participantsInfo) {
> +                QVariantMap newMap = participantInfo.toMap();
> +                newMap[History::FieldParticipantState] = participants.at(count).state();
> +                newMap[History::FieldParticipantRoles] = participants.at(count++).roles();
> +                finalParticipantsList << newMap;
> +            }

Same as before, 

for (int i = 0; i < participantsInfo.size(); ++i) {
	QVariantMap newMap = participantsInfo.value(i).toMap();
	newMap[History::FieldParticipantState] = participants.at(i).state();
	newMap[History::FieldParticipantRoles] = participants.at(i).roles();
	finalParticipantsList << newMap;
}

Actually, as both are quite similar, maybe it would be a good idea doing this in a separated method

> +            result = finalParticipantsList;
> +        } else {
> +            //FIXME: handle contact changes
> +            result = participants.identifiers();
> +        }
> +
> +        break;
> +    }
> +    case ParticipantsLocalPendingRole: {
> +        // FIXME: reimplement in a cleaner way
> +        QStringList identifiers;
> +        History::Participants participants;
> +        Q_FOREACH(const History::Participant &participant, History::Participants::fromVariantList(properties[History::FieldParticipants].toList())) {
> +            if (participant.state() == History::ParticipantStateLocalPending) {
> +                participants << participant;
> +            }
> +        }
> +
> +        if (mMatchContacts) {
> +            QVariantList finalParticipantsList;
> +            QVariantList participantsInfo = History::ContactMatcher::instance()->contactInfo(properties[History::FieldAccountId].toString(),
> +                                                                      identifiers);
> +            int count = 0;
> +            Q_FOREACH(const QVariant &participantInfo, participantsInfo) {
> +                QVariantMap newMap = participantInfo.toMap();
> +                newMap[History::FieldParticipantState] = participants.at(count).state();
> +                newMap[History::FieldParticipantRoles] = participants.at(count++).roles();
> +                finalParticipantsList << newMap;
> +            }
> +            result = finalParticipantsList;

Same as before. Rewrite in a separated method

> +        } else {
> +            //FIXME: handle contact changes
> +            result = identifiers;
> +        }
> +
> +        break;
> +    }
> +    case ParticipantIdsRole:
> +        result = History::Participants::fromVariantList(properties[History::FieldParticipants].toList()).identifiers();
> +        break;
> +    case TimestampRole:
> +        result = QDateTime::fromString(properties[History::FieldTimestamp].toString(), Qt::ISODate);
> +        break;
> +    }
> +
>      return result;
>  }
>  
> 
> === modified file 'daemon/historydaemon.cpp'
> --- daemon/historydaemon.cpp	2015-11-20 12:53:49 +0000
> +++ daemon/historydaemon.cpp	2016-10-20 14:04:14 +0000
> @@ -414,8 +613,270 @@
>      event[History::FieldMissed] = missed;
>      event[History::FieldDuration] = duration;
>      // FIXME: check what to do when there are more than just one remote participant
> -    event[History::FieldRemoteParticipant] = participants[0];
> -    writeEvents(QList<QVariantMap>() << event);
> +    event[History::FieldRemoteParticipant] = participants[0].toMap()[History::FieldIdentifier];
> +    writeEvents(QList<QVariantMap>() << event, properties);
> +}
> +
> +void HistoryDaemon::onTextChannelAvailable(const Tp::TextChannelPtr channel)
> +{
> +    // for Rooms we need to explicitly create the thread to allow users to send messages to groups even
> +    // before they receive any message.
> +    // for other types, we can wait until messages are received
> +    if (channel->targetHandleType() == Tp::HandleTypeRoom) {
> +        QString accountId = channel->property(History::FieldAccountId).toString();
> +        QVariantMap properties = propertiesFromChannel(channel);
> +
> +        // first try to fetch the existing thread to see if there is any.
> +        QVariantMap thread = threadForProperties(accountId,
> +                                                 History::EventTypeText,
> +                                                 properties,
> +                                                 matchFlagsForChannel(channel),
> +                                                 false);
> +        if (thread.isEmpty()) {
> +            // if there no existing thread, create one
> +            properties["Requested"] = channel->isRequested();
> +            thread = threadForProperties(accountId,
> +                                         History::EventTypeText,
> +                                         properties,
> +                                         matchFlagsForChannel(channel),
> +                                         true);
> +
> +            // write information event including all initial invitees
> +            Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
> +                writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
> +            }
> +
> +            // update participants only if the thread is not available previously. Otherwise we'll wait for membersChanged event
> +            // for reflect in conversation information events for modified participants.
> +            updateRoomParticipants(channel);
> +        }
> +
> +        // write an entry saying you joined the group if 'joined' flag in thread is false and modify that flag.
> +        if (!thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {

Maybe hold "Joined" string as a const value somewhere?

> +            writeInformationEvent(thread, History::InformationTypeSelfJoined);
> +            // update backend
> +            updateRoomProperties(channel, QVariantMap{{"Joined", true}});
> +        }
> +
> +        Tp::AbstractInterface *room_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomInterface>();
> +        Tp::AbstractInterface *room_config_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceRoomConfigInterface>();
> +        Tp::AbstractInterface *subject_interface = channel->optionalInterface<Tp::Client::ChannelInterfaceSubjectInterface>();
> +        ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
> +
> +        QList<Tp::AbstractInterface*> interfaces;
> +        interfaces << room_interface << room_config_interface << subject_interface << roles_interface;
> +        for (auto interface : interfaces) {
> +            if (interface) {
> +                interface->setMonitorProperties(true);
> +                interface->setProperty(History::FieldAccountId, accountId);
> +                interface->setProperty(History::FieldThreadId, thread[History::FieldThreadId].toString());
> +                interface->setProperty(History::FieldType, thread[History::FieldType].toInt());
> +                connect(interface, SIGNAL(propertiesChanged(const QVariantMap &,const QStringList &)),
> +                                   SLOT(onRoomPropertiesChanged(const QVariantMap &,const QStringList &)));
> +                // update the stored info
> +                Q_EMIT interface->propertiesChanged(getInterfaceProperties(interface), QStringList());
> +            }
> +        }
> +
> +        connect(channel.data(), SIGNAL(groupMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Channel::GroupMemberChangeDetails &)),
> +                SLOT(onGroupMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Channel::GroupMemberChangeDetails &)));
> +
> +        connect(roles_interface, SIGNAL(RolesChanged(const HandleRolesMap&, const HandleRolesMap&)), SLOT(onRolesChanged(const HandleRolesMap&, const HandleRolesMap&)));
> +    }
> +}
> +
> +void HistoryDaemon::onGroupMembersChanged(const Tp::Contacts &groupMembersAdded,
> +                                          const Tp::Contacts &groupLocalPendingMembersAdded,
> +                                          const Tp::Contacts &groupRemotePendingMembersAdded,
> +                                          const Tp::Contacts &groupMembersRemoved,
> +                                          const Tp::Channel::GroupMemberChangeDetails &details)
> +{
> +    Tp::TextChannelPtr channel(qobject_cast<Tp::TextChannel*>(sender()));
> +
> +    QVariantMap properties;
> +    QVariantMap thread;
> +
> +    // information events for members updates.
> +    bool hasRemotePendingMembersAdded = groupRemotePendingMembersAdded.size() > 0;
> +    bool hasMembersAdded = groupMembersAdded.size() > 0;
> +    bool hasMembersRemoved = groupMembersRemoved.size() > 0;
> +
> +    if (hasRemotePendingMembersAdded || hasMembersAdded || hasMembersRemoved) {
> +        properties = propertiesFromChannel(channel);
> +        thread = threadForProperties(channel->property(History::FieldAccountId).toString(),
> +                                                       History::EventTypeText,
> +                                                       properties,
> +                                                       matchFlagsForChannel(channel),
> +                                                       false);
> +        if (!thread.isEmpty()) {
> +            if (hasRemotePendingMembersAdded) {
> +                Q_FOREACH (const Tp::ContactPtr& contact, groupRemotePendingMembersAdded) {
> +                    if (!foundInThread(contact, thread)) {
> +                        writeInformationEvent(thread, History::InformationTypeInvitationSent, contact->alias());
> +                    }
> +                }
> +            }
> +            if (hasMembersAdded) {
> +                Q_FOREACH (const Tp::ContactPtr& contact, groupMembersAdded) {
> +                    // if this member was not previously regular member in thread, notify about his join
> +                    if (!foundAsMemberInThread(contact, thread)) {
> +                        writeInformationEvent(thread, History::InformationTypeJoined, contact->alias());
> +                    }
> +                }
> +            }
> +
> +            if (hasMembersRemoved) {
> +                if (channel->groupSelfContactRemoveInfo().isValid()) {
> +                    // evaluate if we are leaving by our own or we are kicked
> +                    History::InformationType type = History::InformationTypeSelfLeaving;
> +                    if (channel->groupSelfContactRemoveInfo().hasReason()) {
> +                        switch (channel->groupSelfContactRemoveInfo().reason()) {
> +                        case ChannelGroupChangeReasonKicked:
> +                            type = History::InformationTypeSelfKicked;
> +                            break;
> +                        case ChannelGroupChangeReasonGone:
> +                            type = History::InformationTypeGroupGone;
> +                            break;
> +                        }
> +                    }
> +                    writeInformationEvent(thread, type);
> +                    // update backend
> +                    updateRoomProperties(channel, QVariantMap{{"Joined", false}});
> +                }
> +                else // don't notify any other group member removal if we are leaving the group
> +                {
> +                    Q_FOREACH (const Tp::ContactPtr& contact, groupMembersRemoved) {
> +                        // inform about removed members other than us
> +                        if (contact->id() != channel->groupSelfContact()->id()) {
> +                            writeInformationEvent(thread, History::InformationTypeLeaving, contact->alias());
> +                        }
> +                    }
> +                }
> +            }
> +        }
> +    }
> +
> +    updateRoomParticipants(channel);
> +}
> +
> +void HistoryDaemon::updateRoomParticipants(const Tp::TextChannelPtr channel)
> +{
> +    if (!channel) {
> +        return;
> +    }
> +
> +    QVariantList participants;
> +    QStringList contactsAdded;
> +
> +    ChannelInterfaceRolesInterface *roles_interface = channel->optionalInterface<ChannelInterfaceRolesInterface>();
> +    RolesMap roles;
> +    if (roles_interface) {
> +        roles = roles_interface->getRoles();
> +    }
> +
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
> +        QVariantMap participant;
> +        contactsAdded << contact->id();
> +        participant[History::FieldIdentifier] = contact->id();
> +        participant[History::FieldAlias] = contact->alias();
> +        participant[History::FieldParticipantState] = History::ParticipantStateRemotePending;
> +        participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
> +        participants << QVariant::fromValue(participant);
> +    }
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupLocalPendingContacts(false)) {
> +        QVariantMap participant;
> +        contactsAdded << contact->id();
> +        participant[History::FieldIdentifier] = contact->id();
> +        participant[History::FieldAlias] = contact->alias();
> +        participant[History::FieldParticipantState] = History::ParticipantStateLocalPending;
> +        participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
> +        participants << QVariant::fromValue(participant);
> +    }
> +
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
> +        // do not include remote and local pending members
> +        if (contactsAdded.contains(contact->id())) {
> +            continue;
> +        }
> +        QVariantMap participant;
> +        participant[History::FieldIdentifier] = contact->id();
> +        participant[History::FieldAlias] = contact->alias();
> +        participant[History::FieldParticipantState] = History::ParticipantStateRegular;
> +        participant[History::FieldParticipantRoles] = roles[contact->handle().at(0)];
> +        participants << QVariant::fromValue(participant);
> +    }
> +
> +    QString accountId = channel->property(History::FieldAccountId).toString();
> +    QString threadId = channel->targetId();
> +    if (mBackend->updateRoomParticipants(accountId, threadId, History::EventTypeText, participants)) {
> +        QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
> +        mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
> +    }
> +}
> +
> +void HistoryDaemon::updateRoomRoles(const Tp::TextChannelPtr &channel, const RolesMap &rolesMap)
> +{
> +    if (!channel) {
> +        return;
> +    }
> +
> +    QVariantMap participantsRoles;
> +
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupRemotePendingContacts(false)) {
> +        participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
> +    }
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupLocalPendingContacts(false)) {
> +        participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
> +    }
> +
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
> +        if (!participantsRoles.contains(contact->id())) {
> +            participantsRoles[contact->id()] = rolesMap[contact->handle().at(0)];
> +        }
> +    }
> +
> +    // update participants roles
> +    QString accountId = channel->property(History::FieldAccountId).toString();
> +    QString threadId = channel->targetId();
> +    if (mBackend->updateRoomParticipantsRoles(accountId, threadId, History::EventTypeText, participantsRoles)) {
> +        QVariantMap updatedThread = getSingleThread(History::EventTypeText, accountId, threadId, QVariantMap());
> +        mDBus.notifyThreadsModified(QList<QVariantMap>() << updatedThread);
> +    }
> +
> +    // update self roles in room properties
> +    uint selfRoles = rolesMap[channel->groupSelfContact()->handle().at(0)];
> +    updateRoomProperties(channel, QVariantMap{{"SelfRoles", selfRoles}});
> +}
> +
> +void HistoryDaemon::onRoomPropertiesChanged(const QVariantMap &properties,const QStringList &invalidated)
> +{
> +    QString accountId = sender()->property(History::FieldAccountId).toString();
> +    QString threadId = sender()->property(History::FieldThreadId).toString();
> +    History::EventType type = (History::EventType)sender()->property(History::FieldType).toInt();
> +
> +    // get thread before updating to see if there are changes to insert as information events
> +    QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
> +    if (!thread.empty()) {
> +        writeRoomChangesInformationEvents(thread, properties);
> +    }
> +
> +    updateRoomProperties(accountId, threadId, type, properties, invalidated);
> +}
> +
> +void HistoryDaemon::updateRoomProperties(const Tp::TextChannelPtr &channel, const QVariantMap &properties)
> +{
> +    QString accountId = channel->property(History::FieldAccountId).toString();
> +    QString threadId = channel->targetId();
> +    History::EventType type = History::EventTypeText;
> +    updateRoomProperties(accountId, threadId, type, properties, QStringList());
> +}
> +
> +void HistoryDaemon::updateRoomProperties(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
> +{
> +    if (mBackend->updateRoomInfo(accountId, threadId, type, properties, invalidated)) {
> +        QVariantMap thread = getSingleThread(type, accountId, threadId, QVariantMap());
> +        mDBus.notifyThreadsModified(QList<QVariantMap>() << thread);
> +    }
>  }
>  
>  void HistoryDaemon::onMessageReceived(const Tp::TextChannelPtr textChannel, const Tp::ReceivedMessage &message)
> @@ -424,8 +885,13 @@
>      QString eventId;
>      Tp::MessagePart header = message.header();
>      QString senderId;
> +    QVariantMap properties = propertiesFromChannel(textChannel);
>      History::MessageStatus status = History::MessageStatusUnknown;
> -    if (message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
> +    if (!message.sender() || message.sender()->handle().at(0) == textChannel->connection()->selfHandle()) {
> +        qDebug() << __PRETTY_FUNCTION__ << message.sender();

Do we need these traces?

> +        if (message.sender()) {
> +            qDebug() << __PRETTY_FUNCTION__ << "size: " << message.sender()->handle().size() << "first handle" << message.sender()->handle().at(0);
> +        }
>          senderId = "self";
>          status = History::MessageStatusDelivered;
>      } else {
> @@ -707,3 +1184,88 @@
>      hash += "#-#" + thread[History::FieldThreadId].toString();
>      return hash;
>  }
> +
> +QVariantMap HistoryDaemon::getInterfaceProperties(const Tp::AbstractInterface *interface)
> +{
> +    QDBusInterface propsInterface(interface->service(), interface->path(), "org.freedesktop.DBus.Properties");
> +    QDBusReply<QVariantMap> reply = propsInterface.call("GetAll", interface->interface());
> +    if (!reply.isValid()) {
> +        qWarning() << "Failed to fetch channel properties for interface" << interface->interface() << reply.error().message();
> +    }
> +    return reply.value();
> +}
> +
> +void HistoryDaemon::writeInformationEvent(const QVariantMap &thread, History::InformationType type, const QString &subject, const QString &sender, const QString &text)
> +{
> +    History::TextEvent historyEvent = History::TextEvent(thread[History::FieldAccountId].toString(),
> +                                                         thread[History::FieldThreadId].toString(),
> +                                                         QString(QCryptographicHash::hash(QByteArray(
> +                                                                 (QDateTime::currentDateTime().toString() + subject + text).toLatin1()),
> +                                                                 QCryptographicHash::Md5).toHex()),
> +                                                         sender,
> +                                                         QDateTime::currentDateTime(),
> +                                                         false,
> +                                                         text,
> +                                                         History::MessageTypeInformation,
> +                                                         History::MessageStatusUnknown,
> +                                                         QDateTime::currentDateTime(),
> +                                                         subject,
> +                                                         type);
> +    writeEvents(QList<QVariantMap>() << historyEvent.properties(), thread);
> +}
> +
> +void HistoryDaemon::writeRoomChangesInformationEvents(const QVariantMap &thread, const QVariantMap &interfaceProperties)
> +{
> +    if (!thread.isEmpty()) {
> +        // group subject
> +        QString storedSubject = thread[History::FieldChatRoomInfo].toMap()["Subject"].toString();
> +        QString newSubject = interfaceProperties["Subject"].toString();

Should be also have "Subject" somewhere as const value?

> +        if (!newSubject.isEmpty() && storedSubject != newSubject) {
> +            //see if we have an actor. If actor is 'me', we have changed that subject
> +            QString actor = thread[History::FieldChatRoomInfo].toMap()["Actor"].toString();
> +            if (actor == "me") {
> +                actor = "self";
> +            }
> +            writeInformationEvent(thread, History::InformationTypeTitleChanged, newSubject, actor);
> +        }
> +    }
> +}
> +
> +void HistoryDaemon::writeRolesInformationEvents(const QVariantMap &thread, const Tp::ChannelPtr &channel, const RolesMap &rolesMap)
> +{
> +    if (thread.isEmpty()) {
> +        return;
> +    }
> +
> +    if (!thread[History::FieldChatRoomInfo].toMap()["Joined"].toBool()) {
> +        return;
> +    }
> +
> +    // list of identifiers for current channel admins
> +    QStringList adminIds;
> +
> +    Q_FOREACH(const Tp::ContactPtr contact, channel->groupContacts(false)) {
> +        // see if admin role (ChannelAdminRole == 2)
> +        if (rolesMap[contact->handle().at(0)] & AdminRole) {
> +            adminIds << contact->id();
> +        }
> +    }
> +
> +    Q_FOREACH (QVariant participant, thread[History::FieldParticipants].toList()) {
> +        QString participantId = participant.toMap()[History::FieldIdentifier].toString();
> +        if (adminIds.contains(participantId)) {
> +            // see if already was admin or not (ChannelAdminRole == 2)
> +            if (! (participant.toMap()[History::FieldParticipantRoles].toUInt() & AdminRole)) {
> +                writeInformationEvent(thread, History::InformationTypeAdminGranted, participantId);
> +            }
> +        }
> +    }
> +
> +    //evaluate now self roles
> +    if (rolesMap[channel->groupSelfContact()->handle().at(0)] & AdminRole) {
> +        uint selfRoles = thread[History::FieldChatRoomInfo].toMap()["SelfRoles"].toUInt();
> +        if (! (selfRoles & AdminRole)) {
> +            writeInformationEvent(thread, History::InformationTypeSelfAdminGranted);
> +        }
> +    }
> +}
> 
> === modified file 'plugins/sqlite/sqlitehistoryplugin.cpp'
> --- plugins/sqlite/sqlitehistoryplugin.cpp	2015-11-20 11:40:36 +0000
> +++ plugins/sqlite/sqlitehistoryplugin.cpp	2016-10-20 14:04:14 +0000
> @@ -469,58 +510,341 @@
>      return result;
>  }
>  
> -// Writer
> -QVariantMap SQLiteHistoryPlugin::createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants)
> +bool SQLiteHistoryPlugin::updateRoomParticipants(const QString &accountId, const QString &threadId, History::EventType type, const QVariantList &participants)
> +{
> +    QSqlQuery query(SQLiteDatabase::instance()->database());
> +    if (accountId.isEmpty() || threadId.isEmpty()) {
> +        return false;
> +    }
> +
> +    SQLiteDatabase::instance()->beginTransation();
> +    QString deleteString("DELETE FROM thread_participants WHERE threadId=:threadId AND type=:type AND accountId=:accountId");
> +    query.prepare(deleteString);
> +    query.bindValue(":accountId", accountId);
> +    query.bindValue(":threadId", threadId);
> +    query.bindValue(":type", type);
> +    if (!query.exec()) {
> +        qCritical() << "Error removing old participants:" << query.lastError() << query.lastQuery();
> +        SQLiteDatabase::instance()->rollbackTransaction();
> +        return false;
> +    }
> +
> +    // and insert the participants
> +    Q_FOREACH(const QVariant &participantVariant, participants) {
> +        QVariantMap participant = participantVariant.toMap();
> +        query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId, alias, state, roles)"
> +                      "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId, :alias, :state, :roles)");
> +        query.bindValue(":accountId", accountId);
> +        query.bindValue(":threadId", threadId);
> +        query.bindValue(":type", type);
> +        query.bindValue(":participantId", participant["identifier"].toString());
> +        query.bindValue(":normalizedId", participant["identifier"].toString());
> +        query.bindValue(":alias", participant["alias"].toString());
> +        query.bindValue(":state", participant["state"].toUInt());
> +        query.bindValue(":roles", participant["roles"].toUInt());
> +        if (!query.exec()) {
> +            qCritical() << "Error:" << query.lastError() << query.lastQuery();
> +            SQLiteDatabase::instance()->rollbackTransaction();
> +            return false;
> +        }
> +    }
> +
> +    if (!SQLiteDatabase::instance()->finishTransaction()) {
> +        qCritical() << "Failed to commit the transaction.";
> +        return false;
> +    }
> +
> +    QVariantMap existingThread = getSingleThread(type,
> +                                                 accountId,
> +                                                 threadId,
> +                                                 QVariantMap());
> +
> +    if (!existingThread.isEmpty()) {
> +        addThreadsToCache(QList<QVariantMap>() << existingThread);
> +    }
> +
> +    return true;
> +}
> +
> +bool SQLiteHistoryPlugin::updateRoomParticipantsRoles(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &participantsRoles)
> +{
> +    QSqlQuery query(SQLiteDatabase::instance()->database());
> +    if (accountId.isEmpty() || threadId.isEmpty()) {
> +        return false;
> +    }
> +
> +    SQLiteDatabase::instance()->beginTransation();
> +    Q_FOREACH(const QString &participantId, participantsRoles.keys()) {
> +        query.prepare("UPDATE thread_participants SET roles=:roles WHERE accountId=:accountId AND threadId=:threadId AND type=:type AND participantId=:participantId");
> +        query.bindValue(":roles", participantsRoles.value(participantId).toUInt());
> +        query.bindValue(":accountId", accountId);
> +        query.bindValue(":threadId", threadId);
> +        query.bindValue(":type", type);
> +        query.bindValue(":participantId", participantId);
> +        if (!query.exec()) {
> +            qCritical() << "Error:" << query.lastError() << query.lastQuery();
> +            SQLiteDatabase::instance()->rollbackTransaction();
> +            return false;
> +        }
> +    }
> +
> +    if (!SQLiteDatabase::instance()->finishTransaction()) {
> +        qCritical() << "Failed to commit the transaction.";
> +        return false;
> +    }
> +
> +    QVariantMap existingThread = getSingleThread(type,
> +                                                 accountId,
> +                                                 threadId,
> +                                                 QVariantMap());
> +
> +    if (!existingThread.isEmpty()) {
> +        addThreadsToCache(QList<QVariantMap>() << existingThread);
> +    }
> +
> +    return true;
> +}
> +
> +bool SQLiteHistoryPlugin::updateRoomInfo(const QString &accountId, const QString &threadId, History::EventType type, const QVariantMap &properties, const QStringList &invalidated)
> +{
> +    QSqlQuery query(SQLiteDatabase::instance()->database());
> +
> +    if (threadId.isEmpty() || accountId.isEmpty()) {
> +        return false;
> +    }
> +
> +    SQLiteDatabase::instance()->beginTransation();
> +
> +    QDateTime creationTimestamp = QDateTime::fromTime_t(properties["CreationTimestamp"].toUInt());
> +    QDateTime timestamp = QDateTime::fromTime_t(properties["Timestamp"].toUInt());
> +
> +    QVariantMap propertyMapping;
> +    propertyMapping["RoomName"] = "roomName";
> +    propertyMapping["Server"] = "server";
> +    propertyMapping["Creator"] = "creator";
> +    propertyMapping["CreationTimestamp"] = "creationTimestamp";
> +    propertyMapping["Anonymous"] = "anonymous";
> +    propertyMapping["InviteOnly"] = "inviteOnly";
> +    propertyMapping["Limit"] = "participantLimit";
> +    propertyMapping["Moderated"] = "moderated";
> +    propertyMapping["Title"] = "title";
> +    propertyMapping["Description"] = "description";
> +    propertyMapping["Persistent"] = "persistent";
> +    propertyMapping["Private"] = "private";
> +    propertyMapping["PasswordProtected"] = "passwordProtected";
> +    propertyMapping["Password"] = "password";
> +    propertyMapping["PasswordHint"] = "passwordHint";
> +    propertyMapping["CanUpdateConfiguration"] = "canUpdateConfiguration";
> +    propertyMapping["Subject"] = "subject";
> +    propertyMapping["Actor"] = "actor";
> +    propertyMapping["Timestamp"] = "timestamp";
> +    propertyMapping["Joined"] = "joined";
> +    propertyMapping["SelfRoles"] = "selfRoles";
> +
> +    QStringList changedPropListValues;
> +    // populate sql query
> +    Q_FOREACH (const QString &key, properties.keys()) {
> +        if (propertyMapping.contains(key)) {
> +            QString prop = propertyMapping[key].toString();
> +            changedPropListValues << QString(prop+"=:"+ prop);
> +        }
> +    }
> +    if (changedPropListValues.isEmpty()) {
> +       return false;
> +    }
> +
> +    query.prepare("UPDATE chat_room_info SET "+ changedPropListValues.join(", ")+" WHERE accountId=:accountId AND threadId=:threadId AND type=:type");
> +    query.bindValue(":accountId", accountId);
> +    query.bindValue(":threadId", threadId);
> +    query.bindValue(":type", (int) type);
> +    query.bindValue(":roomName", properties["RoomName"].toString());
> +    query.bindValue(":server", properties["Server"].toString());
> +    query.bindValue(":creator", properties["Creator"].toString());
> +    query.bindValue(":creationTimestamp", creationTimestamp.toUTC().toString(timestampFormat));
> +    query.bindValue(":anonymous", properties["Anonymous"].toBool());
> +    query.bindValue(":inviteOnly", properties["InviteOnly"].toBool());
> +    query.bindValue(":participantLimit", properties["Limit"].toInt());
> +    query.bindValue(":moderated", properties["Moderated"].toBool());
> +    query.bindValue(":title", properties["Title"].toString());
> +    query.bindValue(":description", properties["Description"].toString());
> +    query.bindValue(":persistent", properties["Persistent"].toBool());
> +    query.bindValue(":private", properties["Private"].toBool());
> +    query.bindValue(":passwordProtected", properties["PasswordProtected"].toBool());
> +    query.bindValue(":password", properties["Password"].toString());
> +    query.bindValue(":passwordHint", properties["PasswordHint"].toString());
> +    query.bindValue(":canUpdateConfiguration", properties["CanUpdateConfiguration"].toBool());
> +    query.bindValue(":subject", properties["Subject"].toString());
> +    query.bindValue(":actor", properties["Actor"].toString());
> +    query.bindValue(":timestamp", timestamp.toUTC().toString(timestampFormat));
> +    query.bindValue(":joined", properties["Joined"].toBool());
> +    query.bindValue(":selfRoles", properties["SelfRoles"].toInt());
> +
> +    if (!query.exec()) {
> +        qCritical() << "Error:" << query.lastError() << query.lastQuery();
> +        SQLiteDatabase::instance()->rollbackTransaction();
> +        return false;
> +    }
> +
> +    if (!SQLiteDatabase::instance()->finishTransaction()) {
> +        qCritical() << "Failed to commit the transaction.";
> +        return false;
> +    }
> +
> +    QVariantMap existingThread = getSingleThread(type,
> +                                                 accountId,
> +                                                 threadId,
> +                                                 QVariantMap());
> +
> +    if (!existingThread.isEmpty()) {
> +        addThreadsToCache(QList<QVariantMap>() << existingThread);
> +    }
> +
> +    return true;
> +}
> +
> +QVariantMap SQLiteHistoryPlugin::createThreadForProperties(const QString &accountId, History::EventType type, const QVariantMap &properties)
>  {
>      // WARNING: this function does NOT test to check if the thread is already created, you should check using HistoryReader::threadForParticipants()
>  
>      QVariantMap thread;
> +    History::Participants participants = History::Participants::fromVariant(properties[History::FieldParticipantIds]);
>  
>      // Create a new thread
>      // FIXME: define what the threadId will be
> -    QString threadId = participants.join("%");
> +    QString threadId;
> +    History::ChatType chatType = (History::ChatType)properties[History::FieldChatType].toInt();
> +    QVariantMap chatRoomInfo;
> +
> +    SQLiteDatabase::instance()->beginTransation();
> +
> +    if (chatType == History::ChatTypeRoom) {
> +        threadId = properties[History::FieldThreadId].toString();
> +        // we cannot save chat room without threadId
> +        if (accountId.isEmpty() || threadId.isEmpty()) {
> +            SQLiteDatabase::instance()->rollbackTransaction();
> +            return thread;
> +        }
> +        chatRoomInfo = properties[History::FieldChatRoomInfo].toMap();
> +        QSqlQuery query(SQLiteDatabase::instance()->database());
> +
> +        QDateTime creationTimestamp = QDateTime::fromTime_t(chatRoomInfo["CreationTimestamp"].toUInt());
> +        QDateTime timestamp = QDateTime::fromTime_t(chatRoomInfo["Timestamp"].toUInt());
> +
> +        query.prepare("INSERT INTO chat_room_info (accountId, threadId, type, roomName, server, creator, creationTimestamp, anonymous, inviteOnly, participantLimit, moderated, title, description, persistent, private, passwordProtected, password, passwordHint, canUpdateConfiguration, subject, actor, timestamp, joined, selfRoles) "
> +                      "VALUES (:accountId, :threadId, :type, :roomName, :server, :creator, :creationTimestamp, :anonymous, :inviteOnly, :participantLimit, :moderated, :title, :description, :persistent, :private, :passwordProtected, :password, :passwordHint, :canUpdateConfiguration, :subject, :actor, :timestamp, :joined, :selfRoles)");
> +        query.bindValue(":accountId", accountId);
> +        query.bindValue(":threadId", threadId);
> +        query.bindValue(":type", (int) type);
> +        query.bindValue(":roomName", chatRoomInfo["RoomName"].toString());
> +        query.bindValue(":server", chatRoomInfo["Server"].toString());
> +        query.bindValue(":creator", chatRoomInfo["Creator"].toString());
> +        query.bindValue(":creationTimestamp", creationTimestamp.toUTC().toString(timestampFormat));
> +        query.bindValue(":anonymous", chatRoomInfo["Anonymous"].toBool());
> +        query.bindValue(":inviteOnly", chatRoomInfo["InviteOnly"].toBool());
> +        query.bindValue(":participantLimit", chatRoomInfo["Limit"].toInt());
> +        query.bindValue(":moderated", chatRoomInfo["Moderated"].toBool());
> +        query.bindValue(":title", chatRoomInfo["Title"].toString());
> +        query.bindValue(":description", chatRoomInfo["Description"].toString());
> +        query.bindValue(":persistent", chatRoomInfo["Persistent"].toBool());
> +        query.bindValue(":private", chatRoomInfo["Private"].toBool());
> +        query.bindValue(":passwordProtected", chatRoomInfo["PasswordProtected"].toBool());
> +        query.bindValue(":password", chatRoomInfo["Password"].toString());
> +        query.bindValue(":passwordHint", chatRoomInfo["PasswordHint"].toString());
> +        query.bindValue(":canUpdateConfiguration", chatRoomInfo["CanUpdateConfiguration"].toBool());
> +        query.bindValue(":subject", chatRoomInfo["Subject"].toString());
> +        query.bindValue(":actor", chatRoomInfo["Actor"].toString());
> +        query.bindValue(":timestamp", timestamp.toUTC().toString(timestampFormat));
> +        query.bindValue(":joined", chatRoomInfo["Joined"].toBool());
> +        query.bindValue(":selfRoles", chatRoomInfo["SelfRoles"].toInt());
> +
> +        if (!query.exec()) {
> +            qCritical() << "Error:" << query.lastError() << query.lastQuery();
> +            SQLiteDatabase::instance()->rollbackTransaction();
> +            return QVariantMap();
> +        }
> +        for (QVariantMap::iterator iter = chatRoomInfo.begin(); iter != chatRoomInfo.end();) {
> +            if (!iter.value().isValid()) {
> +                iter = chatRoomInfo.erase(iter);
> +            } else {
> +                iter++;
> +            }
> +        }
> +        thread[History::FieldChatRoomInfo] = chatRoomInfo;
> +    } else {
> +        threadId = participants.identifiers().join("%");
> +    }
>  
>      QSqlQuery query(SQLiteDatabase::instance()->database());
> -    query.prepare("INSERT INTO threads (accountId, threadId, type, count, unreadCount)"
> -                  "VALUES (:accountId, :threadId, :type, :count, :unreadCount)");
> +    query.prepare("INSERT INTO threads (accountId, threadId, type, count, unreadCount, chatType, lastEventTimestamp)"
> +                  "VALUES (:accountId, :threadId, :type, :count, :unreadCount, :chatType, :lastEventTimestamp)");
>      query.bindValue(":accountId", accountId);
>      query.bindValue(":threadId", threadId);
>      query.bindValue(":type", (int) type);
>      query.bindValue(":count", 0);
>      query.bindValue(":unreadCount", 0);
> +    query.bindValue(":chatType", (int) chatType);
> +    // make sure threads are created with an up-to-date timestamp
> +    query.bindValue(":lastEventTimestamp", QDateTime::currentDateTimeUtc().toString(timestampFormat));
>      if (!query.exec()) {
>          qCritical() << "Error:" << query.lastError() << query.lastQuery();
> +        SQLiteDatabase::instance()->rollbackTransaction();
>          return QVariantMap();
>      }
>  
>      // and insert the participants
> -    Q_FOREACH(const QString &participant, participants) {
> -        query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId)"
> -                      "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId)");
> +    Q_FOREACH(const History::Participant &participant, participants) {
> +        query.prepare("INSERT INTO thread_participants (accountId, threadId, type, participantId, normalizedId, alias, state, roles)"
> +                      "VALUES (:accountId, :threadId, :type, :participantId, :normalizedId, :alias, :state, :roles)");
>          query.bindValue(":accountId", accountId);
>          query.bindValue(":threadId", threadId);
>          query.bindValue(":type", type);
> -        query.bindValue(":participantId", participant);
> -        query.bindValue(":normalizedId", History::Utils::normalizeId(accountId, participant));
> +        query.bindValue(":participantId", participant.identifier());
> +        query.bindValue(":normalizedId", History::Utils::normalizeId(accountId, participant.identifier()));
> +        query.bindValue(":alias", participant.alias());
> +        query.bindValue(":state", participant.state());
> +        query.bindValue(":roles", participant.roles());
>          if (!query.exec()) {
>              qCritical() << "Error:" << query.lastError() << query.lastQuery();
> +            SQLiteDatabase::instance()->rollbackTransaction();
>              return QVariantMap();
>          }
>      }
>  
> +    if (!SQLiteDatabase::instance()->finishTransaction()) {
> +        qCritical() << "Failed to commit the transaction.";
> +        return QVariantMap();
> +    }
> +
>      // and finally create the thread
>      thread[History::FieldAccountId] = accountId;
>      thread[History::FieldThreadId] = threadId;
>      thread[History::FieldType] = (int) type;
> -    thread[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
> +    QVariantList contactList;
> +    int count = 0;
> +    Q_FOREACH (QVariant contact, History::ContactMatcher::instance()->contactInfo(accountId, participants.identifiers(), true)) {
> +        QVariantMap map = contact.toMap();
> +        map["state"] = participants.at(count).state();
> +        map["roles"] = participants.at(count++).roles();
> +        contactList << map;
> +    }

same as before, i prefer using classic for (int i = 0... etc. But up to you to change it or not.
In any case, if you leave the code as is, I would set count++ in a single line:
 map["state"] = participants.at(count).state();
 map["roles"] = participants.at(count).roles();
 ++count;

> +    thread[History::FieldParticipants] = contactList;
>      thread[History::FieldCount] = 0;
>      thread[History::FieldUnreadCount] = 0;
> +    thread[History::FieldChatType] = (int)chatType;
>  
>      addThreadsToCache(QList<QVariantMap>() << thread);
>  
>      return thread;
>  }
>  
> +// Writer
> +QVariantMap SQLiteHistoryPlugin::createThreadForParticipants(const QString &accountId, History::EventType type, const QStringList &participants)
> +{
> +    QVariantMap properties;
> +    properties[History::FieldParticipantIds] = participants;
> +    return createThreadForProperties(accountId, type, properties);
> +}
> +
>  bool SQLiteHistoryPlugin::removeThread(const QVariantMap &thread)
>  {
>      QSqlQuery query(SQLiteDatabase::instance()->database());
> @@ -820,13 +1165,31 @@
>          thread[History::FieldEventId] = query.value(2);
>          thread[History::FieldCount] = query.value(3);
>          thread[History::FieldUnreadCount] = query.value(4);
> -        QStringList participants = query.value(5).toString().split("|,|");
> -        thread[History::FieldParticipants] = History::ContactMatcher::instance()->contactInfo(accountId, participants, true);
> +        QStringList participants = query.value(6).toString().split("|,|", QString::SkipEmptyParts);
> +        QList<int> participantStatus;
> +        QStringList participantStatusString = query.value(7).toString().split("|,|", QString::SkipEmptyParts);
> +        Q_FOREACH(const QString &statusString, participantStatusString) {
> +            participantStatus << statusString.toUInt();
> +        }
> +        QStringList participantRolesString = query.value(8).toString().split("|,|", QString::SkipEmptyParts);
> +        QList<int> participantRoles;
> +        Q_FOREACH(const QString &rolesString, participantRolesString) {
> +            participantRoles << rolesString.toUInt();
> +        }
> +        QVariantList contactList;
> +        int count = 0;
> +        Q_FOREACH (QVariant contact, History::ContactMatcher::instance()->contactInfo(accountId, participants, true)) {
> +            QVariantMap map = contact.toMap();
> +            map["state"] = participantStatus.at(count);
> +            map["roles"] = participantRoles.at(count++);

use classic for or count++ in a separated line

> +            contactList << map;
> +        }
> +        thread[History::FieldParticipants] = contactList;
>  
>          // the generic event fields
> -        thread[History::FieldSenderId] = query.value(6);
> -        thread[History::FieldTimestamp] = toLocalTimeString(query.value(7).toDateTime());
> -        thread[History::FieldNewEvent] = query.value(8).toBool();
> +        thread[History::FieldSenderId] = query.value(9);
> +        thread[History::FieldTimestamp] = toLocalTimeString(query.value(5).toDateTime());
> +        thread[History::FieldNewEvent] = query.value(10).toBool();
>  
>          // the next step is to get the last event
>          switch (type) {


-- 
https://code.launchpad.net/~phablet-team/history-service/staging/+merge/308933
Your team Ubuntu Phablet Team is subscribed to branch lp:history-service.



More information about the Ubuntu-reviews mailing list