KIO
kurlcompletion.cpp
Go to the documentation of this file.
00001 /* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*- 00002 00003 This file is part of the KDE libraries 00004 Copyright (C) 2000 David Smith <dsmith@algonet.se> 00005 Copyright (C) 2004 Scott Wheeler <wheeler@kde.org> 00006 00007 This class was inspired by a previous KUrlCompletion by 00008 Henner Zeller <zeller@think.de> 00009 00010 This library is free software; you can redistribute it and/or 00011 modify it under the terms of the GNU Library General Public 00012 License as published by the Free Software Foundation; either 00013 version 2 of the License, or (at your option) any later version. 00014 00015 This library is distributed in the hope that it will be useful, 00016 but WITHOUT ANY WARRANTY; without even the implied warranty of 00017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00018 Library General Public License for more details. 00019 00020 You should have received a copy of the GNU Library General Public License 00021 along with this library; see the file COPYING.LIB. If not, write to 00022 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00023 Boston, MA 02110-1301, USA. 00024 */ 00025 00026 #include "kurlcompletion.h" 00027 00028 #include <config.h> 00029 00030 #include <stdlib.h> 00031 #include <assert.h> 00032 #include <limits.h> 00033 00034 #include <QtCore/QCoreApplication> 00035 #include <QtCore/QMutableStringListIterator> 00036 #include <QtCore/QRegExp> 00037 #include <QtCore/QTimer> 00038 #include <QtCore/QDir> 00039 #include <QtCore/QDirIterator> 00040 #include <QtCore/QFile> 00041 #include <QtCore/QTextIStream> 00042 #include <QtCore/QThread> 00043 #include <QtGui/QActionEvent> 00044 00045 #include <kauthorized.h> 00046 #include <kdebug.h> 00047 #include <kurl.h> 00048 #include <kio/job.h> 00049 #include <kprotocolmanager.h> 00050 #include <kconfig.h> 00051 #include <kglobal.h> 00052 #include <kglobalsettings.h> 00053 #include <kde_file.h> 00054 00055 #include <sys/types.h> 00056 #include <dirent.h> 00057 #include <unistd.h> 00058 #include <sys/stat.h> 00059 #include <pwd.h> 00060 #include <time.h> 00061 #include <sys/param.h> 00062 #include <kconfiggroup.h> 00063 00064 #ifdef Q_WS_WIN 00065 #include <kkernel_win.h> 00066 #endif 00067 00068 static bool expandTilde(QString&); 00069 static bool expandEnv(QString&); 00070 00071 static QString unescape(const QString& text); 00072 00073 // Permission mask for files that are executable by 00074 // user, group or other 00075 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) 00076 00077 // Constants for types of completion 00078 enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; 00079 00080 class CompletionThread; 00081 00084 // KUrlCompletionPrivate 00085 // 00086 class KUrlCompletionPrivate 00087 { 00088 public: 00089 KUrlCompletionPrivate(KUrlCompletion* parent) 00090 : q(parent), 00091 url_auto_completion(true), 00092 userListThread(0), 00093 dirListThread(0) { 00094 } 00095 00096 ~KUrlCompletionPrivate(); 00097 00098 void _k_slotEntries(KIO::Job*, const KIO::UDSEntryList&); 00099 void _k_slotIOFinished(KJob*); 00100 00101 class MyURL; 00102 bool userCompletion(const MyURL& url, QString* match); 00103 bool envCompletion(const MyURL& url, QString* match); 00104 bool exeCompletion(const MyURL& url, QString* match); 00105 bool fileCompletion(const MyURL& url, QString* match); 00106 bool urlCompletion(const MyURL& url, QString* match); 00107 00108 bool isAutoCompletion(); 00109 00110 // List the next dir in m_dirs 00111 QString listDirectories(const QStringList&, 00112 const QString&, 00113 bool only_exe = false, 00114 bool only_dir = false, 00115 bool no_hidden = false, 00116 bool stat_files = true); 00117 00118 void listUrls(const QList<KUrl> &urls, 00119 const QString& filter = QString(), 00120 bool only_exe = false, 00121 bool no_hidden = false); 00122 00123 void addMatches(const QStringList&); 00124 QString finished(); 00125 00126 void init(); 00127 00128 void setListedUrl(int compl_type /* enum ComplType */, 00129 const QString& dir = QString(), 00130 const QString& filter = QString(), 00131 bool no_hidden = false); 00132 00133 bool isListedUrl(int compl_type /* enum ComplType */, 00134 const QString& dir = QString(), 00135 const QString& filter = QString(), 00136 bool no_hidden = false); 00137 00138 KUrlCompletion* q; 00139 QList<KUrl> list_urls; 00140 00141 bool onlyLocalProto; 00142 00143 // urlCompletion() in Auto/Popup mode? 00144 bool url_auto_completion; 00145 00146 // Append '/' to directories in Popup mode? 00147 // Doing that stat's all files and is slower 00148 bool popup_append_slash; 00149 00150 // Keep track of currently listed files to avoid reading them again 00151 QString last_path_listed; 00152 QString last_file_listed; 00153 QString last_prepend; 00154 int last_compl_type; 00155 int last_no_hidden; 00156 00157 QString cwd; // "current directory" = base dir for completion 00158 00159 KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion 00160 bool replace_env; 00161 bool replace_home; 00162 bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path 00163 00164 KIO::ListJob* list_job; // kio job to list directories 00165 00166 QString prepend; // text to prepend to listed items 00167 QString compl_text; // text to pass on to KCompletion 00168 00169 // Filters for files read with kio 00170 bool list_urls_only_exe; // true = only list executables 00171 bool list_urls_no_hidden; 00172 QString list_urls_filter; // filter for listed files 00173 00174 CompletionThread* userListThread; 00175 CompletionThread* dirListThread; 00176 }; 00177 00183 class CompletionMatchEvent : public QEvent 00184 { 00185 public: 00186 CompletionMatchEvent(CompletionThread* thread) : 00187 QEvent(uniqueType()), 00188 m_completionThread(thread) 00189 {} 00190 00191 CompletionThread* completionThread() const { 00192 return m_completionThread; 00193 } 00194 static Type uniqueType() { 00195 return Type(User + 61080); 00196 } 00197 00198 private: 00199 CompletionThread* m_completionThread; 00200 }; 00201 00202 class CompletionThread : public QThread 00203 { 00204 protected: 00205 CompletionThread(KUrlCompletionPrivate* receiver) : 00206 QThread(), 00207 m_prepend(receiver->prepend), 00208 m_complete_url(receiver->complete_url), 00209 m_receiver(receiver), 00210 m_terminationRequested(false) 00211 {} 00212 00213 public: 00214 void requestTermination() { 00215 m_terminationRequested = true; 00216 } 00217 QStringList matches() const { 00218 return m_matches; 00219 } 00220 00221 protected: 00222 void addMatch(const QString& match) { 00223 m_matches.append(match); 00224 } 00225 bool terminationRequested() const { 00226 return m_terminationRequested; 00227 } 00228 void done() { 00229 if (!m_terminationRequested) 00230 qApp->postEvent(m_receiver->q, new CompletionMatchEvent(this)); 00231 else 00232 deleteLater(); 00233 } 00234 00235 const QString m_prepend; 00236 const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path 00237 00238 private: 00239 KUrlCompletionPrivate* m_receiver; 00240 QStringList m_matches; 00241 bool m_terminationRequested; 00242 }; 00243 00249 class UserListThread : public CompletionThread 00250 { 00251 public: 00252 UserListThread(KUrlCompletionPrivate* receiver) : 00253 CompletionThread(receiver) 00254 {} 00255 00256 protected: 00257 virtual void run() { 00258 static const QChar tilde = '~'; 00259 00260 // we don't need to handle prepend here, right? ~user is always at pos 0 00261 assert(m_prepend.isEmpty()); 00262 struct passwd* pw; 00263 while ((pw = ::getpwent()) && !terminationRequested()) 00264 addMatch(tilde + QString::fromLocal8Bit(pw->pw_name)); 00265 00266 ::endpwent(); 00267 00268 addMatch(QString(tilde)); 00269 00270 done(); 00271 } 00272 }; 00273 00274 class DirectoryListThread : public CompletionThread 00275 { 00276 public: 00277 DirectoryListThread(KUrlCompletionPrivate* receiver, 00278 const QStringList& dirList, 00279 const QString& filter, 00280 bool onlyExe, 00281 bool onlyDir, 00282 bool noHidden, 00283 bool appendSlashToDir) : 00284 CompletionThread(receiver), 00285 m_dirList(dirList), 00286 m_filter(filter), 00287 m_onlyExe(onlyExe), 00288 m_onlyDir(onlyDir), 00289 m_noHidden(noHidden), 00290 m_appendSlashToDir(appendSlashToDir) 00291 {} 00292 00293 virtual void run(); 00294 00295 private: 00296 QStringList m_dirList; 00297 QString m_filter; 00298 bool m_onlyExe; 00299 bool m_onlyDir; 00300 bool m_noHidden; 00301 bool m_appendSlashToDir; 00302 }; 00303 00304 void DirectoryListThread::run() 00305 { 00306 // Thread safety notes: 00307 // 00308 // There very possibly may be thread safety issues here, but I've done a check 00309 // of all of the things that would seem to be problematic. Here are a few 00310 // things that I have checked to be safe here (some used indirectly): 00311 // 00312 // QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName() 00313 // QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale() 00314 // 00315 // Also see (for POSIX functions): 00316 // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html 00317 00318 // kDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size(); 00319 00320 QStringList::ConstIterator end = m_dirList.constEnd(); 00321 for (QStringList::ConstIterator it = m_dirList.constBegin(); 00322 it != end && !terminationRequested(); 00323 ++it) { 00324 // kDebug() << "Scanning directory" << *it; 00325 00326 // A trick from KIO that helps performance by a little bit: 00327 // chdir to the directory so we won't have to deal with full paths 00328 // with stat() 00329 00330 QString path = QDir::currentPath(); 00331 QDir::setCurrent(*it); 00332 00333 QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot; 00334 00335 if (m_onlyExe) 00336 iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable); 00337 else if (m_onlyDir) 00338 iterator_filter |= QDir::Dirs; 00339 else 00340 iterator_filter |= (QDir::Dirs | QDir::Files); 00341 00342 QDirIterator current_dir_iterator(*it, iterator_filter); 00343 00344 while (current_dir_iterator.hasNext()) { 00345 current_dir_iterator.next(); 00346 00347 QFileInfo file_info = current_dir_iterator.fileInfo(); 00348 const QString file_name = file_info.fileName(); 00349 00350 //kDebug() << "Found" << file_name; 00351 00352 if (m_filter.isEmpty() || file_name.startsWith(m_filter)) { 00353 00354 QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file_name) : file_name; 00355 // Add '/' to directories 00356 if (m_appendSlashToDir && file_info.isDir()) 00357 toAppend.append(QLatin1Char('/')); 00358 00359 addMatch(m_prepend + toAppend); 00360 } 00361 } 00362 00363 // chdir to the original directory 00364 QDir::setCurrent(path); 00365 } 00366 00367 done(); 00368 } 00369 00370 KUrlCompletionPrivate::~KUrlCompletionPrivate() 00371 { 00372 if (userListThread) 00373 userListThread->requestTermination(); 00374 if (dirListThread) 00375 dirListThread->requestTermination(); 00376 } 00377 00380 // MyURL - wrapper for KUrl with some different functionality 00381 // 00382 00383 class KUrlCompletionPrivate::MyURL 00384 { 00385 public: 00386 MyURL(const QString& url, const QString& cwd); 00387 MyURL(const MyURL& url); 00388 ~MyURL(); 00389 00390 KUrl kurl() const { 00391 return m_kurl; 00392 } 00393 00394 QString protocol() const { 00395 return m_kurl.protocol(); 00396 } 00397 // The directory with a trailing '/' 00398 QString dir() const { 00399 return m_kurl.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash); 00400 } 00401 QString file() const { 00402 return m_kurl.fileName(KUrl::ObeyTrailingSlash); 00403 } 00404 00405 // The initial, unparsed, url, as a string. 00406 QString url() const { 00407 return m_url; 00408 } 00409 00410 // Is the initial string a URL, or just a path (whether absolute or relative) 00411 bool isURL() const { 00412 return m_isURL; 00413 } 00414 00415 void filter(bool replace_user_dir, bool replace_env); 00416 00417 private: 00418 void init(const QString& url, const QString& cwd); 00419 00420 KUrl m_kurl; 00421 QString m_url; 00422 bool m_isURL; 00423 }; 00424 00425 KUrlCompletionPrivate::MyURL::MyURL(const QString& _url, const QString& cwd) 00426 { 00427 init(_url, cwd); 00428 } 00429 00430 KUrlCompletionPrivate::MyURL::MyURL(const MyURL& _url) 00431 : m_kurl(_url.m_kurl) 00432 { 00433 m_url = _url.m_url; 00434 m_isURL = _url.m_isURL; 00435 } 00436 00437 void KUrlCompletionPrivate::MyURL::init(const QString& _url, const QString& cwd) 00438 { 00439 // Save the original text 00440 m_url = _url; 00441 00442 // Non-const copy 00443 QString url_copy = _url; 00444 00445 // Special shortcuts for "man:" and "info:" 00446 if (url_copy.startsWith(QLatin1Char('#'))) { 00447 if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) 00448 url_copy.replace(0, 2, QLatin1String("info:")); 00449 else 00450 url_copy.replace(0, 1, QLatin1String("man:")); 00451 } 00452 00453 // Look for a protocol in 'url' 00454 QRegExp protocol_regex = QRegExp("^(?![A-Za-z]:)[^/\\s\\\\]*:"); 00455 00456 // Assume "file:" or whatever is given by 'cwd' if there is 00457 // no protocol. (KUrl does this only for absolute paths) 00458 if (protocol_regex.indexIn(url_copy) == 0) { 00459 m_kurl = KUrl(url_copy); 00460 m_isURL = true; 00461 } else { // relative path or ~ or $something 00462 m_isURL = false; 00463 if (!QDir::isRelativePath(url_copy) || 00464 url_copy.startsWith(QLatin1Char('~')) || 00465 url_copy.startsWith(QLatin1Char('$'))) { 00466 m_kurl = KUrl(); 00467 m_kurl.setPath(url_copy); 00468 } else { 00469 if (cwd.isEmpty()) { 00470 m_kurl = KUrl(url_copy); 00471 } else { 00472 m_kurl = KUrl(cwd); 00473 m_kurl.addPath(url_copy); 00474 } 00475 } 00476 } 00477 } 00478 00479 KUrlCompletionPrivate::MyURL::~MyURL() 00480 { 00481 } 00482 00483 void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env) 00484 { 00485 QString d = dir() + file(); 00486 if (replace_user_dir) expandTilde(d); 00487 if (replace_env) expandEnv(d); 00488 m_kurl.setPath(d); 00489 } 00490 00493 // KUrlCompletion 00494 // 00495 00496 KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this)) 00497 { 00498 d->init(); 00499 } 00500 00501 00502 KUrlCompletion::KUrlCompletion(Mode _mode) 00503 : KCompletion(), 00504 d(new KUrlCompletionPrivate(this)) 00505 { 00506 d->init(); 00507 setMode(_mode); 00508 } 00509 00510 KUrlCompletion::~KUrlCompletion() 00511 { 00512 stop(); 00513 delete d; 00514 } 00515 00516 00517 void KUrlCompletionPrivate::init() 00518 { 00519 cwd = QDir::homePath(); 00520 00521 replace_home = true; 00522 replace_env = true; 00523 last_no_hidden = false; 00524 last_compl_type = 0; 00525 list_job = 0L; 00526 mode = KUrlCompletion::FileCompletion; 00527 00528 // Read settings 00529 KConfigGroup cg(KGlobal::config(), "URLCompletion"); 00530 00531 url_auto_completion = cg.readEntry("alwaysAutoComplete", true); 00532 popup_append_slash = cg.readEntry("popupAppendSlash", true); 00533 onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false); 00534 00535 q->setIgnoreCase(true); 00536 } 00537 00538 void KUrlCompletion::setDir(const QString& _dir) 00539 { 00540 d->cwd = _dir; 00541 } 00542 00543 QString KUrlCompletion::dir() const 00544 { 00545 return d->cwd; 00546 } 00547 00548 KUrlCompletion::Mode KUrlCompletion::mode() const 00549 { 00550 return d->mode; 00551 } 00552 00553 void KUrlCompletion::setMode(Mode _mode) 00554 { 00555 d->mode = _mode; 00556 } 00557 00558 bool KUrlCompletion::replaceEnv() const 00559 { 00560 return d->replace_env; 00561 } 00562 00563 void KUrlCompletion::setReplaceEnv(bool replace) 00564 { 00565 d->replace_env = replace; 00566 } 00567 00568 bool KUrlCompletion::replaceHome() const 00569 { 00570 return d->replace_home; 00571 } 00572 00573 void KUrlCompletion::setReplaceHome(bool replace) 00574 { 00575 d->replace_home = replace; 00576 } 00577 00578 /* 00579 * makeCompletion() 00580 * 00581 * Entry point for file name completion 00582 */ 00583 QString KUrlCompletion::makeCompletion(const QString& text) 00584 { 00585 //kDebug() << text << "d->cwd=" << d->cwd; 00586 00587 KUrlCompletionPrivate::MyURL url(text, d->cwd); 00588 00589 d->compl_text = text; 00590 00591 // Set d->prepend to the original URL, with the filename [and ref/query] stripped. 00592 // This is what gets prepended to the directory-listing matches. 00593 int toRemove = url.file().length() - url.kurl().query().length(); 00594 if (url.kurl().hasRef()) 00595 toRemove += url.kurl().ref().length() + 1; 00596 d->prepend = text.left(text.length() - toRemove); 00597 d->complete_url = url.isURL(); 00598 00599 QString aMatch; 00600 00601 // Environment variables 00602 // 00603 if (d->replace_env && d->envCompletion(url, &aMatch)) 00604 return aMatch; 00605 00606 // User directories 00607 // 00608 if (d->replace_home && d->userCompletion(url, &aMatch)) 00609 return aMatch; 00610 00611 // Replace user directories and variables 00612 url.filter(d->replace_home, d->replace_env); 00613 00614 //kDebug() << "Filtered: proto=" << url.protocol() 00615 // << ", dir=" << url.dir() 00616 // << ", file=" << url.file() 00617 // << ", kurl url=" << *url.kurl(); 00618 00619 if (d->mode == ExeCompletion) { 00620 // Executables 00621 // 00622 if (d->exeCompletion(url, &aMatch)) 00623 return aMatch; 00624 00625 // KRun can run "man:" and "info:" etc. so why not treat them 00626 // as executables... 00627 00628 if (d->urlCompletion(url, &aMatch)) 00629 return aMatch; 00630 } else { 00631 // Local files, directories 00632 // 00633 if (d->fileCompletion(url, &aMatch)) 00634 return aMatch; 00635 00636 // All other... 00637 // 00638 if (d->urlCompletion(url, &aMatch)) 00639 return aMatch; 00640 } 00641 00642 d->setListedUrl(CTNone); 00643 stop(); 00644 00645 return QString(); 00646 } 00647 00648 /* 00649 * finished 00650 * 00651 * Go on and call KCompletion. 00652 * Called when all matches have been added 00653 */ 00654 QString KUrlCompletionPrivate::finished() 00655 { 00656 if (last_compl_type == CTInfo) 00657 return q->KCompletion::makeCompletion(compl_text.toLower()); 00658 else 00659 return q->KCompletion::makeCompletion(compl_text); 00660 } 00661 00662 /* 00663 * isRunning 00664 * 00665 * Return true if either a KIO job or the DirLister 00666 * is running 00667 */ 00668 bool KUrlCompletion::isRunning() const 00669 { 00670 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()); 00671 } 00672 00673 /* 00674 * stop 00675 * 00676 * Stop and delete a running KIO job or the DirLister 00677 */ 00678 void KUrlCompletion::stop() 00679 { 00680 if (d->list_job) { 00681 d->list_job->kill(); 00682 d->list_job = 0L; 00683 } 00684 00685 if (d->dirListThread) { 00686 d->dirListThread->requestTermination(); 00687 d->dirListThread = 0; 00688 } 00689 } 00690 00691 /* 00692 * Keep track of the last listed directory 00693 */ 00694 void KUrlCompletionPrivate::setListedUrl(int complType, 00695 const QString& directory, 00696 const QString& filter, 00697 bool no_hidden) 00698 { 00699 last_compl_type = complType; 00700 last_path_listed = directory; 00701 last_file_listed = filter; 00702 last_no_hidden = (int) no_hidden; 00703 last_prepend = prepend; 00704 } 00705 00706 bool KUrlCompletionPrivate::isListedUrl(int complType, 00707 const QString& directory, 00708 const QString& filter, 00709 bool no_hidden) 00710 { 00711 return last_compl_type == complType 00712 && (last_path_listed == directory 00713 || (directory.isEmpty() && last_path_listed.isEmpty())) 00714 && (filter.startsWith (last_file_listed) 00715 || (filter.isEmpty() && last_file_listed.isEmpty())) 00716 && last_no_hidden == (int) no_hidden 00717 && last_prepend == prepend; // e.g. relative path vs absolute 00718 } 00719 00720 /* 00721 * isAutoCompletion 00722 * 00723 * Returns true if completion mode is Auto or Popup 00724 */ 00725 bool KUrlCompletionPrivate::isAutoCompletion() 00726 { 00727 return q->completionMode() == KGlobalSettings::CompletionAuto 00728 || q->completionMode() == KGlobalSettings::CompletionPopup 00729 || q->completionMode() == KGlobalSettings::CompletionMan 00730 || q->completionMode() == KGlobalSettings::CompletionPopupAuto; 00731 } 00734 // User directories 00735 // 00736 00737 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00738 { 00739 if (url.protocol() != QLatin1String("file") 00740 || !url.dir().isEmpty() 00741 || !url.file().startsWith(QLatin1Char('~'))) 00742 return false; 00743 00744 if (!isListedUrl(CTUser)) { 00745 q->stop(); 00746 q->clear(); 00747 00748 if (!userListThread) { 00749 userListThread = new UserListThread(this); 00750 userListThread->start(); 00751 00752 // If the thread finishes quickly make sure that the results 00753 // are added to the first matching case. 00754 00755 userListThread->wait(200); 00756 const QStringList l = userListThread->matches(); 00757 addMatches(l); 00758 } 00759 } 00760 *pMatch = finished(); 00761 return true; 00762 } 00763 00766 // Environment variables 00767 // 00768 00769 #ifndef Q_OS_WIN 00770 extern char** environ; // Array of environment variables 00771 #endif 00772 00773 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00774 { 00775 if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) 00776 return false; 00777 00778 if (!isListedUrl(CTEnv)) { 00779 q->stop(); 00780 q->clear(); 00781 00782 char** env = environ; 00783 00784 QString dollar = QLatin1String("$"); 00785 00786 QStringList l; 00787 00788 while (*env) { 00789 QString s = QString::fromLocal8Bit(*env); 00790 00791 int pos = s.indexOf(QLatin1Char('=')); 00792 00793 if (pos == -1) 00794 pos = s.length(); 00795 00796 if (pos > 0) 00797 l.append(prepend + dollar + s.left(pos)); 00798 00799 env++; 00800 } 00801 00802 addMatches(l); 00803 } 00804 00805 setListedUrl(CTEnv); 00806 00807 *pMatch = finished(); 00808 return true; 00809 } 00810 00813 // Executables 00814 // 00815 00816 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00817 { 00818 if (url.protocol() != QLatin1String("file")) 00819 return false; 00820 00821 QString directory = unescape(url.dir()); // remove escapes 00822 00823 // Find directories to search for completions, either 00824 // 00825 // 1. complete path given in url 00826 // 2. current directory (d->cwd) 00827 // 3. $PATH 00828 // 4. no directory at all 00829 00830 QStringList dirList; 00831 00832 if (!url.file().isEmpty()) { 00833 // $PATH 00834 dirList = QString::fromLocal8Bit(qgetenv("PATH")).split( 00835 KPATH_SEPARATOR, QString::SkipEmptyParts); 00836 00837 QStringList::Iterator it = dirList.begin(); 00838 00839 for (; it != dirList.end(); ++it) 00840 it->append(QLatin1Char('/')); 00841 } else if (!QDir::isRelativePath(directory)) { 00842 // complete path in url 00843 dirList.append(directory); 00844 } else if (!directory.isEmpty() && !cwd.isEmpty()) { 00845 // current directory 00846 dirList.append(cwd + QLatin1Char('/') + directory); 00847 } 00848 00849 // No hidden files unless the user types "." 00850 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.'); 00851 00852 // List files if needed 00853 // 00854 if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) { 00855 q->stop(); 00856 q->clear(); 00857 00858 setListedUrl(CTExe, directory, url.file(), no_hidden_files); 00859 00860 *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files); 00861 } else if (!q->isRunning()) { 00862 *pMatch = finished(); 00863 } else { 00864 if (dirListThread) 00865 setListedUrl(CTExe, directory, url.file(), no_hidden_files); 00866 pMatch->clear(); 00867 } 00868 00869 return true; 00870 } 00871 00874 // Local files 00875 // 00876 00877 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00878 { 00879 if (url.protocol() != QLatin1String("file")) 00880 return false; 00881 00882 QString directory = unescape(url.dir()); 00883 00884 if (url.url().length() && url.url().at(0) == QLatin1Char('.')) { 00885 if (url.url().length() == 1) { 00886 *pMatch = (q->completionMode() == KGlobalSettings::CompletionMan) ? 00887 QLatin1String(".") : 00888 QLatin1String(".."); 00889 return true; 00890 } else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.')) { 00891 *pMatch = QLatin1String(".."); 00892 return true; 00893 } 00894 } 00895 00896 //kDebug() << "fileCompletion" << url << "dir=" << dir; 00897 00898 // Find directories to search for completions, either 00899 // 00900 // 1. complete path given in url 00901 // 2. current directory (d->cwd) 00902 // 3. no directory at all 00903 00904 QStringList dirList; 00905 00906 if (!QDir::isRelativePath(directory)) { 00907 // complete path in url 00908 dirList.append(directory); 00909 } else if (!cwd.isEmpty()) { 00910 // current directory 00911 QString dirToAdd = cwd; 00912 if (!directory.isEmpty()) { 00913 if (!cwd.endsWith('/')) 00914 dirToAdd.append(QLatin1Char('/')); 00915 dirToAdd.append(directory); 00916 } 00917 dirList.append(dirToAdd); 00918 } 00919 00920 // No hidden files unless the user types "." 00921 bool no_hidden_files = !url.file().startsWith(QLatin1Char('.')); 00922 00923 // List files if needed 00924 // 00925 if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) { 00926 q->stop(); 00927 q->clear(); 00928 00929 setListedUrl(CTFile, directory, QString(), no_hidden_files); 00930 00931 // Append '/' to directories in Popup mode? 00932 bool append_slash = (popup_append_slash 00933 && (q->completionMode() == KGlobalSettings::CompletionPopup || 00934 q->completionMode() == KGlobalSettings::CompletionPopupAuto)); 00935 00936 bool only_dir = (mode == KUrlCompletion::DirCompletion); 00937 00938 *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, 00939 append_slash); 00940 } else if (!q->isRunning()) { 00941 *pMatch = finished(); 00942 } else { 00943 pMatch->clear(); 00944 } 00945 00946 return true; 00947 } 00948 00951 // URLs not handled elsewhere... 00952 // 00953 00954 static bool isLocalProtocol(const QString& protocol) 00955 { 00956 return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")); 00957 } 00958 00959 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00960 { 00961 //kDebug() << *url.kurl(); 00962 if (onlyLocalProto && isLocalProtocol(url.protocol())) 00963 return false; 00964 00965 // Use d->cwd as base url in case url is not absolute 00966 KUrl url_dir = url.kurl(); 00967 if (url_dir.isRelative() && !cwd.isEmpty()) { 00968 const KUrl url_cwd (cwd); 00969 // Create an URL with the directory to be listed 00970 url_dir = KUrl(url_cwd, url_dir.url()); 00971 } 00972 00973 // url is malformed 00974 if (!url_dir.isValid()) 00975 return false; 00976 00977 // url handler doesn't support listing 00978 if (!KProtocolManager::supportsListing(url_dir)) 00979 return false; 00980 00981 // non local urls 00982 if (!isLocalProtocol(url.protocol())) { 00983 // url does not specify host 00984 if (url_dir.host().isEmpty()) 00985 return false; 00986 00987 // url does not specify a valid directory 00988 if (url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash).isEmpty()) 00989 return false; 00990 00991 // automatic completion is disabled 00992 if (isAutoCompletion() && !url_auto_completion) 00993 return false; 00994 } 00995 00996 url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway... 00997 00998 // Remove escapes 00999 QString directory = unescape(url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash)); 01000 01001 url_dir.setPath(directory); 01002 01003 // List files if needed 01004 // 01005 if (!isListedUrl(CTUrl, url_dir.prettyUrl(), url.file())) { 01006 q->stop(); 01007 q->clear(); 01008 01009 setListedUrl(CTUrl, url_dir.prettyUrl(), QString()); 01010 01011 QList<KUrl> url_list; 01012 url_list.append(url_dir); 01013 01014 listUrls(url_list, QString(), false); 01015 01016 pMatch->clear(); 01017 } else if (!q->isRunning()) { 01018 *pMatch = finished(); 01019 } else { 01020 pMatch->clear(); 01021 } 01022 01023 return true; 01024 } 01025 01028 // Directory and URL listing 01029 // 01030 01031 /* 01032 * addMatches 01033 * 01034 * Called to add matches to KCompletion 01035 */ 01036 void KUrlCompletionPrivate::addMatches(const QStringList& matchList) 01037 { 01038 q->insertItems(matchList); 01039 } 01040 01041 /* 01042 * listDirectories 01043 * 01044 * List files starting with 'filter' in the given directories, 01045 * either using DirLister or listURLs() 01046 * 01047 * In either case, addMatches() is called with the listed 01048 * files, and eventually finished() when the listing is done 01049 * 01050 * Returns the match if available, or QString() if 01051 * DirLister timed out or using kio 01052 */ 01053 QString KUrlCompletionPrivate::listDirectories( 01054 const QStringList& dirList, 01055 const QString& filter, 01056 bool only_exe, 01057 bool only_dir, 01058 bool no_hidden, 01059 bool append_slash_to_dir) 01060 { 01061 assert(!q->isRunning()); 01062 01063 if (qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty()) { 01064 01065 //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO"; 01066 01067 // Don't use KIO 01068 01069 if (dirListThread) 01070 dirListThread->requestTermination(); 01071 01072 QStringList dirs; 01073 01074 QStringList::ConstIterator end = dirList.constEnd(); 01075 for (QStringList::ConstIterator it = dirList.constBegin(); 01076 it != end; 01077 ++it) { 01078 KUrl url; 01079 url.setPath(*it); 01080 if (KAuthorized::authorizeUrlAction(QLatin1String("list"), KUrl(), url)) 01081 dirs.append(*it); 01082 } 01083 01084 dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir, 01085 no_hidden, append_slash_to_dir); 01086 dirListThread->start(); 01087 dirListThread->wait(200); 01088 addMatches(dirListThread->matches()); 01089 01090 return finished(); 01091 } 01092 01093 // Use KIO 01094 //kDebug() << "Listing (listDirectories):" << dirList << "with KIO"; 01095 01096 QList<KUrl> url_list; 01097 01098 QStringList::ConstIterator it = dirList.constBegin(); 01099 QStringList::ConstIterator end = dirList.constEnd(); 01100 01101 for (; it != end; ++it) { 01102 url_list.append(KUrl(*it)); 01103 } 01104 01105 listUrls(url_list, filter, only_exe, no_hidden); 01106 // Will call addMatches() and finished() 01107 01108 return QString(); 01109 } 01110 01111 /* 01112 * listURLs 01113 * 01114 * Use KIO to list the given urls 01115 * 01116 * addMatches() is called with the listed files 01117 * finished() is called when the listing is done 01118 */ 01119 void KUrlCompletionPrivate::listUrls( 01120 const QList<KUrl> &urls, 01121 const QString& filter, 01122 bool only_exe, 01123 bool no_hidden) 01124 { 01125 assert(list_urls.isEmpty()); 01126 assert(list_job == 0L); 01127 01128 list_urls = urls; 01129 list_urls_filter = filter; 01130 list_urls_only_exe = only_exe; 01131 list_urls_no_hidden = no_hidden; 01132 01133 //kDebug() << "Listing URLs:" << *urls[0] << ",..."; 01134 01135 // Start it off by calling _k_slotIOFinished 01136 // 01137 // This will start a new list job as long as there 01138 // are urls in d->list_urls 01139 // 01140 _k_slotIOFinished(0); 01141 } 01142 01143 /* 01144 * _k_slotEntries 01145 * 01146 * Receive files listed by KIO and call addMatches() 01147 */ 01148 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries) 01149 { 01150 QStringList matchList; 01151 01152 KIO::UDSEntryList::ConstIterator it = entries.constBegin(); 01153 const KIO::UDSEntryList::ConstIterator end = entries.constEnd(); 01154 01155 QString filter = list_urls_filter; 01156 01157 int filter_len = filter.length(); 01158 01159 // Iterate over all files 01160 // 01161 for (; it != end; ++it) { 01162 const KIO::UDSEntry& entry = *it; 01163 const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL); 01164 01165 QString entry_name; 01166 if (!url.isEmpty()) { 01167 // kDebug() << "url:" << url; 01168 entry_name = KUrl(url).fileName(); 01169 } else { 01170 entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME); 01171 } 01172 01173 // kDebug() << "name:" << name; 01174 01175 if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) && 01176 (list_urls_no_hidden || 01177 entry_name.length() == 1 || 01178 (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) 01179 continue; 01180 01181 const bool isDir = entry.isDir(); 01182 01183 if (mode == KUrlCompletion::DirCompletion && !isDir) 01184 continue; 01185 01186 if (filter_len == 0 || entry_name.left(filter_len) == filter) { 01187 01188 QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name; 01189 01190 if (isDir) 01191 toAppend.append(QLatin1Char('/')); 01192 01193 if (!list_urls_only_exe || 01194 (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable 01195 ) { 01196 matchList.append(prepend + toAppend); 01197 } 01198 } 01199 } 01200 01201 addMatches(matchList); 01202 } 01203 01204 /* 01205 * _k_slotIOFinished 01206 * 01207 * Called when a KIO job is finished. 01208 * 01209 * Start a new list job if there are still urls in 01210 * list_urls, otherwise call finished() 01211 */ 01212 void KUrlCompletionPrivate::_k_slotIOFinished(KJob* job) 01213 { 01214 assert(job == list_job); Q_UNUSED(job) 01215 01216 if (list_urls.isEmpty()) { 01217 01218 list_job = 0L; 01219 01220 finished(); // will call KCompletion::makeCompletion() 01221 01222 } else { 01223 01224 KUrl kurl(list_urls.takeFirst()); 01225 01226 // list_urls.removeAll( kurl ); 01227 01228 // kDebug() << "Start KIO::listDir" << kurl; 01229 01230 list_job = KIO::listDir(kurl, KIO::HideProgressInfo); 01231 list_job->addMetaData("no-auth-prompt", "true"); 01232 01233 assert(list_job); 01234 01235 q->connect(list_job, 01236 SIGNAL(result(KJob*)), 01237 SLOT(_k_slotIOFinished(KJob*))); 01238 01239 q->connect(list_job, 01240 SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)), 01241 SLOT(_k_slotEntries(KIO::Job*, const KIO::UDSEntryList&))); 01242 } 01243 } 01244 01247 01248 /* 01249 * postProcessMatch, postProcessMatches 01250 * 01251 * Called by KCompletion before emitting match() and matches() 01252 * 01253 * Append '/' to directories for file completion. This is 01254 * done here to avoid stat()'ing a lot of files 01255 */ 01256 void KUrlCompletion::postProcessMatch(QString* pMatch) const 01257 { 01258 // kDebug() << *pMatch; 01259 01260 if (!pMatch->isEmpty()) { 01261 01262 // Add '/' to directories in file completion mode 01263 // unless it has already been done 01264 if (d->last_compl_type == CTFile 01265 && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) { 01266 QString copy; 01267 01268 if (pMatch->startsWith(QLatin1String("file:"))) 01269 copy = KUrl(*pMatch).toLocalFile(); 01270 else 01271 copy = *pMatch; 01272 01273 expandTilde(copy); 01274 expandEnv(copy); 01275 #ifdef Q_WS_WIN 01276 DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16()); 01277 if (dwAttr == INVALID_FILE_ATTRIBUTES) { 01278 kDebug() << "Could not get file attribs ( " 01279 << GetLastError() 01280 << " ) for " 01281 << copy; 01282 } else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) 01283 pMatch->append(QLatin1Char('/')); 01284 #else 01285 if (QDir::isRelativePath(copy)) 01286 copy.prepend(d->cwd + QLatin1Char('/')); 01287 01288 // kDebug() << "stat'ing" << copy; 01289 01290 KDE_struct_stat sbuff; 01291 01292 QByteArray file = QFile::encodeName(copy); 01293 01294 if (KDE_stat(file.data(), &sbuff) == 0) { 01295 if (S_ISDIR(sbuff.st_mode)) 01296 pMatch->append(QLatin1Char('/')); 01297 } else { 01298 kDebug() << "Could not stat file" << copy; 01299 } 01300 #endif 01301 } 01302 } 01303 } 01304 01305 void KUrlCompletion::postProcessMatches(QStringList* /*matches*/) const 01306 { 01307 // Maybe '/' should be added to directories here as in 01308 // postProcessMatch() but it would slow things down 01309 // when there are a lot of matches... 01310 } 01311 01312 void KUrlCompletion::postProcessMatches(KCompletionMatches* /*matches*/) const 01313 { 01314 // Maybe '/' should be added to directories here as in 01315 // postProcessMatch() but it would slow things down 01316 // when there are a lot of matches... 01317 } 01318 01319 void KUrlCompletion::customEvent(QEvent* e) 01320 { 01321 if (e->type() == CompletionMatchEvent::uniqueType()) { 01322 01323 CompletionMatchEvent* matchEvent = static_cast<CompletionMatchEvent*>(e); 01324 01325 matchEvent->completionThread()->wait(); 01326 01327 if (!d->isListedUrl(CTUser)) { 01328 stop(); 01329 clear(); 01330 d->addMatches(matchEvent->completionThread()->matches()); 01331 } else { 01332 d->setListedUrl(CTUser); 01333 } 01334 01335 if (d->userListThread == matchEvent->completionThread()) 01336 d->userListThread = 0; 01337 01338 if (d->dirListThread == matchEvent->completionThread()) 01339 d->dirListThread = 0; 01340 01341 delete matchEvent->completionThread(); 01342 } 01343 } 01344 01345 // static 01346 QString KUrlCompletion::replacedPath(const QString& text, bool replaceHome, bool replaceEnv) 01347 { 01348 if (text.isEmpty()) 01349 return text; 01350 01351 KUrlCompletionPrivate::MyURL url(text, QString()); // no need to replace something of our current cwd 01352 if (!url.kurl().isLocalFile()) 01353 return text; 01354 01355 url.filter(replaceHome, replaceEnv); 01356 return url.dir() + url.file(); 01357 } 01358 01359 01360 QString KUrlCompletion::replacedPath(const QString& text) const 01361 { 01362 return replacedPath(text, d->replace_home, d->replace_env); 01363 } 01364 01367 // Static functions 01368 01369 /* 01370 * expandEnv 01371 * 01372 * Expand environment variables in text. Escaped '$' are ignored. 01373 * Return true if expansion was made. 01374 */ 01375 static bool expandEnv(QString& text) 01376 { 01377 // Find all environment variables beginning with '$' 01378 // 01379 int pos = 0; 01380 01381 bool expanded = false; 01382 01383 while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) { 01384 01385 // Skip escaped '$' 01386 // 01387 if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) { 01388 pos++; 01389 } 01390 // Variable found => expand 01391 // 01392 else { 01393 // Find the end of the variable = next '/' or ' ' 01394 // 01395 int pos2 = text.indexOf(QLatin1Char(' '), pos + 1); 01396 int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1); 01397 01398 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) 01399 pos2 = pos_tmp; 01400 01401 if (pos2 == -1) 01402 pos2 = text.length(); 01403 01404 // Replace if the variable is terminated by '/' or ' ' 01405 // and defined 01406 // 01407 if (pos2 >= 0) { 01408 int len = pos2 - pos; 01409 QString key = text.mid(pos + 1, len - 1); 01410 QString value = 01411 QString::fromLocal8Bit(qgetenv(key.toLocal8Bit())); 01412 01413 if (!value.isEmpty()) { 01414 expanded = true; 01415 text.replace(pos, len, value); 01416 pos = pos + value.length(); 01417 } else { 01418 pos = pos2; 01419 } 01420 } 01421 } 01422 } 01423 01424 return expanded; 01425 } 01426 01427 /* 01428 * expandTilde 01429 * 01430 * Replace "~user" with the users home directory 01431 * Return true if expansion was made. 01432 */ 01433 static bool expandTilde(QString& text) 01434 { 01435 if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) 01436 return false; 01437 01438 bool expanded = false; 01439 01440 // Find the end of the user name = next '/' or ' ' 01441 // 01442 int pos2 = text.indexOf(QLatin1Char(' '), 1); 01443 int pos_tmp = text.indexOf(QLatin1Char('/'), 1); 01444 01445 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) 01446 pos2 = pos_tmp; 01447 01448 if (pos2 == -1) 01449 pos2 = text.length(); 01450 01451 // Replace ~user if the user name is terminated by '/' or ' ' 01452 // 01453 if (pos2 >= 0) { 01454 01455 QString user = text.mid(1, pos2 - 1); 01456 QString dir; 01457 01458 // A single ~ is replaced with $HOME 01459 // 01460 if (user.isEmpty()) { 01461 dir = QDir::homePath(); 01462 } 01463 // ~user is replaced with the dir from passwd 01464 // 01465 else { 01466 struct passwd* pw = ::getpwnam(user.toLocal8Bit()); 01467 01468 if (pw) 01469 dir = QFile::decodeName(pw->pw_dir); 01470 01471 ::endpwent(); 01472 } 01473 01474 if (!dir.isEmpty()) { 01475 expanded = true; 01476 text.replace(0, pos2, dir); 01477 } 01478 } 01479 01480 return expanded; 01481 } 01482 01483 /* 01484 * unescape 01485 * 01486 * Remove escapes and return the result in a new string 01487 * 01488 */ 01489 static QString unescape(const QString& text) 01490 { 01491 QString result; 01492 01493 for (int pos = 0; pos < text.length(); pos++) 01494 if (text.at(pos) != QLatin1Char('\\')) 01495 result.insert(result.length(), text.at(pos)); 01496 01497 return result; 01498 } 01499 01500 #include "kurlcompletion.moc"
KDE 4.7 API Reference