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