<commit_before>// Copyright (c) 2011-2013 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "addresstablemodel.h"

#include "guiutil.h"
#include "walletmodel.h"

#include "base58.h"
#include "wallet.h"

#include <QFont>
#include <QDebug>

const QString AddressTableModel::Send = "S";
const QString AddressTableModel::Receive = "R";

struct AddressTableEntry
{
    enum Type {
        Sending,
        Receiving,
        Hidden /* QSortFilterProxyModel will filter these out */
    };

    Type type;
    QString label;
    QString address;

    AddressTableEntry() {}
    AddressTableEntry(Type type, const QString &label, const QString &address):
        type(type), label(label), address(address) {}
};

struct AddressTableEntryLessThan
{
    bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
    {
        return a.address < b.address;
    }
    bool operator()(const AddressTableEntry &a, const QString &b) const
    {
        return a.address < b;
    }
    bool operator()(const QString &a, const AddressTableEntry &b) const
    {
        return a < b.address;
    }
};

/* Determine address type from address purpose */
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
{
    AddressTableEntry::Type addressType = AddressTableEntry::Hidden;
    // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all.
    if (strPurpose == "send")
        addressType = AddressTableEntry::Sending;
    else if (strPurpose == "receive")
        addressType = AddressTableEntry::Receiving;
    else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
        addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
    return addressType;
}

// Private implementation
class AddressTablePriv
{
public:
    CWallet *wallet;
    QList<AddressTableEntry> cachedAddressTable;
    AddressTableModel *parent;

    AddressTablePriv(CWallet *wallet, AddressTableModel *parent):
        wallet(wallet), parent(parent) {}

    void refreshAddressTable()
    {
        cachedAddressTable.clear();
        {
            LOCK(wallet->cs_wallet);
            BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
            {
                const CBitcoinAddress& address = item.first;
                bool fMine = IsMine(*wallet, address.Get());
                AddressTableEntry::Type addressType = translateTransactionType(
                        QString::fromStdString(item.second.purpose), fMine);
                const std::string& strName = item.second.name;
                cachedAddressTable.append(AddressTableEntry(addressType,
                                  QString::fromStdString(strName),
                                  QString::fromStdString(address.ToString())));
            }
        }
        // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
        // Even though the map is already sorted this re-sorting step is needed because the originating map
        // is sorted by binary address, not by base58() address.
        qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
    }

    void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
    {
        // Find address / label in model
        QList<AddressTableEntry>::iterator lower = qLowerBound(
            cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
        QList<AddressTableEntry>::iterator upper = qUpperBound(
            cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
        int lowerIndex = (lower - cachedAddressTable.begin());
        int upperIndex = (upper - cachedAddressTable.begin());
        bool inModel = (lower != upper);
        AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);

        switch(status)
        {
        case CT_NEW:
            if(inModel)
            {
                qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_NOW, but entry is already in model";
                break;
            }
            parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
            cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
            parent->endInsertRows();
            break;
        case CT_UPDATED:
            if(!inModel)
            {
                qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_UPDATED, but entry is not in model";
                break;
            }
            lower->type = newEntryType;
            lower->label = label;
            parent->emitDataChanged(lowerIndex);
            break;
        case CT_DELETED:
            if(!inModel)
            {
                qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_DELETED, but entry is not in model";
                break;
            }
            parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
            cachedAddressTable.erase(lower, upper);
            parent->endRemoveRows();
            break;
        }
    }

    int size()
    {
        return cachedAddressTable.size();
    }

    AddressTableEntry *index(int idx)
    {
        if(idx >= 0 && idx < cachedAddressTable.size())
        {
            return &cachedAddressTable[idx];
        }
        else
        {
            return 0;
        }
    }
};

AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) :
    QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0)
{
    columns << tr("Label") << tr("Address");
    priv = new AddressTablePriv(wallet, this);
    priv->refreshAddressTable();
}

AddressTableModel::~AddressTableModel()
{
    delete priv;
}

int AddressTableModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return priv->size();
}

int AddressTableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return columns.length();
}

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

    AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());

    if(role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch(index.column())
        {
        case Label:
            if(rec->label.isEmpty() && role == Qt::DisplayRole)
            {
                return tr("(no label)");
            }
            else
            {
                return rec->label;
            }
        case Address:
            return rec->address;
        }
    }
    else if (role == Qt::FontRole)
    {
        QFont font;
        if(index.column() == Address)
        {
            font = GUIUtil::bitcoinAddressFont();
        }
        return font;
    }
    else if (role == TypeRole)
    {
        switch(rec->type)
        {
        case AddressTableEntry::Sending:
            return Send;
        case AddressTableEntry::Receiving:
            return Receive;
        default: break;
        }
    }
    return QVariant();
}

bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(!index.isValid())
        return false;
    AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
    std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
    editStatus = OK;

    if(role == Qt::EditRole)
    {
        LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */
        CTxDestination curAddress = CBitcoinAddress(rec->address.toStdString()).Get();
        if(index.column() == Label)
        {
            // Do nothing, if old label == new label
            if(rec->label == value.toString())
            {
                editStatus = NO_CHANGES;
                return false;
            }
            wallet->SetAddressBook(curAddress, value.toString().toStdString(), strPurpose);
        } else if(index.column() == Address) {
            CTxDestination newAddress = CBitcoinAddress(value.toString().toStdString()).Get();
            // Refuse to set invalid address, set error status and return false
            if(boost::get<CNoDestination>(&newAddress))
            {
                editStatus = INVALID_ADDRESS;
                return false;
            }
            // Do nothing, if old address == new address
            else if(newAddress == curAddress)
            {
                editStatus = NO_CHANGES;
                return false;
            }
            // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
            // to paste an existing address over another address (with a different label)
            else if(wallet->mapAddressBook.count(newAddress))
            {
                editStatus = DUPLICATE_ADDRESS;
                return false;
            }
            // Double-check that we're not overwriting a receiving address
            else if(rec->type == AddressTableEntry::Sending)
            {
                // Remove old entry
                wallet->DelAddressBook(curAddress);
                // Add new entry with new address
                wallet->SetAddressBook(newAddress, rec->label.toStdString(), strPurpose);
            }
        }
        return true;
    }
    return false;
}

QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(orientation == Qt::Horizontal)
    {
        if(role == Qt::DisplayRole && section < columns.size())
        {
            return columns[section];
        }
    }
    return QVariant();
}

Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return 0;
    AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());

    Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    // Can edit address and label for sending addresses,
    // and only label for receiving addresses.
    if(rec->type == AddressTableEntry::Sending ||
      (rec->type == AddressTableEntry::Receiving && index.column()==Label))
    {
        retval |= Qt::ItemIsEditable;
    }
    return retval;
}

QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    AddressTableEntry *data = priv->index(row);
    if(data)
    {
        return createIndex(row, column, priv->index(row));
    }
    else
    {
        return QModelIndex();
    }
}

void AddressTableModel::updateEntry(const QString &address,
        const QString &label, bool isMine, const QString &purpose, int status)
{
    // Update address book model from Dogecoin core
    priv->updateEntry(address, label, isMine, purpose, status);
}

QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address)
{
    std::string strLabel = label.toStdString();
    std::string strAddress = address.toStdString();

    editStatus = OK;

    if(type == Send)
    {
        if(!walletModel->validateAddress(address))
        {
            editStatus = INVALID_ADDRESS;
            return QString();
        }
        // Check for duplicate addresses
        {
            LOCK(wallet->cs_wallet);
            if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress).Get()))
            {
                editStatus = DUPLICATE_ADDRESS;
                return QString();
            }
        }
    }
    else if(type == Receive)
    {
        // Generate a new address to associate with given label
        CPubKey newKey;
        if(!wallet->GetKeyFromPool(newKey))
        {
            WalletModel::UnlockContext ctx(walletModel->requestUnlock());
            if(!ctx.isValid())
            {
                // Unlock wallet failed or was cancelled
                editStatus = WALLET_UNLOCK_FAILURE;
                return QString();
            }
            if(!wallet->GetKeyFromPool(newKey))
            {
                editStatus = KEY_GENERATION_FAILURE;
                return QString();
            }
        }
        strAddress = CBitcoinAddress(newKey.GetID()).ToString();
    }
    else
    {
        return QString();
    }

    // Add entry
    {
        LOCK(wallet->cs_wallet);
        wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel,
                               (type == Send ? "send" : "receive"));
    }
    return QString::fromStdString(strAddress);
}

bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(parent);
    AddressTableEntry *rec = priv->index(row);
    if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
    {
        // Can only remove one row at a time, and cannot remove rows not in model.
        // Also refuse to remove receiving addresses.
        return false;
    }
    {
        LOCK(wallet->cs_wallet);
        wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get());
    }
    return true;
}

/* Look up label for address in address book, if not found return empty string.
 */
QString AddressTableModel::labelForAddress(const QString &address) const
{
    {
        LOCK(wallet->cs_wallet);
        CBitcoinAddress address_parsed(address.toStdString());
        std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get());
        if (mi != wallet->mapAddressBook.end())
        {
            return QString::fromStdString(mi->second.name);
        }
    }
    return QString();
}

int AddressTableModel::lookupAddress(const QString &address) const
{
    QModelIndexList lst = match(index(0, Address, QModelIndex()),
                                Qt::EditRole, address, 1, Qt::MatchExactly);
    if(lst.isEmpty())
    {
        return -1;
    }
    else
    {
        return lst.at(0).row();
    }
}

void AddressTableModel::emitDataChanged(int idx)
{
    emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
}
<commit_msg>qt: change CT_NOW string to CT_NEW in log message<commit_after>// Copyright (c) 2011-2013 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "addresstablemodel.h"

#include "guiutil.h"
#include "walletmodel.h"

#include "base58.h"
#include "wallet.h"

#include <QFont>
#include <QDebug>

const QString AddressTableModel::Send = "S";
const QString AddressTableModel::Receive = "R";

struct AddressTableEntry
{
    enum Type {
        Sending,
        Receiving,
        Hidden /* QSortFilterProxyModel will filter these out */
    };

    Type type;
    QString label;
    QString address;

    AddressTableEntry() {}
    AddressTableEntry(Type type, const QString &label, const QString &address):
        type(type), label(label), address(address) {}
};

struct AddressTableEntryLessThan
{
    bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
    {
        return a.address < b.address;
    }
    bool operator()(const AddressTableEntry &a, const QString &b) const
    {
        return a.address < b;
    }
    bool operator()(const QString &a, const AddressTableEntry &b) const
    {
        return a < b.address;
    }
};

/* Determine address type from address purpose */
static AddressTableEntry::Type translateTransactionType(const QString &strPurpose, bool isMine)
{
    AddressTableEntry::Type addressType = AddressTableEntry::Hidden;
    // "refund" addresses aren't shown, and change addresses aren't in mapAddressBook at all.
    if (strPurpose == "send")
        addressType = AddressTableEntry::Sending;
    else if (strPurpose == "receive")
        addressType = AddressTableEntry::Receiving;
    else if (strPurpose == "unknown" || strPurpose == "") // if purpose not set, guess
        addressType = (isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending);
    return addressType;
}

// Private implementation
class AddressTablePriv
{
public:
    CWallet *wallet;
    QList<AddressTableEntry> cachedAddressTable;
    AddressTableModel *parent;

    AddressTablePriv(CWallet *wallet, AddressTableModel *parent):
        wallet(wallet), parent(parent) {}

    void refreshAddressTable()
    {
        cachedAddressTable.clear();
        {
            LOCK(wallet->cs_wallet);
            BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
            {
                const CBitcoinAddress& address = item.first;
                bool fMine = IsMine(*wallet, address.Get());
                AddressTableEntry::Type addressType = translateTransactionType(
                        QString::fromStdString(item.second.purpose), fMine);
                const std::string& strName = item.second.name;
                cachedAddressTable.append(AddressTableEntry(addressType,
                                  QString::fromStdString(strName),
                                  QString::fromStdString(address.ToString())));
            }
        }
        // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
        // Even though the map is already sorted this re-sorting step is needed because the originating map
        // is sorted by binary address, not by base58() address.
        qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
    }

    void updateEntry(const QString &address, const QString &label, bool isMine, const QString &purpose, int status)
    {
        // Find address / label in model
        QList<AddressTableEntry>::iterator lower = qLowerBound(
            cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
        QList<AddressTableEntry>::iterator upper = qUpperBound(
            cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
        int lowerIndex = (lower - cachedAddressTable.begin());
        int upperIndex = (upper - cachedAddressTable.begin());
        bool inModel = (lower != upper);
        AddressTableEntry::Type newEntryType = translateTransactionType(purpose, isMine);

        switch(status)
        {
        case CT_NEW:
            if(inModel)
            {
                qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_NEW, but entry is already in model";
                break;
            }
            parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
            cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
            parent->endInsertRows();
            break;
        case CT_UPDATED:
            if(!inModel)
            {
                qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_UPDATED, but entry is not in model";
                break;
            }
            lower->type = newEntryType;
            lower->label = label;
            parent->emitDataChanged(lowerIndex);
            break;
        case CT_DELETED:
            if(!inModel)
            {
                qDebug() << "AddressTablePriv::updateEntry : Warning: Got CT_DELETED, but entry is not in model";
                break;
            }
            parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
            cachedAddressTable.erase(lower, upper);
            parent->endRemoveRows();
            break;
        }
    }

    int size()
    {
        return cachedAddressTable.size();
    }

    AddressTableEntry *index(int idx)
    {
        if(idx >= 0 && idx < cachedAddressTable.size())
        {
            return &cachedAddressTable[idx];
        }
        else
        {
            return 0;
        }
    }
};

AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) :
    QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0)
{
    columns << tr("Label") << tr("Address");
    priv = new AddressTablePriv(wallet, this);
    priv->refreshAddressTable();
}

AddressTableModel::~AddressTableModel()
{
    delete priv;
}

int AddressTableModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return priv->size();
}

int AddressTableModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return columns.length();
}

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

    AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());

    if(role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch(index.column())
        {
        case Label:
            if(rec->label.isEmpty() && role == Qt::DisplayRole)
            {
                return tr("(no label)");
            }
            else
            {
                return rec->label;
            }
        case Address:
            return rec->address;
        }
    }
    else if (role == Qt::FontRole)
    {
        QFont font;
        if(index.column() == Address)
        {
            font = GUIUtil::bitcoinAddressFont();
        }
        return font;
    }
    else if (role == TypeRole)
    {
        switch(rec->type)
        {
        case AddressTableEntry::Sending:
            return Send;
        case AddressTableEntry::Receiving:
            return Receive;
        default: break;
        }
    }
    return QVariant();
}

bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(!index.isValid())
        return false;
    AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
    std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive");
    editStatus = OK;

    if(role == Qt::EditRole)
    {
        LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */
        CTxDestination curAddress = CBitcoinAddress(rec->address.toStdString()).Get();
        if(index.column() == Label)
        {
            // Do nothing, if old label == new label
            if(rec->label == value.toString())
            {
                editStatus = NO_CHANGES;
                return false;
            }
            wallet->SetAddressBook(curAddress, value.toString().toStdString(), strPurpose);
        } else if(index.column() == Address) {
            CTxDestination newAddress = CBitcoinAddress(value.toString().toStdString()).Get();
            // Refuse to set invalid address, set error status and return false
            if(boost::get<CNoDestination>(&newAddress))
            {
                editStatus = INVALID_ADDRESS;
                return false;
            }
            // Do nothing, if old address == new address
            else if(newAddress == curAddress)
            {
                editStatus = NO_CHANGES;
                return false;
            }
            // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
            // to paste an existing address over another address (with a different label)
            else if(wallet->mapAddressBook.count(newAddress))
            {
                editStatus = DUPLICATE_ADDRESS;
                return false;
            }
            // Double-check that we're not overwriting a receiving address
            else if(rec->type == AddressTableEntry::Sending)
            {
                // Remove old entry
                wallet->DelAddressBook(curAddress);
                // Add new entry with new address
                wallet->SetAddressBook(newAddress, rec->label.toStdString(), strPurpose);
            }
        }
        return true;
    }
    return false;
}

QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(orientation == Qt::Horizontal)
    {
        if(role == Qt::DisplayRole && section < columns.size())
        {
            return columns[section];
        }
    }
    return QVariant();
}

Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return 0;
    AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());

    Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
    // Can edit address and label for sending addresses,
    // and only label for receiving addresses.
    if(rec->type == AddressTableEntry::Sending ||
      (rec->type == AddressTableEntry::Receiving && index.column()==Label))
    {
        retval |= Qt::ItemIsEditable;
    }
    return retval;
}

QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    AddressTableEntry *data = priv->index(row);
    if(data)
    {
        return createIndex(row, column, priv->index(row));
    }
    else
    {
        return QModelIndex();
    }
}

void AddressTableModel::updateEntry(const QString &address,
        const QString &label, bool isMine, const QString &purpose, int status)
{
    // Update address book model from Dogecoin core
    priv->updateEntry(address, label, isMine, purpose, status);
}

QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address)
{
    std::string strLabel = label.toStdString();
    std::string strAddress = address.toStdString();

    editStatus = OK;

    if(type == Send)
    {
        if(!walletModel->validateAddress(address))
        {
            editStatus = INVALID_ADDRESS;
            return QString();
        }
        // Check for duplicate addresses
        {
            LOCK(wallet->cs_wallet);
            if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress).Get()))
            {
                editStatus = DUPLICATE_ADDRESS;
                return QString();
            }
        }
    }
    else if(type == Receive)
    {
        // Generate a new address to associate with given label
        CPubKey newKey;
        if(!wallet->GetKeyFromPool(newKey))
        {
            WalletModel::UnlockContext ctx(walletModel->requestUnlock());
            if(!ctx.isValid())
            {
                // Unlock wallet failed or was cancelled
                editStatus = WALLET_UNLOCK_FAILURE;
                return QString();
            }
            if(!wallet->GetKeyFromPool(newKey))
            {
                editStatus = KEY_GENERATION_FAILURE;
                return QString();
            }
        }
        strAddress = CBitcoinAddress(newKey.GetID()).ToString();
    }
    else
    {
        return QString();
    }

    // Add entry
    {
        LOCK(wallet->cs_wallet);
        wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel,
                               (type == Send ? "send" : "receive"));
    }
    return QString::fromStdString(strAddress);
}

bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_UNUSED(parent);
    AddressTableEntry *rec = priv->index(row);
    if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
    {
        // Can only remove one row at a time, and cannot remove rows not in model.
        // Also refuse to remove receiving addresses.
        return false;
    }
    {
        LOCK(wallet->cs_wallet);
        wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get());
    }
    return true;
}

/* Look up label for address in address book, if not found return empty string.
 */
QString AddressTableModel::labelForAddress(const QString &address) const
{
    {
        LOCK(wallet->cs_wallet);
        CBitcoinAddress address_parsed(address.toStdString());
        std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(address_parsed.Get());
        if (mi != wallet->mapAddressBook.end())
        {
            return QString::fromStdString(mi->second.name);
        }
    }
    return QString();
}

int AddressTableModel::lookupAddress(const QString &address) const
{
    QModelIndexList lst = match(index(0, Address, QModelIndex()),
                                Qt::EditRole, address, 1, Qt::MatchExactly);
    if(lst.isEmpty())
    {
        return -1;
    }
    else
    {
        return lst.at(0).row();
    }
}

void AddressTableModel::emitDataChanged(int idx)
{
    emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
}
<|endoftext|>


<commit_before>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2012-2015 Inviwo Foundation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 2. 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 OWNER 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.
 *
 *********************************************************************************/

#include <inviwo/qt/editor/helpwidget.h>
#include <inviwo/core/util/stringconversion.h>
#include <inviwo/core/common/inviwoapplication.h>
#include <QFrame>
#include <QVBoxLayout>
#include <QTextBrowser>
#include <QHelpEngineCore>

#include <QMap>
#include <QString>
#include <QUrl>
#include <QFile>
#include <QByteArray>
#include <QCoreApplication>

namespace inviwo {

/*
 * Paths: qthelp://org.inviwo/doc/<files>
 *
 * This to do to generate info:
 * override css ( the default one is very large and make the text browser slow... )
 * Inviwo-dev\tools\doxygen\doc-qt\html> cp ..\..\style\qt-stylesheet.css .\doxygen.css
 *
 * generate qch (compressed help)
 * Inviwo-dev\tools\doxygen\doc-qt\html> qhelpgenerator.exe -o ..\inviwo.qch .\index.qhp
 *
 * Copy qch
 * Inviwo-dev\data\help> cp ..\..\tools\doxygen\doc-qt\inviwo.qch .
 *
 * Generate qhc (help collection)
 * Inviwo\Inviwo-dev\data\help> qcollectiongenerator.exe inviwo.qhcp -o inviwo.qhc
 */

HelpWidget::HelpWidget(QWidget* parent)
    : InviwoDockWidget(tr("Help"), parent), helpBrowser_(nullptr), helpEngine_(nullptr) {
    setObjectName("HelpWidget");
    setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

    QWidget* centralWidget = new QWidget();
    QVBoxLayout* vLayout = new QVBoxLayout(centralWidget);
    vLayout->setSpacing(7);
    vLayout->setContentsMargins(0, 0, 0, 0);

    std::string helpfile =
        InviwoApplication::getPtr()->getPath(InviwoApplication::PATH_HELP, "inviwo.qhc");

    helpEngine_ = new QHelpEngineCore(QString::fromStdString(helpfile), this);

    helpBrowser_ = new HelpBrowser(this, helpEngine_);
    helpBrowser_->setHtml(QString("Hello world"));
    vLayout->addWidget(helpBrowser_);
    centralWidget->setLayout(vLayout);
    setWidget(centralWidget);

    connect(helpEngine_, SIGNAL(setupFinished()), this, SLOT(setupFinished()));

    if (!helpEngine_->setupData()) {
        LogWarn("Faild to setup the help engine:" << helpEngine_->error().toUtf8().constData());
        delete helpEngine_;
        helpEngine_ = nullptr;
    }
}

void HelpWidget::setupFinished() {
    if (!helpEngine_) return;
    helpBrowser_->setSource(QUrl("qthelp://org.inviwo/doc/docpage-org_8inviwo_8Background.html"));
}

void HelpWidget::showDocForClassName(std::string classIdentifier) {
    if (!helpEngine_) return;
    std::string className = joinString(splitString(classIdentifier, '.'), "_8");

    QUrl url(
        QString::fromStdString("qthelp://org.inviwo/doc/docpage-" + className + ".html"));

    QUrl foundUrl = helpEngine_->findFile(url);

    if (foundUrl.isValid()) {
        helpBrowser_->setSource(foundUrl);
    } else {
        helpBrowser_->setText(QString("No documentation available"));
    }
}

HelpWidget::HelpBrowser::HelpBrowser(QWidget* parent, QHelpEngineCore* helpEngine)
    : QTextBrowser(parent)
    , helpEngine_(helpEngine) {

    setReadOnly(true);
    setUndoRedoEnabled(false);
    setContextMenuPolicy(Qt::NoContextMenu);
    setAcceptRichText(false);

}

HelpWidget::HelpBrowser::~HelpBrowser() {}

QVariant HelpWidget::HelpBrowser::loadResource(int type, const QUrl& name) {
    QUrl url(name);
    if (name.isRelative()) url = source().resolved(url);

#ifdef IVW_DEBUG // Look for the html in the doc-qt folder.
    if (type == QTextDocument::HtmlResource || type == QTextDocument::ImageResource) {
        std::string docbase = InviwoApplication::getPtr()->getPath(InviwoApplication::PATH_DATA,
                                                         "../tools/doxygen/doc-qt/html");
        QString file = name.toString();
        file.replace("qthelp://org.inviwo/doc", QString::fromStdString(docbase));
        QFile newfile(file);

        if (newfile.exists() && newfile.open(QIODevice::ReadOnly)) {
            QByteArray data = newfile.readAll();
            return data;
        }
    }
#endif

    QByteArray data = helpEngine_->fileData(url);
    switch (type) {
        case QTextDocument::HtmlResource:
            return data;
        case QTextDocument::ImageResource:
            return data;
        case QTextDocument::StyleSheetResource:
            return data;
    }
    return QTextBrowser::loadResource(type, url);
}

}  // namespace<commit_msg>Qt: helpwidget, you can now autogenerate a processor screenshot like this<commit_after>/*********************************************************************************
 *
 * Inviwo - Interactive Visualization Workshop
 *
 * Copyright (c) 2012-2015 Inviwo Foundation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 2. 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 OWNER 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.
 *
 *********************************************************************************/

#include <inviwo/qt/editor/helpwidget.h>
#include <inviwo/qt/editor/processorpreview.h>
#include <inviwo/core/util/stringconversion.h>
#include <inviwo/core/common/inviwoapplication.h>
#include <QFrame>
#include <QVBoxLayout>
#include <QTextBrowser>
#include <QHelpEngineCore>

#include <QMap>
#include <QString>
#include <QUrl>
#include <QFile>
#include <QByteArray>
#include <QCoreApplication>
#include <QBuffer>

namespace inviwo {

/*
 * Paths: qthelp://org.inviwo/doc/<files>
 *
 * This to do to generate info:
 * override css ( the default one is very large and make the text browser slow... )
 * Inviwo-dev\tools\doxygen\doc-qt\html> cp ..\..\style\qt-stylesheet.css .\doxygen.css
 *
 * generate qch (compressed help)
 * Inviwo-dev\tools\doxygen\doc-qt\html> qhelpgenerator.exe -o ..\inviwo.qch .\index.qhp
 *
 * Copy qch
 * Inviwo-dev\data\help> cp ..\..\tools\doxygen\doc-qt\inviwo.qch .
 *
 * Generate qhc (help collection)
 * Inviwo\Inviwo-dev\data\help> qcollectiongenerator.exe inviwo.qhcp -o inviwo.qhc
 */

HelpWidget::HelpWidget(QWidget* parent)
    : InviwoDockWidget(tr("Help"), parent), helpBrowser_(nullptr), helpEngine_(nullptr) {
    setObjectName("HelpWidget");
    setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

    QWidget* centralWidget = new QWidget();
    QVBoxLayout* vLayout = new QVBoxLayout(centralWidget);
    vLayout->setSpacing(7);
    vLayout->setContentsMargins(0, 0, 0, 0);

    std::string helpfile =
        InviwoApplication::getPtr()->getPath(InviwoApplication::PATH_HELP, "inviwo.qhc");

    helpEngine_ = new QHelpEngineCore(QString::fromStdString(helpfile), this);

    helpBrowser_ = new HelpBrowser(this, helpEngine_);
    helpBrowser_->setHtml(QString("Hello world"));
    vLayout->addWidget(helpBrowser_);
    centralWidget->setLayout(vLayout);
    setWidget(centralWidget);

    connect(helpEngine_, SIGNAL(setupFinished()), this, SLOT(setupFinished()));

    if (!helpEngine_->setupData()) {
        LogWarn("Faild to setup the help engine:" << helpEngine_->error().toUtf8().constData());
        delete helpEngine_;
        helpEngine_ = nullptr;
    }
}

void HelpWidget::setupFinished() {
    if (!helpEngine_) return;
    helpBrowser_->setSource(QUrl("qthelp://org.inviwo/doc/docpage-org_8inviwo_8Background.html"));
}

void HelpWidget::showDocForClassName(std::string classIdentifier) {
    if (!helpEngine_) return;
    std::string className = joinString(splitString(classIdentifier, '.'), "_8");

    QUrl url(
        QString::fromStdString("qthelp://org.inviwo/doc/docpage-" + className + ".html"));

    QUrl foundUrl = helpEngine_->findFile(url);

    if (foundUrl.isValid()) {
        helpBrowser_->setSource(foundUrl);
    } else {
        helpBrowser_->setText(QString("No documentation available"));
    }
}

HelpWidget::HelpBrowser::HelpBrowser(QWidget* parent, QHelpEngineCore* helpEngine)
    : QTextBrowser(parent)
    , helpEngine_(helpEngine) {

    setReadOnly(true);
    setUndoRedoEnabled(false);
    setContextMenuPolicy(Qt::NoContextMenu);
    setAcceptRichText(false);

}

HelpWidget::HelpBrowser::~HelpBrowser() {}

QVariant HelpWidget::HelpBrowser::loadResource(int type, const QUrl& name) {
    QUrl url(name);
    if (name.isRelative()) url = source().resolved(url);

    if (url.fileName() == "processor.png") {
        std::string quary = url.query().toStdString();
        auto pairs = splitString(quary, '$');
        for (auto& pair : pairs) {
            auto elem = splitString(pair, '=');
            if (elem[0] == "classIdentifier") {
                QImage img = generatePreview(elem[1]);

                QByteArray data;
                QBuffer buffer(&data);
                buffer.open(QIODevice::WriteOnly);
                img.save(&buffer, "PNG");

                return data;
            }
        }
    }

#ifdef IVW_DEBUG  // Look for the html in the doc-qt folder.
    if (type == QTextDocument::HtmlResource || type == QTextDocument::ImageResource) {
        std::string docbase = InviwoApplication::getPtr()->getPath(InviwoApplication::PATH_DATA,
                                                                   "../tools/doxygen/doc-qt/html");
        QString file = name.toString();
        file.replace("qthelp://org.inviwo/doc", QString::fromStdString(docbase));
        QFile newfile(file);

        if (newfile.exists() && newfile.open(QIODevice::ReadOnly)) {
            QByteArray data = newfile.readAll();
            return data;
        }
    }
#endif

    QByteArray data = helpEngine_->fileData(url);
    switch (type) {
        case QTextDocument::HtmlResource:
            return data;
        case QTextDocument::ImageResource:
            return data;
        case QTextDocument::StyleSheetResource:
            return data;
    }
    return QTextBrowser::loadResource(type, url);
}

}  // namespace<|endoftext|>


<commit_before>#include "transactionrecord.h"

#include "wallet.h"

/* Return positive answer if transaction should be shown in list.
 */
bool TransactionRecord::showTransaction(const CWalletTx &wtx)
{
  if(wtx.IsCoinBase())
  {
    // Don't show generated coin until confirmed by at least one block after it
    // so we don't get the user's hopes up until it looks like it's probably accepted.
    //
    // It is not an error when generated blocks are not accepted.  By design,
    // some percentage of blocks, like 10% or more, will end up not accepted.
    // This is the normal mechanism by which the network copes with latency.
    //
    // We display regular transactions right away before any confirmation
    // because they can always get into some block eventually.  Generated coins
    // are special because if their block is not accepted, they are not valid.
    //
    if(wtx.GetDepthInMainChain() < 2)
    {
      return false;
    }
  }
  return true;
}

/*
 * Decompose CWallet transaction to model transaction records.
 */
QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx,
                                                                 bool fBurnMint)
{
  QList<TransactionRecord> parts;
  int64 nTime = wtx.GetTxTime();
  int64 nCredit = wtx.GetCredit(true);
  int64 nDebit = wtx.GetDebit();
  int64 nNet = nCredit - nDebit;
  uint256 hash = wtx.GetHash();
  std::map<std::string, std::string> mapValue = wtx.mapValue;

  if(showTransaction(wtx))
  {
    if(fBurnMint)
      parts.append(TransactionRecord(hash, nTime, TransactionRecord::BurnMint, "", -nDebit, wtx.GetValueOut()));
    else if(wtx.IsCoinStake()) // slimcoin: coinstake transaction
      parts.append(TransactionRecord(hash, nTime, TransactionRecord::StakeMint, "", -nDebit, wtx.GetValueOut()));
    else if(nNet > 0 || wtx.IsCoinBase())
    {
      //
      // Credit
      //
      BOOST_FOREACH(const CTxOut& txout, wtx.vout)
      {
        if(wallet->IsMine(txout))
        {
          TransactionRecord sub(hash, nTime);
          CBitcoinAddress address;
          sub.idx = parts.size(); // sequence number
          sub.credit = txout.nValue;
          if(wtx.IsCoinBase())
          {
            // Generated by mining
            sub.type = TransactionRecord::Generated;
          }
          else if(ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address))
          {
            // Received by Bitcoin Address
            sub.type = TransactionRecord::RecvWithAddress;
            sub.address = address.ToString();
          }
          else
          {
            // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction
            sub.type = TransactionRecord::RecvFromOther;
            sub.address = mapValue["from"];
          }

          parts.append(sub);
        }
      }
    }
    else
    {
      bool fAllFromMe = true;
      BOOST_FOREACH(const CTxIn& txin, wtx.vin)
        fAllFromMe = fAllFromMe && wallet->IsMine(txin);

      bool fAllToMe = true;
      BOOST_FOREACH(const CTxOut& txout, wtx.vout)
        fAllToMe = fAllToMe && wallet->IsMine(txout);

      if(fAllFromMe && fAllToMe)
      {
        // Payment to self
        int64 nChange = wtx.GetChange();

        parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "",
                                       -(nDebit - nChange), nCredit - nChange));
      }
      else if(fAllFromMe)
      {
        //
        // Debit
        //
        int64 nTxFee = nDebit - wtx.GetValueOut();

        for(int nOut = 0; nOut < wtx.vout.size(); nOut++)
        {
          const CTxOut &txout = wtx.vout[nOut];
          TransactionRecord sub(hash, nTime);
          sub.idx = parts.size();

          if(wallet->IsMine(txout))
          {
            // Ignore parts sent to self, as this is usually the change
            // from a transaction sent back to our own address.
            continue;
          }

          CBitcoinAddress address;
          CBurnAddress burnAddress;

          if(ExtractAddress(txout.scriptPubKey, address))
          {
            if(address != burnAddress)
              sub.type = TransactionRecord::SendToAddress; // Sent to Slimcoin Address
            else
              sub.type = TransactionRecord::Burned; // Burned coins

            sub.address = address.ToString();
          }else{
            // Sent to IP, or other non-address transaction like OP_EVAL
            sub.type = TransactionRecord::SendToOther;
            sub.address = mapValue["to"];
          }

          int64 nValue = txout.nValue;
          /* Add fee to first output */
          if(nTxFee > 0)
          {
            nValue += nTxFee;
            nTxFee = 0;
          }
          sub.debit = -nValue;

          parts.append(sub);
        }
      }else{
        //
        // Mixed debit transaction, can't break down payees
        //
        parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0));
      }
    }
  }

  return parts;
}

void TransactionRecord::updateStatus(const CWalletTx &wtx)
{
  // Determine transaction status

  // Find the block the tx is in
  CBlockIndex* pindex = NULL;
  std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock);
  if(mi != mapBlockIndex.end())
    pindex = (*mi).second;

  // Sort order, unrecorded transactions sort to the top
  status.sortKey = strprintf("%010d-%01d-%010u-%03d",
                             (pindex ? pindex->nHeight : std::numeric_limits<int>::max()),
                             (wtx.IsCoinBase() ? 1 : 0),
                             wtx.nTimeReceived,
                             idx);

  status.confirmed = wtx.IsConfirmed();
  status.depth = wtx.GetDepthInMainChain();

  //burn transactions need a bit more information
  if(wtx.IsBurnTx())
  {
    //burn transactions mature differently
    status.burnIsMature = wtx.IsBurnTxMature();
    status.burnDepth = wtx.GetBurnDepthInMainChain();
  }

  status.cur_num_blocks = nBestHeight;

  if(!wtx.IsFinal())
  {
    if(wtx.nLockTime < LOCKTIME_THRESHOLD)
    {
      status.status = TransactionStatus::OpenUntilBlock;
      status.open_for = nBestHeight - wtx.nLockTime;
    }
    else
    {
      status.status = TransactionStatus::OpenUntilDate;
      status.open_for = wtx.nLockTime;
    }
  }
  else
  {
    if(GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
    {
      status.status = TransactionStatus::Offline;
    }
    else if(status.depth < NumConfirmations)
    {
      status.status = TransactionStatus::Unconfirmed;
    }
    else
    {
      status.status = TransactionStatus::HaveConfirmations;
    }
  }

  // For generated transactions, determine maturity
  if(type == TransactionRecord::Generated || type == TransactionRecord::StakeMint
     || type == TransactionRecord::BurnMint)
  {
    int64 nCredit = wtx.GetCredit(true);
    if(nCredit == 0)
    {
      status.maturity = TransactionStatus::Immature;

      if(wtx.IsInMainChain())
      {
        status.matures_in = wtx.GetBlocksToMaturity();

        // Check if the block was requested by anyone
        if(GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
          status.maturity = TransactionStatus::MaturesWarning;
      }
      else
      {
        status.maturity = TransactionStatus::NotAccepted;
      }
    }
    else
    {
      status.maturity = TransactionStatus::Mature;
    }
  }
}

bool TransactionRecord::statusUpdateNeeded()
{
  return status.cur_num_blocks != nBestHeight;
}

std::string TransactionRecord::getTxID()
{
  return hash.ToString() + strprintf("-%03d", idx);
}

<commit_msg>transactionrecord.cpp: fix false mint-by-burn bug<commit_after>#include "transactionrecord.h"

#include "wallet.h"

/* Return positive answer if transaction should be shown in list.
 */
bool TransactionRecord::showTransaction(const CWalletTx &wtx)
{
  if(wtx.IsCoinBase())
  {
    // Don't show generated coin until confirmed by at least one block after it
    // so we don't get the user's hopes up until it looks like it's probably accepted.
    //
    // It is not an error when generated blocks are not accepted.  By design,
    // some percentage of blocks, like 10% or more, will end up not accepted.
    // This is the normal mechanism by which the network copes with latency.
    //
    // We display regular transactions right away before any confirmation
    // because they can always get into some block eventually.  Generated coins
    // are special because if their block is not accepted, they are not valid.
    //
    if(wtx.GetDepthInMainChain() < 2)
    {
      return false;
    }
  }
  return true;
}

/*
 * Decompose CWallet transaction to model transaction records.
 */
QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet *wallet, const CWalletTx &wtx,
                                                                 bool fBurnMint)
{
  QList<TransactionRecord> parts;
  int64 nTime = wtx.GetTxTime();
  int64 nCredit = wtx.GetCredit(true);
  int64 nDebit = wtx.GetDebit();
  int64 nNet = nCredit - nDebit;
  uint256 hash = wtx.GetHash();
  std::map<std::string, std::string> mapValue = wtx.mapValue;

  if(showTransaction(wtx))
  {
    if(fBurnMint && wtx.IsCoinBase())
      parts.append(TransactionRecord(hash, nTime, TransactionRecord::BurnMint, "", -nDebit, wtx.GetValueOut()));
    else if(wtx.IsCoinStake()) // slimcoin: coinstake transaction
      parts.append(TransactionRecord(hash, nTime, TransactionRecord::StakeMint, "", -nDebit, wtx.GetValueOut()));
    else if(nNet > 0 || wtx.IsCoinBase())
    {
      //
      // Credit
      //
      BOOST_FOREACH(const CTxOut& txout, wtx.vout)
      {
        if(wallet->IsMine(txout))
        {
          TransactionRecord sub(hash, nTime);
          CBitcoinAddress address;
          sub.idx = parts.size(); // sequence number
          sub.credit = txout.nValue;
          if(wtx.IsCoinBase())
          {
            // Generated by mining
            sub.type = TransactionRecord::Generated;
          }
          else if(ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address))
          {
            // Received by Bitcoin Address
            sub.type = TransactionRecord::RecvWithAddress;
            sub.address = address.ToString();
          }
          else
          {
            // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction
            sub.type = TransactionRecord::RecvFromOther;
            sub.address = mapValue["from"];
          }

          parts.append(sub);
        }
      }
    }
    else
    {
      bool fAllFromMe = true;
      BOOST_FOREACH(const CTxIn& txin, wtx.vin)
        fAllFromMe = fAllFromMe && wallet->IsMine(txin);

      bool fAllToMe = true;
      BOOST_FOREACH(const CTxOut& txout, wtx.vout)
        fAllToMe = fAllToMe && wallet->IsMine(txout);

      if(fAllFromMe && fAllToMe)
      {
        // Payment to self
        int64 nChange = wtx.GetChange();

        parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "",
                                       -(nDebit - nChange), nCredit - nChange));
      }
      else if(fAllFromMe)
      {
        //
        // Debit
        //
        int64 nTxFee = nDebit - wtx.GetValueOut();

        for(int nOut = 0; nOut < wtx.vout.size(); nOut++)
        {
          const CTxOut &txout = wtx.vout[nOut];
          TransactionRecord sub(hash, nTime);
          sub.idx = parts.size();

          if(wallet->IsMine(txout))
          {
            // Ignore parts sent to self, as this is usually the change
            // from a transaction sent back to our own address.
            continue;
          }

          CBitcoinAddress address;
          CBurnAddress burnAddress;

          if(ExtractAddress(txout.scriptPubKey, address))
          {
            if(address != burnAddress)
              sub.type = TransactionRecord::SendToAddress; // Sent to Slimcoin Address
            else
              sub.type = TransactionRecord::Burned; // Burned coins

            sub.address = address.ToString();
          }else{
            // Sent to IP, or other non-address transaction like OP_EVAL
            sub.type = TransactionRecord::SendToOther;
            sub.address = mapValue["to"];
          }

          int64 nValue = txout.nValue;
          /* Add fee to first output */
          if(nTxFee > 0)
          {
            nValue += nTxFee;
            nTxFee = 0;
          }
          sub.debit = -nValue;

          parts.append(sub);
        }
      }else{
        //
        // Mixed debit transaction, can't break down payees
        //
        parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0));
      }
    }
  }

  return parts;
}

void TransactionRecord::updateStatus(const CWalletTx &wtx)
{
  // Determine transaction status

  // Find the block the tx is in
  CBlockIndex* pindex = NULL;
  std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock);
  if(mi != mapBlockIndex.end())
    pindex = (*mi).second;

  // Sort order, unrecorded transactions sort to the top
  status.sortKey = strprintf("%010d-%01d-%010u-%03d",
                             (pindex ? pindex->nHeight : std::numeric_limits<int>::max()),
                             (wtx.IsCoinBase() ? 1 : 0),
                             wtx.nTimeReceived,
                             idx);

  status.confirmed = wtx.IsConfirmed();
  status.depth = wtx.GetDepthInMainChain();

  //burn transactions need a bit more information
  if(wtx.IsBurnTx())
  {
    //burn transactions mature differently
    status.burnIsMature = wtx.IsBurnTxMature();
    status.burnDepth = wtx.GetBurnDepthInMainChain();
  }

  status.cur_num_blocks = nBestHeight;

  if(!wtx.IsFinal())
  {
    if(wtx.nLockTime < LOCKTIME_THRESHOLD)
    {
      status.status = TransactionStatus::OpenUntilBlock;
      status.open_for = nBestHeight - wtx.nLockTime;
    }
    else
    {
      status.status = TransactionStatus::OpenUntilDate;
      status.open_for = wtx.nLockTime;
    }
  }
  else
  {
    if(GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
    {
      status.status = TransactionStatus::Offline;
    }
    else if(status.depth < NumConfirmations)
    {
      status.status = TransactionStatus::Unconfirmed;
    }
    else
    {
      status.status = TransactionStatus::HaveConfirmations;
    }
  }

  // For generated transactions, determine maturity
  if(type == TransactionRecord::Generated || type == TransactionRecord::StakeMint
     || type == TransactionRecord::BurnMint)
  {
    int64 nCredit = wtx.GetCredit(true);
    if(nCredit == 0)
    {
      status.maturity = TransactionStatus::Immature;

      if(wtx.IsInMainChain())
      {
        status.matures_in = wtx.GetBlocksToMaturity();

        // Check if the block was requested by anyone
        if(GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
          status.maturity = TransactionStatus::MaturesWarning;
      }
      else
      {
        status.maturity = TransactionStatus::NotAccepted;
      }
    }
    else
    {
      status.maturity = TransactionStatus::Mature;
    }
  }
}

bool TransactionRecord::statusUpdateNeeded()
{
  return status.cur_num_blocks != nBestHeight;
}

std::string TransactionRecord::getTxID()
{
  return hash.ToString() + strprintf("-%03d", idx);
}

<|endoftext|>


<commit_before>// Copyright 2010-2013 RethinkDB, all rights reserved.

#include "rdb_protocol/batching.hpp"

#include "rdb_protocol/datum.hpp"
#include "rdb_protocol/env.hpp"
#include "rdb_protocol/error.hpp"

namespace ql {

static const int64_t DEFAULT_MIN_ELS = 8;
static const int64_t DEFAULT_FIRST_SCALEDOWN = 4;
static const int64_t DEFAULT_MAX_SIZE = MEGABYTE;
// The maximum duration of a batch in microseconds.
static const int64_t DEFAULT_MAX_DURATION = 500 * 1000;
// These numbers are sort of arbitrary, but they seem to work. See `scale_down()`
// for an explanation.
static const int64_t DIVISOR_SCALING_FACTOR = 8;
static const int64_t SCALE_CONSTANT = 8;

RDB_IMPL_SERIALIZABLE_7(batchspec_t,
                        batch_type,
                        min_els,
                        max_els,
                        max_size,
                        first_scaledown_factor,
                        max_dur,
                        start_time);
INSTANTIATE_SERIALIZABLE_FOR_CLUSTER(batchspec_t);

batchspec_t::batchspec_t(
    batch_type_t _batch_type,
    int64_t _min_els,
    int64_t _max_els,
    int64_t _max_size,
    int64_t _first_scaledown,
    int64_t _max_dur,
    microtime_t _start_time)
    : batch_type(_batch_type),
      min_els(_min_els),
      max_els(_max_els),
      max_size(_max_size),
      first_scaledown_factor(_first_scaledown),
      max_dur(_max_dur),
      start_time(_start_time) {
    r_sanity_check(first_scaledown_factor >= 1);
    r_sanity_check(max_els >= 1);
    r_sanity_check(min_els >= 1);
    r_sanity_check(max_els >= min_els);
    r_sanity_check(max_size >= 1);
    r_sanity_check(max_dur >= 0);
}

batchspec_t batchspec_t::user(batch_type_t batch_type,
                              const counted_t<const datum_t> &conf) {
    counted_t<const datum_t> max_els_d, min_els_d, max_size_d, max_dur_d;
    counted_t<const datum_t> first_scaledown_d;
    if (conf.has()) {
        min_els_d = conf->get("min_els", NOTHROW);
        max_els_d = conf->get("max_els", NOTHROW);
        max_size_d = conf->get("max_size", NOTHROW);
        first_scaledown_d = conf->get("first_scaledown", NOTHROW);
        max_dur_d = conf->get("max_dur", NOTHROW);
    }
    int64_t max_els = max_els_d.has()
                      ? max_els_d->as_int()
                      : std::numeric_limits<decltype(batchspec_t().max_els)>::max();
    int64_t min_els = min_els_d.has()
                      ? min_els_d->as_int()
                      : std::min<int64_t>(max_els, DEFAULT_MIN_ELS);
    int64_t max_size = max_size_d.has() ? max_size_d->as_int() : DEFAULT_MAX_SIZE;
    int64_t first_sd = first_scaledown_d.has()
                       ? first_scaledown_d->as_int()
                       : DEFAULT_FIRST_SCALEDOWN;
    int64_t max_dur = max_dur_d.has() ? max_dur_d->as_int() : DEFAULT_MAX_DURATION;
    return batchspec_t(batch_type,
                       min_els,
                       max_els,
                       max_size,
                       first_sd,
                       max_dur,
                       current_microtime());
}

batchspec_t batchspec_t::all() {
    return batchspec_t(batch_type_t::TERMINAL,
                       DEFAULT_MIN_ELS,
                       std::numeric_limits<decltype(batchspec_t().max_els)>::max(),
                       std::numeric_limits<decltype(batchspec_t().max_size)>::max(),
                       1,
                       0,
                       current_microtime());
}

batchspec_t batchspec_t::user(batch_type_t batch_type, env_t *env) {
    counted_t<val_t> v = env->global_optargs.get_optarg(env, "batch_conf");
    return user(batch_type, v.has() ? v->as_datum() : counted_t<const datum_t>());
}

batchspec_t batchspec_t::with_new_batch_type(batch_type_t new_batch_type) const {
    return batchspec_t(new_batch_type, min_els, max_els, max_size,
                       first_scaledown_factor, max_dur, start_time);
}

batchspec_t batchspec_t::with_at_most(uint64_t _max_els) const {
    // Special case: if _max_els is 1, we want min_els to also be 1 for maximum
    // efficiency.
    // If _max_els on the other hand is > 1 but still small, having
    // min_els = _max_els could result in multiple round-trips, since our unsharding
    // code would throw out a lot of documents. So in that case we just keep min_els
    // unchanged (and in fact elevate _max_els to min_els if _max_els < min_els
    // otherwise).
    int64_t new_min_els = _max_els == 1 ? 1 : min_els;
    int64_t new_max_els = std::max<int64_t>(new_min_els,
            std::min(uint64_t(std::numeric_limits<int64_t>::max()), _max_els));
    return batchspec_t(
        batch_type,
        new_min_els,
        new_max_els,
        max_size,
        first_scaledown_factor,
        max_dur,
        start_time);
}

batchspec_t batchspec_t::scale_down(int64_t divisor) const {
    // We divide by e.g. 7/8th of the divisor (assuming DIVISOR_SCALING_FACTOR == 8)
    // and add SCALE_CONSTANT to reduce the chances of needing a second round-trip
    // (we add a constant because unequal division is more likely with very small
    // sizes).  Law of large numbers says that the chances of needing a second
    // round-trip for large, non-pathological datasets are extremely low.
    int64_t new_max_els =
        max_els == std::numeric_limits<decltype(batchspec_t().max_els)>::max()
            ? max_els
            : std::min(max_els, (max_els * DIVISOR_SCALING_FACTOR
                                 / ((DIVISOR_SCALING_FACTOR - 1) * divisor))
                                + SCALE_CONSTANT);
    int64_t new_max_size =
        max_size == std::numeric_limits<decltype(batchspec_t().max_size)>::max()
            ? max_size
            : std::min(max_size, (max_size * DIVISOR_SCALING_FACTOR
                                  / ((DIVISOR_SCALING_FACTOR - 1) * divisor))
                                 + SCALE_CONSTANT);
    int64_t new_min_els = std::min(min_els, new_max_els);

    return batchspec_t(batch_type, new_min_els, new_max_els, new_max_size,
                       first_scaledown_factor, max_dur, start_time);
}

batcher_t batchspec_t::to_batcher() const {
    int64_t real_min_els =
        batch_type != batch_type_t::NORMAL_FIRST
            ? min_els
            : std::max<int64_t>(1, min_els / first_scaledown_factor);
    int64_t real_max_els =
        max_els == std::numeric_limits<decltype(batchspec_t().max_els)>::max()
        || batch_type != batch_type_t::NORMAL_FIRST
            ? max_els
            : std::max<int64_t>(1, max_els / first_scaledown_factor);
    int64_t real_max_size =
        max_size == std::numeric_limits<decltype(batchspec_t().max_size)>::max()
        || batch_type != batch_type_t::NORMAL_FIRST
            ? max_size
            : std::max<int64_t>(1, max_size / first_scaledown_factor);
    microtime_t cur_time = current_microtime();
    microtime_t end_time;
    if (batch_type == batch_type_t::NORMAL) {
        end_time = std::max(cur_time + (cur_time - start_time), start_time + max_dur);
    } else if (batch_type == batch_type_t::NORMAL_FIRST) {
        end_time = std::max(start_time + (max_dur / first_scaledown_factor),
                            cur_time + (max_dur / (first_scaledown_factor * 2)));
    } else {
        end_time = std::numeric_limits<decltype(end_time)>::max();
    }
    return batcher_t(batch_type, real_min_els, real_max_els, real_max_size, end_time);
}

bool batcher_t::should_send_batch() const {
    // We ignore size_left as long as we have not got at least `min_wanted_els`
    // documents.
    return els_left <= 0
        || (size_left <= 0 && min_els_left <= 0)
        || (current_microtime() >= end_time && seen_one_el);
}

batcher_t::batcher_t(
    batch_type_t _batch_type,
    int64_t min_els,
    int64_t max_els,
    int64_t max_size,
    microtime_t _end_time)
    : batch_type(_batch_type),
      seen_one_el(false),
      min_els_left(min_els),
      els_left(max_els),
      size_left(max_size),
      end_time(_end_time) { }

} // namespace ql
<commit_msg>Per @danielmewes, instead make new_max_els at least min_els.<commit_after>// Copyright 2010-2013 RethinkDB, all rights reserved.

#include "rdb_protocol/batching.hpp"

#include "rdb_protocol/datum.hpp"
#include "rdb_protocol/env.hpp"
#include "rdb_protocol/error.hpp"

namespace ql {

static const int64_t DEFAULT_MIN_ELS = 8;
static const int64_t DEFAULT_FIRST_SCALEDOWN = 4;
static const int64_t DEFAULT_MAX_SIZE = MEGABYTE;
// The maximum duration of a batch in microseconds.
static const int64_t DEFAULT_MAX_DURATION = 500 * 1000;
// These numbers are sort of arbitrary, but they seem to work. See `scale_down()`
// for an explanation.
static const int64_t DIVISOR_SCALING_FACTOR = 8;
static const int64_t SCALE_CONSTANT = 8;

RDB_IMPL_SERIALIZABLE_7(batchspec_t,
                        batch_type,
                        min_els,
                        max_els,
                        max_size,
                        first_scaledown_factor,
                        max_dur,
                        start_time);
INSTANTIATE_SERIALIZABLE_FOR_CLUSTER(batchspec_t);

batchspec_t::batchspec_t(
    batch_type_t _batch_type,
    int64_t _min_els,
    int64_t _max_els,
    int64_t _max_size,
    int64_t _first_scaledown,
    int64_t _max_dur,
    microtime_t _start_time)
    : batch_type(_batch_type),
      min_els(_min_els),
      max_els(_max_els),
      max_size(_max_size),
      first_scaledown_factor(_first_scaledown),
      max_dur(_max_dur),
      start_time(_start_time) {
    r_sanity_check(first_scaledown_factor >= 1);
    r_sanity_check(max_els >= 1);
    r_sanity_check(min_els >= 1);
    r_sanity_check(max_els >= min_els);
    r_sanity_check(max_size >= 1);
    r_sanity_check(max_dur >= 0);
}

batchspec_t batchspec_t::user(batch_type_t batch_type,
                              const counted_t<const datum_t> &conf) {
    counted_t<const datum_t> max_els_d, min_els_d, max_size_d, max_dur_d;
    counted_t<const datum_t> first_scaledown_d;
    if (conf.has()) {
        min_els_d = conf->get("min_els", NOTHROW);
        max_els_d = conf->get("max_els", NOTHROW);
        max_size_d = conf->get("max_size", NOTHROW);
        first_scaledown_d = conf->get("first_scaledown", NOTHROW);
        max_dur_d = conf->get("max_dur", NOTHROW);
    }
    int64_t max_els = max_els_d.has()
                      ? max_els_d->as_int()
                      : std::numeric_limits<decltype(batchspec_t().max_els)>::max();
    int64_t min_els = min_els_d.has()
                      ? min_els_d->as_int()
                      : std::min<int64_t>(max_els, DEFAULT_MIN_ELS);
    int64_t max_size = max_size_d.has() ? max_size_d->as_int() : DEFAULT_MAX_SIZE;
    int64_t first_sd = first_scaledown_d.has()
                       ? first_scaledown_d->as_int()
                       : DEFAULT_FIRST_SCALEDOWN;
    int64_t max_dur = max_dur_d.has() ? max_dur_d->as_int() : DEFAULT_MAX_DURATION;
    return batchspec_t(batch_type,
                       min_els,
                       max_els,
                       max_size,
                       first_sd,
                       max_dur,
                       current_microtime());
}

batchspec_t batchspec_t::all() {
    return batchspec_t(batch_type_t::TERMINAL,
                       DEFAULT_MIN_ELS,
                       std::numeric_limits<decltype(batchspec_t().max_els)>::max(),
                       std::numeric_limits<decltype(batchspec_t().max_size)>::max(),
                       1,
                       0,
                       current_microtime());
}

batchspec_t batchspec_t::user(batch_type_t batch_type, env_t *env) {
    counted_t<val_t> v = env->global_optargs.get_optarg(env, "batch_conf");
    return user(batch_type, v.has() ? v->as_datum() : counted_t<const datum_t>());
}

batchspec_t batchspec_t::with_new_batch_type(batch_type_t new_batch_type) const {
    return batchspec_t(new_batch_type, min_els, max_els, max_size,
                       first_scaledown_factor, max_dur, start_time);
}

batchspec_t batchspec_t::with_at_most(uint64_t _max_els) const {
    // Special case: if _max_els is 1, we want min_els to also be 1 for maximum
    // efficiency.
    // If _max_els on the other hand is > 1 but still small, having
    // min_els = _max_els could result in multiple round-trips, since our unsharding
    // code would throw out a lot of documents. So in that case we just keep min_els
    // unchanged (and in fact elevate _max_els to min_els if _max_els < min_els
    // otherwise).
    int64_t new_min_els = _max_els == 1 ? 1 : min_els;
    int64_t new_max_els = std::max<int64_t>(new_min_els,
            std::min(uint64_t(std::numeric_limits<int64_t>::max()), _max_els));
    return batchspec_t(
        batch_type,
        new_min_els,
        new_max_els,
        max_size,
        first_scaledown_factor,
        max_dur,
        start_time);
}

batchspec_t batchspec_t::scale_down(int64_t divisor) const {
    // We divide by e.g. 7/8th of the divisor (assuming DIVISOR_SCALING_FACTOR == 8)
    // and add SCALE_CONSTANT to reduce the chances of needing a second round-trip
    // (we add a constant because unequal division is more likely with very small
    // sizes).  Law of large numbers says that the chances of needing a second
    // round-trip for large, non-pathological datasets are extremely low.
    int64_t new_max_els =
        max_els == std::numeric_limits<decltype(batchspec_t().max_els)>::max()
            ? max_els
            : std::min(max_els, (max_els * DIVISOR_SCALING_FACTOR
                                 / ((DIVISOR_SCALING_FACTOR - 1) * divisor))
                                + SCALE_CONSTANT);
    int64_t new_max_size =
        max_size == std::numeric_limits<decltype(batchspec_t().max_size)>::max()
            ? max_size
            : std::min(max_size, (max_size * DIVISOR_SCALING_FACTOR
                                  / ((DIVISOR_SCALING_FACTOR - 1) * divisor))
                                 + SCALE_CONSTANT);
    // to avoid problems when the batches get really tiny, we clamp new_max_els to be at least min_els.
    new_max_els = std::max(min_els, new_max_els);

    return batchspec_t(batch_type, min_els, new_max_els, new_max_size,
                       first_scaledown_factor, max_dur, start_time);
}

batcher_t batchspec_t::to_batcher() const {
    int64_t real_min_els =
        batch_type != batch_type_t::NORMAL_FIRST
            ? min_els
            : std::max<int64_t>(1, min_els / first_scaledown_factor);
    int64_t real_max_els =
        max_els == std::numeric_limits<decltype(batchspec_t().max_els)>::max()
        || batch_type != batch_type_t::NORMAL_FIRST
            ? max_els
            : std::max<int64_t>(1, max_els / first_scaledown_factor);
    int64_t real_max_size =
        max_size == std::numeric_limits<decltype(batchspec_t().max_size)>::max()
        || batch_type != batch_type_t::NORMAL_FIRST
            ? max_size
            : std::max<int64_t>(1, max_size / first_scaledown_factor);
    microtime_t cur_time = current_microtime();
    microtime_t end_time;
    if (batch_type == batch_type_t::NORMAL) {
        end_time = std::max(cur_time + (cur_time - start_time), start_time + max_dur);
    } else if (batch_type == batch_type_t::NORMAL_FIRST) {
        end_time = std::max(start_time + (max_dur / first_scaledown_factor),
                            cur_time + (max_dur / (first_scaledown_factor * 2)));
    } else {
        end_time = std::numeric_limits<decltype(end_time)>::max();
    }
    return batcher_t(batch_type, real_min_els, real_max_els, real_max_size, end_time);
}

bool batcher_t::should_send_batch() const {
    // We ignore size_left as long as we have not got at least `min_wanted_els`
    // documents.
    return els_left <= 0
        || (size_left <= 0 && min_els_left <= 0)
        || (current_microtime() >= end_time && seen_one_el);
}

batcher_t::batcher_t(
    batch_type_t _batch_type,
    int64_t min_els,
    int64_t max_els,
    int64_t max_size,
    microtime_t _end_time)
    : batch_type(_batch_type),
      seen_one_el(false),
      min_els_left(min_els),
      els_left(max_els),
      size_left(max_size),
      end_time(_end_time) { }

} // namespace ql
<|endoftext|>