kio Library API Documentation

kurlcompletion.cpp

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