PageRenderTime 40ms CodeModel.GetById 2ms app.highlight 33ms RepoModel.GetById 1ms app.codeStats 0ms

/src/lib/opensearch/searchenginesmanager.cpp

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