mirror of
https://github.com/feather-wallet/feather.git
synced 2024-12-22 03:29:24 +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]
|
||||
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
|
||||
connect(ui->actionImportHistoryCSV, &QAction::triggered, this, &MainWindow::onImportHistoryDescriptionsCSV);
|
||||
|
||||
// [Wallet] -> [Contacts]
|
||||
connect(ui->actionExportContactsCSV, &QAction::triggered, this, &MainWindow::onExportContactsCSV);
|
||||
|
@ -575,7 +576,7 @@ void MainWindow::onWalletOpened() {
|
|||
m_wallet->history()->refresh();
|
||||
});
|
||||
// Vice versa
|
||||
connect(m_wallet->history(), &TransactionHistory::txNoteChanged, [this] {
|
||||
connect(m_wallet->transactionHistoryModel(), &TransactionHistoryModel::transactionDescriptionChanged, [this] {
|
||||
m_wallet->coins()->refresh();
|
||||
});
|
||||
|
||||
|
@ -1713,6 +1714,21 @@ void MainWindow::onExportHistoryCSV() {
|
|||
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() {
|
||||
auto *model = m_wallet->addressBookModel();
|
||||
if (model->rowCount() <= 0){
|
||||
|
|
|
@ -113,6 +113,7 @@ private slots:
|
|||
void menuToggleTabVisible(const QString &key);
|
||||
void menuClearHistoryClicked();
|
||||
void onExportHistoryCSV();
|
||||
void onImportHistoryDescriptionsCSV();
|
||||
void onExportContactsCSV();
|
||||
void onCreateDesktopEntry();
|
||||
void onShowDocumentation();
|
||||
|
|
|
@ -499,6 +499,7 @@
|
|||
<string>History</string>
|
||||
</property>
|
||||
<addaction name="actionExport_CSV"/>
|
||||
<addaction name="actionImportHistoryCSV"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuContacts">
|
||||
<property name="title">
|
||||
|
@ -939,6 +940,11 @@
|
|||
<string>Tx pool viewer</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionImportHistoryCSV">
|
||||
<property name="text">
|
||||
<string>Import descriptions from CSV</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
|
|
|
@ -34,6 +34,8 @@ CoinsInfo* Coins::coin(int index)
|
|||
|
||||
void Coins::refresh()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
|
||||
emit refreshStarted();
|
||||
|
||||
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()
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
|
||||
QDateTime firstDateTime = QDate(2014, 4, 18).startOfDay();
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
|
||||
|
||||
m_wallet2->set_tx_note(htxid, note.toStdString());
|
||||
refresh();
|
||||
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);
|
||||
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);
|
||||
void refresh();
|
||||
void setTxNote(const QString &txid, const QString ¬e);
|
||||
bool writeCSV(const QString &path);
|
||||
quint64 count() const;
|
||||
QDateTime firstDateTime() const;
|
||||
QDateTime lastDateTime() const;
|
||||
|
@ -42,6 +41,9 @@ public:
|
|||
bool locked() const;
|
||||
void clearRows();
|
||||
|
||||
bool writeCSV(const QString &path);
|
||||
QString importLabelsFromCSV(const QString &fileName);
|
||||
|
||||
signals:
|
||||
void refreshStarted() const;
|
||||
void refreshFinished() const;
|
||||
|
|
|
@ -74,10 +74,6 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent)
|
|||
this->updateBalance();
|
||||
}
|
||||
|
||||
connect(this->history(), &TransactionHistory::txNoteChanged, [this]{
|
||||
this->history()->refresh();
|
||||
});
|
||||
|
||||
connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed);
|
||||
connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock);
|
||||
connect(this, &Wallet::updated, this, &Wallet::onUpdated);
|
||||
|
|
|
@ -236,6 +236,8 @@ bool TransactionHistoryModel::setData(const QModelIndex &index, const QVariant &
|
|||
hash = tInfo.hash();
|
||||
});
|
||||
m_transactionHistory->setTxNote(hash, value.toString());
|
||||
m_transactionHistory->refresh();
|
||||
emit transactionDescriptionChanged();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
|
||||
signals:
|
||||
void transactionHistoryChanged();
|
||||
void transactionDescriptionChanged();
|
||||
|
||||
private:
|
||||
QVariant parseTransactionInfo(const TransactionRow &tInfo, int column, int role) const;
|
||||
|
|
Loading…
Reference in a new issue