Falkon Develop
Cross-platform Qt-based web browser
searchenginesmanager.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2017 David Rosca <nowrep@gmail.com>
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* ============================================================ */
19#include "searchenginesdialog.h"
20#include "editsearchengine.h"
21#include "networkmanager.h"
22#include "iconprovider.h"
23#include "mainapplication.h"
24#include "opensearchreader.h"
25#include "settings.h"
26#include "qzsettings.h"
27#include "webview.h"
28#include "sqldatabase.h"
29
30#include <QNetworkReply>
31#include <QMessageBox>
32#include <QBuffer>
33
34#include <QUrlQuery>
35
36#define ENSURE_LOADED if (!m_settingsLoaded) loadSettings();
37
38static QIcon iconFromBase64(const QByteArray &data)
39{
40 QIcon image;
41 QByteArray bArray = QByteArray::fromBase64(data);
42 QBuffer buffer(&bArray);
43 buffer.open(QIODevice::ReadOnly);
44 QDataStream in(&buffer);
45 in >> image;
46 buffer.close();
47
48 if (!image.isNull()) {
49 return image;
50 }
51
53}
54
55static QByteArray iconToBase64(const QIcon &icon)
56{
57 QByteArray bArray;
58 QBuffer buffer(&bArray);
59 buffer.open(QIODevice::WriteOnly);
60 QDataStream out(&buffer);
61 out << icon;
62 buffer.close();
63 return bArray.toBase64();
64}
65
67 : QObject(parent)
68 , m_settingsLoaded(false)
69 , m_saveScheduled(false)
70{
71 Settings settings;
72 settings.beginGroup(QSL("SearchEngines"));
73 m_startingEngineName = settings.value(QSL("activeEngine"), QSL("DuckDuckGo")).toString();
74 m_defaultEngineName = settings.value(QSL("DefaultEngine"), QSL("DuckDuckGo")).toString();
75 settings.endGroup();
76
77 connect(this, &SearchEnginesManager::enginesChanged, this, &SearchEnginesManager::scheduleSave);
78}
79
80void SearchEnginesManager::loadSettings()
81{
82 m_settingsLoaded = true;
83
84 QSqlQuery query(SqlDatabase::instance()->database());
85 query.exec(QSL("SELECT name, icon, url, shortcut, suggestionsUrl, suggestionsParameters, postData FROM search_engines"));
86
87 while (query.next()) {
88 Engine en;
89 en.name = query.value(0).toString();
90 en.icon = iconFromBase64(query.value(1).toByteArray());
91 en.url = query.value(2).toString();
92 en.shortcut = query.value(3).toString();
93 en.suggestionsUrl = query.value(4).toString();
94 en.suggestionsParameters = query.value(5).toByteArray();
95 en.postData = query.value(6).toByteArray();
96
97 m_allEngines.append(en);
98
99 if (en.name == m_defaultEngineName) {
100 m_defaultEngine = en;
101 }
102 }
103
104 if (m_allEngines.isEmpty()) {
106 }
107
108 if (m_defaultEngine.name.isEmpty()) {
109 m_defaultEngine = m_allEngines[0];
110 }
111}
112
114{
115 Engine returnEngine;
116
117 if (shortcut.isEmpty()) {
118 return returnEngine;
119 }
120
121 for (const Engine &en : std::as_const(m_allEngines)) {
122 if (en.shortcut == shortcut) {
123 returnEngine = en;
124 break;
125 }
126 }
127
128 return returnEngine;
129}
130
131LoadRequest SearchEnginesManager::searchResult(const Engine &engine, const QString &string)
132{
134
135 // GET search engine
136 if (engine.postData.isEmpty()) {
137 QByteArray url = engine.url.toUtf8();
138 url.replace("%s", QUrl::toPercentEncoding(string));
139
140 return LoadRequest(QUrl::fromEncoded(url));
141 }
142
143 // POST search engine
144 QByteArray data = engine.postData;
145 data.replace("%s", QUrl::toPercentEncoding(string));
146
147 return LoadRequest(QUrl::fromEncoded(engine.url.toUtf8()), LoadRequest::PostOperation, data);
148}
149
151{
153
154 const Engine en = qzSettings->searchWithDefaultEngine ? m_defaultEngine : m_activeEngine;
155 return searchResult(en, string);
156}
157
159{
160 Engine duck;
161 duck.name = QSL("DuckDuckGo");
162 duck.icon = QIcon(QSL(":/icons/sites/duck.png"));
163 duck.url = QSL("https://duckduckgo.com/?q=%s&t=qupzilla");
164 duck.shortcut = QSL("d");
165 duck.suggestionsUrl = QSL("https://ac.duckduckgo.com/ac/?q=%s&type=list");
166
167 Engine sp;
168 sp.name = QSL("Startpage");
169 sp.icon = QIcon(QSL(":/icons/sites/startpage.png"));
170 sp.url = QSL("https://www.startpage.com/sp/search?query=%s&cat=web&pl=opensearch");
171 sp.shortcut = QSL("sp");
172 sp.suggestionsUrl = QSL("https://www.startpage.com/osuggestions?q=%s");
173
174 Engine wiki;
175 wiki.name = QSL("Wikipedia (en)");
176 wiki.icon = QIcon(QSL(":/icons/sites/wikipedia.png"));
177 wiki.url = QSL("https://en.wikipedia.org/wiki/Special:Search?search=%s&fulltext=Search");
178 wiki.shortcut = QSL("w");
179 wiki.suggestionsUrl = QSL("https://en.wikipedia.org/w/api.php?action=opensearch&search=%s&namespace=0");
180
181 Engine google;
182 google.name = QSL("Google");
183 google.icon = QIcon(QSL(":icons/sites/google.png"));
184 google.url = QSL("https://www.google.com/search?client=falkon&q=%s");
185 google.shortcut = QSL("g");
186 google.suggestionsUrl = QSL("https://suggestqueries.google.com/complete/search?output=firefox&q=%s");
187
188 addEngine(duck);
189 addEngine(sp);
190 addEngine(wiki);
191 addEngine(google);
192
193 m_defaultEngine = duck;
194
195 Q_EMIT enginesChanged();
196}
197
198// static
200{
201 QIcon ic = IconProvider::iconForDomain(url);
202
203 if (ic.isNull()) {
204 ic = QIcon::fromTheme(QSL("edit-find"), QIcon(QSL(":icons/menu/search-icon.svg")));
205 }
206
207 return ic;
208}
209
210void SearchEnginesManager::engineChangedImage()
211{
212 auto* engine = qobject_cast<OpenSearchEngine*>(sender());
213
214 if (!engine) {
215 return;
216 }
217
218 for (const Engine &e : std::as_const(m_allEngines)) {
219 if (e.name == engine->name() &&
220 e.url.contains(engine->searchUrl(QSL("%s")).toString()) &&
221 !engine->image().isNull()
222 ) {
223 int index = m_allEngines.indexOf(e);
224 if (index != -1) {
225 m_allEngines[index].icon = QIcon(QPixmap::fromImage(engine->image()));
226
227 Q_EMIT enginesChanged();
228
229 delete engine;
230 break;
231 }
232 }
233 }
234}
235
236void SearchEnginesManager::editEngine(const Engine &before, const Engine &after)
237{
238 removeEngine(before);
239 addEngine(after);
240}
241
243{
245
246 if (m_allEngines.contains(engine)) {
247 return;
248 }
249
250 m_allEngines.append(engine);
251
252 Q_EMIT enginesChanged();
253}
254
255void SearchEnginesManager::addEngineFromForm(const QVariantMap &formData, WebView *view)
256{
257 if (formData.isEmpty())
258 return;
259
260 const QString method = formData.value(QSL("method")).toString();
261 bool isPost = method == QL1S("post");
262
263 QUrl actionUrl = formData.value(QSL("action")).toUrl();
264
265 if (actionUrl.isRelative()) {
266 actionUrl = view->url().resolved(actionUrl);
267 }
268
269 QUrl parameterUrl = actionUrl;
270
271 if (isPost) {
272 parameterUrl = QUrl(QSL("http://foo.bar"));
273 }
274
275 const QString &inputName = formData.value(QSL("inputName")).toString();
276
277 QUrlQuery query(parameterUrl);
278 query.addQueryItem(inputName, QSL("SEARCH"));
279
280 const QVariantList &inputs = formData.value(QSL("inputs")).toList();
281 for (const QVariant &pair : inputs) {
282 const QVariantList &list = pair.toList();
283 if (list.size() != 2)
284 continue;
285
286 const QString &name = list.at(0).toString();
287 const QString &value = list.at(1).toString();
288
289 if (name == inputName || name.isEmpty() || value.isEmpty())
290 continue;
291
292 query.addQueryItem(name, value);
293 }
294
295 parameterUrl.setQuery(query);
296
297 if (!isPost) {
298 actionUrl = parameterUrl;
299 }
300
301 SearchEngine engine;
302 engine.name = view->title();
303 engine.icon = view->icon();
304 engine.url = QString::fromUtf8(actionUrl.toEncoded());
305
306 if (isPost) {
307 QByteArray data = parameterUrl.toEncoded(QUrl::RemoveScheme);
308 engine.postData = data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray();
309 engine.postData.replace((inputName + QL1S("=SEARCH")).toUtf8(), (inputName + QL1S("=%s")).toUtf8());
310 } else {
311 engine.url.replace(inputName + QL1S("=SEARCH"), inputName + QL1S("=%s"));
312 }
313
314 EditSearchEngine dialog(SearchEnginesDialog::tr("Add Search Engine"), view);
315 dialog.setName(engine.name);
316 dialog.setIcon(engine.icon);
317 dialog.setUrl(engine.url);
318 dialog.setPostData(QString::fromUtf8(engine.postData));
319
320 if (dialog.exec() != QDialog::Accepted) {
321 return;
322 }
323
324 engine.name = dialog.name();
325 engine.icon = dialog.icon();
326 engine.url = dialog.url();
327 engine.shortcut = dialog.shortcut();
328 engine.postData = dialog.postData().toUtf8();
329
330 if (engine.name.isEmpty() || engine.url.isEmpty()) {
331 return;
332 }
333
334 addEngine(engine);
335}
336
338{
340
341 Engine en;
342 en.name = engine->name();
343 en.url = engine->searchUrl(QSL("searchstring")).toString().replace(QLatin1String("searchstring"), QLatin1String("%s"));
344
345 if (engine->image().isNull()) {
346 en.icon = iconForSearchEngine(engine->searchUrl(QString()));
347 }
348 else {
349 en.icon = QIcon(QPixmap::fromImage(engine->image()));
350 }
351
352 en.suggestionsUrl = engine->getSuggestionsUrl();
354 en.postData = engine->getPostData(QSL("searchstring")).replace("searchstring", "%s");
355
356 addEngine(en);
357
358 connect(engine, &OpenSearchEngine::imageChanged, this, &SearchEnginesManager::engineChangedImage);
359}
360
362{
364
365 if (!url.isValid()) {
366 return;
367 }
368
369 qApp->setOverrideCursor(Qt::WaitCursor);
370
371 QNetworkReply* reply = mApp->networkManager()->get(QNetworkRequest(url));
372 reply->setParent(this);
373 connect(reply, &QNetworkReply::finished, this, &SearchEnginesManager::replyFinished);
374}
375
376void SearchEnginesManager::replyFinished()
377{
378 qApp->restoreOverrideCursor();
379
380 auto* reply = qobject_cast<QNetworkReply*>(sender());
381 if (!reply) {
382 return;
383 }
384
385 if (reply->error() != QNetworkReply::NoError) {
386 reply->close();
387 reply->deleteLater();
388 return;
389 }
390
391 OpenSearchReader reader;
392 OpenSearchEngine* engine = reader.read(reply);
393 engine->setNetworkAccessManager(mApp->networkManager());
394
395 reply->close();
396 reply->deleteLater();
397
398 if (checkEngine(engine)) {
399 addEngine(engine);
400 QMessageBox::information(nullptr, tr("Search Engine Added"), tr("Search Engine \"%1\" has been successfully added.").arg(engine->name()));
401 }
402}
403
404bool SearchEnginesManager::checkEngine(OpenSearchEngine* engine)
405{
406 if (!engine->isValid()) {
407 QString errorString = tr("Search Engine is not valid!");
408 QMessageBox::warning(nullptr, tr("Error"), tr("Error while adding Search Engine <br><b>Error Message: </b> %1").arg(errorString));
409
410 return false;
411 }
412
413 return true;
414}
415
417{
419
420 if (!m_allEngines.contains(engine)) {
421 return;
422 }
423
424 m_activeEngine = engine;
425 Q_EMIT activeEngineChanged();
426}
427
429{
431
432 if (!m_allEngines.contains(engine)) {
433 return;
434 }
435
436 m_defaultEngine = engine;
437 Q_EMIT defaultEngineChanged();
438}
439
441{
443
444 int index = m_allEngines.indexOf(engine);
445
446 if (index < 0) {
447 return;
448 }
449
450 QSqlQuery query(SqlDatabase::instance()->database());
451 query.prepare(QSL("DELETE FROM search_engines WHERE name=? AND url=?"));
452 query.bindValue(0, engine.name);
453 query.bindValue(1, engine.url);
454 query.exec();
455
456 m_allEngines.remove(index);
457 Q_EMIT enginesChanged();
458}
459
460void SearchEnginesManager::setAllEngines(const QVector<Engine> &engines)
461{
463
464 m_allEngines = engines;
465 Q_EMIT enginesChanged();
466}
467
468QVector<SearchEngine> SearchEnginesManager::allEngines()
469{
471
472 return m_allEngines;
473}
474
476{
477 Settings settings;
478 settings.beginGroup(QSL("SearchEngines"));
479 settings.setValue(QSL("activeEngine"), m_activeEngine.name);
480 settings.setValue(QSL("DefaultEngine"), m_defaultEngine.name);
481 settings.endGroup();
482
483 if (!m_saveScheduled) {
484 return;
485 }
486
487 // Well, this is not the best implementation to do as this is taking some time.
488 // Actually, it is delaying the quit of app for about a 1 sec on my machine with only
489 // 5 engines. Another problem is that deleting rows without VACUUM isn't actually freeing
490 // space in database.
491 //
492 // But as long as user is not playing with search engines every run it is acceptable.
493
494 QSqlQuery query(SqlDatabase::instance()->database());
495 query.exec(QSL("DELETE FROM search_engines"));
496
497 for (const Engine &en : std::as_const(m_allEngines)) {
498 query.prepare(QSL("INSERT INTO search_engines (name, icon, url, shortcut, suggestionsUrl, suggestionsParameters, postData) VALUES (?, ?, ?, ?, ?, ?, ?)"));
499 query.addBindValue(en.name);
500 query.addBindValue(iconToBase64(en.icon));
501 query.addBindValue(en.url);
502 query.addBindValue(en.shortcut);
503 query.addBindValue(en.suggestionsUrl);
504 query.addBindValue(en.suggestionsParameters);
505 query.addBindValue(en.postData);
506
507 query.exec();
508 }
509}
void setPostData(const QString &postData)
void setUrl(const QString &url)
void setIcon(const QIcon &icon)
void setName(const QString &name)
static QIcon iconForDomain(const QUrl &url, bool allowNull=false)
static QIcon emptyWebIcon()
A class representing a single search engine described in OpenSearch format.
QUrl searchUrl(const QString &searchTerm) const
QImage image() const
QByteArray getSuggestionsParameters()
void setNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
QByteArray getPostData(const QString &searchTerm) const
QString name
the name of the engine
A class reading a search engine description from an external source.
OpenSearchEngine * read(QIODevice *device)
QVector< Engine > allEngines()
void editEngine(const Engine &before, const Engine &after)
Engine engineForShortcut(const QString &shortcut)
void addEngine(const QUrl &url)
static QIcon iconForSearchEngine(const QUrl &url)
void setDefaultEngine(const Engine &engine)
void setActiveEngine(const Engine &engine)
void addEngineFromForm(const QVariantMap &formData, WebView *view)
void removeEngine(const Engine &engine)
LoadRequest searchResult(const Engine &engine, const QString &string)
SearchEnginesManager(QObject *parent=nullptr)
void setAllEngines(const QVector< Engine > &engines)
void beginGroup(const QString &prefix)
Definition: settings.cpp:79
void endGroup()
Definition: settings.cpp:84
QVariant value(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:74
void setValue(const QString &key, const QVariant &defaultValue=QVariant())
Definition: settings.cpp:69
static SqlDatabase * instance()
QIcon icon(bool allowNull=false) const
Definition: webview.cpp:90
QString title(bool allowEmpty=false) const
Definition: webview.cpp:107
#define mApp
int value(const QColor &c)
Definition: colors.cpp:238
#define QL1S(x)
Definition: qzcommon.h:44
#define QSL(x)
Definition: qzcommon.h:40
#define qzSettings
Definition: qzsettings.h:69
#define ENSURE_LOADED