<commit_before>/*
    Copyright (C) 2016  Martin Klapetek <mklapetek@kde.org>

    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 Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "mainlogmodel.h"

#include <QSqlQueryModel>
#include <QSqlRecord>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlDatabase>
#include <QStandardPaths>

#include <TelepathyQt/AccountManager>
#include <TelepathyQt/PendingOperation>
#include <TelepathyQt/PendingChannel>
#include <TelepathyQt/TextChannel>
#include <TelepathyQt/Channel>
#include <TelepathyQt/ChannelClassSpecList>

#include <KPeople/PersonData>

#include "conversation.h"

#include <QDebug>

static inline Tp::ChannelClassSpecList channelClassList()
{
    return Tp::ChannelClassSpecList() << Tp::ChannelClassSpec::textChat();
}

ObserverProxy::ObserverProxy(MainLogModel *model)
    : QObject(model),
      Tp::AbstractClientObserver(channelClassList(), true),
      m_model(model)
{

}

void ObserverProxy::observeChannels(const Tp::MethodInvocationContextPtr<> &context,
                                    const Tp::AccountPtr &account,
                                    const Tp::ConnectionPtr &connection,
                                    const QList<Tp::ChannelPtr> &channels,
                                    const Tp::ChannelDispatchOperationPtr &dispatchOperation,
                                    const QList<Tp::ChannelRequestPtr> &requestsSatisfied,
                                    const Tp::AbstractClientObserver::ObserverInfo &observerInfo)
{
    Q_UNUSED(context)
    Q_UNUSED(connection)
    Q_UNUSED(requestsSatisfied)
    Q_UNUSED(observerInfo)

    Q_FOREACH(const Tp::ChannelPtr &channel, channels) {
        Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel);
        if (textChannel) {
            textChannel.data()->setProperty("dispatchOperation", QVariant::fromValue(dispatchOperation));
            m_model->handleChannel(account, textChannel);
        }
    }
}

// -----------------------------------------------------------------------

MainLogModel::MainLogModel(QObject *parent)
    : QAbstractListModel(parent),
      Tp::AbstractClientHandler(channelClassList()),
      m_observerProxy(new ObserverProxy(this))
{
    QCommandLineParser parser;
    parser.process(qApp->arguments());

    m_openIncomingChannel = parser.isSet(QStringLiteral("openIncomingChannel"));

    const QString dbLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/ktp-mobile-logger/");

    connect(qApp, &QCoreApplication::aboutToQuit, this, [=]() {
        Q_FOREACH (Conversation *c, m_conversations.values()) {
            if (!c->textChannel().isNull()) {
                c->textChannel()->requestClose();
            }
        }
    });

    m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("logger-db"));
    m_db.setDatabaseName(dbLocation + QStringLiteral("history.db3"));
    qDebug() << dbLocation << m_db.open();
    m_query = QSqlQuery(QStringLiteral("SELECT data.messageDateTime, data.message, "
                                       "accountData.accountObjectPath, contactData.targetContact "
                                       "FROM data LEFT JOIN contactData ON data.targetContactId = contactData.id "
                                       "LEFT JOIN accountData ON data.accountId = accountData.id "
                                       "GROUP BY data.targetContactId ORDER BY data.messageDateTime DESC"),
                        m_db);

    m_query.exec();

    // The query results are processed as soon as AccountManager
    // is passsed to MainLogModel
}

MainLogModel::~MainLogModel()
{

}

void MainLogModel::processQueryResults(QSqlQuery query)
{
    while (query.next()) {
        LogItem item;
        item.messageDateTime = query.value(QStringLiteral("messageDateTime")).toDateTime();
        item.message = query.value(QStringLiteral("message")).toString();
        item.accountObjectPath = query.value(QStringLiteral("accountObjectPath")).toString();
        item.targetContact = query.value(QStringLiteral("targetContact")).toString();

        const QString accountObjectPath = item.accountObjectPath.mid(35);
        item.conversation = new Conversation(item.targetContact, m_accountManager->accountForObjectPath(item.accountObjectPath), this);

        m_conversations.insert(accountObjectPath + item.targetContact, item.conversation);
        setupSignals(item.conversation);

        //TODO: This might be more effective to insert at once?
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_logItems << item;
        endInsertRows();
    }
}

QVariant MainLogModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    const int row = index.row();

    switch (role) {
        case MainLogModel::ContactIdRole:
            return m_logItems.at(row).targetContact;
        case MainLogModel::AccountIdRole:
            return m_logItems.at(row).accountObjectPath.mid(35);
        case MainLogModel::LastMessageDateRole:
        case MainLogModel::LastMessageTextRole:
        case MainLogModel::ConversationRole:
        case MainLogModel::HasUnreadMessagesRole:
        case MainLogModel::UnreadMessagesCountRole:
        case MainLogModel::ContactDisplayNameRole:
        case MainLogModel::PersonUriRole:
        {
            if (role == MainLogModel::ConversationRole) {
                return QVariant::fromValue(m_logItems.at(row).conversation);
            }

            const Conversation *conversation = m_logItems.at(row).conversation;

            if (!conversation->personData()->isValid()) {
                if (role == MainLogModel::PersonUriRole || role == MainLogModel::ContactDisplayNameRole) {
                    return QVariant();
                }
            } else {
                if (role == MainLogModel::PersonUriRole) {
                    return conversation->personData()->personUri();
                } else if (role == MainLogModel::ContactDisplayNameRole) {
                    return conversation->personData()->name();
                }
            }

            if (!conversation->isValid()) {
                if (role == MainLogModel::HasUnreadMessagesRole) {
                    return false;
                } else if (role == MainLogModel::UnreadMessagesCountRole) {
                    // TODO this needs to be replaced once the persistent
                    //      unread count is done
                    return 0;
                } else if (role == MainLogModel::LastMessageDateRole) {
                    return m_logItems.at(row).messageDateTime;
                } else if (role == MainLogModel::LastMessageTextRole) {
                    return m_logItems.at(row).message;
                }
            } else {
                if (role == MainLogModel::HasUnreadMessagesRole) {
                    return conversation->hasUnreadMessages();
                } else if (role == MainLogModel::UnreadMessagesCountRole) {
                    return conversation->messages()->unreadCount();
                } else if (role == MainLogModel::LastMessageDateRole) {
                    return conversation->messages()->lastMessageDateTime();
                } else if (role == MainLogModel::LastMessageTextRole) {
                    return conversation->messages()->lastMessage();
                }
            }
        }
    }

    return QVariant();
}

QVariant MainLogModel::data(int index, QByteArray role) const
{
    return data(createIndex(index, 0), roleNames().key(role));
}

int MainLogModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)

    return m_logItems.size();
}

QHash<int, QByteArray> MainLogModel::roleNames() const
{
    QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();

    roles.insert(ContactDisplayNameRole, "contactDisplayName");
    roles.insert(ContactIdRole, "contactId");
    roles.insert(PersonUriRole, "personUri");
    roles.insert(AccountIdRole, "accountId");
    roles.insert(LastMessageDateRole, "lastMessageDate");
    roles.insert(LastMessageTextRole, "lastMessageText");
    roles.insert(ConversationRole, "conversation");
    roles.insert(HasUnreadMessagesRole, "hasUnreadMessages");
    roles.insert(UnreadMessagesCountRole, "unreadMessagesCount");

    return roles;
}

bool MainLogModel::canChat(const QString &accountId) const
{
    if (m_accountManager.isNull()) {
        return false;
    }

    const QString objectPath = TP_QT_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountId;
    const Tp::AccountPtr account = m_accountManager->accountForObjectPath(objectPath);

    if (!account.isNull() && account->currentPresence().type() != Tp::ConnectionPresenceTypeOffline) {
        return true;
    }

    return false;
}

void MainLogModel::startChat(const QString &accountId, const QString &contactId)
{
    const QString objectPath = TP_QT_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountId;
    const Tp::AccountPtr account = m_accountManager->accountForObjectPath(objectPath);

    if (account.isNull()) {
        qWarning() << "Cannot get account for" << accountId;
    }

    if (m_conversations.contains(accountId + contactId)) {
        Conversation *conversation = m_conversations.value(accountId + contactId);
        if (conversation->isValid() && !conversation->textChannel().isNull()) {
            Tp::ChannelDispatchOperationPtr dispatch = conversation->textChannel().data()->property("dispatchOperation").value<Tp::ChannelDispatchOperationPtr>();

            if (!dispatch.isNull()) {
                // Claim the channel that is being observed
                dispatch->claim();
            }
            // We already have a conversation, don't request new channel
            return;
        }
    }

    Tp::PendingChannel *pendingChannel = account->ensureAndHandleTextChat(contactId);
    connect(pendingChannel, &Tp::PendingChannel::finished, [=](Tp::PendingOperation *op) {
        if (op->isError()) {
            qWarning() << "Requesting text channel failed:" << op->errorName() << op->errorMessage();
            return;
        }

        Tp::PendingChannel *pc = qobject_cast<Tp::PendingChannel*>(op);
        if (pc) {
            Tp::TextChannel *channel = qobject_cast<Tp::TextChannel*>(pc->channel().data());
            handleChannel(account, Tp::TextChannelPtr(channel));
        }
    });
}

void MainLogModel::setAccountManager(const Tp::AccountManagerPtr &accountManager)
{
    m_accountManager = accountManager;

    processQueryResults(m_query);
}

QObject* MainLogModel::observerProxy() const
{
    return m_observerProxy;
}

void MainLogModel::handleChannels(const Tp::MethodInvocationContextPtr<> &context,
                                        const Tp::AccountPtr &account,
                                        const Tp::ConnectionPtr &connection,
                                        const QList<Tp::ChannelPtr> &channels,
                                        const QList<Tp::ChannelRequestPtr> &channelRequests,
                                        const QDateTime &userActionTime,
                                        const HandlerInfo &handlerInfo)
{
    Q_UNUSED(connection);
    Q_UNUSED(channelRequests);
    Q_UNUSED(userActionTime);
    Q_UNUSED(handlerInfo);

    //check that the channel is of type text
    Tp::TextChannelPtr textChannel;
    Q_FOREACH (const Tp::ChannelPtr &channel, channels) {
        textChannel = Tp::TextChannelPtr::dynamicCast(channel);
        if (textChannel) {
            break;
        }
    }

    Q_ASSERT(textChannel);

    const QString targetContact = textChannel->targetContact()->id();
    const QString accountObjectPath = account->objectPath();

    bool existsInModel = false;
    Q_FOREACH (const LogItem &item, m_logItems) {
        if (item.targetContact == targetContact && item.accountObjectPath == accountObjectPath) {
            existsInModel = true;
            break;
        }
    }

    if (!existsInModel) {
        QSqlQuery q(m_db);
        q.prepare(QStringLiteral("SELECT data.messageDateTime, data.message, "
                                 "accountData.accountObjectPath, contactData.targetContact "
                                 "FROM data LEFT JOIN contactData ON data.targetContactId = contactData.id "
                                 "LEFT JOIN accountData ON data.accountId = accountData.id "
                                 "WHERE contactData.targetContact = :contactId AND accountData.accountObjectPath = :accountObjectPath "
                                 "GROUP BY data.targetContactId ORDER BY data.messageDateTime DESC"));
        q.bindValue(QStringLiteral(":contactId"), targetContact);
        q.bindValue(QStringLiteral(":accountObjectPath"), accountObjectPath);

        q.exec();

        if (q.lastError().isValid()) {
            qWarning() << "Error selecting latest conversation from log database:" << q.lastError().text();
        }

        processQueryResults(q);
    }

    handleChannel(account, textChannel);
    context->setFinished();
}

bool MainLogModel::bypassApproval() const
{
    return true;
}

void MainLogModel::handleChannel(const Tp::AccountPtr &account, const Tp::TextChannelPtr &channel)
{
    if (channel && account) {
        const QString accountId = account->objectPath().mid(35);
        const QString contactId = channel->targetContact()->id();
        qDebug() << accountId << contactId;

        int i = 0;
        for (i = 0; i < m_logItems.size(); i++) {
            LogItem &item = m_logItems[i];
            if (item.targetContact == contactId && item.accountObjectPath == account->objectPath()) {
                // Don't set the same channel again
                if (item.conversation->textChannel() == channel) {
                    return;
                }

                item.conversation->setTextChannel(channel);
                break;
            }
        }

        QModelIndex contactIndex = createIndex(i, 0);

        // No existing log item was found and it's a local request,
        // do a model insert
        if (i == m_logItems.size()) {
            LogItem item;
            item.targetContact = contactId;
            item.accountObjectPath = account->objectPath();

            Conversation *conversation = new Conversation(contactId, account, this);
            item.conversation = conversation;
            setupSignals(conversation);
            m_conversations.insert(accountId + contactId, conversation);

            conversation->setTextChannel(channel);

            beginInsertRows(QModelIndex(), m_logItems.size(), m_logItems.size());
            m_logItems << item;
            endInsertRows();
        } else {
            if (contactIndex.isValid()) {
                Q_EMIT dataChanged(contactIndex, contactIndex);
            }
        }

        if (channel->isRequested() || m_openIncomingChannel) {
            Q_EMIT newRequestedChannel(contactIndex);
            m_openIncomingChannel = false;
        }
    }
}

void MainLogModel::setupSignals(Conversation *conversation) const
{
    connect(conversation, &Conversation::unreadMessagesChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::avatarChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::presenceIconChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::titleChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::validityChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::lastMessageChanged, this, &MainLogModel::onConversationChanged);
}

void MainLogModel::onConversationChanged()
{
    Conversation *conversation = qobject_cast<Conversation*>(sender());
    if (!conversation || !conversation->isValid()) {
        return;
    }

    int i = 0;
    for (i = 0; i < m_logItems.size(); i++) {
        if (m_logItems.at(i).conversation == conversation) {
            break;
        }
    }

    const QModelIndex index = createIndex(i, 0);

    if (index.isValid()) {
        Q_EMIT dataChanged(index, index);
    }
}
<commit_msg>MainLogModel: Fix a crash if can't get account on start chat<commit_after>/*
    Copyright (C) 2016  Martin Klapetek <mklapetek@kde.org>

    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 Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "mainlogmodel.h"

#include <QSqlQueryModel>
#include <QSqlRecord>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlDatabase>
#include <QStandardPaths>

#include <TelepathyQt/AccountManager>
#include <TelepathyQt/PendingOperation>
#include <TelepathyQt/PendingChannel>
#include <TelepathyQt/TextChannel>
#include <TelepathyQt/Channel>
#include <TelepathyQt/ChannelClassSpecList>

#include <KPeople/PersonData>

#include "conversation.h"

#include <QDebug>

static inline Tp::ChannelClassSpecList channelClassList()
{
    return Tp::ChannelClassSpecList() << Tp::ChannelClassSpec::textChat();
}

ObserverProxy::ObserverProxy(MainLogModel *model)
    : QObject(model),
      Tp::AbstractClientObserver(channelClassList(), true),
      m_model(model)
{

}

void ObserverProxy::observeChannels(const Tp::MethodInvocationContextPtr<> &context,
                                    const Tp::AccountPtr &account,
                                    const Tp::ConnectionPtr &connection,
                                    const QList<Tp::ChannelPtr> &channels,
                                    const Tp::ChannelDispatchOperationPtr &dispatchOperation,
                                    const QList<Tp::ChannelRequestPtr> &requestsSatisfied,
                                    const Tp::AbstractClientObserver::ObserverInfo &observerInfo)
{
    Q_UNUSED(context)
    Q_UNUSED(connection)
    Q_UNUSED(requestsSatisfied)
    Q_UNUSED(observerInfo)

    Q_FOREACH(const Tp::ChannelPtr &channel, channels) {
        Tp::TextChannelPtr textChannel = Tp::TextChannelPtr::dynamicCast(channel);
        if (textChannel) {
            textChannel.data()->setProperty("dispatchOperation", QVariant::fromValue(dispatchOperation));
            m_model->handleChannel(account, textChannel);
        }
    }
}

// -----------------------------------------------------------------------

MainLogModel::MainLogModel(QObject *parent)
    : QAbstractListModel(parent),
      Tp::AbstractClientHandler(channelClassList()),
      m_observerProxy(new ObserverProxy(this))
{
    QCommandLineParser parser;
    parser.process(qApp->arguments());

    m_openIncomingChannel = parser.isSet(QStringLiteral("openIncomingChannel"));

    const QString dbLocation = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/ktp-mobile-logger/");

    connect(qApp, &QCoreApplication::aboutToQuit, this, [=]() {
        Q_FOREACH (Conversation *c, m_conversations.values()) {
            if (!c->textChannel().isNull()) {
                c->textChannel()->requestClose();
            }
        }
    });

    m_db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), QStringLiteral("logger-db"));
    m_db.setDatabaseName(dbLocation + QStringLiteral("history.db3"));
    qDebug() << dbLocation << m_db.open();
    m_query = QSqlQuery(QStringLiteral("SELECT data.messageDateTime, data.message, "
                                       "accountData.accountObjectPath, contactData.targetContact "
                                       "FROM data LEFT JOIN contactData ON data.targetContactId = contactData.id "
                                       "LEFT JOIN accountData ON data.accountId = accountData.id "
                                       "GROUP BY data.targetContactId ORDER BY data.messageDateTime DESC"),
                        m_db);

    m_query.exec();

    // The query results are processed as soon as AccountManager
    // is passsed to MainLogModel
}

MainLogModel::~MainLogModel()
{

}

void MainLogModel::processQueryResults(QSqlQuery query)
{
    while (query.next()) {
        LogItem item;
        item.messageDateTime = query.value(QStringLiteral("messageDateTime")).toDateTime();
        item.message = query.value(QStringLiteral("message")).toString();
        item.accountObjectPath = query.value(QStringLiteral("accountObjectPath")).toString();
        item.targetContact = query.value(QStringLiteral("targetContact")).toString();

        const QString accountObjectPath = item.accountObjectPath.mid(35);
        item.conversation = new Conversation(item.targetContact, m_accountManager->accountForObjectPath(item.accountObjectPath), this);

        m_conversations.insert(accountObjectPath + item.targetContact, item.conversation);
        setupSignals(item.conversation);

        //TODO: This might be more effective to insert at once?
        beginInsertRows(QModelIndex(), rowCount(), rowCount());
        m_logItems << item;
        endInsertRows();
    }
}

QVariant MainLogModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid()) {
        return QVariant();
    }

    const int row = index.row();

    switch (role) {
        case MainLogModel::ContactIdRole:
            return m_logItems.at(row).targetContact;
        case MainLogModel::AccountIdRole:
            return m_logItems.at(row).accountObjectPath.mid(35);
        case MainLogModel::LastMessageDateRole:
        case MainLogModel::LastMessageTextRole:
        case MainLogModel::ConversationRole:
        case MainLogModel::HasUnreadMessagesRole:
        case MainLogModel::UnreadMessagesCountRole:
        case MainLogModel::ContactDisplayNameRole:
        case MainLogModel::PersonUriRole:
        {
            if (role == MainLogModel::ConversationRole) {
                return QVariant::fromValue(m_logItems.at(row).conversation);
            }

            const Conversation *conversation = m_logItems.at(row).conversation;

            if (!conversation->personData()->isValid()) {
                if (role == MainLogModel::PersonUriRole || role == MainLogModel::ContactDisplayNameRole) {
                    return QVariant();
                }
            } else {
                if (role == MainLogModel::PersonUriRole) {
                    return conversation->personData()->personUri();
                } else if (role == MainLogModel::ContactDisplayNameRole) {
                    return conversation->personData()->name();
                }
            }

            if (!conversation->isValid()) {
                if (role == MainLogModel::HasUnreadMessagesRole) {
                    return false;
                } else if (role == MainLogModel::UnreadMessagesCountRole) {
                    // TODO this needs to be replaced once the persistent
                    //      unread count is done
                    return 0;
                } else if (role == MainLogModel::LastMessageDateRole) {
                    return m_logItems.at(row).messageDateTime;
                } else if (role == MainLogModel::LastMessageTextRole) {
                    return m_logItems.at(row).message;
                }
            } else {
                if (role == MainLogModel::HasUnreadMessagesRole) {
                    return conversation->hasUnreadMessages();
                } else if (role == MainLogModel::UnreadMessagesCountRole) {
                    return conversation->messages()->unreadCount();
                } else if (role == MainLogModel::LastMessageDateRole) {
                    return conversation->messages()->lastMessageDateTime();
                } else if (role == MainLogModel::LastMessageTextRole) {
                    return conversation->messages()->lastMessage();
                }
            }
        }
    }

    return QVariant();
}

QVariant MainLogModel::data(int index, QByteArray role) const
{
    return data(createIndex(index, 0), roleNames().key(role));
}

int MainLogModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)

    return m_logItems.size();
}

QHash<int, QByteArray> MainLogModel::roleNames() const
{
    QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();

    roles.insert(ContactDisplayNameRole, "contactDisplayName");
    roles.insert(ContactIdRole, "contactId");
    roles.insert(PersonUriRole, "personUri");
    roles.insert(AccountIdRole, "accountId");
    roles.insert(LastMessageDateRole, "lastMessageDate");
    roles.insert(LastMessageTextRole, "lastMessageText");
    roles.insert(ConversationRole, "conversation");
    roles.insert(HasUnreadMessagesRole, "hasUnreadMessages");
    roles.insert(UnreadMessagesCountRole, "unreadMessagesCount");

    return roles;
}

bool MainLogModel::canChat(const QString &accountId) const
{
    if (m_accountManager.isNull()) {
        return false;
    }

    const QString objectPath = TP_QT_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountId;
    const Tp::AccountPtr account = m_accountManager->accountForObjectPath(objectPath);

    if (!account.isNull() && account->currentPresence().type() != Tp::ConnectionPresenceTypeOffline) {
        return true;
    }

    return false;
}

void MainLogModel::startChat(const QString &accountId, const QString &contactId)
{
    const QString objectPath = TP_QT_ACCOUNT_OBJECT_PATH_BASE + QLatin1Char('/') + accountId;
    const Tp::AccountPtr account = m_accountManager->accountForObjectPath(objectPath);

    if (account.isNull()) {
        qWarning() << "Cannot get account for" << accountId;
        return;
    }

    if (m_conversations.contains(accountId + contactId)) {
        Conversation *conversation = m_conversations.value(accountId + contactId);
        if (conversation->isValid() && !conversation->textChannel().isNull()) {
            Tp::ChannelDispatchOperationPtr dispatch = conversation->textChannel().data()->property("dispatchOperation").value<Tp::ChannelDispatchOperationPtr>();

            if (!dispatch.isNull()) {
                // Claim the channel that is being observed
                dispatch->claim();
            }
            // We already have a conversation, don't request new channel
            return;
        }
    }

    Tp::PendingChannel *pendingChannel = account->ensureAndHandleTextChat(contactId);
    connect(pendingChannel, &Tp::PendingChannel::finished, [=](Tp::PendingOperation *op) {
        if (op->isError()) {
            qWarning() << "Requesting text channel failed:" << op->errorName() << op->errorMessage();
            return;
        }

        Tp::PendingChannel *pc = qobject_cast<Tp::PendingChannel*>(op);
        if (pc) {
            Tp::TextChannel *channel = qobject_cast<Tp::TextChannel*>(pc->channel().data());
            handleChannel(account, Tp::TextChannelPtr(channel));
        }
    });
}

void MainLogModel::setAccountManager(const Tp::AccountManagerPtr &accountManager)
{
    m_accountManager = accountManager;

    processQueryResults(m_query);
}

QObject* MainLogModel::observerProxy() const
{
    return m_observerProxy;
}

void MainLogModel::handleChannels(const Tp::MethodInvocationContextPtr<> &context,
                                        const Tp::AccountPtr &account,
                                        const Tp::ConnectionPtr &connection,
                                        const QList<Tp::ChannelPtr> &channels,
                                        const QList<Tp::ChannelRequestPtr> &channelRequests,
                                        const QDateTime &userActionTime,
                                        const HandlerInfo &handlerInfo)
{
    Q_UNUSED(connection);
    Q_UNUSED(channelRequests);
    Q_UNUSED(userActionTime);
    Q_UNUSED(handlerInfo);

    //check that the channel is of type text
    Tp::TextChannelPtr textChannel;
    Q_FOREACH (const Tp::ChannelPtr &channel, channels) {
        textChannel = Tp::TextChannelPtr::dynamicCast(channel);
        if (textChannel) {
            break;
        }
    }

    Q_ASSERT(textChannel);

    const QString targetContact = textChannel->targetContact()->id();
    const QString accountObjectPath = account->objectPath();

    bool existsInModel = false;
    Q_FOREACH (const LogItem &item, m_logItems) {
        if (item.targetContact == targetContact && item.accountObjectPath == accountObjectPath) {
            existsInModel = true;
            break;
        }
    }

    if (!existsInModel) {
        QSqlQuery q(m_db);
        q.prepare(QStringLiteral("SELECT data.messageDateTime, data.message, "
                                 "accountData.accountObjectPath, contactData.targetContact "
                                 "FROM data LEFT JOIN contactData ON data.targetContactId = contactData.id "
                                 "LEFT JOIN accountData ON data.accountId = accountData.id "
                                 "WHERE contactData.targetContact = :contactId AND accountData.accountObjectPath = :accountObjectPath "
                                 "GROUP BY data.targetContactId ORDER BY data.messageDateTime DESC"));
        q.bindValue(QStringLiteral(":contactId"), targetContact);
        q.bindValue(QStringLiteral(":accountObjectPath"), accountObjectPath);

        q.exec();

        if (q.lastError().isValid()) {
            qWarning() << "Error selecting latest conversation from log database:" << q.lastError().text();
        }

        processQueryResults(q);
    }

    handleChannel(account, textChannel);
    context->setFinished();
}

bool MainLogModel::bypassApproval() const
{
    return true;
}

void MainLogModel::handleChannel(const Tp::AccountPtr &account, const Tp::TextChannelPtr &channel)
{
    if (channel && account) {
        const QString accountId = account->objectPath().mid(35);
        const QString contactId = channel->targetContact()->id();
        qDebug() << accountId << contactId;

        int i = 0;
        for (i = 0; i < m_logItems.size(); i++) {
            LogItem &item = m_logItems[i];
            if (item.targetContact == contactId && item.accountObjectPath == account->objectPath()) {
                // Don't set the same channel again
                if (item.conversation->textChannel() == channel) {
                    return;
                }

                item.conversation->setTextChannel(channel);
                break;
            }
        }

        QModelIndex contactIndex = createIndex(i, 0);

        // No existing log item was found and it's a local request,
        // do a model insert
        if (i == m_logItems.size()) {
            LogItem item;
            item.targetContact = contactId;
            item.accountObjectPath = account->objectPath();

            Conversation *conversation = new Conversation(contactId, account, this);
            item.conversation = conversation;
            setupSignals(conversation);
            m_conversations.insert(accountId + contactId, conversation);

            conversation->setTextChannel(channel);

            beginInsertRows(QModelIndex(), m_logItems.size(), m_logItems.size());
            m_logItems << item;
            endInsertRows();
        } else {
            if (contactIndex.isValid()) {
                Q_EMIT dataChanged(contactIndex, contactIndex);
            }
        }

        if (channel->isRequested() || m_openIncomingChannel) {
            Q_EMIT newRequestedChannel(contactIndex);
            m_openIncomingChannel = false;
        }
    }
}

void MainLogModel::setupSignals(Conversation *conversation) const
{
    connect(conversation, &Conversation::unreadMessagesChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::avatarChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::presenceIconChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::titleChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::validityChanged, this, &MainLogModel::onConversationChanged);
    connect(conversation, &Conversation::lastMessageChanged, this, &MainLogModel::onConversationChanged);
}

void MainLogModel::onConversationChanged()
{
    Conversation *conversation = qobject_cast<Conversation*>(sender());
    if (!conversation || !conversation->isValid()) {
        return;
    }

    int i = 0;
    for (i = 0; i < m_logItems.size(); i++) {
        if (m_logItems.at(i).conversation == conversation) {
            break;
        }
    }

    const QModelIndex index = createIndex(i, 0);

    if (index.isValid()) {
        Q_EMIT dataChanged(index, index);
    }
}
<|endoftext|>


<commit_before>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkSpherePuzzle.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkSpherePuzzle.h"

#include "vtkAppendPolyData.h"
#include "vtkLinearExtrusionFilter.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSphereSource.h"
#include "vtkTransform.h"
#include "vtkTransformFilter.h"

#include <math.h>

vtkCxxRevisionMacro(vtkSpherePuzzle, "1.13");
vtkStandardNewMacro(vtkSpherePuzzle);

//----------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzle::vtkSpherePuzzle()
{
  this->Transform = vtkTransform::New();
  this->Reset();
  this->Active = 0;
}

//----------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzle::~vtkSpherePuzzle()
{
  this->Transform->Delete();
  this->Transform = NULL;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::Reset()
{
  int idx;

  this->Modified();
  for (idx = 0; idx < 32; ++idx)
    {
    this->State[idx] = idx;
    this->PieceMask[idx] = 0;
    }
  this->Transform->Identity();
  for (idx = 0; idx < 4; ++idx)
    {
    this->Colors[0 + idx*8*3] = 255;
    this->Colors[1 + idx*8*3] = 0;
    this->Colors[2 + idx*8*3] = 0;

    this->Colors[3 + idx*8*3] = 255;
    this->Colors[4 + idx*8*3] = 175;
    this->Colors[5 + idx*8*3] = 0;

    this->Colors[6 + idx*8*3] = 255;
    this->Colors[7 + idx*8*3] = 255;
    this->Colors[8 + idx*8*3] = 0;

    this->Colors[9 + idx*8*3] = 0;
    this->Colors[10 + idx*8*3] = 255;
    this->Colors[11 + idx*8*3] = 0;

    this->Colors[12 + idx*8*3] = 0;
    this->Colors[13 + idx*8*3] = 255;
    this->Colors[14 + idx*8*3] = 255;

    this->Colors[15 + idx*8*3] = 0;
    this->Colors[16 + idx*8*3] = 0;
    this->Colors[17 + idx*8*3] = 255;

    this->Colors[18 + idx*8*3] = 175;
    this->Colors[19 + idx*8*3] = 0;
    this->Colors[20 + idx*8*3] = 255;

    this->Colors[21 + idx*8*3] = 255;
    this->Colors[22 + idx*8*3] = 50;
    this->Colors[23 + idx*8*3] = 150;
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::Execute()
{
  int i, j, k, num;
  int color;
  vtkAppendPolyData *append = vtkAppendPolyData::New();
  vtkSphereSource *sphere = vtkSphereSource::New();
  vtkTransformFilter *tf = vtkTransformFilter::New();
  vtkUnsignedCharArray *scalars = vtkUnsignedCharArray::New();
  vtkPolyData *tmp;
  vtkPolyData *originalSphereOutput;
  int count = 0;
  unsigned char r, g, b;

  scalars->SetNumberOfComponents(3);

  sphere->SetPhiResolution(4);
  sphere->SetThetaResolution(4);

  tf->SetTransform(this->Transform);
  tf->SetInput(sphere->GetOutput());
  originalSphereOutput = sphere->GetOutput();

  for (j = 0; j < 4; ++j)
    {
    for (i = 0; i < 8; ++i)
      {
      color = this->State[count] * 3;
      sphere->SetStartTheta((360.0 * (double)(i) / 8.0));
      sphere->SetEndTheta((360.0 * (double)(i+1) / 8.0));
      sphere->SetStartPhi((180.0 * (double)(j) / 4.0));
      sphere->SetEndPhi((180.0 * (double)(j+1) / 4.0));
      tmp = vtkPolyData::New();
      if (this->PieceMask[count])
        { // Spheres original output is transforms input. Put it back.
        sphere->SetOutput(originalSphereOutput);
        tf->SetOutput(tmp);
        tmp->Update();
        tf->SetOutput(NULL);
        }
      else
        { // Piece not involved in partial move. Just use the sphere.
        sphere->SetOutput(tmp);
        tmp->Update();
        sphere->SetOutput(NULL);
        }
      // Now create the colors for the faces.
      num = tmp->GetNumberOfPoints();
      for (k = 0; k < num; ++k)
        {
        r = this->Colors[color];
        g = this->Colors[color+1];
        b = this->Colors[color+2];
        // Lighten the active pieces
        if (this->Active && this->PieceMask[count])
          {
          r = r + (unsigned char)((255 - r) * 0.4);
          g = g + (unsigned char)((255 - g) * 0.4);
          b = b + (unsigned char)((255 - b) * 0.4);
          }
        scalars->InsertNextValue(r);
        scalars->InsertNextValue(g);
        scalars->InsertNextValue(b);
        }

      // append all the pieces.
      append->AddInput(tmp);
      tmp->Delete();
      ++count;
      }
    }

  append->Update();

  // Move the data to the output.
  tmp = this->GetOutput();
  tmp->CopyStructure(append->GetOutput());
  tmp->GetPointData()->PassData(append->GetOutput()->GetPointData());
  tmp->GetPointData()->SetScalars(scalars);

  sphere->Delete();
  scalars->Delete();
  append->Delete();
  tf->Delete();
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MarkHorizontal(int section)
{
  int i;

  for (i = 0; i < 32; ++i)
    {
    this->PieceMask[i] = 0;
    }
  // Find the start of the section.
  section = section * 8;
  for (i = 0; i < 8; ++i)
    {
    this->PieceMask[i+section] = 1;
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MarkVertical(int section)
{
  int i, j, offset;

  for (i = 0; i < 32; ++i)
    {
    this->PieceMask[i] = 1;
    }
  for (i = 0; i < 4; ++i)
    {
    offset = (section + i) % 8;
    for (j = 0; j < 4; ++j)
      {
      this->PieceMask[offset+(j*8)] = 0;
      }
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MoveHorizontal(int slab, int percentage, int rightFlag)
{
  int offset;
  int tmp;
  int i;

  this->Modified();

  // Clear out previous partial moves.
  this->Transform->Identity();
  this->MarkHorizontal(slab);

  // Move zero does nothing.
  if (percentage <= 0)
    {
    return;
    }

  // Offset is used to determine which pieces are involved.
  offset = slab * 8;

  // Move 100 percent changes state.
  if (percentage >= 100)
    { // Just do the state change.
    if (rightFlag)
      {
      tmp = this->State[offset+7];
      for (i = 7; i > 0; --i)
        {
        this->State[i+offset] = this->State[i-1+offset];
        }
      this->State[offset] = tmp;
      }
    else
      {
      tmp = this->State[offset];
      for (i = 0; i < 7; ++i)
        {
        this->State[i+offset] = this->State[i+1+offset];
        }
      this->State[offset+7] = tmp;
      }
    return;
    }

  // Partial move.
  // This does not change the state.  It is ust for animating
  // the move.
  // Setup the pieces that are involved in the move.
  if ( ! rightFlag)
    {
    percentage = -percentage;
    }
  this->Transform->RotateZ(((double)(percentage) / 100.0)
                           * (360.0 / 8.0) );
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MoveVertical(int half, int percentage, int rightFlag)
{
  int tmp;
  int off0, off1, off2, off3;
  double theta;

  this->Modified();

  // Clear out previous partial moves.
  this->Transform->Identity();
  this->MarkVertical(half);

  // Move zero does nothing.
  if (percentage <= 0)
    {
    return;
    }

  off0 = (4+half) % 8;
  off1 = (5+half) % 8;
  off2 = (6+half) % 8;
  off3 = (7+half) % 8;

  // Move 100 percent changes state.
  if (percentage >= 100)
    { // Just do the state change.
    tmp = this->State[off0];
    this->State[off0] = this->State[24+off3];
    this->State[24+off3] = tmp;

    tmp = this->State[off1];
    this->State[off1] = this->State[24+off2];
    this->State[24+off2] = tmp;

    tmp = this->State[off2];
    this->State[off2] = this->State[24+off1];
    this->State[24+off1] = tmp;

    tmp = this->State[off3];
    this->State[off3] = this->State[24+off0];
    this->State[24+off0] = tmp;

    tmp = this->State[8+off0];
    this->State[8+off0] = this->State[16+off3];
    this->State[16+off3] = tmp;

    tmp = this->State[8+off1];
    this->State[8+off1] = this->State[16+off2];
    this->State[16+off2] = tmp;

    tmp = this->State[8+off2];
    this->State[8+off2] = this->State[16+off1];
    this->State[16+off1] = tmp;

    tmp = this->State[8+off3];
    this->State[8+off3] = this->State[16+off0];
    this->State[16+off0] = tmp;
    return;
    }

  // Partial move.
  // This does not change the state.  It is use for animating the move.
  if (rightFlag)
    {
    percentage = -percentage;
    }
  theta = (double)(half) * vtkMath::Pi() / 4.0;
  this->Transform->RotateWXYZ(((double)(percentage)/100.0)*(360.0/2.0),
                              sin(theta), -cos(theta), 0.0);

}

//----------------------------------------------------------------------------
int vtkSpherePuzzle::SetPoint(double x, double y, double z)
{
  double pt[3];
  double theta, phi;
  int xi, yi;
  double xp, yp;
  double xn, yn;

  this->Modified();

  if (x < 0.2 && x > -0.2 && y < 0.2 && y > -0.2 && z < 0.2 && z > -0.2)
    {
    this->Active = 0;
    return 0;
    }

  // normalize
  pt[0] = x;
  pt[1] = y;
  pt[2] = z;
  vtkMath::Normalize(pt);

  // Convert this into phi and theta.
  theta = 180.0 - atan2(pt[0], pt[1]) * 180 / vtkMath::Pi();
  phi = 90.0 - asin(pt[2]) * 180 / vtkMath::Pi();

  // Compute the piece the point is in.
  xi = (int)(theta * 8.0 / 360.0);
  yi = (int)(phi * 8 / 360.0);
  xn = (theta/(360.0/8.0)) - (double)(xi);
  yn = (phi/(360.0/8.0)) - (double)(yi);

  //vtkErrorMacro("point: " << x << ", " << y << ", " << z);
  //vtkErrorMacro("theta: " << theta << ",  phi: " << phi);
  //vtkErrorMacro("theta: " << xi << ", " << xn << ",  phi: " << yi << ", " << y);

  xp = 1.0 - xn;
  yp = 1.0 - yn;
  if (xn > 0.2 && xp > 0.2 && yn > 0.2 && yp > 0.2)
    { // Do nothing in the center of the face.
    this->Active = 0;
    return 0;
    }
  this->Active = 1;
  if (xn < xp && xn < yp && xn < yn)
    {
    this->VerticalFlag = 1;
    this->RightFlag = (yn < yp);
    this->Section = xi+2;
    this->MarkVertical(this->Section);
    return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
    }
  if (xp < xn && xp < yp && xp < yn)
    {
    this->VerticalFlag = 1;
    this->RightFlag = (yp < yn);
    this->Section = xi+7;
    this->MarkVertical(this->Section);
    return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
    }
  // The remaining options move a horizontal slab.
  this->VerticalFlag = 0;
  this->RightFlag = (xn > xp);
  this->Section = yi;
  this->MarkHorizontal(this->Section);
  return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MovePoint(int percentage)
{
  if ( ! this->Active)
    {
    return;
    }
  this->Modified();

  if (this->VerticalFlag)
    {
    this->MoveVertical(this->Section, percentage, this->RightFlag);
    }
  else
    {
    this->MoveHorizontal(this->Section, percentage, this->RightFlag);
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::PrintSelf(ostream& os, vtkIndent indent)
{
  int idx;
  this->Superclass::PrintSelf(os,indent);

  os << indent << "State: " << this->State[0];
  for (idx = 1; idx < 16; ++idx)
    {
    os << ", " << this->State[idx];
    }
  os << endl;
}

<commit_msg>ENH: Using deferred garbage collection to speed up execution.<commit_after>/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkSpherePuzzle.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkSpherePuzzle.h"

#include "vtkAppendPolyData.h"
#include "vtkGarbageCollector.h"
#include "vtkLinearExtrusionFilter.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSphereSource.h"
#include "vtkTransform.h"
#include "vtkTransformFilter.h"

#include <math.h>

vtkCxxRevisionMacro(vtkSpherePuzzle, "1.14");
vtkStandardNewMacro(vtkSpherePuzzle);

//----------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzle::vtkSpherePuzzle()
{
  this->Transform = vtkTransform::New();
  this->Reset();
  this->Active = 0;
}

//----------------------------------------------------------------------------
// Construct a new puzzle.
vtkSpherePuzzle::~vtkSpherePuzzle()
{
  this->Transform->Delete();
  this->Transform = NULL;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::Reset()
{
  int idx;

  this->Modified();
  for (idx = 0; idx < 32; ++idx)
    {
    this->State[idx] = idx;
    this->PieceMask[idx] = 0;
    }
  this->Transform->Identity();
  for (idx = 0; idx < 4; ++idx)
    {
    this->Colors[0 + idx*8*3] = 255;
    this->Colors[1 + idx*8*3] = 0;
    this->Colors[2 + idx*8*3] = 0;

    this->Colors[3 + idx*8*3] = 255;
    this->Colors[4 + idx*8*3] = 175;
    this->Colors[5 + idx*8*3] = 0;

    this->Colors[6 + idx*8*3] = 255;
    this->Colors[7 + idx*8*3] = 255;
    this->Colors[8 + idx*8*3] = 0;

    this->Colors[9 + idx*8*3] = 0;
    this->Colors[10 + idx*8*3] = 255;
    this->Colors[11 + idx*8*3] = 0;

    this->Colors[12 + idx*8*3] = 0;
    this->Colors[13 + idx*8*3] = 255;
    this->Colors[14 + idx*8*3] = 255;

    this->Colors[15 + idx*8*3] = 0;
    this->Colors[16 + idx*8*3] = 0;
    this->Colors[17 + idx*8*3] = 255;

    this->Colors[18 + idx*8*3] = 175;
    this->Colors[19 + idx*8*3] = 0;
    this->Colors[20 + idx*8*3] = 255;

    this->Colors[21 + idx*8*3] = 255;
    this->Colors[22 + idx*8*3] = 50;
    this->Colors[23 + idx*8*3] = 150;
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::Execute()
{
  // We are about to create/destroy alot of objects.  Defer garbage
  // collection until we are done.
  vtkGarbageCollector::DeferredCollectionPush();

  int i, j, k, num;
  int color;
  vtkAppendPolyData *append = vtkAppendPolyData::New();
  vtkSphereSource *sphere = vtkSphereSource::New();
  vtkTransformFilter *tf = vtkTransformFilter::New();
  vtkUnsignedCharArray *scalars = vtkUnsignedCharArray::New();
  vtkPolyData *tmp;
  vtkPolyData *originalSphereOutput;
  int count = 0;
  unsigned char r, g, b;

  scalars->SetNumberOfComponents(3);

  sphere->SetPhiResolution(4);
  sphere->SetThetaResolution(4);

  tf->SetTransform(this->Transform);
  tf->SetInput(sphere->GetOutput());
  originalSphereOutput = sphere->GetOutput();

  for (j = 0; j < 4; ++j)
    {
    for (i = 0; i < 8; ++i)
      {
      color = this->State[count] * 3;
      sphere->SetStartTheta((360.0 * (double)(i) / 8.0));
      sphere->SetEndTheta((360.0 * (double)(i+1) / 8.0));
      sphere->SetStartPhi((180.0 * (double)(j) / 4.0));
      sphere->SetEndPhi((180.0 * (double)(j+1) / 4.0));
      tmp = vtkPolyData::New();
      if (this->PieceMask[count])
        { // Spheres original output is transforms input. Put it back.
        sphere->SetOutput(originalSphereOutput);
        tf->SetOutput(tmp);
        tmp->Update();
        tf->SetOutput(NULL);
        }
      else
        { // Piece not involved in partial move. Just use the sphere.
        sphere->SetOutput(tmp);
        tmp->Update();
        sphere->SetOutput(NULL);
        }
      // Now create the colors for the faces.
      num = tmp->GetNumberOfPoints();
      for (k = 0; k < num; ++k)
        {
        r = this->Colors[color];
        g = this->Colors[color+1];
        b = this->Colors[color+2];
        // Lighten the active pieces
        if (this->Active && this->PieceMask[count])
          {
          r = r + (unsigned char)((255 - r) * 0.4);
          g = g + (unsigned char)((255 - g) * 0.4);
          b = b + (unsigned char)((255 - b) * 0.4);
          }
        scalars->InsertNextValue(r);
        scalars->InsertNextValue(g);
        scalars->InsertNextValue(b);
        }

      // append all the pieces.
      append->AddInput(tmp);
      tmp->Delete();
      ++count;
      }
    }

  append->Update();

  // Move the data to the output.
  tmp = this->GetOutput();
  tmp->CopyStructure(append->GetOutput());
  tmp->GetPointData()->PassData(append->GetOutput()->GetPointData());
  tmp->GetPointData()->SetScalars(scalars);

  sphere->Delete();
  scalars->Delete();
  append->Delete();
  tf->Delete();

  // We are done creating/destroying objects.
  vtkGarbageCollector::DeferredCollectionPop();
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MarkHorizontal(int section)
{
  int i;

  for (i = 0; i < 32; ++i)
    {
    this->PieceMask[i] = 0;
    }
  // Find the start of the section.
  section = section * 8;
  for (i = 0; i < 8; ++i)
    {
    this->PieceMask[i+section] = 1;
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MarkVertical(int section)
{
  int i, j, offset;

  for (i = 0; i < 32; ++i)
    {
    this->PieceMask[i] = 1;
    }
  for (i = 0; i < 4; ++i)
    {
    offset = (section + i) % 8;
    for (j = 0; j < 4; ++j)
      {
      this->PieceMask[offset+(j*8)] = 0;
      }
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MoveHorizontal(int slab, int percentage, int rightFlag)
{
  int offset;
  int tmp;
  int i;

  this->Modified();

  // Clear out previous partial moves.
  this->Transform->Identity();
  this->MarkHorizontal(slab);

  // Move zero does nothing.
  if (percentage <= 0)
    {
    return;
    }

  // Offset is used to determine which pieces are involved.
  offset = slab * 8;

  // Move 100 percent changes state.
  if (percentage >= 100)
    { // Just do the state change.
    if (rightFlag)
      {
      tmp = this->State[offset+7];
      for (i = 7; i > 0; --i)
        {
        this->State[i+offset] = this->State[i-1+offset];
        }
      this->State[offset] = tmp;
      }
    else
      {
      tmp = this->State[offset];
      for (i = 0; i < 7; ++i)
        {
        this->State[i+offset] = this->State[i+1+offset];
        }
      this->State[offset+7] = tmp;
      }
    return;
    }

  // Partial move.
  // This does not change the state.  It is ust for animating
  // the move.
  // Setup the pieces that are involved in the move.
  if ( ! rightFlag)
    {
    percentage = -percentage;
    }
  this->Transform->RotateZ(((double)(percentage) / 100.0)
                           * (360.0 / 8.0) );
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MoveVertical(int half, int percentage, int rightFlag)
{
  int tmp;
  int off0, off1, off2, off3;
  double theta;

  this->Modified();

  // Clear out previous partial moves.
  this->Transform->Identity();
  this->MarkVertical(half);

  // Move zero does nothing.
  if (percentage <= 0)
    {
    return;
    }

  off0 = (4+half) % 8;
  off1 = (5+half) % 8;
  off2 = (6+half) % 8;
  off3 = (7+half) % 8;

  // Move 100 percent changes state.
  if (percentage >= 100)
    { // Just do the state change.
    tmp = this->State[off0];
    this->State[off0] = this->State[24+off3];
    this->State[24+off3] = tmp;

    tmp = this->State[off1];
    this->State[off1] = this->State[24+off2];
    this->State[24+off2] = tmp;

    tmp = this->State[off2];
    this->State[off2] = this->State[24+off1];
    this->State[24+off1] = tmp;

    tmp = this->State[off3];
    this->State[off3] = this->State[24+off0];
    this->State[24+off0] = tmp;

    tmp = this->State[8+off0];
    this->State[8+off0] = this->State[16+off3];
    this->State[16+off3] = tmp;

    tmp = this->State[8+off1];
    this->State[8+off1] = this->State[16+off2];
    this->State[16+off2] = tmp;

    tmp = this->State[8+off2];
    this->State[8+off2] = this->State[16+off1];
    this->State[16+off1] = tmp;

    tmp = this->State[8+off3];
    this->State[8+off3] = this->State[16+off0];
    this->State[16+off0] = tmp;
    return;
    }

  // Partial move.
  // This does not change the state.  It is use for animating the move.
  if (rightFlag)
    {
    percentage = -percentage;
    }
  theta = (double)(half) * vtkMath::Pi() / 4.0;
  this->Transform->RotateWXYZ(((double)(percentage)/100.0)*(360.0/2.0),
                              sin(theta), -cos(theta), 0.0);

}

//----------------------------------------------------------------------------
int vtkSpherePuzzle::SetPoint(double x, double y, double z)
{
  double pt[3];
  double theta, phi;
  int xi, yi;
  double xp, yp;
  double xn, yn;

  this->Modified();

  if (x < 0.2 && x > -0.2 && y < 0.2 && y > -0.2 && z < 0.2 && z > -0.2)
    {
    this->Active = 0;
    return 0;
    }

  // normalize
  pt[0] = x;
  pt[1] = y;
  pt[2] = z;
  vtkMath::Normalize(pt);

  // Convert this into phi and theta.
  theta = 180.0 - atan2(pt[0], pt[1]) * 180 / vtkMath::Pi();
  phi = 90.0 - asin(pt[2]) * 180 / vtkMath::Pi();

  // Compute the piece the point is in.
  xi = (int)(theta * 8.0 / 360.0);
  yi = (int)(phi * 8 / 360.0);
  xn = (theta/(360.0/8.0)) - (double)(xi);
  yn = (phi/(360.0/8.0)) - (double)(yi);

  //vtkErrorMacro("point: " << x << ", " << y << ", " << z);
  //vtkErrorMacro("theta: " << theta << ",  phi: " << phi);
  //vtkErrorMacro("theta: " << xi << ", " << xn << ",  phi: " << yi << ", " << y);

  xp = 1.0 - xn;
  yp = 1.0 - yn;
  if (xn > 0.2 && xp > 0.2 && yn > 0.2 && yp > 0.2)
    { // Do nothing in the center of the face.
    this->Active = 0;
    return 0;
    }
  this->Active = 1;
  if (xn < xp && xn < yp && xn < yn)
    {
    this->VerticalFlag = 1;
    this->RightFlag = (yn < yp);
    this->Section = xi+2;
    this->MarkVertical(this->Section);
    return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
    }
  if (xp < xn && xp < yp && xp < yn)
    {
    this->VerticalFlag = 1;
    this->RightFlag = (yp < yn);
    this->Section = xi+7;
    this->MarkVertical(this->Section);
    return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
    }
  // The remaining options move a horizontal slab.
  this->VerticalFlag = 0;
  this->RightFlag = (xn > xp);
  this->Section = yi;
  this->MarkHorizontal(this->Section);
  return this->Section + this->VerticalFlag * 10 + this->RightFlag * 100;
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::MovePoint(int percentage)
{
  if ( ! this->Active)
    {
    return;
    }
  this->Modified();

  if (this->VerticalFlag)
    {
    this->MoveVertical(this->Section, percentage, this->RightFlag);
    }
  else
    {
    this->MoveHorizontal(this->Section, percentage, this->RightFlag);
    }
}

//----------------------------------------------------------------------------
void vtkSpherePuzzle::PrintSelf(ostream& os, vtkIndent indent)
{
  int idx;
  this->Superclass::PrintSelf(os,indent);

  os << indent << "State: " << this->State[0];
  for (idx = 1; idx < 16; ++idx)
    {
    os << ", " << this->State[idx];
    }
  os << endl;
}

<|endoftext|>


<commit_before>/** \copyright
 * Copyright (c) 2015, Stuart W Baker
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are  permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \file EEPROMEmulation.cxx
 * This file implements a generic EEPROM emulation in FLASH.
 *
 * @author Stuart W. Baker
 * @date 24 June 2015
 */

#include "EEPROMEmulation.hxx"

#include <cstring>

const size_t EEPROMEmulation::HEADER_BLOCK_COUNT = 3;

const uint32_t EEPROMEmulation::MAGIC_DIRTY = 0xaa55aa55;
const uint32_t EEPROMEmulation::MAGIC_INTACT = 0xaa558001;
const uint32_t EEPROMEmulation::MAGIC_USED = 0x00000000;
const uint32_t EEPROMEmulation::MAGIC_ERASED = 0xFFFFFFFF;

/** Constructor.
 * @param name device name
 * @param file_size maximum file size that we can grow to.
 */
EEPROMEmulation::EEPROMEmulation(const char *name, size_t file_size)
    : EEPROM(name, file_size)
{
    /* make sure we have an appropriate sized region of memory for our device */
    HASSERT(FLASH_SIZE >= (2 * SECTOR_SIZE));  // at least two of them
    HASSERT((FLASH_SIZE % SECTOR_SIZE) == 0);  // and nothing remaining
    HASSERT(file_size <= (SECTOR_SIZE >> 1));  // single block fit all the data
    HASSERT(file_size <= (1024 * 64 - 2));  // uint16 indexes, 0xffff reserved
    HASSERT(BLOCK_SIZE >= 4); // we don't support block sizes less than 4 bytes
    HASSERT((BLOCK_SIZE % 4) == 0); // block size must be on 4 byte boundary
}

/** Mount the EEPROM file.
 */
void EEPROMEmulation::mount()
{
    /* look for an active block that is not used up */
    for (unsigned i = 0; i < sector_count(); ++i)
    {
        if (*block(i, MAGIC_DIRTY_INDEX) == MAGIC_DIRTY  &&
            *block(i, MAGIC_INTACT_INDEX) == MAGIC_INTACT &&
            *block(i, MAGIC_USED_INDEX) == MAGIC_ERASED )
        {
            activeSector_ = i;
            break;
        }
    }

    if (*block(activeSector_, MAGIC_DIRTY_INDEX) != MAGIC_DIRTY  ||
        *block(activeSector_, MAGIC_INTACT_INDEX) != MAGIC_INTACT ||
        *block(activeSector_, MAGIC_USED_INDEX) != MAGIC_ERASED )
    {
        /* our active block is corrupted, we are starting over */
        uint32_t data[4] = {MAGIC_DIRTY, 0, 0, 0};

        flash_erase(activeSector_);
        flash_program(activeSector_, MAGIC_DIRTY_INDEX, data, BLOCK_SIZE);

        data[0] = MAGIC_INTACT;
        flash_program(activeSector_, MAGIC_INTACT_INDEX, data, BLOCK_SIZE);

        availableSlots_ = slot_count();
    }
    else
    {
        /* look for first data block */
        for (unsigned block_index = rawBlockCount_ - 1;
        	 block_index != MAGIC_COUNT; --block_index)
        {
            if (*block(activeSector_, block_index) != MAGIC_ERASED)
            {
                break;
            }
            ++availableSlots_;
        }
    }

    /* do we shadow_ the data in RAM to speed up reads */
    if (SHADOW_IN_RAM)
    {
        /* copy EEPROM data into shadow_ ram */
        shadow_ = new uint8_t[file_size()];

        /* prime the shadow_ RAM with the EEPROM data */
        read(0, shadow_, file_size());

        /* turn on shadowing */
        shadowInRam_ = true;
    }
}

/** Write to the EEPROM.  NOTE!!! This is not necessarily atomic across
 * BLOCK_SIZE boundaries in the case of power loss.  The user should take this
 * into account as it relates to data integrity of a whole block.
 * @param index within EEPROM address space to start write
 * @param buf data to write
 * @param len length in bytes of data to write
 */
void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len)
{
    HASSERT((index + len) <= file_size());

    uint8_t* byte_data = (uint8_t*)buf;
    uint8_t* shadow_data = (uint8_t*)buf;
    unsigned shadow_index = index;
    size_t shadow_len = len;

    while (len)
    {
        /* get the least significant address bits */
        unsigned int lsa = index & (BYTES_PER_BLOCK - 1);
        if (lsa)
        {
            /* head, (unaligned) address */
            uint8_t data[BYTES_PER_BLOCK];
            size_t write_size = len < (BYTES_PER_BLOCK - lsa) ?
                                len : (BYTES_PER_BLOCK - lsa);
            read_fblock(index / BYTES_PER_BLOCK, data);

            if (memcmp(data + lsa, byte_data, write_size) != 0)
            {
                /* at least some data has changed */
                memcpy(data + lsa, byte_data, write_size);
                write_fblock(index / BYTES_PER_BLOCK, data);
            }

            index     += write_size;
            len       -= write_size;
            byte_data += write_size;
        }
        else if (len < BYTES_PER_BLOCK)
        {
            /* tail, (unaligned) address */
            uint8_t data[BYTES_PER_BLOCK];
            read_fblock(index / BYTES_PER_BLOCK, data);

            if (memcmp(data, byte_data, len) != 0)
            {
                /* at least some data has changed */
                memcpy(data, byte_data, len);
                write_fblock(index / BYTES_PER_BLOCK, data);
            }

            len = 0;
        }
        else
        {
            /* aligned data */
            uint8_t data[BYTES_PER_BLOCK];
            read_fblock(index / BYTES_PER_BLOCK, data);

            if (memcmp(data, byte_data, BYTES_PER_BLOCK) != 0)
            {
                /* at least some data has changed */
                memcpy(data, byte_data, BYTES_PER_BLOCK);
                write_fblock(index / BYTES_PER_BLOCK, data);
            }

            index     += BYTES_PER_BLOCK;
            len       -= BYTES_PER_BLOCK;
            byte_data += BYTES_PER_BLOCK;
        }
    }

    if (shadowInRam_)
    {
        memcpy(shadow_ + shadow_index, shadow_data, shadow_len);
    }
}

/** Write to the EEPROM on a native block boundary.
 * @param index block within EEPROM address space to write
 * @param data data to write, array size must be @ref BYTES_PER_BLOCK large
 */
void EEPROMEmulation::write_fblock(unsigned int index, const uint8_t data[])
{
    if (availableSlots_)
    {
        /* still have room in this sector for at least one more write */
        uint32_t slot_data[BLOCK_SIZE / sizeof(uint32_t)];
        for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
        {
            slot_data[i] = (index << 16) |
                           (data[(i * 2) + 1] << 8) |
                           (data[(i * 2) + 0] << 0);
        }
        flash_program(activeSector_, rawBlockCount_ - availableSlots_, slot_data, BLOCK_SIZE);
        --availableSlots_;
    }
    else
    {
        /* we need to overflow into the next sector */
        unsigned new_sector = next_active();
        uint32_t magic[4] = {MAGIC_DIRTY, 0, 0, 0};

        /* prep the new block */
        flash_erase(new_sector);
        flash_program(new_sector, MAGIC_DIRTY_INDEX, magic, BLOCK_SIZE);

        /* reset the available count */
        unsigned available_slots = slot_count();

        /* move any existing data over */
        for (unsigned int fblock = 0; fblock < (file_size() / BYTES_PER_BLOCK); ++fblock)
        {
            uint32_t slot_data[BLOCK_SIZE / sizeof(uint32_t)];
            if (fblock == index) // the new data to be written
            {
                for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
                {
                    slot_data[i] = (index << 16) |
                                   (data[(i * 2) + 1] << 8) |
                                   (data[(i * 2) + 0] << 0);
                }
            }
            else
            {
                /* this is old data we need to move over */
                uint8_t read_data[BYTES_PER_BLOCK];
                if (!read_fblock(fblock, read_data))
                {
                    /* nothing to write, this is the default "erased" value */
                    continue;
                }
                for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
                {
                    slot_data[i] = (fblock << 16) |
                                   (read_data[(i * 2) + 1] << 8) |
                                   (read_data[(i * 2) + 0] << 0);
                }
            }
            /* commit the write */
            flash_program(new_sector, rawBlockCount_ - available_slots, slot_data, BLOCK_SIZE);
            --available_slots;
        }
        /* finalize the data move and write */
        magic[0] = MAGIC_INTACT;
        flash_program(new_sector, MAGIC_INTACT_INDEX, magic, BLOCK_SIZE);
        magic[0] = MAGIC_USED;
        flash_program(activeSector_, MAGIC_USED_INDEX, magic, BLOCK_SIZE);
        activeSector_ = new_sector;
        availableSlots_ = available_slots;
    }
}

/** Read from the EEPROM.
 * @param offset within EEPROM address space to start read
 * @param buf location to post read data
 * @param len length in bytes of data to read
 */
void EEPROMEmulation::read(unsigned int offset, void *buf, size_t len)
{
    HASSERT((offset + len) <= file_size());

    if (shadowInRam_)
    {
        memcpy(buf, shadow_ + offset, len);
        return;
    }

    uint8_t *byte_data = (uint8_t *)buf;
    memset(byte_data, 0xff, len); // default if data not found

    for (unsigned block_index = slot_first();
         block_index <= slot_last();
         ++block_index)
    {
    	const uint32_t *address = block(activeSector_, block_index);
    	if (*address == MAGIC_ERASED)
            break;
        unsigned slot_offset = (address[0] >> 16) * BYTES_PER_BLOCK;
        // Check if slot overlaps with desired data.
        if (offset + len <= slot_offset)
        {
            continue;
        }
        if (slot_offset + BYTES_PER_BLOCK <= offset)
        {
            continue;
        }
        // Reads the block
        uint8_t data[BYTES_PER_BLOCK];
        for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
        {
            data[(i * 2) + 0] = (address[i] >> 0) & 0xFF;
            data[(i * 2) + 1] = (address[i] >> 8) & 0xFF;
        }
        // Copies the right part into the output buffer.
        unsigned slotofs, bufofs;
        if (slot_offset < offset)
        {
            slotofs = offset - slot_offset;
            bufofs = 0;
        }
        else
        {
            slotofs = 0;
            bufofs = slot_offset - offset;
        }
        unsigned copylen = BYTES_PER_BLOCK - slotofs;
        if (slot_offset + BYTES_PER_BLOCK > offset + len)
        {
            HASSERT(copylen >= (slot_offset + BYTES_PER_BLOCK) - (offset + len));
            copylen -= (slot_offset + BYTES_PER_BLOCK) - (offset + len);
        }
        memcpy(byte_data + bufofs, data + slotofs, copylen);
    }
}

/** Read from the EEPROM on a native block boundary.
 * @param index block within EEPROM address space to read
 * @param data location to place read data, array size must be @ref
 *           BYTES_PER_BLOCK large
 * @param return true if any of the data is not "erased", else return false
 */
bool EEPROMEmulation::read_fblock(unsigned int index, uint8_t data[])
{
    if (shadowInRam_)
    {
        memset(data, 0xff, BYTES_PER_BLOCK);
        if (memcmp(data, shadow_ + (index * BYTES_PER_BLOCK), BYTES_PER_BLOCK) !=
            0)
        {
            memcpy(data, shadow_ + (index * BYTES_PER_BLOCK), BYTES_PER_BLOCK);
            return true;
        }
        return false;
    }
    else
    {
        /* default data value if not found */
        memset(data, 0xFF, BYTES_PER_BLOCK);

        /* look for data */
        for (unsigned raw_block = slot_last();
             raw_block >= slot_first();
             --raw_block)
        {
        	const uint32_t* address = block(activeSector_, raw_block);
            if (index == (*address >> 16))
            {
                /* found the data */
                for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
                {
                    data[(i * 2) + 0] = (address[i] >> 0) & 0xFF;
                    data[(i * 2) + 1] = (address[i] >> 8) & 0xFF;
                }
                return true;
            }
        }
    }

    return false;
}
<commit_msg>Makes the detection of end-of-block more robust; now the reading and writing code will both go until availableSlots_ instead of stopping at the first 0xffff.<commit_after>/** \copyright
 * Copyright (c) 2015, Stuart W Baker
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are  permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \file EEPROMEmulation.cxx
 * This file implements a generic EEPROM emulation in FLASH.
 *
 * @author Stuart W. Baker
 * @date 24 June 2015
 */

#include "EEPROMEmulation.hxx"

#include <cstring>

const size_t EEPROMEmulation::HEADER_BLOCK_COUNT = 3;

const uint32_t EEPROMEmulation::MAGIC_DIRTY = 0xaa55aa55;
const uint32_t EEPROMEmulation::MAGIC_INTACT = 0xaa558001;
const uint32_t EEPROMEmulation::MAGIC_USED = 0x00000000;
const uint32_t EEPROMEmulation::MAGIC_ERASED = 0xFFFFFFFF;

/** Constructor.
 * @param name device name
 * @param file_size maximum file size that we can grow to.
 */
EEPROMEmulation::EEPROMEmulation(const char *name, size_t file_size)
    : EEPROM(name, file_size)
{
    /* make sure we have an appropriate sized region of memory for our device */
    HASSERT(FLASH_SIZE >= (2 * SECTOR_SIZE));  // at least two of them
    HASSERT((FLASH_SIZE % SECTOR_SIZE) == 0);  // and nothing remaining
    HASSERT(file_size <= (SECTOR_SIZE >> 1));  // single block fit all the data
    HASSERT(file_size <= (1024 * 64 - 2));  // uint16 indexes, 0xffff reserved
    HASSERT(BLOCK_SIZE >= 4); // we don't support block sizes less than 4 bytes
    HASSERT((BLOCK_SIZE % 4) == 0); // block size must be on 4 byte boundary
}

/** Mount the EEPROM file.
 */
void EEPROMEmulation::mount()
{
    /* look for an active block that is not used up */
    for (unsigned i = 0; i < sector_count(); ++i)
    {
        if (*block(i, MAGIC_DIRTY_INDEX) == MAGIC_DIRTY  &&
            *block(i, MAGIC_INTACT_INDEX) == MAGIC_INTACT &&
            *block(i, MAGIC_USED_INDEX) == MAGIC_ERASED )
        {
            activeSector_ = i;
            break;
        }
    }

    if (*block(activeSector_, MAGIC_DIRTY_INDEX) != MAGIC_DIRTY  ||
        *block(activeSector_, MAGIC_INTACT_INDEX) != MAGIC_INTACT ||
        *block(activeSector_, MAGIC_USED_INDEX) != MAGIC_ERASED )
    {
        /* our active block is corrupted, we are starting over */
        uint32_t data[4] = {MAGIC_DIRTY, 0, 0, 0};

        flash_erase(activeSector_);
        flash_program(activeSector_, MAGIC_DIRTY_INDEX, data, BLOCK_SIZE);

        data[0] = MAGIC_INTACT;
        flash_program(activeSector_, MAGIC_INTACT_INDEX, data, BLOCK_SIZE);

        availableSlots_ = slot_count();
    }
    else
    {
        /* look for first data block */
        for (unsigned block_index = rawBlockCount_ - 1;
        	 block_index != MAGIC_COUNT; --block_index)
        {
            if (*block(activeSector_, block_index) != MAGIC_ERASED)
            {
                break;
            }
            ++availableSlots_;
        }
    }

    /* do we shadow_ the data in RAM to speed up reads */
    if (SHADOW_IN_RAM)
    {
        /* copy EEPROM data into shadow_ ram */
        shadow_ = new uint8_t[file_size()];

        /* prime the shadow_ RAM with the EEPROM data */
        read(0, shadow_, file_size());

        /* turn on shadowing */
        shadowInRam_ = true;
    }
}

/** Write to the EEPROM.  NOTE!!! This is not necessarily atomic across
 * BLOCK_SIZE boundaries in the case of power loss.  The user should take this
 * into account as it relates to data integrity of a whole block.
 * @param index within EEPROM address space to start write
 * @param buf data to write
 * @param len length in bytes of data to write
 */
void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len)
{
    HASSERT((index + len) <= file_size());

    uint8_t* byte_data = (uint8_t*)buf;
    uint8_t* shadow_data = (uint8_t*)buf;
    unsigned shadow_index = index;
    size_t shadow_len = len;

    while (len)
    {
        /* get the least significant address bits */
        unsigned int lsa = index & (BYTES_PER_BLOCK - 1);
        if (lsa)
        {
            /* head, (unaligned) address */
            uint8_t data[BYTES_PER_BLOCK];
            size_t write_size = len < (BYTES_PER_BLOCK - lsa) ?
                                len : (BYTES_PER_BLOCK - lsa);
            read_fblock(index / BYTES_PER_BLOCK, data);

            if (memcmp(data + lsa, byte_data, write_size) != 0)
            {
                /* at least some data has changed */
                memcpy(data + lsa, byte_data, write_size);
                write_fblock(index / BYTES_PER_BLOCK, data);
            }

            index     += write_size;
            len       -= write_size;
            byte_data += write_size;
        }
        else if (len < BYTES_PER_BLOCK)
        {
            /* tail, (unaligned) address */
            uint8_t data[BYTES_PER_BLOCK];
            read_fblock(index / BYTES_PER_BLOCK, data);

            if (memcmp(data, byte_data, len) != 0)
            {
                /* at least some data has changed */
                memcpy(data, byte_data, len);
                write_fblock(index / BYTES_PER_BLOCK, data);
            }

            len = 0;
        }
        else
        {
            /* aligned data */
            uint8_t data[BYTES_PER_BLOCK];
            read_fblock(index / BYTES_PER_BLOCK, data);

            if (memcmp(data, byte_data, BYTES_PER_BLOCK) != 0)
            {
                /* at least some data has changed */
                memcpy(data, byte_data, BYTES_PER_BLOCK);
                write_fblock(index / BYTES_PER_BLOCK, data);
            }

            index     += BYTES_PER_BLOCK;
            len       -= BYTES_PER_BLOCK;
            byte_data += BYTES_PER_BLOCK;
        }
    }

    if (shadowInRam_)
    {
        memcpy(shadow_ + shadow_index, shadow_data, shadow_len);
    }
}

/** Write to the EEPROM on a native block boundary.
 * @param index block within EEPROM address space to write
 * @param data data to write, array size must be @ref BYTES_PER_BLOCK large
 */
void EEPROMEmulation::write_fblock(unsigned int index, const uint8_t data[])
{
    if (availableSlots_)
    {
        /* still have room in this sector for at least one more write */
        uint32_t slot_data[BLOCK_SIZE / sizeof(uint32_t)];
        for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
        {
            slot_data[i] = (index << 16) |
                           (data[(i * 2) + 1] << 8) |
                           (data[(i * 2) + 0] << 0);
        }
        flash_program(activeSector_, rawBlockCount_ - availableSlots_, slot_data, BLOCK_SIZE);
        --availableSlots_;
    }
    else
    {
        /* we need to overflow into the next sector */
        unsigned new_sector = next_active();
        uint32_t magic[4] = {MAGIC_DIRTY, 0, 0, 0};

        /* prep the new block */
        flash_erase(new_sector);
        flash_program(new_sector, MAGIC_DIRTY_INDEX, magic, BLOCK_SIZE);

        /* reset the available count */
        unsigned available_slots = slot_count();

        /* move any existing data over */
        for (unsigned int fblock = 0; fblock < (file_size() / BYTES_PER_BLOCK); ++fblock)
        {
            uint32_t slot_data[BLOCK_SIZE / sizeof(uint32_t)];
            if (fblock == index) // the new data to be written
            {
                for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
                {
                    slot_data[i] = (index << 16) |
                                   (data[(i * 2) + 1] << 8) |
                                   (data[(i * 2) + 0] << 0);
                }
            }
            else
            {
                /* this is old data we need to move over */
                uint8_t read_data[BYTES_PER_BLOCK];
                if (!read_fblock(fblock, read_data))
                {
                    /* nothing to write, this is the default "erased" value */
                    continue;
                }
                for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
                {
                    slot_data[i] = (fblock << 16) |
                                   (read_data[(i * 2) + 1] << 8) |
                                   (read_data[(i * 2) + 0] << 0);
                }
            }
            /* commit the write */
            flash_program(new_sector, rawBlockCount_ - available_slots, slot_data, BLOCK_SIZE);
            --available_slots;
        }
        /* finalize the data move and write */
        magic[0] = MAGIC_INTACT;
        flash_program(new_sector, MAGIC_INTACT_INDEX, magic, BLOCK_SIZE);
        magic[0] = MAGIC_USED;
        flash_program(activeSector_, MAGIC_USED_INDEX, magic, BLOCK_SIZE);
        activeSector_ = new_sector;
        availableSlots_ = available_slots;
    }
}

/** Read from the EEPROM.
 * @param offset within EEPROM address space to start read
 * @param buf location to post read data
 * @param len length in bytes of data to read
 */
void EEPROMEmulation::read(unsigned int offset, void *buf, size_t len)
{
    HASSERT((offset + len) <= file_size());

    if (shadowInRam_)
    {
        memcpy(buf, shadow_ + offset, len);
        return;
    }

    uint8_t *byte_data = (uint8_t *)buf;
    memset(byte_data, 0xff, len); // default if data not found

    for (unsigned block_index = slot_first();
         block_index < rawBlockCount_ - availableSlots_;
         ++block_index)
    {
    	const uint32_t *address = block(activeSector_, block_index);
        unsigned slot_offset = (address[0] >> 16) * BYTES_PER_BLOCK;
        // Check if slot overlaps with desired data.
        if (offset + len <= slot_offset)
        {
            continue;
        }
        if (slot_offset + BYTES_PER_BLOCK <= offset)
        {
            continue;
        }
        // Reads the block
        uint8_t data[BYTES_PER_BLOCK];
        for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
        {
            data[(i * 2) + 0] = (address[i] >> 0) & 0xFF;
            data[(i * 2) + 1] = (address[i] >> 8) & 0xFF;
        }
        // Copies the right part into the output buffer.
        unsigned slotofs, bufofs;
        if (slot_offset < offset)
        {
            slotofs = offset - slot_offset;
            bufofs = 0;
        }
        else
        {
            slotofs = 0;
            bufofs = slot_offset - offset;
        }
        unsigned copylen = BYTES_PER_BLOCK - slotofs;
        if (slot_offset + BYTES_PER_BLOCK > offset + len)
        {
            HASSERT(copylen >= (slot_offset + BYTES_PER_BLOCK) - (offset + len));
            copylen -= (slot_offset + BYTES_PER_BLOCK) - (offset + len);
        }
        memcpy(byte_data + bufofs, data + slotofs, copylen);
    }
}

/** Read from the EEPROM on a native block boundary.
 * @param index block within EEPROM address space to read
 * @param data location to place read data, array size must be @ref
 *           BYTES_PER_BLOCK large
 * @param return true if any of the data is not "erased", else return false
 */
bool EEPROMEmulation::read_fblock(unsigned int index, uint8_t data[])
{
    if (shadowInRam_)
    {
        memset(data, 0xff, BYTES_PER_BLOCK);
        if (memcmp(data, shadow_ + (index * BYTES_PER_BLOCK), BYTES_PER_BLOCK) !=
            0)
        {
            memcpy(data, shadow_ + (index * BYTES_PER_BLOCK), BYTES_PER_BLOCK);
            return true;
        }
        return false;
    }
    else
    {
        /* default data value if not found */
        memset(data, 0xFF, BYTES_PER_BLOCK);

        /* look for data */
        for (unsigned raw_block = slot_last();
             raw_block >= slot_first();
             --raw_block)
        {
        	const uint32_t* address = block(activeSector_, raw_block);
            if (index == (*address >> 16))
            {
                /* found the data */
                for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i)
                {
                    data[(i * 2) + 0] = (address[i] >> 0) & 0xFF;
                    data[(i * 2) + 1] = (address[i] >> 8) & 0xFF;
                }
                return true;
            }
        }
    }

    return false;
}
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  $RCSfile: gui.cxx,v $
 *
 *  $Revision: 1.3 $
 *
 *  last change: $Author: vg $ $Date: 2003-04-15 15:55:13 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  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 Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#ifdef MAC
#include <cursorctl.h>
#endif

#ifndef _RSCRSC_HXX
#include <rscrsc.hxx>
#endif
#ifndef _RSCDB_HXX
#include <rscdb.hxx>
#endif

/*************** G l o b a l e   V a r i a b l e n **********************/
static RscCompiler * pRscCompiler = NULL;
/****************************************************************/
/*                                                              */
/*  Function    :   ExitProgram()                               */
/*                                                              */
/*  Description :   Gibt die Temporaeren Dateien frei.          */
/****************************************************************/
#if defined( UNX ) || ( defined( PM2 ) && ( defined( TCPP ) || defined ( GCC )) ) || defined( MAC ) || defined (WTC) || defined (MTW)
        void ExitProgram( void ){
#else
#if defined( CSET )
    void _Optlink ExitProgram( void ){
#else
    void cdecl ExitProgram( void ){
#endif
#endif
    if( pRscCompiler )
        delete pRscCompiler;
}

void PrimeNumber(){
    USHORT i, n;
    for( i = 801; i < 1000; i += 2 ){
        for( n = 2; n < i && ((i % n) != 0); n++ );
        if( n == i ){
            printf( "\nPrimzahl: %d\n", i );
            return;
        }
    }
}

#if defined( UNX ) || defined( MAC ) || ( defined( PM2 ) && ( defined( CSET ) || defined ( GCC ))) || defined (WTC) || defined(ICC)
int main ( int argc, char ** argv) {
#else
#if defined( MTW )
int  main ( int argc, char const ** argv) {
#else
int cdecl main ( int argc, char ** argv) {
#endif
#endif
#ifndef UNX
#ifdef CSET
    atexit( ExitProgram );
#else
    atexit( ExitProgram );
#endif
#endif
#ifdef MAC
    InitCursorCtl( 0 );
#endif
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "debugging %s\n", argv[0] );
#endif

    ERRTYPE     aError;

    InitRscCompiler();
    RscError*   pErrHdl    = new RscError();
#ifdef MTW
    RscCmdLine* pCmdLine   = new RscCmdLine( argc, (char **)argv, pErrHdl );
#else
    RscCmdLine* pCmdLine   = new RscCmdLine( argc, argv, pErrHdl );
#endif
    RscTypCont* pTypCont   = new RscTypCont( pErrHdl,
                                             pCmdLine->m_aOutputFiles.front().nLangTypeId,
                                             pCmdLine->nByteOrder,
                                             pCmdLine->m_aOutputFiles.front().nSourceCharSet,
                                             pCmdLine->aPath,
                                             pCmdLine->nCommands );

    if( pErrHdl->nErrors )
        aError = ERR_ERROR;
    else{
        RscCompiler* pCompiler = new RscCompiler( pCmdLine, pTypCont );

        pRscCompiler = pCompiler;
        aError = pCompiler->Start();
        pRscCompiler = NULL;

        delete pCompiler;
    }

    delete pTypCont;
    delete pCmdLine;
    delete pErrHdl;
    delete pHS; // wird durch InitRscCompiler erzeugt

    if( aError.IsOk() )
        return( 0 );
    else
        return( 1 );
}

void RscExit( USHORT nExit )
{
    if( nExit )
        printf( "Program exit is %d\n", nExit );
    exit( nExit );
}
<commit_msg>INTEGRATION: CWS mergebuild (1.3.22); FILE MERGED 2004/01/21 14:57:45 pl 1.3.22.1: #110286# fallback resources, move to iso codes<commit_after>/*************************************************************************
 *
 *  $RCSfile: gui.cxx,v $
 *
 *  $Revision: 1.4 $
 *
 *  last change: $Author: hjs $ $Date: 2004-06-26 20:25:58 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  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 Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#ifdef MAC
#include <cursorctl.h>
#endif

#ifndef _RSCRSC_HXX
#include <rscrsc.hxx>
#endif
#ifndef _RSCDB_HXX
#include <rscdb.hxx>
#endif

/*************** G l o b a l e   V a r i a b l e n **********************/
static RscCompiler * pRscCompiler = NULL;
/****************************************************************/
/*                                                              */
/*  Function    :   ExitProgram()                               */
/*                                                              */
/*  Description :   Gibt die Temporaeren Dateien frei.          */
/****************************************************************/
#if defined( UNX ) || ( defined( PM2 ) && ( defined( TCPP ) || defined ( GCC )) ) || defined( MAC ) || defined (WTC) || defined (MTW)
        void ExitProgram( void ){
#else
#if defined( CSET )
    void _Optlink ExitProgram( void ){
#else
    void cdecl ExitProgram( void ){
#endif
#endif
    if( pRscCompiler )
        delete pRscCompiler;
}

void PrimeNumber(){
    USHORT i, n;
    for( i = 801; i < 1000; i += 2 ){
        for( n = 2; n < i && ((i % n) != 0); n++ );
        if( n == i ){
            printf( "\nPrimzahl: %d\n", i );
            return;
        }
    }
}

#if defined( UNX ) || defined( MAC ) || ( defined( PM2 ) && ( defined( CSET ) || defined ( GCC ))) || defined (WTC) || defined(ICC)
int main ( int argc, char ** argv) {
#else
#if defined( MTW )
int  main ( int argc, char const ** argv) {
#else
int cdecl main ( int argc, char ** argv) {
#endif
#endif
#ifndef UNX
#ifdef CSET
    atexit( ExitProgram );
#else
    atexit( ExitProgram );
#endif
#endif
#ifdef MAC
    InitCursorCtl( 0 );
#endif
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "debugging %s\n", argv[0] );
#endif

    ERRTYPE     aError;

    InitRscCompiler();
    RscError*   pErrHdl    = new RscError();
#ifdef MTW
    RscCmdLine* pCmdLine   = new RscCmdLine( argc, (char **)argv, pErrHdl );
#else
    RscCmdLine* pCmdLine   = new RscCmdLine( argc, argv, pErrHdl );
#endif
    RscTypCont* pTypCont   = new RscTypCont( pErrHdl,
                                             pCmdLine->nByteOrder,
                                             pCmdLine->aPath,
                                             pCmdLine->nCommands );

    if( pErrHdl->nErrors )
        aError = ERR_ERROR;
    else{
        RscCompiler* pCompiler = new RscCompiler( pCmdLine, pTypCont );

        pRscCompiler = pCompiler;
        aError = pCompiler->Start();
        pRscCompiler = NULL;

        delete pCompiler;
    }

    delete pTypCont;
    delete pCmdLine;
    delete pErrHdl;
    delete pHS; // wird durch InitRscCompiler erzeugt

    if( aError.IsOk() )
        return( 0 );
    else
        return( 1 );
}

void RscExit( USHORT nExit )
{
    if( nExit )
        printf( "Program exit is %d\n", nExit );
    exit( nExit );
}
<|endoftext|>