• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

Plasma

runnercontext.cpp

Go to the documentation of this file.
00001 /*
00002  *   Copyright 2006-2007 Aaron Seigo <aseigo@kde.org>
00003  *
00004  *   This program is free software; you can redistribute it and/or modify
00005  *   it under the terms of the GNU Library General Public License as
00006  *   published by the Free Software Foundation; either version 2, or
00007  *   (at your option) any later version.
00008  *
00009  *   This program is distributed in the hope that it will be useful,
00010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  *   GNU General Public License for more details
00013  *
00014  *   You should have received a copy of the GNU Library General Public
00015  *   License along with this program; if not, write to the
00016  *   Free Software Foundation, Inc.,
00017  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00018  */
00019 
00020 #include "runnercontext.h"
00021 
00022 #include <cmath>
00023 
00024 #include <QReadWriteLock>
00025 
00026 #include <QDir>
00027 #include <QFile>
00028 #include <QFileInfo>
00029 #include <QSharedData>
00030 
00031 #include <kcompletion.h>
00032 #include <kconfiggroup.h>
00033 #include <kdebug.h>
00034 #include <kmimetype.h>
00035 #include <kshell.h>
00036 #include <kstandarddirs.h>
00037 #include <kurl.h>
00038 #include <kprotocolinfo.h>
00039 
00040 #include "abstractrunner.h"
00041 #include "querymatch.h"
00042 
00043 //#define LOCK_FOR_READ(d) if (d->policy == Shared) { d->lock.lockForRead(); }
00044 //#define LOCK_FOR_WRITE(d) if (d->policy == Shared) { d->lock.lockForWrite(); }
00045 //#define UNLOCK(d) if (d->policy == Shared) { d->lock.unlock(); }
00046 
00047 #define LOCK_FOR_READ(d) d->lock.lockForRead();
00048 #define LOCK_FOR_WRITE(d) d->lock.lockForWrite();
00049 #define UNLOCK(d) d->lock.unlock();
00050 
00051 namespace Plasma
00052 {
00053 
00054 /*
00055 Corrects the case of the last component in a path (e.g. /usr/liB -> /usr/lib)
00056 path: The path to be processed.
00057 correctCasePath: The corrected-case path
00058 mustBeDir: Tells whether the last component is a folder or doesn't matter
00059 Returns true on success and false on error, in case of error, correctCasePath is not modified
00060 */
00061 bool correctLastComponentCase(const QString &path, QString &correctCasePath, const bool mustBeDir)
00062 {
00063     //kDebug() << "Correcting " << path;
00064 
00065     // If the file already exists then no need to search for it.
00066     if (QFile::exists(path)) {
00067         correctCasePath = path;
00068         //kDebug() << "Correct path is" << correctCasePath;
00069         return true;
00070     }
00071 
00072     const QFileInfo pathInfo(path);
00073 
00074     const QDir fileDir = pathInfo.dir();
00075     //kDebug() << "Directory is" << fileDir;
00076 
00077     const QString filename = pathInfo.fileName();
00078     //kDebug() << "Filename is" << filename;
00079 
00080     //kDebug() << "searching for a" << (mustBeDir ? "directory" : "directory/file");
00081 
00082     const QStringList matchingFilenames = fileDir.entryList(QStringList(filename),
00083                                           mustBeDir ? QDir::Dirs : QDir::NoFilter);
00084 
00085     if (matchingFilenames.empty()) {
00086         //kDebug() << "No matches found!!\n";
00087         return false;
00088     } else {
00089         /*if (matchingFilenames.size() > 1) {
00090             kDebug() << "Found multiple matches!!\n";
00091         }*/
00092 
00093         if (fileDir.path().endsWith(QDir::separator())) {
00094             correctCasePath = fileDir.path() + matchingFilenames[0];
00095         } else {
00096             correctCasePath = fileDir.path() + QDir::separator() + matchingFilenames[0];
00097         }
00098 
00099         //kDebug() << "Correct path is" << correctCasePath;
00100         return true;
00101     }
00102 }
00103 
00104 /*
00105 Corrects the case of a path (e.g. /uSr/loCAL/bIN -> /usr/local/bin)
00106 path: The path to be processed.
00107 corrected: The corrected-case path
00108 Returns true on success and false on error, in case of error, corrected is not modified
00109 */
00110 bool correctPathCase(const QString& path, QString &corrected)
00111 {
00112     // early exit check
00113     if (QFile::exists(path)) {
00114         corrected = path;
00115         return true;
00116     }
00117 
00118     // path components
00119     QStringList components = QString(path).split(QDir::separator());
00120 
00121     if (components.size() < 1) {
00122         return false;
00123     }
00124 
00125     const bool mustBeDir = components.back().isEmpty();
00126 
00127     //kDebug() << "Components are" << components;
00128 
00129     if (mustBeDir) {
00130         components.pop_back();
00131     }
00132 
00133     if (components.isEmpty()) {
00134         return true;
00135     }
00136 
00137     QString correctPath;
00138     const unsigned initialComponents = components.size();
00139     for (unsigned i = 0; i < initialComponents - 1; i ++) {
00140         const QString tmp = components[0] + QDir::separator() + components[1];
00141 
00142         if (!correctLastComponentCase(tmp, correctPath, components.size() > 2 || mustBeDir)) {
00143             //kDebug() << "search was not successful";
00144             return false;
00145         }
00146 
00147         components.removeFirst();
00148         components[0] = correctPath;
00149     }
00150 
00151     corrected = correctPath;
00152     return true;
00153 }
00154 
00155 class RunnerContextPrivate : public QSharedData
00156 {
00157     public:
00158         RunnerContextPrivate(RunnerContext *context)
00159             : QSharedData(),
00160               type(RunnerContext::UnknownType),
00161               q(context),
00162               singleRunnerQueryMode(false)
00163         {
00164         }
00165 
00166         RunnerContextPrivate(const RunnerContextPrivate &p)
00167             : QSharedData(),
00168               launchCounts(p.launchCounts),
00169               type(RunnerContext::None),
00170               q(p.q),
00171               singleRunnerQueryMode(false)
00172         {
00173             //kDebug() << "¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿¿boo yeah" << type;
00174         }
00175 
00176         ~RunnerContextPrivate()
00177         {
00178         }
00179 
00184         void determineType()
00185         {
00186             // NOTE! this method must NEVER be called from
00187             // code that may be running in multiple threads
00188             // with the same data.
00189             type = RunnerContext::UnknownType;
00190             QString path = QDir::cleanPath(KShell::tildeExpand(term));
00191 
00192             int space = path.indexOf(' ');
00193             if (!KStandardDirs::findExe(path.left(space)).isEmpty()) {
00194                 // it's a shell command if there's a space because that implies
00195                 // that it has arguments!
00196                 type = (space > 0) ? RunnerContext::ShellCommand :
00197                                      RunnerContext::Executable;
00198             } else {
00199                 KUrl url(term);
00200                 // check for a normal URL first
00201                 //kDebug() << url << KProtocolInfo::protocolClass(url.protocol()) << url.hasHost() <<
00202                 //    url.host() << url.isLocalFile() << path << path.indexOf('/');
00203                 const bool hasProtocol = !url.protocol().isEmpty();
00204                 const bool isLocalProtocol = KProtocolInfo::protocolClass(url.protocol()) == ":local";
00205                 if (hasProtocol && 
00206                     ((!isLocalProtocol && url.hasHost()) ||
00207                      (isLocalProtocol && url.protocol() != "file"))) {
00208                     // we either have a network protocol with a host, so we can show matches for it
00209                     // or we have a non-file url that may be local so a host isn't required
00210                     type = RunnerContext::NetworkLocation;
00211                 } else if (isLocalProtocol) {
00212                     // at this point in the game, we assume we have a path,
00213                     // but if a path doesn't have any slashes
00214                     // it's too ambiguous to be sure we're in a filesystem context
00215                     path = QDir::cleanPath(url.toLocalFile());
00216                     //kDebug( )<< "slash check" << path;
00217                     if (hasProtocol || ((path.indexOf('/') != -1 || path.indexOf('\\') != -1))) {
00218                         QString correctCasePath;
00219                         if (correctPathCase(path, correctCasePath)) {
00220                             path = correctCasePath;
00221                             QFileInfo info(path);
00222                             //kDebug( )<< "correct cas epath is" << correctCasePath << info.isSymLink() <<
00223                             //    info.isDir() << info.isFile();
00224 
00225                             if (info.isSymLink()) {
00226                                 path = info.canonicalFilePath();
00227                                 info = QFileInfo(path);
00228                             }
00229                             if (info.isDir()) {
00230                                 type = RunnerContext::Directory;
00231                                 mimeType = "inode/folder";
00232                             } else if (info.isFile()) {
00233                                 type = RunnerContext::File;
00234                                 KMimeType::Ptr mimeTypePtr = KMimeType::findByPath(path);
00235                                 if (mimeTypePtr) {
00236                                     mimeType = mimeTypePtr->name();
00237                                 }
00238                             }
00239                         }
00240                     }
00241                 }
00242             }
00243 
00244             //kDebug() << "term2type" << term << type;
00245         }
00246 
00247         void invalidate()
00248         {
00249             q = &s_dummyContext;
00250         }
00251 
00252         QReadWriteLock lock;
00253         QList<QueryMatch> matches;
00254         QMap<QString, const QueryMatch*> matchesById;
00255         QHash<QString, int> launchCounts;
00256         QString term;
00257         QString mimeType;
00258         RunnerContext::Type type;
00259         RunnerContext * q;
00260         static RunnerContext s_dummyContext;
00261         bool singleRunnerQueryMode;
00262 };
00263 
00264 RunnerContext RunnerContextPrivate::s_dummyContext;
00265 
00266 RunnerContext::RunnerContext(QObject *parent)
00267     : QObject(parent),
00268       d(new RunnerContextPrivate(this))
00269 {
00270 }
00271 
00272 //copy ctor
00273 RunnerContext::RunnerContext(RunnerContext &other, QObject *parent)
00274     : QObject(parent)
00275 {
00276     LOCK_FOR_READ(other.d)
00277     d = other.d;
00278     UNLOCK(other.d)
00279 }
00280 
00281 RunnerContext::~RunnerContext()
00282 {
00283 }
00284 
00285 RunnerContext &RunnerContext::operator=(const RunnerContext &other)
00286 {
00287     if (this->d == other.d) {
00288         return *this;
00289     }
00290 
00291     QExplicitlySharedDataPointer<Plasma::RunnerContextPrivate> oldD = d;
00292     LOCK_FOR_WRITE(d)
00293     LOCK_FOR_READ(other.d)
00294     d = other.d;
00295     UNLOCK(other.d)
00296     UNLOCK(oldD)
00297     return *this;
00298 }
00299 
00300 void RunnerContext::reset()
00301 {
00302     // We will detach if we are a copy of someone. But we will reset
00303     // if we are the 'main' context others copied from. Resetting
00304     // one RunnerContext makes all the copies obsolete.
00305 
00306     // We need to mark the q pointer of the detached RunnerContextPrivate
00307     // as dirty on detach to avoid receiving results for old queries
00308     d->invalidate();
00309 
00310     d.detach();
00311 
00312     // Now that we detached the d pointer we need to reset its q pointer
00313 
00314     d->q = this;
00315 
00316     // we still have to remove all the matches, since if the
00317     // ref count was 1 (e.g. only the RunnerContext is using
00318     // the dptr) then we won't get a copy made
00319     if (!d->matches.isEmpty()) {
00320         d->matchesById.clear();
00321         d->matches.clear();
00322         emit matchesChanged();
00323     }
00324 
00325     d->term.clear();
00326     d->mimeType.clear();
00327     d->type = UnknownType;
00328     d->singleRunnerQueryMode = false;
00329     //kDebug() << "match count" << d->matches.count();
00330 }
00331 
00332 void RunnerContext::setQuery(const QString &term)
00333 {
00334     reset();
00335 
00336     if (term.isEmpty()) {
00337         return;
00338     }
00339 
00340     d->term = term;
00341     d->determineType();
00342 }
00343 
00344 QString RunnerContext::query() const
00345 {
00346     // the query term should never be set after
00347     // a search starts. in fact, reset() ensures this
00348     // and setQuery(QString) calls reset()
00349     return d->term;
00350 }
00351 
00352 RunnerContext::Type RunnerContext::type() const
00353 {
00354     return d->type;
00355 }
00356 
00357 QString RunnerContext::mimeType() const
00358 {
00359     return d->mimeType;
00360 }
00361 
00362 bool RunnerContext::isValid() const
00363 {
00364     // if our qptr is dirty, we aren't useful anymore
00365     return (d->q != &(d->s_dummyContext));
00366 }
00367 
00368 bool RunnerContext::addMatches(const QString &term, const QList<QueryMatch> &matches)
00369 {
00370     Q_UNUSED(term)
00371 
00372     if (matches.isEmpty() || !isValid()) {
00373        //Bail out if the query is empty or the qptr is dirty
00374         return false;
00375     }
00376 
00377     LOCK_FOR_WRITE(d)
00378     foreach (QueryMatch match, matches) {
00379         // Give previously launched matches a slight boost in relevance
00380         // The boost smoothly saturates to 0.5;
00381         if (int count = d->launchCounts.value(match.id())) {
00382             match.setRelevance(match.relevance() + 0.5 * (1-exp(-count*0.3)));
00383         }
00384 
00385         d->matches.append(match);
00386 #ifndef NDEBUG
00387         if (d->matchesById.contains(match.id())) {
00388             kDebug() << "Duplicate match id " << match.id() << "from" << match.runner()->name();
00389         }
00390 #endif
00391         d->matchesById.insert(match.id(), &d->matches.at(d->matches.size() - 1));
00392     }
00393     UNLOCK(d);
00394     //kDebug()<< "add matches";
00395     // A copied searchContext may share the d pointer,
00396     // we always want to sent the signal of the object that created
00397     // the d pointer
00398     emit d->q->matchesChanged();
00399 
00400     return true;
00401 }
00402 
00403 bool RunnerContext::addMatch(const QString &term, const QueryMatch &match)
00404 {
00405     Q_UNUSED(term)
00406 
00407     if (!isValid()) {
00408         // Bail out if the qptr is dirty
00409         return false;
00410     }
00411 
00412     QueryMatch m(match); // match must be non-const to modify relevance
00413 
00414     LOCK_FOR_WRITE(d)
00415 
00416     if (int count = d->launchCounts.value(m.id())) {
00417         m.setRelevance(m.relevance() + 0.05 * count);
00418     }
00419 
00420     d->matches.append(m);
00421     d->matchesById.insert(m.id(), &d->matches.at(d->matches.size() - 1));
00422     UNLOCK(d);
00423     //kDebug()<< "added match" << match->text();
00424     emit d->q->matchesChanged();
00425 
00426     return true;
00427 }
00428 
00429 bool RunnerContext::removeMatches(const QStringList matchIdList)
00430 {
00431     if (!isValid()) {
00432         return false;
00433     }
00434 
00435     QStringList presentMatchIdList;
00436     QList<const QueryMatch*> presentMatchList;
00437 
00438     LOCK_FOR_READ(d)
00439     foreach(const QString &matchId, matchIdList) {
00440         const QueryMatch* match = d->matchesById.value(matchId, 0);
00441         if (match) {
00442             presentMatchList << match;
00443             presentMatchIdList << matchId;
00444         }
00445     }
00446     UNLOCK(d)
00447 
00448     if (presentMatchIdList.isEmpty()) {
00449         return false;
00450     }
00451 
00452     LOCK_FOR_WRITE(d)
00453     foreach(const QueryMatch *match, presentMatchList) {
00454         d->matches.removeAll(*match);
00455     }
00456     foreach(const QString &matchId, presentMatchIdList) {
00457         d->matchesById.remove(matchId);
00458     }
00459     UNLOCK(d)
00460 
00461     emit d->q->matchesChanged();
00462 
00463     return true;
00464 }
00465 
00466 bool RunnerContext::removeMatch(const QString matchId)
00467 {
00468     if (!isValid()) {
00469         return false;
00470     }
00471     LOCK_FOR_READ(d)
00472     const QueryMatch* match = d->matchesById.value(matchId, 0);
00473     UNLOCK(d)
00474     if (!match) {
00475         return false;
00476     }
00477     LOCK_FOR_WRITE(d)
00478     d->matches.removeAll(*match);
00479     d->matchesById.remove(matchId);
00480     UNLOCK(d)
00481     emit d->q->matchesChanged();
00482 
00483     return true;
00484 }
00485 
00486 QList<QueryMatch> RunnerContext::matches() const
00487 {
00488     LOCK_FOR_READ(d)
00489     QList<QueryMatch> matches = d->matches;
00490     UNLOCK(d);
00491     return matches;
00492 }
00493 
00494 QueryMatch RunnerContext::match(const QString &id) const
00495 {
00496     LOCK_FOR_READ(d)
00497     const QueryMatch *match = d->matchesById.value(id, 0);
00498     UNLOCK(d)
00499 
00500     if (match) {
00501         return *match;
00502     }
00503 
00504     return QueryMatch(0);
00505 }
00506 
00507 void RunnerContext::setSingleRunnerQueryMode(bool enabled)
00508 {
00509     d->singleRunnerQueryMode = enabled;
00510 }
00511 
00512 bool RunnerContext::singleRunnerQueryMode() const
00513 {
00514     return d->singleRunnerQueryMode;
00515 }
00516 
00517 void RunnerContext::restore(const KConfigGroup &config)
00518 {
00519     const QStringList cfgList = config.readEntry("LaunchCounts", QStringList());
00520 
00521     const QRegExp r("(\\d*) (.*)");
00522     foreach (const QString& entry, cfgList) {
00523         r.indexIn(entry);
00524         int count = r.cap(1).toInt();
00525         QString id = r.cap(2);
00526         d->launchCounts[id] = count;
00527     }
00528 }
00529 
00530 void RunnerContext::save(KConfigGroup &config)
00531 {
00532     QStringList countList;
00533 
00534     typedef QHash<QString, int>::const_iterator Iterator;
00535     Iterator end = d->launchCounts.constEnd();
00536     for (Iterator i = d->launchCounts.constBegin(); i != end; ++i) {
00537         countList << QString("%2 %1").arg(i.key()).arg(i.value());
00538     }
00539 
00540     config.writeEntry("LaunchCounts", countList);
00541     config.sync();
00542 }
00543 
00544 void RunnerContext::run(const QueryMatch &match)
00545 {
00546     ++d->launchCounts[match.id()];
00547     match.run(*this);
00548 }
00549 
00550 } // Plasma namespace
00551 
00552 #include "runnercontext.moc"

Plasma

Skip menu "Plasma"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal