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"
KDE 4.6 API Reference