Browse Source

Merge pull request #3521

4d90102 [Qt] Add sorting feature to the requested payments table (Cozz Lovan)
8476d5d [Qt] Permanently store requested payments in wallet (Cozz Lovan)
b10e147 wallet: add interface for storing generic data on destinations (Wladimir J. van der Laan)
0.10
Wladimir J. van der Laan 11 years ago
parent
commit
ceab53b41d
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 6
      src/qt/forms/receivecoinsdialog.ui
  2. 2
      src/qt/receivecoinsdialog.cpp
  3. 78
      src/qt/recentrequeststablemodel.cpp
  4. 45
      src/qt/recentrequeststablemodel.h
  5. 24
      src/qt/walletmodel.cpp
  6. 42
      src/qt/walletmodel.h
  7. 54
      src/wallet.cpp
  8. 12
      src/wallet.h
  9. 24
      src/walletdb.cpp
  10. 5
      src/walletdb.h

6
src/qt/forms/receivecoinsdialog.ui

@ -207,7 +207,11 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTableView" name="recentRequestsView"/> <widget class="QTableView" name="recentRequestsView">
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">

2
src/qt/receivecoinsdialog.cpp

@ -55,6 +55,8 @@ void ReceiveCoinsDialog::setModel(WalletModel *model)
ui->recentRequestsView->horizontalHeader()->setSectionResizeMode(RecentRequestsTableModel::Message, QHeaderView::Stretch); ui->recentRequestsView->horizontalHeader()->setSectionResizeMode(RecentRequestsTableModel::Message, QHeaderView::Stretch);
#endif #endif
ui->recentRequestsView->horizontalHeader()->resizeSection(RecentRequestsTableModel::Amount, 100); ui->recentRequestsView->horizontalHeader()->resizeSection(RecentRequestsTableModel::Amount, 100);
model->getRecentRequestsTableModel()->sort(RecentRequestsTableModel::Date, Qt::DescendingOrder);
} }
} }

78
src/qt/recentrequeststablemodel.cpp

@ -12,6 +12,13 @@ RecentRequestsTableModel::RecentRequestsTableModel(CWallet *wallet, WalletModel
walletModel(parent) walletModel(parent)
{ {
Q_UNUSED(wallet); Q_UNUSED(wallet);
nReceiveRequestsMaxId = 0;
// Load entries from wallet
std::vector<std::string> vReceiveRequests;
parent->loadReceiveRequests(vReceiveRequests);
BOOST_FOREACH(const std::string& request, vReceiveRequests)
addNewRequest(request);
/* These columns must match the indices in the ColumnIndex enumeration */ /* These columns must match the indices in the ColumnIndex enumeration */
columns << tr("Date") << tr("Label") << tr("Message") << tr("Amount"); columns << tr("Date") << tr("Label") << tr("Message") << tr("Amount");
@ -104,6 +111,14 @@ bool RecentRequestsTableModel::removeRows(int row, int count, const QModelIndex
if(count > 0 && row >= 0 && (row+count) <= list.size()) if(count > 0 && row >= 0 && (row+count) <= list.size())
{ {
const RecentRequestEntry *rec;
for (int i = 0; i < count; ++i)
{
rec = &list[row+i];
if (!walletModel->saveReceiveRequest(rec->recipient.address.toStdString(), rec->id, ""))
return false;
}
beginRemoveRows(parent, row, row + count - 1); beginRemoveRows(parent, row, row + count - 1);
list.erase(list.begin() + row, list.begin() + row + count); list.erase(list.begin() + row, list.begin() + row + count);
endRemoveRows(); endRemoveRows();
@ -118,12 +133,73 @@ Qt::ItemFlags RecentRequestsTableModel::flags(const QModelIndex &index) const
return Qt::ItemIsSelectable | Qt::ItemIsEnabled; return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
} }
// called when adding a request from the GUI
void RecentRequestsTableModel::addNewRequest(const SendCoinsRecipient &recipient) void RecentRequestsTableModel::addNewRequest(const SendCoinsRecipient &recipient)
{ {
RecentRequestEntry newEntry; RecentRequestEntry newEntry;
newEntry.id = ++nReceiveRequestsMaxId;
newEntry.date = QDateTime::currentDateTime(); newEntry.date = QDateTime::currentDateTime();
newEntry.recipient = recipient; newEntry.recipient = recipient;
CDataStream ss(SER_DISK, CLIENT_VERSION);
ss << newEntry;
if (!walletModel->saveReceiveRequest(recipient.address.toStdString(), newEntry.id, ss.str()))
return;
addNewRequest(newEntry);
}
// called from ctor when loading from wallet
void RecentRequestsTableModel::addNewRequest(const std::string &recipient)
{
std::vector<char> data(recipient.begin(), recipient.end());
CDataStream ss(data, SER_DISK, CLIENT_VERSION);
RecentRequestEntry entry;
ss >> entry;
if (entry.id == 0) // should not happen
return;
if (entry.id > nReceiveRequestsMaxId)
nReceiveRequestsMaxId = entry.id;
addNewRequest(entry);
}
// actually add to table in GUI
void RecentRequestsTableModel::addNewRequest(RecentRequestEntry &recipient)
{
beginInsertRows(QModelIndex(), 0, 0); beginInsertRows(QModelIndex(), 0, 0);
list.prepend(newEntry); list.prepend(recipient);
endInsertRows(); endInsertRows();
} }
void RecentRequestsTableModel::sort(int column, Qt::SortOrder order)
{
qSort(list.begin(), list.end(), RecentRequestEntryLessThan(column, order));
emit dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex()));
}
bool RecentRequestEntryLessThan::operator()(RecentRequestEntry &left, RecentRequestEntry &right) const
{
RecentRequestEntry *pLeft = &left;
RecentRequestEntry *pRight = &right;
if (order == Qt::DescendingOrder)
std::swap(pLeft, pRight);
switch(column)
{
case RecentRequestsTableModel::Date:
return pLeft->date.toTime_t() < pRight->date.toTime_t();
case RecentRequestsTableModel::Label:
return pLeft->recipient.label < pRight->recipient.label;
case RecentRequestsTableModel::Message:
return pLeft->recipient.message < pRight->recipient.message;
case RecentRequestsTableModel::Amount:
return pLeft->recipient.amount < pRight->recipient.amount;
default:
return pLeft->id < pRight->id;
}
}

45
src/qt/recentrequeststablemodel.h

@ -13,10 +13,44 @@
class CWallet; class CWallet;
struct RecentRequestEntry class RecentRequestEntry
{ {
public:
RecentRequestEntry() : nVersion(RecentRequestEntry::CURRENT_VERSION), id(0) { }
static const int CURRENT_VERSION=1;
int nVersion;
int64_t id;
QDateTime date; QDateTime date;
SendCoinsRecipient recipient; SendCoinsRecipient recipient;
IMPLEMENT_SERIALIZE
(
RecentRequestEntry* pthis = const_cast<RecentRequestEntry*>(this);
unsigned int nDate = date.toTime_t();
READWRITE(pthis->nVersion);
nVersion = pthis->nVersion;
READWRITE(id);
READWRITE(nDate);
READWRITE(recipient);
if (fRead)
pthis->date = QDateTime::fromTime_t(nDate);
)
};
class RecentRequestEntryLessThan
{
public:
RecentRequestEntryLessThan(int nColumn, Qt::SortOrder fOrder):
column(nColumn), order(fOrder) {}
bool operator()(RecentRequestEntry &left, RecentRequestEntry &right ) const;
private:
int column;
Qt::SortOrder order;
}; };
/** Model for list of recently generated payment requests / bitcoin URIs. /** Model for list of recently generated payment requests / bitcoin URIs.
@ -34,7 +68,8 @@ public:
Date = 0, Date = 0,
Label = 1, Label = 1,
Message = 2, Message = 2,
Amount = 3 Amount = 3,
NUMBER_OF_COLUMNS
}; };
/** @name Methods overridden from QAbstractTableModel /** @name Methods overridden from QAbstractTableModel
@ -51,11 +86,17 @@ public:
const RecentRequestEntry &entry(int row) const { return list[row]; } const RecentRequestEntry &entry(int row) const { return list[row]; }
void addNewRequest(const SendCoinsRecipient &recipient); void addNewRequest(const SendCoinsRecipient &recipient);
void addNewRequest(const std::string &recipient);
void addNewRequest(RecentRequestEntry &recipient);
public slots:
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
private: private:
WalletModel *walletModel; WalletModel *walletModel;
QStringList columns; QStringList columns;
QList<RecentRequestEntry> list; QList<RecentRequestEntry> list;
int64_t nReceiveRequestsMaxId;
}; };
#endif #endif

24
src/qt/walletmodel.cpp

@ -554,3 +554,27 @@ void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
wallet->ListLockedCoins(vOutpts); wallet->ListLockedCoins(vOutpts);
} }
void WalletModel::loadReceiveRequests(std::vector<std::string>& vReceiveRequests)
{
LOCK(wallet->cs_wallet);
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(std::string, std::string)& item2, item.second.destdata)
if (item2.first.size() > 2 && item2.first.substr(0,2) == "rr") // receive request
vReceiveRequests.push_back(item2.second);
}
bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest)
{
CTxDestination dest = CBitcoinAddress(sAddress).Get();
std::stringstream ss;
ss << nId;
std::string key = "rr" + ss.str(); // "rr" prefix = "receive request" in destdata
LOCK(wallet->cs_wallet);
if (sRequest.empty())
return wallet->EraseDestData(dest, key);
else
return wallet->AddDestData(dest, key, sRequest);
}

42
src/qt/walletmodel.h

@ -36,9 +36,9 @@ QT_END_NAMESPACE
class SendCoinsRecipient class SendCoinsRecipient
{ {
public: public:
explicit SendCoinsRecipient() : amount(0) { } explicit SendCoinsRecipient() : amount(0), nVersion(SendCoinsRecipient::CURRENT_VERSION) { }
explicit SendCoinsRecipient(const QString &addr, const QString &label, quint64 amount, const QString &message): explicit SendCoinsRecipient(const QString &addr, const QString &label, quint64 amount, const QString &message):
address(addr), label(label), amount(amount), message(message) {} address(addr), label(label), amount(amount), message(message), nVersion(SendCoinsRecipient::CURRENT_VERSION) {}
// If from an insecure payment request, this is used for storing // If from an insecure payment request, this is used for storing
// the addresses, e.g. address-A<br />address-B<br />address-C. // the addresses, e.g. address-A<br />address-B<br />address-C.
@ -55,6 +55,41 @@ public:
PaymentRequestPlus paymentRequest; PaymentRequestPlus paymentRequest;
// Empty if no authentication or invalid signature/cert/etc. // Empty if no authentication or invalid signature/cert/etc.
QString authenticatedMerchant; QString authenticatedMerchant;
static const int CURRENT_VERSION=1;
int nVersion;
IMPLEMENT_SERIALIZE
(
SendCoinsRecipient* pthis = const_cast<SendCoinsRecipient*>(this);
std::string sAddress = pthis->address.toStdString();
std::string sLabel = pthis->label.toStdString();
std::string sMessage = pthis->message.toStdString();
std::string sPaymentRequest;
if (!fRead && pthis->paymentRequest.IsInitialized())
pthis->paymentRequest.SerializeToString(&sPaymentRequest);
std::string sAuthenticatedMerchant = pthis->authenticatedMerchant.toStdString();
READWRITE(pthis->nVersion);
nVersion = pthis->nVersion;
READWRITE(sAddress);
READWRITE(sLabel);
READWRITE(amount);
READWRITE(sMessage);
READWRITE(sPaymentRequest);
READWRITE(sAuthenticatedMerchant);
if (fRead)
{
pthis->address = QString::fromStdString(sAddress);
pthis->label = QString::fromStdString(sLabel);
pthis->message = QString::fromStdString(sMessage);
if (!sPaymentRequest.empty())
pthis->paymentRequest.parse(QByteArray::fromRawData(sPaymentRequest.data(), sPaymentRequest.size()));
pthis->authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant);
}
)
}; };
/** Interface to Bitcoin wallet from Qt view code. */ /** Interface to Bitcoin wallet from Qt view code. */
@ -152,6 +187,9 @@ public:
void unlockCoin(COutPoint& output); void unlockCoin(COutPoint& output);
void listLockedCoins(std::vector<COutPoint>& vOutpts); void listLockedCoins(std::vector<COutPoint>& vOutpts);
void loadReceiveRequests(std::vector<std::string>& vReceiveRequests);
bool saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest);
private: private:
CWallet *wallet; CWallet *wallet;

54
src/wallet.cpp

@ -1534,7 +1534,19 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const string& strNam
bool CWallet::DelAddressBook(const CTxDestination& address) bool CWallet::DelAddressBook(const CTxDestination& address)
{ {
AssertLockHeld(cs_wallet); // mapAddressBook AssertLockHeld(cs_wallet); // mapAddressBook
if(fFileBacked)
{
// Delete destdata tuples associated with address
std::string strAddress = CBitcoinAddress(address).ToString();
BOOST_FOREACH(const PAIRTYPE(string, string) &item, mapAddressBook[address].destdata)
{
CWalletDB(strWalletFile).EraseDestData(strAddress, item.first);
}
}
mapAddressBook.erase(address); mapAddressBook.erase(address);
NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), "", CT_DELETED); NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address), "", CT_DELETED);
if (!fFileBacked) if (!fFileBacked)
@ -2008,3 +2020,45 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++) for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++)
mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off mapKeyBirth[it->first] = it->second->nTime - 7200; // block times can be 2h off
} }
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
if (boost::get<CNoDestination>(&dest))
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteDestData(CBitcoinAddress(dest).ToString(), key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key)
{
if (!mapAddressBook[dest].destdata.erase(key))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).EraseDestData(CBitcoinAddress(dest).ToString(), key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return true;
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
{
std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest);
if(i != mapAddressBook.end())
{
CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key);
if(j != i->second.destdata.end())
{
if(value)
*value = j->second;
return true;
}
}
return false;
}

12
src/wallet.h

@ -83,6 +83,9 @@ public:
{ {
purpose = "unknown"; purpose = "unknown";
} }
typedef std::map<std::string, std::string> StringMap;
StringMap destdata;
}; };
/** A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, /** A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
@ -189,6 +192,15 @@ public:
bool AddCScript(const CScript& redeemScript); bool AddCScript(const CScript& redeemScript);
bool LoadCScript(const CScript& redeemScript) { return CCryptoKeyStore::AddCScript(redeemScript); } bool LoadCScript(const CScript& redeemScript) { return CCryptoKeyStore::AddCScript(redeemScript); }
/// Adds a destination data tuple to the store, and saves it to disk
bool AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
/// Erases a destination data tuple in the store and on disk
bool EraseDestData(const CTxDestination &dest, const std::string &key);
/// Adds a destination data tuple to the store, without saving it to disk
bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
/// Look up a destination data tuple in the store, return true if found false otherwise
bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
bool Unlock(const SecureString& strWalletPassphrase); bool Unlock(const SecureString& strWalletPassphrase);
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
bool EncryptWallet(const SecureString& strWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase);

24
src/walletdb.cpp

@ -564,6 +564,18 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
{ {
ssValue >> pwallet->nOrderPosNext; ssValue >> pwallet->nOrderPosNext;
} }
else if (strType == "destdata")
{
std::string strAddress, strKey, strValue;
ssKey >> strAddress;
ssKey >> strKey;
ssValue >> strValue;
if (!pwallet->LoadDestData(CBitcoinAddress(strAddress).Get(), strKey, strValue))
{
strErr = "Error reading wallet database: LoadDestData failed";
return false;
}
}
} catch (...) } catch (...)
{ {
return false; return false;
@ -865,3 +877,15 @@ bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename)
{ {
return CWalletDB::Recover(dbenv, filename, false); return CWalletDB::Recover(dbenv, filename, false);
} }
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
{
nWalletDBUpdated++;
return Write(boost::make_tuple(std::string("destdata"), address, key), value);
}
bool CWalletDB::EraseDestData(const std::string &address, const std::string &key)
{
nWalletDBUpdated++;
return Erase(boost::make_tuple(string("destdata"), address, key));
}

5
src/walletdb.h

@ -124,6 +124,11 @@ public:
bool ReadAccount(const std::string& strAccount, CAccount& account); bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account); bool WriteAccount(const std::string& strAccount, const CAccount& account);
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
/// Erase destination data tuple from wallet database
bool EraseDestData(const std::string &address, const std::string &key);
private: private:
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry); bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
public: public:

Loading…
Cancel
Save