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

This commit is contained in:
tobtoht 2024-10-07 15:34:27 +02:00
parent b7ef31610e
commit 6aa661d7ba
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
9 changed files with 137 additions and 9 deletions

View file

@ -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){

View file

@ -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();

View file

@ -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>

View file

@ -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);

View file

@ -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 &note) void TransactionHistory::setTxNote(const QString &txid, const QString &note)
{ {
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 {};
}

View file

@ -34,7 +34,6 @@ public:
TransactionRow* transaction(int index); TransactionRow* transaction(int index);
void refresh(); void refresh();
void setTxNote(const QString &txid, const QString &note); void setTxNote(const QString &txid, const QString &note);
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;

View file

@ -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);

View file

@ -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:

View file

@ -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;