Skip to content

Commit c8e8412

Browse files
committed
database download import in help tab of trainer app
1 parent 22c2d32 commit c8e8412

File tree

1 file changed

+149
-51
lines changed

1 file changed

+149
-51
lines changed

src/lekhika-trainer.cpp

Lines changed: 149 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,7 @@ class HelpTab : public QWidget
800800
801801
<h2 style="font-size: 1.2em; font-weight: 600; color: #34495e;">Tabs Overview</h2>
802802
<ul style="padding-left: 20px; list-style-type: disc;">
803-
<li><b>Learn Words:</b> Import text files (.txt, .html) with Devanagari. Extracts valid words into your dictionary (increments frequency for existing words).</li>
803+
<li><b>Learn Words:</b> Import text files (.txt) with Devanagari texts. Extracts valid words into your dictionary (increments frequency for existing words).</li>
804804
<li><b>Edit Dictionary:</b> View, add, delete, or reset your personal word database.</li>
805805
<li><b>Settings:</b> Configure & test transliteration engine settings.</li>
806806
<li><b>Help:</b> This guide + download pre-trained database.</li>
@@ -828,20 +828,25 @@ class HelpTab : public QWidget
828828
</div>
829829
)");
830830
helpContent_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
831+
helpContent_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
831832
mainLay->addWidget(helpContent_, 1);
832833
dwninfo = new QLabel("If you want a head start, you can download a dictionary with pre trained common words.");
833834
dwninfo->setWordWrap(true);
835+
dwninfo->setStyleSheet("color: red;");
834836
auto *group = new QGroupBox("");
835837
auto *groupLay = new QVBoxLayout(group);
836838
groupLay->addWidget(dwninfo);
837839
downloadBtn_ = new QPushButton("Download and Replace Database");
840+
stopDownloadBtn_ = new QPushButton("Stop Download");
841+
stopDownloadBtn_->setVisible(false);
838842
log_ = new QPlainTextEdit;
839843
log_->setReadOnly(true);
840844
log_->setMaximumHeight(80);
841845
log_->setPlaceholderText("Log output will appear here...");
842846

843847
auto *btnLay = new QHBoxLayout;
844848
btnLay->addWidget(downloadBtn_);
849+
btnLay->addWidget(stopDownloadBtn_);
845850
btnLay->addStretch();
846851

847852
groupLay->addLayout(btnLay);
@@ -850,11 +855,44 @@ class HelpTab : public QWidget
850855

851856
netManager_ = new QNetworkAccessManager(this);
852857
connect(downloadBtn_, &QPushButton::clicked, this, &HelpTab::downloadDatabase);
858+
connect(stopDownloadBtn_, &QPushButton::clicked, this, &HelpTab::stopDownload);
859+
860+
m_downloadTimer = new QTimer(this);
861+
m_downloadTimer->setSingleShot(true);
862+
connect(m_downloadTimer, &QTimer::timeout, this, &HelpTab::onDownloadTimeout);
853863
}
854864

865+
void setOnDatabaseUpdateCallback(std::function<void()> callback) {
866+
onDatabaseUpdate_ = callback;
867+
}
855868

856869
private:
870+
void onDownloadTimeout() {
871+
if (m_currentReply) {
872+
log_->appendPlainText("Download timed out (no activity for 15 seconds).");
873+
m_currentReply->abort();
874+
}
875+
}
876+
877+
void stopDownload() {
878+
if (m_currentReply) {
879+
m_downloadTimer->stop();
880+
m_userStopped = true;
881+
m_currentReply->abort();
882+
}
883+
}
884+
857885
QLabel *dwninfo;
886+
QTextEdit *helpContent_;
887+
QPushButton *downloadBtn_;
888+
QPushButton *stopDownloadBtn_;
889+
QPlainTextEdit *log_;
890+
QNetworkAccessManager *netManager_;
891+
QNetworkReply *m_currentReply = nullptr;
892+
std::function<void()> onDatabaseUpdate_ = nullptr;
893+
QTimer *m_downloadTimer = nullptr;
894+
bool m_userStopped = false;
895+
858896
void downloadDatabase()
859897
{
860898
const QString warningText =
@@ -878,61 +916,110 @@ class HelpTab : public QWidget
878916
log_->clear();
879917
log_->appendPlainText("Looking for fresh dictionary…");
880918
downloadBtn_->setEnabled(false);
919+
stopDownloadBtn_->setVisible(true);
920+
m_userStopped = false;
881921

882922
QString dirPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)
883923
+ QLatin1String("/fcitx5-lekhika");
884924
QDir dir(dirPath);
885925
if (!dir.exists() && !dir.mkpath(".")) {
886926
log_->appendPlainText("Error: could not create " + dirPath);
887927
downloadBtn_->setEnabled(true);
928+
stopDownloadBtn_->setVisible(false);
888929
return;
889930
}
890931

891932
const QString localFile = dirPath + QLatin1String("/lekhikadict.akshardb");
892933
const QUrl url("https://github.com/khumnath/fcitx5-lekhika/releases/download/dictionary/lekhikadict.akshardb");
893934
QNetworkRequest req(url);
894-
QNetworkReply *netReply = netManager_->get(req);
895-
896-
connect(netReply, &QNetworkReply::finished, this, [this, netReply, localFile]() {
897-
if (netReply->error() != QNetworkReply::NoError) {
898-
QString detail;
899-
if (netReply->error() == QNetworkReply::ContentNotFoundError)
900-
detail = "Server replied: 404 – dictionary not found. database not changed.";
901-
else if (netReply->error() == QNetworkReply::HostNotFoundError ||
902-
netReply->error() == QNetworkReply::TimeoutError)
903-
detail = "No internet connection.";
904-
else
905-
detail = QString("Network error: %1").arg(netReply->errorString());
906-
907-
log_->appendPlainText("Download failed. " + detail);
908-
} else {
909-
QString tempFile = localFile + ".tmp";
910-
QFile file(tempFile);
911-
if (!file.open(QIODevice::WriteOnly)) {
912-
log_->appendPlainText(QString("Error: cannot write temporary file %1").arg(tempFile));
913-
} else {
914-
file.write(netReply->readAll());
915-
file.close();
916-
917-
QFile::remove(localFile);
918-
if (QFile::rename(tempFile, localFile)) {
919-
log_->appendPlainText("Success! Dictionary updated.");
920-
log_->appendPlainText("Please restart fcitx5 to use the new dictionary. dictionary can be tested on settings tab without restart this application. ");
935+
m_currentReply = netManager_->get(req);
936+
m_downloadTimer->start(15000); // 15 second timeout
937+
938+
connect(m_currentReply, &QNetworkReply::downloadProgress, this, [this](qint64 bytesReceived, qint64 bytesTotal) {
939+
m_downloadTimer->start(15000); // Reset timer on progress
940+
if (bytesTotal > 0) {
941+
QString progressText = QString::asprintf("Downloading: %.2f MB / %.2f MB",
942+
bytesReceived / (1024.0 * 1024.0),
943+
bytesTotal / (1024.0 * 1024.0));
944+
QMetaObject::invokeMethod(log_, [this, progressText]() {
945+
QTextCursor cursor = log_->textCursor();
946+
cursor.movePosition(QTextCursor::End);
947+
cursor.select(QTextCursor::BlockUnderCursor);
948+
cursor.removeSelectedText();
949+
if (log_->toPlainText().endsWith('\n')) {
950+
QTextCursor cleanupCursor = log_->textCursor();
951+
cleanupCursor.movePosition(QTextCursor::End);
952+
cleanupCursor.deletePreviousChar();
953+
}
954+
log_->appendPlainText(progressText);
955+
}, Qt::QueuedConnection);
956+
}
957+
});
958+
959+
connect(m_currentReply, &QNetworkReply::finished, this, [this, localFile]() {
960+
m_downloadTimer->stop();
961+
962+
QNetworkReply::NetworkError error = m_currentReply->error();
963+
QString errorString = m_currentReply->errorString();
964+
QByteArray data;
965+
if (error == QNetworkReply::NoError) {
966+
data = m_currentReply->readAll();
967+
}
968+
969+
m_currentReply->deleteLater();
970+
m_currentReply = nullptr;
971+
972+
QMetaObject::invokeMethod(this, [this, error, errorString, data, localFile]() {
973+
QTextCursor cursor = log_->textCursor();
974+
cursor.movePosition(QTextCursor::End);
975+
cursor.select(QTextCursor::BlockUnderCursor);
976+
cursor.removeSelectedText();
977+
if (!log_->toPlainText().isEmpty() && !log_->toPlainText().endsWith('\n')) {
978+
QTextCursor cleanupCursor = log_->textCursor();
979+
cleanupCursor.movePosition(QTextCursor::End);
980+
cleanupCursor.deletePreviousChar();
981+
}
982+
983+
if (error == QNetworkReply::OperationCanceledError) {
984+
if (!m_userStopped) {
985+
} else {
986+
log_->appendPlainText("Download cancelled by user.");
987+
}
988+
} else if (error != QNetworkReply::NoError) {
989+
QString detail;
990+
if (error == QNetworkReply::ContentNotFoundError)
991+
detail = "Server replied: 404 – dictionary not found. database not changed.";
992+
else if (error == QNetworkReply::HostNotFoundError ||
993+
error == QNetworkReply::TimeoutError)
994+
detail = "No internet connection.";
995+
else
996+
detail = QString("Network error: %1").arg(errorString);
997+
log_->appendPlainText("Download failed. " + detail);
921998
} else {
922-
log_->appendPlainText("Error: could not replace the old dictionary file.");
999+
QString tempFile = localFile + ".tmp";
1000+
QFile file(tempFile);
1001+
if (!file.open(QIODevice::WriteOnly)) {
1002+
log_->appendPlainText(QString("Error: cannot write temporary file %1").arg(tempFile));
1003+
} else {
1004+
file.write(data);
1005+
file.close();
1006+
1007+
QFile::remove(localFile);
1008+
if (QFile::rename(tempFile, localFile)) {
1009+
log_->appendPlainText("Success! Dictionary updated.");
1010+
log_->appendPlainText("Please restart fcitx5 to use the new dictionary. dictionary can be tested on settings tab without restart this application. ");
1011+
if (onDatabaseUpdate_) onDatabaseUpdate_();
1012+
} else {
1013+
log_->appendPlainText("Error: could not replace the old dictionary file.");
1014+
}
1015+
}
9231016
}
924-
}
925-
}
9261017

927-
downloadBtn_->setEnabled(true);
928-
netReply->deleteLater();
1018+
downloadBtn_->setEnabled(true);
1019+
stopDownloadBtn_->setVisible(false);
1020+
}, Qt::QueuedConnection);
9291021
});
9301022
}
931-
932-
QTextEdit *helpContent_;
933-
QPushButton *downloadBtn_;
934-
QPlainTextEdit *log_;
935-
QNetworkAccessManager *netManager_;
9361023
};
9371024

9381025

@@ -956,33 +1043,44 @@ class MainWin : public QMainWindow
9561043
tab->setDocumentMode(true);
9571044
tabBar->setExpanding(true);
9581045

959-
auto *importTab = new ImportTab;
960-
auto *editTab = new DbEditorTab;
961-
auto *testTab = new TestTab;
962-
auto *helpTab = new HelpTab;
1046+
m_importTab = new ImportTab;
1047+
m_editTab = new DbEditorTab;
1048+
m_testTab = new TestTab;
1049+
m_helpTab = new HelpTab;
9631050

964-
tab->addTab(importTab, "Learn Words");
965-
tab->addTab(editTab, "Edit Dictionary");
966-
tab->addTab(testTab, "Test");
967-
tab->addTab(helpTab, "Help");
1051+
tab->addTab(m_importTab, "Learn Words");
1052+
tab->addTab(m_editTab, "Edit Dictionary");
1053+
tab->addTab(m_testTab, "Test");
1054+
tab->addTab(m_helpTab, "Help");
9681055

969-
connect(tab, &QTabWidget::currentChanged, this, [tab, editTab](int idx) {
970-
if (tab->widget(idx) == editTab) editTab->refresh();
1056+
connect(tab, &QTabWidget::currentChanged, this, [this, tab](int idx) {
1057+
if (tab->widget(idx) == m_editTab) m_editTab->refresh();
9711058
});
9721059

9731060
setCentralWidget(tab);
974-
auto update_fn = [this]() {
1061+
1062+
// Setup callbacks
1063+
auto update_status_fn = [this]() {
9751064
this->updateStatusBar();
9761065
};
9771066

978-
importTab->setOnDatabaseUpdateCallback(update_fn);
979-
editTab->setOnDatabaseUpdateCallback(update_fn);
1067+
m_importTab->setOnDatabaseUpdateCallback(update_status_fn);
1068+
m_editTab->setOnDatabaseUpdateCallback(update_status_fn);
1069+
1070+
m_helpTab->setOnDatabaseUpdateCallback([this](){
1071+
m_editTab->refresh();
1072+
this->updateStatusBar();
1073+
});
9801074

9811075
updateStatusBar();
9821076
}
9831077

9841078

9851079
private:
1080+
ImportTab* m_importTab;
1081+
DbEditorTab* m_editTab;
1082+
TestTab* m_testTab;
1083+
HelpTab* m_helpTab;
9861084
QWidget *m_statusWidget{nullptr};
9871085

9881086
void updateStatusBar() {

0 commit comments

Comments
 (0)