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 = file_name; 00355 // Add '/' to directories 00356 if (m_appendSlashToDir && file_info.isDir()) 00357 toAppend.append(QLatin1Char('/')); 00358 00359 if (m_complete_url) { 00360 KUrl url(m_prepend); 00361 url.addPath(toAppend); 00362 addMatch(url.prettyUrl()); 00363 } else { 00364 addMatch(m_prepend + toAppend); 00365 } 00366 } 00367 } 00368 00369 // chdir to the original directory 00370 QDir::setCurrent(path); 00371 } 00372 00373 done(); 00374 } 00375 00376 KUrlCompletionPrivate::~KUrlCompletionPrivate() 00377 { 00378 if (userListThread) 00379 userListThread->requestTermination(); 00380 if (dirListThread) 00381 dirListThread->requestTermination(); 00382 } 00383 00386 // MyURL - wrapper for KUrl with some different functionality 00387 // 00388 00389 class KUrlCompletionPrivate::MyURL 00390 { 00391 public: 00392 MyURL(const QString& url, const QString& cwd); 00393 MyURL(const MyURL& url); 00394 ~MyURL(); 00395 00396 KUrl kurl() const { 00397 return m_kurl; 00398 } 00399 00400 QString protocol() const { 00401 return m_kurl.protocol(); 00402 } 00403 // The directory with a trailing '/' 00404 QString dir() const { 00405 return m_kurl.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash); 00406 } 00407 QString file() const { 00408 return m_kurl.fileName(KUrl::ObeyTrailingSlash); 00409 } 00410 00411 // The initial, unparsed, url, as a string. 00412 QString url() const { 00413 return m_url; 00414 } 00415 00416 // Is the initial string a URL, or just a path (whether absolute or relative) 00417 bool isURL() const { 00418 return m_isURL; 00419 } 00420 00421 void filter(bool replace_user_dir, bool replace_env); 00422 00423 private: 00424 void init(const QString& url, const QString& cwd); 00425 00426 KUrl m_kurl; 00427 QString m_url; 00428 bool m_isURL; 00429 }; 00430 00431 KUrlCompletionPrivate::MyURL::MyURL(const QString& _url, const QString& cwd) 00432 { 00433 init(_url, cwd); 00434 } 00435 00436 KUrlCompletionPrivate::MyURL::MyURL(const MyURL& _url) 00437 : m_kurl(_url.m_kurl) 00438 { 00439 m_url = _url.m_url; 00440 m_isURL = _url.m_isURL; 00441 } 00442 00443 void KUrlCompletionPrivate::MyURL::init(const QString& _url, const QString& cwd) 00444 { 00445 // Save the original text 00446 m_url = _url; 00447 00448 // Non-const copy 00449 QString url_copy = _url; 00450 00451 // Special shortcuts for "man:" and "info:" 00452 if (url_copy.startsWith(QLatin1Char('#'))) { 00453 if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) 00454 url_copy.replace(0, 2, QLatin1String("info:")); 00455 else 00456 url_copy.replace(0, 1, QLatin1String("man:")); 00457 } 00458 00459 // Look for a protocol in 'url' 00460 QRegExp protocol_regex = QRegExp("^(?![A-Za-z]:)[^/\\s\\\\]*:"); 00461 00462 // Assume "file:" or whatever is given by 'cwd' if there is 00463 // no protocol. (KUrl does this only for absolute paths) 00464 if (protocol_regex.indexIn(url_copy) == 0) { 00465 m_kurl = KUrl(url_copy); 00466 m_isURL = true; 00467 } else { // relative path or ~ or $something 00468 m_isURL = false; 00469 if (!QDir::isRelativePath(url_copy) || 00470 url_copy.startsWith(QLatin1Char('~')) || 00471 url_copy.startsWith(QLatin1Char('$'))) { 00472 m_kurl = KUrl(); 00473 m_kurl.setPath(url_copy); 00474 } else { 00475 if (cwd.isEmpty()) { 00476 m_kurl = KUrl(url_copy); 00477 } else { 00478 m_kurl = KUrl(cwd); 00479 m_kurl.addPath(url_copy); 00480 } 00481 } 00482 } 00483 } 00484 00485 KUrlCompletionPrivate::MyURL::~MyURL() 00486 { 00487 } 00488 00489 void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env) 00490 { 00491 QString d = dir() + file(); 00492 if (replace_user_dir) expandTilde(d); 00493 if (replace_env) expandEnv(d); 00494 m_kurl.setPath(d); 00495 } 00496 00499 // KUrlCompletion 00500 // 00501 00502 KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this)) 00503 { 00504 d->init(); 00505 } 00506 00507 00508 KUrlCompletion::KUrlCompletion(Mode _mode) 00509 : KCompletion(), 00510 d(new KUrlCompletionPrivate(this)) 00511 { 00512 d->init(); 00513 setMode(_mode); 00514 } 00515 00516 KUrlCompletion::~KUrlCompletion() 00517 { 00518 stop(); 00519 delete d; 00520 } 00521 00522 00523 void KUrlCompletionPrivate::init() 00524 { 00525 cwd = QDir::homePath(); 00526 00527 replace_home = true; 00528 replace_env = true; 00529 last_no_hidden = false; 00530 last_compl_type = 0; 00531 list_job = 0L; 00532 mode = KUrlCompletion::FileCompletion; 00533 00534 // Read settings 00535 KConfigGroup cg(KGlobal::config(), "URLCompletion"); 00536 00537 url_auto_completion = cg.readEntry("alwaysAutoComplete", true); 00538 popup_append_slash = cg.readEntry("popupAppendSlash", true); 00539 onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false); 00540 00541 q->setIgnoreCase(true); 00542 } 00543 00544 void KUrlCompletion::setDir(const QString& _dir) 00545 { 00546 d->cwd = _dir; 00547 } 00548 00549 QString KUrlCompletion::dir() const 00550 { 00551 return d->cwd; 00552 } 00553 00554 KUrlCompletion::Mode KUrlCompletion::mode() const 00555 { 00556 return d->mode; 00557 } 00558 00559 void KUrlCompletion::setMode(Mode _mode) 00560 { 00561 d->mode = _mode; 00562 } 00563 00564 bool KUrlCompletion::replaceEnv() const 00565 { 00566 return d->replace_env; 00567 } 00568 00569 void KUrlCompletion::setReplaceEnv(bool replace) 00570 { 00571 d->replace_env = replace; 00572 } 00573 00574 bool KUrlCompletion::replaceHome() const 00575 { 00576 return d->replace_home; 00577 } 00578 00579 void KUrlCompletion::setReplaceHome(bool replace) 00580 { 00581 d->replace_home = replace; 00582 } 00583 00584 /* 00585 * makeCompletion() 00586 * 00587 * Entry point for file name completion 00588 */ 00589 QString KUrlCompletion::makeCompletion(const QString& text) 00590 { 00591 //kDebug() << text << "d->cwd=" << d->cwd; 00592 00593 KUrlCompletionPrivate::MyURL url(text, d->cwd); 00594 00595 d->compl_text = text; 00596 00597 // Set d->prepend to the original URL, with the filename [and ref/query] stripped. 00598 // This is what gets prepended to the directory-listing matches. 00599 int toRemove = url.file().length() - url.kurl().query().length(); 00600 if (url.kurl().hasRef()) 00601 toRemove += url.kurl().ref().length() + 1; 00602 d->prepend = text.left(text.length() - toRemove); 00603 d->complete_url = url.isURL(); 00604 00605 QString aMatch; 00606 00607 // Environment variables 00608 // 00609 if (d->replace_env && d->envCompletion(url, &aMatch)) 00610 return aMatch; 00611 00612 // User directories 00613 // 00614 if (d->replace_home && d->userCompletion(url, &aMatch)) 00615 return aMatch; 00616 00617 // Replace user directories and variables 00618 url.filter(d->replace_home, d->replace_env); 00619 00620 //kDebug() << "Filtered: proto=" << url.protocol() 00621 // << ", dir=" << url.dir() 00622 // << ", file=" << url.file() 00623 // << ", kurl url=" << *url.kurl(); 00624 00625 if (d->mode == ExeCompletion) { 00626 // Executables 00627 // 00628 if (d->exeCompletion(url, &aMatch)) 00629 return aMatch; 00630 00631 // KRun can run "man:" and "info:" etc. so why not treat them 00632 // as executables... 00633 00634 if (d->urlCompletion(url, &aMatch)) 00635 return aMatch; 00636 } else { 00637 // Local files, directories 00638 // 00639 if (d->fileCompletion(url, &aMatch)) 00640 return aMatch; 00641 00642 // All other... 00643 // 00644 if (d->urlCompletion(url, &aMatch)) 00645 return aMatch; 00646 } 00647 00648 d->setListedUrl(CTNone); 00649 stop(); 00650 00651 return QString(); 00652 } 00653 00654 /* 00655 * finished 00656 * 00657 * Go on and call KCompletion. 00658 * Called when all matches have been added 00659 */ 00660 QString KUrlCompletionPrivate::finished() 00661 { 00662 if (last_compl_type == CTInfo) 00663 return q->KCompletion::makeCompletion(compl_text.toLower()); 00664 else 00665 return q->KCompletion::makeCompletion(compl_text); 00666 } 00667 00668 /* 00669 * isRunning 00670 * 00671 * Return true if either a KIO job or the DirLister 00672 * is running 00673 */ 00674 bool KUrlCompletion::isRunning() const 00675 { 00676 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()); 00677 } 00678 00679 /* 00680 * stop 00681 * 00682 * Stop and delete a running KIO job or the DirLister 00683 */ 00684 void KUrlCompletion::stop() 00685 { 00686 if (d->list_job) { 00687 d->list_job->kill(); 00688 d->list_job = 0L; 00689 } 00690 00691 if (d->dirListThread) { 00692 d->dirListThread->requestTermination(); 00693 d->dirListThread = 0; 00694 } 00695 } 00696 00697 /* 00698 * Keep track of the last listed directory 00699 */ 00700 void KUrlCompletionPrivate::setListedUrl(int complType, 00701 const QString& directory, 00702 const QString& filter, 00703 bool no_hidden) 00704 { 00705 last_compl_type = complType; 00706 last_path_listed = directory; 00707 last_file_listed = filter; 00708 last_no_hidden = (int) no_hidden; 00709 last_prepend = prepend; 00710 } 00711 00712 bool KUrlCompletionPrivate::isListedUrl(int complType, 00713 const QString& directory, 00714 const QString& filter, 00715 bool no_hidden) 00716 { 00717 return last_compl_type == complType 00718 && (last_path_listed == directory 00719 || (directory.isEmpty() && last_path_listed.isEmpty())) 00720 && (filter.startsWith (last_file_listed) 00721 || (filter.isEmpty() && last_file_listed.isEmpty())) 00722 && last_no_hidden == (int) no_hidden 00723 && last_prepend == prepend; // e.g. relative path vs absolute 00724 } 00725 00726 /* 00727 * isAutoCompletion 00728 * 00729 * Returns true if completion mode is Auto or Popup 00730 */ 00731 bool KUrlCompletionPrivate::isAutoCompletion() 00732 { 00733 return q->completionMode() == KGlobalSettings::CompletionAuto 00734 || q->completionMode() == KGlobalSettings::CompletionPopup 00735 || q->completionMode() == KGlobalSettings::CompletionMan 00736 || q->completionMode() == KGlobalSettings::CompletionPopupAuto; 00737 } 00740 // User directories 00741 // 00742 00743 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00744 { 00745 if (url.protocol() != QLatin1String("file") 00746 || !url.dir().isEmpty() 00747 || !url.file().startsWith(QLatin1Char('~'))) 00748 return false; 00749 00750 if (!isListedUrl(CTUser)) { 00751 q->stop(); 00752 q->clear(); 00753 00754 if (!userListThread) { 00755 userListThread = new UserListThread(this); 00756 userListThread->start(); 00757 00758 // If the thread finishes quickly make sure that the results 00759 // are added to the first matching case. 00760 00761 userListThread->wait(200); 00762 const QStringList l = userListThread->matches(); 00763 addMatches(l); 00764 } 00765 } 00766 *pMatch = finished(); 00767 return true; 00768 } 00769 00772 // Environment variables 00773 // 00774 00775 #ifndef Q_OS_WIN 00776 extern char** environ; // Array of environment variables 00777 #endif 00778 00779 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00780 { 00781 if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) 00782 return false; 00783 00784 if (!isListedUrl(CTEnv)) { 00785 q->stop(); 00786 q->clear(); 00787 00788 char** env = environ; 00789 00790 QString dollar = QLatin1String("$"); 00791 00792 QStringList l; 00793 00794 while (*env) { 00795 QString s = QString::fromLocal8Bit(*env); 00796 00797 int pos = s.indexOf(QLatin1Char('=')); 00798 00799 if (pos == -1) 00800 pos = s.length(); 00801 00802 if (pos > 0) 00803 l.append(prepend + dollar + s.left(pos)); 00804 00805 env++; 00806 } 00807 00808 addMatches(l); 00809 } 00810 00811 setListedUrl(CTEnv); 00812 00813 *pMatch = finished(); 00814 return true; 00815 } 00816 00819 // Executables 00820 // 00821 00822 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00823 { 00824 if (url.protocol() != QLatin1String("file")) 00825 return false; 00826 00827 QString directory = unescape(url.dir()); // remove escapes 00828 00829 // Find directories to search for completions, either 00830 // 00831 // 1. complete path given in url 00832 // 2. current directory (d->cwd) 00833 // 3. $PATH 00834 // 4. no directory at all 00835 00836 QStringList dirList; 00837 00838 if (!url.file().isEmpty()) { 00839 // $PATH 00840 dirList = QString::fromLocal8Bit(qgetenv("PATH")).split( 00841 KPATH_SEPARATOR, QString::SkipEmptyParts); 00842 00843 QStringList::Iterator it = dirList.begin(); 00844 00845 for (; it != dirList.end(); ++it) 00846 it->append(QLatin1Char('/')); 00847 } else if (!QDir::isRelativePath(directory)) { 00848 // complete path in url 00849 dirList.append(directory); 00850 } else if (!directory.isEmpty() && !cwd.isEmpty()) { 00851 // current directory 00852 dirList.append(cwd + QLatin1Char('/') + directory); 00853 } 00854 00855 // No hidden files unless the user types "." 00856 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.'); 00857 00858 // List files if needed 00859 // 00860 if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) { 00861 q->stop(); 00862 q->clear(); 00863 00864 setListedUrl(CTExe, directory, url.file(), no_hidden_files); 00865 00866 *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files); 00867 } else if (!q->isRunning()) { 00868 *pMatch = finished(); 00869 } else { 00870 if (dirListThread) 00871 setListedUrl(CTExe, directory, url.file(), no_hidden_files); 00872 pMatch->clear(); 00873 } 00874 00875 return true; 00876 } 00877 00880 // Local files 00881 // 00882 00883 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00884 { 00885 if (url.protocol() != QLatin1String("file")) 00886 return false; 00887 00888 QString directory = unescape(url.dir()); 00889 00890 if (url.url().length() && url.url().at(0) == QLatin1Char('.')) { 00891 if (url.url().length() == 1) { 00892 *pMatch = (q->completionMode() == KGlobalSettings::CompletionMan) ? 00893 QLatin1String(".") : 00894 QLatin1String(".."); 00895 return true; 00896 } else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.')) { 00897 *pMatch = QLatin1String(".."); 00898 return true; 00899 } 00900 } 00901 00902 //kDebug() << "fileCompletion" << url << "dir=" << dir; 00903 00904 // Find directories to search for completions, either 00905 // 00906 // 1. complete path given in url 00907 // 2. current directory (d->cwd) 00908 // 3. no directory at all 00909 00910 QStringList dirList; 00911 00912 if (!QDir::isRelativePath(directory)) { 00913 // complete path in url 00914 dirList.append(directory); 00915 } else if (!cwd.isEmpty()) { 00916 // current directory 00917 QString dirToAdd = cwd; 00918 if (!directory.isEmpty()) { 00919 if (!cwd.endsWith('/')) 00920 dirToAdd.append(QLatin1Char('/')); 00921 dirToAdd.append(directory); 00922 } 00923 dirList.append(dirToAdd); 00924 } 00925 00926 // No hidden files unless the user types "." 00927 bool no_hidden_files = !url.file().startsWith(QLatin1Char('.')); 00928 00929 // List files if needed 00930 // 00931 if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) { 00932 q->stop(); 00933 q->clear(); 00934 00935 setListedUrl(CTFile, directory, QString(), no_hidden_files); 00936 00937 // Append '/' to directories in Popup mode? 00938 bool append_slash = (popup_append_slash 00939 && (q->completionMode() == KGlobalSettings::CompletionPopup || 00940 q->completionMode() == KGlobalSettings::CompletionPopupAuto)); 00941 00942 bool only_dir = (mode == KUrlCompletion::DirCompletion); 00943 00944 *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, 00945 append_slash); 00946 } else if (!q->isRunning()) { 00947 *pMatch = finished(); 00948 } else { 00949 pMatch->clear(); 00950 } 00951 00952 return true; 00953 } 00954 00957 // URLs not handled elsewhere... 00958 // 00959 00960 static bool isLocalProtocol(const QString& protocol) 00961 { 00962 return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")); 00963 } 00964 00965 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch) 00966 { 00967 //kDebug() << *url.kurl(); 00968 if (onlyLocalProto && isLocalProtocol(url.protocol())) 00969 return false; 00970 00971 // Use d->cwd as base url in case url is not absolute 00972 KUrl url_dir = url.kurl(); 00973 if (url_dir.isRelative() && !cwd.isEmpty()) { 00974 const KUrl url_cwd (cwd); 00975 // Create an URL with the directory to be listed 00976 url_dir = KUrl(url_cwd, url_dir.url()); 00977 } 00978 00979 // url is malformed 00980 if (!url_dir.isValid()) 00981 return false; 00982 00983 // non local urls 00984 if (!isLocalProtocol(url.protocol())) { 00985 // url does not specify host 00986 if (url_dir.host().isEmpty()) 00987 return false; 00988 00989 // url does not specify a valid directory 00990 if (url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash).isEmpty()) 00991 return false; 00992 00993 // automatic completion is disabled 00994 if (isAutoCompletion() && !url_auto_completion) 00995 return false; 00996 } 00997 00998 // url handler doesn't support listing 00999 if (!KProtocolManager::supportsListing(url_dir)) 01000 return false; 01001 01002 url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway... 01003 01004 // Remove escapes 01005 QString directory = unescape(url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash)); 01006 01007 url_dir.setPath(directory); 01008 01009 // List files if needed 01010 // 01011 if (!isListedUrl(CTUrl, url_dir.prettyUrl(), url.file())) { 01012 q->stop(); 01013 q->clear(); 01014 01015 setListedUrl(CTUrl, url_dir.prettyUrl(), QString()); 01016 01017 QList<KUrl> url_list; 01018 url_list.append(url_dir); 01019 01020 listUrls(url_list, QString(), false); 01021 01022 pMatch->clear(); 01023 } else if (!q->isRunning()) { 01024 *pMatch = finished(); 01025 } else { 01026 pMatch->clear(); 01027 } 01028 01029 return true; 01030 } 01031 01034 // Directory and URL listing 01035 // 01036 01037 /* 01038 * addMatches 01039 * 01040 * Called to add matches to KCompletion 01041 */ 01042 void KUrlCompletionPrivate::addMatches(const QStringList& matchList) 01043 { 01044 q->insertItems(matchList); 01045 } 01046 01047 /* 01048 * listDirectories 01049 * 01050 * List files starting with 'filter' in the given directories, 01051 * either using DirLister or listURLs() 01052 * 01053 * In either case, addMatches() is called with the listed 01054 * files, and eventually finished() when the listing is done 01055 * 01056 * Returns the match if available, or QString() if 01057 * DirLister timed out or using kio 01058 */ 01059 QString KUrlCompletionPrivate::listDirectories( 01060 const QStringList& dirList, 01061 const QString& filter, 01062 bool only_exe, 01063 bool only_dir, 01064 bool no_hidden, 01065 bool append_slash_to_dir) 01066 { 01067 assert(!q->isRunning()); 01068 01069 if (qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty()) { 01070 01071 //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO"; 01072 01073 // Don't use KIO 01074 01075 if (dirListThread) 01076 dirListThread->requestTermination(); 01077 01078 QStringList dirs; 01079 01080 QStringList::ConstIterator end = dirList.constEnd(); 01081 for (QStringList::ConstIterator it = dirList.constBegin(); 01082 it != end; 01083 ++it) { 01084 KUrl url; 01085 url.setPath(*it); 01086 if (KAuthorized::authorizeUrlAction(QLatin1String("list"), KUrl(), url)) 01087 dirs.append(*it); 01088 } 01089 01090 dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir, 01091 no_hidden, append_slash_to_dir); 01092 dirListThread->start(); 01093 dirListThread->wait(200); 01094 addMatches(dirListThread->matches()); 01095 01096 return finished(); 01097 } 01098 01099 // Use KIO 01100 //kDebug() << "Listing (listDirectories):" << dirList << "with KIO"; 01101 01102 QList<KUrl> url_list; 01103 01104 QStringList::ConstIterator it = dirList.constBegin(); 01105 QStringList::ConstIterator end = dirList.constEnd(); 01106 01107 for (; it != end; ++it) { 01108 url_list.append(KUrl(*it)); 01109 } 01110 01111 listUrls(url_list, filter, only_exe, no_hidden); 01112 // Will call addMatches() and finished() 01113 01114 return QString(); 01115 } 01116 01117 /* 01118 * listURLs 01119 * 01120 * Use KIO to list the given urls 01121 * 01122 * addMatches() is called with the listed files 01123 * finished() is called when the listing is done 01124 */ 01125 void KUrlCompletionPrivate::listUrls( 01126 const QList<KUrl> &urls, 01127 const QString& filter, 01128 bool only_exe, 01129 bool no_hidden) 01130 { 01131 assert(list_urls.isEmpty()); 01132 assert(list_job == 0L); 01133 01134 list_urls = urls; 01135 list_urls_filter = filter; 01136 list_urls_only_exe = only_exe; 01137 list_urls_no_hidden = no_hidden; 01138 01139 //kDebug() << "Listing URLs:" << *urls[0] << ",..."; 01140 01141 // Start it off by calling _k_slotIOFinished 01142 // 01143 // This will start a new list job as long as there 01144 // are urls in d->list_urls 01145 // 01146 _k_slotIOFinished(0); 01147 } 01148 01149 /* 01150 * _k_slotEntries 01151 * 01152 * Receive files listed by KIO and call addMatches() 01153 */ 01154 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries) 01155 { 01156 QStringList matchList; 01157 01158 KIO::UDSEntryList::ConstIterator it = entries.constBegin(); 01159 const KIO::UDSEntryList::ConstIterator end = entries.constEnd(); 01160 01161 QString filter = list_urls_filter; 01162 01163 int filter_len = filter.length(); 01164 01165 // Iterate over all files 01166 // 01167 for (; it != end; ++it) { 01168 const KIO::UDSEntry& entry = *it; 01169 const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL); 01170 01171 QString entry_name; 01172 if (!url.isEmpty()) { 01173 // kDebug() << "url:" << url; 01174 entry_name = KUrl(url).fileName(); 01175 } else { 01176 entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME); 01177 } 01178 01179 // kDebug() << "name:" << name; 01180 01181 if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) && 01182 (list_urls_no_hidden || 01183 entry_name.length() == 1 || 01184 (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) 01185 continue; 01186 01187 const bool isDir = entry.isDir(); 01188 01189 if (mode == KUrlCompletion::DirCompletion && !isDir) 01190 continue; 01191 01192 if (filter_len == 0 || entry_name.left(filter_len) == filter) { 01193 01194 QString toAppend = entry_name; 01195 01196 if (isDir) 01197 toAppend.append(QLatin1Char('/')); 01198 01199 if (!list_urls_only_exe || 01200 (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable 01201 ) { 01202 if (complete_url) { 01203 KUrl url(prepend); 01204 url.addPath(toAppend); 01205 matchList.append(url.prettyUrl()); 01206 } else { 01207 matchList.append(prepend + toAppend); 01208 } 01209 } 01210 } 01211 } 01212 01213 addMatches(matchList); 01214 } 01215 01216 /* 01217 * _k_slotIOFinished 01218 * 01219 * Called when a KIO job is finished. 01220 * 01221 * Start a new list job if there are still urls in 01222 * list_urls, otherwise call finished() 01223 */ 01224 void KUrlCompletionPrivate::_k_slotIOFinished(KJob* job) 01225 { 01226 assert(job == list_job); Q_UNUSED(job) 01227 01228 if (list_urls.isEmpty()) { 01229 01230 list_job = 0L; 01231 01232 finished(); // will call KCompletion::makeCompletion() 01233 01234 } else { 01235 01236 KUrl kurl(list_urls.takeFirst()); 01237 01238 // list_urls.removeAll( kurl ); 01239 01240 // kDebug() << "Start KIO::listDir" << kurl; 01241 01242 list_job = KIO::listDir(kurl, KIO::HideProgressInfo); 01243 list_job->addMetaData("no-auth-prompt", "true"); 01244 01245 assert(list_job); 01246 01247 q->connect(list_job, 01248 SIGNAL(result(KJob*)), 01249 SLOT(_k_slotIOFinished(KJob*))); 01250 01251 q->connect(list_job, 01252 SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), 01253 SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); 01254 } 01255 } 01256 01259 01260 /* 01261 * postProcessMatch, postProcessMatches 01262 * 01263 * Called by KCompletion before emitting match() and matches() 01264 * 01265 * Append '/' to directories for file completion. This is 01266 * done here to avoid stat()'ing a lot of files 01267 */ 01268 void KUrlCompletion::postProcessMatch(QString* pMatch) const 01269 { 01270 // kDebug() << *pMatch; 01271 01272 if (!pMatch->isEmpty()) { 01273 01274 // Add '/' to directories in file completion mode 01275 // unless it has already been done 01276 if (d->last_compl_type == CTFile 01277 && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) { 01278 QString copy; 01279 01280 if (pMatch->startsWith(QLatin1String("file:"))) 01281 copy = KUrl(*pMatch).toLocalFile(); 01282 else 01283 copy = *pMatch; 01284 01285 expandTilde(copy); 01286 expandEnv(copy); 01287 #ifdef Q_WS_WIN 01288 DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16()); 01289 if (dwAttr == INVALID_FILE_ATTRIBUTES) { 01290 kDebug() << "Could not get file attribs ( " 01291 << GetLastError() 01292 << " ) for " 01293 << copy; 01294 } else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) 01295 pMatch->append(QLatin1Char('/')); 01296 #else 01297 if (QDir::isRelativePath(copy)) 01298 copy.prepend(d->cwd + QLatin1Char('/')); 01299 01300 // kDebug() << "stat'ing" << copy; 01301 01302 KDE_struct_stat sbuff; 01303 01304 QByteArray file = QFile::encodeName(copy); 01305 01306 if (KDE_stat(file.data(), &sbuff) == 0) { 01307 if (S_ISDIR(sbuff.st_mode)) 01308 pMatch->append(QLatin1Char('/')); 01309 } else { 01310 kDebug() << "Could not stat file" << copy; 01311 } 01312 #endif 01313 } 01314 } 01315 } 01316 01317 void KUrlCompletion::postProcessMatches(QStringList* /*matches*/) const 01318 { 01319 // Maybe '/' should be added to directories here as in 01320 // postProcessMatch() but it would slow things down 01321 // when there are a lot of matches... 01322 } 01323 01324 void KUrlCompletion::postProcessMatches(KCompletionMatches* /*matches*/) const 01325 { 01326 // Maybe '/' should be added to directories here as in 01327 // postProcessMatch() but it would slow things down 01328 // when there are a lot of matches... 01329 } 01330 01331 void KUrlCompletion::customEvent(QEvent* e) 01332 { 01333 if (e->type() == CompletionMatchEvent::uniqueType()) { 01334 01335 CompletionMatchEvent* matchEvent = static_cast<CompletionMatchEvent*>(e); 01336 01337 matchEvent->completionThread()->wait(); 01338 01339 if (!d->isListedUrl(CTUser)) { 01340 stop(); 01341 clear(); 01342 d->addMatches(matchEvent->completionThread()->matches()); 01343 } else { 01344 d->setListedUrl(CTUser); 01345 } 01346 01347 if (d->userListThread == matchEvent->completionThread()) 01348 d->userListThread = 0; 01349 01350 if (d->dirListThread == matchEvent->completionThread()) 01351 d->dirListThread = 0; 01352 01353 delete matchEvent->completionThread(); 01354 } 01355 } 01356 01357 // static 01358 QString KUrlCompletion::replacedPath(const QString& text, bool replaceHome, bool replaceEnv) 01359 { 01360 if (text.isEmpty()) 01361 return text; 01362 01363 KUrlCompletionPrivate::MyURL url(text, QString()); // no need to replace something of our current cwd 01364 if (!url.kurl().isLocalFile()) 01365 return text; 01366 01367 url.filter(replaceHome, replaceEnv); 01368 return url.dir() + url.file(); 01369 } 01370 01371 01372 QString KUrlCompletion::replacedPath(const QString& text) const 01373 { 01374 return replacedPath(text, d->replace_home, d->replace_env); 01375 } 01376 01379 // Static functions 01380 01381 /* 01382 * expandEnv 01383 * 01384 * Expand environment variables in text. Escaped '$' are ignored. 01385 * Return true if expansion was made. 01386 */ 01387 static bool expandEnv(QString& text) 01388 { 01389 // Find all environment variables beginning with '$' 01390 // 01391 int pos = 0; 01392 01393 bool expanded = false; 01394 01395 while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) { 01396 01397 // Skip escaped '$' 01398 // 01399 if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) { 01400 pos++; 01401 } 01402 // Variable found => expand 01403 // 01404 else { 01405 // Find the end of the variable = next '/' or ' ' 01406 // 01407 int pos2 = text.indexOf(QLatin1Char(' '), pos + 1); 01408 int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1); 01409 01410 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) 01411 pos2 = pos_tmp; 01412 01413 if (pos2 == -1) 01414 pos2 = text.length(); 01415 01416 // Replace if the variable is terminated by '/' or ' ' 01417 // and defined 01418 // 01419 if (pos2 >= 0) { 01420 int len = pos2 - pos; 01421 QString key = text.mid(pos + 1, len - 1); 01422 QString value = 01423 QString::fromLocal8Bit(qgetenv(key.toLocal8Bit())); 01424 01425 if (!value.isEmpty()) { 01426 expanded = true; 01427 text.replace(pos, len, value); 01428 pos = pos + value.length(); 01429 } else { 01430 pos = pos2; 01431 } 01432 } 01433 } 01434 } 01435 01436 return expanded; 01437 } 01438 01439 /* 01440 * expandTilde 01441 * 01442 * Replace "~user" with the users home directory 01443 * Return true if expansion was made. 01444 */ 01445 static bool expandTilde(QString& text) 01446 { 01447 if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) 01448 return false; 01449 01450 bool expanded = false; 01451 01452 // Find the end of the user name = next '/' or ' ' 01453 // 01454 int pos2 = text.indexOf(QLatin1Char(' '), 1); 01455 int pos_tmp = text.indexOf(QLatin1Char('/'), 1); 01456 01457 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) 01458 pos2 = pos_tmp; 01459 01460 if (pos2 == -1) 01461 pos2 = text.length(); 01462 01463 // Replace ~user if the user name is terminated by '/' or ' ' 01464 // 01465 if (pos2 >= 0) { 01466 01467 QString user = text.mid(1, pos2 - 1); 01468 QString dir; 01469 01470 // A single ~ is replaced with $HOME 01471 // 01472 if (user.isEmpty()) { 01473 dir = QDir::homePath(); 01474 } 01475 // ~user is replaced with the dir from passwd 01476 // 01477 else { 01478 struct passwd* pw = ::getpwnam(user.toLocal8Bit()); 01479 01480 if (pw) 01481 dir = QFile::decodeName(pw->pw_dir); 01482 01483 ::endpwent(); 01484 } 01485 01486 if (!dir.isEmpty()) { 01487 expanded = true; 01488 text.replace(0, pos2, dir); 01489 } 01490 } 01491 01492 return expanded; 01493 } 01494 01495 /* 01496 * unescape 01497 * 01498 * Remove escapes and return the result in a new string 01499 * 01500 */ 01501 static QString unescape(const QString& text) 01502 { 01503 QString result; 01504 01505 for (int pos = 0; pos < text.length(); pos++) 01506 if (text.at(pos) != QLatin1Char('\\')) 01507 result.insert(result.length(), text.at(pos)); 01508 01509 return result; 01510 } 01511 01512 #include "kurlcompletion.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:02 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:02 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.