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