1 /***********************************************************************
2 *
3 * Copyright (C) 2009, 2013, 2014, 2016 Graeme Gott <graeme@gottcode.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***********************************************************************/
19
20 #include "dictionary.h"
21
22 #include "wordlist.h"
23
24 #include <QDateTime>
25 #include <QDir>
26 #include <QFile>
27 #include <QFileInfo>
28 #include <QNetworkAccessManager>
29 #include <QNetworkReply>
30 #include <QNetworkRequest>
31 #include <QStandardPaths>
32 #include <QTextStream>
33 #include <QXmlStreamReader>
34
35 static const QByteArray USER_AGENT = "Connectagram/" + QByteArray(VERSIONSTR) + " (https://gottcode.org/connectagram/; Qt/" + qVersion() + ")";
36
37 static const QDateTime BUILD_DATE = QLocale::c().toDateTime(QString("%1 %2").arg(__DATE__, __TIME__).simplified(), "MMM d yyyy hh:ss:mm");
38
Dictionary(const WordList * wordlist,QObject * parent)39 Dictionary::Dictionary(const WordList* wordlist, QObject* parent)
40 : QObject(parent), m_wordlist(wordlist) {
41 m_url.setScheme("https");
42 m_url.setPath("/w/api.php");
43 m_query.addQueryItem("format", "xml");
44 m_query.addQueryItem("action", "mobileview");
45 m_query.addQueryItem("sections", "all");
46 m_query.addQueryItem("noimages", "");
47
48 m_manager = new QNetworkAccessManager(this);
49 connect(m_manager, &QNetworkAccessManager::finished, this, &Dictionary::lookupFinished);
50
51 connect(m_wordlist, &WordList::languageChanged, this, &Dictionary::setLanguage);
52 }
53
54 //-----------------------------------------------------------------------------
55
lookup(const QString & word)56 void Dictionary::lookup(const QString& word) {
57 // Check if word exists in cache and is recent
58 QFileInfo info(m_cache_path + word);
59 if (info.exists() && (info.lastModified() >= std::max(BUILD_DATE, QDateTime::currentDateTime().addDays(-14)))) {
60 QFile file(info.absoluteFilePath());
61 if (file.open(QFile::ReadOnly | QFile::Text)) {
62 QTextStream stream(&file);
63 stream.setCodec("UTF-8");
64 QString definition = stream.readAll();
65 file.close();
66 emit wordDefined(word, definition);
67 return;
68 }
69 }
70
71 // Look up word
72 QStringList spellings = m_wordlist->spellings(word);
73 for (const QString& spelling : spellings) {
74 m_spellings[spelling] = word;
75
76 QNetworkRequest request;
77 QUrl url = m_url;
78 QUrlQuery query = m_query;
79 query.addQueryItem("page", spelling);
80 url.setQuery(query);
81 request.setUrl(url);
82 request.setRawHeader("User-Agent", USER_AGENT);
83
84 QNetworkReply* reply = m_manager->get(request);
85 m_reply_details[reply] = spelling;
86 }
87 }
88
89 //-----------------------------------------------------------------------------
90
wait()91 void Dictionary::wait() {
92 QHashIterator<QNetworkReply*, QString> i(m_reply_details);
93 while (i.hasNext()) {
94 i.key()->abort();
95 }
96 }
97
98 //-----------------------------------------------------------------------------
99
lookupFinished(QNetworkReply * reply)100 void Dictionary::lookupFinished(QNetworkReply* reply) {
101 // Find word
102 QString word = m_reply_details.value(reply);
103 if (!word.isEmpty()) {
104 m_reply_details.remove(reply);
105 word = m_spellings.take(word);
106 }
107 if (word.isEmpty()) {
108 qWarning("Unknown lookup");
109 reply->deleteLater();
110 return;
111 }
112
113 // Determine if this is the last spelling of a word
114 bool last_definition = m_spellings.key(word).isEmpty();
115
116 // Fetch word definitions
117 QString definition = m_definitions.value(word);
118 if (reply->error() == QNetworkReply::NoError) {
119 QXmlStreamReader xml(reply);
120 while (!xml.atEnd()) {
121 xml.readNext();
122 if (!xml.isStartElement()) {
123 continue;
124 }
125
126 if (xml.name() == "section") {
127 definition += xml.readElementText();
128 } else if (xml.name() == "error") {
129 xml.raiseError();
130 }
131 }
132
133 if (!xml.hasError()) {
134 if (last_definition) {
135 definition += "<p align=\"right\">" + tr("Definition from Wiktionary, the free dictionary") + "</p>";
136 m_definitions.remove(word);
137 } else {
138 definition += "<hr>";
139 m_definitions[word] = definition;
140 }
141 } else if (last_definition && definition.isEmpty()) {
142 definition += tr("No definition found");
143 }
144 } else {
145 definition = tr("Unable to connect to Wiktionary");
146 last_definition = true;
147 }
148
149 // Show spelling if definition is done
150 if (last_definition) {
151 emit wordDefined(word, definition);
152
153 // Save word to cache
154 QFile file(m_cache_path + word);
155 if (file.open(QFile::WriteOnly | QFile::Text)) {
156 QTextStream stream(&file);
157 stream.setCodec("UTF-8");
158 stream << definition;
159 file.close();
160 }
161 }
162 reply->deleteLater();
163 }
164
165 //-----------------------------------------------------------------------------
166
setLanguage(const QString & langcode)167 void Dictionary::setLanguage(const QString& langcode) {
168 m_url.setHost(langcode + ".wiktionary.org");
169
170 // Find cache path
171 m_cache_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
172 m_cache_path += "/" + langcode + "/";
173
174 // Create cache directory
175 QDir dir(m_cache_path);
176 dir.mkpath(dir.absolutePath());
177 }
178