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