mirror of
https://github.com/feather-wallet/feather.git
synced 2024-12-22 11:39:25 +00:00
history: import descriptions from CSV
Some checks are pending
ci/gh-actions/build / build-ubuntu-without-scanner (push) Waiting to run
ci/gh-actions/guix / cache-sources (push) Waiting to run
ci/gh-actions/guix / aarch64-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / arm-linux-gnueabihf (push) Blocked by required conditions
ci/gh-actions/guix / arm64-apple-darwin (push) Blocked by required conditions
ci/gh-actions/guix / i686-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / riscv64-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-apple-darwin (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-linux-gnu.no-tor-bundle (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-linux-gnu.pack (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-w64-mingw32.installer (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-w64-mingw32 (push) Blocked by required conditions
ci/gh-actions/guix / bundle-logs (push) Blocked by required conditions
Some checks are pending
ci/gh-actions/build / build-ubuntu-without-scanner (push) Waiting to run
ci/gh-actions/guix / cache-sources (push) Waiting to run
ci/gh-actions/guix / aarch64-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / arm-linux-gnueabihf (push) Blocked by required conditions
ci/gh-actions/guix / arm64-apple-darwin (push) Blocked by required conditions
ci/gh-actions/guix / i686-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / riscv64-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-apple-darwin (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-linux-gnu.no-tor-bundle (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-linux-gnu.pack (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-linux-gnu (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-w64-mingw32.installer (push) Blocked by required conditions
ci/gh-actions/guix / x86_64-w64-mingw32 (push) Blocked by required conditions
ci/gh-actions/guix / bundle-logs (push) Blocked by required conditions
This commit is contained in:
parent
b7ef31610e
commit
6aa661d7ba
9 changed files with 137 additions and 9 deletions
|
@ -315,6 +315,7 @@ void MainWindow::initMenu() {
|
||||||
|
|
||||||
// [Wallet] -> [History]
|
// [Wallet] -> [History]
|
||||||
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
|
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
|
||||||
|
connect(ui->actionImportHistoryCSV, &QAction::triggered, this, &MainWindow::onImportHistoryDescriptionsCSV);
|
||||||
|
|
||||||
// [Wallet] -> [Contacts]
|
// [Wallet] -> [Contacts]
|
||||||
connect(ui->actionExportContactsCSV, &QAction::triggered, this, &MainWindow::onExportContactsCSV);
|
connect(ui->actionExportContactsCSV, &QAction::triggered, this, &MainWindow::onExportContactsCSV);
|
||||||
|
@ -575,7 +576,7 @@ void MainWindow::onWalletOpened() {
|
||||||
m_wallet->history()->refresh();
|
m_wallet->history()->refresh();
|
||||||
});
|
});
|
||||||
// Vice versa
|
// Vice versa
|
||||||
connect(m_wallet->history(), &TransactionHistory::txNoteChanged, [this] {
|
connect(m_wallet->transactionHistoryModel(), &TransactionHistoryModel::transactionDescriptionChanged, [this] {
|
||||||
m_wallet->coins()->refresh();
|
m_wallet->coins()->refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1713,6 +1714,21 @@ void MainWindow::onExportHistoryCSV() {
|
||||||
Utils::showInfo(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
|
Utils::showInfo(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onImportHistoryDescriptionsCSV() {
|
||||||
|
const QString fileName = QFileDialog::getOpenFileName(this, "Import CSV file", QDir::homePath(), "CSV Files (*.csv)");
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString error = m_wallet->history()->importLabelsFromCSV(fileName);
|
||||||
|
if (!error.isEmpty()) {
|
||||||
|
Utils::showError(this, "Unable to import transaction descriptions from CSV", error);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Utils::showInfo(this, "Successfully imported transaction descriptions from CSV");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onExportContactsCSV() {
|
void MainWindow::onExportContactsCSV() {
|
||||||
auto *model = m_wallet->addressBookModel();
|
auto *model = m_wallet->addressBookModel();
|
||||||
if (model->rowCount() <= 0){
|
if (model->rowCount() <= 0){
|
||||||
|
|
|
@ -113,6 +113,7 @@ private slots:
|
||||||
void menuToggleTabVisible(const QString &key);
|
void menuToggleTabVisible(const QString &key);
|
||||||
void menuClearHistoryClicked();
|
void menuClearHistoryClicked();
|
||||||
void onExportHistoryCSV();
|
void onExportHistoryCSV();
|
||||||
|
void onImportHistoryDescriptionsCSV();
|
||||||
void onExportContactsCSV();
|
void onExportContactsCSV();
|
||||||
void onCreateDesktopEntry();
|
void onCreateDesktopEntry();
|
||||||
void onShowDocumentation();
|
void onShowDocumentation();
|
||||||
|
|
|
@ -499,6 +499,7 @@
|
||||||
<string>History</string>
|
<string>History</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionExport_CSV"/>
|
<addaction name="actionExport_CSV"/>
|
||||||
|
<addaction name="actionImportHistoryCSV"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuContacts">
|
<widget class="QMenu" name="menuContacts">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -939,6 +940,11 @@
|
||||||
<string>Tx pool viewer</string>
|
<string>Tx pool viewer</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionImportHistoryCSV">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import descriptions from CSV</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
|
|
@ -34,6 +34,8 @@ CoinsInfo* Coins::coin(int index)
|
||||||
|
|
||||||
void Coins::refresh()
|
void Coins::refresh()
|
||||||
{
|
{
|
||||||
|
qDebug() << Q_FUNC_INFO;
|
||||||
|
|
||||||
emit refreshStarted();
|
emit refreshStarted();
|
||||||
|
|
||||||
boost::shared_lock<boost::shared_mutex> transfers_lock(m_wallet2->m_transfers_mutex);
|
boost::shared_lock<boost::shared_mutex> transfers_lock(m_wallet2->m_transfers_mutex);
|
||||||
|
|
|
@ -64,6 +64,8 @@ TransactionRow* TransactionHistory::transaction(int index)
|
||||||
|
|
||||||
void TransactionHistory::refresh()
|
void TransactionHistory::refresh()
|
||||||
{
|
{
|
||||||
|
qDebug() << Q_FUNC_INFO;
|
||||||
|
|
||||||
QDateTime firstDateTime = QDate(2014, 4, 18).startOfDay();
|
QDateTime firstDateTime = QDate(2014, 4, 18).startOfDay();
|
||||||
QDateTime lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
|
QDateTime lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
|
||||||
|
|
||||||
|
@ -281,12 +283,14 @@ void TransactionHistory::refresh()
|
||||||
void TransactionHistory::setTxNote(const QString &txid, const QString ¬e)
|
void TransactionHistory::setTxNote(const QString &txid, const QString ¬e)
|
||||||
{
|
{
|
||||||
cryptonote::blobdata txid_data;
|
cryptonote::blobdata txid_data;
|
||||||
if(!epee::string_tools::parse_hexstr_to_binbuff(txid.toStdString(), txid_data) || txid_data.size() != sizeof(crypto::hash))
|
if (!epee::string_tools::parse_hexstr_to_binbuff(txid.toStdString(), txid_data) || txid_data.size() != sizeof(crypto::hash)) {
|
||||||
|
qDebug() << Q_FUNC_INFO << "invalid txid";
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
|
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
|
||||||
|
|
||||||
m_wallet2->set_tx_note(htxid, note.toStdString());
|
m_wallet2->set_tx_note(htxid, note.toStdString());
|
||||||
refresh();
|
|
||||||
emit txNoteChanged();
|
emit txNoteChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,4 +405,102 @@ bool TransactionHistory::writeCSV(const QString &path) {
|
||||||
|
|
||||||
data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data);
|
data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data);
|
||||||
return Utils::fileWrite(path, data);
|
return Utils::fileWrite(path, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList parseCSVLine(const QString &line) {
|
||||||
|
QStringList result;
|
||||||
|
QString currentField;
|
||||||
|
bool inQuotes = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < line.length(); ++i) {
|
||||||
|
QChar currentChar = line[i];
|
||||||
|
|
||||||
|
if (currentChar == '"') {
|
||||||
|
if (inQuotes && i + 1 < line.length() && line[i + 1] == '"') {
|
||||||
|
currentField.append('"');
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
inQuotes = !inQuotes;
|
||||||
|
}
|
||||||
|
} else if (currentChar == ',' && !inQuotes) {
|
||||||
|
result.append(currentField.trimmed());
|
||||||
|
currentField.clear();
|
||||||
|
} else {
|
||||||
|
currentField.append(currentChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(currentField.trimmed());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TransactionHistory::importLabelsFromCSV(const QString &fileName) {
|
||||||
|
QFile file(fileName);
|
||||||
|
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
|
return QString("Could not open file: %1").arg(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream in(&file);
|
||||||
|
|
||||||
|
QList<QStringList> fields;
|
||||||
|
while (!in.atEnd()) {
|
||||||
|
QString line = in.readLine();
|
||||||
|
fields.append(parseCSVLine(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.empty()) {
|
||||||
|
return "CSV file appears to be empty";
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 txidField = -1;
|
||||||
|
qint64 descriptionField = -1;
|
||||||
|
|
||||||
|
QStringList header = fields[0];
|
||||||
|
for (int i = 0; i < header.length(); i++) {
|
||||||
|
if (header[i] == "txid") {
|
||||||
|
txidField = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (header[i] == "description") {
|
||||||
|
descriptionField = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txidField < 0) {
|
||||||
|
return "'txid' field not found in CSV header";
|
||||||
|
}
|
||||||
|
if (descriptionField < 0) {
|
||||||
|
return "'description' field not found in CSV header";
|
||||||
|
}
|
||||||
|
qint64 maxIndex = std::max(txidField, descriptionField);
|
||||||
|
|
||||||
|
QList<QPair<QString, QString>> descriptions;
|
||||||
|
|
||||||
|
for (int i = 1; i < fields.length(); i++) {
|
||||||
|
const auto& row = fields[i];
|
||||||
|
if (maxIndex >= row.length()) {
|
||||||
|
qDebug() << "Row with invalid length in CSV";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row[txidField].isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row[descriptionField].isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptions.push_back({row[txidField], row[descriptionField]});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& description : descriptions) {
|
||||||
|
qDebug() << "Setting note for tx:" << description.first << "description:" << description.second;
|
||||||
|
this->setTxNote(description.first, description.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->refresh();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ public:
|
||||||
TransactionRow* transaction(int index);
|
TransactionRow* transaction(int index);
|
||||||
void refresh();
|
void refresh();
|
||||||
void setTxNote(const QString &txid, const QString ¬e);
|
void setTxNote(const QString &txid, const QString ¬e);
|
||||||
bool writeCSV(const QString &path);
|
|
||||||
quint64 count() const;
|
quint64 count() const;
|
||||||
QDateTime firstDateTime() const;
|
QDateTime firstDateTime() const;
|
||||||
QDateTime lastDateTime() const;
|
QDateTime lastDateTime() const;
|
||||||
|
@ -42,6 +41,9 @@ public:
|
||||||
bool locked() const;
|
bool locked() const;
|
||||||
void clearRows();
|
void clearRows();
|
||||||
|
|
||||||
|
bool writeCSV(const QString &path);
|
||||||
|
QString importLabelsFromCSV(const QString &fileName);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void refreshStarted() const;
|
void refreshStarted() const;
|
||||||
void refreshFinished() const;
|
void refreshFinished() const;
|
||||||
|
|
|
@ -74,10 +74,6 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent)
|
||||||
this->updateBalance();
|
this->updateBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(this->history(), &TransactionHistory::txNoteChanged, [this]{
|
|
||||||
this->history()->refresh();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed);
|
connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed);
|
||||||
connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock);
|
connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock);
|
||||||
connect(this, &Wallet::updated, this, &Wallet::onUpdated);
|
connect(this, &Wallet::updated, this, &Wallet::onUpdated);
|
||||||
|
|
|
@ -236,6 +236,8 @@ bool TransactionHistoryModel::setData(const QModelIndex &index, const QVariant &
|
||||||
hash = tInfo.hash();
|
hash = tInfo.hash();
|
||||||
});
|
});
|
||||||
m_transactionHistory->setTxNote(hash, value.toString());
|
m_transactionHistory->setTxNote(hash, value.toString());
|
||||||
|
m_transactionHistory->refresh();
|
||||||
|
emit transactionDescriptionChanged();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -45,6 +45,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void transactionHistoryChanged();
|
void transactionHistoryChanged();
|
||||||
|
void transactionDescriptionChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVariant parseTransactionInfo(const TransactionRow &tInfo, int column, int role) const;
|
QVariant parseTransactionInfo(const TransactionRow &tInfo, int column, int role) const;
|
||||||
|
|
Loading…
Reference in a new issue