kio Library API Documentation

kurlcompletion.cpp

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