~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to plasma/generic/runners/bookmarks/bookmarksrunner.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *   Copyright 2007 Glenn Ergeerts <glenn.ergeerts@telenet.be>
 
3
 *
 
4
 *   This program is free software; you can redistribute it and/or modify
 
5
 *   it under the terms of the GNU Library General Public License as
 
6
 *   published by the Free Software Foundation; either version 2, or
 
7
 *   (at your option) any later version.
 
8
 *
 
9
 *   This program is distributed in the hope that it will be useful,
 
10
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 *   GNU General Public License for more details
 
13
 *
 
14
 *   You should have received a copy of the GNU Library General Public
 
15
 *   License along with this program; if not, write to the
 
16
 *   Free Software Foundation, Inc.,
 
17
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
18
 */
 
19
 
 
20
#include "bookmarksrunner.h"
 
21
 
 
22
#include <QDBusInterface>
 
23
#include <QDBusReply>
 
24
#include <QList>
 
25
#include <QStack>
 
26
#include <QSqlQuery>
 
27
#include <QDir>
 
28
 
 
29
#include <KIcon>
 
30
#include <KBookmarkManager>
 
31
#include <KMimeType>
 
32
#include <KMimeTypeTrader>
 
33
#include <KToolInvocation>
 
34
#include <KUrl>
 
35
#include <KStandardDirs>
 
36
#include <KDebug>
 
37
#include <KIO/Job>
 
38
 
 
39
BookmarksRunner::BookmarksRunner( QObject* parent, const QVariantList &args )
 
40
    : Plasma::AbstractRunner(parent, args)
 
41
{
 
42
    Q_UNUSED(args)
 
43
    setObjectName( QLatin1String("Bookmarks" ));
 
44
    m_icon = KIcon("bookmarks");
 
45
    m_bookmarkManager = KBookmarkManager::userBookmarksManager();
 
46
    m_browser = whichBrowser();
 
47
    addSyntax(Plasma::RunnerSyntax(":q:", i18n("Finds web browser bookmarks matching :q:.")));
 
48
    setDefaultSyntax(Plasma::RunnerSyntax(i18nc("list of all web browser bookmarks", "bookmarks"),
 
49
                                   i18n("List all web browser bookmarks")));
 
50
 
 
51
    connect(this, SIGNAL(prepare()), this, SLOT(prep()));
 
52
    connect(this, SIGNAL(teardown()), this, SLOT(down()));
 
53
 
 
54
    reloadConfiguration();
 
55
}
 
56
 
 
57
BookmarksRunner::~BookmarksRunner()
 
58
{
 
59
    if (!m_dbCacheFile.isEmpty()) {
 
60
        QFile db_CacheFile(m_dbCacheFile);
 
61
        if (db_CacheFile.exists()) {
 
62
            kDebug() << "Cache file was removed: " << db_CacheFile.remove();
 
63
        }
 
64
    }
 
65
}
 
66
 
 
67
void BookmarksRunner::reloadConfiguration()
 
68
{
 
69
    if (m_browser == Firefox) {
 
70
        if (QSqlDatabase::isDriverAvailable("QSQLITE")) {
 
71
            KConfigGroup grp = config();
 
72
            /* This allows the user to specify a profile database */
 
73
            m_dbFile = grp.readEntry<QString>("dbfile", "");
 
74
            if (m_dbFile.isEmpty() || QFile::exists(m_dbFile)) {
 
75
                //Try to get the right database file, the default profile is used
 
76
                KConfig firefoxProfile(QDir::homePath() + "/.mozilla/firefox/profiles.ini",
 
77
                                       KConfig::SimpleConfig);
 
78
                QStringList profilesList = firefoxProfile.groupList();
 
79
                profilesList = profilesList.filter(QRegExp("^Profile\\d+$"));
 
80
                int size = profilesList.size();
 
81
 
 
82
                QString profilePath;
 
83
                if (size == 1) {
 
84
                    // There is only 1 profile so we select it
 
85
                    KConfigGroup fGrp = firefoxProfile.group(profilesList.first());
 
86
                    profilePath = fGrp.readEntry("Path", "");
 
87
                } else {
 
88
                    // There are multiple profiles, find the default one
 
89
                    foreach(const QString & profileName, profilesList) {
 
90
                        KConfigGroup fGrp = firefoxProfile.group(profileName);
 
91
                        if (fGrp.readEntry<int>("Default", 0)) {
 
92
                            profilePath = fGrp.readEntry("Path", "");
 
93
                            break;
 
94
                        }
 
95
                    }
 
96
                }
 
97
 
 
98
                if (profilePath.isEmpty()) {
 
99
                    kDebug() << "No default firefox profile found";
 
100
                    m_db = QSqlDatabase();
 
101
                    return;
 
102
                }
 
103
 
 
104
                profilePath.prepend(QString("%1/.mozilla/firefox/").arg(QDir::homePath()));
 
105
                m_dbFile = profilePath + "/places.sqlite";
 
106
                grp.writeEntry("dbfile", m_dbFile);
 
107
            }
 
108
            m_db = QSqlDatabase::addDatabase("QSQLITE");
 
109
            m_db.setHostName("localhost");
 
110
        } else {
 
111
            kDebug() << "SQLITE driver isn't available";
 
112
            m_db = QSqlDatabase();
 
113
        }
 
114
    }
 
115
}
 
116
 
 
117
void BookmarksRunner::prep()
 
118
{
 
119
    m_browser = whichBrowser();
 
120
    if (m_browser == Firefox) {
 
121
        if (m_db.isValid()) {
 
122
            if (m_dbCacheFile.isEmpty()) {
 
123
                m_dbCacheFile = KStandardDirs::locateLocal("cache", "") + "bookmarkrunnerfirefoxdbfile.sqlite";
 
124
            }
 
125
 
 
126
            // ### DO NOT USE KIO FROM RUNNER THREADS!
 
127
            // ### This looks like a local copy, so use QFile::copy instead.
 
128
            KIO::Job *job = KIO::file_copy(m_dbFile, m_dbCacheFile, -1,
 
129
                                           KIO::HideProgressInfo | KIO::Overwrite);
 
130
            connect(job, SIGNAL(result(KJob*)), this, SLOT(dbCopied(KJob*)));
 
131
        }
 
132
    } else if (m_browser == Opera) {
 
133
        // open bookmarks file
 
134
        QString operaBookmarksFilePath = QDir::homePath() + "/.opera/bookmarks.adr";
 
135
        QFile operaBookmarksFile(operaBookmarksFilePath);
 
136
        if (!operaBookmarksFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
 
137
            kDebug() << "Could not open Operas Bookmark File " + operaBookmarksFilePath;
 
138
            return;
 
139
        }
 
140
 
 
141
        // check format
 
142
        QString firstLine = operaBookmarksFile.readLine();
 
143
        if (firstLine.compare("Opera Hotlist version 2.0\n")) {
 
144
            kDebug() << "Format of Opera Bookmarks File might have changed.";
 
145
        }
 
146
        operaBookmarksFile.readLine(); // skip options line ("Options: encoding = utf8, version=3")
 
147
        operaBookmarksFile.readLine(); // skip empty line
 
148
 
 
149
        // load contents
 
150
        QString contents = operaBookmarksFile.readAll();
 
151
        m_operaBookmarkEntries = contents.split("\n\n", QString::SkipEmptyParts);
 
152
 
 
153
        // close file
 
154
        operaBookmarksFile.close();
 
155
    }
 
156
}
 
157
 
 
158
void BookmarksRunner::dbCopied(KJob *)
 
159
{
 
160
    m_db.setDatabaseName(m_dbCacheFile);
 
161
    m_dbOK = m_db.open();
 
162
    kDebug() << "Database was opened: " << m_dbOK;
 
163
}
 
164
 
 
165
void BookmarksRunner::down()
 
166
{
 
167
    if (m_browser == Firefox) {
 
168
        if (m_db.isOpen()) {
 
169
            m_db.close();
 
170
            m_dbOK = false;
 
171
        }
 
172
    } else if (m_browser == Opera) {
 
173
        m_operaBookmarkEntries.clear();
 
174
    }
 
175
}
 
176
 
 
177
void BookmarksRunner::match(Plasma::RunnerContext &context)
 
178
{
 
179
    const QString term = context.query();
 
180
    if ((term.length() < 3) && (!context.singleRunnerQueryMode())) {
 
181
        return;
 
182
    }
 
183
 
 
184
    bool allBookmarks = term.compare(i18nc("list of all konqueror bookmarks", "bookmarks"),
 
185
                                     Qt::CaseInsensitive) == 0;
 
186
    switch (m_browser) {
 
187
        case Firefox:
 
188
            matchFirefoxBookmarks(context, allBookmarks, term);
 
189
            break;
 
190
        case Opera:
 
191
            matchOperaBookmarks(context, allBookmarks, term);
 
192
            break;
 
193
        case Default:
 
194
            matchKonquerorBookmarks(context, allBookmarks, term);
 
195
            break;
 
196
    }
 
197
}
 
198
 
 
199
KIcon BookmarksRunner::favicon(const KUrl &url)
 
200
{
 
201
    // query the favicons module
 
202
    const QString iconFile = KMimeType::favIconForUrl(url);
 
203
 
 
204
    if (iconFile.isEmpty()) {
 
205
        return m_icon;
 
206
    }
 
207
 
 
208
    return KIcon(iconFile);
 
209
}
 
210
 
 
211
void BookmarksRunner::matchOperaBookmarks(Plasma::RunnerContext& context, bool allBookmarks,
 
212
                                                        const QString& term)
 
213
{
 
214
    QList<Plasma::QueryMatch> matches;
 
215
 
 
216
    QLatin1String nameStart("\tNAME=");
 
217
    QLatin1String urlStart("\tURL=");
 
218
    QLatin1String descriptionStart("\tDESCRIPTION=");
 
219
 
 
220
    // search
 
221
    foreach (const QString & entry, m_operaBookmarkEntries) {
 
222
        if (!context.isValid()) {
 
223
            return;
 
224
        }
 
225
 
 
226
        QStringList entryLines = entry.split("\n");
 
227
        if (!entryLines.first().startsWith(QString("#URL"))) {
 
228
            continue; // skip folder entries
 
229
        }
 
230
        entryLines.pop_front();
 
231
 
 
232
        QString name;
 
233
        QString url;
 
234
        QString description;
 
235
 
 
236
        foreach (const QString & line, entryLines) {
 
237
            if (line.startsWith(nameStart)) {
 
238
                name = line.mid( QString(nameStart).length() ).simplified();
 
239
            } else if (line.startsWith(urlStart)) {
 
240
                url = line.mid( QString(urlStart).length() ).simplified();
 
241
            } else if (line.startsWith(descriptionStart)) {
 
242
                description = line.mid(QString(descriptionStart).length())
 
243
                              .simplified();
 
244
            }
 
245
        }
 
246
 
 
247
        if (url.simplified().isEmpty())
 
248
            continue; // skip useless entries
 
249
 
 
250
        Plasma::QueryMatch::Type type = Plasma::QueryMatch::NoMatch;
 
251
        qreal relevance = 0;
 
252
 
 
253
        if (name.compare(term, Qt::CaseInsensitive) == 0
 
254
             || description.compare(term, Qt::CaseInsensitive) == 0) {
 
255
            type = Plasma::QueryMatch::ExactMatch;
 
256
            relevance = 1.0;
 
257
        } else if (name.contains(term, Qt::CaseInsensitive)) {
 
258
            type = Plasma::QueryMatch::PossibleMatch;
 
259
            relevance = 0.45;
 
260
        } else if (description.contains(term, Qt::CaseInsensitive)) {
 
261
            type = Plasma::QueryMatch::PossibleMatch;
 
262
            relevance = 0.3;
 
263
        } else if (url.contains(term, Qt::CaseInsensitive)) {
 
264
            type = Plasma::QueryMatch::PossibleMatch;
 
265
            relevance = 0.2;
 
266
        } else if (allBookmarks) {
 
267
            type = Plasma::QueryMatch::PossibleMatch;
 
268
            relevance = 0.18;
 
269
        }
 
270
 
 
271
        if (type != Plasma::QueryMatch::NoMatch) {
 
272
            bool isNameEmpty = name.isEmpty();
 
273
            bool isDescriptionEmpty = description.isEmpty();
 
274
 
 
275
            Plasma::QueryMatch match(this);
 
276
            match.setType(type);
 
277
            match.setRelevance(relevance);
 
278
            match.setIcon(m_icon);
 
279
 
 
280
            // Try to set the following as text in this order: name, description, url
 
281
            match.setText( isNameEmpty
 
282
                           ?
 
283
                           (!isDescriptionEmpty ? description : url)
 
284
                           :
 
285
                           name );
 
286
 
 
287
            match.setData(url);
 
288
            matches << match;
 
289
        }
 
290
    }
 
291
    context.addMatches(term, matches);
 
292
}
 
293
 
 
294
void BookmarksRunner::matchKonquerorBookmarks(Plasma::RunnerContext& context, bool allBookmarks,
 
295
                                              const QString& term)
 
296
{
 
297
    KBookmarkGroup bookmarkGroup = m_bookmarkManager->root();
 
298
 
 
299
    QList<Plasma::QueryMatch> matches;
 
300
    QStack<KBookmarkGroup> groups;
 
301
 
 
302
    KBookmark bookmark = bookmarkGroup.first();
 
303
    while (!bookmark.isNull()) {
 
304
        if (!context.isValid()) {
 
305
            return;
 
306
        }
 
307
 
 
308
        if (bookmark.isSeparator()) {
 
309
            bookmark = bookmarkGroup.next(bookmark);
 
310
            continue;
 
311
        }
 
312
 
 
313
        if (bookmark.isGroup()) { // descend
 
314
            //kDebug () << "descending into" << bookmark.text();
 
315
            groups.push(bookmarkGroup);
 
316
            bookmarkGroup = bookmark.toGroup();
 
317
            bookmark = bookmarkGroup.first();
 
318
 
 
319
            while (bookmark.isNull() && !groups.isEmpty()) {
 
320
                if (!context.isValid()) {
 
321
                    return;
 
322
                }
 
323
 
 
324
                bookmark = bookmarkGroup;
 
325
                bookmarkGroup = groups.pop();
 
326
                bookmark = bookmarkGroup.next(bookmark);
 
327
            }
 
328
 
 
329
            continue;
 
330
        }
 
331
 
 
332
        Plasma::QueryMatch::Type type = Plasma::QueryMatch::NoMatch;
 
333
        qreal relevance = 0;
 
334
 
 
335
        const QString text = bookmark.text();
 
336
        const QString url = bookmark.url().prettyUrl();
 
337
        if (text.compare(term, Qt::CaseInsensitive) == 0) {
 
338
            type = Plasma::QueryMatch::ExactMatch;
 
339
            relevance = 1.0;
 
340
        } else if (text.contains(term, Qt::CaseInsensitive)) {
 
341
            type = Plasma::QueryMatch::PossibleMatch;
 
342
            relevance = 0.45;
 
343
        } else if (url.contains(term, Qt::CaseInsensitive)) {
 
344
            type = Plasma::QueryMatch::PossibleMatch;
 
345
            relevance = 0.2;
 
346
        } else if (allBookmarks) {
 
347
            type = Plasma::QueryMatch::PossibleMatch;
 
348
            relevance = 0.18;
 
349
        }
 
350
 
 
351
        if (type != Plasma::QueryMatch::NoMatch) {
 
352
            //kDebug() << "Found bookmark: " << bookmark.text() << " (" << bookmark.url().prettyUrl() << ")";
 
353
            Plasma::QueryMatch match(this);
 
354
 
 
355
            QIcon icon = favicon(bookmark.url());
 
356
            if (icon.isNull()) {
 
357
                match.setIcon(m_icon);
 
358
            } else {
 
359
                match.setIcon(icon);
 
360
            }
 
361
 
 
362
            match.setType(type);
 
363
            match.setRelevance(relevance);
 
364
            match.setText(bookmark.text());
 
365
            match.setData(bookmark.url().url());
 
366
            matches << match;
 
367
        }
 
368
 
 
369
        bookmark = bookmarkGroup.next(bookmark);
 
370
        while (bookmark.isNull() && !groups.isEmpty()) {
 
371
            if (!context.isValid()) {
 
372
                return;
 
373
            }
 
374
 
 
375
            bookmark = bookmarkGroup;
 
376
            bookmarkGroup = groups.pop();
 
377
            //kDebug() << "ascending from" << bookmark.text() << "to" << bookmarkGroup.text();
 
378
            bookmark = bookmarkGroup.next(bookmark);
 
379
        }
 
380
    }
 
381
    context.addMatches(term, matches);
 
382
}
 
383
 
 
384
void BookmarksRunner::matchFirefoxBookmarks(Plasma::RunnerContext& context, bool allBookmarks, const QString& term)
 
385
{
 
386
    if (!m_dbOK) {
 
387
        return;
 
388
    }
 
389
 
 
390
    QList<Plasma::QueryMatch> matches;
 
391
    QString tmpTerm = term;
 
392
    QSqlQuery query;
 
393
    if (allBookmarks) {
 
394
        query = QSqlQuery("SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url," \
 
395
                    "moz_places.favicon_id FROM moz_bookmarks, moz_places WHERE " \
 
396
                    "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id");
 
397
    } else {
 
398
        const QString escapedTerm = tmpTerm.replace('\'', "\\'");
 
399
        query = QSqlQuery("SELECT moz_bookmarks.fk, moz_bookmarks.title, moz_places.url," \
 
400
                        "moz_places.favicon_id FROM moz_bookmarks, moz_places WHERE " \
 
401
                        "moz_bookmarks.type = 1 AND moz_bookmarks.fk = moz_places.id AND " \
 
402
                        "(moz_bookmarks.title LIKE  '%" + escapedTerm + "%' or moz_places.url LIKE '%"
 
403
                        + escapedTerm + "%')");
 
404
    }
 
405
 
 
406
    while (query.next() && context.isValid()) {
 
407
        const QString title = query.value(1).toString();
 
408
        const QUrl url = query.value(2).toString();
 
409
        //const int favicon_id = query.value(3).toInt();
 
410
 
 
411
        if (title.isEmpty() || url.isEmpty() || url.scheme().contains("place")) {
 
412
            //Don't use bookmarks with empty title, url or Firefox intern url
 
413
            kDebug() << "element was not added";
 
414
            continue;
 
415
        }
 
416
 
 
417
        Plasma::QueryMatch::Type type = Plasma::QueryMatch::NoMatch;
 
418
        qreal relevance = 0;
 
419
 
 
420
        if (title.compare(term, Qt::CaseInsensitive) == 0) {
 
421
            type = Plasma::QueryMatch::ExactMatch;
 
422
            relevance = 1.0;
 
423
        } else if (title.contains(term, Qt::CaseInsensitive)) {
 
424
            type = Plasma::QueryMatch::PossibleMatch;
 
425
            relevance = 0.45;
 
426
        } else if (url.toString().contains(term, Qt::CaseInsensitive)) {
 
427
            type = Plasma::QueryMatch::PossibleMatch;
 
428
            relevance = 0.2;
 
429
        } else if (allBookmarks) {
 
430
            type = Plasma::QueryMatch::PossibleMatch;
 
431
            relevance = 0.18;
 
432
        }
 
433
 
 
434
        Plasma::QueryMatch match(this);
 
435
        QIcon icon = favicon(url);
 
436
        if (icon.isNull()) {
 
437
            match.setIcon(m_icon);
 
438
        } else {
 
439
            match.setIcon(icon);
 
440
        }
 
441
        match.setText(title);
 
442
        match.setData(url);
 
443
        match.setType(type);
 
444
        match.setRelevance(relevance);
 
445
        matches << match;
 
446
    }
 
447
    context.addMatches(term, matches);
 
448
}
 
449
 
 
450
BookmarksRunner::Browser BookmarksRunner::whichBrowser()
 
451
{
 
452
    //HACK find the default browser
 
453
    KConfigGroup config(KSharedConfig::openConfig("kdeglobals"), QLatin1String("General") );
 
454
    QString exec = config.readPathEntry(QLatin1String("BrowserApplication"), QString());
 
455
    if (exec.isEmpty()) {
 
456
        KService::Ptr service = KMimeTypeTrader::self()->preferredService("text/html");
 
457
        if (service) {
 
458
            exec = service->exec();
 
459
        }
 
460
    }
 
461
 
 
462
    //kDebug() << exec;
 
463
    if (exec.contains("firefox", Qt::CaseInsensitive)) {
 
464
        return Firefox;
 
465
    } else if (exec.contains("opera", Qt::CaseInsensitive)) {
 
466
        return Opera;
 
467
    } else {
 
468
        return Default;
 
469
    }
 
470
}
 
471
 
 
472
void BookmarksRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &action)
 
473
{
 
474
    Q_UNUSED(context);
 
475
    const QString term = action.data().toString();
 
476
    KUrl url = KUrl(term);
 
477
 
 
478
    //support urls like "kde.org" by transforming them to http://kde.org
 
479
    if (url.protocol().isEmpty()) {
 
480
        const int idx = term.indexOf('/');
 
481
 
 
482
        url.clear();
 
483
        url.setHost(term.left(idx));
 
484
        if (idx != -1) {
 
485
            //allow queries
 
486
            const int queryStart = term.indexOf('?', idx);
 
487
            int pathLength = -1;
 
488
            if ((queryStart > -1) && (idx < queryStart)) {
 
489
                pathLength = queryStart - idx;
 
490
                url.setQuery(term.mid(queryStart));
 
491
            }
 
492
 
 
493
            url.setPath(term.mid(idx, pathLength));
 
494
        }
 
495
        url.setProtocol("http");
 
496
    }
 
497
 
 
498
    //kDebug() << "BookmarksRunner::run opening: " << url.url();
 
499
    KToolInvocation::invokeBrowser(url.url());
 
500
}
 
501
 
 
502
QMimeData * BookmarksRunner::mimeDataForMatch(const Plasma::QueryMatch * match)
 
503
{
 
504
    QMimeData * result = new QMimeData();
 
505
    QList<QUrl> urls;
 
506
    urls << QUrl(match->data().toString());
 
507
    result->setUrls(urls);
 
508
 
 
509
    result->setText(match->data().toString());
 
510
 
 
511
    return result;
 
512
}
 
513
 
 
514
#include "bookmarksrunner.moc"